commit b422c43928fae3557d4320468bfea3c9ee6eef36
Author: “dongming” <“lidongming@aituringflow.com”>
Date: Thu Dec 18 12:22:21 2025 +0800
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e32df38
--- /dev/null
+++ b/README.md
@@ -0,0 +1,150 @@
+# turingflow-blog-001
+
+个人作品集/博客网站模板,适用于设计师、开发者、自由职业者展示作品和服务。
+
+## 技术栈
+
+- React 19 + Vite 7
+- Tailwind CSS 4
+- React Router DOM 7
+- Lucide React (图标库)
+
+## 快速开始
+
+```bash
+pnpm install
+pnpm run dev
+```
+
+## 目录结构
+
+```text
+src/
+├── App.jsx # 应用入口,包含路由配置
+├── main.jsx # React 挂载点
+├── index.css # 全局样式 (Tailwind)
+├── components/ # 可复用组件
+│ ├── NavBar.jsx # 导航栏
+│ ├── Footer.jsx # 页脚
+│ ├── Button.jsx # 按钮组件
+│ ├── AboutCard.jsx # 关于卡片
+│ ├── ProjectCards.jsx # 项目卡片列表
+│ ├── Testimonials.jsx # 客户评价
+│ ├── Faq.jsx # 常见问题
+│ └── ... # 其他组件
+├── pages/ # 页面组件
+│ ├── Home.jsx # 首页
+│ ├── About.jsx # 关于页面
+│ ├── Services.jsx # 服务页面
+│ ├── Portfolio.jsx # 作品集列表
+│ ├── ProductDetail.jsx# 作品详情
+│ ├── Blog.jsx # 博客列表
+│ ├── BlogDetail.jsx # 博客详情
+│ └── Contact.jsx # 联系页面
+├── data/ # 静态数据 (JSON)
+│ ├── menu.json # 导航菜单配置
+│ ├── projects.json # 作品集数据
+│ ├── blog.json # 博客文章数据
+│ ├── testimonials.json# 客户评价数据
+│ ├── brands.json # 合作品牌数据
+│ ├── certifications.json # 资质认证数据
+│ ├── faq.json # 常见问题数据
+│ └── work.json # 工作经历数据
+└── lib/ # 工具库
+ └── Head.jsx # 页面头部管理
+```
+
+## 路由配置
+
+| 路径 | 页面 | 说明 |
+|------|------|------|
+| `/` | Home | 首页 |
+| `/about` | About | 关于我 |
+| `/services` | Services | 服务介绍 |
+| `/portfolio` | Portfolio | 作品集列表 |
+| `/portfolio/:slug` | ProductDetail | 作品详情 |
+| `/blog` | Blog | 博客列表 |
+| `/blog/:slug` | BlogDetail | 博客详情 |
+| `/contact` | Contact | 联系方式 |
+
+## 数据结构
+
+### 导航菜单 (data/menu.json)
+
+```json
+[
+ { "label": "首页", "path": "/" },
+ { "label": "关于", "path": "/about" },
+ { "label": "服务", "path": "/services" }
+]
+```
+
+### 项目数据 (data/projects.json)
+
+```json
+[
+ {
+ "id": 1,
+ "slug": "project-name",
+ "title": "项目标题",
+ "description": "项目描述",
+ "image": "/images/project.jpg",
+ "category": "Web Design",
+ "tags": ["React", "Tailwind"]
+ }
+]
+```
+
+### 博客数据 (data/blog.json)
+
+```json
+[
+ {
+ "id": 1,
+ "slug": "article-slug",
+ "title": "文章标题",
+ "excerpt": "文章摘要",
+ "content": "文章正文内容...",
+ "image": "/images/blog.jpg",
+ "date": "2024-01-15",
+ "author": "作者名",
+ "tags": ["设计", "开发"]
+ }
+]
+```
+
+## 常见修改任务
+
+### 修改网站信息
+
+1. 编辑 `index.html` 中的 `
` 和 meta 标签
+2. 修改 `src/components/NavBar.jsx` 中的 Logo 和网站名称
+3. 修改 `src/components/Footer.jsx` 中的版权信息
+
+### 修改导航菜单
+
+编辑 `src/data/menu.json`,添加或删除菜单项
+
+### 添加新页面
+
+1. 在 `src/pages/` 创建新页面组件
+2. 在 `src/App.jsx` 的 Routes 中添加路由
+
+### 修改样式主题
+
+编辑 `src/index.css` 中的 CSS 变量或 Tailwind 配置
+
+### 添加作品/博客
+
+编辑 `src/data/projects.json` 或 `src/data/blog.json`
+
+## 布局说明
+
+- `PageShell` 组件统一控制页面最大宽度 (`max-w-7xl`)
+- 响应式断点:`lg:px-10` (大屏增加内边距)
+- 所有页面共享 NavBar 和 Footer
+
+## 图片资源
+
+- 存放在 `public/` 目录
+- 引用路径:`/images/xxx.jpg`
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..cee1e2c
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c0d14e7
--- /dev/null
+++ b/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+ Priscy Designs
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d920334
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "cillaux",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "dev-host":"vite --host",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tailwindcss/vite": "^4.1.14",
+ "lucide-react": "^0.546.0",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "react-router-dom": "^7.9.4",
+ "tailwindcss": "^4.1.14"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.36.0",
+ "@types/react": "^19.1.16",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.4",
+ "eslint": "^9.36.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.22",
+ "globals": "^16.4.0",
+ "vite": "^7.1.7"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..0dea1e1
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,2141 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@tailwindcss/vite':
+ specifier: ^4.1.14
+ version: 4.1.18(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))
+ lucide-react:
+ specifier: ^0.546.0
+ version: 0.546.0(react@19.2.3)
+ react:
+ specifier: 19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: 19.2.3
+ version: 19.2.3(react@19.2.3)
+ react-router-dom:
+ specifier: ^7.9.4
+ version: 7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ tailwindcss:
+ specifier: ^4.1.14
+ version: 4.1.18
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.36.0
+ version: 9.39.2
+ '@types/react':
+ specifier: ^19.1.16
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: ^19.1.9
+ version: 19.2.3(@types/react@19.2.7)
+ '@vitejs/plugin-react':
+ specifier: ^5.0.4
+ version: 5.1.2(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))
+ eslint:
+ specifier: ^9.36.0
+ version: 9.39.2(jiti@2.6.1)
+ eslint-plugin-react-hooks:
+ specifier: ^5.2.0
+ version: 5.2.0(eslint@9.39.2(jiti@2.6.1))
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.22
+ version: 0.4.26(eslint@9.39.2(jiti@2.6.1))
+ globals:
+ specifier: ^16.4.0
+ version: 16.5.0
+ vite:
+ specifier: ^7.1.7
+ version: 7.3.0(jiti@2.6.1)(lightningcss@1.30.2)
+
+packages:
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
+ '@esbuild/aix-ppc64@0.27.2':
+ resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.27.2':
+ resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.2':
+ resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.2':
+ resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.27.2':
+ resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.2':
+ resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.27.2':
+ resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.2':
+ resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.27.2':
+ resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.2':
+ resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.2':
+ resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.2':
+ resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.2':
+ resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.2':
+ resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.2':
+ resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.2':
+ resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.2':
+ resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.27.2':
+ resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.2':
+ resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.27.2':
+ resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.2':
+ resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.27.2':
+ resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.27.2':
+ resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.27.2':
+ resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.2':
+ resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.2':
+ resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.1':
+ resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.3':
+ resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.2':
+ resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@rolldown/pluginutils@1.0.0-beta.53':
+ resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
+
+ '@rollup/rollup-android-arm-eabi@4.53.5':
+ resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.53.5':
+ resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.53.5':
+ resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.53.5':
+ resolution: {integrity: sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.53.5':
+ resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.53.5':
+ resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.5':
+ resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.5':
+ resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.5':
+ resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.53.5':
+ resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.5':
+ resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.5':
+ resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.5':
+ resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.5':
+ resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.5':
+ resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.53.5':
+ resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.53.5':
+ resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.53.5':
+ resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.5':
+ resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.5':
+ resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.53.5':
+ resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.53.5':
+ resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/node@4.1.18':
+ resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.18':
+ resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/vite@4.1.18':
+ resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.7':
+ resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
+
+ '@vitejs/plugin-react@5.1.2':
+ resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ baseline-browser-mapping@2.9.9:
+ resolution: {integrity: sha512-V8fbOCSeOFvlDj7LLChUcqbZrdKD9RU/VR260piF1790vT0mfLSwGc/Qzxv3IqiTukOpNtItePa0HBpMAj7MDg==}
+ hasBin: true
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001760:
+ resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ electron-to-chromium@1.5.267:
+ resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+ enhanced-resolve@5.18.4:
+ resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
+ engines: {node: '>=10.13.0'}
+
+ esbuild@0.27.2:
+ resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-plugin-react-hooks@5.2.0:
+ resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react-refresh@0.4.26:
+ resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==}
+ peerDependencies:
+ eslint: '>=8.40'
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.39.2:
+ resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@16.5.0:
+ resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
+ engines: {node: '>=18'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lucide-react@0.546.0:
+ resolution: {integrity: sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ react-dom@19.2.3:
+ resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
+ peerDependencies:
+ react: ^19.2.3
+
+ react-refresh@0.18.0:
+ resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+ engines: {node: '>=0.10.0'}
+
+ react-router-dom@7.11.0:
+ resolution: {integrity: sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.11.0:
+ resolution: {integrity: sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react@19.2.3:
+ resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ rollup@4.53.5:
+ resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ vite@7.3.0:
+ resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+snapshots:
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.5': {}
+
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@esbuild/aix-ppc64@0.27.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/android-arm@0.27.2':
+ optional: true
+
+ '@esbuild/android-x64@0.27.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.2':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.2':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))':
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.3':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.2': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@rolldown/pluginutils@1.0.0-beta.53': {}
+
+ '@rollup/rollup-android-arm-eabi@4.53.5':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.53.5':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.53.5':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.5':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.53.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.53.5':
+ optional: true
+
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.4
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/vite@4.1.18(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ tailwindcss: 4.1.18
+ vite: 7.3.0(jiti@2.6.1)(lightningcss@1.30.2)
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/estree@1.0.8': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/react-dom@19.2.3(@types/react@19.2.7)':
+ dependencies:
+ '@types/react': 19.2.7
+
+ '@types/react@19.2.7':
+ dependencies:
+ csstype: 3.2.3
+
+ '@vitejs/plugin-react@5.1.2(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.53
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.18.0
+ vite: 7.3.0(jiti@2.6.1)(lightningcss@1.30.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ argparse@2.0.1: {}
+
+ balanced-match@1.0.2: {}
+
+ baseline-browser-mapping@2.9.9: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.9
+ caniuse-lite: 1.0.30001760
+ electron-to-chromium: 1.5.267
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ callsites@3.1.0: {}
+
+ caniuse-lite@1.0.30001760: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ concat-map@0.0.1: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@1.1.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.2.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ deep-is@0.1.4: {}
+
+ detect-libc@2.1.2: {}
+
+ electron-to-chromium@1.5.267: {}
+
+ enhanced-resolve@5.18.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ esbuild@0.27.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.2
+ '@esbuild/android-arm': 0.27.2
+ '@esbuild/android-arm64': 0.27.2
+ '@esbuild/android-x64': 0.27.2
+ '@esbuild/darwin-arm64': 0.27.2
+ '@esbuild/darwin-x64': 0.27.2
+ '@esbuild/freebsd-arm64': 0.27.2
+ '@esbuild/freebsd-x64': 0.27.2
+ '@esbuild/linux-arm': 0.27.2
+ '@esbuild/linux-arm64': 0.27.2
+ '@esbuild/linux-ia32': 0.27.2
+ '@esbuild/linux-loong64': 0.27.2
+ '@esbuild/linux-mips64el': 0.27.2
+ '@esbuild/linux-ppc64': 0.27.2
+ '@esbuild/linux-riscv64': 0.27.2
+ '@esbuild/linux-s390x': 0.27.2
+ '@esbuild/linux-x64': 0.27.2
+ '@esbuild/netbsd-arm64': 0.27.2
+ '@esbuild/netbsd-x64': 0.27.2
+ '@esbuild/openbsd-arm64': 0.27.2
+ '@esbuild/openbsd-x64': 0.27.2
+ '@esbuild/openharmony-arm64': 0.27.2
+ '@esbuild/sunos-x64': 0.27.2
+ '@esbuild/win32-arm64': 0.27.2
+ '@esbuild/win32-ia32': 0.27.2
+ '@esbuild/win32-x64': 0.27.2
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+
+ eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)):
+ dependencies:
+ eslint: 9.39.2(jiti@2.6.1)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint@9.39.2(jiti@2.6.1):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.1
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.3
+ '@eslint/js': 9.39.2
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatted@3.3.3: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ globals@16.5.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ has-flag@4.0.0: {}
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ is-extglob@2.1.1: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ isexe@2.0.0: {}
+
+ jiti@2.6.1: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@2.2.3: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.546.0(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ natural-compare@1.4.0: {}
+
+ node-releases@2.0.27: {}
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ punycode@2.3.1: {}
+
+ react-dom@19.2.3(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ scheduler: 0.27.0
+
+ react-refresh@0.18.0: {}
+
+ react-router-dom@7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ react-router: 7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+
+ react-router@7.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.2.3
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.3(react@19.2.3)
+
+ react@19.2.3: {}
+
+ resolve-from@4.0.0: {}
+
+ rollup@4.53.5:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.53.5
+ '@rollup/rollup-android-arm64': 4.53.5
+ '@rollup/rollup-darwin-arm64': 4.53.5
+ '@rollup/rollup-darwin-x64': 4.53.5
+ '@rollup/rollup-freebsd-arm64': 4.53.5
+ '@rollup/rollup-freebsd-x64': 4.53.5
+ '@rollup/rollup-linux-arm-gnueabihf': 4.53.5
+ '@rollup/rollup-linux-arm-musleabihf': 4.53.5
+ '@rollup/rollup-linux-arm64-gnu': 4.53.5
+ '@rollup/rollup-linux-arm64-musl': 4.53.5
+ '@rollup/rollup-linux-loong64-gnu': 4.53.5
+ '@rollup/rollup-linux-ppc64-gnu': 4.53.5
+ '@rollup/rollup-linux-riscv64-gnu': 4.53.5
+ '@rollup/rollup-linux-riscv64-musl': 4.53.5
+ '@rollup/rollup-linux-s390x-gnu': 4.53.5
+ '@rollup/rollup-linux-x64-gnu': 4.53.5
+ '@rollup/rollup-linux-x64-musl': 4.53.5
+ '@rollup/rollup-openharmony-arm64': 4.53.5
+ '@rollup/rollup-win32-arm64-msvc': 4.53.5
+ '@rollup/rollup-win32-ia32-msvc': 4.53.5
+ '@rollup/rollup-win32-x64-gnu': 4.53.5
+ '@rollup/rollup-win32-x64-msvc': 4.53.5
+ fsevents: 2.3.3
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ set-cookie-parser@2.7.2: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ source-map-js@1.2.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2):
+ dependencies:
+ esbuild: 0.27.2
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.5
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ yallist@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
diff --git a/public/assets/arrow-right.svg b/public/assets/arrow-right.svg
new file mode 100644
index 0000000..cbd2d82
--- /dev/null
+++ b/public/assets/arrow-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/arrow-up-right.svg b/public/assets/arrow-up-right.svg
new file mode 100644
index 0000000..250eaaf
--- /dev/null
+++ b/public/assets/arrow-up-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/arrow.svg b/public/assets/arrow.svg
new file mode 100644
index 0000000..e02ecd0
--- /dev/null
+++ b/public/assets/arrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/blog-img.jpg b/public/assets/blog-img.jpg
new file mode 100644
index 0000000..d569ff8
Binary files /dev/null and b/public/assets/blog-img.jpg differ
diff --git a/public/assets/briefcase.svg b/public/assets/briefcase.svg
new file mode 100644
index 0000000..f294615
--- /dev/null
+++ b/public/assets/briefcase.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/chat.svg b/public/assets/chat.svg
new file mode 100644
index 0000000..ca6d17c
--- /dev/null
+++ b/public/assets/chat.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/copy.svg b/public/assets/copy.svg
new file mode 100644
index 0000000..425792c
--- /dev/null
+++ b/public/assets/copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/facebook.svg b/public/assets/facebook.svg
new file mode 100644
index 0000000..a49a91f
--- /dev/null
+++ b/public/assets/facebook.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/figma.svg b/public/assets/figma.svg
new file mode 100644
index 0000000..1460da0
--- /dev/null
+++ b/public/assets/figma.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/house.svg b/public/assets/house.svg
new file mode 100644
index 0000000..6e9d928
--- /dev/null
+++ b/public/assets/house.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/instagram.svg b/public/assets/instagram.svg
new file mode 100644
index 0000000..e4c180e
--- /dev/null
+++ b/public/assets/instagram.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/layers.svg b/public/assets/layers.svg
new file mode 100644
index 0000000..0aa0fa8
--- /dev/null
+++ b/public/assets/layers.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/linkedin.svg b/public/assets/linkedin.svg
new file mode 100644
index 0000000..19341ee
--- /dev/null
+++ b/public/assets/linkedin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/message.svg b/public/assets/message.svg
new file mode 100644
index 0000000..578596f
--- /dev/null
+++ b/public/assets/message.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/pencil.svg b/public/assets/pencil.svg
new file mode 100644
index 0000000..698e479
--- /dev/null
+++ b/public/assets/pencil.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/phone.svg b/public/assets/phone.svg
new file mode 100644
index 0000000..f6e216f
--- /dev/null
+++ b/public/assets/phone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/react.svg b/public/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/public/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/assets/user.jpg b/public/assets/user.jpg
new file mode 100644
index 0000000..2d46586
Binary files /dev/null and b/public/assets/user.jpg differ
diff --git a/public/assets/user.svg b/public/assets/user.svg
new file mode 100644
index 0000000..bef9f58
--- /dev/null
+++ b/public/assets/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..42e40cc
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..de86d0d
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Allow: /
+Sitemap: https://priscy-orcin.vercel.app/sitemap.xml
\ No newline at end of file
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..1c6b4c7
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,57 @@
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+import Home from "./pages/Home";
+import About from "./pages/About";
+import Services from "./pages/Services";
+import Portfolio from "./pages/Portfolio";
+import Contact from "./pages/Contact";
+import Blog from "./pages/Blog";
+import ProductDetail from "./pages/ProductDetail";
+import NavBar from "./components/NavBar";
+import Footer from "./components/Footer";
+import "./index.css";
+import BlogDetail from "./pages/BlogDetail";
+
+/** One shell to rule them all */
+function PageShell({ children, className = "" }) {
+ // Pick one width and it will apply everywhere
+ // (feel free to change max-w-* value)
+ return (
+
+ {children}
+
+ );
+}
+
+function App() {
+ return (
+
+ {/* Header */}
+
+
+
+
+ {/* Main content */}
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ {/* Footer */}
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/components/AboutCard.jsx b/src/components/AboutCard.jsx
new file mode 100644
index 0000000..f107f28
--- /dev/null
+++ b/src/components/AboutCard.jsx
@@ -0,0 +1,106 @@
+import React from "react";
+import aboutImage from "/assets/user.jpg";
+import Button from "./Button";
+import fbIcon from "/assets/facebook.svg";
+import instgramIcon from "/assets/instagram.svg";
+import linkedinIcon from "/assets/linkedin.svg";
+import figmaIcon from "/assets/figma.svg";
+
+export default function AboutCard() {
+ const socialMedia = [
+ { id: 1, name: "Facebook", icon: fbIcon, link: "https://facebook.com" },
+ {
+ id: 2,
+ name: "Instagram",
+ icon: instgramIcon,
+ link: "https://www.instagram.com/",
+ },
+ {
+ id: 3,
+ name: "LinkedIn",
+ icon: linkedinIcon,
+ link: "https://www.linkedin.com/",
+ },
+ { id: 4, name: "Figma", icon: figmaIcon, link: "https://figma.com" },
+ ];
+
+ return (
+
+ {/* Portrait / cover media */}
+
+
+ {/* Keep caption for screen readers only */}
+
+ Priscillia Beaumont — Full-Stack Developer and Product Designer
+
+
+
+
+ Priscillia Beaumont 👋
+
+
+
+ A passionate Full-Stack Developer {" "}
+ 🖥️ & Product Designer {" "}
+ with 12 years of experience across 24+ countries
+ worldwide.
+
+
+ {/* Primary actions */}
+
+
+
+
+
+ {/* Social links as a list */}
+
+
+ {socialMedia.map((item) => (
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/AboutDetails.jsx b/src/components/AboutDetails.jsx
new file mode 100644
index 0000000..e336b62
--- /dev/null
+++ b/src/components/AboutDetails.jsx
@@ -0,0 +1,53 @@
+import React from "react";
+import Brands from "./Brands";
+import Testimonials from "./Testimonials";
+import Certifications from "./Certifications";
+import WorkTogetherSlider from "./WorkTogetherSlider";
+
+export default function AboutDetails() {
+ const workProof = [
+ { id: 1, title: "Years of Experience", details: "40+" },
+ { id: 2, title: "Project Completed", details: "86+" },
+ { id: 3, title: "Happy Client", details: "72+" },
+ ];
+
+ return (
+
+
+
+
+ Hi, This Is Priscillia Beaumont
+
+
+ Available For Hire
+
+
+
👋
+
+
+ A Passionate Full Stack Developer 🖥️ &{" "}
+ Product Designer
+ having 12 years of Experiences over 24+ Country
+ Worldwide.
+
+
+
+ {workProof.map((work) => (
+
+
{work.details}
+
{work.title}
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Brands.jsx b/src/components/Brands.jsx
new file mode 100644
index 0000000..91680e1
--- /dev/null
+++ b/src/components/Brands.jsx
@@ -0,0 +1,42 @@
+import React from "react";
+import brandData from "../data/brands.json";
+
+export default function Brands() {
+ const count = Array.isArray(brandData) ? brandData.length : 0;
+
+ return (
+
+
+ Working With 50+ Brands ✨ Worldwide
+
+ {" "}
+ — currently showcasing {count} brand logos
+
+
+
+
+ {brandData.map((brand) => (
+
+
+
+ {/* Keep the visible name out if you want it super minimal; figcaption is optional/sr-only */}
+ {brand.name}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Button.jsx b/src/components/Button.jsx
new file mode 100644
index 0000000..f8703d8
--- /dev/null
+++ b/src/components/Button.jsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { Link } from "react-router-dom";
+import chatIcon from "/assets/arrow.svg";
+
+export default function Button({ title, bgColor, hoverBgColor, textColor = "text-white" }) {
+ return (
+
+
+
{title}
+
+
+
+ );
+}
diff --git a/src/components/Certifications.jsx b/src/components/Certifications.jsx
new file mode 100644
index 0000000..a0408f7
--- /dev/null
+++ b/src/components/Certifications.jsx
@@ -0,0 +1,94 @@
+import React from "react";
+import certificateData from "../data/certifications.json";
+import { Link } from "react-router-dom";
+import chatIcon from "/assets/arrow-up-right.svg";
+
+function isExternal(href = "") {
+ return /^https?:\/\//i.test(href);
+}
+
+export default function Certifications() {
+ return (
+
+
+ Professional Certifications
+
+
+
+ {certificateData.map((data) => {
+ const External = isExternal(data.link);
+ const Wrapper = External ? "a" : Link;
+ const wrapperProps = External
+ ? {
+ href: data.link,
+ target: "_blank",
+ rel: "noopener noreferrer",
+ }
+ : { to: data.link };
+
+ return (
+
+
+ {/* Left: logo + meta */}
+
+
+
+ {data.title}
+
+
+
+
+ {data.title}
+
+ {data.year && (
+
+ Year:
+ {data.year}
+
+ )}
+
+
+
+ {/* Right: status + link */}
+
+
+ Status:
+ {data.status}
+
+
+
+ View Certificate
+
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/ContactDetails.jsx b/src/components/ContactDetails.jsx
new file mode 100644
index 0000000..7c12947
--- /dev/null
+++ b/src/components/ContactDetails.jsx
@@ -0,0 +1,40 @@
+import React from "react";
+import ContactForm from "./ContactForm";
+import Faq from "./Faq";
+import WorkTogetherSlider from "./WorkTogetherSlider";
+
+export default function ContactDetails() {
+ return (
+
+
+
+
+ Let's 👋 Work Together
+
+
+ I'm here to help if you're searching for a product designer to bring
+ your idea to life or a design partner to help take your business to
+ the next level.
+
+
+
+
+
+
+ {/*
*/}
+
+ );
+}
diff --git a/src/components/ContactForm.jsx b/src/components/ContactForm.jsx
new file mode 100644
index 0000000..86026bc
--- /dev/null
+++ b/src/components/ContactForm.jsx
@@ -0,0 +1,111 @@
+import React from "react";
+import Button from "./Button";
+
+export default function ContactForm() {
+ const budgets = [
+ "Select budget...",
+ "$500",
+ "$1000-$2000",
+ "$2000-$4000",
+ "$5000+",
+ ];
+
+ return (
+
+ );
+}
diff --git a/src/components/ExpertArea.jsx b/src/components/ExpertArea.jsx
new file mode 100644
index 0000000..863a179
--- /dev/null
+++ b/src/components/ExpertArea.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import workData from "../data/work.json";
+
+export default function ExpertArea() {
+ return (
+
+
+ My Expert Area
+
+
+
+ {workData.map((work) => (
+
+
+
+ {work.company}
+
+ {work.company}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Faq.jsx b/src/components/Faq.jsx
new file mode 100644
index 0000000..0dbe700
--- /dev/null
+++ b/src/components/Faq.jsx
@@ -0,0 +1,114 @@
+import { useRef, useState } from "react";
+import faqData from "../data/faq.json";
+import { Minus, Plus } from "lucide-react";
+
+export default function Faq() {
+ const [openId, setOpenId] = useState(null); // which item is open (or null)
+ const btnRefs = useRef({}); // map of id -> button element
+
+ const toggle = (id) => setOpenId((curr) => (curr === id ? null : id));
+
+ const handleKeyDown = (e, idList, id) => {
+ const idx = idList.indexOf(id);
+ if (idx === -1) return;
+
+ let nextIndex = idx;
+ switch (e.key) {
+ case "ArrowDown":
+ e.preventDefault();
+ nextIndex = (idx + 1) % idList.length;
+ break;
+ case "ArrowUp":
+ e.preventDefault();
+ nextIndex = (idx - 1 + idList.length) % idList.length;
+ break;
+ case "Home":
+ e.preventDefault();
+ nextIndex = 0;
+ break;
+ case "End":
+ e.preventDefault();
+ nextIndex = idList.length - 1;
+ break;
+ default:
+ return;
+ }
+ const nextId = idList[nextIndex];
+ btnRefs.current[nextId]?.focus();
+ };
+
+ // Stable list of IDs (fallback to index if JSON item lacks id)
+ const ids = faqData.map((item, i) => item.id ?? i);
+
+ return (
+
+
+ Frequently Asked Questions
+
+
+
+ {faqData.map((item, i) => {
+ const id = item.id ?? i;
+ const isOpen = openId === id;
+ const panelId = `faq-panel-${id}`;
+ const buttonId = `faq-button-${id}`;
+
+ return (
+
+
+ {/* Heading contains the toggle button (ARIA accordion pattern) */}
+
+ (btnRefs.current[id] = el)}
+ onClick={() => toggle(id)}
+ onKeyDown={(e) => handleKeyDown(e, ids, id)}
+ className={`w-full flex items-center justify-between gap-3 text-left
+ font-medium lg:text-xl focus:outline-none focus-visible:ring
+ focus-visible:ring-blue-500 rounded-md px-2 py-1`}
+ aria-expanded={isOpen}
+ aria-controls={panelId}
+ >
+
+ {item.question}
+
+
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Panel (region) */}
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx
new file mode 100644
index 0000000..6060df8
--- /dev/null
+++ b/src/components/Footer.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+
+export default function Footer() {
+ const year = new Date().getFullYear();
+
+ return (
+
+ );
+}
diff --git a/src/components/GallerySlider.jsx b/src/components/GallerySlider.jsx
new file mode 100644
index 0000000..d04ff47
--- /dev/null
+++ b/src/components/GallerySlider.jsx
@@ -0,0 +1,207 @@
+import React, { useEffect, useRef, useState } from "react";
+
+/**
+ * GallerySlider (a11y tuned)
+ * @param {Array<{src:string, alt?:string, caption?:string}>} images
+ * @param {number} interval ms between slides (default 4000)
+ * @param {boolean} loop wrap around (default true)
+ * @param {string} ariaLabel accessible label for the carousel (default "Image gallery")
+ * @param {string} imgSizes attr for responsive layouts (optional)
+ * @param {number} heightPx fixed slide height in px (default 310)
+ */
+export default function GallerySlider({
+ images = [],
+ interval = 4000,
+ loop = true,
+ ariaLabel = "Image gallery",
+ imgSizes,
+ heightPx = 310,
+}) {
+ const [index, setIndex] = useState(0);
+ const [isPaused, setIsPaused] = useState(false);
+ const [reducedMotion, setReducedMotion] = useState(false);
+ const timerRef = useRef(null);
+ const touchStartX = useRef(null);
+ const touchDeltaX = useRef(0);
+ const slideCount = images.length;
+
+ const clampIndex = (i) => {
+ if (slideCount === 0) return 0;
+ if (loop) return (i + slideCount) % slideCount;
+ return Math.max(0, Math.min(slideCount - 1, i));
+ };
+
+ const goTo = (i) => setIndex(clampIndex(i));
+ const next = () => goTo(index + 1);
+ const prev = () => goTo(index - 1);
+
+ // Respect prefers-reduced-motion
+ useEffect(() => {
+ const mq = window.matchMedia?.("(prefers-reduced-motion: reduce)");
+ const update = () => setReducedMotion(!!mq?.matches);
+ update();
+ mq?.addEventListener?.("change", update);
+ return () => mq?.removeEventListener?.("change", update);
+ }, []);
+
+ // Auto-play (disabled if reduced motion or paused or only 1 slide)
+ useEffect(() => {
+ if (slideCount <= 1 || isPaused || reducedMotion) return;
+ timerRef.current = setInterval(next, interval);
+ return () => clearInterval(timerRef.current);
+ }, [index, isPaused, interval, slideCount, reducedMotion]);
+
+ // Pause when tab hidden
+ useEffect(() => {
+ const onVis = () => setIsPaused(document.hidden || isPaused);
+ document.addEventListener("visibilitychange", onVis);
+ return () => document.removeEventListener("visibilitychange", onVis);
+ }, [isPaused]);
+
+ // Keyboard: arrows + Home/End
+ const onKeyDown = (e) => {
+ if (e.key === "ArrowRight") {
+ e.preventDefault();
+ next();
+ } else if (e.key === "ArrowLeft") {
+ e.preventDefault();
+ prev();
+ } else if (e.key === "Home") {
+ e.preventDefault();
+ goTo(0);
+ } else if (e.key === "End") {
+ e.preventDefault();
+ goTo(slideCount - 1);
+ }
+ };
+
+ // Touch swipe
+ const onTouchStart = (e) => {
+ touchStartX.current = e.touches[0].clientX;
+ touchDeltaX.current = 0;
+ setIsPaused(true);
+ };
+ const onTouchMove = (e) => {
+ if (touchStartX.current == null) return;
+ touchDeltaX.current = e.touches[0].clientX - touchStartX.current;
+ };
+ const onTouchEnd = () => {
+ if (touchStartX.current == null) return;
+ const threshold = 40; // px
+ if (touchDeltaX.current <= -threshold) next();
+ else if (touchDeltaX.current >= threshold) prev();
+ touchStartX.current = null;
+ touchDeltaX.current = 0;
+ setIsPaused(false);
+ };
+
+ if (slideCount === 0) {
+ return (
+
+ );
+ }
+
+ const heightClass = `h-[${heightPx}px]`; // Tailwind won’t parse dynamic value; we’ll inline style below
+
+ const atStart = !loop && index === 0;
+ const atEnd = !loop && index === slideCount - 1;
+
+ return (
+ setIsPaused(true)}
+ onMouseLeave={() => setIsPaused(false)}
+ onFocus={() => setIsPaused(true)}
+ onBlur={() => setIsPaused(false)}
+ onKeyDown={onKeyDown}
+ tabIndex={0} // focusable for keyboard control
+ >
+ {/* Viewport */}
+
+ {/* Track */}
+
+ {images.map((img, i) => (
+
+
+ {img.caption && (
+
+ {img.caption}
+
+ )}
+
+ ))}
+
+
+
+ {/* Left / Right controls */}
+
+ ‹
+
+
+ ›
+
+
+ {/* Dots */}
+
+ {images.map((_, i) => {
+ const active = i === index;
+ return (
+ goTo(i)}
+ aria-label={`Go to slide ${i + 1}`}
+ aria-current={active ? "true" : undefined}
+ className={`h-2.5 rounded-full transition-all ${
+ active ? "w-6 bg-blue-500" : "w-2.5 bg-gray-300 dark:bg-neutral-700"
+ }`}
+ />
+ );
+ })}
+
+
+ {/* Live region for screen readers */}
+
+ Slide {index + 1} of {slideCount}
+ {images[index]?.caption ? ` — ${images[index].caption}` : ""}
+
+
+ );
+}
diff --git a/src/components/NavBar.jsx b/src/components/NavBar.jsx
new file mode 100644
index 0000000..14edd92
--- /dev/null
+++ b/src/components/NavBar.jsx
@@ -0,0 +1,257 @@
+import { useEffect, useRef, useState } from "react";
+import { Link } from "react-router-dom";
+import menuItems from "../data/menu.json";
+import Button from "./Button";
+import { Menu, MoonIcon, SunIcon, X } from "lucide-react";
+
+export default function NavBar() {
+ // THEME
+ const [theme, setTheme] = useState(() => {
+ if (typeof window === "undefined") return "light";
+ const stored = localStorage.getItem("theme");
+ if (stored === "light" || stored === "dark") return stored;
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
+ ? "dark"
+ : "light";
+ });
+
+ useEffect(() => {
+ if (typeof document === "undefined") return;
+ const root = document.documentElement;
+ if (theme === "dark") root.classList.add("dark");
+ else root.classList.remove("dark");
+ localStorage.setItem("theme", theme);
+ }, [theme]);
+
+ const toggleTheme = () => setTheme((t) => (t === "dark" ? "light" : "dark"));
+
+ // MOBILE MENU
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const toggleBtnRef = useRef(null);
+ const drawerRef = useRef(null);
+
+ const openMobile = () => setMobileOpen(true);
+ const closeMobile = () => setMobileOpen(false);
+ const toggleMobile = () => setMobileOpen((o) => !o);
+
+ // Prevent body scroll when drawer is open
+ useEffect(() => {
+ if (typeof document === "undefined") return;
+ document.body.style.overflow = mobileOpen ? "hidden" : "";
+ return () => {
+ document.body.style.overflow = "";
+ };
+ }, [mobileOpen]);
+
+ // Focus management & Esc to close for dialog
+ useEffect(() => {
+ if (!mobileOpen) {
+ // return focus to toggle button
+ toggleBtnRef.current?.focus();
+ return;
+ }
+ // move focus into the drawer
+ drawerRef.current?.focus();
+
+ const onKeyDown = (e) => {
+ if (e.key === "Escape") {
+ e.preventDefault();
+ closeMobile();
+ }
+ };
+ document.addEventListener("keydown", onKeyDown);
+ return () => document.removeEventListener("keydown", onKeyDown);
+ }, [mobileOpen]);
+
+ const menuData = menuItems.data;
+
+ return (
+ <>
+ {/* Skip link for keyboard users */}
+
+ Skip to content
+
+
+
+
+
+ {/* Brand */}
+
+
+ PriscyDesigns
+
+
+
+ {/* Desktop menu */}
+
+ {menuData.map((item) => (
+
+
+
+ {item.title}
+
+
+ ))}
+
+
+ {/* Desktop actions */}
+
+
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Mobile toggle */}
+
+ {mobileOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {/* Backdrop (presentational) */}
+
+
+ {/* Mobile Drawer */}
+
+ {/* Top: Brand */}
+
+
+ PriscyDesigns
+
+
+
+ {/* Middle: Menu items */}
+
+
+ {menuData.map((item) => (
+
+
+
+ {item.title}
+
+
+ ))}
+
+
+
+ {/* Bottom: Actions */}
+
+
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+
+ {theme === "dark"
+ ? "Switch to Light Mode"
+ : "Switch to Dark Mode"}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/ProjectCards.jsx b/src/components/ProjectCards.jsx
new file mode 100644
index 0000000..d91c661
--- /dev/null
+++ b/src/components/ProjectCards.jsx
@@ -0,0 +1,149 @@
+import React, { useEffect, useMemo } from "react";
+import { Link, useSearchParams } from "react-router-dom";
+import projectsData from "../data/projects.json";
+
+/**
+ * Props:
+ * - pagination (boolean) => show Prev/Next controls and read/write ?page= (default: true)
+ * - perPage (number) => items per page (default: 4)
+ * - prevText (string) => label for Prev (default: "Prev")
+ * - nextText (string) => label for Next (default: "Next")
+ */
+export default function ProjectCards({
+ pagination = true,
+ perPage = 4,
+ prevText = "Prev",
+ nextText = "Next",
+}) {
+ const [params, setParams] = useSearchParams();
+
+ // Always call hooks; ignore when pagination=false
+ const pageFromUrl = Number(params.get("page")) || 1;
+
+ const totalPages = Math.max(1, Math.ceil(projectsData.length / (perPage || 1)));
+ const page = pagination ? Math.min(Math.max(1, pageFromUrl), totalPages) : 1;
+
+ // Keep URL valid if someone types ?page=abc or too big
+ useEffect(() => {
+ if (!pagination) return;
+ if (page !== pageFromUrl) {
+ setParams({ page: String(page) }, { replace: true });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pagination, page, pageFromUrl]);
+
+ // Slice for this page (or first chunk if pagination=false)
+ const projects = useMemo(() => {
+ const start = (page - 1) * perPage;
+ return projectsData.slice(start, start + perPage);
+ }, [page, perPage]);
+
+ // Scroll to top when changing page (only when paginating)
+ useEffect(() => {
+ if (!pagination) return;
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ }, [pagination, page]);
+
+ const goto = (p) => setParams({ page: String(p) });
+
+ return (
+
+
+ Projects
+
+
+ {/* Grid list */}
+
+ {projects.map((p, i) => {
+ const slug = p.slug ?? p.title.toLowerCase().replace(/\s+/g, "-");
+ const key = p.id ?? slug ?? `${p.title}-${i}`;
+ return (
+
+
+
+
+
+ {/* figcaption is optional; keep SR-only to avoid visual noise */}
+ {p.title}
+
+
+
+
+
+
+ {p.title}
+
+
+
+
+ See Project
+
+
+
+ {p.category && (
+
+ Category:
+ {p.category}
+
+ )}
+
+
+ );
+ })}
+
+
+ {/* Prev / Next (optional) */}
+ {pagination && (
+
+ goto(page - 1)}
+ disabled={page === 1}
+ className="px-3 py-2 rounded-lg border border-gray-300 dark:border-neutral-800 disabled:opacity-50"
+ aria-label="Go to previous page"
+ >
+ {prevText}
+
+
+ {/* Announce page changes politely for AT */}
+
+ Page {page} of {totalPages}
+
+
+ goto(page + 1)}
+ disabled={page === totalPages}
+ className="px-3 py-2 rounded-lg border border-gray-300 dark:border-neutral-800 disabled:opacity-50"
+ aria-label="Go to next page"
+ >
+ {nextText}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/ProjectDetails.jsx b/src/components/ProjectDetails.jsx
new file mode 100644
index 0000000..bfb9b5e
--- /dev/null
+++ b/src/components/ProjectDetails.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import WorkTogetherSlider from "./WorkTogetherSlider";
+import ProjectCards from "./ProjectCards";
+
+export default function ProjectDetails() {
+ return (
+
+
+
+
+ Check Out My Latest Projects
+
+
+ I'm here to help if you're searching for a product designer to bring
+ your idea to life or a design partner to help take your business to
+ the next level.
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ServiceDetails.jsx b/src/components/ServiceDetails.jsx
new file mode 100644
index 0000000..bf822c8
--- /dev/null
+++ b/src/components/ServiceDetails.jsx
@@ -0,0 +1,46 @@
+import React from "react";
+import ServiceOffer from "./ServiceOffer";
+import bannerImg from "/assets/blog-img.jpg";
+import Brands from "./Brands";
+import Testimonials from "./Testimonials";
+import Certifications from "./Certifications";
+import Faq from "./Faq";
+import WorkTogetherSlider from "./WorkTogetherSlider";
+
+export default function ServiceDetails() {
+ return (
+
+
+
+
+ Services I Offer
+
+
+ Available For Hire
+
+
+
+
+
+ Transforming Ideas into Innovative Reality, Elevate Your Vision with Our
+ Expert Product Design and Development Services!
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ServiceOffer.jsx b/src/components/ServiceOffer.jsx
new file mode 100644
index 0000000..c67c5a9
--- /dev/null
+++ b/src/components/ServiceOffer.jsx
@@ -0,0 +1,70 @@
+import React from "react";
+import { Link } from "react-router-dom";
+import rightArrow from "/assets/arrow-right.svg";
+import workData from "../data/work.json";
+
+export default function ServiceOffer() {
+ const workLimit = workData.slice(0, 4);
+
+ return (
+
+
+
+ Services I Offer
+
+
+
+
+ See All Services
+
+
+
+
+
+
+
+ {workLimit.map((work) => (
+
+
+
+
+ {work.company}
+
+
+
+ {work.company}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Testimonials.jsx b/src/components/Testimonials.jsx
new file mode 100644
index 0000000..0bf46e0
--- /dev/null
+++ b/src/components/Testimonials.jsx
@@ -0,0 +1,282 @@
+import React, { useEffect, useRef, useState, useMemo } from "react";
+import data from "../data/testimonials.json";
+
+/**
+ * Testimonials slider (accessible)
+ * - Auto-advances every few seconds (pauses on hover/focus and reduced motion)
+ * - Keyboard: Left/Right, Home/End
+ * - Touch swipe on mobile
+ * - Next/Prev buttons are pinned top-right on lg+
+ */
+export default function Testimonials({
+ interval = 5000,
+ loop = true,
+ ariaLabel = "Client testimonials",
+}) {
+ const testimonials = useMemo(() => data ?? [], []);
+ const count = testimonials.length;
+
+ const [index, setIndex] = useState(0);
+ const [paused, setPaused] = useState(false);
+ const [reducedMotion, setReducedMotion] = useState(false);
+
+ const timerRef = useRef(null);
+ const trackRef = useRef(null);
+ const touchStartX = useRef(null);
+ const touchDeltaX = useRef(0);
+
+ const clamp = (i) => {
+ if (count === 0) return 0;
+ return loop ? (i + count) % count : Math.max(0, Math.min(count - 1, i));
+ };
+ const goTo = (i) => setIndex(clamp(i));
+ const next = () => goTo(index + 1);
+ const prev = () => goTo(index - 1);
+
+ // Reduced motion
+ useEffect(() => {
+ const mq = window.matchMedia?.("(prefers-reduced-motion: reduce)");
+ const update = () => setReducedMotion(!!mq?.matches);
+ update();
+ mq?.addEventListener?.("change", update);
+ return () => mq?.removeEventListener?.("change", update);
+ }, []);
+
+ // Autoplay
+ useEffect(() => {
+ if (count <= 1 || paused || reducedMotion) return;
+ timerRef.current = setInterval(next, interval);
+ return () => clearInterval(timerRef.current);
+ }, [index, paused, interval, count, reducedMotion]);
+
+ // Pause when tab hidden
+ useEffect(() => {
+ const onVis = () => setPaused((p) => document.hidden || p);
+ document.addEventListener("visibilitychange", onVis);
+ return () => document.removeEventListener("visibilitychange", onVis);
+ }, []);
+
+ // Keyboard
+ const onKeyDown = (e) => {
+ if (e.key === "ArrowRight") {
+ e.preventDefault();
+ next();
+ } else if (e.key === "ArrowLeft") {
+ e.preventDefault();
+ prev();
+ } else if (e.key === "Home") {
+ e.preventDefault();
+ goTo(0);
+ } else if (e.key === "End") {
+ e.preventDefault();
+ goTo(count - 1);
+ }
+ };
+
+ // Touch
+ const onTouchStart = (e) => {
+ touchStartX.current = e.touches[0].clientX;
+ touchDeltaX.current = 0;
+ setPaused(true);
+ };
+ const onTouchMove = (e) => {
+ if (touchStartX.current == null) return;
+ touchDeltaX.current = e.touches[0].clientX - touchStartX.current;
+ };
+ const onTouchEnd = () => {
+ if (touchStartX.current == null) return;
+ const threshold = 40;
+ if (touchDeltaX.current <= -threshold) next();
+ else if (touchDeltaX.current >= threshold) prev();
+ touchStartX.current = null;
+ touchDeltaX.current = 0;
+ setPaused(false);
+ };
+
+ if (count === 0) {
+ return (
+
+ Trusted By Clients
+ No testimonials yet.
+
+ );
+ }
+
+ const atStart = !loop && index === 0;
+ const atEnd = !loop && index === count - 1;
+
+ return (
+
+
+
+
+ Trusted By 1200+ Clients
+
+
+ Real words from the teams we've partnered with.
+
+
+
+ {/* Top-right controls on large screens */}
+
+
+ ← Prev
+
+
+ Next →
+
+
+
+
+ {/* Carousel */}
+ setPaused(true)}
+ onMouseLeave={() => setPaused(false)}
+ onFocus={() => setPaused(true)}
+ onBlur={() => setPaused(false)}
+ onKeyDown={onKeyDown}
+ tabIndex={0}
+ >
+ {/* Track */}
+
+ {testimonials.map((t, i) => (
+
+
+ {/* Avatar */}
+
+
+ {t.name}
+
+
+ {/* Quote card */}
+
+ {/* Stars */}
+
+ {[...Array(5)].map((_, s) => (
+
+
+
+ ))}
+
+
+
+ “{t.quote}”
+
+
+
+ {t.name}
+
+ {t.role}
+
+
+
+
+
+ ))}
+
+
+ {/* Mobile controls (bottom overlay) */}
+
+
+ ← Prev
+
+
+ Next →
+
+
+
+ {/* Dots */}
+
+ {testimonials.map((_, i) => {
+ const active = i === index;
+ return (
+ goTo(i)}
+ aria-label={`Go to testimonial ${i + 1}`}
+ aria-current={active ? "true" : undefined}
+ className={`h-2.5 rounded-full transition-all ${
+ active
+ ? "w-6 bg-blue-500"
+ : "w-2.5 bg-gray-300 dark:bg-neutral-700"
+ }`}
+ />
+ );
+ })}
+
+
+ {/* Live region */}
+
+ Testimonial {index + 1} of {count}. {testimonials[index]?.name}.
+
+
+
+ );
+}
diff --git a/src/components/WorkExperience.jsx b/src/components/WorkExperience.jsx
new file mode 100644
index 0000000..1f2532a
--- /dev/null
+++ b/src/components/WorkExperience.jsx
@@ -0,0 +1,83 @@
+import React from "react";
+import workData from "../data/work.json";
+
+export default function WorkExperience() {
+ return (
+
+ {" "}
+ {/* 50% of parent */}
+
+
Work Experience
+
+ {/* Auto-scrolling viewport */}
+
+ {" "}
+ {/* adjust h-80 as you like */}
+
+ {/* CONTENT BLOCK 1 */}
+
+ {workData.map((work) => (
+
+
+
+
+
+
+
{work.company}
+
{work.position}
+
+
+ ))}
+
+
+ {/* CONTENT BLOCK 2 (duplicate for seamless loop) */}
+
+ {workData.map((work) => (
+
+
+
+
+
+
+
{work.company}
+
{work.position}
+
+
+ ))}
+
+
+
+
+ {/* Local styles for the marquee effect */}
+
+
+ );
+}
diff --git a/src/components/WorkTogetherCard.jsx b/src/components/WorkTogetherCard.jsx
new file mode 100644
index 0000000..388d078
--- /dev/null
+++ b/src/components/WorkTogetherCard.jsx
@@ -0,0 +1,82 @@
+import React from "react";
+import rightArrow from "/assets/arrow-right.svg";
+import { Link } from "react-router-dom";
+
+export default function WorkTogetherCard() {
+ const ticker =
+ "Available For Hire — Crafting Digital Experiences — Available For Hire — Crafting Digital Experiences";
+
+ return (
+
+ {/* Marquee (hidden from AT; provide SR-only static alt below) */}
+
+
+
{ticker}
+ {/* duplicate for seamless loop */}
+
+ {ticker}
+
+
+
+ {/* SR-only static equivalent of the marquee text */}
+
+ Available for hire. Crafting digital experiences.
+
+
+
+ Let's
+ 👋
+
+ Work Together
+
+
+
+
+
Let's Talk
+
+
+
+
+
+ {/* Local styles for the marquee + reduced-motion support */}
+
+
+ );
+}
diff --git a/src/components/WorkTogetherSlider.jsx b/src/components/WorkTogetherSlider.jsx
new file mode 100644
index 0000000..1b0a914
--- /dev/null
+++ b/src/components/WorkTogetherSlider.jsx
@@ -0,0 +1,59 @@
+import React from "react";
+import { Link } from "react-router-dom";
+
+export default function WorkTogetherSlider() {
+ const ticker = "Let's 👋 Work Together · Let's 👋 Work Together ·";
+
+ return (
+
+
+ Work Together Call-to-Action
+
+
+ {/* Full-width clickable viewport */}
+
+
+ {/* Absolutely positioned track so it can't expand layout width */}
+
+
{ticker}
+ {/* duplicate for seamless loop */}
+
+ {ticker}
+
+
+
+
+ {/* Static equivalent for screen readers */}
+ Let’s work together — contact me
+
+
+ {/* Local styles for the marquee */}
+
+
+ );
+}
diff --git a/src/data/blog.json b/src/data/blog.json
new file mode 100644
index 0000000..58a7bff
--- /dev/null
+++ b/src/data/blog.json
@@ -0,0 +1,137 @@
+[
+ {
+ "id": 1,
+ "slug": "designing-for-speed",
+ "category": "Performance",
+ "title": "Designing for Speed: Faster UI, Happier Users",
+ "excerpt": "Practical ways to make interfaces feel instant—perceived performance, skeletons, and optimistic updates.",
+ "body": "We explore perceived performance techniques, including skeleton screens, optimistic actions, and prefetching routes. We also cover image strategy, font loading, and micro-interactions that mask latency.",
+ "coverImage": "https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=1600&auto=format&fit=crop",
+ "images": [
+ "https://images.unsplash.com/photo-1519389950473-47ba0277781c?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "author": "Priscy",
+ "date": "2025-03-06",
+ "url": "https://example.com/blog/designing-for-speed"
+ },
+ {
+ "id": 2,
+ "slug": "color-systems-in-2025",
+ "category": "Design",
+ "title": "Color Systems in 2025: Tokens, Modes, and Contrast",
+ "excerpt": "Dark mode, high contrast themes, and how to structure tokens so brand changes are painless.",
+ "body": "We map semantic tokens to modes, discuss WCAG contrast, and show how to keep brand palettes flexible without breaking components.",
+ "coverImage": "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=1600&auto=format&fit=crop",
+ "images": [
+ "https://images.unsplash.com/photo-1512496015851-a90fb38ba796?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "author": "Priscy",
+ "date": "2025-02-14",
+ "url": "https://example.com/blog/color-systems-in-2025"
+ },
+ {
+ "id": 3,
+ "slug": "portfolio-case-study-template",
+ "category": "Process",
+ "title": "A Reusable Portfolio Case Study Template",
+ "excerpt": "Tell a tight story: problem → approach → impact. A simple outline you can reuse across projects.",
+ "body": "This template keeps you focused on outcomes. We include example sections, asset checklists, and metrics to highlight.",
+ "coverImage": "https://images.unsplash.com/photo-1493666438817-866a91353ca9?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2025-01-22",
+ "url": "https://example.com/blog/portfolio-case-study-template"
+ },
+ {
+ "id": 4,
+ "slug": "microinteractions-that-delight",
+ "category": "UI/UX",
+ "title": "Micro-interactions That Delight (Without Being Noisy)",
+ "excerpt": "Small, purposeful animations that guide attention and communicate state.",
+ "body": "We cover duration, easing, and how to use motion as feedback rather than decoration. Includes examples and do/don'ts.",
+ "coverImage": "https://images.unsplash.com/photo-1550565118-3a14e8d038d9?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-12-11",
+ "url": "https://example.com/blog/microinteractions-that-delight"
+ },
+ {
+ "id": 5,
+ "slug": "building-accessible-modals",
+ "category": "Accessibility",
+ "title": "Building Accessible Modals in React",
+ "excerpt": "Focus traps, aria labels, scroll locking, and escape hatches.",
+ "body": "A checklist and code snippets for modals that work for everyone—including keyboard and screen reader users.",
+ "coverImage": "https://images.unsplash.com/photo-1518779578993-ec3579fee39f?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-11-05",
+ "url": "https://example.com/blog/building-accessible-modals"
+ },
+ {
+ "id": 6,
+ "slug": "image-loading-strategies",
+ "category": "Performance",
+ "title": "Smart Image Loading for Portfolio Sites",
+ "excerpt": "When to use `object-fit`, responsive sources, and lazy loading to keep pages snappy.",
+ "body": "We compare techniques for art direction and bandwidth savings: `srcset`, `sizes`, `loading=lazy`, and preloading hero assets.",
+ "coverImage": "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-10-10",
+ "url": "https://example.com/blog/image-loading-strategies"
+ },
+ {
+ "id": 7,
+ "slug": "design-handoff-checklist",
+ "category": "Process",
+ "title": "Design → Dev Handoff Checklist",
+ "excerpt": "Everything devs need: tokens, states, empty/error screens, and motion specs.",
+ "body": "Use this checklist before handoff to reduce rework and mismatches in production.",
+ "coverImage": "https://images.unsplash.com/photo-1519389950473-47ba0277781c?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-08-28",
+ "url": "https://example.com/blog/design-handoff-checklist"
+ },
+ {
+ "id": 8,
+ "slug": "grid-layouts-that-scale",
+ "category": "CSS",
+ "title": "Grid Layouts That Scale Across Breakpoints",
+ "excerpt": "Fluid columns, minmax, clamp, and intrinsic sizing patterns.",
+ "body": "We show common responsive patterns that keep content readable at any width.",
+ "coverImage": "https://images.unsplash.com/photo-1491884662610-dfcd28f30cf5?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-07-17",
+ "url": "https://example.com/blog/grid-layouts-that-scale"
+ },
+ {
+ "id": 9,
+ "slug": "navbars-that-dont-annoy",
+ "category": "UI/UX",
+ "title": "Sticky Navbars That Don’t Annoy Users",
+ "excerpt": "When to use sticky vs fixed, and how to keep them out of the way.",
+ "body": "Tradeoffs, safe areas, and scroll-aware patterns that balance access with focus.",
+ "coverImage": "https://images.unsplash.com/photo-1520975916090-3105956dac38?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-06-02",
+ "url": "https://example.com/blog/navbars-that-dont-annoy"
+ },
+ {
+ "id": 10,
+ "slug": "writing-case-studies-people-read",
+ "category": "Content",
+ "title": "Writing Case Studies People Actually Read",
+ "excerpt": "Short intros, visuals first, and clear outcomes—skip the fluff.",
+ "body": "How to structure, what to cut, and quick heuristics to keep readers engaged.",
+ "coverImage": "https://images.unsplash.com/photo-1493666438207-1c01b1c1b6b6?q=80&w=1600&auto=format&fit=crop",
+ "images": [],
+ "author": "Priscy",
+ "date": "2024-05-01",
+ "url": "https://example.com/blog/writing-case-studies-people-read"
+ }
+]
diff --git a/src/data/brands.json b/src/data/brands.json
new file mode 100644
index 0000000..8e9cfb4
--- /dev/null
+++ b/src/data/brands.json
@@ -0,0 +1,82 @@
+[
+ {
+ "name": "Acme Studios",
+ "image": "https://placehold.co/320x160?text=Acme+Studios+Logo"
+ },
+ {
+ "name": "Northstar Labs",
+ "image": "https://placehold.co/320x160?text=Northstar+Labs+Logo"
+ },
+ {
+ "name": "Aurora Apparel",
+ "image": "https://placehold.co/320x160?text=Aurora+Apparel+Logo"
+ },
+ {
+ "name": "Pixel Forge",
+ "image": "https://placehold.co/320x160?text=Pixel+Forge+Logo"
+ },
+ {
+ "name": "Summit Health",
+ "image": "https://placehold.co/320x160?text=Summit+Health+Logo"
+ },
+ {
+ "name": "Blue Harbor",
+ "image": "https://placehold.co/320x160?text=Blue+Harbor+Logo"
+ },
+ {
+ "name": "Zenith Finance",
+ "image": "https://placehold.co/320x160?text=Zenith+Finance+Logo"
+ },
+ {
+ "name": "Ember Coffee",
+ "image": "https://placehold.co/320x160?text=Ember+Coffee+Logo"
+ },
+ {
+ "name": "Lighthouse Travel",
+ "image": "https://placehold.co/320x160?text=Lighthouse+Travel+Logo"
+ },
+ {
+ "name": "Nimbus Cloud",
+ "image": "https://placehold.co/320x160?text=Nimbus+Cloud+Logo"
+ },
+ {
+ "name": "Terra Foods",
+ "image": "https://placehold.co/320x160?text=Terra+Foods+Logo"
+ },
+ {
+ "name": "Silverline Media",
+ "image": "https://placehold.co/320x160?text=Silverline+Media+Logo"
+ },
+ {
+ "name": "Echo Fitness",
+ "image": "https://placehold.co/320x160?text=Echo+Fitness+Logo"
+ },
+ {
+ "name": "Horizon Motors",
+ "image": "https://placehold.co/320x160?text=Horizon+Motors+Logo"
+ },
+ {
+ "name": "Atlas Outdoor",
+ "image": "https://placehold.co/320x160?text=Atlas+Outdoor+Logo"
+ },
+ {
+ "name": "Velvet Cosmetics",
+ "image": "https://placehold.co/320x160?text=Velvet+Cosmetics+Logo"
+ },
+ {
+ "name": "Quantum Learning",
+ "image": "https://placehold.co/320x160?text=Quantum+Learning+Logo"
+ },
+ {
+ "name": "Drift Bikes",
+ "image": "https://placehold.co/320x160?text=Drift+Bikes+Logo"
+ },
+ {
+ "name": "Sunbeam Energy",
+ "image": "https://placehold.co/320x160?text=Sunbeam+Energy+Logo"
+ },
+ {
+ "name": "Meadow Petcare",
+ "image": "https://placehold.co/320x160?text=Meadow+Petcare+Logo"
+ }
+]
diff --git a/src/data/certifications.json b/src/data/certifications.json
new file mode 100644
index 0000000..7db42f9
--- /dev/null
+++ b/src/data/certifications.json
@@ -0,0 +1,34 @@
+[
+ {
+ "id": 1,
+ "title": "Cousera UI/UX Design",
+ "year": "2022 - 2023",
+ "status": "Completed",
+ "link": "#",
+ "image": "/assets/facebook.svg"
+ },
+ {
+ "id": 2,
+ "title": "Adobe UI/UX Master Class",
+ "year": "2022 - 2023",
+ "status": "Completed",
+ "link": "#",
+ "image": "/assets/instagram.svg"
+ },
+ {
+ "id": 3,
+ "title": "Figma UI/UX Design",
+ "year": "2022 - 2023",
+ "status": "Completed",
+ "link": "#",
+ "image": "/assets/facebook.svg"
+ },
+ {
+ "id": 4,
+ "title": "DevOps UI/UX Master Class",
+ "year": "2022 - 2023",
+ "status": "Completed",
+ "link": "#",
+ "image": "/assets/instagram.svg"
+ }
+]
diff --git a/src/data/faq.json b/src/data/faq.json
new file mode 100644
index 0000000..b971f46
--- /dev/null
+++ b/src/data/faq.json
@@ -0,0 +1,27 @@
+[
+ {
+ "id": 1,
+ "question": "What does a product designer need to know?",
+ "answer": "I'm here to help if you're searching for a product designer to bring your idea to life or a design partner to help take your business to the next level."
+ },
+ {
+ "id": 2,
+ "question": "What does a product designer need to know?",
+ "answer": "I'm here to help if you're searching for a product designer to bring your idea to life or a design partner to help take your business to the next level."
+ },
+ {
+ "id": 3,
+ "question": "What does a product designer need to know?",
+ "answer": "I'm here to help if you're searching for a product designer to bring your idea to life or a design partner to help take your business to the next level."
+ },
+ {
+ "id": 4,
+ "question": "What does a product designer need to know?",
+ "answer": "I'm here to help if you're searching for a product designer to bring your idea to life or a design partner to help take your business to the next level."
+ },
+ {
+ "id": 5,
+ "question": "What does a product designer need to know?",
+ "answer": "I'm here to help if you're searching for a product designer to bring your idea to life or a design partner to help take your business to the next level."
+ }
+]
diff --git a/src/data/menu.json b/src/data/menu.json
new file mode 100644
index 0000000..5e06b37
--- /dev/null
+++ b/src/data/menu.json
@@ -0,0 +1,40 @@
+{
+ "data": [
+ {
+ "id": 1,
+ "title": "Home",
+ "link": "/",
+ "image": "/assets/house.svg"
+ },
+ {
+ "id": 2,
+ "title": "About",
+ "link": "/about",
+ "image": "/assets/user.svg"
+ },
+ {
+ "id": 3,
+ "title": "Services",
+ "link": "/services",
+ "image": "/assets/layers.svg"
+ },
+ {
+ "id": 4,
+ "title": "Portfolio",
+ "link": "/portfolio",
+ "image": "/assets/briefcase.svg"
+ },
+ {
+ "id": 5,
+ "title": "Blog",
+ "link": "/blog",
+ "image": "/assets/pencil.svg"
+ },
+ {
+ "id": 6,
+ "title": "Contact",
+ "link": "/contact",
+ "image": "/assets/message.svg"
+ }
+ ]
+}
diff --git a/src/data/projects.json b/src/data/projects.json
new file mode 100644
index 0000000..c3c32e2
--- /dev/null
+++ b/src/data/projects.json
@@ -0,0 +1,80 @@
+[
+ {
+ "id": 1,
+ "category": "Web Design",
+ "title": "Minimal Furniture Shop",
+ "body": "A clean e-commerce concept focusing on crisp typography and generous whitespace. Built with React and a headless cart pattern to keep interactions snappy.",
+ "images": [
+ "https://images.unsplash.com/photo-1493666438817-866a91353ca9?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1524758631624-e2822e304c36?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1493666438207-1c01b1c1b6b6?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1493666438817-866a91353ca9?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/minimal-furniture"
+ },
+ {
+ "id": 2,
+ "category": "Branding",
+ "title": "Cold Brew Identity",
+ "body": "Logo, color system, and can label layout for a small-batch cold brew startup. The palette leans into deep blues and copper accents.",
+ "images": [
+ "https://images.unsplash.com/photo-1511920170033-f8396924c348?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1453614512568-c4024d13c247?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1519389950473-47ba0277781c?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/cold-brew-identity"
+ },
+ {
+ "id": 3,
+ "category": "UI/UX",
+ "title": "Wellness Mobile App",
+ "body": "Meditation and habit-tracking flows with gentle micro-interactions. Focused on clarity and accessibility with large tap targets and calm motion.",
+ "images": [
+ "https://images.unsplash.com/photo-1550565118-3a14e8d038d9?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1512496015851-a90fb38ba796?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1493666438817-866a91353ca9?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/wellness-app"
+ },
+ {
+ "id": 4,
+ "category": "Development",
+ "title": "SaaS Dashboard",
+ "body": "Admin dashboard with role-based access, dark mode, and lightweight charts. Emphasis on performance and a cohesive component system.",
+ "images": [
+ "https://images.unsplash.com/photo-1551281044-8d8e8aa0f0a8?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1519389950473-47ba0277781c?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1519389950473-47ba0277781c?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/saas-dashboard"
+ },
+ {
+ "id": 5,
+ "category": "Illustration",
+ "title": "City Nights Poster Series",
+ "body": "A trio of vector posters exploring neon-lit cityscapes. Strong geometry, halftone textures, and a vibrant, high-contrast palette.",
+ "images": [
+ "https://images.unsplash.com/photo-1508057198894-247b23fe5ade?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1491884662610-dfcd28f30cf5?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1469474968028-56623f02e42e?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1508057198894-247b23fe5ade?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/city-nights"
+ },
+ {
+ "id": 6,
+ "category": "Photography",
+ "title": "Analog Portraits",
+ "body": "A study in natural light and grain shot on 35mm film. Subtle color grading to preserve the character of the stock.",
+ "images": [
+ "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1520975916090-3105956dac38?q=80&w=1600&auto=format&fit=crop",
+ "https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=1600&auto=format&fit=crop"
+ ],
+ "coverImage": "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?q=80&w=1200&auto=format&fit=crop",
+ "url": "https://example.com/projects/analog-portraits"
+ }
+]
diff --git a/src/data/testimonials.json b/src/data/testimonials.json
new file mode 100644
index 0000000..9af7419
--- /dev/null
+++ b/src/data/testimonials.json
@@ -0,0 +1,50 @@
+[
+ {
+ "id": 1,
+ "name": "Amara O.",
+ "role": "Head of Product, Nebula Pay",
+ "rating": 5,
+ "quote": "Priscy delivered beyond expectations. The UX flow cut our onboarding time by 37% and customers noticed immediately.",
+ "avatar": "https://i.pravatar.cc/120?img=1"
+ },
+ {
+ "id": 2,
+ "name": "David K.",
+ "role": "Founder, CraftCart",
+ "rating": 5,
+ "quote": "Clean code, crisp design, and excellent communication. Launch went smoothly and the site is blazing fast.",
+ "avatar": "https://i.pravatar.cc/120?img=2"
+ },
+ {
+ "id": 3,
+ "name": "Lina P.",
+ "role": "Marketing Lead, Oceanic",
+ "rating": 4,
+ "quote": "Our rebrand looks modern and consistent across channels. The design system has been a game changer for our team.",
+ "avatar": "https://i.pravatar.cc/120?img=3"
+ },
+ {
+ "id": 4,
+ "name": "Zach M.",
+ "role": "CTO, ByteForge",
+ "rating": 5,
+ "quote": "Delivered a robust dashboard with thoughtful accessibility. Dark mode is immaculate.",
+ "avatar": "https://i.pravatar.cc/120?img=4"
+ },
+ {
+ "id": 5,
+ "name": "Chidera N.",
+ "role": "Ops Manager, FreshHub",
+ "rating": 5,
+ "quote": "From kickoff to handoff, everything was clear and on schedule. Mobile conversion improved noticeably.",
+ "avatar": "https://i.pravatar.cc/120?img=5"
+ },
+ {
+ "id": 6,
+ "name": "Marco S.",
+ "role": "CEO, AtlasLabs",
+ "rating": 4,
+ "quote": "Reliable partner. The team understood our constraints and shipped a polished MVP on time.",
+ "avatar": "https://i.pravatar.cc/120?img=6"
+ }
+]
diff --git a/src/data/work.json b/src/data/work.json
new file mode 100644
index 0000000..4d77c4a
--- /dev/null
+++ b/src/data/work.json
@@ -0,0 +1,37 @@
+[
+ {
+ "id": 1,
+ "year": "2020 - Present",
+ "position": "Frontend Developer",
+ "company": "Jive",
+ "image": "/assets/facebook.svg"
+ },
+ {
+ "id": 2,
+ "year": "2018 - 2020",
+ "position": "UI/UX Designer",
+ "company": "Figma",
+ "image": "/assets/figma.svg"
+ },
+ {
+ "id": 3,
+ "year": "2016 - 2018",
+ "position": "Intern Developer",
+ "company": "Web Startup",
+ "image": "/assets/instagram.svg"
+ },
+ {
+ "id": 4,
+ "year": "2015 - 2016",
+ "position": "Junior Developer",
+ "company": "Code Factory",
+ "image": "/assets/linkedin.svg"
+ },
+ {
+ "id": 5,
+ "year": "2014 - 2015",
+ "position": "Trainee",
+ "company": "DevWorks",
+ "image": "/assets/facebook.svg"
+ }
+]
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..16fe5e1
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,88 @@
+@import "tailwindcss";
+
+:root {
+ --font-sans: "Bricolage Grotesque", -apple-system, BlinkMacSystemFont,
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans",
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
+ --grad-1: #7dd3fc; /* sky-300 */
+ --grad-2: #c4b5fd; /* violet-300 */
+ --grad-3: #fda4af; /* rose-300 */
+ --grad-4: #86efac; /* green-300 */
+ --bg: #ffffff;
+ --text: #0a0a0a;
+ --card: #ffffff;
+ --brandbg: #040c31;
+ --border: #e5e7eb;
+ --hover: #e5e7eb;
+}
+
+html.dark {
+ --bg: #0a0a0a;
+ --text: #f5f5f5;
+ --card: #0f0f10;
+ --border: #27272a;
+ --hover: #1f1f22;
+}
+
+html,
+body,
+#root {
+ height: 100%;
+}
+
+body {
+ font-family: var(--font-sans);
+ margin: 0;
+ background: var(--bg);
+ color: var(--text);
+ background: linear-gradient(
+ 120deg,
+ var(--grad-1),
+ var(--grad-2),
+ var(--grad-3),
+ var(--grad-4)
+ );
+ background-size: 400% 400%;
+ animation: bg-gradient 22s ease infinite;
+ /* Optional: keeps bg fixed while content scrolls */
+ background-attachment: fixed;
+}
+
+/* Respect reduced motion */
+@media (prefers-reduced-motion: reduce) {
+ body {
+ animation: none;
+ }
+}
+
+@keyframes bg-gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+/* Optional readability veil; tweak or remove */
+body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ backdrop-filter: none;
+ background: rgba(255, 255, 255, 0.08); /* subtle wash */
+ pointer-events: none;
+}
+
+@layer components {
+ .wrapper {
+ @apply w-full max-w-7xl mx-auto px-4;
+ }
+
+ .section {
+ @apply py-16 md:py-24;
+ }
+}
diff --git a/src/lib/Head.jsx b/src/lib/Head.jsx
new file mode 100644
index 0000000..97cd3e3
--- /dev/null
+++ b/src/lib/Head.jsx
@@ -0,0 +1,120 @@
+import { useEffect } from "react";
+
+function upsertMeta(selector, attrs) {
+ let el = document.head.querySelector(selector);
+ if (!el) {
+ el = document.createElement("meta");
+ el.setAttribute("data-managed", "head");
+ document.head.appendChild(el);
+ }
+ Object.entries(attrs).forEach(([k, v]) => {
+ if (v != null) el.setAttribute(k, v);
+ });
+ return el;
+}
+function upsertLink(rel, href) {
+ let el = document.head.querySelector(`link[rel="${rel}"]`);
+ if (!el) {
+ el = document.createElement("link");
+ el.setAttribute("rel", rel);
+ el.setAttribute("data-managed", "head");
+ document.head.appendChild(el);
+ }
+ el.setAttribute("href", href);
+ return el;
+}
+function upsertScript(id, type, text) {
+ let el = document.getElementById(id);
+ if (!el) {
+ el = document.createElement("script");
+ el.id = id;
+ el.type = type;
+ el.setAttribute("data-managed", "head");
+ document.head.appendChild(el);
+ }
+ el.textContent = text;
+ return el;
+}
+
+export default function Head({
+ title,
+ description,
+ canonical,
+ og = {},
+ twitter = {},
+ jsonLd, // object or array of objects
+}) {
+ useEffect(() => {
+ const created = [];
+
+ if (title) {
+ const prev = document.title;
+ document.title = title;
+ created.push(() => (document.title = prev));
+ }
+ if (description) {
+ const el = upsertMeta('meta[name="description"]', {
+ name: "description",
+ content: description,
+ });
+ created.push(() => el.remove());
+ }
+ if (canonical) {
+ const el = upsertLink("canonical", canonical);
+ created.push(() => el.remove());
+ }
+
+ // Open Graph
+ const ogMap = {
+ "og:type": og.type || "website",
+ "og:title": og.title || title,
+ "og:description": og.description || description,
+ "og:url": og.url || canonical,
+ "og:image": og.image,
+ "og:site_name": og.siteName,
+ };
+ Object.entries(ogMap).forEach(([prop, content]) => {
+ if (!content) return;
+ const el = upsertMeta(`meta[property="${prop}"]`, {
+ property: prop,
+ content,
+ });
+ created.push(() => el.remove());
+ });
+
+ // Twitter
+ const twMap = {
+ "twitter:card": twitter.card || "summary_large_image",
+ "twitter:title": twitter.title || title,
+ "twitter:description": twitter.description || description,
+ "twitter:image": twitter.image || og.image,
+ };
+ Object.entries(twMap).forEach(([name, content]) => {
+ if (!content) return;
+ const el = upsertMeta(`meta[name="${name}"]`, { name, content });
+ created.push(() => el.remove());
+ });
+
+ // JSON-LD
+ if (jsonLd) {
+ const items = Array.isArray(jsonLd) ? jsonLd : [jsonLd];
+ const el = upsertScript(
+ "jsonld-webpage",
+ "application/ld+json",
+ JSON.stringify(items.length === 1 ? items[0] : items)
+ );
+ created.push(() => el.remove());
+ }
+
+ return () => created.reverse().forEach((fn) => fn());
+ }, [
+ title,
+ description,
+ canonical,
+ JSON.stringify(og),
+ JSON.stringify(twitter),
+ JSON.stringify(jsonLd),
+ ]);
+
+ return null;
+}
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..b9a1a6d
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/src/pages/About.jsx b/src/pages/About.jsx
new file mode 100644
index 0000000..fe39286
--- /dev/null
+++ b/src/pages/About.jsx
@@ -0,0 +1,59 @@
+import React from "react";
+import AboutCard from "../components/AboutCard";
+import AboutDetails from "../components/AboutDetails";
+import Head from "../lib/Head";
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "About — Priscy Designs";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+export default function About() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: `${SITE_URL}/about`,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* Left column: sticky card */}
+
+
+ {" "}
+ {/* adjust top-6 for offset under your navbar */}
+
+
+
+
+ {/* Right columns: long scrolling content */}
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/Blog.jsx b/src/pages/Blog.jsx
new file mode 100644
index 0000000..19ee00b
--- /dev/null
+++ b/src/pages/Blog.jsx
@@ -0,0 +1,136 @@
+import React, { useEffect, useMemo } from "react";
+import { Link, useSearchParams } from "react-router-dom";
+import posts from "../data/blog.json";
+import Head from "../lib/Head";
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "Blog — Priscy Designs";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+const PER_PAGE = 6;
+
+export default function Blog() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: `${SITE_URL}/blog`,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+
+ const [params, setParams] = useSearchParams();
+ const pageFromUrl = Number(params.get("page")) || 1;
+
+ const totalPages = Math.max(1, Math.ceil(posts.length / PER_PAGE));
+ const page = Math.min(Math.max(1, pageFromUrl), totalPages);
+
+ // Keep URL clean & valid if someone types ?page=999 or ?page=abc
+ useEffect(() => {
+ if (page !== pageFromUrl)
+ setParams({ page: String(page) }, { replace: true });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [page, pageFromUrl]);
+
+ // Slice the posts for this page
+ const pagePosts = useMemo(() => {
+ const start = (page - 1) * PER_PAGE;
+ return posts.slice(start, start + PER_PAGE);
+ }, [page]);
+
+ // Scroll to top on page change
+ useEffect(() => {
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ }, [page]);
+
+ const goto = (p) => setParams({ page: String(p) });
+
+ return (
+ <>
+
+
+
Recent Posts
+
+
+ {pagePosts.map((p) => (
+
+
+
+
+ {p.category} • {new Date(p.date).toLocaleDateString()}
+
+
{p.title}
+
+ {p.excerpt}
+
+
+ See More →
+
+
+
+ ))}
+
+
+ {/* Pagination */}
+
+ goto(page - 1)}
+ disabled={page === 1}
+ className="px-3 py-2 rounded-lg border border-gray-300 dark:border-neutral-800 disabled:opacity-50"
+ >
+ Prev
+
+
+ {/* Page numbers (simple: show all; you can window this if pages get big) */}
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
+ goto(p)}
+ aria-current={p === page ? "page" : undefined}
+ className={`px-3 py-2 rounded-lg border border-gray-300 dark:border-neutral-800
+ ${p === page ? "bg-blue-600 text-white border-blue-600" : ""}`}
+ >
+ {p}
+
+ ))}
+
+ goto(page + 1)}
+ disabled={page === totalPages}
+ className="px-3 py-2 rounded-lg border border-gray-300 dark:border-neutral-800 disabled:opacity-50"
+ >
+ Next
+
+
+
+ >
+ );
+}
diff --git a/src/pages/BlogDetail.jsx b/src/pages/BlogDetail.jsx
new file mode 100644
index 0000000..8a2df4a
--- /dev/null
+++ b/src/pages/BlogDetail.jsx
@@ -0,0 +1,335 @@
+// src/pages/BlogDetail.jsx
+import { useMemo } from "react";
+import { Link, useParams, useNavigate } from "react-router-dom";
+import posts from "../data/blog.json";
+import Head from "../lib/Head";
+
+function seededRandom(seed) {
+ let h = 2166136261 >>> 0;
+ for (let i = 0; i < seed.length; i++) {
+ h ^= seed.charCodeAt(i);
+ h = Math.imul(h, 16777619);
+ }
+ return () => {
+ h += 0x6d2b79f5;
+ let t = Math.imul(h ^ (h >>> 15), 1 | h);
+ t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+export default function BlogDetail() {
+ const { slug } = useParams();
+ const navigate = useNavigate();
+
+ // find post + index (always called)
+ const { post, index } = useMemo(() => {
+ const i = posts.findIndex((p) => p.slug === slug);
+ return i !== -1 ? { post: posts[i], index: i } : { post: null, index: -1 };
+ }, [slug]);
+
+ // SEO values
+ const origin =
+ typeof window !== "undefined"
+ ? window.location.origin
+ : "https://your-domain.com";
+ const canonical = post
+ ? `${origin}/blog/${post.slug}`
+ : `${origin}/blog/not-found`;
+ const title = post
+ ? `${post.title} — Priscy Designs`
+ : "Post not found — Priscy Designs";
+ const description = post
+ ? (post.excerpt || post.body || "").toString().slice(0, 160)
+ : "The requested blog post could not be found.";
+ const cover = post?.coverImage || "/og-cover.jpg";
+ const logo = "/logo.png";
+
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "BlogPosting",
+ headline: post?.title || "Post not found",
+ description: description,
+ image: cover,
+ url: canonical,
+ datePublished: post?.date,
+ author: post?.author ? { "@type": "Person", name: post.author } : undefined,
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: logo },
+ },
+ };
+
+ // related (always called)
+ const related = useMemo(() => {
+ if (!post) return [];
+ const pool = posts.filter((p) => p !== post);
+ const rng = seededRandom(post.slug);
+ for (let i = pool.length - 1; i > 0; i--) {
+ const j = Math.floor(rng() * (i + 1));
+ [pool[i], pool[j]] = [pool[j], pool[i]];
+ }
+ return pool.slice(0, 4);
+ }, [post]);
+
+ // prev/next (compute early)
+ const prev =
+ index === -1 ? null : posts[(index - 1 + posts.length) % posts.length];
+ const next = index === -1 ? null : posts[(index + 1) % posts.length];
+
+ if (!post) {
+ return (
+ <>
+
+
+
+ Post not found
+
+
+ We couldn’t find a blog post at this URL.
+
+
+ navigate(-1)}
+ className="text-blue-600 hover:underline"
+ >
+ ← Go Back
+
+
+
+ >
+ );
+ }
+
+ const toSlug = (p) => `/blog/${p.slug}`;
+ const date = new Date(post.date);
+ const dateStr = date.toLocaleDateString();
+
+ return (
+ <>
+
+
+
+
+ {/* Main article */}
+
+ {/* Breadcrumb */}
+
+
+
+
+ Blog
+
+
+ /
+
+ {post.title}
+
+
+
+
+ {/* Hero */}
+
+
+ {post.excerpt && (
+ {post.excerpt}
+ )}
+
+
+ {/* Header */}
+
+
+ {/* Body */}
+
+
{post.body}
+ {Array.isArray(post.images) && post.images.length > 0 && (
+
+ {post.images.map((src, i) => (
+
+ ))}
+
+ )}
+
+
+ {/* External link */}
+ {post.url && (
+
+
+ Continue reading ↗
+
+
+ )}
+
+ {/* Prev / Next */}
+ {prev && next && (
+
+
+
+ ←
+
+ {prev.title}
+
+
+ {next.title}
+
+ →
+
+
+
+ )}
+
+
+ {/* Related */}
+
+
+ More Posts
+
+
+ {related.map((p) => (
+
+
+
+
+
+
{p.category}
+
+ {p.title}
+
+
+
+
+
+ ))}
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx
new file mode 100644
index 0000000..797f63c
--- /dev/null
+++ b/src/pages/Contact.jsx
@@ -0,0 +1,58 @@
+import React from "react";
+import AboutCard from "../components/AboutCard";
+import ContactDetails from "../components/ContactDetails";
+import Head from "../lib/Head";
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "Services — Priscy Designs";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+export default function Contact() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: `${SITE_URL}/services`,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+ return (
+ <>
+
+
+
+
+ {/* Left column: sticky card */}
+
+
+ {" "}
+ {/* adjust top-6 for offset under your navbar */}
+
+
+
+
+ {/* Right columns: long scrolling content */}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
new file mode 100644
index 0000000..045549a
--- /dev/null
+++ b/src/pages/Home.jsx
@@ -0,0 +1,133 @@
+import React from "react";
+import AboutCard from "../components/AboutCard";
+import WorkExperience from "../components/WorkExperience";
+import ExpertArea from "../components/ExpertArea";
+import ServiceOffer from "../components/ServiceOffer";
+import WorkTogetherCard from "../components/WorkTogetherCard";
+import ProjectCards from "../components/ProjectCards";
+import GallerySlider from "../components/GallerySlider";
+import Head from "../lib/Head";
+
+// Hero/gallery images with descriptive alt
+const images = [
+ {
+ src: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1600&q=80&auto=format&fit=crop",
+ alt: "City skyline at dusk with reflections on water",
+ },
+ {
+ src: "https://images.unsplash.com/photo-1519681393784-d120267933ba?w=1600&q=80&auto=format&fit=crop",
+ alt: "Abstract gradient texture in vibrant colors",
+ },
+ {
+ src: "https://images.unsplash.com/photo-1496307042754-b4aa456c4a2d?w=1600&q=80&auto=format&fit=crop",
+ alt: "Snow-capped mountains surrounding a calm lake",
+ },
+];
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "Priscy Designs — Portfolio & Services";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+export default function Home() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: SITE_URL,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+
+ return (
+ <>
+
+ {/* Skip link for keyboard users */}
+
+ Skip to content
+
+
+ {/* Main landmark */}
+
+ {/* Page heading (only one H1 per page) */}
+
+ Priscy Designs — Portfolio and Services
+
+
+ {/* Intro grid: About + Featured Projects */}
+
+
+
+
+
+
+ Featured Projects
+
+ {/* 4 items per page? set pagination prop accordingly */}
+
+
+
+
+
+ {/* Gallery + Work Experience + Expertise */}
+
+
+ Work, Expertise, and Gallery
+
+
+ {/* The slider already has ARIA from earlier; ensure it has a label prop if you add one */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Services & CTA */}
+
+
+ >
+ );
+}
diff --git a/src/pages/Portfolio.jsx b/src/pages/Portfolio.jsx
new file mode 100644
index 0000000..d112472
--- /dev/null
+++ b/src/pages/Portfolio.jsx
@@ -0,0 +1,59 @@
+import React from "react";
+import AboutCard from "../components/AboutCard";
+import ProjectDetails from "../components/ProjectDetails";
+import Head from "../lib/Head";
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "Portfolio — Priscy Designs";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+export default function Portfolio() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: `${SITE_URL}/portfolio`,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* Left column: sticky card */}
+
+
+ {" "}
+ {/* adjust top-6 for offset under your navbar */}
+
+
+
+
+ {/* Right columns: long scrolling content */}
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/ProductDetail.jsx b/src/pages/ProductDetail.jsx
new file mode 100644
index 0000000..ba3e469
--- /dev/null
+++ b/src/pages/ProductDetail.jsx
@@ -0,0 +1,320 @@
+import { useMemo } from "react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import products from "../data/projects.json";
+import Head from "../lib/Head";
+
+function slugify(s) {
+ return (s || "")
+ .toString()
+ .trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)+/g, "");
+}
+
+// tiny seeded RNG so "random" is stable per product
+function seededRandom(seed) {
+ let h = 2166136261 >>> 0;
+ for (let i = 0; i < seed.length; i++) {
+ h ^= seed.charCodeAt(i);
+ h = Math.imul(h, 16777619);
+ }
+ return () => {
+ h += 0x6d2b79f5;
+ let t = Math.imul(h ^ (h >>> 15), 1 | h);
+ t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+export default function ProductDetail() {
+ const { slug } = useParams();
+ const navigate = useNavigate();
+
+ // find product + index (hook always called)
+ const { product, index } = useMemo(() => {
+ const bySlug = products.findIndex((p) => p.slug === slug);
+ if (bySlug !== -1) return { product: products[bySlug], index: bySlug };
+
+ const byTitle = products.findIndex((p) => slugify(p.title) === slug);
+ if (byTitle !== -1) return { product: products[byTitle], index: byTitle };
+
+ return { product: null, index: -1 };
+ }, [slug]);
+
+ // related list (hook always called; returns [] when no product)
+ const related = useMemo(() => {
+ if (!product) return [];
+ const pool = products.filter((p) => p !== product);
+ const rng = seededRandom(product.slug ?? slugify(product.title));
+ for (let i = pool.length - 1; i > 0; i--) {
+ const j = Math.floor(rng() * (i + 1));
+ [pool[i], pool[j]] = [pool[j], pool[i]];
+ }
+ return pool.slice(0, 4);
+ }, [product]);
+
+ // prev/next (compute even if product is null; values only used when product exists)
+ const prevIndex =
+ index === -1 ? -1 : (index - 1 + products.length) % products.length;
+ const nextIndex = index === -1 ? -1 : (index + 1) % products.length;
+ const prev = prevIndex === -1 ? null : products[prevIndex];
+ const next = nextIndex === -1 ? null : products[nextIndex];
+ const toSlug = (p) => `/portfolio/${p.slug ?? slugify(p.title)}`;
+
+ // now it’s safe to conditionally return
+ if (!product) {
+ return (
+ <>
+
+
+
Product not found
+
+ We couldn’t find a product with that URL.
+
+
navigate(-1)}
+ className="text-blue-600 hover:underline"
+ >
+ ← Go Back
+
+
+ >
+ );
+ }
+
+ /* ---------- ✅ Build SEO values from the product ---------- */
+ const prodSlug = product.slug ?? slugify(product.title); //replace with your actual product slug/url
+ const origin =
+ typeof window !== "undefined"
+ ? window.location.origin
+ : "https://your-domain.com";
+ const canonical = `${origin}/portfolio/${prodSlug}`;
+ const title = `${product.title} — Priscy Designs`;
+ const description =
+ product.body?.slice(0, 160) || "Project case study from Priscy Designs.";
+ const cover = product.coverImage ?? product.images?.[0] ?? "/og-cover.jpg";
+ const logo = "/logo.png";
+
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: title,
+ url: canonical,
+ description,
+ primaryImageOfPage: { "@type": "ImageObject", url: cover },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: logo },
+ },
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* Main project */}
+
+ {/* Breadcrumb */}
+
+
+
+
+ Portfolio
+
+
+ /
+
+ {product.title}
+
+
+
+
+ {/* Hero */}
+
+
+ {description && (
+ {description}
+ )}
+
+
+ {/* Header */}
+
+
+ {/* Body */}
+
+
+ {/* Gallery */}
+ {Array.isArray(product.images) && product.images.length > 0 && (
+
+ {product.images.map((src, i) => (
+
+ ))}
+
+ )}
+
+ {/* External URL */}
+ {product.url && (
+
+
+ Visit Project ↗
+
+
+ )}
+
+ {/* Prev / Next */}
+ {prev && next && (
+
+
+
+ ←
+
+ {prev.title}
+
+
+ {next.title}
+
+ →
+
+
+
+ )}
+
+
+ {/* Related projects */}
+
+
+ More Projects
+
+
+ {related.map((p) => (
+
+
+
+
+
+
{p.category}
+
+ {p.title}
+
+
+
+
+
+ ))}
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/Services.jsx b/src/pages/Services.jsx
new file mode 100644
index 0000000..7287bd7
--- /dev/null
+++ b/src/pages/Services.jsx
@@ -0,0 +1,59 @@
+import React from "react";
+import AboutCard from "../components/AboutCard";
+import ServiceDetails from "../components/ServiceDetails";
+import Head from "../lib/Head";
+
+const SITE_URL = "https://priscy-orcin.vercel.app";
+const COVER_URL = "https://priscy-orcin.vercel.app/og-cover.jpg";
+const LOGO_URL = "https://priscy-orcin.vercel.app/logo.png";
+const TITLE = "Services — Priscy Designs";
+const DESCRIPTION =
+ "Explore UI/UX, full-stack development, branding, and product design projects.";
+
+export default function Services() {
+ const jsonLdWebPage = {
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ name: TITLE,
+ url: `${SITE_URL}/services`,
+ description: DESCRIPTION,
+ primaryImageOfPage: { "@type": "ImageObject", url: COVER_URL },
+ publisher: {
+ "@type": "Organization",
+ name: "Priscy Designs",
+ logo: { "@type": "ImageObject", url: LOGO_URL },
+ },
+ };
+
+ return (
+ <>
+
+
+
+
+ {/* Left column: sticky card */}
+
+
+ {" "}
+ {/* adjust top-6 for offset under your navbar */}
+
+
+
+
+ {/* Right columns: long scrolling content */}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..9521b14
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,8 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [tailwindcss(), react()],
+});