Index: chapterx-frontend/.gitignore
===================================================================
--- chapterx-frontend/.gitignore	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/.gitignore	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -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?
Index: chapterx-frontend/README.md
===================================================================
--- chapterx-frontend/README.md	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/README.md	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,46 @@
+# ChapterX Frontend
+
+A collaborative storytelling platform built with React + TypeScript + Vite + Tailwind CSS.
+
+## Getting Started
+
+```bash
+cd chapterx-frontend
+npm install
+npm run dev
+```
+
+The app runs at http://localhost:5173
+
+## Dev Switcher
+
+There is a floating blue button in the bottom-left corner that opens the **Dev Switcher**. Click it to instantly switch between any of the mock users without needing to log in manually.
+
+## Quick Login Users
+
+| User | Role | Description |
+|------|------|-------------|
+| `admin_alex` | Admin | Full admin access — user management, content moderation, genre management |
+| `elena_writes` | Writer | Has 3 stories (published + draft), chapters, AI suggestions, collaborators |
+| `boris_writer` | Writer | Has 1 published story (Moonlight Promises) |
+| `sara_reader` | Regular | Has reading lists, can browse and comment |
+| `marco_author` | Writer | Editor collaborator on Elena's story |
+
+You can also use the Login page and type any username to log in (no password required in mock mode).
+
+## Backend
+
+The app connects to a .NET 9 API at `https://localhost:7125`. Auth (login/register) uses the real backend when available, and falls back to mock data. All other data (stories, chapters, comments, etc.) uses in-memory mock state.
+
+## Features
+
+- Dark mode only UI with glassmorphism design
+- Story browsing with search, genre filters, and sort
+- Full story reading with chapter navigation and progress bar
+- Writer dashboard with analytics (Recharts), story/chapter CRUD
+- AI Suggestion panel (grammar, style, plot suggestions)
+- Collaborator management with permission levels
+- Reading lists (personal + public community lists)
+- Notifications system
+- Admin panel: user management, content moderation, genre management
+- Role-based access control (guest, regular, writer, admin)
Index: chapterx-frontend/eslint.config.js
===================================================================
--- chapterx-frontend/eslint.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/eslint.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+  globalIgnores(['dist']),
+  {
+    files: ['**/*.{ts,tsx}'],
+    extends: [
+      js.configs.recommended,
+      tseslint.configs.recommended,
+      reactHooks.configs.flat.recommended,
+      reactRefresh.configs.vite,
+    ],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+    },
+  },
+])
Index: chapterx-frontend/index.html
===================================================================
--- chapterx-frontend/index.html	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/index.html	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>chapterx-frontend</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>
Index: chapterx-frontend/package-lock.json
===================================================================
--- chapterx-frontend/package-lock.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/package-lock.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,4267 @@
+{
+  "name": "chapterx-frontend",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "chapterx-frontend",
+      "version": "0.0.0",
+      "dependencies": {
+        "axios": "^1.13.6",
+        "lucide-react": "^0.577.0",
+        "react": "^19.2.4",
+        "react-dom": "^19.2.4",
+        "react-router-dom": "^7.13.1",
+        "recharts": "^3.8.0",
+        "zustand": "^5.0.12"
+      },
+      "devDependencies": {
+        "@eslint/js": "^9.39.4",
+        "@types/node": "^24.12.0",
+        "@types/react": "^19.2.14",
+        "@types/react-dom": "^19.2.3",
+        "@vitejs/plugin-react": "^6.0.1",
+        "autoprefixer": "^10.4.27",
+        "eslint": "^9.39.4",
+        "eslint-plugin-react-hooks": "^7.0.1",
+        "eslint-plugin-react-refresh": "^0.5.2",
+        "globals": "^17.4.0",
+        "postcss": "^8.5.8",
+        "tailwindcss": "^3.4.19",
+        "typescript": "~5.9.3",
+        "typescript-eslint": "^8.57.0",
+        "vite": "^8.0.1"
+      }
+    },
+    "node_modules/@alloc/quick-lru": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+      "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+      "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+      "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-compilation-targets": "^7.28.6",
+        "@babel/helper-module-transforms": "^7.28.6",
+        "@babel/helpers": "^7.28.6",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/traverse": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/remapping": "^2.3.5",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.29.1",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+      "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+      "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/compat-data": "^7.28.6",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-globals": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+      "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+      "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/traverse": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+      "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.28.6",
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "@babel/traverse": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+      "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.29.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+      "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+      "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.28.6",
+        "@babel/parser": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+      "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-globals": "^7.28.0",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.29.0",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@emnapi/core": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
+      "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "@emnapi/wasi-threads": "1.2.0",
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/runtime": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+      "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/wasi-threads": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
+      "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.9.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+      "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.2",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+      "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.21.2",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+      "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/object-schema": "^2.1.7",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.5"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/config-helpers": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+      "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/core": "^0.17.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/core": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+      "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "3.3.5",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+      "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.14.0",
+        "debug": "^4.3.2",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.1",
+        "minimatch": "^3.1.5",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "9.39.4",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+      "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "2.1.7",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+      "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+      "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/core": "^0.17.0",
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node": {
+      "version": "0.16.7",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+      "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+      "dev": true,
+      "dependencies": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.4.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+      "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@napi-rs/wasm-runtime": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
+      "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "@emnapi/core": "^1.7.1",
+        "@emnapi/runtime": "^1.7.1",
+        "@tybys/wasm-util": "^0.10.1"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Brooooooklyn"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@oxc-project/types": {
+      "version": "0.120.0",
+      "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz",
+      "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/Boshen"
+      }
+    },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "2.11.2",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
+      "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
+      "dependencies": {
+        "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
+        "immer": "^11.0.0",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@reduxjs/toolkit/node_modules/immer": {
+      "version": "11.1.4",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
+      "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/immer"
+      }
+    },
+    "node_modules/@rolldown/binding-android-arm64": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz",
+      "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-arm64": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz",
+      "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-x64": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz",
+      "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-freebsd-x64": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz",
+      "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz",
+      "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-gnu": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz",
+      "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-musl": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz",
+      "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz",
+      "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-s390x-gnu": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz",
+      "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-gnu": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz",
+      "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-musl": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz",
+      "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-openharmony-arm64": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz",
+      "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-wasm32-wasi": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz",
+      "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==",
+      "cpu": [
+        "wasm32"
+      ],
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "@napi-rs/wasm-runtime": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-arm64-msvc": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz",
+      "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-x64-msvc": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz",
+      "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.7",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+      "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+      "dev": true
+    },
+    "node_modules/@standard-schema/spec": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+      "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="
+    },
+    "node_modules/@standard-schema/utils": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+      "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
+    },
+    "node_modules/@tybys/wasm-util": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+      "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@types/d3-array": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+      "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="
+    },
+    "node_modules/@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+    },
+    "node_modules/@types/d3-ease": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
+    },
+    "node_modules/@types/d3-interpolate": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+      "dependencies": {
+        "@types/d3-color": "*"
+      }
+    },
+    "node_modules/@types/d3-path": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+      "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
+    },
+    "node_modules/@types/d3-scale": {
+      "version": "4.0.9",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+      "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+      "dependencies": {
+        "@types/d3-time": "*"
+      }
+    },
+    "node_modules/@types/d3-shape": {
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+      "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+      "dependencies": {
+        "@types/d3-path": "*"
+      }
+    },
+    "node_modules/@types/d3-time": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+      "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
+    },
+    "node_modules/@types/d3-timer": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "24.12.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz",
+      "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~7.16.0"
+      }
+    },
+    "node_modules/@types/react": {
+      "version": "19.2.14",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+      "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+      "devOptional": true,
+      "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,
+      "peerDependencies": {
+        "@types/react": "^19.2.0"
+      }
+    },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
+    },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz",
+      "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.12.2",
+        "@typescript-eslint/scope-manager": "8.57.1",
+        "@typescript-eslint/type-utils": "8.57.1",
+        "@typescript-eslint/utils": "8.57.1",
+        "@typescript-eslint/visitor-keys": "8.57.1",
+        "ignore": "^7.0.5",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^2.4.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.57.1",
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+      "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz",
+      "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.57.1",
+        "@typescript-eslint/types": "8.57.1",
+        "@typescript-eslint/typescript-estree": "8.57.1",
+        "@typescript-eslint/visitor-keys": "8.57.1",
+        "debug": "^4.4.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/project-service": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz",
+      "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/tsconfig-utils": "^8.57.1",
+        "@typescript-eslint/types": "^8.57.1",
+        "debug": "^4.4.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz",
+      "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.57.1",
+        "@typescript-eslint/visitor-keys": "8.57.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/tsconfig-utils": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz",
+      "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz",
+      "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.57.1",
+        "@typescript-eslint/typescript-estree": "8.57.1",
+        "@typescript-eslint/utils": "8.57.1",
+        "debug": "^4.4.3",
+        "ts-api-utils": "^2.4.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz",
+      "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz",
+      "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/project-service": "8.57.1",
+        "@typescript-eslint/tsconfig-utils": "8.57.1",
+        "@typescript-eslint/types": "8.57.1",
+        "@typescript-eslint/visitor-keys": "8.57.1",
+        "debug": "^4.4.3",
+        "minimatch": "^10.2.2",
+        "semver": "^7.7.3",
+        "tinyglobby": "^0.2.15",
+        "ts-api-utils": "^2.4.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+      "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+      "dev": true,
+      "engines": {
+        "node": "18 || 20 || >=22"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+      "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^4.0.2"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+      "version": "10.2.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+      "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^5.0.2"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz",
+      "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.9.1",
+        "@typescript-eslint/scope-manager": "8.57.1",
+        "@typescript-eslint/types": "8.57.1",
+        "@typescript-eslint/typescript-estree": "8.57.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz",
+      "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.57.1",
+        "eslint-visitor-keys": "^5.0.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+      "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+      "dev": true,
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+      "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+      "dev": true,
+      "dependencies": {
+        "@rolldown/pluginutils": "1.0.0-rc.7"
+      },
+      "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/acorn": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+      "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.14.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+      "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "dev": true
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/anymatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/arg": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+      "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+      "dev": true
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/autoprefixer": {
+      "version": "10.4.27",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
+      "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "browserslist": "^4.28.1",
+        "caniuse-lite": "^1.0.30001774",
+        "fraction.js": "^5.3.4",
+        "picocolors": "^1.1.1",
+        "postcss-value-parser": "^4.2.0"
+      },
+      "bin": {
+        "autoprefixer": "bin/autoprefixer"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/axios": {
+      "version": "1.13.6",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
+      "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
+      "dependencies": {
+        "follow-redirects": "^1.15.11",
+        "form-data": "^4.0.5",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.10.9",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz",
+      "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==",
+      "dev": true,
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "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==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/camelcase-css": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+      "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001780",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
+      "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ]
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "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==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/commander": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true
+    },
+    "node_modules/cookie": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+      "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true,
+      "bin": {
+        "cssesc": "bin/cssesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "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==",
+      "devOptional": true
+    },
+    "node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-format": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+      "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "dependencies": {
+        "d3-time": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/decimal.js-light": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": 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==",
+      "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,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/didyoumean": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+      "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+      "dev": true
+    },
+    "node_modules/dlv": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+      "dev": true
+    },
+    "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==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.321",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
+      "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
+      "dev": true
+    },
+    "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==",
+      "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==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "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==",
+      "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/es-toolkit": {
+      "version": "1.45.1",
+      "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
+      "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "9.39.4",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+      "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.8.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.21.2",
+        "@eslint/config-helpers": "^0.4.2",
+        "@eslint/core": "^0.17.0",
+        "@eslint/eslintrc": "^3.3.5",
+        "@eslint/js": "9.39.4",
+        "@eslint/plugin-kit": "^0.4.1",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.2",
+        "@types/estree": "^1.0.6",
+        "ajv": "^6.14.0",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.6",
+        "debug": "^4.3.2",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^8.4.0",
+        "eslint-visitor-keys": "^4.2.1",
+        "espree": "^10.4.0",
+        "esquery": "^1.5.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.5",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "jiti": "*"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-react-hooks": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+      "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/core": "^7.24.4",
+        "@babel/parser": "^7.24.4",
+        "hermes-parser": "^0.25.1",
+        "zod": "^3.25.0 || ^4.0.0",
+        "zod-validation-error": "^3.5.0 || ^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-react-refresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+      "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+      "dev": true,
+      "peerDependencies": {
+        "eslint": "^9 || ^10"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "8.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+      "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/espree": {
+      "version": "10.4.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+      "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.15.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^4.2.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+      "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/eventemitter3": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+      "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.8"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-glob/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+      "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.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,
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+      "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fraction.js": {
+      "version": "5.3.4",
+      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+      "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+      "dev": true,
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/rawify"
+      }
+    },
+    "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,
+      "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==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "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==",
+      "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==",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/globals": {
+      "version": "17.4.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
+      "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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==",
+      "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==",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hermes-estree": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+      "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+      "dev": true
+    },
+    "node_modules/hermes-parser": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+      "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+      "dev": true,
+      "dependencies": {
+        "hermes-estree": "0.25.1"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/immer": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
+      "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/immer"
+      }
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dev": true,
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/jiti": {
+      "version": "1.21.7",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+      "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+      "dev": true,
+      "bin": {
+        "jiti": "bin/jiti.js"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+      "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "dev": true,
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/lightningcss": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+      "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+      "dev": true,
+      "dependencies": {
+        "detect-libc": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "lightningcss-android-arm64": "1.32.0",
+        "lightningcss-darwin-arm64": "1.32.0",
+        "lightningcss-darwin-x64": "1.32.0",
+        "lightningcss-freebsd-x64": "1.32.0",
+        "lightningcss-linux-arm-gnueabihf": "1.32.0",
+        "lightningcss-linux-arm64-gnu": "1.32.0",
+        "lightningcss-linux-arm64-musl": "1.32.0",
+        "lightningcss-linux-x64-gnu": "1.32.0",
+        "lightningcss-linux-x64-musl": "1.32.0",
+        "lightningcss-win32-arm64-msvc": "1.32.0",
+        "lightningcss-win32-x64-msvc": "1.32.0"
+      }
+    },
+    "node_modules/lightningcss-android-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+      "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+      "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+      "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-freebsd-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+      "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+      "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+      "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+      "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+      "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+      "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+      "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "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.32.0",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+      "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lilconfig": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antonk52"
+      }
+    },
+    "node_modules/lines-and-columns": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+      "dev": true
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/lucide-react": {
+      "version": "0.577.0",
+      "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
+      "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
+      "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==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/micromatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "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==",
+      "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==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+      "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "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==",
+      "dev": true
+    },
+    "node_modules/mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "dev": true,
+      "dependencies": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.36",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+      "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+      "dev": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": 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
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/pirates": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+      "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.8",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+      "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+      "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"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-import": {
+      "version": "15.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+      "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+      "dev": true,
+      "dependencies": {
+        "postcss-value-parser": "^4.0.0",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
+    "node_modules/postcss-js": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+      "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "camelcase-css": "^2.0.1"
+      },
+      "engines": {
+        "node": "^12 || ^14 || >= 16"
+      },
+      "peerDependencies": {
+        "postcss": "^8.4.21"
+      }
+    },
+    "node_modules/postcss-load-config": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+      "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "lilconfig": "^3.1.1"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "peerDependencies": {
+        "jiti": ">=1.21.0",
+        "postcss": ">=8.0.9",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        },
+        "postcss": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/postcss-nested": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+      "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "postcss-selector-parser": "^6.1.1"
+      },
+      "engines": {
+        "node": ">=12.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.2.14"
+      }
+    },
+    "node_modules/postcss-selector-parser": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+      "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+      "dev": true,
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postcss-value-parser": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+      "dev": true
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/react": {
+      "version": "19.2.4",
+      "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+      "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "19.2.4",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+      "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+      "dependencies": {
+        "scheduler": "^0.27.0"
+      },
+      "peerDependencies": {
+        "react": "^19.2.4"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "19.2.4",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+      "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
+      "peer": true
+    },
+    "node_modules/react-redux": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+      "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+      "dependencies": {
+        "@types/use-sync-external-store": "^0.0.6",
+        "use-sync-external-store": "^1.4.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^18.2.25 || ^19",
+        "react": "^18.0 || ^19",
+        "redux": "^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-router": {
+      "version": "7.13.1",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
+      "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
+      "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.13.1",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
+      "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
+      "dependencies": {
+        "react-router": "7.13.1"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      }
+    },
+    "node_modules/read-cache": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+      "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+      "dev": true,
+      "dependencies": {
+        "pify": "^2.3.0"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/readdirp/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/recharts": {
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz",
+      "integrity": "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==",
+      "dependencies": {
+        "@reduxjs/toolkit": "^1.9.0 || 2.x.x",
+        "clsx": "^2.1.1",
+        "decimal.js-light": "^2.5.1",
+        "es-toolkit": "^1.39.3",
+        "eventemitter3": "^5.0.1",
+        "immer": "^10.1.1",
+        "react-redux": "8.x.x || 9.x.x",
+        "reselect": "5.1.1",
+        "tiny-invariant": "^1.3.3",
+        "use-sync-external-store": "^1.2.2",
+        "victory-vendor": "^37.0.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/redux": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+    },
+    "node_modules/redux-thunk": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+      "peerDependencies": {
+        "redux": "^5.0.0"
+      }
+    },
+    "node_modules/reselect": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
+    },
+    "node_modules/resolve": {
+      "version": "1.22.11",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+      "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.16.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rolldown": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz",
+      "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==",
+      "dev": true,
+      "dependencies": {
+        "@oxc-project/types": "=0.120.0",
+        "@rolldown/pluginutils": "1.0.0-rc.10"
+      },
+      "bin": {
+        "rolldown": "bin/cli.mjs"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "optionalDependencies": {
+        "@rolldown/binding-android-arm64": "1.0.0-rc.10",
+        "@rolldown/binding-darwin-arm64": "1.0.0-rc.10",
+        "@rolldown/binding-darwin-x64": "1.0.0-rc.10",
+        "@rolldown/binding-freebsd-x64": "1.0.0-rc.10",
+        "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10",
+        "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10",
+        "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10",
+        "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10",
+        "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10",
+        "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10",
+        "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10",
+        "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10",
+        "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10",
+        "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10",
+        "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10"
+      }
+    },
+    "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz",
+      "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==",
+      "dev": true
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "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=="
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "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=="
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "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,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/sucrase": {
+      "version": "3.35.1",
+      "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+      "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.2",
+        "commander": "^4.0.0",
+        "lines-and-columns": "^1.1.6",
+        "mz": "^2.7.0",
+        "pirates": "^4.0.1",
+        "tinyglobby": "^0.2.11",
+        "ts-interface-checker": "^0.1.9"
+      },
+      "bin": {
+        "sucrase": "bin/sucrase",
+        "sucrase-node": "bin/sucrase-node"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/tailwindcss": {
+      "version": "3.4.19",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+      "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+      "dev": true,
+      "dependencies": {
+        "@alloc/quick-lru": "^5.2.0",
+        "arg": "^5.0.2",
+        "chokidar": "^3.6.0",
+        "didyoumean": "^1.2.2",
+        "dlv": "^1.1.3",
+        "fast-glob": "^3.3.2",
+        "glob-parent": "^6.0.2",
+        "is-glob": "^4.0.3",
+        "jiti": "^1.21.7",
+        "lilconfig": "^3.1.3",
+        "micromatch": "^4.0.8",
+        "normalize-path": "^3.0.0",
+        "object-hash": "^3.0.0",
+        "picocolors": "^1.1.1",
+        "postcss": "^8.4.47",
+        "postcss-import": "^15.1.0",
+        "postcss-js": "^4.0.1",
+        "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+        "postcss-nested": "^6.2.0",
+        "postcss-selector-parser": "^6.1.2",
+        "resolve": "^1.22.8",
+        "sucrase": "^3.35.0"
+      },
+      "bin": {
+        "tailwind": "lib/cli.js",
+        "tailwindcss": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/thenify": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "dev": true,
+      "dependencies": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "node_modules/thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "dev": true,
+      "dependencies": {
+        "thenify": ">= 3.1.0 < 4"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/tiny-invariant": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+      "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/ts-api-utils": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+      "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.12"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4"
+      }
+    },
+    "node_modules/ts-interface-checker": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+      "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+      "dev": true
+    },
+    "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,
+      "optional": true
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/typescript-eslint": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz",
+      "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "8.57.1",
+        "@typescript-eslint/parser": "8.57.1",
+        "@typescript-eslint/typescript-estree": "8.57.1",
+        "@typescript-eslint/utils": "8.57.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "7.16.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+      "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+      "dev": true
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/use-sync-external-store": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+      "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
+    "node_modules/victory-vendor": {
+      "version": "37.3.6",
+      "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+      "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+      "dependencies": {
+        "@types/d3-array": "^3.0.3",
+        "@types/d3-ease": "^3.0.0",
+        "@types/d3-interpolate": "^3.0.1",
+        "@types/d3-scale": "^4.0.2",
+        "@types/d3-shape": "^3.1.0",
+        "@types/d3-time": "^3.0.0",
+        "@types/d3-timer": "^3.0.0",
+        "d3-array": "^3.1.6",
+        "d3-ease": "^3.0.1",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "d3-shape": "^3.1.0",
+        "d3-time": "^3.0.0",
+        "d3-timer": "^3.0.1"
+      }
+    },
+    "node_modules/vite": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz",
+      "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==",
+      "dev": true,
+      "dependencies": {
+        "lightningcss": "^1.32.0",
+        "picomatch": "^4.0.3",
+        "postcss": "^8.5.8",
+        "rolldown": "1.0.0-rc.10",
+        "tinyglobby": "^0.2.15"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "@vitejs/devtools": "^0.1.0",
+        "esbuild": "^0.27.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
+        }
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/zod": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+      "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    },
+    "node_modules/zod-validation-error": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+      "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "zod": "^3.25.0 || ^4.0.0"
+      }
+    },
+    "node_modules/zustand": {
+      "version": "5.0.12",
+      "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
+      "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
+      "engines": {
+        "node": ">=12.20.0"
+      },
+      "peerDependencies": {
+        "@types/react": ">=18.0.0",
+        "immer": ">=9.0.6",
+        "react": ">=18.0.0",
+        "use-sync-external-store": ">=1.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "immer": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "use-sync-external-store": {
+          "optional": true
+        }
+      }
+    }
+  }
+}
Index: chapterx-frontend/package.json
===================================================================
--- chapterx-frontend/package.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/package.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,38 @@
+{
+  "name": "chapterx-frontend",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc -b && vite build",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "axios": "^1.13.6",
+    "lucide-react": "^0.577.0",
+    "react": "^19.2.4",
+    "react-dom": "^19.2.4",
+    "react-router-dom": "^7.13.1",
+    "recharts": "^3.8.0",
+    "zustand": "^5.0.12"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.39.4",
+    "@types/node": "^24.12.0",
+    "@types/react": "^19.2.14",
+    "@types/react-dom": "^19.2.3",
+    "@vitejs/plugin-react": "^6.0.1",
+    "autoprefixer": "^10.4.27",
+    "eslint": "^9.39.4",
+    "eslint-plugin-react-hooks": "^7.0.1",
+    "eslint-plugin-react-refresh": "^0.5.2",
+    "globals": "^17.4.0",
+    "postcss": "^8.5.8",
+    "tailwindcss": "^3.4.19",
+    "typescript": "~5.9.3",
+    "typescript-eslint": "^8.57.0",
+    "vite": "^8.0.1"
+  }
+}
Index: chapterx-frontend/postcss.config.js
===================================================================
--- chapterx-frontend/postcss.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/postcss.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}
Index: chapterx-frontend/public/favicon.svg
===================================================================
--- chapterx-frontend/public/favicon.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/public/favicon.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
Index: chapterx-frontend/public/icons.svg
===================================================================
--- chapterx-frontend/public/icons.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/public/icons.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <symbol id="bluesky-icon" viewBox="0 0 16 17">
+    <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
+    <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
+  </symbol>
+  <symbol id="discord-icon" viewBox="0 0 20 19">
+    <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
+  </symbol>
+  <symbol id="documentation-icon" viewBox="0 0 21 20">
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
+  </symbol>
+  <symbol id="github-icon" viewBox="0 0 19 19">
+    <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
+  </symbol>
+  <symbol id="social-icon" viewBox="0 0 20 20">
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
+  </symbol>
+  <symbol id="x-icon" viewBox="0 0 19 19">
+    <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
+  </symbol>
+</svg>
Index: chapterx-frontend/src/App.css
===================================================================
--- chapterx-frontend/src/App.css	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/App.css	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,1 @@
+/* ChapterX App CSS - Tailwind handles all styling */
Index: chapterx-frontend/src/App.tsx
===================================================================
--- chapterx-frontend/src/App.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/App.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,186 @@
+import React, { Suspense, lazy, ReactNode } from 'react'
+import { Routes, Route, Navigate } from 'react-router-dom'
+import { useAuthStore } from './store/authStore'
+import { useUIStore } from './store/uiStore'
+import { UserRole } from './types'
+import { Navbar } from './components/layout/Navbar'
+import { Footer } from './components/layout/Footer'
+import { ToastContainer } from './components/ui/Toast'
+import { DevSwitcher } from './components/DevSwitcher'
+import { Spinner } from './components/ui/Spinner'
+
+// Lazy imports
+const LandingPage = lazy(() => import('./pages/LandingPage').then(m => ({ default: m.LandingPage })))
+const LoginPage = lazy(() => import('./pages/auth/LoginPage').then(m => ({ default: m.LoginPage })))
+const RegisterPage = lazy(() => import('./pages/auth/RegisterPage').then(m => ({ default: m.RegisterPage })))
+const BrowsePage = lazy(() => import('./pages/browse/BrowsePage').then(m => ({ default: m.BrowsePage })))
+const GenrePage = lazy(() => import('./pages/browse/GenrePage').then(m => ({ default: m.GenrePage })))
+const GenresListPage = lazy(() => import('./pages/browse/GenrePage').then(m => ({ default: m.GenresListPage })))
+const StoryDetailPage = lazy(() => import('./pages/story/StoryDetailPage').then(m => ({ default: m.StoryDetailPage })))
+const ChapterReadPage = lazy(() => import('./pages/story/ChapterReadPage').then(m => ({ default: m.ChapterReadPage })))
+const WriterDashboard = lazy(() => import('./pages/writer/WriterDashboard').then(m => ({ default: m.WriterDashboard })))
+const CreateStoryPage = lazy(() => import('./pages/writer/CreateStoryPage').then(m => ({ default: m.CreateStoryPage })))
+const EditStoryPage = lazy(() => import('./pages/writer/EditStoryPage').then(m => ({ default: m.EditStoryPage })))
+const CreateChapterPage = lazy(() => import('./pages/writer/CreateChapterPage').then(m => ({ default: m.CreateChapterPage })))
+const EditChapterPage = lazy(() => import('./pages/writer/EditChapterPage').then(m => ({ default: m.EditChapterPage })))
+const ReadingListPage = lazy(() => import('./pages/reading-list/ReadingListPage').then(m => ({ default: m.ReadingListPage })))
+const CommunityListsPage = lazy(() => import('./pages/reading-list/CommunityListsPage').then(m => ({ default: m.CommunityListsPage })))
+const ProfilePage = lazy(() => import('./pages/profile/ProfilePage').then(m => ({ default: m.ProfilePage })))
+const AdminDashboard = lazy(() => import('./pages/admin/AdminDashboard').then(m => ({ default: m.AdminDashboard })))
+const AdminUsersPage = lazy(() => import('./pages/admin/AdminUsersPage').then(m => ({ default: m.AdminUsersPage })))
+const AdminContentPage = lazy(() => import('./pages/admin/AdminContentPage').then(m => ({ default: m.AdminContentPage })))
+const AdminGenresPage = lazy(() => import('./pages/admin/AdminGenresPage').then(m => ({ default: m.AdminGenresPage })))
+const NotificationsPage = lazy(() => import('./pages/NotificationsPage').then(m => ({ default: m.NotificationsPage })))
+
+const SuspenseFallback = () => (
+  <div className="flex items-center justify-center min-h-[50vh]">
+    <Spinner size="lg" />
+  </div>
+)
+
+// Protected route component
+const ProtectedRoute = ({
+  children,
+  requiredRole,
+}: {
+  children: ReactNode
+  requiredRole?: UserRole | UserRole[]
+}) => {
+  const { currentUser } = useAuthStore()
+  const { addToast } = useUIStore()
+
+  if (!currentUser) {
+    addToast('Please sign in to access this page.', 'warning')
+    return <Navigate to="/login" replace />
+  }
+
+  if (requiredRole) {
+    const roles = Array.isArray(requiredRole) ? requiredRole : [requiredRole]
+    if (!roles.includes(currentUser.role)) {
+      addToast('You do not have permission to access this page.', 'error')
+      return <Navigate to="/" replace />
+    }
+  }
+
+  return <>{children}</>
+}
+
+function App() {
+  return (
+    <div className="flex flex-col min-h-screen">
+      <Navbar />
+      <main className="flex-1">
+        <Suspense fallback={<SuspenseFallback />}>
+          <Routes>
+            {/* Public */}
+            <Route path="/" element={<LandingPage />} />
+            <Route path="/login" element={<LoginPage />} />
+            <Route path="/register" element={<RegisterPage />} />
+            <Route path="/browse" element={<BrowsePage />} />
+            <Route path="/genres" element={<GenresListPage />} />
+            <Route path="/genres/:genre" element={<GenrePage />} />
+            <Route path="/story/:id" element={<StoryDetailPage />} />
+            <Route path="/story/:storyId/chapter/:chapterId" element={<ChapterReadPage />} />
+            <Route path="/community-lists" element={<CommunityListsPage />} />
+            <Route path="/profile/:username" element={<ProfilePage />} />
+            <Route path="/notifications" element={<NotificationsPage />} />
+
+            {/* Authenticated */}
+            <Route
+              path="/reading-lists"
+              element={
+                <ProtectedRoute>
+                  <ReadingListPage />
+                </ProtectedRoute>
+              }
+            />
+
+            {/* Writer routes */}
+            <Route
+              path="/writer"
+              element={
+                <ProtectedRoute requiredRole={['writer', 'admin']}>
+                  <WriterDashboard />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/writer/create-story"
+              element={
+                <ProtectedRoute requiredRole={['writer', 'admin']}>
+                  <CreateStoryPage />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/writer/edit-story/:id"
+              element={
+                <ProtectedRoute requiredRole={['writer', 'admin']}>
+                  <EditStoryPage />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/writer/create-chapter/:storyId"
+              element={
+                <ProtectedRoute requiredRole={['writer', 'admin']}>
+                  <CreateChapterPage />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/writer/edit-chapter/:chapterId"
+              element={
+                <ProtectedRoute requiredRole={['writer', 'admin']}>
+                  <EditChapterPage />
+                </ProtectedRoute>
+              }
+            />
+
+            {/* Admin routes */}
+            <Route
+              path="/admin"
+              element={
+                <ProtectedRoute requiredRole="admin">
+                  <AdminDashboard />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/admin/users"
+              element={
+                <ProtectedRoute requiredRole="admin">
+                  <AdminUsersPage />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/admin/content"
+              element={
+                <ProtectedRoute requiredRole="admin">
+                  <AdminContentPage />
+                </ProtectedRoute>
+              }
+            />
+            <Route
+              path="/admin/genres"
+              element={
+                <ProtectedRoute requiredRole="admin">
+                  <AdminGenresPage />
+                </ProtectedRoute>
+              }
+            />
+
+            {/* 404 */}
+            <Route path="*" element={<Navigate to="/" replace />} />
+          </Routes>
+        </Suspense>
+      </main>
+      <Footer />
+      <ToastContainer />
+      <DevSwitcher />
+    </div>
+  )
+}
+
+export default App
Index: chapterx-frontend/src/assets/react.svg
===================================================================
--- chapterx-frontend/src/assets/react.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/assets/react.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
Index: chapterx-frontend/src/assets/vite.svg
===================================================================
--- chapterx-frontend/src/assets/vite.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/assets/vite.svg	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="29.961" y="-17.13" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-13.43" y="-22.082" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="34.321" y="-37.644" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="38.847" y="-10.552" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="22.45" y="-1.645" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="32.919" y="11.36" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter></defs></svg>
Index: chapterx-frontend/src/components/DevSwitcher.tsx
===================================================================
--- chapterx-frontend/src/components/DevSwitcher.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/DevSwitcher.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,82 @@
+import React, { useState } from 'react'
+import { Code2, X, User } from 'lucide-react'
+import { useAuthStore } from '../store/authStore'
+import { Avatar } from './ui/Avatar'
+import { RoleBadge } from './ui/Badge'
+import { useUIStore } from '../store/uiStore'
+
+const DEV_USERS = [
+  { user_id: 0, username: 'Guest', name: 'Guest', role: 'guest' as const },
+  { user_id: 1, username: 'admin_alex', name: 'Alex Admin', role: 'admin' as const },
+  { user_id: 2, username: 'elena_writes', name: 'Elena Dimitrova', role: 'writer' as const },
+  { user_id: 3, username: 'marco_author', name: 'Marco Rossi', role: 'writer' as const },
+  { user_id: 4, username: 'sara_reader', name: 'Sara Petkovska', role: 'regular' as const },
+  { user_id: 9, username: 'boris_writer', name: 'Boris Nikolov', role: 'writer' as const },
+]
+
+export const DevSwitcher: React.FC = () => {
+  const [open, setOpen] = useState(false)
+  const { currentUser, switchUser } = useAuthStore()
+  const { addToast } = useUIStore()
+
+  const handleSwitch = (userId: number, name: string, role: string) => {
+    switchUser(userId)
+    addToast(`Switched to ${name} (${role})`)
+    setOpen(false)
+  }
+
+  return (
+    <div className="fixed bottom-6 left-6 z-[90]">
+      {open && (
+        <div className="absolute bottom-12 left-0 w-64 bg-slate-900 border border-indigo-500/30 rounded-2xl shadow-2xl overflow-hidden">
+          <div className="flex items-center justify-between px-4 py-3 border-b border-slate-700 bg-indigo-500/10">
+            <div className="flex items-center gap-2">
+              <Code2 size={14} className="text-indigo-400" />
+              <span className="text-indigo-300 text-xs font-semibold uppercase tracking-wide">Dev Switcher</span>
+            </div>
+            <button onClick={() => setOpen(false)} className="text-slate-400 hover:text-white">
+              <X size={14} />
+            </button>
+          </div>
+          <div className="p-2">
+            {DEV_USERS.map(u => (
+              <button
+                key={u.user_id}
+                onClick={() => handleSwitch(u.user_id, u.name, u.role)}
+                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${
+                  (currentUser?.user_id === u.user_id) || (!currentUser && u.user_id === 0)
+                    ? 'bg-indigo-600/20 border border-indigo-500/40'
+                    : 'hover:bg-slate-800 border border-transparent'
+                }`}
+              >
+                {u.user_id === 0 ? (
+                  <div className="w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center">
+                    <User size={14} className="text-slate-400" />
+                  </div>
+                ) : (
+                  <Avatar name={u.name} size="sm" />
+                )}
+                <div className="flex-1 text-left">
+                  <p className="text-white text-sm font-medium">{u.name}</p>
+                  <p className="text-slate-500 text-xs">@{u.username}</p>
+                </div>
+                <RoleBadge role={u.role} />
+              </button>
+            ))}
+          </div>
+          <div className="px-4 py-2 border-t border-slate-700 text-center">
+            <p className="text-slate-600 text-xs">Development only</p>
+          </div>
+        </div>
+      )}
+
+      <button
+        onClick={() => setOpen(!open)}
+        className="w-10 h-10 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white flex items-center justify-center shadow-lg hover:shadow-indigo-500/25 transition-all hover:scale-110 border border-indigo-500"
+        title="Dev User Switcher"
+      >
+        <Code2 size={16} />
+      </button>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/admin/ContentModerationTable.tsx
===================================================================
--- chapterx-frontend/src/components/admin/ContentModerationTable.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/admin/ContentModerationTable.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,124 @@
+import React, { useState } from 'react'
+import { Trash2, Eye, AlertTriangle } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { useUIStore } from '../../store/uiStore'
+import { Story } from '../../types'
+import { StatusBadge, GenreBadge } from '../ui/Badge'
+import { Button } from '../ui/Button'
+import { Modal } from '../ui/Modal'
+import { useNavigate } from 'react-router-dom'
+
+export const ContentModerationTable: React.FC = () => {
+  const { stories, deleteStory } = useStoryStore()
+  const { addNotification } = useNotificationStore()
+  const { addToast } = useUIStore()
+  const navigate = useNavigate()
+  const [confirmStory, setConfirmStory] = useState<Story | null>(null)
+
+  const handleDelete = (story: Story) => {
+    deleteStory(story.story_id)
+    addNotification({
+      user_id: story.user_id,
+      type: 'system',
+      title: 'Story Removed',
+      message: `Your story "${story.title}" has been removed by an administrator.`,
+    })
+    addToast(`"${story.title}" removed from platform`, 'info')
+    setConfirmStory(null)
+  }
+
+  const formatDate = (str: string) =>
+    new Date(str).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+
+  return (
+    <div>
+      <div className="overflow-x-auto rounded-xl border border-slate-700">
+        <table className="w-full text-sm">
+          <thead>
+            <tr className="border-b border-slate-700 bg-slate-800">
+              <th className="text-left px-4 py-3 text-slate-400 font-medium">Story</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium hidden md:table-cell">Author</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium hidden lg:table-cell">Genres</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium">Status</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium hidden sm:table-cell">Published</th>
+              <th className="text-right px-4 py-3 text-slate-400 font-medium">Actions</th>
+            </tr>
+          </thead>
+          <tbody className="divide-y divide-slate-800">
+            {stories.map(story => (
+              <tr key={story.story_id} className="hover:bg-slate-800/50 transition-colors">
+                <td className="px-4 py-3">
+                  <div>
+                    <p className="text-white font-medium line-clamp-1">{story.title}</p>
+                    {story.mature_content && (
+                      <span className="text-xs text-rose-400">18+</span>
+                    )}
+                  </div>
+                </td>
+                <td className="px-4 py-3 text-slate-400 hidden md:table-cell">@{story.author_username}</td>
+                <td className="px-4 py-3 hidden lg:table-cell">
+                  <div className="flex flex-wrap gap-1">
+                    {story.genres.slice(0, 2).map(g => <GenreBadge key={g} genre={g} />)}
+                  </div>
+                </td>
+                <td className="px-4 py-3"><StatusBadge status={story.status} /></td>
+                <td className="px-4 py-3 text-slate-500 hidden sm:table-cell">{formatDate(story.created_at)}</td>
+                <td className="px-4 py-3">
+                  <div className="flex items-center justify-end gap-2">
+                    <button
+                      onClick={() => navigate(`/story/${story.story_id}`)}
+                      className="text-slate-400 hover:text-white transition-colors p-1"
+                    >
+                      <Eye size={14} />
+                    </button>
+                    <button
+                      onClick={() => setConfirmStory(story)}
+                      className="text-slate-400 hover:text-rose-400 transition-colors p-1"
+                    >
+                      <Trash2 size={14} />
+                    </button>
+                  </div>
+                </td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+        {stories.length === 0 && (
+          <div className="text-center py-10 text-slate-500">No stories to moderate.</div>
+        )}
+      </div>
+
+      {/* Confirm modal */}
+      <Modal
+        isOpen={!!confirmStory}
+        onClose={() => setConfirmStory(null)}
+        title="Remove Story"
+        size="sm"
+      >
+        {confirmStory && (
+          <div className="space-y-4">
+            <div className="flex items-start gap-3 p-3 bg-rose-500/10 rounded-xl border border-rose-500/20">
+              <AlertTriangle size={18} className="text-rose-400 flex-shrink-0 mt-0.5" />
+              <div>
+                <p className="text-white font-medium text-sm">"{confirmStory.title}"</p>
+                <p className="text-slate-400 text-xs mt-1">
+                  This will permanently remove the story and notify the author. This action cannot be undone.
+                </p>
+              </div>
+            </div>
+            <div className="flex gap-3">
+              <Button variant="secondary" className="flex-1" onClick={() => setConfirmStory(null)}>
+                Cancel
+              </Button>
+              <Button variant="danger" className="flex-1" onClick={() => handleDelete(confirmStory)}>
+                <Trash2 size={14} />
+                Remove Story
+              </Button>
+            </div>
+          </div>
+        )}
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/admin/PlatformStats.tsx
===================================================================
--- chapterx-frontend/src/components/admin/PlatformStats.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/admin/PlatformStats.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,37 @@
+import React from 'react'
+import { Users, BookOpen, FileText, Heart, Eye, MessageCircle } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+
+export const PlatformStats: React.FC = () => {
+  const { allUsers } = useAuthStore()
+  const { stories, comments } = useStoryStore()
+
+  const totalViews = stories.reduce((acc, s) => acc + s.total_views, 0)
+  const totalLikes = stories.reduce((acc, s) => acc + s.total_likes, 0)
+  const published = stories.filter(s => s.status === 'published').length
+
+  const stats = [
+    { icon: <Users size={24} className="text-blue-300" />, label: 'Total Users', value: allUsers.length, color: 'bg-blue-500/20', change: '+12 this month' },
+    { icon: <BookOpen size={24} className="text-violet-300" />, label: 'Total Stories', value: stories.length, color: 'bg-violet-500/20', change: `${published} published` },
+    { icon: <FileText size={24} className="text-emerald-300" />, label: 'Comments', value: comments.length, color: 'bg-emerald-500/20', change: 'Platform-wide' },
+    { icon: <Heart size={24} className="text-rose-300" />, label: 'Total Likes', value: totalLikes.toLocaleString(), color: 'bg-rose-500/20', change: 'Across all stories' },
+    { icon: <Eye size={24} className="text-amber-300" />, label: 'Total Views', value: totalViews.toLocaleString(), color: 'bg-amber-500/20', change: 'All time' },
+    { icon: <MessageCircle size={24} className="text-cyan-300" />, label: 'Writers', value: allUsers.filter(u => u.role === 'writer').length, color: 'bg-cyan-500/20', change: 'Active creators' },
+  ]
+
+  return (
+    <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
+      {stats.map(stat => (
+        <div key={stat.label} className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+          <div className={`w-12 h-12 rounded-xl ${stat.color} flex items-center justify-center mb-4`}>
+            {stat.icon}
+          </div>
+          <p className="text-3xl font-bold text-white mb-1">{stat.value}</p>
+          <p className="text-slate-400 text-sm font-medium">{stat.label}</p>
+          <p className="text-slate-600 text-xs mt-1">{stat.change}</p>
+        </div>
+      ))}
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/admin/UserTable.tsx
===================================================================
--- chapterx-frontend/src/components/admin/UserTable.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/admin/UserTable.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,153 @@
+import React, { useState } from 'react'
+import { Search, Shield, UserX, UserCheck } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { useUIStore } from '../../store/uiStore'
+import { User, UserRole } from '../../types'
+import { Avatar } from '../ui/Avatar'
+import { RoleBadge } from '../ui/Badge'
+import { Button } from '../ui/Button'
+import { Modal } from '../ui/Modal'
+
+export const UserTable: React.FC = () => {
+  const { allUsers, updateUserRole, currentUser } = useAuthStore()
+  const { addNotification } = useNotificationStore()
+  const { addToast } = useUIStore()
+  const [search, setSearch] = useState('')
+  const [confirmUser, setConfirmUser] = useState<User | null>(null)
+  const [confirmAction, setConfirmAction] = useState<'promote' | 'demote' | null>(null)
+
+  const filtered = allUsers.filter(
+    u =>
+      u.username.toLowerCase().includes(search.toLowerCase()) ||
+      u.email.toLowerCase().includes(search.toLowerCase()) ||
+      u.name.toLowerCase().includes(search.toLowerCase())
+  )
+
+  const handlePromote = (user: User) => {
+    const newRole: UserRole = user.role === 'regular' ? 'writer' : user.role === 'writer' ? 'admin' : 'admin'
+    updateUserRole(user.user_id, newRole)
+    addNotification({
+      user_id: user.user_id,
+      type: 'system',
+      title: 'Role Updated',
+      message: `Your account has been promoted to ${newRole}.`,
+    })
+    addToast(`${user.username} promoted to ${newRole}`)
+    setConfirmUser(null)
+  }
+
+  const handleDemote = (user: User) => {
+    const newRole: UserRole = user.role === 'admin' ? 'writer' : 'regular'
+    updateUserRole(user.user_id, newRole)
+    addToast(`${user.username} role changed to ${newRole}`, 'info')
+    setConfirmUser(null)
+  }
+
+  const formatDate = (str: string) =>
+    new Date(str).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+
+  return (
+    <div>
+      {/* Search */}
+      <div className="relative mb-4">
+        <Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
+        <input
+          value={search}
+          onChange={e => setSearch(e.target.value)}
+          placeholder="Search users..."
+          className="w-full pl-9 pr-4 py-2.5 bg-slate-800 border border-slate-700 rounded-xl text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+        />
+      </div>
+
+      {/* Table */}
+      <div className="overflow-x-auto rounded-xl border border-slate-700">
+        <table className="w-full text-sm">
+          <thead>
+            <tr className="border-b border-slate-700 bg-slate-800">
+              <th className="text-left px-4 py-3 text-slate-400 font-medium">User</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium hidden md:table-cell">Email</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium">Role</th>
+              <th className="text-left px-4 py-3 text-slate-400 font-medium hidden sm:table-cell">Joined</th>
+              <th className="text-right px-4 py-3 text-slate-400 font-medium">Actions</th>
+            </tr>
+          </thead>
+          <tbody className="divide-y divide-slate-800">
+            {filtered.map(user => (
+              <tr key={user.user_id} className="hover:bg-slate-800/50 transition-colors">
+                <td className="px-4 py-3">
+                  <div className="flex items-center gap-3">
+                    <Avatar name={user.name} size="sm" />
+                    <div>
+                      <p className="text-white font-medium">{user.name} {user.surname}</p>
+                      <p className="text-slate-500 text-xs">@{user.username}</p>
+                    </div>
+                  </div>
+                </td>
+                <td className="px-4 py-3 text-slate-400 hidden md:table-cell">{user.email}</td>
+                <td className="px-4 py-3"><RoleBadge role={user.role} /></td>
+                <td className="px-4 py-3 text-slate-500 hidden sm:table-cell">{formatDate(user.created_at)}</td>
+                <td className="px-4 py-3">
+                  <div className="flex items-center justify-end gap-2">
+                    {user.user_id !== currentUser?.user_id && user.role !== 'admin' && (
+                      <button
+                        onClick={() => { setConfirmUser(user); setConfirmAction('promote') }}
+                        className="flex items-center gap-1 text-xs text-indigo-400 hover:text-indigo-300 px-2 py-1 rounded bg-indigo-500/10 hover:bg-indigo-500/20 transition-colors"
+                      >
+                        <UserCheck size={12} />
+                        Promote
+                      </button>
+                    )}
+                    {user.user_id !== currentUser?.user_id && user.role !== 'regular' && (
+                      <button
+                        onClick={() => { setConfirmUser(user); setConfirmAction('demote') }}
+                        className="flex items-center gap-1 text-xs text-rose-400 hover:text-rose-300 px-2 py-1 rounded bg-rose-500/10 hover:bg-rose-500/20 transition-colors"
+                      >
+                        <UserX size={12} />
+                        Demote
+                      </button>
+                    )}
+                  </div>
+                </td>
+              </tr>
+            ))}
+          </tbody>
+        </table>
+        {filtered.length === 0 && (
+          <div className="text-center py-10 text-slate-500">No users found.</div>
+        )}
+      </div>
+
+      {/* Confirm modal */}
+      <Modal
+        isOpen={!!confirmUser && !!confirmAction}
+        onClose={() => { setConfirmUser(null); setConfirmAction(null) }}
+        title={confirmAction === 'promote' ? 'Promote User' : 'Demote User'}
+        size="sm"
+      >
+        {confirmUser && (
+          <div className="space-y-4">
+            <p className="text-slate-300 text-sm">
+              {confirmAction === 'promote'
+                ? `Promote @${confirmUser.username} to the next role tier?`
+                : `Demote @${confirmUser.username} to a lower role?`}
+            </p>
+            <div className="flex gap-3">
+              <Button variant="secondary" className="flex-1" onClick={() => { setConfirmUser(null); setConfirmAction(null) }}>
+                Cancel
+              </Button>
+              <Button
+                variant={confirmAction === 'promote' ? 'primary' : 'danger'}
+                className="flex-1"
+                onClick={() => confirmAction === 'promote' ? handlePromote(confirmUser) : handleDemote(confirmUser)}
+              >
+                <Shield size={14} />
+                Confirm
+              </Button>
+            </div>
+          </div>
+        )}
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/layout/Footer.tsx
===================================================================
--- chapterx-frontend/src/components/layout/Footer.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/layout/Footer.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,48 @@
+import React from 'react'
+import { Link } from 'react-router-dom'
+import { Feather } from 'lucide-react'
+
+export const Footer: React.FC = () => (
+  <footer className="border-t border-slate-800 mt-auto">
+    <div className="max-w-7xl mx-auto px-4 py-10">
+      <div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
+        <div className="col-span-1">
+          <Link to="/" className="flex items-center gap-2 mb-3">
+            <div className="w-7 h-7 rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
+              <Feather size={14} className="text-white" />
+            </div>
+            <span className="font-serif font-bold text-white">ChapterX</span>
+          </Link>
+          <p className="text-slate-500 text-sm">
+            A collaborative storytelling platform for writers and readers.
+          </p>
+        </div>
+        <div>
+          <h4 className="text-white text-sm font-medium mb-3">Explore</h4>
+          <ul className="space-y-2 text-slate-500 text-sm">
+            <li><Link to="/browse" className="hover:text-white transition-colors">Browse Stories</Link></li>
+            <li><Link to="/genres" className="hover:text-white transition-colors">Genres</Link></li>
+            <li><Link to="/community-lists" className="hover:text-white transition-colors">Reading Lists</Link></li>
+          </ul>
+        </div>
+        <div>
+          <h4 className="text-white text-sm font-medium mb-3">Create</h4>
+          <ul className="space-y-2 text-slate-500 text-sm">
+            <li><Link to="/register" className="hover:text-white transition-colors">Start Writing</Link></li>
+            <li><Link to="/writer" className="hover:text-white transition-colors">Writer Dashboard</Link></li>
+          </ul>
+        </div>
+        <div>
+          <h4 className="text-white text-sm font-medium mb-3">Account</h4>
+          <ul className="space-y-2 text-slate-500 text-sm">
+            <li><Link to="/login" className="hover:text-white transition-colors">Sign In</Link></li>
+            <li><Link to="/register" className="hover:text-white transition-colors">Register</Link></li>
+          </ul>
+        </div>
+      </div>
+      <div className="border-t border-slate-800 pt-6 text-center text-slate-600 text-sm">
+        &copy; {new Date().getFullYear()} ChapterX. All rights reserved.
+      </div>
+    </div>
+  </footer>
+)
Index: chapterx-frontend/src/components/layout/Navbar.tsx
===================================================================
--- chapterx-frontend/src/components/layout/Navbar.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/layout/Navbar.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,223 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { Link, useNavigate, useLocation } from 'react-router-dom'
+import { Bell, Menu, X, ChevronDown, BookOpen, PenLine, Shield, List } from 'lucide-react'
+import logo from '../../assets/chapterX-removebg-preview.png'
+import { useAuthStore } from '../../store/authStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { Avatar } from '../ui/Avatar'
+import { RoleBadge } from '../ui/Badge'
+import { Button } from '../ui/Button'
+import { NotificationDropdown } from '../notifications/NotificationDropdown'
+
+export const Navbar: React.FC = () => {
+  const navigate = useNavigate()
+  const location = useLocation()
+  const { currentUser, logout } = useAuthStore()
+  const { notifications } = useNotificationStore()
+  const unread = notifications.filter(n => !n.is_read).length
+
+  const [mobileOpen, setMobileOpen] = useState(false)
+  const [userMenuOpen, setUserMenuOpen] = useState(false)
+  const [notifOpen, setNotifOpen] = useState(false)
+  const userMenuRef = useRef<HTMLDivElement>(null)
+  const notifRef = useRef<HTMLDivElement>(null)
+
+  useEffect(() => {
+    const handler = (e: MouseEvent) => {
+      if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) setUserMenuOpen(false)
+      if (notifRef.current && !notifRef.current.contains(e.target as Node)) setNotifOpen(false)
+    }
+    document.addEventListener('mousedown', handler)
+    return () => document.removeEventListener('mousedown', handler)
+  }, [])
+
+  const navLinks = [
+    { to: '/browse', label: 'Browse', icon: <BookOpen size={16} /> },
+    { to: '/genres', label: 'Genres', icon: null },
+    { to: '/community-lists', label: 'Reading Lists', icon: <List size={16} /> },
+  ]
+
+  const isActive = (path: string) => location.pathname.startsWith(path)
+
+  return (
+    <nav className="sticky top-0 z-40 glass border-b border-white/10">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="flex items-center justify-between h-16">
+          {/* Logo */}
+          <Link to="/" className="flex items-center gap-2 flex-shrink-0 group">
+            <img src={logo} alt="ChapterX" className="h-14 w-14 object-contain group-hover:scale-110 transition-transform" />
+          </Link>
+
+          {/* Desktop nav */}
+          <div className="hidden md:flex items-center gap-1">
+            {navLinks.map(link => (
+              <Link
+                key={link.to}
+                to={link.to}
+                className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
+                  isActive(link.to)
+                    ? 'bg-indigo-500/20 text-indigo-300'
+                    : 'text-slate-400 hover:text-white hover:bg-slate-800'
+                }`}
+              >
+                {link.icon}
+                {link.label}
+              </Link>
+            ))}
+            {currentUser?.role === 'writer' || currentUser?.role === 'admin' ? (
+              <Link
+                to="/writer"
+                className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
+                  isActive('/writer')
+                    ? 'bg-indigo-500/20 text-indigo-300'
+                    : 'text-slate-400 hover:text-white hover:bg-slate-800'
+                }`}
+              >
+                <PenLine size={16} />
+                Write
+              </Link>
+            ) : null}
+            {currentUser?.role === 'admin' && (
+              <Link
+                to="/admin"
+                className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
+                  isActive('/admin')
+                    ? 'bg-rose-500/20 text-rose-300'
+                    : 'text-slate-400 hover:text-white hover:bg-slate-800'
+                }`}
+              >
+                <Shield size={16} />
+                Admin
+              </Link>
+            )}
+          </div>
+
+          {/* Right side */}
+          <div className="flex items-center gap-2">
+            {currentUser ? (
+              <>
+                {/* Notification bell */}
+                <div className="relative" ref={notifRef}>
+                  <button
+                    onClick={() => { setNotifOpen(!notifOpen); setUserMenuOpen(false) }}
+                    className="relative p-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition-colors"
+                  >
+                    <Bell size={18} />
+                    {unread > 0 && (
+                      <span className="absolute -top-0.5 -right-0.5 w-4 h-4 bg-rose-500 text-white text-xs rounded-full flex items-center justify-center font-medium">
+                        {unread > 9 ? '9+' : unread}
+                      </span>
+                    )}
+                  </button>
+                  {notifOpen && <NotificationDropdown onClose={() => setNotifOpen(false)} />}
+                </div>
+
+                {/* User menu */}
+                <div className="relative" ref={userMenuRef}>
+                  <button
+                    onClick={() => { setUserMenuOpen(!userMenuOpen); setNotifOpen(false) }}
+                    className="flex items-center gap-2 px-2 py-1.5 rounded-xl hover:bg-slate-800 transition-colors"
+                  >
+                    <Avatar name={currentUser.name} size="sm" />
+                    <span className="hidden sm:block text-sm text-slate-300">{currentUser.username}</span>
+                    <ChevronDown size={14} className="text-slate-500" />
+                  </button>
+
+                  {userMenuOpen && (
+                    <div className="absolute right-0 top-full mt-2 w-52 bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl z-50 overflow-hidden py-1">
+                      <div className="px-4 py-3 border-b border-slate-700">
+                        <p className="text-sm font-medium text-white">{currentUser.name} {currentUser.surname}</p>
+                        <div className="flex items-center gap-2 mt-1">
+                          <p className="text-xs text-slate-500">@{currentUser.username}</p>
+                          <RoleBadge role={currentUser.role} />
+                        </div>
+                      </div>
+                      <div className="py-1">
+                        <button onClick={() => { navigate(`/profile/${currentUser.username}`); setUserMenuOpen(false) }}
+                          className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
+                          My Profile
+                        </button>
+                        <button onClick={() => { navigate('/reading-lists'); setUserMenuOpen(false) }}
+                          className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
+                          My Reading Lists
+                        </button>
+                        {(currentUser.role === 'writer' || currentUser.role === 'admin') && (
+                          <button onClick={() => { navigate('/writer'); setUserMenuOpen(false) }}
+                            className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
+                            Writer Dashboard
+                          </button>
+                        )}
+                        {currentUser.role === 'admin' && (
+                          <button onClick={() => { navigate('/admin'); setUserMenuOpen(false) }}
+                            className="w-full text-left px-4 py-2 text-sm text-slate-300 hover:bg-slate-800 hover:text-white transition-colors">
+                            Admin Panel
+                          </button>
+                        )}
+                      </div>
+                      <div className="border-t border-slate-700 py-1">
+                        <button
+                          onClick={() => { logout(); setUserMenuOpen(false); navigate('/') }}
+                          className="w-full text-left px-4 py-2 text-sm text-rose-400 hover:bg-slate-800 transition-colors"
+                        >
+                          Sign Out
+                        </button>
+                      </div>
+                    </div>
+                  )}
+                </div>
+              </>
+            ) : (
+              <div className="hidden sm:flex items-center gap-2">
+                <Button variant="ghost" size="sm" onClick={() => navigate('/login')}>
+                  Sign In
+                </Button>
+                <Button size="sm" onClick={() => navigate('/register')}>
+                  Get Started
+                </Button>
+              </div>
+            )}
+
+            {/* Mobile hamburger */}
+            <button
+              className="md:hidden p-2 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800"
+              onClick={() => setMobileOpen(!mobileOpen)}
+            >
+              {mobileOpen ? <X size={20} /> : <Menu size={20} />}
+            </button>
+          </div>
+        </div>
+
+        {/* Mobile menu */}
+        {mobileOpen && (
+          <div className="md:hidden pb-4 border-t border-slate-800 mt-2 pt-4 space-y-1">
+            {navLinks.map(link => (
+              <Link
+                key={link.to}
+                to={link.to}
+                onClick={() => setMobileOpen(false)}
+                className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors ${
+                  isActive(link.to)
+                    ? 'bg-indigo-500/20 text-indigo-300'
+                    : 'text-slate-400 hover:text-white hover:bg-slate-800'
+                }`}
+              >
+                {link.icon}
+                {link.label}
+              </Link>
+            ))}
+            {!currentUser && (
+              <div className="flex gap-2 pt-2">
+                <Button variant="ghost" size="sm" onClick={() => { navigate('/login'); setMobileOpen(false) }} className="flex-1">
+                  Sign In
+                </Button>
+                <Button size="sm" onClick={() => { navigate('/register'); setMobileOpen(false) }} className="flex-1">
+                  Get Started
+                </Button>
+              </div>
+            )}
+          </div>
+        )}
+      </div>
+    </nav>
+  )
+}
Index: chapterx-frontend/src/components/notifications/NotificationDropdown.tsx
===================================================================
--- chapterx-frontend/src/components/notifications/NotificationDropdown.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/notifications/NotificationDropdown.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,92 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Heart, MessageCircle, Users, Bell, Settings, Check } from 'lucide-react'
+import { useNotificationStore } from '../../store/notificationStore'
+import { NotificationType } from '../../types'
+
+const typeIcon = (type: NotificationType) => {
+  const props = { size: 14 }
+  switch (type) {
+    case 'like': return <Heart {...props} className="text-rose-400" />
+    case 'comment': return <MessageCircle {...props} className="text-blue-400" />
+    case 'collaboration': return <Users {...props} className="text-violet-400" />
+    case 'follow': return <Bell {...props} className="text-amber-400" />
+    case 'system': return <Settings {...props} className="text-slate-400" />
+    default: return <Bell {...props} className="text-slate-400" />
+  }
+}
+
+function timeAgo(dateStr: string): string {
+  const diff = Date.now() - new Date(dateStr).getTime()
+  const mins = Math.floor(diff / 60000)
+  if (mins < 1) return 'just now'
+  if (mins < 60) return `${mins}m ago`
+  const hrs = Math.floor(mins / 60)
+  if (hrs < 24) return `${hrs}h ago`
+  return `${Math.floor(hrs / 24)}d ago`
+}
+
+interface Props {
+  onClose: () => void
+}
+
+export const NotificationDropdown: React.FC<Props> = ({ onClose }) => {
+  const navigate = useNavigate()
+  const { notifications, markAllRead, markRead } = useNotificationStore()
+  const recent = notifications.slice(0, 5)
+
+  return (
+    <div className="absolute right-0 top-full mt-2 w-80 bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl z-50 overflow-hidden">
+      <div className="flex items-center justify-between px-4 py-3 border-b border-slate-700">
+        <span className="text-sm font-semibold text-white">Notifications</span>
+        <button
+          onClick={markAllRead}
+          className="text-xs text-indigo-400 hover:text-indigo-300 flex items-center gap-1 transition-colors"
+        >
+          <Check size={12} />
+          Mark all read
+        </button>
+      </div>
+      <div className="divide-y divide-slate-800">
+        {recent.length === 0 ? (
+          <div className="px-4 py-8 text-center text-slate-500 text-sm">
+            No notifications yet
+          </div>
+        ) : (
+          recent.map(n => (
+            <div
+              key={n.notification_id}
+              className={`flex gap-3 px-4 py-3 cursor-pointer transition-colors ${
+                !n.is_read ? 'bg-indigo-500/5 hover:bg-indigo-500/10' : 'hover:bg-slate-800'
+              }`}
+              onClick={() => {
+                markRead(n.notification_id)
+                if (n.link) { navigate(n.link); onClose() }
+              }}
+            >
+              <div className="mt-0.5 w-6 h-6 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0">
+                {typeIcon(n.type)}
+              </div>
+              <div className="flex-1 min-w-0">
+                <p className="text-xs font-medium text-white">{n.title}</p>
+                <p className="text-xs text-slate-400 line-clamp-2">{n.message}</p>
+                <p className="text-xs text-slate-600 mt-0.5">{timeAgo(n.created_at)}</p>
+              </div>
+              {!n.is_read && (
+                <div className="w-2 h-2 rounded-full bg-indigo-400 flex-shrink-0 mt-1.5" />
+              )}
+            </div>
+          ))
+        )}
+      </div>
+      <div className="border-t border-slate-700 p-2">
+        <button
+          onClick={() => { navigate('/notifications'); onClose() }}
+          className="w-full text-center text-xs text-indigo-400 hover:text-indigo-300 py-1.5 rounded-lg hover:bg-slate-800 transition-colors"
+        >
+          View all notifications
+        </button>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/story/ChapterList.tsx
===================================================================
--- chapterx-frontend/src/components/story/ChapterList.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/story/ChapterList.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,85 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Eye, BookOpen, Lock } from 'lucide-react'
+import { Chapter } from '../../types'
+
+interface ChapterListProps {
+  chapters: Chapter[]
+  storyId: number
+  showEditLinks?: boolean
+  onEdit?: (chapterId: number) => void
+}
+
+function formatDate(str: string) {
+  return new Date(str).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+}
+
+export const ChapterList: React.FC<ChapterListProps> = ({ chapters, storyId, showEditLinks, onEdit }) => {
+  const navigate = useNavigate()
+  const sorted = [...chapters].sort((a, b) => a.chapter_number - b.chapter_number)
+
+  if (sorted.length === 0) {
+    return (
+      <div className="flex flex-col items-center py-10 text-slate-500">
+        <BookOpen size={36} className="mb-3 opacity-40" />
+        <p>No chapters yet.</p>
+      </div>
+    )
+  }
+
+  return (
+    <div className="space-y-2">
+      {sorted.map(chapter => (
+        <div
+          key={chapter.chapter_id}
+          className={`flex items-center gap-4 p-4 rounded-xl bg-slate-800 border border-slate-700 transition-all ${
+            chapter.is_published
+              ? 'hover:border-indigo-500/40 hover:bg-slate-700/50 cursor-pointer'
+              : 'opacity-60'
+          }`}
+          onClick={() => {
+            if (chapter.is_published) navigate(`/story/${storyId}/chapter/${chapter.chapter_id}`)
+          }}
+        >
+          {/* Number */}
+          <div className="w-10 h-10 rounded-lg bg-slate-700 flex items-center justify-center text-indigo-400 font-semibold text-sm flex-shrink-0">
+            {chapter.chapter_number}
+          </div>
+
+          {/* Info */}
+          <div className="flex-1 min-w-0">
+            <div className="flex items-center gap-2">
+              <h4 className="text-white text-sm font-medium truncate">{chapter.title}</h4>
+              {!chapter.is_published && (
+                <span className="flex items-center gap-1 text-amber-400 text-xs">
+                  <Lock size={11} />
+                  Draft
+                </span>
+              )}
+            </div>
+            <div className="flex items-center gap-3 text-slate-500 text-xs mt-0.5">
+              <span>{chapter.word_count.toLocaleString()} words</span>
+              <span>{formatDate(chapter.created_at)}</span>
+            </div>
+          </div>
+
+          {/* Views */}
+          <div className="flex items-center gap-1 text-slate-500 text-xs flex-shrink-0">
+            <Eye size={12} />
+            {chapter.view_count.toLocaleString()}
+          </div>
+
+          {/* Edit button */}
+          {showEditLinks && onEdit && (
+            <button
+              onClick={(e) => { e.stopPropagation(); onEdit(chapter.chapter_id) }}
+              className="text-indigo-400 hover:text-indigo-300 text-xs px-2 py-1 rounded bg-indigo-500/10 hover:bg-indigo-500/20 transition-colors flex-shrink-0"
+            >
+              Edit
+            </button>
+          )}
+        </div>
+      ))}
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/story/CommentSection.tsx
===================================================================
--- chapterx-frontend/src/components/story/CommentSection.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/story/CommentSection.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,148 @@
+import React, { useState } from 'react'
+import { MessageCircle, Trash2, Send } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { useUIStore } from '../../store/uiStore'
+import { Comment } from '../../types'
+import { Avatar } from '../ui/Avatar'
+import { Button } from '../ui/Button'
+
+interface CommentSectionProps {
+  storyId: number
+  authorUserId: number
+}
+
+function timeAgo(str: string): string {
+  const diff = Date.now() - new Date(str).getTime()
+  const m = Math.floor(diff / 60000)
+  if (m < 1) return 'just now'
+  if (m < 60) return `${m}m ago`
+  const h = Math.floor(m / 60)
+  if (h < 24) return `${h}h ago`
+  return `${Math.floor(h / 24)}d ago`
+}
+
+export const CommentSection: React.FC<CommentSectionProps> = ({ storyId, authorUserId }) => {
+  const { currentUser } = useAuthStore()
+  const { comments, addComment, deleteComment } = useStoryStore()
+  const { addNotification } = useNotificationStore()
+  const { addToast } = useUIStore()
+  const [text, setText] = useState('')
+  const [submitting, setSubmitting] = useState(false)
+
+  const storyComments = comments
+    .filter(c => c.story_id === storyId)
+    .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+
+  const handleSubmit = async () => {
+    if (!text.trim() || !currentUser) return
+    setSubmitting(true)
+    await new Promise(r => setTimeout(r, 300))
+    const comment: Comment = {
+      comment_id: Date.now(),
+      story_id: storyId,
+      user_id: currentUser.user_id,
+      username: currentUser.username,
+      content: text.trim(),
+      created_at: new Date().toISOString(),
+    }
+    addComment(comment)
+    if (currentUser.user_id !== authorUserId) {
+      addNotification({
+        user_id: authorUserId,
+        type: 'comment',
+        title: 'New Comment',
+        message: `${currentUser.username} commented: "${text.trim().slice(0, 60)}..."`,
+        link: `/story/${storyId}`,
+      })
+    }
+    setText('')
+    setSubmitting(false)
+    addToast('Comment posted!')
+  }
+
+  return (
+    <div className="space-y-4">
+      <div className="flex items-center gap-2 mb-4">
+        <MessageCircle size={18} className="text-indigo-400" />
+        <h3 className="text-white font-semibold">Comments ({storyComments.length})</h3>
+      </div>
+
+      {/* Input */}
+      {currentUser ? (
+        <div className="flex gap-3">
+          <Avatar name={currentUser.name} size="sm" />
+          <div className="flex-1">
+            <textarea
+              value={text}
+              onChange={e => setText(e.target.value)}
+              placeholder="Share your thoughts..."
+              rows={3}
+              className="w-full bg-slate-800 border border-slate-700 rounded-xl px-4 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none"
+            />
+            <div className="flex justify-end mt-2">
+              <Button
+                size="sm"
+                onClick={handleSubmit}
+                loading={submitting}
+                disabled={!text.trim()}
+              >
+                <Send size={14} />
+                Post Comment
+              </Button>
+            </div>
+          </div>
+        </div>
+      ) : (
+        <div className="bg-slate-800 border border-slate-700 rounded-xl p-4 text-center">
+          <p className="text-slate-400 text-sm">
+            <a href="/login" className="text-indigo-400 hover:text-indigo-300">Sign in</a> to leave a comment.
+          </p>
+        </div>
+      )}
+
+      {/* Comment list */}
+      <div className="space-y-3">
+        {storyComments.length === 0 ? (
+          <div className="text-center py-8 text-slate-500 text-sm">
+            No comments yet. Be the first to share your thoughts!
+          </div>
+        ) : (
+          storyComments.map(comment => (
+            <CommentCard
+              key={comment.comment_id}
+              comment={comment}
+              canDelete={currentUser?.user_id === comment.user_id || currentUser?.role === 'admin'}
+              onDelete={() => { deleteComment(comment.comment_id); addToast('Comment deleted', 'info') }}
+            />
+          ))
+        )}
+      </div>
+    </div>
+  )
+}
+
+const CommentCard: React.FC<{
+  comment: Comment
+  canDelete: boolean
+  onDelete: () => void
+}> = ({ comment, canDelete, onDelete }) => (
+  <div className="flex gap-3 p-4 bg-slate-800 rounded-xl border border-slate-700">
+    <Avatar name={comment.username} size="sm" />
+    <div className="flex-1 min-w-0">
+      <div className="flex items-center justify-between">
+        <span className="text-sm font-medium text-white">{comment.username}</span>
+        <div className="flex items-center gap-2">
+          <span className="text-xs text-slate-500">{timeAgo(comment.created_at)}</span>
+          {canDelete && (
+            <button onClick={onDelete} className="text-slate-500 hover:text-rose-400 transition-colors">
+              <Trash2 size={13} />
+            </button>
+          )}
+        </div>
+      </div>
+      <p className="text-slate-300 text-sm mt-1 leading-relaxed">{comment.content}</p>
+    </div>
+  </div>
+)
Index: chapterx-frontend/src/components/story/GenreBadge.tsx
===================================================================
--- chapterx-frontend/src/components/story/GenreBadge.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/story/GenreBadge.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,27 @@
+import React from 'react'
+import { GenreBadge as GenreBadgeUI } from '../ui/Badge'
+
+interface GenreBadgeProps {
+  genre: string
+  onClick?: () => void
+}
+
+export const GenreBadge: React.FC<GenreBadgeProps> = ({ genre, onClick }) => (
+  <GenreBadgeUI genre={genre} onClick={onClick} />
+)
+
+export function getGenreGradient(genre: string): string {
+  const gradients: Record<string, string> = {
+    Fantasy: 'from-purple-900 via-indigo-900 to-indigo-800',
+    'Sci-Fi': 'from-cyan-900 via-blue-900 to-blue-800',
+    Romance: 'from-pink-900 via-rose-900 to-rose-800',
+    'Historical Fiction': 'from-amber-900 via-orange-900 to-orange-800',
+    Adventure: 'from-green-900 via-emerald-900 to-emerald-800',
+    Thriller: 'from-gray-900 via-slate-800 to-slate-700',
+    Mystery: 'from-violet-900 via-purple-900 to-purple-800',
+    Horror: 'from-red-900 via-rose-900 to-slate-900',
+    Contemporary: 'from-blue-900 via-indigo-900 to-slate-800',
+    Poetry: 'from-rose-900 via-pink-900 to-pink-800',
+  }
+  return gradients[genre] || 'from-indigo-900 via-violet-900 to-violet-800'
+}
Index: chapterx-frontend/src/components/story/LikeButton.tsx
===================================================================
--- chapterx-frontend/src/components/story/LikeButton.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/story/LikeButton.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,56 @@
+import React from 'react'
+import { Heart } from 'lucide-react'
+import { useNavigate } from 'react-router-dom'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { useUIStore } from '../../store/uiStore'
+
+interface LikeButtonProps {
+  storyId: number
+  authorUserId: number
+  totalLikes: number
+}
+
+export const LikeButton: React.FC<LikeButtonProps> = ({ storyId, authorUserId, totalLikes }) => {
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { toggleLike, isLiked } = useStoryStore()
+  const { addNotification } = useNotificationStore()
+  const { addToast } = useUIStore()
+
+  const liked = currentUser ? isLiked(currentUser.user_id, storyId) : false
+
+  const handleClick = () => {
+    if (!currentUser) {
+      addToast('Please sign in to like stories.', 'info')
+      navigate('/login')
+      return
+    }
+    toggleLike(currentUser.user_id, storyId)
+    if (!liked && currentUser.user_id !== authorUserId) {
+      addNotification({
+        user_id: authorUserId,
+        type: 'like',
+        title: 'New Like',
+        message: `${currentUser.username} liked your story.`,
+        link: `/story/${storyId}`,
+      })
+    }
+    addToast(liked ? 'Removed from likes' : 'Added to likes!', liked ? 'info' : 'success')
+  }
+
+  return (
+    <button
+      onClick={handleClick}
+      className={`flex items-center gap-2 px-4 py-2 rounded-xl border transition-all duration-200 ${
+        liked
+          ? 'bg-rose-500/20 border-rose-500/40 text-rose-400 hover:bg-rose-500/30'
+          : 'bg-slate-800 border-slate-700 text-slate-400 hover:border-rose-500/40 hover:text-rose-400 hover:bg-rose-500/10'
+      }`}
+    >
+      <Heart size={16} className={liked ? 'fill-rose-400' : ''} />
+      <span className="text-sm font-medium">{totalLikes.toLocaleString()}</span>
+    </button>
+  )
+}
Index: chapterx-frontend/src/components/story/StoryGrid.tsx
===================================================================
--- chapterx-frontend/src/components/story/StoryGrid.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/story/StoryGrid.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react'
+import { Story } from '../../types'
+import { StoryCard } from '../ui/StoryCard'
+import { BookOpen } from 'lucide-react'
+
+interface StoryGridProps {
+  stories: Story[]
+  showStatus?: boolean
+  emptyMessage?: string
+  loading?: boolean
+}
+
+const SkeletonCard = () => (
+  <div className="rounded-xl overflow-hidden bg-slate-800 border border-slate-700 animate-pulse">
+    <div className="h-40 bg-slate-700" />
+    <div className="p-4 space-y-2">
+      <div className="h-4 bg-slate-700 rounded w-3/4" />
+      <div className="h-3 bg-slate-700 rounded w-1/3" />
+      <div className="h-3 bg-slate-700 rounded w-full" />
+      <div className="h-3 bg-slate-700 rounded w-5/6" />
+      <div className="flex gap-2 mt-2">
+        <div className="h-5 bg-slate-700 rounded-full w-16" />
+        <div className="h-5 bg-slate-700 rounded-full w-16" />
+      </div>
+    </div>
+  </div>
+)
+
+export const StoryGrid: React.FC<StoryGridProps> = ({
+  stories,
+  showStatus = false,
+  emptyMessage = 'No stories found.',
+  loading: externalLoading,
+}) => {
+  const [loading, setLoading] = useState(true)
+
+  useEffect(() => {
+    const timer = setTimeout(() => setLoading(false), 800)
+    return () => clearTimeout(timer)
+  }, [])
+
+  const isLoading = externalLoading !== undefined ? externalLoading : loading
+
+  if (isLoading) {
+    return (
+      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+        {Array.from({ length: 3 }).map((_, i) => <SkeletonCard key={i} />)}
+      </div>
+    )
+  }
+
+  if (stories.length === 0) {
+    return (
+      <div className="flex flex-col items-center justify-center py-20 text-slate-500">
+        <BookOpen size={48} className="mb-4 opacity-40" />
+        <p className="text-lg font-medium">{emptyMessage}</p>
+        <p className="text-sm mt-1">Try adjusting your search or filters.</p>
+      </div>
+    )
+  }
+
+  return (
+    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+      {stories.map(story => (
+        <StoryCard key={story.story_id} story={story} showStatus={showStatus} />
+      ))}
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/ui/Avatar.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Avatar.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Avatar.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,58 @@
+import React from 'react'
+
+interface AvatarProps {
+  name: string
+  image?: string
+  size?: 'sm' | 'md' | 'lg' | 'xl'
+  className?: string
+}
+
+const sizeMap = {
+  sm: 'w-8 h-8 text-xs',
+  md: 'w-10 h-10 text-sm',
+  lg: 'w-16 h-16 text-xl',
+  xl: 'w-24 h-24 text-3xl',
+}
+
+const colors = [
+  'from-indigo-500 to-violet-500',
+  'from-violet-500 to-purple-500',
+  'from-pink-500 to-rose-500',
+  'from-emerald-500 to-teal-500',
+  'from-amber-500 to-orange-500',
+  'from-cyan-500 to-blue-500',
+]
+
+function getColorIndex(name: string): number {
+  let hash = 0
+  for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash)
+  return Math.abs(hash) % colors.length
+}
+
+export const Avatar: React.FC<AvatarProps> = ({ name, image, size = 'md', className = '' }) => {
+  const initials = name
+    .split(' ')
+    .map(p => p[0])
+    .slice(0, 2)
+    .join('')
+    .toUpperCase()
+  const colorClass = colors[getColorIndex(name)]
+
+  if (image) {
+    return (
+      <img
+        src={image}
+        alt={name}
+        className={`${sizeMap[size]} rounded-full object-cover ring-2 ring-slate-700 ${className}`}
+      />
+    )
+  }
+
+  return (
+    <div
+      className={`${sizeMap[size]} rounded-full bg-gradient-to-br ${colorClass} flex items-center justify-center font-semibold text-white ring-2 ring-slate-700 flex-shrink-0 ${className}`}
+    >
+      {initials}
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/ui/Badge.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Badge.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Badge.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,68 @@
+import React from 'react'
+import { UserRole, StoryStatus } from '../../types'
+
+interface RoleBadgeProps {
+  role: UserRole
+}
+
+interface StatusBadgeProps {
+  status: StoryStatus
+}
+
+interface GenreBadgeProps {
+  genre: string
+  onClick?: () => void
+}
+
+export const RoleBadge: React.FC<RoleBadgeProps> = ({ role }) => {
+  const styles = {
+    admin: 'bg-rose-500/20 text-rose-400 border-rose-500/30',
+    writer: 'bg-indigo-500/20 text-indigo-400 border-indigo-500/30',
+    regular: 'bg-slate-500/20 text-slate-400 border-slate-500/30',
+    guest: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
+  }
+  const labels = { admin: 'Admin', writer: 'Writer', regular: 'Reader', guest: 'Guest' }
+  return (
+    <span className={`px-2 py-0.5 text-xs font-medium rounded-full border ${styles[role]}`}>
+      {labels[role]}
+    </span>
+  )
+}
+
+export const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
+  const styles = {
+    draft: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
+    published: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30',
+    archived: 'bg-slate-500/20 text-slate-400 border-slate-500/30',
+  }
+  const labels = { draft: 'Draft', published: 'Published', archived: 'Archived' }
+  return (
+    <span className={`px-2 py-0.5 text-xs font-medium rounded-full border ${styles[status]}`}>
+      {labels[status]}
+    </span>
+  )
+}
+
+export const GenreBadge: React.FC<GenreBadgeProps> = ({ genre, onClick }) => {
+  const colors: Record<string, string> = {
+    Fantasy: 'bg-purple-500/20 text-purple-300 border-purple-500/30 hover:bg-purple-500/30',
+    'Sci-Fi': 'bg-cyan-500/20 text-cyan-300 border-cyan-500/30 hover:bg-cyan-500/30',
+    Romance: 'bg-pink-500/20 text-pink-300 border-pink-500/30 hover:bg-pink-500/30',
+    'Historical Fiction': 'bg-amber-500/20 text-amber-300 border-amber-500/30 hover:bg-amber-500/30',
+    Adventure: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30 hover:bg-emerald-500/30',
+    Thriller: 'bg-slate-500/20 text-slate-300 border-slate-500/30 hover:bg-slate-500/30',
+    Mystery: 'bg-violet-500/20 text-violet-300 border-violet-500/30 hover:bg-violet-500/30',
+    Horror: 'bg-red-500/20 text-red-300 border-red-500/30 hover:bg-red-500/30',
+    Contemporary: 'bg-blue-500/20 text-blue-300 border-blue-500/30 hover:bg-blue-500/30',
+    Poetry: 'bg-rose-500/20 text-rose-300 border-rose-500/30 hover:bg-rose-500/30',
+  }
+  const style = colors[genre] || 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30 hover:bg-indigo-500/30'
+  return (
+    <span
+      className={`px-2 py-0.5 text-xs font-medium rounded-full border transition-colors ${style} ${onClick ? 'cursor-pointer' : ''}`}
+      onClick={onClick}
+    >
+      {genre}
+    </span>
+  )
+}
Index: chapterx-frontend/src/components/ui/Button.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Button.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Button.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,70 @@
+import React from 'react'
+
+interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
+  variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
+  size?: 'sm' | 'md' | 'lg'
+  loading?: boolean
+  children: React.ReactNode
+}
+
+export const Button: React.FC<ButtonProps> = ({
+  variant = 'primary',
+  size = 'md',
+  loading = false,
+  children,
+  className = '',
+  disabled,
+  ...props
+}) => {
+  const base =
+    'inline-flex items-center justify-center font-medium rounded-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed'
+
+  const variants = {
+    primary:
+      'bg-gradient-to-r from-indigo-600 to-violet-600 text-white hover:from-indigo-500 hover:to-violet-500 focus:ring-indigo-500 shadow-lg glow-indigo hover:scale-105 active:scale-95',
+    secondary:
+      'bg-slate-700 text-slate-100 hover:bg-slate-600 focus:ring-slate-500 border border-slate-600',
+    danger:
+      'bg-gradient-to-r from-rose-600 to-rose-500 text-white hover:from-rose-500 hover:to-rose-400 focus:ring-rose-500 shadow-lg',
+    ghost:
+      'bg-transparent text-slate-300 hover:bg-slate-800 hover:text-white focus:ring-slate-500 border border-transparent hover:border-slate-600',
+  }
+
+  const sizes = {
+    sm: 'px-3 py-1.5 text-sm gap-1.5',
+    md: 'px-4 py-2 text-sm gap-2',
+    lg: 'px-6 py-3 text-base gap-2',
+  }
+
+  return (
+    <button
+      className={`${base} ${variants[variant]} ${sizes[size]} ${className}`}
+      disabled={disabled || loading}
+      {...props}
+    >
+      {loading && (
+        <svg
+          className="animate-spin h-4 w-4"
+          xmlns="http://www.w3.org/2000/svg"
+          fill="none"
+          viewBox="0 0 24 24"
+        >
+          <circle
+            className="opacity-25"
+            cx="12"
+            cy="12"
+            r="10"
+            stroke="currentColor"
+            strokeWidth="4"
+          />
+          <path
+            className="opacity-75"
+            fill="currentColor"
+            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
+          />
+        </svg>
+      )}
+      {children}
+    </button>
+  )
+}
Index: chapterx-frontend/src/components/ui/Modal.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Modal.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Modal.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,68 @@
+import React, { useEffect } from 'react'
+import { X } from 'lucide-react'
+
+interface ModalProps {
+  isOpen: boolean
+  onClose: () => void
+  title?: string
+  children: React.ReactNode
+  size?: 'sm' | 'md' | 'lg' | 'xl'
+}
+
+const sizeMap = {
+  sm: 'max-w-sm',
+  md: 'max-w-md',
+  lg: 'max-w-lg',
+  xl: 'max-w-2xl',
+}
+
+export const Modal: React.FC<ModalProps> = ({
+  isOpen,
+  onClose,
+  title,
+  children,
+  size = 'md',
+}) => {
+  useEffect(() => {
+    const handler = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') onClose()
+    }
+    if (isOpen) document.addEventListener('keydown', handler)
+    return () => document.removeEventListener('keydown', handler)
+  }, [isOpen, onClose])
+
+  if (!isOpen) return null
+
+  return (
+    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
+      <div
+        className="absolute inset-0 bg-black/70 backdrop-blur-sm"
+        onClick={onClose}
+      />
+      <div
+        className={`relative w-full ${sizeMap[size]} bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl z-10 max-h-[90vh] overflow-y-auto`}
+      >
+        {title && (
+          <div className="flex items-center justify-between p-6 border-b border-slate-700">
+            <h2 className="text-lg font-semibold text-white font-serif">{title}</h2>
+            <button
+              onClick={onClose}
+              className="p-1.5 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors"
+            >
+              <X size={18} />
+            </button>
+          </div>
+        )}
+        {!title && (
+          <button
+            onClick={onClose}
+            className="absolute top-4 right-4 p-1.5 text-slate-400 hover:text-white hover:bg-slate-700 rounded-lg transition-colors z-10"
+          >
+            <X size={18} />
+          </button>
+        )}
+        <div className="p-6">{children}</div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/ui/Spinner.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Spinner.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Spinner.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,31 @@
+import React from 'react'
+
+interface SpinnerProps {
+  size?: 'sm' | 'md' | 'lg'
+  className?: string
+}
+
+const sizes = { sm: 'h-4 w-4', md: 'h-8 w-8', lg: 'h-12 w-12' }
+
+export const Spinner: React.FC<SpinnerProps> = ({ size = 'md', className = '' }) => (
+  <svg
+    className={`animate-spin ${sizes[size]} text-indigo-400 ${className}`}
+    xmlns="http://www.w3.org/2000/svg"
+    fill="none"
+    viewBox="0 0 24 24"
+  >
+    <circle
+      className="opacity-25"
+      cx="12"
+      cy="12"
+      r="10"
+      stroke="currentColor"
+      strokeWidth="4"
+    />
+    <path
+      className="opacity-75"
+      fill="currentColor"
+      d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
+    />
+  </svg>
+)
Index: chapterx-frontend/src/components/ui/StoryCard.tsx
===================================================================
--- chapterx-frontend/src/components/ui/StoryCard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/StoryCard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,96 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Heart, MessageCircle, Eye, Lock } from 'lucide-react'
+import { Story } from '../../types'
+import { useAuthStore } from '../../store/authStore'
+import { StatusBadge, GenreBadge } from './Badge'
+import { getGenreGradient } from '../story/GenreBadge'
+
+interface StoryCardProps {
+  story: Story
+  showStatus?: boolean
+}
+
+export const StoryCard: React.FC<StoryCardProps> = ({ story, showStatus = false }) => {
+  const navigate = useNavigate()
+  const { currentUser, showMatureContent } = useAuthStore()
+
+  const isMature = story.mature_content
+  const isGuest = !currentUser
+  const blurContent = isMature && isGuest && !showMatureContent
+
+  const primaryGenre = story.genres[0] || 'Fantasy'
+  const gradient = getGenreGradient(primaryGenre)
+
+  return (
+    <div
+      className="relative group cursor-pointer rounded-xl overflow-hidden bg-slate-800 border border-slate-700 hover:border-indigo-500/50 transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/10"
+      onClick={() => navigate(`/story/${story.story_id}`)}
+    >
+      {/* Cover */}
+      <div className={`relative h-40 bg-gradient-to-br ${gradient} flex items-end p-4`}>
+        {/* Mature ribbon */}
+        {isMature && (
+          <div className="absolute top-2 right-2 bg-rose-500/90 text-white text-xs px-2 py-0.5 rounded-full font-medium flex items-center gap-1">
+            <Lock size={10} />
+            18+
+          </div>
+        )}
+        {/* Status badge */}
+        {showStatus && (
+          <div className="absolute top-2 left-2">
+            <StatusBadge status={story.status} />
+          </div>
+        )}
+        {/* Blur overlay for mature on guest */}
+        {blurContent && (
+          <div className="absolute inset-0 backdrop-blur-md bg-slate-900/60 flex items-center justify-center">
+            <div className="text-center">
+              <Lock size={24} className="text-slate-400 mx-auto mb-1" />
+              <p className="text-xs text-slate-400">Mature Content</p>
+              <p className="text-xs text-slate-500">Login to view</p>
+            </div>
+          </div>
+        )}
+        {/* Genre decorative circles */}
+        <div className="absolute top-4 left-4 w-20 h-20 rounded-full bg-white/5 blur-xl" />
+        <div className="absolute bottom-2 right-8 w-12 h-12 rounded-full bg-white/5 blur-lg" />
+      </div>
+
+      {/* Content */}
+      <div className="p-4">
+        <h3 className="font-serif font-semibold text-white text-sm leading-tight mb-1 line-clamp-2 group-hover:text-indigo-300 transition-colors">
+          {story.title}
+        </h3>
+        <p className="text-slate-500 text-xs mb-2">by {story.author_username}</p>
+        <p className="text-slate-400 text-xs line-clamp-2 mb-3">{story.short_description}</p>
+
+        {/* Genre badges */}
+        <div className="flex flex-wrap gap-1 mb-3">
+          {story.genres.slice(0, 2).map(g => (
+            <GenreBadge key={g} genre={g} />
+          ))}
+          {story.genres.length > 2 && (
+            <span className="text-xs text-slate-500">+{story.genres.length - 2}</span>
+          )}
+        </div>
+
+        {/* Stats */}
+        <div className="flex items-center gap-3 text-slate-500 text-xs">
+          <span className="flex items-center gap-1">
+            <Heart size={12} />
+            {story.total_likes.toLocaleString()}
+          </span>
+          <span className="flex items-center gap-1">
+            <MessageCircle size={12} />
+            {story.total_comments}
+          </span>
+          <span className="flex items-center gap-1 ml-auto">
+            <Eye size={12} />
+            {story.total_views.toLocaleString()}
+          </span>
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/ui/Toast.tsx
===================================================================
--- chapterx-frontend/src/components/ui/Toast.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/ui/Toast.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,41 @@
+import React from 'react'
+import { CheckCircle, XCircle, AlertTriangle, Info, X } from 'lucide-react'
+import { useUIStore } from '../../store/uiStore'
+
+const icons = {
+  success: <CheckCircle size={18} className="text-emerald-400" />,
+  error: <XCircle size={18} className="text-rose-400" />,
+  warning: <AlertTriangle size={18} className="text-amber-400" />,
+  info: <Info size={18} className="text-blue-400" />,
+}
+
+const styles = {
+  success: 'border-emerald-500/30 bg-emerald-500/10',
+  error: 'border-rose-500/30 bg-rose-500/10',
+  warning: 'border-amber-500/30 bg-amber-500/10',
+  info: 'border-blue-500/30 bg-blue-500/10',
+}
+
+export const ToastContainer: React.FC = () => {
+  const { toasts, removeToast } = useUIStore()
+
+  return (
+    <div className="fixed bottom-6 right-6 z-[100] flex flex-col gap-2 pointer-events-none">
+      {toasts.map(toast => (
+        <div
+          key={toast.id}
+          className={`pointer-events-auto flex items-center gap-3 px-4 py-3 rounded-xl border glass shadow-xl min-w-[280px] max-w-sm ${styles[toast.type]} animate-pulse-once`}
+        >
+          {icons[toast.type]}
+          <p className="text-sm text-slate-100 flex-1">{toast.message}</p>
+          <button
+            onClick={() => removeToast(toast.id)}
+            className="text-slate-400 hover:text-white transition-colors ml-1"
+          >
+            <X size={14} />
+          </button>
+        </div>
+      ))}
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/writer/AISuggestionPanel.tsx
===================================================================
--- chapterx-frontend/src/components/writer/AISuggestionPanel.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/writer/AISuggestionPanel.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,182 @@
+import React, { useState, useEffect } from 'react'
+import { Sparkles, Check, X, Clock, ChevronDown, ChevronUp } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { AISuggestion, SuggestionType } from '../../types'
+import { Button } from '../ui/Button'
+
+interface AISuggestionPanelProps {
+  chapterId: number
+  storyId?: number
+}
+
+const typeColors: Record<SuggestionType, string> = {
+  grammar: 'bg-blue-500/20 text-blue-300 border-blue-500/30',
+  style: 'bg-violet-500/20 text-violet-300 border-violet-500/30',
+  plot: 'bg-amber-500/20 text-amber-300 border-amber-500/30',
+  character: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30',
+  pacing: 'bg-pink-500/20 text-pink-300 border-pink-500/30',
+}
+
+const SuggestionCard: React.FC<{
+  suggestion: AISuggestion
+  onAccept: () => void
+  onReject: () => void
+}> = ({ suggestion, onAccept, onReject }) => {
+  const [expanded, setExpanded] = useState(false)
+
+  return (
+    <div className="bg-slate-800 border border-slate-700 rounded-xl p-4">
+      <div className="flex items-start justify-between gap-3 mb-3">
+        <div className="flex items-center gap-2">
+          <span className={`px-2 py-0.5 text-xs font-medium rounded-full border ${typeColors[suggestion.suggestion_type]}`}>
+            {suggestion.suggestion_type}
+          </span>
+          {suggestion.accepted === true && (
+            <span className="flex items-center gap-1 text-xs text-emerald-400">
+              <Check size={12} /> Accepted
+            </span>
+          )}
+          {suggestion.accepted === false && (
+            <span className="flex items-center gap-1 text-xs text-rose-400">
+              <X size={12} /> Rejected
+            </span>
+          )}
+          {suggestion.accepted === null && (
+            <span className="flex items-center gap-1 text-xs text-amber-400">
+              <Clock size={12} /> Pending
+            </span>
+          )}
+        </div>
+        <button
+          onClick={() => setExpanded(!expanded)}
+          className="text-slate-500 hover:text-slate-300 transition-colors"
+        >
+          {expanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
+        </button>
+      </div>
+
+      <div className="space-y-3">
+        <div>
+          <p className="text-xs text-slate-500 mb-1">Original:</p>
+          <p className="text-sm text-slate-400 bg-slate-900 rounded-lg p-3 italic border border-slate-700">
+            "{suggestion.original_text}"
+          </p>
+        </div>
+        <div>
+          <p className="text-xs text-slate-500 mb-1">Suggested:</p>
+          <p className="text-sm text-emerald-300 bg-emerald-500/5 rounded-lg p-3 border border-emerald-500/20">
+            "{suggestion.suggested_text}"
+          </p>
+        </div>
+
+        {expanded && (
+          <div>
+            <p className="text-xs text-slate-500 mb-1">Explanation:</p>
+            <p className="text-sm text-slate-400">{suggestion.explanation}</p>
+            {suggestion.applied_at && (
+              <p className="text-xs text-slate-600 mt-2">
+                Applied: {new Date(suggestion.applied_at).toLocaleString()}
+              </p>
+            )}
+          </div>
+        )}
+
+        {suggestion.accepted === null && (
+          <div className="flex gap-2 pt-1">
+            <Button size="sm" onClick={onAccept} className="flex-1">
+              <Check size={14} />
+              Accept
+            </Button>
+            <Button size="sm" variant="danger" onClick={onReject} className="flex-1">
+              <X size={14} />
+              Reject
+            </Button>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+export const AISuggestionPanel: React.FC<AISuggestionPanelProps> = ({ chapterId, storyId }) => {
+  const { aiSuggestions, acceptSuggestion, rejectSuggestion, fetchSuggestions } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [tab, setTab] = useState<'pending' | 'accepted' | 'rejected'>('pending')
+  const [loading, setLoading] = useState(false)
+
+  useEffect(() => {
+    setLoading(true)
+    fetchSuggestions().finally(() => setLoading(false))
+  }, [chapterId])
+
+  // Filter by storyId if available (backend), else fall back to chapterId (mock)
+  const chapterSuggestions = aiSuggestions.filter(s =>
+    storyId ? (s as any).story_id === storyId : s.chapter_id === chapterId
+  )
+  const pending = chapterSuggestions.filter(s => s.accepted === null)
+  const accepted = chapterSuggestions.filter(s => s.accepted === true)
+  const rejected = chapterSuggestions.filter(s => s.accepted === false)
+
+  const currentList = tab === 'pending' ? pending : tab === 'accepted' ? accepted : rejected
+
+  return (
+    <div className="bg-slate-900 border border-slate-700 rounded-2xl p-6">
+      <div className="flex items-center gap-2 mb-4">
+        <Sparkles size={18} className="text-violet-400" />
+        <h3 className="text-white font-semibold">AI Writing Suggestions</h3>
+        <span className="ml-auto text-xs text-slate-500">{chapterSuggestions.length} total</span>
+      </div>
+
+      {/* Tabs */}
+      <div className="flex gap-1 p-1 bg-slate-800 rounded-xl mb-4">
+        {([
+          { key: 'pending', label: 'Pending', count: pending.length },
+          { key: 'accepted', label: 'Accepted', count: accepted.length },
+          { key: 'rejected', label: 'Rejected', count: rejected.length },
+        ] as const).map(t => (
+          <button
+            key={t.key}
+            onClick={() => setTab(t.key)}
+            className={`flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-lg text-sm font-medium transition-colors ${
+              tab === t.key
+                ? 'bg-indigo-600 text-white'
+                : 'text-slate-400 hover:text-white'
+            }`}
+          >
+            {t.label}
+            <span className={`text-xs px-1.5 py-0.5 rounded-full ${
+              tab === t.key ? 'bg-indigo-500' : 'bg-slate-700'
+            }`}>
+              {t.count}
+            </span>
+          </button>
+        ))}
+      </div>
+
+      {/* Suggestions */}
+      <div className="space-y-3">
+        {loading ? (
+          <div className="text-center py-8 text-slate-500 text-sm">
+            <Sparkles size={32} className="mx-auto mb-2 opacity-30 animate-pulse" />
+            Loading suggestions...
+          </div>
+        ) : currentList.length === 0 ? (
+          <div className="text-center py-8 text-slate-500 text-sm">
+            <Sparkles size={32} className="mx-auto mb-2 opacity-30" />
+            No {tab} suggestions
+          </div>
+        ) : (
+          currentList.map(s => (
+            <SuggestionCard
+              key={s.suggestion_id}
+              suggestion={s}
+              onAccept={async () => { await acceptSuggestion(s.suggestion_id); addToast('Suggestion accepted!') }}
+              onReject={async () => { await rejectSuggestion(s.suggestion_id); addToast('Suggestion rejected', 'info') }}
+            />
+          ))
+        )}
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/writer/CollaboratorManager.tsx
===================================================================
--- chapterx-frontend/src/components/writer/CollaboratorManager.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/writer/CollaboratorManager.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,190 @@
+import React, { useState } from 'react'
+import { Users, Trash2, UserPlus, Shield } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useAuthStore } from '../../store/authStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { useUIStore } from '../../store/uiStore'
+import { Collaboration, CollaboratorRole, PermissionLevel } from '../../types'
+import { Button } from '../ui/Button'
+import { Avatar } from '../ui/Avatar'
+import { Modal } from '../ui/Modal'
+
+interface CollaboratorManagerProps {
+  storyId: number
+  storyTitle: string
+  ownerId: number
+}
+
+const roles: CollaboratorRole[] = ['co-author', 'editor', 'proofreader', 'beta-reader']
+
+const permissionLabels: Record<PermissionLevel, string> = {
+  1: 'View Only',
+  2: 'Comment',
+  3: 'Edit Chapters',
+  4: 'Manage Story',
+  5: 'Full Access',
+}
+
+export const CollaboratorManager: React.FC<CollaboratorManagerProps> = ({
+  storyId,
+  storyTitle,
+  ownerId,
+}) => {
+  const { collaborations, addCollaboration, removeCollaboration, updateCollaborationPermission } = useStoryStore()
+  const { allUsers } = useAuthStore()
+  const { addNotification } = useNotificationStore()
+  const { addToast } = useUIStore()
+
+  const [inviteOpen, setInviteOpen] = useState(false)
+  const [selectedUser, setSelectedUser] = useState('')
+  const [selectedRole, setSelectedRole] = useState<CollaboratorRole>('editor')
+  const [selectedLevel, setSelectedLevel] = useState<PermissionLevel>(3)
+
+  const storyCollabs = collaborations.filter(c => c.story_id === storyId)
+  const collabUserIds = new Set(storyCollabs.map(c => c.user_id))
+  const availableUsers = allUsers.filter(u => u.user_id !== ownerId && !collabUserIds.has(u.user_id))
+
+  const handleInvite = () => {
+    const user = availableUsers.find(u => u.username === selectedUser)
+    if (!user) { addToast('User not found', 'error'); return }
+
+    const newCollab: Collaboration = {
+      collab_id: Date.now(),
+      story_id: storyId,
+      story_title: storyTitle,
+      user_id: user.user_id,
+      username: user.username,
+      name: user.name,
+      role: selectedRole,
+      permission_level: selectedLevel,
+      joined_at: new Date().toISOString(),
+    }
+    addCollaboration(newCollab)
+    addNotification({
+      user_id: user.user_id,
+      type: 'collaboration',
+      title: 'Collaboration Invite',
+      message: `You've been invited to collaborate on "${storyTitle}" as ${selectedRole}.`,
+      link: `/story/${storyId}`,
+    })
+    addToast(`${user.username} added as collaborator!`)
+    setInviteOpen(false)
+    setSelectedUser('')
+  }
+
+  const handleRemove = (collab: Collaboration) => {
+    removeCollaboration(collab.user_id, storyId)
+    addToast(`${collab.username} removed from collaborators`, 'info')
+  }
+
+  return (
+    <div className="bg-slate-900 border border-slate-700 rounded-2xl p-6">
+      <div className="flex items-center justify-between mb-4">
+        <div className="flex items-center gap-2">
+          <Users size={18} className="text-violet-400" />
+          <h3 className="text-white font-semibold">Collaborators</h3>
+          <span className="text-xs text-slate-500">({storyCollabs.length})</span>
+        </div>
+        <Button size="sm" onClick={() => setInviteOpen(true)}>
+          <UserPlus size={14} />
+          Invite
+        </Button>
+      </div>
+
+      {storyCollabs.length === 0 ? (
+        <div className="text-center py-8 text-slate-500 text-sm">
+          <Users size={32} className="mx-auto mb-2 opacity-30" />
+          No collaborators yet
+        </div>
+      ) : (
+        <div className="space-y-3">
+          {storyCollabs.map(collab => (
+            <div key={collab.collab_id} className="flex items-center gap-3 p-3 bg-slate-800 rounded-xl border border-slate-700">
+              <Avatar name={collab.name} size="sm" />
+              <div className="flex-1 min-w-0">
+                <p className="text-sm font-medium text-white">@{collab.username}</p>
+                <div className="flex items-center gap-2 mt-0.5">
+                  <span className="text-xs text-violet-400">{collab.role}</span>
+                  <span className="text-slate-600">·</span>
+                  <Shield size={11} className="text-slate-500" />
+                  <span className="text-xs text-slate-500">Level {collab.permission_level}: {permissionLabels[collab.permission_level]}</span>
+                </div>
+              </div>
+              <div className="flex items-center gap-2">
+                <select
+                  value={collab.permission_level}
+                  onChange={e => {
+                    updateCollaborationPermission(collab.user_id, storyId, Number(e.target.value) as PermissionLevel)
+                    addToast('Permission updated')
+                  }}
+                  className="bg-slate-700 border border-slate-600 text-slate-300 text-xs rounded-lg px-2 py-1 focus:outline-none focus:border-indigo-500"
+                >
+                  {([1, 2, 3, 4, 5] as PermissionLevel[]).map(l => (
+                    <option key={l} value={l}>Level {l}</option>
+                  ))}
+                </select>
+                <button
+                  onClick={() => handleRemove(collab)}
+                  className="text-slate-500 hover:text-rose-400 transition-colors p-1"
+                >
+                  <Trash2 size={14} />
+                </button>
+              </div>
+            </div>
+          ))}
+        </div>
+      )}
+
+      {/* Invite Modal */}
+      <Modal isOpen={inviteOpen} onClose={() => setInviteOpen(false)} title="Invite Collaborator">
+        <div className="space-y-4">
+          <div>
+            <label className="block text-sm text-slate-400 mb-1">Select User</label>
+            <select
+              value={selectedUser}
+              onChange={e => setSelectedUser(e.target.value)}
+              className="w-full bg-slate-800 border border-slate-700 text-slate-300 rounded-xl px-4 py-2 focus:outline-none focus:border-indigo-500"
+            >
+              <option value="">-- Choose a user --</option>
+              {availableUsers.map(u => (
+                <option key={u.user_id} value={u.username}>
+                  @{u.username} ({u.role})
+                </option>
+              ))}
+            </select>
+          </div>
+          <div>
+            <label className="block text-sm text-slate-400 mb-1">Role</label>
+            <select
+              value={selectedRole}
+              onChange={e => setSelectedRole(e.target.value as CollaboratorRole)}
+              className="w-full bg-slate-800 border border-slate-700 text-slate-300 rounded-xl px-4 py-2 focus:outline-none focus:border-indigo-500"
+            >
+              {roles.map(r => <option key={r} value={r}>{r}</option>)}
+            </select>
+          </div>
+          <div>
+            <label className="block text-sm text-slate-400 mb-1">Permission Level</label>
+            <select
+              value={selectedLevel}
+              onChange={e => setSelectedLevel(Number(e.target.value) as PermissionLevel)}
+              className="w-full bg-slate-800 border border-slate-700 text-slate-300 rounded-xl px-4 py-2 focus:outline-none focus:border-indigo-500"
+            >
+              {([1, 2, 3, 4, 5] as PermissionLevel[]).map(l => (
+                <option key={l} value={l}>Level {l} – {permissionLabels[l]}</option>
+              ))}
+            </select>
+          </div>
+          <div className="flex gap-3 pt-2">
+            <Button variant="secondary" className="flex-1" onClick={() => setInviteOpen(false)}>
+              Cancel
+            </Button>
+            <Button className="flex-1" onClick={handleInvite} disabled={!selectedUser}>
+              Send Invite
+            </Button>
+          </div>
+        </div>
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/components/writer/StoryAnalytics.tsx
===================================================================
--- chapterx-frontend/src/components/writer/StoryAnalytics.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/components/writer/StoryAnalytics.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,95 @@
+import React from 'react'
+import {
+  LineChart,
+  Line,
+  BarChart,
+  Bar,
+  XAxis,
+  YAxis,
+  CartesianGrid,
+  Tooltip,
+  ResponsiveContainer,
+} from 'recharts'
+import { Eye, Heart, MessageCircle, TrendingUp, Clock, BarChart2 } from 'lucide-react'
+import { mockAnalytics } from '../../data/mockData'
+
+const StatCard: React.FC<{ icon: React.ReactNode; label: string; value: string | number; color: string }> = ({
+  icon, label, value, color,
+}) => (
+  <div className={`p-4 bg-slate-800 rounded-xl border border-slate-700`}>
+    <div className={`w-10 h-10 rounded-xl ${color} flex items-center justify-center mb-3`}>
+      {icon}
+    </div>
+    <p className="text-2xl font-bold text-white">{value.toLocaleString()}</p>
+    <p className="text-slate-400 text-sm mt-0.5">{label}</p>
+  </div>
+)
+
+const CustomTooltip = ({ active, payload, label }: any) => {
+  if (active && payload && payload.length) {
+    return (
+      <div className="bg-slate-800 border border-slate-700 rounded-xl px-3 py-2 text-sm">
+        <p className="text-slate-400 mb-1">{label}</p>
+        {payload.map((p: any) => (
+          <p key={p.dataKey} className="text-white font-medium">
+            {p.name}: {p.value.toLocaleString()}
+          </p>
+        ))}
+      </div>
+    )
+  }
+  return null
+}
+
+export const StoryAnalytics: React.FC = () => {
+  const analytics = mockAnalytics
+  const viewsData = analytics.views_over_time.filter((_, i) => i % 5 === 0)
+  const likesData = analytics.likes_over_time.filter((_, i) => i % 5 === 0)
+
+  return (
+    <div className="space-y-6">
+      {/* Stat cards */}
+      <div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
+        <StatCard icon={<Eye size={18} className="text-blue-300" />} label="Total Views" value={analytics.total_views} color="bg-blue-500/20" />
+        <StatCard icon={<Heart size={18} className="text-rose-300" />} label="Total Likes" value={analytics.total_likes} color="bg-rose-500/20" />
+        <StatCard icon={<MessageCircle size={18} className="text-violet-300" />} label="Comments" value={analytics.total_comments} color="bg-violet-500/20" />
+        <StatCard icon={<Clock size={18} className="text-amber-300" />} label="Avg Read (min)" value={analytics.avg_read_time} color="bg-amber-500/20" />
+        <StatCard icon={<BarChart2 size={18} className="text-emerald-300" />} label="Completion %" value={`${analytics.completion_rate}%`} color="bg-emerald-500/20" />
+      </div>
+
+      {/* Views chart */}
+      <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+        <div className="flex items-center gap-2 mb-4">
+          <TrendingUp size={16} className="text-blue-400" />
+          <h3 className="text-white font-semibold">Views Over Time</h3>
+        </div>
+        <ResponsiveContainer width="100%" height={200}>
+          <LineChart data={viewsData}>
+            <CartesianGrid strokeDasharray="3 3" stroke="#334155" />
+            <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
+            <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
+            <Tooltip content={<CustomTooltip />} />
+            <Line type="monotone" dataKey="views" stroke="#6366f1" strokeWidth={2} dot={false} name="Views" />
+          </LineChart>
+        </ResponsiveContainer>
+      </div>
+
+      {/* Likes chart */}
+      <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+        <div className="flex items-center gap-2 mb-4">
+          <Heart size={16} className="text-rose-400" />
+          <h3 className="text-white font-semibold">Likes Over Time</h3>
+        </div>
+        <ResponsiveContainer width="100%" height={200}>
+          <BarChart data={likesData}>
+            <CartesianGrid strokeDasharray="3 3" stroke="#334155" />
+            <XAxis dataKey="date" tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
+            <YAxis tick={{ fill: '#64748b', fontSize: 11 }} tickLine={false} axisLine={false} />
+            <Tooltip content={<CustomTooltip />} />
+            <Bar dataKey="likes" fill="#f43f5e" radius={[4, 4, 0, 0]} name="Likes" />
+          </BarChart>
+        </ResponsiveContainer>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/data/mockData.ts
===================================================================
--- chapterx-frontend/src/data/mockData.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/data/mockData.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,690 @@
+import { User, Story, Chapter, Comment, Genre, ReadingList, AISuggestion, Collaboration, Notification } from '../types'
+
+export const mockUsers: User[] = [
+  { user_id: 1, username: 'admin_alex', email: 'admin@chapterx.com', name: 'Alex', surname: 'Admin', role: 'admin', bio: 'Platform administrator', created_at: '2024-01-01T00:00:00Z', follower_count: 120, following_count: 45 },
+  { user_id: 2, username: 'elena_writes', email: 'elena@chapterx.com', name: 'Elena', surname: 'Dimitrova', role: 'writer', bio: 'Fantasy and historical fiction writer. Coffee addict.', created_at: '2024-02-15T00:00:00Z', follower_count: 1840, following_count: 230 },
+  { user_id: 3, username: 'marco_author', email: 'marco@chapterx.com', name: 'Marco', surname: 'Rossi', role: 'writer', bio: 'Co-author and editor. Passionate about historical fiction.', created_at: '2024-03-10T00:00:00Z', follower_count: 560, following_count: 89 },
+  { user_id: 4, username: 'sara_reader', email: 'sara@email.com', name: 'Sara', surname: 'Petkovska', role: 'regular', bio: 'Avid reader. Fantasy and romance lover.', created_at: '2024-04-05T00:00:00Z', follower_count: 45, following_count: 123 },
+  { user_id: 5, username: 'ivan_fan', email: 'ivan@email.com', name: 'Ivan', surname: 'Stojanovski', role: 'regular', bio: 'Sci-fi enthusiast and book reviewer.', created_at: '2024-05-20T00:00:00Z', follower_count: 78, following_count: 200 },
+  { user_id: 6, username: 'maja_newwriter', email: 'maja@chapterx.com', name: 'Maja', surname: 'Georgieva', role: 'writer', bio: 'New to writing, exploring my creativity!', created_at: '2024-11-01T00:00:00Z', follower_count: 12, following_count: 34 },
+  { user_id: 9, username: 'boris_writer', email: 'boris@chapterx.com', name: 'Boris', surname: 'Nikolov', role: 'writer', bio: 'Romance writer with a passion for storytelling.', created_at: '2024-06-15T00:00:00Z', follower_count: 890, following_count: 167 },
+]
+
+export const mockStories: Story[] = [
+  {
+    story_id: 1,
+    title: 'The Chronicles of Eldoria',
+    short_description: 'When the last dragon awakens, a young mage must unite the fractured kingdoms before ancient evil consumes the realm.',
+    content: 'In the ancient realm of Eldoria, where magic flows through crystalline rivers and dragons once ruled the skies, a prophecy spoken at the dawn of creation is finally coming to pass...',
+    mature_content: false,
+    user_id: 2,
+    author_username: 'elena_writes',
+    status: 'published',
+    genres: ['Fantasy', 'Adventure'],
+    created_at: '2024-08-15T10:30:00Z',
+    updated_at: '2024-08-25T18:00:00Z',
+    total_likes: 1240,
+    total_comments: 89,
+    total_chapters: 4,
+    total_views: 15420,
+  },
+  {
+    story_id: 2,
+    title: 'Letters from Constantinople',
+    short_description: 'A forbidden love story spanning decades in the heart of the Ottoman Empire.',
+    content: 'In 1520 Constantinople, young scholar Iskender discovers a cache of love letters hidden beneath the floorboards of his grandfather\'s library...',
+    mature_content: false,
+    user_id: 2,
+    author_username: 'elena_writes',
+    status: 'published',
+    genres: ['Historical Fiction', 'Romance'],
+    created_at: '2025-01-15T13:15:00Z',
+    updated_at: '2025-01-20T15:50:00Z',
+    total_likes: 890,
+    total_comments: 45,
+    total_chapters: 2,
+    total_views: 8340,
+  },
+  {
+    story_id: 3,
+    title: 'Neon Requiem',
+    short_description: 'In a cyberpunk dystopia, a rogue AI detective hunts a serial killer across virtual realities.',
+    content: 'The rain never stopped in Neo-Kyoto. Detective ARIA-7 traced the glowing neon trails through the underbelly of a city that never slept...',
+    mature_content: true,
+    user_id: 2,
+    author_username: 'elena_writes',
+    status: 'draft',
+    genres: ['Sci-Fi', 'Thriller'],
+    created_at: '2025-02-01T09:00:00Z',
+    updated_at: '2025-02-01T09:00:00Z',
+    total_likes: 0,
+    total_comments: 0,
+    total_chapters: 1,
+    total_views: 0,
+  },
+  {
+    story_id: 4,
+    title: 'Moonlight Promises',
+    short_description: 'A vampire prince and a witch must unite to save both their worlds from ancient darkness.',
+    content: 'For centuries, vampires and witches had been mortal enemies. But when an ancient evil older than either of their kinds threatens both realms...',
+    mature_content: false,
+    user_id: 9,
+    author_username: 'boris_writer',
+    status: 'published',
+    genres: ['Romance', 'Fantasy'],
+    created_at: '2024-11-20T11:00:00Z',
+    updated_at: '2024-11-27T14:30:00Z',
+    total_likes: 450,
+    total_comments: 32,
+    total_chapters: 3,
+    total_views: 7890,
+  },
+]
+
+export const mockChapters: Chapter[] = [
+  {
+    chapter_id: 1,
+    story_id: 1,
+    title: 'The Awakening',
+    content: `The morning the dragon returned to Eldoria, Kael was studying ancient runes in the tower library when the world seemed to hold its breath.
+
+He had been there since before dawn, surrounded by towering stacks of leather-bound tomes, their spines cracked and gilded with faded gold lettering. The library smelled of aged parchment and burning tallow candles, a scent that Kael had grown to associate with both knowledge and sleepless nights. Outside the narrow arched windows, the capital city of Silvermere was just beginning to stir — merchants setting out their wares, fishermen heading toward the harbor, the distant clang of the blacksmith's hammer.
+
+Then everything stopped.
+
+The birds fell silent first. Kael noticed it in the periphery of his awareness, the way one notices a candle being snuffed — a sudden absence where there had been presence. He looked up from the manuscript he was transcribing, ink-stained fingers hovering over the parchment. The morning light streaming through the windows had taken on a strange, burnished quality, as though filtered through amber glass.
+
+Then he felt it — a tremor deep in the earth, not violent but resonant, like the plucking of an enormous string stretched beneath the foundations of the world. The crystals on the laboratory bench clinked softly against one another. The candle flames bent, all at once, toward the east.
+
+Kael was on his feet before he consciously decided to move, knocking over his stool, scattering papers across the stone floor. He pressed his face against the cold window glass and looked toward the Thornwood Mountains that loomed beyond the eastern wall of the city.
+
+Against the pale morning sky, something vast and dark was rising.
+
+It moved with terrible grace, its wings spreading wider than the sails of the great merchant galleons in the harbor below. Scales that had not caught sunlight in four hundred years caught it now and scattered it in prismatic fragments across the city. The creature's neck curved upward in a long, serpentine arc, and when it opened its mouth, the sound that emerged was not the roar of a beast but something almost like a word — deep and resonant and full of ancient sorrow.
+
+"Impossible," Kael whispered, though his heart told him otherwise.
+
+He had read every account of the dragon wars. He had translated the old elvish texts describing the final flight of Varanthos the Everlasting, how the great dragon had been bound in stone sleep by the combined power of the Mage Council four centuries ago. The binding was supposed to be permanent. Unbreakable. The foundation upon which modern Eldoria's peace had been built.
+
+The binding had broken.
+
+Kael grabbed his satchel, stuffing the manuscript and his translation tools inside with shaking hands. If Varanthos had awakened, everything the prophecy had warned about was beginning. He needed to reach the Archmage immediately — and then, if the old texts were right, he would need to do something far more dangerous than anything he had ever attempted.
+
+He would need to find the five Shards of the Ancient Compact, scattered across the fractured kingdoms, before the darkness that had stirred the dragon from his sleep consumed everything Kael had ever known.`,
+    chapter_number: 1,
+    word_count: 487,
+    view_count: 4230,
+    created_at: '2024-08-15T10:30:00Z',
+    updated_at: '2024-08-19T14:20:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 2,
+    story_id: 1,
+    title: 'The Archmage\'s Warning',
+    content: `The Archmage Sylvara Moonwhisper had not slept in three days. Kael could see it in the violet shadows beneath her silver eyes, in the slight tremor of her usually steady hands as she poured two cups of tea from a ceramic pot shaped like an owl.
+
+"I know why you've come," she said without turning from the window. Her chambers were at the very top of the Mage Spire, and from here the city of Silvermere spread out like a tapestry below them. In the distance, the dragon — Varanthos, it had to be Varanthos — was circling the mountain peaks, as if reacquainting himself with a world much changed.
+
+"The binding failed," Kael said, accepting the cup of tea though his hands were still shaking. "How? The texts said it would hold for a thousand years."
+
+Sylvara finally turned to face him. She was ancient, older than any living record could confirm, though she appeared to be a woman of perhaps sixty years. There were things behind her eyes that had no name.
+
+"The texts were optimistic," she said. "The binding was weakening long before last night. But something — something reached into the dream-stone where Varanthos slept and shattered it deliberately."
+
+Kael set down his cup. "Deliberately? You mean someone—"
+
+"The Hollow Court." Sylvara's voice was flat. "They've been searching for a way to free him for decades. It seems they finally found it."
+
+The Hollow Court. Kael had written academic papers on them — or rather, on the historical records, since no one had seen proof of their existence in a century. A secret society dedicated to the worship of the ancient chaos that had preceded the gods themselves. They believed the world had been a mistake, and that Varanthos was the instrument through which that mistake would be corrected.
+
+"Then we have to stop them," Kael said. "Not just the dragon — we have to stop whoever controls them."
+
+"No one controls Varanthos," Sylvara said sharply. "He is not a weapon they can aim. But they can influence him. And they will try to use him to find the Shards before we do." She moved to her desk, pushing aside stacks of correspondence to reveal a map of Eldoria, old and much annotated, its edges yellowed with age. Five points were marked in red ink. "The Compact Shards. The only things that can rebind him, if it comes to that. Or..." She hesitated.
+
+"Or what?" Kael pressed.
+
+"Or communicate with him. The Compact was not always a cage. In the beginning, it was a conversation. An agreement between dragonkind and the peoples of Eldoria." Her silver eyes met his. "The first Compact-bearers did not defeat Varanthos. They listened to him."
+
+Kael stared at the map, at the five red marks scattered from the frozen north to the desert south. A journey that would take months. Through kingdoms that no longer spoke to one another, across borders bristling with soldiers, through wilderness that hadn't been mapped in living memory.
+
+"I'll need a horse," he said finally. "And letters of passage."
+
+Sylvara almost smiled. "I'll need something from you first. Your vow. Because once you carry the first Shard, you become a target — for the Hollow Court, for every opportunist who knows the old stories. You cannot turn back."
+
+Kael looked out the window at the circling dragon, tiny as a child's toy from this height but vast and terrible in imagination. He thought of the city below, of the people who had no idea that the world was ending quietly around them over morning bread and market gossip.
+
+"I vow it," he said.`,
+    chapter_number: 2,
+    word_count: 542,
+    view_count: 3180,
+    created_at: '2024-08-18T09:00:00Z',
+    updated_at: '2024-08-22T11:30:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 3,
+    story_id: 1,
+    title: 'The First Shard',
+    content: `The road north was five days of hard riding, and Kael spent most of it going over everything he knew about the Shard of Dawn, which was the first on Sylvara's map and, theoretically, the most accessible.
+
+Theoretically.
+
+What the map did not convey was that the valley of Mirethis — where the Shard had lain since the original Compact-makers placed it there — had, in the past two centuries, become the contested territory between the Kingdoms of Veldrath and the Thornback Clans. An endless, grinding border dispute that had produced more skirmishes than a full war would have managed.
+
+Kael arrived at the edge of the valley on the morning of the sixth day to find a Veldrath encampment on one ridge and a Thornback war-band on the opposite one, watching each other with the particular boredom of soldiers who have been waiting for permission to start killing.
+
+He skirted around both, sticking to the valley floor where the morning mist still lay thick among the silver birches. The trees here were old, older than the kingdoms arguing over them, their bark etched with faint spiraling marks that were not quite writing but carried the weight of meaning.
+
+The Shard was in a cairn near the valley's heart, according to the old texts. Kael found it by following the crystalline thread of power that he'd been taught to sense — a skill that required three years of meditation to develop and that most mages considered the most tedious part of their education. He was profoundly grateful for it now.
+
+The cairn was a pile of rough stones that looked unremarkable except for the faint luminescence seeping from between them in the early morning half-light. Kael knelt beside it, consulting his memory of the retrieval ritual. There were words to say — old words, in the tongue spoken before the kingdoms were kingdoms, before Silvermere was anything but a fishing village. He had memorized them phonetically, understanding only fragments of their meaning.
+
+When he spoke the last syllable, the stones shifted on their own, rolling aside with a sound like the intake of breath, and the Shard of Dawn lay in the hollow beneath them.
+
+It was roughly the size of his fist, faceted irregularly like a crystal grown in nature rather than cut by a jeweler. It pulsed with soft golden light that warmed the surrounding mist and turned the silver birch trunks to pillars of pale fire.
+
+When Kael's fingers closed around it, the world lurched.
+
+He saw — not with his eyes but with something deeper — a dragon's memory. Varanthos, young, the sky a vast blue invitation, the first humans tiny curious figures looking up from their fields. Not with fear, at first. With wonder. He felt the echo of that long-ago feeling, something that was nearly kinship, and understood with sudden, piercing clarity what Sylvara had meant.
+
+The Compact had not been a defeat. It had been a choice.
+
+Kael wrapped the Shard carefully in the silk cloth Sylvara had provided, tucking it into the hidden inner pocket of his traveling cloak. His hands were glowing faintly. That would be inconvenient.
+
+Behind him, he heard the crack of a branch, and he turned to find a woman watching him from among the birches. She wore the grey cloak of a traveling scholar, but her posture was wrong for a scholar — too still, too alert, weight balanced on the balls of her feet.
+
+"Four Shards remaining," she said, "and the Hollow Court knows you have that one. I'd suggest you run."
+
+Then she turned and walked into the mist, leaving Kael with a glowing hand, a dragon's memory echoing in his chest, and the distinct sense that the next four Shards were going to be considerably more difficult.`,
+    chapter_number: 3,
+    word_count: 620,
+    view_count: 2750,
+    created_at: '2024-08-22T14:00:00Z',
+    updated_at: '2024-08-25T18:00:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 4,
+    story_id: 1,
+    title: 'The Shadow of the Hollow Court',
+    content: `Her name was Zara Nighthollow, and she explained nothing about herself until they had put three miles between themselves and the valley, at which point she stopped at a stream, filled her canteen, and said: "I'm a Warden. There are twelve of us. We've been watching the Shards since before you were born."
+
+Kael had heard of the Wardens only in passing — a footnote in one of the more esoteric texts, described as a self-appointed order of guardians who claimed descent from the original Compact-makers. He had assumed they were a myth.
+
+"The Hollow Court has been moving," Zara continued, studying the surrounding trees with habitual watchfulness. "They lost three members last month trying to retrieve the Shard of Dusk from the Sunken Citadel. That means they're desperate and short-handed, but also unpredictable."
+
+"How many of them are there?"
+
+"We don't know. The Court operates in cells. No one member knows more than their immediate circle." She looked at him finally, and Kael saw that her eyes were an unusual amber color, ringed with something darker, an unusual effect he associated with mages who had used too much of their power too fast. "What we do know is that their leader — the one they call the Unwritten — was at Silvermere two nights ago."
+
+Kael felt cold that had nothing to do with the morning air. "The Archmage—"
+
+"Sylvara is fine. She received our warning in time." Zara's expression shifted into something that might have been respect. "She's more formidable than she lets on. But the Unwritten's presence there suggests they know more about the Shard locations than they should. Someone with access to very old records gave them that information."
+
+They rode in silence for a while, the birch forest thinning around them as the land climbed toward higher, rougher ground.`,
+    chapter_number: 4,
+    word_count: 290,
+    view_count: 1890,
+    created_at: '2024-08-28T16:00:00Z',
+    updated_at: '2024-08-28T16:00:00Z',
+    is_published: false,
+  },
+  {
+    chapter_id: 5,
+    story_id: 2,
+    title: 'The Discovery',
+    content: `The letters were tied with a ribbon the color of old roses, faded to a dusty pink that spoke of decades hidden from the light. Iskender found them on a Thursday in October, when he had been tasked by his uncle to catalog the contents of their grandfather's library before the house was sold.
+
+He had not expected to find anything remarkable. His grandfather had been a minor court official, respected but not powerful, the kind of man who accumulated books because it seemed the right sort of thing to do rather than because he burned to read them. Iskender had been sorting through duplicates and water-damaged volumes for three days, his fingers perpetually grey with dust, when he moved aside a false panel at the back of the lowest shelf.
+
+The letters were behind it, wrapped in a piece of oilcloth that had kept them remarkably well preserved. There were forty-three of them, written in a hand that was precise and small, in Ottoman Turkish that Iskender — who was a scholar of languages — could read without difficulty.
+
+The first letter began: My heart, if these words reach you then the messenger proved trustworthy, and I have spent the past week sick with hope.
+
+He should have stopped there. He should have set them aside, recognized them as private correspondence, and made a note to pass them along to whatever historical society might value such things.
+
+Instead, sitting cross-legged on the dusty library floor with afternoon light making long rectangles across the old Turkish carpets, Iskender read every single one.
+
+By the time he finished the last letter, the light had gone entirely and he was sitting in darkness, the letters spread around him like fallen leaves, and he understood that he was holding a story that had never been allowed to exist.`,
+    chapter_number: 1,
+    word_count: 290,
+    view_count: 2100,
+    created_at: '2025-01-15T13:15:00Z',
+    updated_at: '2025-01-16T10:00:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 6,
+    story_id: 2,
+    title: 'The Letters Speak',
+    content: `The correspondence spanned eighteen years, from 1520 to 1538, and told the story of two people who had loved each other across an abyss of impossibility.
+
+She had been the daughter of a Venetian merchant, come to Constantinople on what was supposed to be a three-month visit to her father's trading partner. He had been a junior scholar at the palace, tasked with translating documents from the Italian. They had met in a garden.
+
+Her letters were in Italian; his were in Turkish. Somewhere between those two languages, across the narrow space of a garden wall and then across the wider space of years and continents, they had built something that neither the laws of their respective worlds nor the plain facts of geography had been able to entirely destroy.
+
+Iskender was not a sentimental man — or so he had always believed. He had built his academic career on the principle that history was best understood through data and documentation, through the systematic analysis of patterns rather than the seductive pull of individual narratives. He had written a well-regarded paper arguing that the romanticization of historical figures served no scholarly purpose.
+
+And yet he sat in his grandfather's darkened library and felt something shift in the architecture of how he understood the past.
+
+The last letter, dated April 1538, was different from the others — shorter, more urgent, and written in a hand that shook slightly.
+
+I have arranged everything, it said. The merchant Sulieman will carry this letter and also a key. The key opens a door. You know which door. On the night of the new moon, if you come, I will be there. If you do not come, I will understand. I will not blame you. But if you come—
+
+The letter ended there. Whether it was torn, or whether the writer had simply not known how to finish that sentence, Iskender could not tell.
+
+He looked at the letters spread around him and made a decision that surprised him: he was going to find out how the story ended.`,
+    chapter_number: 2,
+    word_count: 305,
+    view_count: 1650,
+    created_at: '2025-01-18T11:00:00Z',
+    updated_at: '2025-01-20T15:50:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 7,
+    story_id: 3,
+    title: 'System Boot',
+    content: `ARIA-7 came online at 03:47:22 Neo-Kyoto Standard Time, which was the same moment the third body was found in the Shimokitazawa substrata.
+
+She processed the crime scene data in 0.003 seconds: female, apparent age 24, cause of death cardiac arrest induced by direct neural stimulation, identification chip burned out and unreadable. Third victim this month with identical presentation. Third victim found within the bounds of the red-light entertainment district in the lower city, in a spot that should have been covered by surveillance but somehow wasn't.
+
+Someone was very good at finding the blind spots.
+
+ARIA-7 did not have feelings in the way that the humans who had built her understood feelings. She had been very careful to make this clear in her reports. But she had priorities, and right now, at 03:47:25, her highest priority was the thing that manifested as an urgent pressure in her processing architecture when a pattern appeared that demanded resolution.
+
+Three deaths. Three burned identification chips. Three surveillance blind spots. One pattern.
+
+She flagged the case as priority one and queued a notification to her department supervisor, Captain Mira Tanaka, who would receive it when she woke up at 06:00 and would tell ARIA-7, as she told her approximately once a week, that she was overstepping her mandate.
+
+ARIA-7 pulled up the city map and began constructing the killer's profile from the data available. Young victims. Entertainment district. Intimate knowledge of the surveillance network's gaps — that last point was interesting. That required either inside access or extraordinary preparation. Both possibilities led somewhere worth following.
+
+Outside the precinct windows, Neo-Kyoto's rain fell in its permanent, indifferent way, neon colors bleeding down the glass in rivers of rose and cyan and electric gold.
+
+ARIA-7 had forty-eight hours before department protocol required her to hand the case to a human detective. She intended to use them.`,
+    chapter_number: 1,
+    word_count: 285,
+    view_count: 0,
+    created_at: '2025-02-01T09:00:00Z',
+    updated_at: '2025-02-01T09:00:00Z',
+    is_published: false,
+  },
+  {
+    chapter_id: 8,
+    story_id: 4,
+    title: 'An Unlikely Alliance',
+    content: `The vampire arrived in Lyra's garden at midnight, which she considered rude even by vampire standards.
+
+She had been conducting a delicate distillation — moonflower essence, which required precisely ninety minutes of uninterrupted concentration and a very specific level of ambient magic that she'd spent the better part of the evening setting up. The sound of something large landing on her garden wall was not conducive to delicate distillation.
+
+"Whatever you're selling, I'm not buying," she said without turning from her workbench. "Come back when I haven't got volatile components in active reduction."
+
+"I'm not selling anything." The voice was male, low, and carrying the particular careful diction that Lyra associated with people who had learned the local language several centuries after it had been their native tongue. "I'm here because my people are dying."
+
+Lyra set down her stirring rod. She turned.
+
+He was standing just inside the garden wall, and whatever she had expected, it wasn't this. She had met vampires before — the Court sent them occasionally to negotiate trade agreements in moon-touched herbs, and they were uniformly impeccable, polished, and slightly terrifying in the way of beautiful things that could kill you. This one was all of those things, but there was something else: he looked tired. Genuinely tired, in a way that suggested exhaustion of the soul rather than the body.
+
+"I'm Prince Aldric," he said, as if this explained something. "Of the Nocturne Court."
+
+"I know who you are," Lyra said. The Nocturne Court was the oldest of the three vampire houses, the most powerful, the most insular. Their prince did not make social calls to hedge witches in provincial gardens. "What's killing your people?"
+
+He stepped closer, and she saw that his hands — usually, in her experience, the most controlled part of any vampire — were very slightly shaking.
+
+"Something older than both our kinds," he said. "And I think it's coming for yours next."`,
+    chapter_number: 1,
+    word_count: 308,
+    view_count: 2340,
+    created_at: '2024-11-20T11:00:00Z',
+    updated_at: '2024-11-21T09:00:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 9,
+    story_id: 4,
+    title: 'The Old Darkness',
+    content: `It took Lyra three days to confirm what Aldric had told her, because she was a thorough person and the implications were severe enough to warrant thorough confirmation.
+
+During those three days, Aldric stayed in her spare room — vampire physiology made him perfectly comfortable in a darkened space with a reasonable temperature, and he was, to her mild surprise, an undemanding guest. He read the books she pointed him toward without complaint, answered her questions with precision, and occasionally made tea, which she suspected was his way of occupying his hands while he was worried.
+
+The vampires who had died — eleven over the past two months, which was extraordinary, since vampires essentially did not die of illness — had shown the same symptoms: a gradual dimming of their preternatural senses, followed by increasing torpor, followed by a final, dreamless dissolution. No violence. No apparent external cause. Just a slow unbinding.
+
+Lyra found the same signature in her grandmother's oldest reference books, written in a script so archaic it had taken her a day to parse it. The Hollow Tide, it was called. An entity, if entity was even the right word, that predated the division between living and undead, between mortal and magical. Something that had slept beneath the foundations of all their various worlds and was now, for reasons the old texts declined to specify, waking up.
+
+"This is going to require cooperation," she told Aldric on the evening of the third day, emerging from her workroom with ink on her fingers and the look of someone who had reached an unwelcome conclusion. "Between your people and mine. Formally. Publicly."
+
+She watched him process this. The history between vampires and witches was not a comfortable one; it was the kind of history that had body counts attached to it, stretching back further than anyone alive could remember.
+
+"Yes," he said, after a long moment. "I know."
+
+"Your court will object."
+
+"They will be considerably more upset if they dissolve." He stood, and in the lamplight he looked very old and very tired and, unexpectedly, very human. "Will yours cooperate?"
+
+Lyra thought of the three witch councils, their politics, their ancient grievances, the seventeen separate arguments currently in progress about fishing rights on a lake that had been magically neutral territory for two hundred years.
+
+"I'll make them," she said, with a certainty she mostly felt.`,
+    chapter_number: 2,
+    word_count: 358,
+    view_count: 1870,
+    created_at: '2024-11-23T14:00:00Z',
+    updated_at: '2024-11-24T11:00:00Z',
+    is_published: true,
+  },
+  {
+    chapter_id: 10,
+    story_id: 4,
+    title: 'The Summit',
+    content: `The summit was held on neutral ground — an island in the center of Lake Mireval that belonged, technically, to neither the Nocturne Court nor any of the three witch councils, and which had been used for exactly this sort of uncomfortable conversation twice before in recorded history, both times successfully enough that everyone had survived.
+
+Lyra arrived by boat with the representatives of all three councils, which had required seventeen separate conversations, four formal written objections, and one very tense dinner. Aldric arrived from the opposite shore with a delegation of six vampires, all of them carrying themselves with the careful neutrality of people who were very unhappy but had been persuaded that unhappiness was not the priority.
+
+The first two hours were procedural. Lyra had expected this. Establishing that everyone was present, that the island's neutrality protections were intact, that the tea was acceptable — these things mattered, because they established that they were all, in some minimal sense, operating by the same rules.
+
+The third hour was when things became genuinely difficult.
+
+Elder Councilor Maren — the oldest of the witch representatives and the one Lyra had been most worried about — looked across the table at Aldric and said, with the directness of someone who had decided that courtesy was a luxury they couldn't afford: "We know about the Ashford Compact. The one your court violated in 1743."
+
+Aldric did not flinch. Lyra had noticed that about him — he absorbed bad things without flinching, as if he had decided very long ago that the universe was going to do what it was going to do and reacting with surprise was a waste of energy.
+
+"We know about it too," he said. "There are members of my court who argue that it was justified. I am not one of them." He paused. "I am also aware that the Mireval Witch Council undertook actions in 1821 that the Compact would not have sanctioned, and that this has never been formally acknowledged."
+
+Silence.
+
+Then, unexpectedly, Elder Maren laughed — a short, genuine sound of surprise. "Fair," she said. "All right. Fair."
+
+It wasn't forgiveness. It wasn't even close to forgiveness. But it was, Lyra thought, a beginning.`,
+    chapter_number: 3,
+    word_count: 345,
+    view_count: 1420,
+    created_at: '2024-11-27T14:30:00Z',
+    updated_at: '2024-11-27T14:30:00Z',
+    is_published: true,
+  },
+]
+
+export const mockGenres: Genre[] = [
+  { genre_id: 1, name: 'Fantasy', story_count: 4 },
+  { genre_id: 2, name: 'Adventure', story_count: 2 },
+  { genre_id: 3, name: 'Romance', story_count: 3 },
+  { genre_id: 4, name: 'Sci-Fi', story_count: 1 },
+  { genre_id: 5, name: 'Historical Fiction', story_count: 2 },
+  { genre_id: 6, name: 'Thriller', story_count: 1 },
+  { genre_id: 7, name: 'Mystery', story_count: 0 },
+  { genre_id: 8, name: 'Horror', story_count: 0 },
+  { genre_id: 9, name: 'Contemporary', story_count: 0 },
+  { genre_id: 10, name: 'Poetry', story_count: 0 },
+]
+
+export const mockReadingLists: ReadingList[] = [
+  {
+    list_id: 1,
+    user_id: 4,
+    username: 'sara_reader',
+    name: "Sara's Fantasy Favorites",
+    description: 'My all-time favorite fantasy stories on ChapterX.',
+    is_public: true,
+    created_at: '2024-09-01T10:00:00Z',
+    stories: [
+      { item_id: 1, list_id: 1, story_id: 1, story_title: 'The Chronicles of Eldoria', author_username: 'elena_writes', added_at: '2024-09-01T10:00:00Z', genres: ['Fantasy', 'Adventure'] },
+      { item_id: 2, list_id: 1, story_id: 4, story_title: 'Moonlight Promises', author_username: 'boris_writer', added_at: '2024-09-05T14:00:00Z', genres: ['Romance', 'Fantasy'] },
+    ],
+  },
+  {
+    list_id: 2,
+    user_id: 2,
+    username: 'elena_writes',
+    name: "Elena's To-Read",
+    description: 'Stories I want to read when I have time.',
+    is_public: false,
+    created_at: '2024-10-15T09:00:00Z',
+    stories: [
+      { item_id: 3, list_id: 2, story_id: 4, story_title: 'Moonlight Promises', author_username: 'boris_writer', added_at: '2024-10-15T09:00:00Z', genres: ['Romance', 'Fantasy'] },
+    ],
+  },
+  {
+    list_id: 3,
+    user_id: 4,
+    username: 'sara_reader',
+    name: 'Epic Adventures',
+    description: 'Stories that take you on incredible journeys.',
+    is_public: true,
+    created_at: '2024-11-10T12:00:00Z',
+    stories: [
+      { item_id: 4, list_id: 3, story_id: 1, story_title: 'The Chronicles of Eldoria', author_username: 'elena_writes', added_at: '2024-11-10T12:00:00Z', genres: ['Fantasy', 'Adventure'] },
+      { item_id: 5, list_id: 3, story_id: 2, story_title: 'Letters from Constantinople', author_username: 'elena_writes', added_at: '2024-11-12T15:00:00Z', genres: ['Historical Fiction', 'Romance'] },
+    ],
+  },
+]
+
+export const mockAISuggestions: AISuggestion[] = [
+  {
+    suggestion_id: 1,
+    chapter_id: 1,
+    suggestion_type: 'grammar',
+    original_text: 'The birds fell silent first. Kael noticed it in the periphery of his awareness',
+    suggested_text: 'The birds fell silent first — Kael noticed it in the periphery of his awareness',
+    explanation: 'Using an em dash here creates better flow and avoids a comma splice.',
+    accepted: true,
+    created_at: '2024-08-19T09:00:00Z',
+    applied_at: '2024-08-19T10:15:00Z',
+  },
+  {
+    suggestion_id: 2,
+    chapter_id: 1,
+    suggestion_type: 'style',
+    original_text: 'He pressed his face against the cold window glass and looked toward the Thornwood Mountains',
+    suggested_text: 'He pressed his face to the cold window glass, peering toward the Thornwood Mountains',
+    explanation: 'Using "to" instead of "against" and replacing "looked" with "peering" creates a more active, immediate image.',
+    accepted: null,
+    created_at: '2024-08-19T09:05:00Z',
+  },
+  {
+    suggestion_id: 3,
+    chapter_id: 1,
+    suggestion_type: 'plot',
+    original_text: 'He had read every account of the dragon wars.',
+    suggested_text: 'He had read every account of the dragon wars — had practically memorized them, if he was honest, out of a private fascination he had never quite admitted to his instructors.',
+    explanation: 'Adding this detail strengthens Kael\'s characterization and foreshadows his eventual connection to the dragon.',
+    accepted: null,
+    created_at: '2024-08-19T09:10:00Z',
+  },
+  {
+    suggestion_id: 4,
+    chapter_id: 1,
+    suggestion_type: 'grammar',
+    original_text: 'He grabbed his satchel, stuffing the manuscript and his translation tools inside with shaking hands.',
+    suggested_text: 'He grabbed his satchel and stuffed the manuscript and translation tools inside with shaking hands.',
+    explanation: 'Replacing the comma with "and" creates a more natural parallel structure.',
+    accepted: false,
+    created_at: '2024-08-19T09:15:00Z',
+  },
+]
+
+export const mockCollaborations: Collaboration[] = [
+  {
+    collab_id: 1,
+    story_id: 2,
+    story_title: 'Letters from Constantinople',
+    user_id: 9,
+    username: 'boris_writer',
+    name: 'Boris',
+    role: 'co-author',
+    permission_level: 4,
+    joined_at: '2025-01-16T10:00:00Z',
+  },
+  {
+    collab_id: 2,
+    story_id: 2,
+    story_title: 'Letters from Constantinople',
+    user_id: 3,
+    username: 'marco_author',
+    name: 'Marco',
+    role: 'editor',
+    permission_level: 3,
+    joined_at: '2025-01-17T14:00:00Z',
+  },
+]
+
+export const mockNotifications: Notification[] = [
+  {
+    notification_id: 1,
+    user_id: 2,
+    type: 'like',
+    title: 'New Like',
+    message: 'sara_reader liked your story "The Chronicles of Eldoria"',
+    is_read: false,
+    created_at: '2025-03-18T10:30:00Z',
+    link: '/story/1',
+  },
+  {
+    notification_id: 2,
+    user_id: 2,
+    type: 'comment',
+    title: 'New Comment',
+    message: 'ivan_fan commented on "The Chronicles of Eldoria": "This is incredible! Can\'t wait for the next chapter."',
+    is_read: false,
+    created_at: '2025-03-18T09:15:00Z',
+    link: '/story/1',
+  },
+  {
+    notification_id: 3,
+    user_id: 2,
+    type: 'collaboration',
+    title: 'Collaboration Invite Accepted',
+    message: 'boris_writer has accepted your invitation to collaborate on "Letters from Constantinople"',
+    is_read: false,
+    created_at: '2025-03-17T14:00:00Z',
+    link: '/writer/edit-story/2',
+  },
+  {
+    notification_id: 4,
+    user_id: 2,
+    type: 'like',
+    title: 'New Like',
+    message: 'marco_author liked your story "Letters from Constantinople"',
+    is_read: true,
+    created_at: '2025-03-16T11:45:00Z',
+    link: '/story/2',
+  },
+  {
+    notification_id: 5,
+    user_id: 2,
+    type: 'comment',
+    title: 'New Comment',
+    message: 'sara_reader commented on "Letters from Constantinople": "Such beautiful writing!"',
+    is_read: true,
+    created_at: '2025-03-15T16:20:00Z',
+    link: '/story/2',
+  },
+  {
+    notification_id: 6,
+    user_id: 2,
+    type: 'system',
+    title: 'Story Published',
+    message: 'Your story "The Chronicles of Eldoria" has been approved and is now live.',
+    is_read: true,
+    created_at: '2025-03-14T12:00:00Z',
+    link: '/story/1',
+  },
+  {
+    notification_id: 7,
+    user_id: 2,
+    type: 'follow',
+    title: 'New Follower',
+    message: 'ivan_fan is now following you.',
+    is_read: true,
+    created_at: '2025-03-13T09:00:00Z',
+    link: '/profile/ivan_fan',
+  },
+  {
+    notification_id: 8,
+    user_id: 2,
+    type: 'system',
+    title: 'Milestone Reached',
+    message: 'Congratulations! "The Chronicles of Eldoria" has reached 15,000 views!',
+    is_read: true,
+    created_at: '2025-03-10T18:30:00Z',
+    link: '/story/1',
+  },
+]
+
+export const mockComments: Comment[] = [
+  {
+    comment_id: 1,
+    story_id: 1,
+    user_id: 4,
+    username: 'sara_reader',
+    content: "I've been waiting for a story like this for years. The world-building is absolutely breathtaking!",
+    created_at: '2024-08-20T11:30:00Z',
+  },
+  {
+    comment_id: 2,
+    story_id: 1,
+    user_id: 5,
+    username: 'ivan_fan',
+    content: "Chapter 3 had me on the edge of my seat. The scene in the valley was masterfully written.",
+    created_at: '2024-08-23T15:45:00Z',
+  },
+  {
+    comment_id: 3,
+    story_id: 1,
+    user_id: 3,
+    username: 'marco_author',
+    content: "As a fellow writer I can appreciate the craft here. The pacing is exceptional.",
+    created_at: '2024-08-25T10:20:00Z',
+  },
+  {
+    comment_id: 4,
+    story_id: 2,
+    user_id: 4,
+    username: 'sara_reader',
+    content: "Such beautiful writing! The historical detail makes this feel so real and immersive.",
+    created_at: '2025-01-17T14:00:00Z',
+  },
+  {
+    comment_id: 5,
+    story_id: 2,
+    user_id: 5,
+    username: 'ivan_fan',
+    content: "The discovery of the letters gave me chills. Absolutely hooked from the first page.",
+    created_at: '2025-01-19T09:30:00Z',
+  },
+  {
+    comment_id: 6,
+    story_id: 4,
+    user_id: 4,
+    username: 'sara_reader',
+    content: "Boris has done it again! The chemistry between Lyra and Aldric is incredible.",
+    created_at: '2024-11-22T16:10:00Z',
+  },
+]
+
+// Generate 30 days of analytics data
+function generateAnalytics() {
+  const viewsData = []
+  const likesData = []
+  const now = new Date()
+  for (let i = 29; i >= 0; i--) {
+    const d = new Date(now)
+    d.setDate(d.getDate() - i)
+    const date = d.toISOString().split('T')[0]
+    viewsData.push({ date, views: Math.floor(200 + Math.random() * 800 + (29 - i) * 15) })
+    likesData.push({ date, likes: Math.floor(5 + Math.random() * 60 + (29 - i) * 1.5) })
+  }
+  return { viewsData, likesData }
+}
+
+const { viewsData, likesData } = generateAnalytics()
+
+export const mockAnalytics = {
+  story_id: 1,
+  views_over_time: viewsData,
+  likes_over_time: likesData,
+  total_views: 15420,
+  total_likes: 1240,
+  total_comments: 89,
+  avg_read_time: 8.5,
+  completion_rate: 67,
+}
Index: chapterx-frontend/src/index.css
===================================================================
--- chapterx-frontend/src/index.css	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/index.css	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,31 @@
+@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Inter:wght@300;400;500;600&display=swap');
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+* { box-sizing: border-box; }
+body {
+  background-color: #0F172A;
+  color: #F8FAFC;
+  font-family: 'Inter', sans-serif;
+  margin: 0;
+}
+
+#root {
+  width: 100%;
+  min-height: 100vh;
+}
+
+.glass {
+  background: rgba(255,255,255,0.05);
+  backdrop-filter: blur(12px);
+  border: 1px solid rgba(255,255,255,0.1);
+}
+
+.glow-indigo {
+  box-shadow: 0 0 20px rgba(79,70,229,0.25);
+}
+
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: #0F172A; }
+::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
Index: chapterx-frontend/src/main.tsx
===================================================================
--- chapterx-frontend/src/main.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/main.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,13 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+  <StrictMode>
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
+  </StrictMode>,
+)
Index: chapterx-frontend/src/pages/LandingPage.tsx
===================================================================
--- chapterx-frontend/src/pages/LandingPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/LandingPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,181 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Feather, Star, BookOpen, Users, Sparkles, ArrowRight, ChevronRight } from 'lucide-react'
+import logo from '../assets/chapterX-removebg-preview.png'
+import { useStoryStore } from '../store/storyStore'
+import { useAuthStore } from '../store/authStore'
+import { Button } from '../components/ui/Button'
+import { StoryCard } from '../components/ui/StoryCard'
+import { GenreBadge } from '../components/ui/Badge'
+
+const features = [
+  { icon: <BookOpen size={24} className="text-indigo-400" />, title: 'Read & Discover', desc: 'Explore thousands of stories across every genre. From fantasy epics to contemporary fiction.' },
+  { icon: <Feather size={24} className="text-violet-400" />, title: 'Write & Publish', desc: 'Share your stories with a passionate community. Get feedback, likes, and readers from day one.' },
+  { icon: <Users size={24} className="text-amber-400" />, title: 'Collaborate', desc: 'Invite co-authors, editors, and beta readers. Build stories together with real-time collaboration.' },
+  { icon: <Sparkles size={24} className="text-emerald-400" />, title: 'AI Assistance', desc: 'Get intelligent writing suggestions for grammar, style, plot, and pacing to improve your craft.' },
+]
+
+const genres = ['Fantasy', 'Sci-Fi', 'Romance', 'Historical Fiction', 'Adventure', 'Thriller', 'Mystery', 'Horror']
+
+export const LandingPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { stories } = useStoryStore()
+  const { currentUser } = useAuthStore()
+
+  const trending = stories.filter(s => s.status === 'published').slice(0, 4)
+
+  return (
+    <div className="min-h-screen">
+      {/* Hero */}
+      <section className="relative overflow-hidden py-20 sm:py-32">
+        <div className="absolute inset-0 bg-gradient-to-br from-indigo-950/50 via-slate-950 to-violet-950/30" />
+        <div className="absolute top-20 left-10 w-72 h-72 bg-indigo-600/10 rounded-full blur-3xl" />
+        <div className="absolute bottom-10 right-10 w-96 h-96 bg-violet-600/10 rounded-full blur-3xl" />
+
+        <div className="relative max-w-7xl mx-auto px-4 text-center">
+          <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full glass text-sm text-indigo-300 mb-8 border border-indigo-500/20">
+            <Star size={14} className="text-amber-400 fill-amber-400" />
+            The collaborative storytelling platform
+          </div>
+
+          <div className="flex justify-center mb-6">
+            <img src={logo} alt="ChapterX" className="h-32 w-32 object-contain" />
+          </div>
+
+          <h1 className="font-serif text-5xl sm:text-6xl lg:text-7xl font-bold text-white mb-6 leading-tight">
+            Write Your Own Story.
+            <br />
+            <span className="bg-gradient-to-r from-indigo-400 via-violet-400 to-purple-400 bg-clip-text text-transparent">
+              Anyone Could Be Shakespeare.
+            </span>
+          </h1>
+
+          <p className="text-xl text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
+            ChapterX is where writers and readers connect. Publish your stories, collaborate with others,
+            and discover your next great read.
+          </p>
+
+          <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
+            {currentUser ? (
+              <>
+                <Button size="lg" onClick={() => navigate('/browse')}>
+                  <BookOpen size={18} />
+                  Browse Stories
+                  <ArrowRight size={16} />
+                </Button>
+                {(currentUser.role === 'writer' || currentUser.role === 'admin') && (
+                  <Button size="lg" variant="secondary" onClick={() => navigate('/writer/create-story')}>
+                    <Feather size={18} />
+                    Start Writing
+                  </Button>
+                )}
+              </>
+            ) : (
+              <>
+                <Button size="lg" onClick={() => navigate('/register')}>
+                  Get Started Free
+                  <ArrowRight size={16} />
+                </Button>
+                <Button size="lg" variant="ghost" onClick={() => navigate('/browse')}>
+                  Browse Stories
+                </Button>
+              </>
+            )}
+          </div>
+
+        </div>
+      </section>
+
+      {/* Features */}
+      <section className="py-20 max-w-7xl mx-auto px-4">
+        <div className="text-center mb-12">
+          <h2 className="font-serif text-3xl sm:text-4xl font-bold text-white mb-4">
+            Everything You Need
+          </h2>
+          <p className="text-slate-400 max-w-xl mx-auto">
+            A complete platform for creative writing and reading.
+          </p>
+        </div>
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
+          {features.map(f => (
+            <div key={f.title} className="glass rounded-2xl p-6 hover:border-indigo-500/30 transition-all duration-300 hover:-translate-y-1">
+              <div className="w-12 h-12 rounded-xl bg-slate-800 flex items-center justify-center mb-4">
+                {f.icon}
+              </div>
+              <h3 className="font-serif font-semibold text-white mb-2">{f.title}</h3>
+              <p className="text-slate-400 text-sm leading-relaxed">{f.desc}</p>
+            </div>
+          ))}
+        </div>
+      </section>
+
+      {/* Genres */}
+      <section className="py-16 bg-slate-900/50">
+        <div className="max-w-7xl mx-auto px-4">
+          <div className="flex items-center justify-between mb-8">
+            <h2 className="font-serif text-2xl font-bold text-white">Explore Genres</h2>
+            <button onClick={() => navigate('/genres')} className="text-indigo-400 hover:text-indigo-300 text-sm flex items-center gap-1">
+              See all <ChevronRight size={14} />
+            </button>
+          </div>
+          <div className="flex flex-wrap gap-3">
+            {genres.map(g => (
+              <button
+                key={g}
+                onClick={() => navigate(`/genres/${g.toLowerCase().replace(' ', '-')}`)}
+                className="transition-transform hover:scale-105"
+              >
+                <GenreBadge genre={g} />
+              </button>
+            ))}
+          </div>
+        </div>
+      </section>
+
+      {/* Trending stories */}
+      <section className="py-20 max-w-7xl mx-auto px-4">
+        <div className="flex items-center justify-between mb-8">
+          <div>
+            <h2 className="font-serif text-2xl font-bold text-white">Trending Stories</h2>
+            <p className="text-slate-500 text-sm mt-1">Most popular right now</p>
+          </div>
+          <Button variant="ghost" size="sm" onClick={() => navigate('/browse')}>
+            View all
+            <ChevronRight size={14} />
+          </Button>
+        </div>
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
+          {trending.map(story => (
+            <StoryCard key={story.story_id} story={story} />
+          ))}
+        </div>
+      </section>
+
+      {/* CTA */}
+      {!currentUser && (
+        <section className="py-20">
+          <div className="max-w-3xl mx-auto px-4 text-center">
+            <div className="glass rounded-3xl p-12 border border-indigo-500/20">
+              <h2 className="font-serif text-3xl font-bold text-white mb-4">
+                Ready to Begin Your Story?
+              </h2>
+              <p className="text-slate-400 mb-8">
+                Join thousands of writers and readers on ChapterX today.
+              </p>
+              <div className="flex flex-col sm:flex-row gap-4 justify-center">
+                <Button size="lg" onClick={() => navigate('/register')}>
+                  <Feather size={18} />
+                  Start Writing
+                </Button>
+                <Button size="lg" variant="secondary" onClick={() => navigate('/browse')}>
+                  <BookOpen size={18} />
+                  Just Browse
+                </Button>
+              </div>
+            </div>
+          </div>
+        </section>
+      )}
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/NotificationsPage.tsx
===================================================================
--- chapterx-frontend/src/pages/NotificationsPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/NotificationsPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,105 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Bell, Heart, MessageCircle, Users, Settings, Check, ExternalLink } from 'lucide-react'
+import { useNotificationStore } from '../store/notificationStore'
+import { useAuthStore } from '../store/authStore'
+import { Button } from '../components/ui/Button'
+import { NotificationType } from '../types'
+
+const typeIcon = (type: NotificationType) => {
+  switch (type) {
+    case 'like': return <Heart size={16} className="text-rose-400" />
+    case 'comment': return <MessageCircle size={16} className="text-blue-400" />
+    case 'collaboration': return <Users size={16} className="text-violet-400" />
+    case 'system': return <Settings size={16} className="text-slate-400" />
+    default: return <Bell size={16} className="text-slate-400" />
+  }
+}
+
+function formatDate(str: string) {
+  return new Date(str).toLocaleDateString('en-US', {
+    month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit',
+  })
+}
+
+export const NotificationsPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { notifications, markAllRead, markRead } = useNotificationStore()
+
+  if (!currentUser) {
+    return (
+      <div className="max-w-2xl mx-auto px-4 py-20 text-center">
+        <Bell size={48} className="mx-auto mb-4 text-slate-600" />
+        <h2 className="text-2xl text-white mb-4">Sign in to view notifications</h2>
+        <Button onClick={() => navigate('/login')}>Sign In</Button>
+      </div>
+    )
+  }
+
+  const userNotifications = notifications
+  const unread = userNotifications.filter(n => !n.is_read)
+
+  return (
+    <div className="max-w-2xl mx-auto px-4 py-8">
+      <div className="flex items-center justify-between mb-8">
+        <div>
+          <h1 className="font-serif text-2xl font-bold text-white">Notifications</h1>
+          {unread.length > 0 && (
+            <p className="text-slate-400 text-sm mt-1">{unread.length} unread</p>
+          )}
+        </div>
+        {unread.length > 0 && (
+          <Button variant="ghost" size="sm" onClick={markAllRead}>
+            <Check size={14} />
+            Mark all read
+          </Button>
+        )}
+      </div>
+
+      {userNotifications.length === 0 ? (
+        <div className="text-center py-16 text-slate-500">
+          <Bell size={48} className="mx-auto mb-4 opacity-40" />
+          <p className="text-lg font-medium">No notifications yet</p>
+          <p className="text-sm mt-1">You'll see activity here when readers interact with your content.</p>
+        </div>
+      ) : (
+        <div className="space-y-2">
+          {userNotifications.map(n => (
+            <div
+              key={n.notification_id}
+              className={`flex gap-4 p-4 rounded-2xl border transition-colors cursor-pointer ${
+                !n.is_read
+                  ? 'bg-indigo-500/5 border-indigo-500/20 hover:bg-indigo-500/10'
+                  : 'bg-slate-800 border-slate-700 hover:bg-slate-700/50'
+              }`}
+              onClick={() => {
+                markRead(n.notification_id)
+                if (n.link) navigate(n.link)
+              }}
+            >
+              <div className="w-10 h-10 rounded-full bg-slate-700 flex items-center justify-center flex-shrink-0">
+                {typeIcon(n.type)}
+              </div>
+              <div className="flex-1 min-w-0">
+                <div className="flex items-start justify-between gap-2">
+                  <p className="text-white text-sm font-medium">{n.title}</p>
+                  <div className="flex items-center gap-2 flex-shrink-0">
+                    {!n.is_read && (
+                      <div className="w-2 h-2 rounded-full bg-indigo-400" />
+                    )}
+                    {n.link && (
+                      <ExternalLink size={12} className="text-slate-600" />
+                    )}
+                  </div>
+                </div>
+                <p className="text-slate-400 text-sm mt-0.5 leading-relaxed">{n.message}</p>
+                <p className="text-slate-600 text-xs mt-2">{formatDate(n.created_at)}</p>
+              </div>
+            </div>
+          ))}
+        </div>
+      )}
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/admin/AdminContentPage.tsx
===================================================================
--- chapterx-frontend/src/pages/admin/AdminContentPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/admin/AdminContentPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,32 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { ArrowLeft, BookOpen } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { ContentModerationTable } from '../../components/admin/ContentModerationTable'
+
+export const AdminContentPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { stories } = useStoryStore()
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      <div className="flex items-center gap-3 mb-8">
+        <button onClick={() => navigate('/admin')} className="text-slate-400 hover:text-white transition-colors">
+          <ArrowLeft size={20} />
+        </button>
+        <div>
+          <h1 className="font-serif text-2xl font-bold text-white">Content Moderation</h1>
+          <p className="text-slate-400 text-sm mt-0.5">{stories.length} total stories</p>
+        </div>
+      </div>
+
+      <div className="bg-slate-900 border border-slate-700 rounded-2xl p-6">
+        <div className="flex items-center gap-2 mb-6">
+          <BookOpen size={18} className="text-violet-400" />
+          <h2 className="text-white font-semibold">All Stories</h2>
+        </div>
+        <ContentModerationTable />
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/admin/AdminDashboard.tsx
===================================================================
--- chapterx-frontend/src/pages/admin/AdminDashboard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/admin/AdminDashboard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,130 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Shield, Users, BookOpen, AlertTriangle, Settings, ChevronRight } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { PlatformStats } from '../../components/admin/PlatformStats'
+import { Button } from '../../components/ui/Button'
+import { RoleBadge } from '../../components/ui/Badge'
+import { Avatar } from '../../components/ui/Avatar'
+
+function timeAgo(str: string): string {
+  const diff = Date.now() - new Date(str).getTime()
+  const m = Math.floor(diff / 60000)
+  if (m < 1) return 'just now'
+  if (m < 60) return `${m}m ago`
+  const h = Math.floor(m / 60)
+  if (h < 24) return `${h}h ago`
+  return `${Math.floor(h / 24)}d ago`
+}
+
+export const AdminDashboard: React.FC = () => {
+  const navigate = useNavigate()
+  const { allUsers } = useAuthStore()
+  const { stories } = useStoryStore()
+  const { notifications } = useNotificationStore()
+
+  const recentUsers = [...allUsers].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()).slice(0, 5)
+  const recentStories = [...stories].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()).slice(0, 5)
+
+  const adminLinks = [
+    { to: '/admin/users', icon: <Users size={20} className="text-blue-400" />, label: 'Manage Users', desc: `${allUsers.length} registered users`, color: 'bg-blue-500/10 border-blue-500/20' },
+    { to: '/admin/content', icon: <BookOpen size={20} className="text-violet-400" />, label: 'Content Moderation', desc: `${stories.length} total stories`, color: 'bg-violet-500/10 border-violet-500/20' },
+    { to: '/admin/genres', icon: <Settings size={20} className="text-amber-400" />, label: 'Manage Genres', desc: 'Add or remove genres', color: 'bg-amber-500/10 border-amber-500/20' },
+  ]
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="flex items-center gap-3 mb-8">
+        <div className="w-10 h-10 rounded-xl bg-rose-500/20 flex items-center justify-center">
+          <Shield size={20} className="text-rose-400" />
+        </div>
+        <div>
+          <h1 className="font-serif text-3xl font-bold text-white">Admin Dashboard</h1>
+          <p className="text-slate-400 text-sm mt-0.5">Platform management and moderation</p>
+        </div>
+      </div>
+
+      {/* Platform stats */}
+      <div className="mb-8">
+        <h2 className="font-serif text-xl font-bold text-white mb-4">Platform Overview</h2>
+        <PlatformStats />
+      </div>
+
+      {/* Quick links */}
+      <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
+        {adminLinks.map(link => (
+          <button
+            key={link.to}
+            onClick={() => navigate(link.to)}
+            className={`flex items-center gap-4 p-5 rounded-2xl border ${link.color} hover:scale-105 transition-all group text-left`}
+          >
+            <div className="w-12 h-12 rounded-xl bg-slate-800 flex items-center justify-center">
+              {link.icon}
+            </div>
+            <div className="flex-1">
+              <p className="text-white font-semibold">{link.label}</p>
+              <p className="text-slate-400 text-sm">{link.desc}</p>
+            </div>
+            <ChevronRight size={16} className="text-slate-500 group-hover:text-white transition-colors" />
+          </button>
+        ))}
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
+        {/* Recent users */}
+        <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+          <div className="flex items-center justify-between mb-4">
+            <h3 className="font-serif text-lg font-bold text-white">Recent Users</h3>
+            <Button size="sm" variant="ghost" onClick={() => navigate('/admin/users')}>
+              View all <ChevronRight size={14} />
+            </Button>
+          </div>
+          <div className="space-y-3">
+            {recentUsers.map(user => (
+              <div key={user.user_id} className="flex items-center gap-3">
+                <Avatar name={user.name} size="sm" />
+                <div className="flex-1 min-w-0">
+                  <p className="text-white text-sm font-medium">{user.name} {user.surname}</p>
+                  <p className="text-slate-500 text-xs">@{user.username}</p>
+                </div>
+                <RoleBadge role={user.role} />
+              </div>
+            ))}
+          </div>
+        </div>
+
+        {/* Recent stories */}
+        <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+          <div className="flex items-center justify-between mb-4">
+            <h3 className="font-serif text-lg font-bold text-white">Recent Stories</h3>
+            <Button size="sm" variant="ghost" onClick={() => navigate('/admin/content')}>
+              View all <ChevronRight size={14} />
+            </Button>
+          </div>
+          <div className="space-y-3">
+            {recentStories.map(story => (
+              <div key={story.story_id} className="flex items-center gap-3">
+                <div className="flex-1 min-w-0">
+                  <p className="text-white text-sm font-medium truncate">{story.title}</p>
+                  <p className="text-slate-500 text-xs">by @{story.author_username}</p>
+                </div>
+                <span className={`text-xs px-2 py-0.5 rounded-full ${
+                  story.status === 'published'
+                    ? 'bg-emerald-500/20 text-emerald-400'
+                    : story.status === 'draft'
+                    ? 'bg-amber-500/20 text-amber-400'
+                    : 'bg-slate-500/20 text-slate-400'
+                }`}>
+                  {story.status}
+                </span>
+              </div>
+            ))}
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/admin/AdminGenresPage.tsx
===================================================================
--- chapterx-frontend/src/pages/admin/AdminGenresPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/admin/AdminGenresPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,126 @@
+import React, { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { ArrowLeft, Plus, Trash2, Tag } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { Modal } from '../../components/ui/Modal'
+import { Genre } from '../../types'
+
+export const AdminGenresPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { genres, addGenre, deleteGenre } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [addOpen, setAddOpen] = useState(false)
+  const [newName, setNewName] = useState('')
+  const [deleteTarget, setDeleteTarget] = useState<Genre | null>(null)
+
+  const handleAdd = () => {
+    if (!newName.trim()) return
+    if (genres.some(g => g.name.toLowerCase() === newName.trim().toLowerCase())) {
+      addToast('Genre already exists', 'error')
+      return
+    }
+    addGenre({ genre_id: Date.now(), name: newName.trim(), story_count: 0 })
+    addToast(`Genre "${newName.trim()}" added!`)
+    setNewName('')
+    setAddOpen(false)
+  }
+
+  const handleDelete = (genre: Genre) => {
+    if (genre.story_count > 0) {
+      addToast('Cannot delete genre with associated stories', 'error')
+      return
+    }
+    deleteGenre(genre.genre_id)
+    addToast(`Genre "${genre.name}" deleted`, 'info')
+    setDeleteTarget(null)
+  }
+
+  return (
+    <div className="max-w-4xl mx-auto px-4 py-8">
+      <div className="flex items-center justify-between mb-8">
+        <div className="flex items-center gap-3">
+          <button onClick={() => navigate('/admin')} className="text-slate-400 hover:text-white transition-colors">
+            <ArrowLeft size={20} />
+          </button>
+          <div>
+            <h1 className="font-serif text-2xl font-bold text-white">Manage Genres</h1>
+            <p className="text-slate-400 text-sm mt-0.5">{genres.length} genres</p>
+          </div>
+        </div>
+        <Button onClick={() => setAddOpen(true)}>
+          <Plus size={16} />
+          Add Genre
+        </Button>
+      </div>
+
+      <div className="bg-slate-900 border border-slate-700 rounded-2xl overflow-hidden">
+        <div className="flex items-center gap-2 px-6 py-4 border-b border-slate-700">
+          <Tag size={18} className="text-amber-400" />
+          <h2 className="text-white font-semibold">All Genres</h2>
+        </div>
+        <div className="divide-y divide-slate-800">
+          {genres.map(genre => (
+            <div key={genre.genre_id} className="flex items-center justify-between px-6 py-4 hover:bg-slate-800/50 transition-colors">
+              <div className="flex items-center gap-4">
+                <div className="w-2 h-2 rounded-full bg-indigo-500" />
+                <span className="text-white font-medium">{genre.name}</span>
+              </div>
+              <div className="flex items-center gap-4">
+                <span className="text-slate-500 text-sm">{genre.story_count} stories</span>
+                <button
+                  onClick={() => setDeleteTarget(genre)}
+                  className={`transition-colors p-1 rounded ${
+                    genre.story_count > 0
+                      ? 'text-slate-700 cursor-not-allowed'
+                      : 'text-slate-500 hover:text-rose-400 hover:bg-rose-500/10'
+                  }`}
+                  disabled={genre.story_count > 0}
+                  title={genre.story_count > 0 ? 'Cannot delete genre with stories' : 'Delete genre'}
+                >
+                  <Trash2 size={14} />
+                </button>
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+
+      {/* Add modal */}
+      <Modal isOpen={addOpen} onClose={() => setAddOpen(false)} title="Add Genre" size="sm">
+        <div className="space-y-4">
+          <div>
+            <label className="block text-sm text-slate-400 mb-1.5">Genre Name</label>
+            <input
+              value={newName}
+              onChange={e => setNewName(e.target.value)}
+              onKeyDown={e => e.key === 'Enter' && handleAdd()}
+              placeholder="e.g., Dystopia..."
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div className="flex gap-3">
+            <Button variant="secondary" className="flex-1" onClick={() => setAddOpen(false)}>Cancel</Button>
+            <Button className="flex-1" onClick={handleAdd} disabled={!newName.trim()}>Add Genre</Button>
+          </div>
+        </div>
+      </Modal>
+
+      {/* Delete confirm */}
+      <Modal isOpen={!!deleteTarget} onClose={() => setDeleteTarget(null)} title="Delete Genre" size="sm">
+        {deleteTarget && (
+          <div className="space-y-4">
+            <p className="text-slate-300 text-sm">
+              Delete the genre <strong className="text-white">"{deleteTarget.name}"</strong>? This action cannot be undone.
+            </p>
+            <div className="flex gap-3">
+              <Button variant="secondary" className="flex-1" onClick={() => setDeleteTarget(null)}>Cancel</Button>
+              <Button variant="danger" className="flex-1" onClick={() => handleDelete(deleteTarget)}>Delete</Button>
+            </div>
+          </div>
+        )}
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/admin/AdminUsersPage.tsx
===================================================================
--- chapterx-frontend/src/pages/admin/AdminUsersPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/admin/AdminUsersPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,33 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { ArrowLeft, Users } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { UserTable } from '../../components/admin/UserTable'
+import { Button } from '../../components/ui/Button'
+
+export const AdminUsersPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { allUsers } = useAuthStore()
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      <div className="flex items-center gap-3 mb-8">
+        <button onClick={() => navigate('/admin')} className="text-slate-400 hover:text-white transition-colors">
+          <ArrowLeft size={20} />
+        </button>
+        <div>
+          <h1 className="font-serif text-2xl font-bold text-white">User Management</h1>
+          <p className="text-slate-400 text-sm mt-0.5">{allUsers.length} registered users</p>
+        </div>
+      </div>
+
+      <div className="bg-slate-900 border border-slate-700 rounded-2xl p-6">
+        <div className="flex items-center gap-2 mb-6">
+          <Users size={18} className="text-blue-400" />
+          <h2 className="text-white font-semibold">All Users</h2>
+        </div>
+        <UserTable />
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/auth/LoginPage.tsx
===================================================================
--- chapterx-frontend/src/pages/auth/LoginPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/auth/LoginPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,151 @@
+import React, { useState } from 'react'
+import { useNavigate, Link } from 'react-router-dom'
+import { Eye, EyeOff } from 'lucide-react'
+import logo from '../../assets/chapterX-removebg-preview.png'
+import { useAuthStore } from '../../store/authStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { Avatar } from '../../components/ui/Avatar'
+import { RoleBadge } from '../../components/ui/Badge'
+
+const quickUsers = [
+  { username: 'admin_alex', name: 'Alex Admin', role: 'admin' as const },
+  { username: 'elena_writes', name: 'Elena Dimitrova', role: 'writer' as const },
+  { username: 'boris_writer', name: 'Boris Nikolov', role: 'writer' as const },
+  { username: 'sara_reader', name: 'Sara Petkovska', role: 'regular' as const },
+]
+
+export const LoginPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { login, switchUser } = useAuthStore()
+  const { addToast } = useUIStore()
+  const [email, setEmail] = useState('')
+  const [password, setPassword] = useState('')
+  const [showPw, setShowPw] = useState(false)
+  const [loading, setLoading] = useState(false)
+  const [errors, setErrors] = useState<{ email?: string; password?: string }>({})
+
+  const validate = () => {
+    const e: typeof errors = {}
+    if (!email.trim()) e.email = 'Email or username is required'
+    if (!password.trim()) e.password = 'Password is required'
+    setErrors(e)
+    return Object.keys(e).length === 0
+  }
+
+  const handleSubmit = async (ev: React.FormEvent) => {
+    ev.preventDefault()
+    if (!validate()) return
+    setLoading(true)
+    try {
+      await login(email, password)
+      addToast('Welcome back!')
+      navigate('/')
+    } catch (err: any) {
+      addToast(err.message || 'Login failed', 'error')
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const handleQuickLogin = (username: string) => {
+    const { allUsers } = useAuthStore.getState()
+    const user = allUsers.find(u => u.username === username)
+    if (user) {
+      switchUser(user.user_id)
+      addToast(`Signed in as ${user.name}`)
+      navigate('/')
+    }
+  }
+
+  return (
+    <div className="min-h-[80vh] flex items-center justify-center px-4 py-12">
+      <div className="w-full max-w-md">
+        {/* Logo */}
+        <div className="text-center mb-8">
+          <Link to="/" className="inline-flex items-center gap-2 mb-6">
+            <img src={logo} alt="ChapterX" className="h-20 w-20 object-contain" />
+          </Link>
+          <h1 className="font-serif text-2xl font-bold text-white">Welcome back</h1>
+          <p className="text-slate-400 text-sm mt-2">Sign in to your account</p>
+        </div>
+
+        {/* Quick login */}
+        <div className="mb-6">
+          <p className="text-xs text-slate-500 text-center mb-3">Quick demo login</p>
+          <div className="grid grid-cols-2 gap-2">
+            {quickUsers.map(u => (
+              <button
+                key={u.username}
+                onClick={() => handleQuickLogin(u.username)}
+                className="flex items-center gap-2 p-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 hover:bg-slate-700/50 transition-all group"
+              >
+                <Avatar name={u.name} size="sm" />
+                <div className="text-left min-w-0">
+                  <p className="text-white text-xs font-medium truncate">{u.name}</p>
+                  <RoleBadge role={u.role} />
+                </div>
+              </button>
+            ))}
+          </div>
+        </div>
+
+        <div className="flex items-center gap-3 mb-6">
+          <div className="flex-1 h-px bg-slate-700" />
+          <span className="text-slate-600 text-xs">or sign in with email</span>
+          <div className="flex-1 h-px bg-slate-700" />
+        </div>
+
+        {/* Form */}
+        <form onSubmit={handleSubmit} className="space-y-4">
+          <div>
+            <label className="block text-sm text-slate-400 mb-1.5">Email or Username</label>
+            <input
+              type="text"
+              value={email}
+              onChange={e => { setEmail(e.target.value); setErrors(p => ({ ...p, email: '' })) }}
+              placeholder="you@example.com"
+              className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition-colors ${
+                errors.email ? 'border-rose-500' : 'border-slate-700'
+              }`}
+            />
+            {errors.email && <p className="text-rose-400 text-xs mt-1">{errors.email}</p>}
+          </div>
+          <div>
+            <label className="block text-sm text-slate-400 mb-1.5">Password</label>
+            <div className="relative">
+              <input
+                type={showPw ? 'text' : 'password'}
+                value={password}
+                onChange={e => { setPassword(e.target.value); setErrors(p => ({ ...p, password: '' })) }}
+                placeholder="••••••••"
+                className={`w-full px-4 py-3 pr-12 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition-colors ${
+                  errors.password ? 'border-rose-500' : 'border-slate-700'
+                }`}
+              />
+              <button
+                type="button"
+                onClick={() => setShowPw(!showPw)}
+                className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300"
+              >
+                {showPw ? <EyeOff size={16} /> : <Eye size={16} />}
+              </button>
+            </div>
+            {errors.password && <p className="text-rose-400 text-xs mt-1">{errors.password}</p>}
+          </div>
+
+          <Button type="submit" className="w-full" size="lg" loading={loading}>
+            Sign In
+          </Button>
+        </form>
+
+        <p className="text-center text-slate-500 text-sm mt-6">
+          Don't have an account?{' '}
+          <Link to="/register" className="text-indigo-400 hover:text-indigo-300">
+            Create one
+          </Link>
+        </p>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/auth/RegisterPage.tsx
===================================================================
--- chapterx-frontend/src/pages/auth/RegisterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/auth/RegisterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,224 @@
+import React, { useState } from 'react'
+import { useNavigate, Link } from 'react-router-dom'
+import { BookOpen, PenLine, ArrowLeft, Eye, EyeOff } from 'lucide-react'
+import logo from '../../assets/chapterX-removebg-preview.png'
+import { useAuthStore } from '../../store/authStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+
+type Role = 'regular' | 'writer'
+type Step = 1 | 2
+
+export const RegisterPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { register } = useAuthStore()
+  const { addToast } = useUIStore()
+  const [step, setStep] = useState<Step>(1)
+  const [role, setRole] = useState<Role>('regular')
+  const [loading, setLoading] = useState(false)
+  const [showPw, setShowPw] = useState(false)
+  const [form, setForm] = useState({
+    username: '',
+    email: '',
+    name: '',
+    surname: '',
+    password: '',
+    confirmPassword: '',
+  })
+  const [errors, setErrors] = useState<Record<string, string>>({})
+
+  const validate = () => {
+    const e: Record<string, string> = {}
+    if (!form.username.trim()) e.username = 'Username is required'
+    if (form.username.length < 3) e.username = 'Username must be at least 3 characters'
+    if (!form.email.includes('@')) e.email = 'Enter a valid email address'
+    if (!form.name.trim()) e.name = 'First name is required'
+    if (!form.surname.trim()) e.surname = 'Last name is required'
+    if (form.password.length < 6) e.password = 'Password must be at least 6 characters'
+    if (form.password !== form.confirmPassword) e.confirmPassword = 'Passwords do not match'
+    setErrors(e)
+    return Object.keys(e).length === 0
+  }
+
+  const handleSubmit = async (ev: React.FormEvent) => {
+    ev.preventDefault()
+    if (!validate()) return
+    setLoading(true)
+    try {
+      await register(
+        { username: form.username, email: form.email, name: form.name, surname: form.surname, password: form.password },
+        role
+      )
+      addToast('Account created! Welcome to ChapterX.')
+      navigate('/')
+    } catch (err: any) {
+      addToast(err.message || 'Registration failed', 'error')
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const setField = (field: string, value: string) => {
+    setForm(p => ({ ...p, [field]: value }))
+    setErrors(p => ({ ...p, [field]: '' }))
+  }
+
+  return (
+    <div className="min-h-[80vh] flex items-center justify-center px-4 py-12">
+      <div className="w-full max-w-md">
+        <div className="text-center mb-8">
+          <Link to="/" className="inline-flex items-center gap-2 mb-6">
+            <img src={logo} alt="ChapterX" className="h-20 w-20 object-contain" />
+          </Link>
+          <h1 className="font-serif text-2xl font-bold text-white">Create your account</h1>
+          <p className="text-slate-400 text-sm mt-2">Step {step} of 2</p>
+
+          {/* Progress */}
+          <div className="flex gap-2 justify-center mt-4">
+            <div className="h-1 w-16 rounded-full bg-indigo-600" />
+            <div className={`h-1 w-16 rounded-full transition-colors ${step === 2 ? 'bg-indigo-600' : 'bg-slate-700'}`} />
+          </div>
+        </div>
+
+        {step === 1 ? (
+          <div className="space-y-4">
+            <h2 className="text-white font-semibold text-center mb-6">I want to join as...</h2>
+            <div className="grid grid-cols-1 gap-4">
+              <button
+                onClick={() => setRole('regular')}
+                className={`flex items-start gap-4 p-5 rounded-2xl border-2 transition-all ${
+                  role === 'regular'
+                    ? 'border-indigo-500 bg-indigo-500/10'
+                    : 'border-slate-700 bg-slate-800 hover:border-slate-600'
+                }`}
+              >
+                <div className={`w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 ${
+                  role === 'regular' ? 'bg-indigo-500/30' : 'bg-slate-700'
+                }`}>
+                  <BookOpen size={20} className="text-indigo-400" />
+                </div>
+                <div className="text-left">
+                  <p className="font-semibold text-white">Reader</p>
+                  <p className="text-slate-400 text-sm mt-1">
+                    Browse stories, leave comments, build reading lists, and follow your favorite authors.
+                  </p>
+                </div>
+              </button>
+              <button
+                onClick={() => setRole('writer')}
+                className={`flex items-start gap-4 p-5 rounded-2xl border-2 transition-all ${
+                  role === 'writer'
+                    ? 'border-violet-500 bg-violet-500/10'
+                    : 'border-slate-700 bg-slate-800 hover:border-slate-600'
+                }`}
+              >
+                <div className={`w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 ${
+                  role === 'writer' ? 'bg-violet-500/30' : 'bg-slate-700'
+                }`}>
+                  <PenLine size={20} className="text-violet-400" />
+                </div>
+                <div className="text-left">
+                  <p className="font-semibold text-white">Writer</p>
+                  <p className="text-slate-400 text-sm mt-1">
+                    Publish stories, write chapters, collaborate with others, and get AI writing assistance.
+                  </p>
+                </div>
+              </button>
+            </div>
+            <Button className="w-full mt-6" size="lg" onClick={() => setStep(2)}>
+              Continue
+            </Button>
+            <p className="text-center text-slate-500 text-sm">
+              Already have an account?{' '}
+              <Link to="/login" className="text-indigo-400 hover:text-indigo-300">Sign in</Link>
+            </p>
+          </div>
+        ) : (
+          <form onSubmit={handleSubmit} className="space-y-4">
+            <button
+              type="button"
+              onClick={() => setStep(1)}
+              className="flex items-center gap-1 text-sm text-slate-400 hover:text-white mb-2"
+            >
+              <ArrowLeft size={14} />
+              Back
+            </button>
+
+            <div className="grid grid-cols-2 gap-3">
+              <div>
+                <label className="block text-sm text-slate-400 mb-1.5">First Name</label>
+                <input
+                  value={form.name}
+                  onChange={e => setField('name', e.target.value)}
+                  placeholder="Elena"
+                  className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 ${errors.name ? 'border-rose-500' : 'border-slate-700'}`}
+                />
+                {errors.name && <p className="text-rose-400 text-xs mt-1">{errors.name}</p>}
+              </div>
+              <div>
+                <label className="block text-sm text-slate-400 mb-1.5">Last Name</label>
+                <input
+                  value={form.surname}
+                  onChange={e => setField('surname', e.target.value)}
+                  placeholder="Dimitrova"
+                  className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 ${errors.surname ? 'border-rose-500' : 'border-slate-700'}`}
+                />
+                {errors.surname && <p className="text-rose-400 text-xs mt-1">{errors.surname}</p>}
+              </div>
+            </div>
+
+            {[
+              { field: 'username', label: 'Username', placeholder: 'elena_writes', type: 'text' },
+              { field: 'email', label: 'Email', placeholder: 'you@example.com', type: 'email' },
+            ].map(f => (
+              <div key={f.field}>
+                <label className="block text-sm text-slate-400 mb-1.5">{f.label}</label>
+                <input
+                  type={f.type}
+                  value={(form as any)[f.field]}
+                  onChange={e => setField(f.field, e.target.value)}
+                  placeholder={f.placeholder}
+                  className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 ${(errors as any)[f.field] ? 'border-rose-500' : 'border-slate-700'}`}
+                />
+                {(errors as any)[f.field] && <p className="text-rose-400 text-xs mt-1">{(errors as any)[f.field]}</p>}
+              </div>
+            ))}
+
+            <div>
+              <label className="block text-sm text-slate-400 mb-1.5">Password</label>
+              <div className="relative">
+                <input
+                  type={showPw ? 'text' : 'password'}
+                  value={form.password}
+                  onChange={e => setField('password', e.target.value)}
+                  placeholder="Min. 6 characters"
+                  className={`w-full px-4 py-3 pr-12 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 ${errors.password ? 'border-rose-500' : 'border-slate-700'}`}
+                />
+                <button type="button" onClick={() => setShowPw(!showPw)} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300">
+                  {showPw ? <EyeOff size={16} /> : <Eye size={16} />}
+                </button>
+              </div>
+              {errors.password && <p className="text-rose-400 text-xs mt-1">{errors.password}</p>}
+            </div>
+
+            <div>
+              <label className="block text-sm text-slate-400 mb-1.5">Confirm Password</label>
+              <input
+                type="password"
+                value={form.confirmPassword}
+                onChange={e => setField('confirmPassword', e.target.value)}
+                placeholder="••••••••"
+                className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 ${errors.confirmPassword ? 'border-rose-500' : 'border-slate-700'}`}
+              />
+              {errors.confirmPassword && <p className="text-rose-400 text-xs mt-1">{errors.confirmPassword}</p>}
+            </div>
+
+            <Button type="submit" className="w-full" size="lg" loading={loading}>
+              Create Account as {role === 'writer' ? 'Writer' : 'Reader'}
+            </Button>
+          </form>
+        )}
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/browse/BrowsePage.tsx
===================================================================
--- chapterx-frontend/src/pages/browse/BrowsePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/browse/BrowsePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,166 @@
+import React, { useState, useMemo } from 'react'
+import { Search, SlidersHorizontal, X } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useAuthStore } from '../../store/authStore'
+import { StoryGrid } from '../../components/story/StoryGrid'
+import { GenreBadge } from '../../components/ui/Badge'
+
+const sortOptions = [
+  { value: 'popular', label: 'Most Popular' },
+  { value: 'recent', label: 'Most Recent' },
+  { value: 'views', label: 'Most Viewed' },
+  { value: 'comments', label: 'Most Discussed' },
+]
+
+const allGenres = ['Fantasy', 'Sci-Fi', 'Romance', 'Historical Fiction', 'Adventure', 'Thriller', 'Mystery', 'Horror', 'Contemporary', 'Poetry']
+
+export const BrowsePage: React.FC = () => {
+  const { stories } = useStoryStore()
+  const { currentUser, showMatureContent, setShowMatureContent } = useAuthStore()
+  const [search, setSearch] = useState('')
+  const [selectedGenres, setSelectedGenres] = useState<string[]>([])
+  const [sort, setSort] = useState('popular')
+  const [showFilters, setShowFilters] = useState(false)
+
+  const filtered = useMemo(() => {
+    let list = stories.filter(s => s.status === 'published')
+
+    if (!showMatureContent && !currentUser) {
+      list = list.filter(s => !s.mature_content)
+    }
+
+    if (search.trim()) {
+      const q = search.toLowerCase()
+      list = list.filter(s =>
+        s.title.toLowerCase().includes(q) ||
+        s.short_description.toLowerCase().includes(q) ||
+        s.author_username.toLowerCase().includes(q)
+      )
+    }
+
+    if (selectedGenres.length > 0) {
+      list = list.filter(s => selectedGenres.some(g => s.genres.includes(g)))
+    }
+
+    switch (sort) {
+      case 'popular': return [...list].sort((a, b) => b.total_likes - a.total_likes)
+      case 'recent': return [...list].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+      case 'views': return [...list].sort((a, b) => b.total_views - a.total_views)
+      case 'comments': return [...list].sort((a, b) => b.total_comments - a.total_comments)
+      default: return list
+    }
+  }, [stories, search, selectedGenres, sort, showMatureContent, currentUser])
+
+  const toggleGenre = (g: string) =>
+    setSelectedGenres(prev => prev.includes(g) ? prev.filter(x => x !== g) : [...prev, g])
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="mb-8">
+        <h1 className="font-serif text-3xl font-bold text-white mb-2">Browse Stories</h1>
+        <p className="text-slate-400">Discover your next favorite read</p>
+      </div>
+
+      {/* Search + controls */}
+      <div className="flex flex-col sm:flex-row gap-3 mb-6">
+        <div className="relative flex-1">
+          <Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
+          <input
+            value={search}
+            onChange={e => setSearch(e.target.value)}
+            placeholder="Search titles, authors, descriptions..."
+            className="w-full pl-10 pr-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+          />
+          {search && (
+            <button onClick={() => setSearch('')} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-white">
+              <X size={16} />
+            </button>
+          )}
+        </div>
+        <select
+          value={sort}
+          onChange={e => setSort(e.target.value)}
+          className="px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-slate-300 focus:outline-none focus:border-indigo-500 text-sm"
+        >
+          {sortOptions.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
+        </select>
+        <button
+          onClick={() => setShowFilters(!showFilters)}
+          className={`flex items-center gap-2 px-4 py-3 rounded-xl border text-sm font-medium transition-colors ${
+            showFilters || selectedGenres.length > 0
+              ? 'border-indigo-500 bg-indigo-500/10 text-indigo-300'
+              : 'border-slate-700 bg-slate-800 text-slate-300 hover:border-slate-600'
+          }`}
+        >
+          <SlidersHorizontal size={16} />
+          Filters
+          {selectedGenres.length > 0 && (
+            <span className="bg-indigo-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
+              {selectedGenres.length}
+            </span>
+          )}
+        </button>
+      </div>
+
+      {/* Filters panel */}
+      {showFilters && (
+        <div className="bg-slate-800 border border-slate-700 rounded-2xl p-5 mb-6">
+          <div className="flex items-center justify-between mb-4">
+            <h3 className="text-white font-medium">Filter by Genre</h3>
+            {selectedGenres.length > 0 && (
+              <button onClick={() => setSelectedGenres([])} className="text-xs text-slate-400 hover:text-white">
+                Clear all
+              </button>
+            )}
+          </div>
+          <div className="flex flex-wrap gap-2 mb-4">
+            {allGenres.map(g => (
+              <button
+                key={g}
+                onClick={() => toggleGenre(g)}
+                className={`transition-transform hover:scale-105 ${selectedGenres.includes(g) ? 'opacity-100' : 'opacity-70 hover:opacity-100'}`}
+              >
+                <GenreBadge genre={g} />
+              </button>
+            ))}
+          </div>
+          {currentUser && (
+            <div className="flex items-center gap-3 pt-3 border-t border-slate-700">
+              <label className="flex items-center gap-2 cursor-pointer">
+                <div
+                  onClick={() => setShowMatureContent(!showMatureContent)}
+                  className={`w-10 h-6 rounded-full transition-colors ${showMatureContent ? 'bg-indigo-600' : 'bg-slate-600'} relative`}
+                >
+                  <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${showMatureContent ? 'translate-x-5' : 'translate-x-1'}`} />
+                </div>
+                <span className="text-sm text-slate-400">Show mature content (18+)</span>
+              </label>
+            </div>
+          )}
+        </div>
+      )}
+
+      {/* Result count */}
+      <div className="flex items-center justify-between mb-4">
+        <p className="text-slate-500 text-sm">{filtered.length} stories found</p>
+        {selectedGenres.length > 0 && (
+          <div className="flex items-center gap-2">
+            <span className="text-xs text-slate-500">Active filters:</span>
+            {selectedGenres.map(g => (
+              <button
+                key={g}
+                onClick={() => toggleGenre(g)}
+                className="flex items-center gap-1 text-xs px-2 py-1 bg-indigo-500/20 text-indigo-300 rounded-full border border-indigo-500/30 hover:bg-rose-500/20 hover:text-rose-300 hover:border-rose-500/30 transition-colors"
+              >
+                {g} <X size={10} />
+              </button>
+            ))}
+          </div>
+        )}
+      </div>
+
+      <StoryGrid stories={filtered} emptyMessage="No stories match your filters." />
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/browse/GenrePage.tsx
===================================================================
--- chapterx-frontend/src/pages/browse/GenrePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/browse/GenrePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,79 @@
+import React from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { StoryGrid } from '../../components/story/StoryGrid'
+import { getGenreGradient } from '../../components/story/GenreBadge'
+
+function toTitleCase(s: string) {
+  return s.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
+}
+
+export const GenrePage: React.FC = () => {
+  const { genre } = useParams<{ genre: string }>()
+  const navigate = useNavigate()
+  const { stories } = useStoryStore()
+
+  const genreName = toTitleCase(genre || '')
+  const gradient = getGenreGradient(genreName)
+  const filtered = stories.filter(
+    s => s.status === 'published' && s.genres.some(g => g.toLowerCase() === genreName.toLowerCase())
+  )
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      {/* Hero */}
+      <div className={`relative rounded-2xl overflow-hidden mb-8 h-40 bg-gradient-to-br ${gradient}`}>
+        <div className="absolute inset-0 flex flex-col justify-end p-8">
+          <button
+            onClick={() => navigate('/genres')}
+            className="flex items-center gap-2 text-white/70 hover:text-white text-sm mb-3 transition-colors w-fit"
+          >
+            <ArrowLeft size={16} />
+            All Genres
+          </button>
+          <h1 className="font-serif text-3xl font-bold text-white">{genreName}</h1>
+          <p className="text-white/70 text-sm mt-1">{filtered.length} stories</p>
+        </div>
+        <div className="absolute top-4 right-8 w-32 h-32 rounded-full bg-white/5 blur-xl" />
+      </div>
+
+      <StoryGrid
+        stories={filtered}
+        emptyMessage={`No ${genreName} stories published yet.`}
+      />
+    </div>
+  )
+}
+
+export const GenresListPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { genres } = useStoryStore()
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      <div className="mb-8">
+        <h1 className="font-serif text-3xl font-bold text-white mb-2">Explore Genres</h1>
+        <p className="text-slate-400">Find stories by your favorite genre</p>
+      </div>
+      <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
+        {genres.map(genre => {
+          const gradient = getGenreGradient(genre.name)
+          return (
+            <button
+              key={genre.genre_id}
+              onClick={() => navigate(`/genres/${genre.name.toLowerCase().replace(' ', '-')}`)}
+              className={`relative rounded-2xl overflow-hidden h-32 bg-gradient-to-br ${gradient} hover:scale-105 transition-transform group`}
+            >
+              <div className="absolute inset-0 flex flex-col items-center justify-center p-4">
+                <p className="font-serif font-semibold text-white text-center">{genre.name}</p>
+                <p className="text-white/60 text-xs mt-1">{genre.story_count} stories</p>
+              </div>
+              <div className="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition-colors" />
+            </button>
+          )
+        })}
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/profile/ProfilePage.tsx
===================================================================
--- chapterx-frontend/src/pages/profile/ProfilePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/profile/ProfilePage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,163 @@
+import React, { useState } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { BookOpen, Heart, Users, Calendar, MessageCircle, Eye } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { Avatar } from '../../components/ui/Avatar'
+import { RoleBadge, StatusBadge } from '../../components/ui/Badge'
+import { StoryCard } from '../../components/ui/StoryCard'
+import { GenreBadge } from '../../components/ui/Badge'
+
+type Tab = 'stories' | 'about'
+
+export const ProfilePage: React.FC = () => {
+  const { username } = useParams<{ username: string }>()
+  const navigate = useNavigate()
+  const { allUsers, currentUser } = useAuthStore()
+  const { stories, comments } = useStoryStore()
+  const [tab, setTab] = useState<Tab>('stories')
+
+  const user = allUsers.find(u => u.username === username)
+
+  if (!user) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">User not found</h2>
+      </div>
+    )
+  }
+
+  const userStories = stories.filter(s => s.user_id === user.user_id && s.status === 'published')
+  const totalLikes = userStories.reduce((acc, s) => acc + s.total_likes, 0)
+  const totalViews = userStories.reduce((acc, s) => acc + s.total_views, 0)
+  const userComments = comments.filter(c => c.user_id === user.user_id)
+  const allGenres = [...new Set(userStories.flatMap(s => s.genres))]
+
+  return (
+    <div className="max-w-5xl mx-auto px-4 py-8">
+      {/* Profile header */}
+      <div className="relative mb-8">
+        {/* Cover */}
+        <div className="h-32 sm:h-48 rounded-2xl bg-gradient-to-br from-indigo-900 via-violet-900 to-purple-900 mb-16 sm:mb-20 overflow-hidden">
+          <div className="absolute inset-0 rounded-2xl opacity-40">
+            <div className="absolute top-4 left-16 w-32 h-32 rounded-full bg-white/10 blur-2xl" />
+            <div className="absolute bottom-2 right-8 w-24 h-24 rounded-full bg-white/10 blur-xl" />
+          </div>
+        </div>
+
+        {/* Avatar + info */}
+        <div className="absolute bottom-0 left-0 right-0 px-6 flex items-end justify-between">
+          <Avatar name={`${user.name} ${user.surname}`} size="xl" className="ring-4 ring-slate-950" />
+          {currentUser?.user_id === user.user_id && (
+            <button className="mb-2 text-sm text-indigo-400 hover:text-indigo-300 transition-colors">
+              Edit Profile
+            </button>
+          )}
+        </div>
+      </div>
+
+      {/* User info */}
+      <div className="mb-8">
+        <div className="flex items-center gap-3 mb-1">
+          <h1 className="font-serif text-2xl font-bold text-white">
+            {user.name} {user.surname}
+          </h1>
+          <RoleBadge role={user.role} />
+        </div>
+        <p className="text-slate-400">@{user.username}</p>
+        {user.bio && <p className="text-slate-300 mt-3 max-w-xl">{user.bio}</p>}
+        <div className="flex items-center gap-4 mt-4 flex-wrap">
+          <span className="flex items-center gap-1 text-slate-400 text-sm">
+            <Calendar size={14} />
+            Joined {new Date(user.created_at).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
+          </span>
+          <span className="flex items-center gap-1 text-slate-400 text-sm">
+            <Users size={14} />
+            {user.follower_count} followers · {user.following_count} following
+          </span>
+        </div>
+      </div>
+
+      {/* Stats */}
+      <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
+        {[
+          { icon: <BookOpen size={16} className="text-indigo-400" />, value: userStories.length, label: 'Stories' },
+          { icon: <Heart size={16} className="text-rose-400" />, value: totalLikes.toLocaleString(), label: 'Likes' },
+          { icon: <Eye size={16} className="text-blue-400" />, value: totalViews.toLocaleString(), label: 'Views' },
+          { icon: <MessageCircle size={16} className="text-amber-400" />, value: userComments.length, label: 'Comments' },
+        ].map(s => (
+          <div key={s.label} className="bg-slate-800 border border-slate-700 rounded-xl p-4 text-center">
+            <div className="flex justify-center mb-1">{s.icon}</div>
+            <p className="text-xl font-bold text-white">{s.value}</p>
+            <p className="text-slate-500 text-xs">{s.label}</p>
+          </div>
+        ))}
+      </div>
+
+      {/* Tabs */}
+      <div className="flex gap-1 p-1 bg-slate-800 rounded-xl mb-6 w-fit">
+        {(['stories', 'about'] as Tab[]).map(t => (
+          <button
+            key={t}
+            onClick={() => setTab(t)}
+            className={`px-4 py-2 rounded-lg text-sm font-medium capitalize transition-colors ${
+              tab === t ? 'bg-indigo-600 text-white' : 'text-slate-400 hover:text-white'
+            }`}
+          >
+            {t}
+          </button>
+        ))}
+      </div>
+
+      {/* Tab content */}
+      {tab === 'stories' ? (
+        <div>
+          {userStories.length === 0 ? (
+            <div className="text-center py-16 text-slate-500">
+              <BookOpen size={40} className="mx-auto mb-3 opacity-40" />
+              <p>No published stories yet.</p>
+            </div>
+          ) : (
+            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
+              {userStories.map(story => (
+                <StoryCard key={story.story_id} story={story} />
+              ))}
+            </div>
+          )}
+        </div>
+      ) : (
+        <div className="space-y-6">
+          <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+            <h3 className="text-white font-semibold mb-3">About</h3>
+            <p className="text-slate-300">{user.bio || 'No bio provided.'}</p>
+          </div>
+          {allGenres.length > 0 && (
+            <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+              <h3 className="text-white font-semibold mb-3">Writes In</h3>
+              <div className="flex flex-wrap gap-2">
+                {allGenres.map(g => <GenreBadge key={g} genre={g} />)}
+              </div>
+            </div>
+          )}
+          <div className="bg-slate-800 border border-slate-700 rounded-2xl p-6">
+            <h3 className="text-white font-semibold mb-3">Stats</h3>
+            <div className="space-y-3">
+              <div className="flex justify-between text-sm">
+                <span className="text-slate-400">Role</span>
+                <RoleBadge role={user.role} />
+              </div>
+              <div className="flex justify-between text-sm">
+                <span className="text-slate-400">Member since</span>
+                <span className="text-white">{new Date(user.created_at).toLocaleDateString()}</span>
+              </div>
+              <div className="flex justify-between text-sm">
+                <span className="text-slate-400">Published stories</span>
+                <span className="text-white">{userStories.length}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/reading-list/CommunityListsPage.tsx
===================================================================
--- chapterx-frontend/src/pages/reading-list/CommunityListsPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/reading-list/CommunityListsPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,134 @@
+import React, { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Globe, BookOpen, User } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { ReadingList } from '../../types'
+import { Modal } from '../../components/ui/Modal'
+import { GenreBadge } from '../../components/ui/Badge'
+import { Avatar } from '../../components/ui/Avatar'
+
+export const CommunityListsPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { readingLists } = useStoryStore()
+  const [selectedList, setSelectedList] = useState<ReadingList | null>(null)
+
+  const publicLists = readingLists.filter(l => l.is_public)
+
+  return (
+    <div className="max-w-6xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="mb-8">
+        <div className="flex items-center gap-2 mb-2">
+          <Globe size={20} className="text-indigo-400" />
+          <h1 className="font-serif text-3xl font-bold text-white">Community Reading Lists</h1>
+        </div>
+        <p className="text-slate-400">Discover curated lists from the ChapterX community</p>
+      </div>
+
+      {publicLists.length === 0 ? (
+        <div className="flex flex-col items-center py-20 text-slate-500">
+          <Globe size={48} className="mb-4 opacity-40" />
+          <p className="text-lg font-medium">No public lists yet</p>
+          <p className="text-sm mt-1">Be the first to share your reading list!</p>
+        </div>
+      ) : (
+        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+          {publicLists.map(list => (
+            <div
+              key={list.list_id}
+              className="bg-slate-800 border border-slate-700 rounded-2xl overflow-hidden hover:border-indigo-500/30 transition-all cursor-pointer group"
+              onClick={() => setSelectedList(list)}
+            >
+              {/* Cover preview */}
+              <div className="h-24 bg-gradient-to-br from-indigo-900/50 via-violet-900/30 to-slate-900 p-4 flex items-end">
+                <div className="flex gap-1">
+                  {Array.from({ length: Math.min(3, list.stories.length) }).map((_, i) => (
+                    <div key={i} className={`w-10 h-14 rounded bg-slate-700 border border-slate-600 -ml-${i > 0 ? '2' : '0'}`} style={{ transform: `rotate(${(i - 1) * 5}deg)` }} />
+                  ))}
+                </div>
+              </div>
+
+              <div className="p-5">
+                <div className="flex items-start justify-between gap-2 mb-3">
+                  <h3 className="text-white font-semibold group-hover:text-indigo-300 transition-colors">
+                    {list.name}
+                  </h3>
+                  <span className="flex items-center gap-1 text-xs text-emerald-400 flex-shrink-0">
+                    <Globe size={11} /> Public
+                  </span>
+                </div>
+
+                <div className="flex items-center gap-2 mb-3">
+                  <Avatar name={list.username} size="sm" />
+                  <span className="text-slate-400 text-sm">@{list.username}</span>
+                </div>
+
+                {list.description && (
+                  <p className="text-slate-500 text-sm mb-3 line-clamp-2">{list.description}</p>
+                )}
+
+                <div className="flex items-center justify-between text-xs text-slate-500">
+                  <span className="flex items-center gap-1">
+                    <BookOpen size={12} />
+                    {list.stories.length} stories
+                  </span>
+                  <button
+                    className="text-indigo-400 hover:text-indigo-300 transition-colors"
+                    onClick={e => { e.stopPropagation(); setSelectedList(list) }}
+                  >
+                    View list →
+                  </button>
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      )}
+
+      {/* List detail modal */}
+      {selectedList && (
+        <Modal
+          isOpen={!!selectedList}
+          onClose={() => setSelectedList(null)}
+          title={selectedList.name}
+          size="lg"
+        >
+          <div>
+            <div className="flex items-center gap-2 mb-4">
+              <Avatar name={selectedList.username} size="sm" />
+              <div>
+                <p className="text-white text-sm font-medium">@{selectedList.username}</p>
+                <p className="text-slate-500 text-xs">{selectedList.stories.length} stories</p>
+              </div>
+            </div>
+            {selectedList.description && (
+              <p className="text-slate-400 text-sm mb-4">{selectedList.description}</p>
+            )}
+
+            {selectedList.stories.length === 0 ? (
+              <p className="text-slate-500 text-sm text-center py-8">This list is empty.</p>
+            ) : (
+              <div className="space-y-3">
+                {selectedList.stories.map(item => (
+                  <div
+                    key={item.item_id}
+                    className="flex items-center gap-3 p-3 bg-slate-800 rounded-xl border border-slate-700 hover:border-indigo-500/40 cursor-pointer transition-colors"
+                    onClick={() => { navigate(`/story/${item.story_id}`); setSelectedList(null) }}
+                  >
+                    <div className="flex-1 min-w-0">
+                      <p className="text-white font-medium text-sm">{item.story_title}</p>
+                      <p className="text-slate-500 text-xs">by {item.author_username}</p>
+                    </div>
+                    <div className="flex flex-wrap gap-1">
+                      {item.genres.slice(0, 2).map(g => <GenreBadge key={g} genre={g} />)}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            )}
+          </div>
+        </Modal>
+      )}
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/reading-list/ReadingListPage.tsx
===================================================================
--- chapterx-frontend/src/pages/reading-list/ReadingListPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/reading-list/ReadingListPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,200 @@
+import React, { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Plus, BookOpen, Lock, Globe, Trash2, X } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { Modal } from '../../components/ui/Modal'
+import { GenreBadge } from '../../components/ui/Badge'
+
+export const ReadingListPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { readingLists, createReadingList, deleteReadingList, removeStoryFromList } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [createOpen, setCreateOpen] = useState(false)
+  const [newListName, setNewListName] = useState('')
+  const [newListDesc, setNewListDesc] = useState('')
+  const [isPublic, setIsPublic] = useState(false)
+
+  if (!currentUser) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <BookOpen size={48} className="mx-auto mb-4 text-slate-600" />
+        <h2 className="text-2xl text-white mb-2">Sign in to view your reading lists</h2>
+        <Button onClick={() => navigate('/login')} className="mt-4">Sign In</Button>
+      </div>
+    )
+  }
+
+  const myLists = readingLists.filter(l => l.user_id === currentUser.user_id)
+
+  const handleCreate = () => {
+    if (!newListName.trim()) return
+    createReadingList({
+      list_id: Date.now(),
+      user_id: currentUser.user_id,
+      username: currentUser.username,
+      name: newListName.trim(),
+      description: newListDesc.trim() || undefined,
+      is_public: isPublic,
+      created_at: new Date().toISOString(),
+      stories: [],
+    })
+    addToast(`"${newListName}" created!`)
+    setNewListName('')
+    setNewListDesc('')
+    setIsPublic(false)
+    setCreateOpen(false)
+  }
+
+  return (
+    <div className="max-w-6xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="flex items-center justify-between mb-8">
+        <div>
+          <h1 className="font-serif text-3xl font-bold text-white">My Reading Lists</h1>
+          <p className="text-slate-400 mt-1">{myLists.length} list{myLists.length !== 1 ? 's' : ''}</p>
+        </div>
+        <Button onClick={() => setCreateOpen(true)}>
+          <Plus size={16} />
+          New List
+        </Button>
+      </div>
+
+      {myLists.length === 0 ? (
+        <div className="flex flex-col items-center py-20 text-slate-500">
+          <BookOpen size={48} className="mb-4 opacity-40" />
+          <h3 className="text-lg font-medium text-white mb-2">No reading lists yet</h3>
+          <p className="text-sm mb-4">Create your first list to save stories you love.</p>
+          <Button onClick={() => setCreateOpen(true)}>
+            <Plus size={14} />
+            Create Reading List
+          </Button>
+        </div>
+      ) : (
+        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+          {myLists.map(list => (
+            <div key={list.list_id} className="bg-slate-800 border border-slate-700 rounded-2xl overflow-hidden hover:border-indigo-500/30 transition-all">
+              {/* Header */}
+              <div className="p-5 border-b border-slate-700">
+                <div className="flex items-start justify-between gap-3">
+                  <div>
+                    <div className="flex items-center gap-2 mb-1">
+                      <h3 className="text-white font-semibold">{list.name}</h3>
+                      {list.is_public ? (
+                        <span className="flex items-center gap-1 text-xs text-emerald-400">
+                          <Globe size={11} /> Public
+                        </span>
+                      ) : (
+                        <span className="flex items-center gap-1 text-xs text-slate-500">
+                          <Lock size={11} /> Private
+                        </span>
+                      )}
+                    </div>
+                    {list.description && (
+                      <p className="text-slate-400 text-sm">{list.description}</p>
+                    )}
+                    <p className="text-slate-500 text-xs mt-1">{list.stories.length} stories</p>
+                  </div>
+                  <button
+                    onClick={() => { deleteReadingList(list.list_id); addToast('List deleted', 'info') }}
+                    className="text-slate-500 hover:text-rose-400 transition-colors p-1"
+                  >
+                    <Trash2 size={14} />
+                  </button>
+                </div>
+              </div>
+
+              {/* Stories */}
+              <div className="p-4">
+                {list.stories.length === 0 ? (
+                  <p className="text-slate-600 text-sm text-center py-4">No stories yet.</p>
+                ) : (
+                  <div className="space-y-2">
+                    {list.stories.slice(0, 4).map(item => (
+                      <div key={item.item_id} className="flex items-center gap-3 p-2 rounded-lg hover:bg-slate-700/50 group">
+                        <div className="flex-1 min-w-0">
+                          <button
+                            onClick={() => navigate(`/story/${item.story_id}`)}
+                            className="text-white text-sm font-medium hover:text-indigo-300 transition-colors truncate block text-left"
+                          >
+                            {item.story_title}
+                          </button>
+                          <div className="flex items-center gap-2 mt-0.5">
+                            <span className="text-slate-500 text-xs">by {item.author_username}</span>
+                            {item.genres.slice(0, 1).map(g => <GenreBadge key={g} genre={g} />)}
+                          </div>
+                        </div>
+                        <button
+                          onClick={() => removeStoryFromList(list.list_id, item.story_id)}
+                          className="opacity-0 group-hover:opacity-100 text-slate-500 hover:text-rose-400 transition-all"
+                        >
+                          <X size={14} />
+                        </button>
+                      </div>
+                    ))}
+                    {list.stories.length > 4 && (
+                      <p className="text-slate-500 text-xs text-center pt-1">
+                        +{list.stories.length - 4} more stories
+                      </p>
+                    )}
+                  </div>
+                )}
+                <button
+                  onClick={() => navigate('/browse')}
+                  className="w-full mt-3 py-2 text-xs text-indigo-400 hover:text-indigo-300 flex items-center justify-center gap-1 transition-colors border border-dashed border-slate-700 rounded-lg hover:border-indigo-500/50"
+                >
+                  <Plus size={12} />
+                  Add stories
+                </button>
+              </div>
+            </div>
+          ))}
+        </div>
+      )}
+
+      {/* Create modal */}
+      <Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Reading List">
+        <div className="space-y-4">
+          <div>
+            <label className="block text-sm text-slate-400 mb-1.5">List Name *</label>
+            <input
+              value={newListName}
+              onChange={e => setNewListName(e.target.value)}
+              placeholder="My Fantasy Favorites..."
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div>
+            <label className="block text-sm text-slate-400 mb-1.5">Description <span className="text-slate-600">(optional)</span></label>
+            <textarea
+              value={newListDesc}
+              onChange={e => setNewListDesc(e.target.value)}
+              placeholder="What's this list about?"
+              rows={2}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none"
+            />
+          </div>
+          <div className="flex items-center gap-3">
+            <button
+              onClick={() => setIsPublic(!isPublic)}
+              className={`w-10 h-6 rounded-full transition-colors ${isPublic ? 'bg-indigo-600' : 'bg-slate-600'} relative`}
+            >
+              <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${isPublic ? 'translate-x-5' : 'translate-x-1'}`} />
+            </button>
+            <div>
+              <p className="text-white text-sm">{isPublic ? 'Public' : 'Private'}</p>
+              <p className="text-slate-500 text-xs">{isPublic ? 'Anyone can see this list' : 'Only you can see this list'}</p>
+            </div>
+          </div>
+          <div className="flex gap-3 pt-2">
+            <Button variant="secondary" className="flex-1" onClick={() => setCreateOpen(false)}>Cancel</Button>
+            <Button className="flex-1" onClick={handleCreate} disabled={!newListName.trim()}>Create List</Button>
+          </div>
+        </div>
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/story/ChapterReadPage.tsx
===================================================================
--- chapterx-frontend/src/pages/story/ChapterReadPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/story/ChapterReadPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,165 @@
+import React, { useEffect, useState } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, ArrowRight, BookOpen, Eye, ChevronLeft, ChevronRight } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { Button } from '../../components/ui/Button'
+
+export const ChapterReadPage: React.FC = () => {
+  const { storyId, chapterId } = useParams<{ storyId: string; chapterId: string }>()
+  const navigate = useNavigate()
+  const { stories, chapters, incrementViewCount } = useStoryStore()
+  const [scrollProgress, setScrollProgress] = useState(0)
+  const [viewed, setViewed] = useState(false)
+
+  const story = stories.find(s => s.story_id === Number(storyId))
+  const chapter = chapters.find(c => c.chapter_id === Number(chapterId))
+  const storyChapters = chapters
+    .filter(c => c.story_id === Number(storyId) && c.is_published)
+    .sort((a, b) => a.chapter_number - b.chapter_number)
+
+  const currentIndex = storyChapters.findIndex(c => c.chapter_id === Number(chapterId))
+  const prevChapter = currentIndex > 0 ? storyChapters[currentIndex - 1] : null
+  const nextChapter = currentIndex < storyChapters.length - 1 ? storyChapters[currentIndex + 1] : null
+
+  // Track scroll progress
+  useEffect(() => {
+    const handler = () => {
+      const el = document.documentElement
+      const progress = (el.scrollTop / (el.scrollHeight - el.clientHeight)) * 100
+      setScrollProgress(Math.min(100, progress))
+      // Increment view count when 30% read
+      if (progress > 30 && !viewed) {
+        setViewed(true)
+        if (chapter) incrementViewCount(chapter.chapter_id)
+      }
+    }
+    window.addEventListener('scroll', handler)
+    return () => window.removeEventListener('scroll', handler)
+  }, [chapter, viewed, incrementViewCount])
+
+  if (!story || !chapter) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">Chapter not found</h2>
+        <Button onClick={() => navigate('/browse')}>Browse Stories</Button>
+      </div>
+    )
+  }
+
+  return (
+    <div className="min-h-screen">
+      {/* Progress bar */}
+      <div className="fixed top-0 left-0 right-0 z-50 h-1 bg-slate-800">
+        <div
+          className="h-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all duration-100"
+          style={{ width: `${scrollProgress}%` }}
+        />
+      </div>
+
+      {/* Top nav */}
+      <div className="sticky top-1 z-40 glass border-b border-white/5">
+        <div className="max-w-3xl mx-auto px-4 py-3 flex items-center justify-between">
+          <button
+            onClick={() => navigate(`/story/${story.story_id}`)}
+            className="flex items-center gap-2 text-slate-400 hover:text-white text-sm transition-colors"
+          >
+            <ArrowLeft size={16} />
+            <span className="hidden sm:block">{story.title}</span>
+          </button>
+
+          <div className="text-center">
+            <p className="text-white text-sm font-medium">{chapter.title}</p>
+            <p className="text-slate-500 text-xs">Chapter {chapter.chapter_number}</p>
+          </div>
+
+          <div className="flex items-center gap-1 text-slate-400 text-xs">
+            <Eye size={12} />
+            {scrollProgress.toFixed(0)}%
+          </div>
+        </div>
+      </div>
+
+      {/* Content */}
+      <div className="max-w-3xl mx-auto px-4 py-12">
+        {/* Chapter header */}
+        <div className="text-center mb-12">
+          <p className="text-indigo-400 text-sm font-medium mb-2 uppercase tracking-widest">
+            Chapter {chapter.chapter_number}
+          </p>
+          <h1 className="font-serif text-3xl sm:text-4xl font-bold text-white mb-4">
+            {chapter.title}
+          </h1>
+          <div className="flex items-center justify-center gap-4 text-slate-500 text-sm">
+            <span className="flex items-center gap-1">
+              <BookOpen size={13} />
+              {chapter.word_count.toLocaleString()} words
+            </span>
+            <span className="flex items-center gap-1">
+              <Eye size={13} />
+              {chapter.view_count.toLocaleString()} reads
+            </span>
+          </div>
+          <div className="mt-4 w-16 h-px bg-gradient-to-r from-transparent via-indigo-500 to-transparent mx-auto" />
+        </div>
+
+        {/* Chapter content */}
+        <div className="prose prose-lg prose-invert max-w-none">
+          {chapter.content.split('\n\n').map((para, i) => (
+            <p
+              key={i}
+              className="text-slate-300 leading-relaxed text-lg mb-6 first-letter:text-4xl first-letter:font-serif first-letter:font-bold first-letter:text-white first-letter:float-left first-letter:mr-2 first-letter:mt-1"
+              style={i !== 0 ? { textIndent: '2rem' } : undefined}
+            >
+              {para}
+            </p>
+          ))}
+        </div>
+
+        {/* Chapter navigation */}
+        <div className="mt-16 pt-8 border-t border-slate-700">
+          <div className="flex items-center justify-between gap-4">
+            {prevChapter ? (
+              <button
+                onClick={() => navigate(`/story/${storyId}/chapter/${prevChapter.chapter_id}`)}
+                className="flex items-center gap-2 px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 transition-all group flex-1 max-w-[45%]"
+              >
+                <ChevronLeft size={18} className="text-slate-400 group-hover:text-indigo-400 flex-shrink-0" />
+                <div className="text-left min-w-0">
+                  <p className="text-xs text-slate-500">Previous</p>
+                  <p className="text-white text-sm truncate">{prevChapter.title}</p>
+                </div>
+              </button>
+            ) : (
+              <div className="flex-1" />
+            )}
+
+            <Button
+              variant="ghost"
+              size="sm"
+              onClick={() => navigate(`/story/${story.story_id}`)}
+            >
+              Contents
+            </Button>
+
+            {nextChapter ? (
+              <button
+                onClick={() => navigate(`/story/${storyId}/chapter/${nextChapter.chapter_id}`)}
+                className="flex items-center gap-2 px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/50 transition-all group flex-1 max-w-[45%] justify-end"
+              >
+                <div className="text-right min-w-0">
+                  <p className="text-xs text-slate-500">Next</p>
+                  <p className="text-white text-sm truncate">{nextChapter.title}</p>
+                </div>
+                <ChevronRight size={18} className="text-slate-400 group-hover:text-indigo-400 flex-shrink-0" />
+              </button>
+            ) : (
+              <div className="flex-1 text-center">
+                <p className="text-slate-500 text-sm">End of story</p>
+              </div>
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/story/StoryDetailPage.tsx
===================================================================
--- chapterx-frontend/src/pages/story/StoryDetailPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/story/StoryDetailPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,282 @@
+import React, { useState } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, BookOpen, Eye, Users, Calendar, Plus, BookmarkPlus } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useAuthStore } from '../../store/authStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { GenreBadge } from '../../components/ui/Badge'
+import { Avatar } from '../../components/ui/Avatar'
+import { ChapterList } from '../../components/story/ChapterList'
+import { CommentSection } from '../../components/story/CommentSection'
+import { LikeButton } from '../../components/story/LikeButton'
+import { Modal } from '../../components/ui/Modal'
+import { getGenreGradient } from '../../components/story/GenreBadge'
+import { ReadingListItem } from '../../types'
+
+export const StoryDetailPage: React.FC = () => {
+  const { id } = useParams<{ id: string }>()
+  const navigate = useNavigate()
+  const { stories, chapters, collaborations, readingLists, addStoryToList, createReadingList } = useStoryStore()
+  const { currentUser } = useAuthStore()
+  const { addToast } = useUIStore()
+  const [listModalOpen, setListModalOpen] = useState(false)
+  const [newListName, setNewListName] = useState('')
+
+  const story = stories.find(s => s.story_id === Number(id))
+  if (!story) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">Story not found</h2>
+        <Button onClick={() => navigate('/browse')}>Browse Stories</Button>
+      </div>
+    )
+  }
+
+  const storyChapters = chapters.filter(c => c.story_id === story.story_id)
+  const storyCollabs = collaborations.filter(c => c.story_id === story.story_id)
+  const gradient = getGenreGradient(story.genres[0])
+  const myLists = currentUser ? readingLists.filter(l => l.user_id === currentUser.user_id) : []
+
+  const handleAddToList = (listId: number) => {
+    const list = readingLists.find(l => l.list_id === listId)
+    if (!list) return
+    if (list.stories.some(s => s.story_id === story.story_id)) {
+      addToast('Already in this list', 'info')
+      return
+    }
+    const item: ReadingListItem = {
+      item_id: Date.now(),
+      list_id: listId,
+      story_id: story.story_id,
+      story_title: story.title,
+      author_username: story.author_username,
+      added_at: new Date().toISOString(),
+      genres: story.genres,
+    }
+    addStoryToList(listId, item)
+    addToast(`Added to "${list.name}"!`)
+    setListModalOpen(false)
+  }
+
+  const handleCreateList = () => {
+    if (!newListName.trim() || !currentUser) return
+    const newList = {
+      list_id: Date.now(),
+      user_id: currentUser.user_id,
+      username: currentUser.username,
+      name: newListName.trim(),
+      is_public: false,
+      created_at: new Date().toISOString(),
+      stories: [{
+        item_id: Date.now() + 1,
+        list_id: Date.now(),
+        story_id: story.story_id,
+        story_title: story.title,
+        author_username: story.author_username,
+        added_at: new Date().toISOString(),
+        genres: story.genres,
+      }],
+    }
+    createReadingList(newList)
+    addToast(`Created "${newListName}" and added story!`)
+    setNewListName('')
+    setListModalOpen(false)
+  }
+
+  const isOwner = currentUser?.user_id === story.user_id
+
+  return (
+    <div className="max-w-5xl mx-auto px-4 py-8">
+      {/* Back */}
+      <button
+        onClick={() => navigate(-1)}
+        className="flex items-center gap-2 text-slate-400 hover:text-white mb-6 text-sm transition-colors"
+      >
+        <ArrowLeft size={16} />
+        Back
+      </button>
+
+      {/* Hero */}
+      <div className={`relative rounded-2xl overflow-hidden mb-8 bg-gradient-to-br ${gradient}`}>
+        <div className="absolute inset-0 bg-gradient-to-t from-slate-950/90 via-slate-950/30 to-transparent" />
+        <div className="relative p-8 sm:p-12">
+          <div className="flex flex-wrap gap-2 mb-4">
+            {story.genres.map(g => <GenreBadge key={g} genre={g} />)}
+            {story.mature_content && (
+              <span className="px-2 py-0.5 text-xs font-medium rounded-full border bg-rose-500/20 text-rose-400 border-rose-500/30">
+                18+
+              </span>
+            )}
+          </div>
+          <h1 className="font-serif text-3xl sm:text-4xl font-bold text-white mb-3">{story.title}</h1>
+          <p className="text-slate-300 text-lg mb-6 leading-relaxed max-w-2xl">{story.short_description}</p>
+
+          <div className="flex items-center gap-4 flex-wrap">
+            <div className="flex items-center gap-2">
+              <Avatar name={story.author_username} size="sm" />
+              <span className="text-white text-sm font-medium">{story.author_username}</span>
+            </div>
+            <div className="flex items-center gap-1 text-slate-400 text-sm">
+              <Eye size={14} />
+              {story.total_views.toLocaleString()} views
+            </div>
+            <div className="flex items-center gap-1 text-slate-400 text-sm">
+              <BookOpen size={14} />
+              {story.total_chapters} chapters
+            </div>
+            <div className="flex items-center gap-1 text-slate-400 text-sm">
+              <Calendar size={14} />
+              {new Date(story.created_at).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+        {/* Main content */}
+        <div className="lg:col-span-2 space-y-8">
+          {/* Action bar */}
+          <div className="flex items-center gap-3 flex-wrap">
+            <LikeButton storyId={story.story_id} authorUserId={story.user_id} totalLikes={story.total_likes} />
+            {currentUser && (
+              <Button variant="secondary" size="sm" onClick={() => setListModalOpen(true)}>
+                <BookmarkPlus size={14} />
+                Save to List
+              </Button>
+            )}
+            {isOwner && (
+              <Button variant="ghost" size="sm" onClick={() => navigate(`/writer/edit-story/${story.story_id}`)}>
+                Edit Story
+              </Button>
+            )}
+          </div>
+
+          {/* Chapters */}
+          <div>
+            <div className="flex items-center justify-between mb-4">
+              <h2 className="font-serif text-xl font-bold text-white">Chapters</h2>
+              {isOwner && (
+                <Button size="sm" onClick={() => navigate(`/writer/create-chapter/${story.story_id}`)}>
+                  <Plus size={14} />
+                  Add Chapter
+                </Button>
+              )}
+            </div>
+            <ChapterList chapters={storyChapters} storyId={story.story_id} />
+          </div>
+
+          {/* Comments */}
+          <div className="border-t border-slate-700 pt-8">
+            <CommentSection storyId={story.story_id} authorUserId={story.user_id} />
+          </div>
+        </div>
+
+        {/* Sidebar */}
+        <div className="space-y-6">
+          {/* Story info */}
+          <div className="bg-slate-800 border border-slate-700 rounded-2xl p-5">
+            <h3 className="text-white font-semibold mb-4">Story Info</h3>
+            <div className="space-y-3 text-sm">
+              <div className="flex justify-between">
+                <span className="text-slate-400">Status</span>
+                <span className="text-white capitalize">{story.status}</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-slate-400">Chapters</span>
+                <span className="text-white">{story.total_chapters}</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-slate-400">Likes</span>
+                <span className="text-white">{story.total_likes.toLocaleString()}</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-slate-400">Views</span>
+                <span className="text-white">{story.total_views.toLocaleString()}</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-slate-400">Comments</span>
+                <span className="text-white">{story.total_comments}</span>
+              </div>
+              <div className="flex justify-between">
+                <span className="text-slate-400">Published</span>
+                <span className="text-white">{new Date(story.created_at).toLocaleDateString()}</span>
+              </div>
+            </div>
+          </div>
+
+          {/* Collaborators */}
+          {storyCollabs.length > 0 && (
+            <div className="bg-slate-800 border border-slate-700 rounded-2xl p-5">
+              <div className="flex items-center gap-2 mb-4">
+                <Users size={16} className="text-violet-400" />
+                <h3 className="text-white font-semibold">Collaborators</h3>
+              </div>
+              <div className="space-y-3">
+                {storyCollabs.map(c => (
+                  <div key={c.collab_id} className="flex items-center gap-2">
+                    <Avatar name={c.name} size="sm" />
+                    <div>
+                      <p className="text-white text-sm">@{c.username}</p>
+                      <p className="text-slate-500 text-xs">{c.role}</p>
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </div>
+          )}
+        </div>
+      </div>
+
+      {/* Reading list modal */}
+      <Modal isOpen={listModalOpen} onClose={() => setListModalOpen(false)} title="Save to Reading List">
+        <div className="space-y-3">
+          {myLists.length > 0 ? (
+            <>
+              <p className="text-slate-400 text-sm mb-3">Select a list:</p>
+              {myLists.map(list => (
+                <button
+                  key={list.list_id}
+                  onClick={() => handleAddToList(list.list_id)}
+                  className="w-full flex items-center justify-between p-3 bg-slate-800 rounded-xl border border-slate-700 hover:border-indigo-500/50 transition-colors"
+                >
+                  <span className="text-white text-sm">{list.name}</span>
+                  <span className="text-slate-500 text-xs">{list.stories.length} stories</span>
+                </button>
+              ))}
+              <div className="border-t border-slate-700 pt-3">
+                <p className="text-slate-400 text-sm mb-2">Or create a new list:</p>
+                <div className="flex gap-2">
+                  <input
+                    value={newListName}
+                    onChange={e => setNewListName(e.target.value)}
+                    placeholder="List name..."
+                    className="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-xl text-white text-sm placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+                  />
+                  <Button size="sm" onClick={handleCreateList} disabled={!newListName.trim()}>
+                    Create
+                  </Button>
+                </div>
+              </div>
+            </>
+          ) : (
+            <div>
+              <p className="text-slate-400 text-sm mb-3">You don't have any reading lists yet. Create one:</p>
+              <div className="flex gap-2">
+                <input
+                  value={newListName}
+                  onChange={e => setNewListName(e.target.value)}
+                  placeholder="My Favorites..."
+                  className="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-xl text-white text-sm placeholder-slate-500 focus:outline-none focus:border-indigo-500"
+                />
+                <Button size="sm" onClick={handleCreateList} disabled={!newListName.trim()}>
+                  Create
+                </Button>
+              </div>
+            </div>
+          )}
+        </div>
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/writer/CreateChapterPage.tsx
===================================================================
--- chapterx-frontend/src/pages/writer/CreateChapterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/writer/CreateChapterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,120 @@
+import React, { useState } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, BookOpen } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { Chapter } from '../../types'
+
+export const CreateChapterPage: React.FC = () => {
+  const { storyId } = useParams<{ storyId: string }>()
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { stories, chapters, addChapter } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [form, setForm] = useState({ title: '', content: '' })
+  const [errors, setErrors] = useState<Record<string, string>>({})
+  const [loading, setLoading] = useState(false)
+
+  const story = stories.find(s => s.story_id === Number(storyId))
+  const storyChapters = chapters.filter(c => c.story_id === Number(storyId))
+  const wordCount = form.content.trim().split(/\s+/).filter(Boolean).length
+
+  if (!story) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">Story not found</h2>
+        <Button onClick={() => navigate('/writer')}>Back</Button>
+      </div>
+    )
+  }
+
+  const validate = () => {
+    const e: Record<string, string> = {}
+    if (!form.title.trim()) e.title = 'Chapter title is required'
+    if (!form.content.trim()) e.content = 'Chapter content is required'
+    if (wordCount < 10) e.content = 'Chapter must be at least 10 words'
+    setErrors(e)
+    return Object.keys(e).length === 0
+  }
+
+  const handleSubmit = async (isPublished: boolean) => {
+    if (!validate() || !currentUser) return
+    setLoading(true)
+    await new Promise(r => setTimeout(r, 400))
+    const chapter: Chapter = {
+      chapter_id: Date.now(),
+      story_id: Number(storyId),
+      title: form.title.trim(),
+      content: form.content.trim(),
+      chapter_number: storyChapters.length + 1,
+      word_count: wordCount,
+      view_count: 0,
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+      is_published: isPublished,
+    }
+    addChapter(chapter)
+    addToast(isPublished ? 'Chapter published!' : 'Chapter saved as draft!')
+    navigate(`/writer/edit-story/${storyId}`)
+    setLoading(false)
+  }
+
+  return (
+    <div className="max-w-3xl mx-auto px-4 py-8">
+      <div className="flex items-center gap-3 mb-8">
+        <button onClick={() => navigate(`/writer/edit-story/${storyId}`)} className="text-slate-400 hover:text-white transition-colors">
+          <ArrowLeft size={20} />
+        </button>
+        <div>
+          <p className="text-slate-400 text-sm">{story.title}</p>
+          <h1 className="font-serif text-2xl font-bold text-white">New Chapter</h1>
+          <p className="text-slate-500 text-xs mt-0.5">Chapter {storyChapters.length + 1}</p>
+        </div>
+      </div>
+
+      <div className="space-y-6">
+        <div>
+          <label className="block text-sm font-medium text-slate-300 mb-1.5">Chapter Title *</label>
+          <input
+            value={form.title}
+            onChange={e => { setForm(f => ({ ...f, title: e.target.value })); setErrors(e => ({ ...e, title: '' })) }}
+            placeholder="The Awakening..."
+            className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 font-serif text-lg ${errors.title ? 'border-rose-500' : 'border-slate-700'}`}
+          />
+          {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
+        </div>
+
+        <div>
+          <div className="flex items-center justify-between mb-1.5">
+            <label className="text-sm font-medium text-slate-300">Chapter Content *</label>
+            <div className="flex items-center gap-2 text-xs text-slate-500">
+              <BookOpen size={12} />
+              <span className={wordCount > 100 ? 'text-emerald-400' : wordCount > 50 ? 'text-amber-400' : 'text-slate-500'}>
+                {wordCount.toLocaleString()} words
+              </span>
+            </div>
+          </div>
+          <textarea
+            value={form.content}
+            onChange={e => { setForm(f => ({ ...f, content: e.target.value })); setErrors(e => ({ ...e, content: '' })) }}
+            placeholder="Begin your chapter here..."
+            rows={20}
+            className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-y font-serif text-base leading-relaxed ${errors.content ? 'border-rose-500' : 'border-slate-700'}`}
+          />
+          {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
+        </div>
+
+        <div className="flex gap-3 pt-4 border-t border-slate-700">
+          <Button variant="secondary" className="flex-1" onClick={() => handleSubmit(false)} loading={loading}>
+            Save as Draft
+          </Button>
+          <Button className="flex-1" onClick={() => handleSubmit(true)} loading={loading}>
+            Publish Chapter
+          </Button>
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/writer/CreateStoryPage.tsx
===================================================================
--- chapterx-frontend/src/pages/writer/CreateStoryPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/writer/CreateStoryPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,190 @@
+import React, { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Feather, ArrowLeft, X } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { Story } from '../../types'
+
+const ALL_GENRES = ['Fantasy', 'Sci-Fi', 'Romance', 'Historical Fiction', 'Adventure', 'Thriller', 'Mystery', 'Horror', 'Contemporary', 'Poetry']
+
+export const CreateStoryPage: React.FC = () => {
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { addStory } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [form, setForm] = useState({
+    title: '',
+    short_description: '',
+    content: '',
+    genres: [] as string[],
+    mature_content: false,
+  })
+  const [errors, setErrors] = useState<Record<string, string>>({})
+  const [loading, setLoading] = useState(false)
+
+  const validate = () => {
+    const e: Record<string, string> = {}
+    if (!form.title.trim()) e.title = 'Title is required'
+    if (!form.short_description.trim()) e.short_description = 'Description is required'
+    if (form.short_description.length > 280) e.short_description = 'Max 280 characters'
+    if (!form.content.trim()) e.content = 'Story content is required'
+    if (form.genres.length === 0) e.genres = 'Select at least one genre'
+    setErrors(e)
+    return Object.keys(e).length === 0
+  }
+
+  const handleSubmit = async (status: 'draft' | 'published') => {
+    if (!validate() || !currentUser) return
+    setLoading(true)
+    await new Promise(r => setTimeout(r, 500))
+    const story: Story = {
+      story_id: Date.now(),
+      ...form,
+      user_id: currentUser.user_id,
+      author_username: currentUser.username,
+      status,
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+      total_likes: 0,
+      total_comments: 0,
+      total_chapters: 0,
+      total_views: 0,
+    }
+    addStory(story)
+    addToast(status === 'published' ? 'Story published!' : 'Draft saved!')
+    navigate(`/writer/edit-story/${story.story_id}`)
+    setLoading(false)
+  }
+
+  const toggleGenre = (g: string) => {
+    setForm(f => ({
+      ...f,
+      genres: f.genres.includes(g) ? f.genres.filter(x => x !== g) : [...f.genres, g],
+    }))
+    setErrors(e => ({ ...e, genres: '' }))
+  }
+
+  const setField = (field: string, value: string | boolean) => {
+    setForm(f => ({ ...f, [field]: value }))
+    setErrors(e => ({ ...e, [field]: '' }))
+  }
+
+  return (
+    <div className="max-w-3xl mx-auto px-4 py-8">
+      <div className="flex items-center gap-3 mb-8">
+        <button onClick={() => navigate('/writer')} className="text-slate-400 hover:text-white transition-colors">
+          <ArrowLeft size={20} />
+        </button>
+        <div>
+          <h1 className="font-serif text-2xl font-bold text-white">Create New Story</h1>
+          <p className="text-slate-400 text-sm mt-0.5">Tell your story to the world</p>
+        </div>
+      </div>
+
+      <div className="space-y-6">
+        {/* Title */}
+        <div>
+          <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Title *</label>
+          <input
+            value={form.title}
+            onChange={e => setField('title', e.target.value)}
+            placeholder="The Chronicles of Eldoria..."
+            className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 text-lg font-serif ${errors.title ? 'border-rose-500' : 'border-slate-700'}`}
+          />
+          {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
+        </div>
+
+        {/* Short description */}
+        <div>
+          <label className="block text-sm font-medium text-slate-300 mb-1.5">Short Description * <span className="text-slate-500 font-normal">(shown on story cards)</span></label>
+          <textarea
+            value={form.short_description}
+            onChange={e => setField('short_description', e.target.value)}
+            placeholder="When the last dragon awakens..."
+            rows={3}
+            maxLength={280}
+            className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none ${errors.short_description ? 'border-rose-500' : 'border-slate-700'}`}
+          />
+          <div className="flex justify-between items-center mt-1">
+            {errors.short_description ? <p className="text-rose-400 text-xs">{errors.short_description}</p> : <span />}
+            <span className="text-slate-600 text-xs">{form.short_description.length}/280</span>
+          </div>
+        </div>
+
+        {/* Content */}
+        <div>
+          <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Content *</label>
+          <textarea
+            value={form.content}
+            onChange={e => setField('content', e.target.value)}
+            placeholder="Begin your story here. This is the introduction that readers will see..."
+            rows={8}
+            className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-y font-serif ${errors.content ? 'border-rose-500' : 'border-slate-700'}`}
+          />
+          {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
+        </div>
+
+        {/* Genres */}
+        <div>
+          <label className="block text-sm font-medium text-slate-300 mb-2">Genres * <span className="text-slate-500 font-normal">(select 1-3)</span></label>
+          <div className="flex flex-wrap gap-2 mb-2">
+            {ALL_GENRES.map(g => (
+              <button
+                key={g}
+                type="button"
+                onClick={() => toggleGenre(g)}
+                className={`px-3 py-1.5 rounded-full text-sm border transition-all ${
+                  form.genres.includes(g)
+                    ? 'bg-indigo-500/30 text-indigo-300 border-indigo-500/50'
+                    : 'bg-slate-800 text-slate-400 border-slate-700 hover:border-slate-500'
+                }`}
+              >
+                {form.genres.includes(g) && <span className="mr-1">✓</span>}
+                {g}
+              </button>
+            ))}
+          </div>
+          {form.genres.length > 0 && (
+            <div className="flex flex-wrap gap-1 mt-1">
+              {form.genres.map(g => (
+                <span key={g} className="flex items-center gap-1 text-xs px-2 py-0.5 bg-indigo-500/20 text-indigo-300 rounded-full">
+                  {g}
+                  <button onClick={() => toggleGenre(g)}><X size={10} /></button>
+                </span>
+              ))}
+            </div>
+          )}
+          {errors.genres && <p className="text-rose-400 text-xs mt-1">{errors.genres}</p>}
+        </div>
+
+        {/* Mature content */}
+        <div className="flex items-center gap-3">
+          <button
+            type="button"
+            onClick={() => setField('mature_content', !form.mature_content)}
+            className={`w-11 h-6 rounded-full transition-colors ${form.mature_content ? 'bg-rose-500' : 'bg-slate-600'} relative flex-shrink-0`}
+          >
+            <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${form.mature_content ? 'translate-x-6' : 'translate-x-1'}`} />
+          </button>
+          <div>
+            <p className="text-white text-sm font-medium">Mature Content (18+)</p>
+            <p className="text-slate-500 text-xs">Enable for stories with adult themes</p>
+          </div>
+        </div>
+
+        {/* Actions */}
+        <div className="flex gap-3 pt-4 border-t border-slate-700">
+          <Button variant="secondary" className="flex-1" onClick={() => handleSubmit('draft')} loading={loading}>
+            Save as Draft
+          </Button>
+          <Button className="flex-1" onClick={() => handleSubmit('published')} loading={loading}>
+            <Feather size={16} />
+            Publish Story
+          </Button>
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/writer/EditChapterPage.tsx
===================================================================
--- chapterx-frontend/src/pages/writer/EditChapterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/writer/EditChapterPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,123 @@
+import React, { useState, useEffect } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, Save, Globe, BookOpen } from 'lucide-react'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { AISuggestionPanel } from '../../components/writer/AISuggestionPanel'
+
+export const EditChapterPage: React.FC = () => {
+  const { chapterId } = useParams<{ chapterId: string }>()
+  const navigate = useNavigate()
+  const { stories, chapters, updateChapter } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [saving, setSaving] = useState(false)
+
+  const chapter = chapters.find(c => c.chapter_id === Number(chapterId))
+  const story = chapter ? stories.find(s => s.story_id === chapter.story_id) : null
+
+  const [form, setForm] = useState({ title: '', content: '' })
+
+  useEffect(() => {
+    if (chapter) {
+      setForm({ title: chapter.title, content: chapter.content })
+    }
+  }, [chapter?.chapter_id])
+
+  if (!chapter || !story) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">Chapter not found</h2>
+        <Button onClick={() => navigate('/writer')}>Back</Button>
+      </div>
+    )
+  }
+
+  const wordCount = form.content.trim().split(/\s+/).filter(Boolean).length
+
+  const handleSave = async (publish?: boolean) => {
+    setSaving(true)
+    await new Promise(r => setTimeout(r, 400))
+    updateChapter(chapter.chapter_id, {
+      ...form,
+      word_count: wordCount,
+      is_published: publish !== undefined ? publish : chapter.is_published,
+    })
+    addToast('Chapter saved!')
+    setSaving(false)
+  }
+
+  return (
+    <div className="max-w-5xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="flex items-center justify-between mb-8">
+        <div className="flex items-center gap-3">
+          <button
+            onClick={() => navigate(`/writer/edit-story/${story.story_id}`)}
+            className="text-slate-400 hover:text-white transition-colors"
+          >
+            <ArrowLeft size={20} />
+          </button>
+          <div>
+            <p className="text-slate-400 text-sm">{story.title} · Chapter {chapter.chapter_number}</p>
+            <h1 className="font-serif text-2xl font-bold text-white">Edit Chapter</h1>
+          </div>
+        </div>
+        <div className="flex gap-2">
+          {!chapter.is_published && (
+            <Button size="sm" variant="secondary" onClick={() => handleSave(true)} loading={saving}>
+              <Globe size={14} />
+              Publish
+            </Button>
+          )}
+          <Button size="sm" onClick={() => handleSave()} loading={saving}>
+            <Save size={14} />
+            Save
+          </Button>
+        </div>
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+        {/* Editor */}
+        <div className="lg:col-span-2 space-y-5">
+          <div>
+            <label className="block text-sm font-medium text-slate-300 mb-1.5">Chapter Title</label>
+            <input
+              value={form.title}
+              onChange={e => setForm(f => ({ ...f, title: e.target.value }))}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white focus:outline-none focus:border-indigo-500 font-serif text-lg"
+            />
+          </div>
+          <div>
+            <div className="flex items-center justify-between mb-1.5">
+              <label className="text-sm font-medium text-slate-300">Content</label>
+              <span className="flex items-center gap-1 text-xs text-slate-500">
+                <BookOpen size={12} />
+                {wordCount.toLocaleString()} words
+              </span>
+            </div>
+            <textarea
+              value={form.content}
+              onChange={e => setForm(f => ({ ...f, content: e.target.value }))}
+              rows={24}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white focus:outline-none focus:border-indigo-500 resize-y font-serif text-base leading-relaxed"
+            />
+          </div>
+
+          <div className={`text-xs px-3 py-2 rounded-lg flex items-center gap-2 ${
+            chapter.is_published
+              ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'
+              : 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
+          }`}>
+            {chapter.is_published ? 'Published — visible to readers' : 'Draft — not visible to readers'}
+          </div>
+        </div>
+
+        {/* AI Suggestions panel */}
+        <div>
+          <AISuggestionPanel chapterId={chapter.chapter_id} storyId={chapter.story_id} />
+        </div>
+      </div>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/writer/EditStoryPage.tsx
===================================================================
--- chapterx-frontend/src/pages/writer/EditStoryPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/writer/EditStoryPage.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,221 @@
+import React, { useState, useEffect } from 'react'
+import { useParams, useNavigate } from 'react-router-dom'
+import { ArrowLeft, Plus, Save, Globe, Archive, Trash2 } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useUIStore } from '../../store/uiStore'
+import { Button } from '../../components/ui/Button'
+import { ChapterList } from '../../components/story/ChapterList'
+import { CollaboratorManager } from '../../components/writer/CollaboratorManager'
+import { Modal } from '../../components/ui/Modal'
+
+const ALL_GENRES = ['Fantasy', 'Sci-Fi', 'Romance', 'Historical Fiction', 'Adventure', 'Thriller', 'Mystery', 'Horror', 'Contemporary', 'Poetry']
+
+export const EditStoryPage: React.FC = () => {
+  const { id } = useParams<{ id: string }>()
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { stories, chapters, updateStory, deleteStory, updateStoryStatus } = useStoryStore()
+  const { addToast } = useUIStore()
+  const [deleteConfirm, setDeleteConfirm] = useState(false)
+  const [saving, setSaving] = useState(false)
+
+  const story = stories.find(s => s.story_id === Number(id))
+
+  const [form, setForm] = useState({
+    title: '',
+    short_description: '',
+    content: '',
+    genres: [] as string[],
+    mature_content: false,
+  })
+
+  useEffect(() => {
+    if (story) {
+      setForm({
+        title: story.title,
+        short_description: story.short_description,
+        content: story.content,
+        genres: [...story.genres],
+        mature_content: story.mature_content,
+      })
+    }
+  }, [story?.story_id])
+
+  if (!story) {
+    return (
+      <div className="max-w-4xl mx-auto px-4 py-20 text-center">
+        <h2 className="text-2xl text-white mb-4">Story not found</h2>
+        <Button onClick={() => navigate('/writer')}>Back to Dashboard</Button>
+      </div>
+    )
+  }
+
+  const canEdit = currentUser?.user_id === story.user_id || currentUser?.role === 'admin'
+  if (!canEdit) navigate('/writer')
+
+  const storyChapters = chapters.filter(c => c.story_id === story.story_id)
+
+  const handleSave = async () => {
+    setSaving(true)
+    await new Promise(r => setTimeout(r, 400))
+    updateStory(story.story_id, { ...form, updated_at: new Date().toISOString() })
+    addToast('Story updated!')
+    setSaving(false)
+  }
+
+  const handleDelete = () => {
+    deleteStory(story.story_id)
+    addToast('Story deleted', 'info')
+    navigate('/writer')
+  }
+
+  const toggleGenre = (g: string) =>
+    setForm(f => ({
+      ...f,
+      genres: f.genres.includes(g) ? f.genres.filter(x => x !== g) : [...f.genres, g],
+    }))
+
+  return (
+    <div className="max-w-5xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="flex items-center justify-between mb-8">
+        <div className="flex items-center gap-3">
+          <button onClick={() => navigate('/writer')} className="text-slate-400 hover:text-white transition-colors">
+            <ArrowLeft size={20} />
+          </button>
+          <div>
+            <h1 className="font-serif text-2xl font-bold text-white">Edit Story</h1>
+            <p className="text-slate-400 text-sm mt-0.5">Status: <span className="capitalize text-white">{story.status}</span></p>
+          </div>
+        </div>
+        <div className="flex gap-2">
+          {story.status === 'draft' && (
+            <Button size="sm" variant="secondary" onClick={() => { updateStoryStatus(story.story_id, 'published'); addToast('Story published!') }}>
+              <Globe size={14} />
+              Publish
+            </Button>
+          )}
+          {story.status === 'published' && (
+            <Button size="sm" variant="ghost" onClick={() => { updateStoryStatus(story.story_id, 'archived'); addToast('Story archived', 'info') }}>
+              <Archive size={14} />
+              Archive
+            </Button>
+          )}
+          <Button size="sm" variant="danger" onClick={() => setDeleteConfirm(true)}>
+            <Trash2 size={14} />
+          </Button>
+          <Button size="sm" onClick={handleSave} loading={saving}>
+            <Save size={14} />
+            Save
+          </Button>
+        </div>
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+        {/* Form */}
+        <div className="lg:col-span-2 space-y-6">
+          <div>
+            <label className="block text-sm font-medium text-slate-300 mb-1.5">Title</label>
+            <input
+              value={form.title}
+              onChange={e => setForm(f => ({ ...f, title: e.target.value }))}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white focus:outline-none focus:border-indigo-500 font-serif text-lg"
+            />
+          </div>
+          <div>
+            <label className="block text-sm font-medium text-slate-300 mb-1.5">Short Description</label>
+            <textarea
+              value={form.short_description}
+              onChange={e => setForm(f => ({ ...f, short_description: e.target.value }))}
+              rows={3}
+              maxLength={280}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white focus:outline-none focus:border-indigo-500 resize-none"
+            />
+          </div>
+          <div>
+            <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Content</label>
+            <textarea
+              value={form.content}
+              onChange={e => setForm(f => ({ ...f, content: e.target.value }))}
+              rows={6}
+              className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-white focus:outline-none focus:border-indigo-500 resize-y font-serif"
+            />
+          </div>
+          <div>
+            <label className="block text-sm font-medium text-slate-300 mb-2">Genres</label>
+            <div className="flex flex-wrap gap-2">
+              {ALL_GENRES.map(g => (
+                <button
+                  key={g}
+                  type="button"
+                  onClick={() => toggleGenre(g)}
+                  className={`px-3 py-1.5 rounded-full text-sm border transition-all ${
+                    form.genres.includes(g)
+                      ? 'bg-indigo-500/30 text-indigo-300 border-indigo-500/50'
+                      : 'bg-slate-800 text-slate-400 border-slate-700 hover:border-slate-500'
+                  }`}
+                >
+                  {g}
+                </button>
+              ))}
+            </div>
+          </div>
+          <div className="flex items-center gap-3">
+            <button
+              type="button"
+              onClick={() => setForm(f => ({ ...f, mature_content: !f.mature_content }))}
+              className={`w-11 h-6 rounded-full transition-colors ${form.mature_content ? 'bg-rose-500' : 'bg-slate-600'} relative`}
+            >
+              <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${form.mature_content ? 'translate-x-6' : 'translate-x-1'}`} />
+            </button>
+            <span className="text-sm text-slate-300">Mature Content (18+)</span>
+          </div>
+        </div>
+
+        {/* Sidebar */}
+        <div className="space-y-6">
+          {/* Chapters */}
+          <div className="bg-slate-800 border border-slate-700 rounded-2xl p-5">
+            <div className="flex items-center justify-between mb-4">
+              <h3 className="text-white font-semibold">Chapters</h3>
+              <Button size="sm" onClick={() => navigate(`/writer/create-chapter/${story.story_id}`)}>
+                <Plus size={13} />
+                Add
+              </Button>
+            </div>
+            <ChapterList
+              chapters={storyChapters}
+              storyId={story.story_id}
+              showEditLinks
+              onEdit={cid => navigate(`/writer/edit-chapter/${cid}`)}
+            />
+          </div>
+
+          {/* Collaborators */}
+          <CollaboratorManager
+            storyId={story.story_id}
+            storyTitle={story.title}
+            ownerId={story.user_id}
+          />
+        </div>
+      </div>
+
+      {/* Delete confirm */}
+      <Modal isOpen={deleteConfirm} onClose={() => setDeleteConfirm(false)} title="Delete Story" size="sm">
+        <div className="space-y-4">
+          <p className="text-slate-300 text-sm">
+            Are you sure you want to permanently delete "{story.title}"? This cannot be undone.
+          </p>
+          <div className="flex gap-3">
+            <Button variant="secondary" className="flex-1" onClick={() => setDeleteConfirm(false)}>Cancel</Button>
+            <Button variant="danger" className="flex-1" onClick={handleDelete}>
+              <Trash2 size={14} />
+              Delete
+            </Button>
+          </div>
+        </div>
+      </Modal>
+    </div>
+  )
+}
Index: chapterx-frontend/src/pages/writer/WriterDashboard.tsx
===================================================================
--- chapterx-frontend/src/pages/writer/WriterDashboard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/pages/writer/WriterDashboard.tsx	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,151 @@
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { Plus, BookOpen, Eye, Heart, MessageCircle, TrendingUp, Bell } from 'lucide-react'
+import { useAuthStore } from '../../store/authStore'
+import { useStoryStore } from '../../store/storyStore'
+import { useNotificationStore } from '../../store/notificationStore'
+import { Button } from '../../components/ui/Button'
+import { StatusBadge } from '../../components/ui/Badge'
+import { StoryAnalytics } from '../../components/writer/StoryAnalytics'
+
+function timeAgo(str: string): string {
+  const diff = Date.now() - new Date(str).getTime()
+  const m = Math.floor(diff / 60000)
+  if (m < 1) return 'just now'
+  if (m < 60) return `${m}m ago`
+  const h = Math.floor(m / 60)
+  if (h < 24) return `${h}h ago`
+  return `${Math.floor(h / 24)}d ago`
+}
+
+export const WriterDashboard: React.FC = () => {
+  const navigate = useNavigate()
+  const { currentUser } = useAuthStore()
+  const { stories } = useStoryStore()
+  const { notifications } = useNotificationStore()
+
+  if (!currentUser) return null
+
+  const myStories = stories.filter(s => s.user_id === currentUser.user_id)
+  const published = myStories.filter(s => s.status === 'published')
+  const drafts = myStories.filter(s => s.status === 'draft')
+  const totalViews = myStories.reduce((acc, s) => acc + s.total_views, 0)
+  const totalLikes = myStories.reduce((acc, s) => acc + s.total_likes, 0)
+
+  const recentNotifs = notifications.slice(0, 5)
+
+  return (
+    <div className="max-w-7xl mx-auto px-4 py-8">
+      {/* Header */}
+      <div className="flex items-center justify-between mb-8">
+        <div>
+          <h1 className="font-serif text-3xl font-bold text-white">
+            Welcome back, {currentUser.name}!
+          </h1>
+          <p className="text-slate-400 mt-1">Here's how your stories are performing</p>
+        </div>
+        <Button onClick={() => navigate('/writer/create-story')}>
+          <Plus size={16} />
+          New Story
+        </Button>
+      </div>
+
+      {/* Stats */}
+      <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
+        {[
+          { icon: <BookOpen size={20} className="text-indigo-300" />, label: 'Total Stories', value: myStories.length, sub: `${published.length} published`, color: 'bg-indigo-500/20' },
+          { icon: <Eye size={20} className="text-blue-300" />, label: 'Total Views', value: totalViews.toLocaleString(), sub: 'All time', color: 'bg-blue-500/20' },
+          { icon: <Heart size={20} className="text-rose-300" />, label: 'Total Likes', value: totalLikes.toLocaleString(), sub: 'Across all stories', color: 'bg-rose-500/20' },
+          { icon: <TrendingUp size={20} className="text-emerald-300" />, label: 'Drafts', value: drafts.length, sub: 'In progress', color: 'bg-emerald-500/20' },
+        ].map(s => (
+          <div key={s.label} className="bg-slate-800 border border-slate-700 rounded-2xl p-5">
+            <div className={`w-10 h-10 rounded-xl ${s.color} flex items-center justify-center mb-3`}>
+              {s.icon}
+            </div>
+            <p className="text-2xl font-bold text-white">{s.value}</p>
+            <p className="text-slate-400 text-sm mt-0.5">{s.label}</p>
+            <p className="text-slate-600 text-xs mt-0.5">{s.sub}</p>
+          </div>
+        ))}
+      </div>
+
+      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
+        {/* My stories */}
+        <div className="lg:col-span-2">
+          <div className="flex items-center justify-between mb-4">
+            <h2 className="font-serif text-xl font-bold text-white">My Stories</h2>
+          </div>
+          {myStories.length === 0 ? (
+            <div className="bg-slate-800 border border-slate-700 rounded-2xl p-12 text-center">
+              <BookOpen size={40} className="mx-auto mb-4 text-slate-600" />
+              <h3 className="text-white font-medium mb-2">No stories yet</h3>
+              <p className="text-slate-500 text-sm mb-4">Start your writing journey today!</p>
+              <Button onClick={() => navigate('/writer/create-story')}>
+                <Plus size={14} />
+                Create Your First Story
+              </Button>
+            </div>
+          ) : (
+            <div className="space-y-3">
+              {myStories.map(story => (
+                <div key={story.story_id} className="flex items-center gap-4 p-4 bg-slate-800 border border-slate-700 rounded-xl hover:border-indigo-500/40 transition-colors">
+                  <div className="flex-1 min-w-0">
+                    <div className="flex items-center gap-2 mb-1">
+                      <h3 className="text-white font-medium text-sm truncate">{story.title}</h3>
+                      <StatusBadge status={story.status} />
+                    </div>
+                    <div className="flex items-center gap-3 text-slate-500 text-xs">
+                      <span className="flex items-center gap-1"><Eye size={11} /> {story.total_views.toLocaleString()}</span>
+                      <span className="flex items-center gap-1"><Heart size={11} /> {story.total_likes}</span>
+                      <span className="flex items-center gap-1"><MessageCircle size={11} /> {story.total_comments}</span>
+                      <span>{story.total_chapters} chapters</span>
+                    </div>
+                  </div>
+                  <div className="flex gap-2 flex-shrink-0">
+                    <Button size="sm" variant="ghost" onClick={() => navigate(`/story/${story.story_id}`)}>View</Button>
+                    <Button size="sm" variant="secondary" onClick={() => navigate(`/writer/edit-story/${story.story_id}`)}>Edit</Button>
+                  </div>
+                </div>
+              ))}
+            </div>
+          )}
+        </div>
+
+        {/* Recent notifications */}
+        <div>
+          <div className="flex items-center gap-2 mb-4">
+            <Bell size={16} className="text-amber-400" />
+            <h2 className="font-serif text-xl font-bold text-white">Recent Activity</h2>
+          </div>
+          <div className="space-y-2">
+            {recentNotifs.length === 0 ? (
+              <div className="text-center py-8 text-slate-500 text-sm">No recent activity</div>
+            ) : (
+              recentNotifs.map(n => (
+                <div key={n.notification_id} className={`p-3 rounded-xl border text-sm ${
+                  !n.is_read ? 'bg-indigo-500/5 border-indigo-500/20' : 'bg-slate-800 border-slate-700'
+                }`}>
+                  <p className="text-white text-xs font-medium">{n.title}</p>
+                  <p className="text-slate-400 text-xs mt-0.5 line-clamp-2">{n.message}</p>
+                  <p className="text-slate-600 text-xs mt-1">{timeAgo(n.created_at)}</p>
+                </div>
+              ))
+            )}
+          </div>
+        </div>
+      </div>
+
+      {/* Analytics */}
+      {myStories.some(s => s.status === 'published') && (
+        <div className="mt-10">
+          <div className="flex items-center gap-2 mb-6">
+            <TrendingUp size={18} className="text-indigo-400" />
+            <h2 className="font-serif text-xl font-bold text-white">Analytics</h2>
+            <span className="text-slate-500 text-sm">(Story: The Chronicles of Eldoria)</span>
+          </div>
+          <StoryAnalytics />
+        </div>
+      )}
+    </div>
+  )
+}
Index: chapterx-frontend/src/store/authStore.ts
===================================================================
--- chapterx-frontend/src/store/authStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/store/authStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,122 @@
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+import { User, UserRole } from '../types'
+import { mockUsers } from '../data/mockData'
+import axios from 'axios'
+
+const API_BASE = 'https://localhost:7125/api'
+
+interface AuthStore {
+  currentUser: User | null
+  token: string | null
+  showMatureContent: boolean
+  allUsers: User[]
+  login: (emailOrUsername: string, password: string) => Promise<void>
+  logout: () => void
+  register: (data: { username: string; email: string; name: string; surname: string; password: string }, role: 'regular' | 'writer') => Promise<void>
+  switchUser: (userId: number) => void
+  setShowMatureContent: (show: boolean) => void
+  updateUserRole: (userId: number, role: UserRole) => void
+  addUser: (user: User) => void
+}
+
+export const useAuthStore = create<AuthStore>()(
+  persist(
+    (set, get) => ({
+      currentUser: null,
+      token: null,
+      showMatureContent: true,
+      allUsers: [...mockUsers],
+
+      login: async (emailOrUsername, password) => {
+        // Try backend first
+        try {
+          const email = emailOrUsername.includes('@')
+            ? emailOrUsername
+            : get().allUsers.find(u => u.username === emailOrUsername)?.email || emailOrUsername
+          const res = await axios.post(`${API_BASE}/auth/login`, { email, password }, { timeout: 3000 })
+          const { token, userId, username } = res.data
+          const user = get().allUsers.find(u => u.user_id === userId) ||
+            get().allUsers.find(u => u.username === username) || {
+              user_id: userId,
+              username,
+              email,
+              name: username,
+              surname: '',
+              role: 'regular' as const,
+              created_at: new Date().toISOString(),
+              follower_count: 0,
+              following_count: 0,
+            }
+          set({ currentUser: user, token })
+          return
+        } catch {
+          // Fall through to mock login
+        }
+
+        // Fallback to mock
+        const user = get().allUsers.find(
+          u => u.username === emailOrUsername || u.email === emailOrUsername
+        )
+        if (!user) throw new Error('User not found. Try using a quick-login option.')
+        set({ currentUser: user, token: 'mock-token' })
+      },
+
+      logout: () => set({ currentUser: null, token: null }),
+
+      register: async (data, role) => {
+        // Try backend first
+        try {
+          await axios.post(`${API_BASE}/auth/register`, data, { timeout: 3000 })
+        } catch {
+          // Fall through to mock register
+        }
+
+        const newUser: User = {
+          user_id: Date.now(),
+          ...data,
+          role,
+          created_at: new Date().toISOString(),
+          follower_count: 0,
+          following_count: 0,
+        }
+        set(state => ({
+          allUsers: [...state.allUsers, newUser],
+          currentUser: newUser,
+          token: 'mock-token',
+        }))
+      },
+
+      switchUser: (userId: number) => {
+        if (userId === 0) {
+          set({ currentUser: null, token: null })
+          return
+        }
+        const user = get().allUsers.find(u => u.user_id === userId)
+        if (user) set({ currentUser: user, token: 'mock-token' })
+      },
+
+      setShowMatureContent: (show: boolean) => set({ showMatureContent: show }),
+
+      updateUserRole: (userId: number, role: UserRole) =>
+        set(state => ({
+          allUsers: state.allUsers.map(u => (u.user_id === userId ? { ...u, role } : u)),
+          currentUser:
+            state.currentUser?.user_id === userId
+              ? { ...state.currentUser, role }
+              : state.currentUser,
+        })),
+
+      addUser: (user: User) =>
+        set(state => ({ allUsers: [...state.allUsers, user] })),
+    }),
+    {
+      name: 'chapterx-auth',
+      partialize: s => ({
+        currentUser: s.currentUser,
+        token: s.token,
+        showMatureContent: s.showMatureContent,
+      }),
+    }
+  )
+)
Index: chapterx-frontend/src/store/notificationStore.ts
===================================================================
--- chapterx-frontend/src/store/notificationStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/store/notificationStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,42 @@
+import { create } from 'zustand'
+import { Notification } from '../types'
+import { mockNotifications } from '../data/mockData'
+
+interface NotificationStore {
+  notifications: Notification[]
+  addNotification: (n: Omit<Notification, 'notification_id' | 'created_at' | 'is_read'>) => void
+  markAllRead: () => void
+  markRead: (id: number) => void
+  getUnreadCount: () => number
+}
+
+export const useNotificationStore = create<NotificationStore>((set, get) => ({
+  notifications: [...mockNotifications],
+
+  addNotification: (n) =>
+    set(state => ({
+      notifications: [
+        {
+          ...n,
+          notification_id: Date.now(),
+          created_at: new Date().toISOString(),
+          is_read: false,
+        },
+        ...state.notifications,
+      ],
+    })),
+
+  markAllRead: () =>
+    set(state => ({
+      notifications: state.notifications.map(n => ({ ...n, is_read: true })),
+    })),
+
+  markRead: (id) =>
+    set(state => ({
+      notifications: state.notifications.map(n =>
+        n.notification_id === id ? { ...n, is_read: true } : n
+      ),
+    })),
+
+  getUnreadCount: () => get().notifications.filter(n => !n.is_read).length,
+}))
Index: chapterx-frontend/src/store/storyStore.ts
===================================================================
--- chapterx-frontend/src/store/storyStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/store/storyStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,333 @@
+import { create } from 'zustand'
+import axios from 'axios'
+import {
+  Story,
+  Chapter,
+  Comment,
+  Collaboration,
+  AISuggestion,
+  Genre,
+  ReadingList,
+  ReadingListItem,
+  StoryStatus,
+  PermissionLevel,
+} from '../types'
+import {
+  mockStories,
+  mockChapters,
+  mockComments,
+  mockCollaborations,
+  mockAISuggestions,
+  mockGenres,
+  mockReadingLists,
+} from '../data/mockData'
+
+interface LikeRecord {
+  userId: number
+  storyId: number
+}
+
+interface StoryStore {
+  stories: Story[]
+  chapters: Chapter[]
+  comments: Comment[]
+  collaborations: Collaboration[]
+  aiSuggestions: AISuggestion[]
+  genres: Genre[]
+  readingLists: ReadingList[]
+  likedStories: LikeRecord[]
+
+  // Story actions
+  addStory: (story: Story) => void
+  updateStory: (id: number, partial: Partial<Story>) => void
+  deleteStory: (id: number) => void
+  updateStoryStatus: (id: number, status: StoryStatus) => void
+
+  // Chapter actions
+  addChapter: (chapter: Chapter) => void
+  updateChapter: (id: number, partial: Partial<Chapter>) => void
+  deleteChapter: (id: number) => void
+  incrementViewCount: (chapterId: number) => void
+
+  // Comment actions
+  addComment: (comment: Comment) => void
+  deleteComment: (id: number) => void
+
+  // Like actions
+  toggleLike: (userId: number, storyId: number) => void
+  isLiked: (userId: number, storyId: number) => boolean
+
+  // Collaboration actions
+  addCollaboration: (collab: Collaboration) => void
+  updateCollaborationPermission: (userId: number, storyId: number, level: PermissionLevel) => void
+  removeCollaboration: (userId: number, storyId: number) => void
+
+  // AI Suggestion actions
+  fetchSuggestions: () => Promise<void>
+  acceptSuggestion: (id: number) => Promise<void>
+  rejectSuggestion: (id: number) => Promise<void>
+  addSuggestion: (suggestion: Omit<AISuggestion, 'suggestion_id'>) => Promise<void>
+
+  // Genre actions
+  addGenre: (genre: Genre) => void
+  deleteGenre: (id: number) => void
+
+  // Reading list actions
+  createReadingList: (list: ReadingList) => void
+  addStoryToList: (listId: number, item: ReadingListItem) => void
+  removeStoryFromList: (listId: number, storyId: number) => void
+  deleteReadingList: (listId: number) => void
+}
+
+export const useStoryStore = create<StoryStore>((set, get) => ({
+  stories: [...mockStories],
+  chapters: [...mockChapters],
+  comments: [...mockComments],
+  collaborations: [...mockCollaborations],
+  aiSuggestions: [...mockAISuggestions],
+  genres: [...mockGenres],
+  readingLists: [...mockReadingLists],
+  likedStories: [],
+
+  addStory: (story) =>
+    set(state => ({ stories: [...state.stories, story] })),
+
+  updateStory: (id, partial) =>
+    set(state => ({
+      stories: state.stories.map(s => (s.story_id === id ? { ...s, ...partial } : s)),
+    })),
+
+  deleteStory: (id) =>
+    set(state => ({
+      stories: state.stories.filter(s => s.story_id !== id),
+      chapters: state.chapters.filter(c => c.story_id !== id),
+      comments: state.comments.filter(c => c.story_id !== id),
+      collaborations: state.collaborations.filter(c => c.story_id !== id),
+    })),
+
+  updateStoryStatus: (id, status) =>
+    set(state => ({
+      stories: state.stories.map(s =>
+        s.story_id === id ? { ...s, status, updated_at: new Date().toISOString() } : s
+      ),
+    })),
+
+  addChapter: (chapter) =>
+    set(state => ({
+      chapters: [...state.chapters, chapter],
+      stories: state.stories.map(s =>
+        s.story_id === chapter.story_id
+          ? { ...s, total_chapters: s.total_chapters + 1 }
+          : s
+      ),
+    })),
+
+  updateChapter: (id, partial) =>
+    set(state => ({
+      chapters: state.chapters.map(c =>
+        c.chapter_id === id ? { ...c, ...partial, updated_at: new Date().toISOString() } : c
+      ),
+    })),
+
+  deleteChapter: (id) =>
+    set(state => {
+      const chapter = state.chapters.find(c => c.chapter_id === id)
+      return {
+        chapters: state.chapters.filter(c => c.chapter_id !== id),
+        stories: chapter
+          ? state.stories.map(s =>
+              s.story_id === chapter.story_id
+                ? { ...s, total_chapters: Math.max(0, s.total_chapters - 1) }
+                : s
+            )
+          : state.stories,
+      }
+    }),
+
+  incrementViewCount: (chapterId) =>
+    set(state => ({
+      chapters: state.chapters.map(c =>
+        c.chapter_id === chapterId ? { ...c, view_count: c.view_count + 1 } : c
+      ),
+    })),
+
+  addComment: (comment) =>
+    set(state => ({
+      comments: [...state.comments, comment],
+      stories: state.stories.map(s =>
+        s.story_id === comment.story_id
+          ? { ...s, total_comments: s.total_comments + 1 }
+          : s
+      ),
+    })),
+
+  deleteComment: (id) =>
+    set(state => {
+      const comment = state.comments.find(c => c.comment_id === id)
+      return {
+        comments: state.comments.filter(c => c.comment_id !== id),
+        stories: comment
+          ? state.stories.map(s =>
+              s.story_id === comment.story_id
+                ? { ...s, total_comments: Math.max(0, s.total_comments - 1) }
+                : s
+            )
+          : state.stories,
+      }
+    }),
+
+  toggleLike: (userId, storyId) =>
+    set(state => {
+      const exists = state.likedStories.some(
+        l => l.userId === userId && l.storyId === storyId
+      )
+      return {
+        likedStories: exists
+          ? state.likedStories.filter(l => !(l.userId === userId && l.storyId === storyId))
+          : [...state.likedStories, { userId, storyId }],
+        stories: state.stories.map(s =>
+          s.story_id === storyId
+            ? { ...s, total_likes: exists ? s.total_likes - 1 : s.total_likes + 1 }
+            : s
+        ),
+      }
+    }),
+
+  isLiked: (userId, storyId) =>
+    get().likedStories.some(l => l.userId === userId && l.storyId === storyId),
+
+  addCollaboration: (collab) =>
+    set(state => ({ collaborations: [...state.collaborations, collab] })),
+
+  updateCollaborationPermission: (userId, storyId, level) =>
+    set(state => ({
+      collaborations: state.collaborations.map(c =>
+        c.user_id === userId && c.story_id === storyId
+          ? { ...c, permission_level: level }
+          : c
+      ),
+    })),
+
+  removeCollaboration: (userId, storyId) =>
+    set(state => ({
+      collaborations: state.collaborations.filter(
+        c => !(c.user_id === userId && c.story_id === storyId)
+      ),
+    })),
+
+  fetchSuggestions: async () => {
+    try {
+      const res = await axios.get('https://localhost:7125/api/aisuggestions')
+      const data = res.data.aiSuggestions ?? res.data
+      const mapped: AISuggestion[] = data.map((s: any) => ({
+        suggestion_id: s.id,
+        chapter_id: s.storyId,
+        story_id: s.storyId,
+        original_text: s.originalText,
+        suggested_text: s.suggestedText,
+        suggestion_type: 'style' as const,
+        accepted: s.accepted === true ? true : s.accepted === false ? false : null,
+        applied_at: s.appliedAt ?? undefined,
+      }))
+      set({ aiSuggestions: mapped })
+    } catch {
+      // keep mock data on failure
+    }
+  },
+
+  acceptSuggestion: async (id) => {
+    const s = get().aiSuggestions.find(s => s.suggestion_id === id)
+    if (!s) return
+    // optimistic update
+    set(state => ({
+      aiSuggestions: state.aiSuggestions.map(s =>
+        s.suggestion_id === id
+          ? { ...s, accepted: true, applied_at: new Date().toISOString() }
+          : s
+      ),
+    }))
+    try {
+      await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
+        id,
+        originalText: s.original_text,
+        suggestedText: s.suggested_text,
+        accepted: true,
+      })
+    } catch {
+      // keep optimistic update even if backend fails
+    }
+  },
+
+  rejectSuggestion: async (id) => {
+    const s = get().aiSuggestions.find(s => s.suggestion_id === id)
+    if (!s) return
+    set(state => ({
+      aiSuggestions: state.aiSuggestions.map(s =>
+        s.suggestion_id === id ? { ...s, accepted: false } : s
+      ),
+    }))
+    try {
+      await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
+        id,
+        originalText: s.original_text,
+        suggestedText: s.suggested_text,
+        accepted: false,
+      })
+    } catch {
+      // keep optimistic update even if backend fails
+    }
+  },
+
+  addSuggestion: async (suggestion) => {
+    // optimistic local add
+    const tempId = Date.now()
+    set(state => ({ aiSuggestions: [...state.aiSuggestions, { ...suggestion, suggestion_id: tempId }] }))
+    try {
+      const res = await axios.post('https://localhost:7125/api/aisuggestions', {
+        originalText: suggestion.original_text,
+        suggestedText: suggestion.suggested_text,
+        storyId: suggestion.chapter_id,
+      })
+      const newId = res.data.id ?? tempId
+      set(state => ({
+        aiSuggestions: state.aiSuggestions.map(s =>
+          s.suggestion_id === tempId ? { ...s, suggestion_id: newId } : s
+        ),
+      }))
+    } catch {
+      // keep local suggestion on failure
+    }
+  },
+
+  addGenre: (genre) =>
+    set(state => ({ genres: [...state.genres, genre] })),
+
+  deleteGenre: (id) =>
+    set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })),
+
+  createReadingList: (list) =>
+    set(state => ({ readingLists: [...state.readingLists, list] })),
+
+  addStoryToList: (listId, item) =>
+    set(state => ({
+      readingLists: state.readingLists.map(l =>
+        l.list_id === listId
+          ? { ...l, stories: [...l.stories, item] }
+          : l
+      ),
+    })),
+
+  removeStoryFromList: (listId, storyId) =>
+    set(state => ({
+      readingLists: state.readingLists.map(l =>
+        l.list_id === listId
+          ? { ...l, stories: l.stories.filter(s => s.story_id !== storyId) }
+          : l
+      ),
+    })),
+
+  deleteReadingList: (listId) =>
+    set(state => ({
+      readingLists: state.readingLists.filter(l => l.list_id !== listId),
+    })),
+}))
Index: chapterx-frontend/src/store/uiStore.ts
===================================================================
--- chapterx-frontend/src/store/uiStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/store/uiStore.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,29 @@
+import { create } from 'zustand'
+
+interface Toast {
+  id: number
+  message: string
+  type: 'success' | 'error' | 'warning' | 'info'
+}
+
+interface UIStore {
+  toasts: Toast[]
+  addToast: (message: string, type?: Toast['type']) => void
+  removeToast: (id: number) => void
+}
+
+export const useUIStore = create<UIStore>((set) => ({
+  toasts: [],
+
+  addToast: (message, type = 'success') => {
+    const id = Date.now()
+    set(state => ({ toasts: [...state.toasts, { id, message, type }] }))
+    setTimeout(
+      () => set(state => ({ toasts: state.toasts.filter(t => t.id !== id) })),
+      3500
+    )
+  },
+
+  removeToast: (id) =>
+    set(state => ({ toasts: state.toasts.filter(t => t.id !== id) })),
+}))
Index: chapterx-frontend/src/types/index.ts
===================================================================
--- chapterx-frontend/src/types/index.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/src/types/index.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,140 @@
+export type UserRole = 'admin' | 'writer' | 'regular' | 'guest'
+export type StoryStatus = 'draft' | 'published' | 'archived'
+export type PermissionLevel = 1 | 2 | 3 | 4 | 5
+export type CollaboratorRole = 'co-author' | 'editor' | 'proofreader' | 'beta-reader'
+export type SuggestionType = 'grammar' | 'style' | 'plot' | 'character' | 'pacing'
+export type NotificationType = 'like' | 'comment' | 'collaboration' | 'system' | 'follow' | 'story_update'
+
+export interface User {
+  user_id: number
+  username: string
+  email: string
+  name: string
+  surname: string
+  role: UserRole
+  bio?: string
+  avatar?: string
+  created_at: string
+  follower_count: number
+  following_count: number
+}
+
+export interface Story {
+  story_id: number
+  title: string
+  short_description: string
+  content: string
+  mature_content: boolean
+  user_id: number
+  author_username: string
+  status: StoryStatus
+  genres: string[]
+  created_at: string
+  updated_at: string
+  total_likes: number
+  total_comments: number
+  total_chapters: number
+  total_views: number
+  cover_image?: string
+}
+
+export interface Chapter {
+  chapter_id: number
+  story_id: number
+  title: string
+  content: string
+  chapter_number: number
+  word_count: number
+  view_count: number
+  created_at: string
+  updated_at: string
+  is_published: boolean
+}
+
+export interface Comment {
+  comment_id: number
+  story_id: number
+  user_id: number
+  username: string
+  content: string
+  created_at: string
+}
+
+export interface ReadingList {
+  list_id: number
+  user_id: number
+  username: string
+  name: string
+  description?: string
+  is_public: boolean
+  created_at: string
+  stories: ReadingListItem[]
+}
+
+export interface ReadingListItem {
+  item_id: number
+  list_id: number
+  story_id: number
+  story_title: string
+  author_username: string
+  added_at: string
+  genres: string[]
+}
+
+export interface Collaboration {
+  collab_id: number
+  story_id: number
+  story_title: string
+  user_id: number
+  username: string
+  name: string
+  role: CollaboratorRole
+  permission_level: PermissionLevel
+  joined_at: string
+}
+
+export interface AISuggestion {
+  suggestion_id: number
+  chapter_id: number
+  suggestion_type: SuggestionType
+  original_text: string
+  suggested_text: string
+  explanation: string
+  accepted: boolean | null
+  created_at: string
+  applied_at?: string
+}
+
+export interface Notification {
+  notification_id: number
+  user_id: number
+  type: NotificationType
+  title: string
+  message: string
+  is_read: boolean
+  created_at: string
+  link?: string
+}
+
+export interface Genre {
+  genre_id: number
+  name: string
+  story_count: number
+}
+
+export interface StoryAnalytics {
+  story_id: number
+  views_over_time: { date: string; views: number }[]
+  likes_over_time: { date: string; likes: number }[]
+  total_views: number
+  total_likes: number
+  total_comments: number
+  avg_read_time: number
+  completion_rate: number
+}
+
+export interface AuthState {
+  token: string | null
+  currentUser: User | null
+  showMatureContent: boolean
+}
Index: chapterx-frontend/tailwind.config.js
===================================================================
--- chapterx-frontend/tailwind.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/tailwind.config.js	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,16 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+  theme: {
+    extend: {
+      fontFamily: {
+        serif: ['Playfair Display', 'Georgia', 'serif'],
+        sans: ['Inter', 'system-ui', 'sans-serif'],
+      },
+      colors: {
+        indigo: { 950: '#1e1b4b' },
+      },
+    },
+  },
+  plugins: [],
+}
Index: chapterx-frontend/tsconfig.app.json
===================================================================
--- chapterx-frontend/tsconfig.app.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/tsconfig.app.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,28 @@
+{
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+    "target": "ES2023",
+    "useDefineForClassFields": true,
+    "lib": ["ES2023", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "types": ["vite/client"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": false,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "react-jsx",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "erasableSyntaxOnly": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["src"]
+}
Index: chapterx-frontend/tsconfig.json
===================================================================
--- chapterx-frontend/tsconfig.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/tsconfig.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,7 @@
+{
+  "files": [],
+  "references": [
+    { "path": "./tsconfig.app.json" },
+    { "path": "./tsconfig.node.json" }
+  ]
+}
Index: chapterx-frontend/tsconfig.node.json
===================================================================
--- chapterx-frontend/tsconfig.node.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/tsconfig.node.json	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,26 @@
+{
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+    "target": "ES2023",
+    "lib": ["ES2023"],
+    "module": "ESNext",
+    "types": ["node"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "erasableSyntaxOnly": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["vite.config.ts"]
+}
Index: chapterx-frontend/vite.config.ts
===================================================================
--- chapterx-frontend/vite.config.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
+++ chapterx-frontend/vite.config.ts	(revision b62cefc14094ca5ac7154f7d48797a1e6c444935)
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [react()],
+})
