From 55b2b091c22836520752202a6652cff54b8da05e Mon Sep 17 00:00:00 2001 From: que01 Date: Fri, 18 Sep 2026 15:00:48 +0800 Subject: [PATCH] chore: initialize monorepo tooling --- .editorconfig | 12 + .env.example | 11 + .gitignore | 13 + .prettierignore | 6 + .prettierrc.json | 7 + README.md | 30 + agent.md | 1 + apps/admin/package.json | 12 + apps/admin/src/main.ts | 1 + apps/admin/tsconfig.json | 5 + apps/api/package.json | 12 + apps/api/src/main.ts | 3 + apps/api/tsconfig.json | 5 + apps/miniapp/package.json | 10 + apps/miniapp/src/index.ts | 1 + apps/miniapp/tsconfig.json | 5 + docs/02-architecture-and-decisions.md | 22 +- docs/03-data-and-api-design.md | 2 +- docs/05-pilot-case-food-stall.md | 12 +- docs/08-clickable-prototype.md | 29 + eslint.config.mjs | 19 + package.json | 28 + packages/api-client/package.json | 14 + packages/api-client/src/index.ts | 1 + packages/api-client/tsconfig.json | 5 + packages/config/package.json | 10 + packages/config/src/index.ts | 1 + packages/config/tsconfig.json | 5 + packages/types/package.json | 17 + packages/types/src/index.ts | 4 + packages/types/tsconfig.json | 5 + packages/validation/package.json | 11 + packages/validation/src/index.ts | 1 + packages/validation/tsconfig.json | 5 + pnpm-lock.yaml | 1084 +++++++++++++++++++++++++ pnpm-workspace.yaml | 3 + prototype/app.js | 190 +++++ prototype/index.html | 60 ++ prototype/mock-data.js | 30 + prototype/styles.css | 151 ++++ tsconfig.base.json | 23 + turbo.json | 10 + 42 files changed, 1858 insertions(+), 18 deletions(-) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 README.md create mode 100644 apps/admin/package.json create mode 100644 apps/admin/src/main.ts create mode 100644 apps/admin/tsconfig.json create mode 100644 apps/api/package.json create mode 100644 apps/api/src/main.ts create mode 100644 apps/api/tsconfig.json create mode 100644 apps/miniapp/package.json create mode 100644 apps/miniapp/src/index.ts create mode 100644 apps/miniapp/tsconfig.json create mode 100644 docs/08-clickable-prototype.md create mode 100644 eslint.config.mjs create mode 100644 package.json create mode 100644 packages/api-client/package.json create mode 100644 packages/api-client/src/index.ts create mode 100644 packages/api-client/tsconfig.json create mode 100644 packages/config/package.json create mode 100644 packages/config/src/index.ts create mode 100644 packages/config/tsconfig.json create mode 100644 packages/types/package.json create mode 100644 packages/types/src/index.ts create mode 100644 packages/types/tsconfig.json create mode 100644 packages/validation/package.json create mode 100644 packages/validation/src/index.ts create mode 100644 packages/validation/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 prototype/app.js create mode 100644 prototype/index.html create mode 100644 prototype/mock-data.js create mode 100644 prototype/styles.css create mode 100644 tsconfig.base.json create mode 100644 turbo.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c52ff9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..29085bd --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +NODE_ENV=development +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/personal_brand +API_PORT=3001 +ADMIN_PORT=3000 +SESSION_SECRET=replace-with-a-long-random-value +OBJECT_STORAGE_ENDPOINT= +OBJECT_STORAGE_BUCKET= +OBJECT_STORAGE_ACCESS_KEY= +OBJECT_STORAGE_SECRET_KEY= +WECHAT_APP_ID= +WECHAT_APP_SECRET= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb3b438 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +.pnpm-store/ +.turbo/ +dist/ +.next/ +coverage/ +.env +.env.* +!.env.example +*.log +.DS_Store +apps/miniapp/miniprogram_npm/ +apps/miniapp/project.private.config.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fcc23c5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +.turbo +dist +.next +prototype +pnpm-lock.yaml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4a1222f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9939b83 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Personal Brand Miniapp + +面向个体经营者的个人品牌小程序试点项目。当前首个试点为餐饮摊主与私厨服务。 + +## 工程结构 + +```text +apps/miniapp # 原生微信小程序 +apps/admin # 管理后台 +apps/api # API 服务 +packages/types # 共享类型 +packages/api-client +packages/validation +packages/config +prisma/ +docs/ +prototype/ +``` + +## 环境要求 + +- Node.js >= 20.18 +- pnpm 9.15 +- PostgreSQL(进入 API/数据库开发阶段时需要) + +```bash +corepack enable +pnpm install +pnpm check +``` diff --git a/agent.md b/agent.md index c80885f..e2743bb 100644 --- a/agent.md +++ b/agent.md @@ -24,6 +24,7 @@ 6. `docs/05-pilot-case-food-stall.md`:当前首个试点的餐饮场景假设和待确认内容。 7. `docs/06-domain-extension-strategy.md`:个人品牌核心能力与行业扩展边界。 8. `docs/07-requirements-interview-decisions.md`:首轮访谈确认的订单与客户身份规则;冲突时优先于早期文档。 +9. `docs/08-clickable-prototype.md`:低保真原型入口、演练路径和边界。 ## 已安装的成熟 Skills diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 0000000..5f52006 --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,12 @@ +{ + "name": "@personal-brand/admin", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch", + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + } +} diff --git a/apps/admin/src/main.ts b/apps/admin/src/main.ts new file mode 100644 index 0000000..053e8ed --- /dev/null +++ b/apps/admin/src/main.ts @@ -0,0 +1 @@ +export const adminAppName = "personal-brand-admin"; diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json new file mode 100644 index 0000000..c2c7d25 --- /dev/null +++ b/apps/admin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src", "lib": ["ES2022", "DOM"] }, + "include": ["src/**/*.ts"] +} diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..8778114 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,12 @@ +{ + "name": "@personal-brand/api", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "tsc -p tsconfig.json --watch", + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + } +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 0000000..d56a588 --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,3 @@ +export function healthMessage(): string { + return "personal-brand-api"; +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..44f4ad8 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts"] +} diff --git a/apps/miniapp/package.json b/apps/miniapp/package.json new file mode 100644 index 0000000..cad9632 --- /dev/null +++ b/apps/miniapp/package.json @@ -0,0 +1,10 @@ +{ + "name": "@personal-brand/miniapp", + "private": true, + "type": "module", + "scripts": { + "lint": "eslint . --ext .ts", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + } +} diff --git a/apps/miniapp/src/index.ts b/apps/miniapp/src/index.ts new file mode 100644 index 0000000..3048c45 --- /dev/null +++ b/apps/miniapp/src/index.ts @@ -0,0 +1 @@ +export const miniappName = "personal-brand-miniapp"; diff --git a/apps/miniapp/tsconfig.json b/apps/miniapp/tsconfig.json new file mode 100644 index 0000000..8080e22 --- /dev/null +++ b/apps/miniapp/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "noEmit": true, "lib": ["ES2022"] }, + "include": ["src/**/*.ts"] +} diff --git a/docs/02-architecture-and-decisions.md b/docs/02-architecture-and-decisions.md index 2c60452..6e84054 100644 --- a/docs/02-architecture-and-decisions.md +++ b/docs/02-architecture-and-decisions.md @@ -6,17 +6,17 @@ ## 2. 技术选型 -| 层 | 选型 | 目的 | -|---|---|---| -| Monorepo | pnpm workspace + Turborepo | 统一脚本、缓存构建、共享包 | -| 小程序 | 原生微信小程序 + TypeScript | 直接使用微信能力,减少跨端框架层 | -| 管理后台 | Next.js + TypeScript | 表单、列表、图片管理和路由生态成熟 | -| API | NestJS + TypeScript | 模块化、依赖注入、权限和校验边界清晰 | -| 数据库 | PostgreSQL | 适合预约、服务、反馈之间的关系 | -| ORM | Prisma | 类型安全、迁移和 schema 管理清晰 | -| 文件存储 | S3 兼容对象存储 | 图片不进入数据库,便于扩容和 CDN | -| 校验 | Zod 或 class-validator,统一一种 | API 与前端共享约束 | -| 部署 | Docker Compose 起步,后续迁移托管服务 | 本地与生产环境差异小 | +| 层 | 选型 | 目的 | +| -------- | ------------------------------------- | ------------------------------------ | +| Monorepo | pnpm workspace + Turborepo | 统一脚本、缓存构建、共享包 | +| 小程序 | 原生微信小程序 + TypeScript | 直接使用微信能力,减少跨端框架层 | +| 管理后台 | Next.js + TypeScript | 表单、列表、图片管理和路由生态成熟 | +| API | NestJS + TypeScript | 模块化、依赖注入、权限和校验边界清晰 | +| 数据库 | PostgreSQL | 适合预约、服务、反馈之间的关系 | +| ORM | Prisma | 类型安全、迁移和 schema 管理清晰 | +| 文件存储 | S3 兼容对象存储 | 图片不进入数据库,便于扩容和 CDN | +| 校验 | Zod 或 class-validator,统一一种 | API 与前端共享约束 | +| 部署 | Docker Compose 起步,后续迁移托管服务 | 本地与生产环境差异小 | ## 3. 仓库结构 diff --git a/docs/03-data-and-api-design.md b/docs/03-data-and-api-design.md index 9cf2529..d7986a6 100644 --- a/docs/03-data-and-api-design.md +++ b/docs/03-data-and-api-design.md @@ -101,7 +101,7 @@ AdminUser 1 ── n AuditLog "error": { "code": "VALIDATION_ERROR", "message": "请填写有效的联系方式", - "fields": {"phone": "格式不正确"} + "fields": { "phone": "格式不正确" } }, "requestId": "..." } diff --git a/docs/05-pilot-case-food-stall.md b/docs/05-pilot-case-food-stall.md index bbdfd3d..981688d 100644 --- a/docs/05-pilot-case-food-stall.md +++ b/docs/05-pilot-case-food-stall.md @@ -107,12 +107,12 @@ 沿用通用状态,但在餐饮后台显示更容易理解的文案: -| 系统状态 | 页面文案 | 说明 | -|---|---|---| -| pending | 待联系 | 客户刚提交需求 | -| contacted | 沟通中 | 已联系,正在确认菜单或细节 | -| completed | 已完成 | 已完成本次餐饮服务 | -| cancelled | 已取消 | 客户或经营者取消 | +| 系统状态 | 页面文案 | 说明 | +| --------- | -------- | -------------------------- | +| pending | 待联系 | 客户刚提交需求 | +| contacted | 沟通中 | 已联系,正在确认菜单或细节 | +| completed | 已完成 | 已完成本次餐饮服务 | +| cancelled | 已取消 | 客户或经营者取消 | 第一版不增加“已确认”状态,避免在没有支付和排班能力时造成履约承诺。若真实业务需要,可在试用后增加 `confirmed`。 diff --git a/docs/08-clickable-prototype.md b/docs/08-clickable-prototype.md new file mode 100644 index 0000000..86947e2 --- /dev/null +++ b/docs/08-clickable-prototype.md @@ -0,0 +1,29 @@ +# 可点击低保真原型 + +## 使用方式 + +直接在浏览器打开 `prototype/index.html`,无需安装依赖或启动服务。 + +原型顶部可以切换“客户小程序”和“经营后台”。它使用 `prototype/mock-data.js` 中的虚拟餐饮数据,不连接数据库,也不会保存刷新后的操作。 + +## 建议演练路径 + +### 普通套餐 + +首页 -> 订普通套餐 -> 选择数量 -> 选择自取/配送 -> 提交 -> 我的订单。 + +### 私厨定制 + +首页 -> 约私厨 -> 填写日期、人数、地点与联系信息 -> 提交 -> 我的订单。 + +### 后台处理 + +经营后台 -> 订单管理 -> 打开未读订单 -> 修改状态 -> 添加标签 -> 填写内部备忘 -> 保存。 + +## 原型边界 + +- 微信登录仅模拟,不调用微信 API。 +- 图片使用公开示例资源,正式项目必须替换为经营者授权图片。 +- 每日菜单维护方式仍待确认,原型只展示固定套餐。 +- 原型不包含支付、库存、自动配送判断和订阅消息。 +- 所有操作只保存在当前浏览器页面内存中,刷新后恢复虚拟数据。 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..625616a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,19 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["**/dist/**", "**/.next/**", "**/node_modules/**", "prototype/**"] }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }, + }, + rules: { + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "@typescript-eslint/no-explicit-any": "warn", + }, + }, +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e178b54 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "personal-brand-miniapp", + "version": "0.1.0", + "private": true, + "packageManager": "pnpm@9.15.0", + "engines": { + "node": ">=20.18.0" + }, + "scripts": { + "build": "turbo run build", + "dev": "turbo run dev --parallel", + "lint": "turbo run lint", + "typecheck": "turbo run typecheck", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "pnpm format:check && pnpm lint && pnpm typecheck", + "clean": "turbo run clean && rimraf node_modules .turbo" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "eslint": "^9.17.0", + "prettier": "^3.4.2", + "rimraf": "^6.0.1", + "turbo": "^2.3.3", + "typescript": "^5.7.2", + "typescript-eslint": "^8.18.0" + } +} diff --git a/packages/api-client/package.json b/packages/api-client/package.json new file mode 100644 index 0000000..2d1ffb8 --- /dev/null +++ b/packages/api-client/package.json @@ -0,0 +1,14 @@ +{ + "name": "@personal-brand/api-client", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + }, + "dependencies": { + "@personal-brand/types": "workspace:*" + } +} diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts new file mode 100644 index 0000000..f2a76a6 --- /dev/null +++ b/packages/api-client/src/index.ts @@ -0,0 +1 @@ +export const apiClientName = "personal-brand-api-client"; diff --git a/packages/api-client/tsconfig.json b/packages/api-client/tsconfig.json new file mode 100644 index 0000000..44f4ad8 --- /dev/null +++ b/packages/api-client/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts"] +} diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 0000000..7135611 --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,10 @@ +{ + "name": "@personal-brand/config", + "private": true, + "type": "module", + "scripts": { + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + } +} diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts new file mode 100644 index 0000000..92ebf5f --- /dev/null +++ b/packages/config/src/index.ts @@ -0,0 +1 @@ +export const configPackageName = "personal-brand-config"; diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json new file mode 100644 index 0000000..a552e34 --- /dev/null +++ b/packages/config/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "noEmit": true }, + "include": ["src/**/*.ts"] +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..3fe8dff --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,17 @@ +{ + "name": "@personal-brand/types", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..57e5a68 --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,4 @@ +export type BookingStatus = "received" | "contacting" | "confirmed" | "completed" | "cancelled"; +export type BookingRequestType = "package" | "private_chef"; +export type ContactPreference = "wechat" | "phone" | "none"; +export type FulfillmentType = "pickup" | "delivery"; diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..44f4ad8 --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts"] +} diff --git a/packages/validation/package.json b/packages/validation/package.json new file mode 100644 index 0000000..cd22f7a --- /dev/null +++ b/packages/validation/package.json @@ -0,0 +1,11 @@ +{ + "name": "@personal-brand/validation", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "lint": "eslint .", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "rimraf dist" + } +} diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts new file mode 100644 index 0000000..d050cc2 --- /dev/null +++ b/packages/validation/src/index.ts @@ -0,0 +1 @@ +export const validationPackageName = "personal-brand-validation"; diff --git a/packages/validation/tsconfig.json b/packages/validation/tsconfig.json new file mode 100644 index 0000000..44f4ad8 --- /dev/null +++ b/packages/validation/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..126e6a1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1084 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^9.17.0 + version: 9.39.5 + eslint: + specifier: ^9.17.0 + version: 9.39.5 + prettier: + specifier: ^3.4.2 + version: 3.9.8 + rimraf: + specifier: ^6.0.1 + version: 6.1.3 + turbo: + specifier: ^2.3.3 + version: 2.10.13 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + typescript-eslint: + specifier: ^8.18.0 + version: 8.70.0(eslint@9.39.5)(typescript@5.9.3) + + apps/admin: {} + + apps/api: {} + + apps/miniapp: {} + + packages/api-client: + dependencies: + '@personal-brand/types': + specifier: workspace:* + version: link:../types + + packages/config: {} + + packages/types: {} + + packages/validation: {} + +packages: + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + 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.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + 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.7': + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + 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.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + 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'} + + '@turbo/darwin-64@2.10.13': + resolution: {integrity: sha512-m5DBcpkxmcKpEJHHFUfqf9GLUjSmw3cDnBJM9nghPW3kjCNb5OyNKmzG7/yT5x5AwzOO+HMwKN+UI4iVKL87YA==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.10.13': + resolution: {integrity: sha512-vfKPvSuoY8BJGj8M8yN+a7aq1g+CejbEacDSDlJLDkSbPN3J/U1DbYAmPG8Ype5XvImI0SgfvUhIorNnFj67zw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.10.13': + resolution: {integrity: sha512-Zp0N5M6j9iEjJtFLTA2L+52TLuYy0VbXT3zLZzfMeqqdKSUJ7pDo2pA1NhqjoQcF2dIsktZ8xZRW3pGI6T81fw==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.10.13': + resolution: {integrity: sha512-M8XcfWdS2D4EP3wzd5ehUyCANucMIOsRE6gZuV0Mr2NIn+FpsUjXFCh7r9HY7GnNcjXH4tUnT76OsCDg2c0Fng==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.10.13': + resolution: {integrity: sha512-QFd3KBMpBBCc31TbxMnxgdBivie8lxJXV8EqmanH8fg8l2e1BdCLWpZJbxk48VNvcF4LHUnwO0FJOq+veKN1cw==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.10.13': + resolution: {integrity: sha512-rM+cFO1P8iAHXdasOdMyK9nDk0UGII7VEFLIA8+jwv/+rh48pnVBbF2OSjJDBOw57pxNLIOEDA0JyKcarTBcIw==} + cpu: [arm64] + os: [win32] + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@typescript-eslint/eslint-plugin@8.70.0': + resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.70.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.70.0': + resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.70.0': + resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.70.0': + resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.70.0': + resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.70.0': + resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.70.0': + resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.70.0': + resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.70.0': + resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.70.0': + resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + 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==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + 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==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + 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==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + 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-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + 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.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + 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.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + 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'} + + ignore@7.0.9: + resolution: {integrity: sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==} + 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==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + 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==} + + 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'} + + 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@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + 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'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + 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'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.9.8: + resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + 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'} + + 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'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + turbo@2.10.13: + resolution: {integrity: sha512-69MkPbjk+G8oyC7pC2DeBk0rPdbLz51wuH5eAT0Q4BVJkzq/b1XM4kUUidsARbmDjwIELSJwd1ezITULY9kWNw==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.70.0: + resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + 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'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5)': + dependencies: + eslint: 9.39.5 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + 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.7': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.5': {} + + '@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.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@turbo/darwin-64@2.10.13': + optional: true + + '@turbo/darwin-arm64@2.10.13': + optional: true + + '@turbo/linux-64@2.10.13': + optional: true + + '@turbo/linux-arm64@2.10.13': + optional: true + + '@turbo/windows-64@2.10.13': + optional: true + + '@turbo/windows-arm64@2.10.13': + optional: true + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/type-utils': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.70.0 + eslint: 9.39.5 + ignore: 7.0.9 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.70.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.70.0 + debug: 4.4.3 + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.70.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@5.9.3) + '@typescript-eslint/types': 8.70.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.70.0': + dependencies: + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/visitor-keys': 8.70.0 + + '@typescript-eslint/tsconfig-utils@8.70.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.70.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.5 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.70.0': {} + + '@typescript-eslint/typescript-estree@8.70.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.70.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@5.9.3) + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/visitor-keys': 8.70.0 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.70.0(eslint@9.39.5)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3) + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.70.0': + dependencies: + '@typescript-eslint/types': 8.70.0 + eslint-visitor-keys: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + ajv@6.15.0: + 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: {} + + balanced-match@4.0.4: {} + + brace-expansion@1.1.21: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + callsites@3.1.0: {} + + 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: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + escape-string-regexp@4.0.0: {} + + 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-visitor-keys@5.0.1: {} + + eslint@9.39.5: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.7 + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + 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.7.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.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.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.7): + optionalDependencies: + picomatch: 4.0.7 + + 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.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.9: {} + + 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: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + 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 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@11.5.2: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.21 + + minipass@7.1.3: {} + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + 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 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + + picomatch@4.0.7: {} + + prelude-ls@1.2.1: {} + + prettier@3.9.8: {} + + punycode@2.3.1: {} + + resolve-from@4.0.0: {} + + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + turbo@2.10.13: + optionalDependencies: + '@turbo/darwin-64': 2.10.13 + '@turbo/darwin-arm64': 2.10.13 + '@turbo/linux-64': 2.10.13 + '@turbo/linux-arm64': 2.10.13 + '@turbo/windows-64': 2.10.13 + '@turbo/windows-arm64': 2.10.13 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.70.0(eslint@9.39.5)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.70.0(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..286cf7f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - apps/* + - packages/* diff --git a/prototype/app.js b/prototype/app.js new file mode 100644 index 0000000..f290592 --- /dev/null +++ b/prototype/app.js @@ -0,0 +1,190 @@ +const data = window.PROTOTYPE_DATA; +const state = { + miniRoute: "home", + adminRoute: "dashboard", + fulfillment: "pickup", + contactPreference: "none", + quantities: Object.fromEntries(data.packages.map((item) => [item.id, 0])), + orders: structuredClone(data.orders), + selectedOrderId: null, + orderFilter: "all" +}; + +const statusLabels = { received: "已收到", contacting: "沟通中", confirmed: "已确认", completed: "已完成", cancelled: "已取消" }; + +function escapeHtml(value = "") { + return String(value).replace(/[&<>'"]/g, (char) => ({ "&": "&", "<": "<", ">": ">", "'": "'", '"': """ })[char]); +} + +function showToast(message) { + const toast = document.querySelector("#toast"); + toast.textContent = message; + toast.classList.add("show"); + setTimeout(() => toast.classList.remove("show"), 1800); +} + +function setMiniRoute(route) { + state.miniRoute = route; + document.querySelectorAll("[data-route]").forEach((button) => button.classList.toggle("active", button.dataset.route === route)); + renderMiniapp(); +} + +function pageHeader(title) { + return ``; +} + +function renderHome() { + return ` +
+ 现做 · 家常 · 可定制 +

${data.brand.name}

+

${data.brand.tagline}

+
+ + +
+
+
+

常见套餐

+
${data.packages.slice(0, 2).map((item) => ` +
${item.name}

${item.name}

${item.description}

¥${item.price}
`).join("")} +
+
+
+

私厨案例

+
${data.portfolio.slice(0, 2).map((item) => `
${item.title}
${item.title}${item.category}
`).join("")}
+
+
+

食客反馈

+ ${data.feedback.map((item) => `

${item.content}

${item.name}
`).join("")} +
`; +} + +function renderPackageOrder() { + const total = data.packages.reduce((sum, item) => sum + item.price * state.quantities[item.id], 0); + return `${pageHeader("预订普通套餐")} +
+

选择套餐和数量

+ ${data.packages.map((item) => `
${item.name}
¥${item.price} / 份
${state.quantities[item.id]}
`).join("")} +
预计总价 ¥${total}
最终以经营者确认和线下结算为准
+
+

取餐方式

+ ${state.fulfillment === "delivery" ? `

${data.brand.deliveryNote}

` : `
`} +

联系信息

+ +
`; +} + +function renderChefRequest() { + return `${pageHeader("提交私厨需求")} +
+
提交后由经营者评估并联系报价,不产生费用,也不代表已经确认。
+

用餐安排

+

需求偏好

+

怎么联系你

+ +
`; +} + +function renderOrders() { + return `${pageHeader("我的订单")}
${state.orders.map((order) => `
${statusLabels[order.status]}${order.createdAt}

${order.summary}

${order.fulfillment}

订单号 ${order.id}

${order.status === "received" ? `` : ""}
`).join("") || `
还没有订单
`}
`; +} + +function renderBrand() { + return `${pageHeader("关于阿成")}
家常菜制作

${data.brand.name}

${data.brand.description}

营业时间

${data.brand.hours}

摊位位置

${data.brand.address}

联系电话

${data.brand.phone}

`; +} + +function renderServices() { + return `${pageHeader("服务")} +

普通套餐

${data.packages.map((item) => `
${item.name}

${item.name}

${item.description}

¥${item.price}
`).join("")}
+

私厨定制

适合家庭聚餐、朋友小聚和小型活动。根据人数、地点和口味人工沟通报价。

`; +} + +function renderMiniapp() { + const views = { home: renderHome, package: renderPackageOrder, chef: renderChefRequest, orders: renderOrders, brand: renderBrand, services: renderServices }; + document.querySelector("#miniapp-content").innerHTML = (views[state.miniRoute] || renderHome)(); +} + +function orderRows(orders) { + return orders.map((order) => `${order.unread ? '' : ""}${order.id}${order.customer}
${order.type === "package" ? "普通套餐" : "私厨需求"}${order.summary}${statusLabels[order.status]}${order.tags.map((tag) => `${tag}`).join("")}${order.createdAt}`).join(""); +} + +function renderDashboard() { + return `

经营概览

今天也先把每一份订单处理好
未读订单${state.orders.filter((o) => o.unread).length}
待处理${state.orders.filter((o) => o.status === "received").length}
沟通中的私厨${state.orders.filter((o) => o.type === "private_chef" && o.status === "contacting").length}

最近订单

${orderRows(state.orders)}
订单客户内容状态标签提交时间
`; +} + +function renderAdminOrders() { + const visible = state.orderFilter === "all" ? state.orders : state.orders.filter((item) => item.type === state.orderFilter); + return `

订单管理

普通套餐和私厨需求统一进入这里
${orderRows(visible)}
订单客户内容状态标签提交时间
`; +} + +function renderOrderDetail(order) { + const allTags = ["普通套餐", "私厨", "配送", "自取", "熟客", "重点跟进"]; + return `

${order.type === "package" ? "普通套餐订单" : "私厨需求"}

${order.id} · ${order.createdAt}

订单内容

客户${order.customer}
内容${order.summary}
履约${order.fulfillment}
预计金额${order.amount === null ? "人工沟通报价" : `¥${order.amount}`}
联系偏好${order.contact}

内部备忘

`; +} + +function renderPlaceholder(title, body) { + return `

${title}

${body}
`; +} + +function renderAdmin() { + let html; + if (state.adminRoute === "dashboard") html = renderDashboard(); + else if (state.adminRoute === "orders") html = renderAdminOrders(); + else if (state.adminRoute === "order-detail") html = renderOrderDetail(state.orders.find((item) => item.id === state.selectedOrderId)); + else if (state.adminRoute === "services") html = renderPlaceholder("套餐与服务", "每日菜单维护方式尚待朋友确认。当前原型只保留固定套餐和启用状态。"); + else if (state.adminRoute === "brand") html = renderPlaceholder("品牌资料", "编辑品牌名称、介绍、营业时间、摊位位置和配送说明。"); + else if (state.adminRoute === "portfolio") html = renderPlaceholder("作品集", "管理招牌菜、日常套餐和私厨案例图片。"); + else html = renderPlaceholder("客户反馈", "审核后才会公开展示客户反馈。"); + document.querySelector("#admin-content").innerHTML = html; +} + +document.addEventListener("click", (event) => { + const mode = event.target.closest("[data-mode]"); + if (mode) { + document.querySelectorAll("[data-mode]").forEach((button) => button.classList.toggle("active", button === mode)); + document.querySelectorAll(".mode-panel").forEach((panel) => panel.classList.toggle("active", panel.id === `${mode.dataset.mode}-shell`)); + } + const route = event.target.closest("[data-route]"); + if (route) setMiniRoute(route.dataset.route); + const action = event.target.closest("[data-action]")?.dataset.action; + if (action === "package-order") { state.miniRoute = "package"; renderMiniapp(); } + if (action === "chef-request") { state.miniRoute = "chef"; renderMiniapp(); } + if (action === "brand") setMiniRoute("brand"); + if (action === "back-home") setMiniRoute("home"); + const qty = event.target.closest("[data-qty]"); + if (qty) { state.quantities[qty.dataset.qty] = Math.max(0, state.quantities[qty.dataset.qty] + Number(qty.dataset.delta)); renderMiniapp(); } + const fulfillment = event.target.closest("[data-fulfillment]"); + if (fulfillment) { state.fulfillment = fulfillment.dataset.fulfillment; renderMiniapp(); } + const cancel = event.target.closest("[data-cancel-order]"); + if (cancel) { const order = state.orders.find((item) => item.id === cancel.dataset.cancelOrder); order.status = "cancelled"; renderMiniapp(); showToast("订单已取消"); } + const adminRoute = event.target.closest("[data-admin-route]"); + if (adminRoute) { state.adminRoute = adminRoute.dataset.adminRoute; document.querySelectorAll("[data-admin-route]").forEach((button) => button.classList.toggle("active", button === adminRoute)); renderAdmin(); } + const orderRow = event.target.closest("[data-order-id]"); + if (orderRow) { state.selectedOrderId = orderRow.dataset.orderId; const order = state.orders.find((item) => item.id === state.selectedOrderId); order.unread = false; state.adminRoute = "order-detail"; renderAdmin(); } + const filter = event.target.closest("[data-order-filter]"); + if (filter) { state.orderFilter = filter.dataset.orderFilter; renderAdmin(); } + const adminAction = event.target.closest("[data-admin-action]")?.dataset.adminAction; + if (adminAction === "all-orders") { state.adminRoute = "orders"; renderAdmin(); } + if (adminAction === "create-tag") showToast("原型:这里打开新建标签弹窗"); + const toggleTag = event.target.closest("[data-toggle-tag]"); + if (toggleTag) { const order = state.orders.find((item) => item.id === state.selectedOrderId); const tag = toggleTag.dataset.toggleTag; order.tags = order.tags.includes(tag) ? order.tags.filter((item) => item !== tag) : [...order.tags, tag]; renderAdmin(); } + if (adminAction === "save-order") { const order = state.orders.find((item) => item.id === state.selectedOrderId); order.status = document.querySelector("#admin-status").value; order.note = document.querySelector("#admin-note").value; renderAdmin(); showToast("处理结果已保存"); } +}); + +document.addEventListener("submit", (event) => { + event.preventDefault(); + if (event.target.id === "package-form") { + const total = data.packages.reduce((sum, item) => sum + item.price * state.quantities[item.id], 0); + if (!total) { showToast("请至少选择一份套餐"); return; } + state.orders.unshift({ id: `AC${Date.now().toString().slice(-11)}`, type: "package", customer: "陈同学", summary: "新提交的套餐订单", amount: total, fulfillment: state.fulfillment === "pickup" ? "自取" : "配送", contact: "按客户偏好", status: "received", unread: true, tags: ["普通套餐", state.fulfillment === "pickup" ? "自取" : "配送"], note: "", createdAt: "刚刚" }); + } else { + state.orders.unshift({ id: `AC${Date.now().toString().slice(-11)}`, type: "private_chef", customer: "新客户", summary: "新提交的私厨需求", amount: null, fulfillment: "待评估", contact: "微信联系", status: "received", unread: true, tags: ["私厨"], note: "", createdAt: "刚刚" }); + } + state.miniRoute = "orders"; + renderMiniapp(); + showToast("提交成功,已进入我的订单"); +}); + +renderMiniapp(); +renderAdmin(); diff --git a/prototype/index.html b/prototype/index.html new file mode 100644 index 0000000..0aa430a --- /dev/null +++ b/prototype/index.html @@ -0,0 +1,60 @@ + + + + + + 个人品牌小程序 · 低保真原型 + + + +
+
+ 餐饮个人品牌 · 交互原型 + 使用虚拟数据,仅验证流程 +
+
+ + +
+
+ +
+
+
+
9:41微信小程序
+
+ +
+
+ +
+
+ +
+
试点环境
+
+
+
+
+
+ +
+ + + + diff --git a/prototype/mock-data.js b/prototype/mock-data.js new file mode 100644 index 0000000..babe67f --- /dev/null +++ b/prototype/mock-data.js @@ -0,0 +1,30 @@ +window.PROTOTYPE_DATA = { + brand: { + name: "阿成家常菜", + tagline: "现做家常套餐,也为小型聚餐定制一桌热饭菜", + description: "每天认真做好一顿家常饭。日常供应现做套餐,也接受家庭聚餐、朋友小聚和小型活动的私厨定制。", + hours: "周一至周六 10:30-20:00", + address: "大学城生活区东门附近", + deliveryNote: "目前支持部分学生宿舍楼配送,无法送达时会主动联系。", + phone: "138 0000 6628" + }, + packages: [ + { id: "pkg-1", name: "红烧肉双拼套餐", price: 28, description: "红烧肉、时蔬、米饭,可选搭配卤蛋", image: "https://images.unsplash.com/photo-1547592180-85f173990554?auto=format&fit=crop&w=900&q=80" }, + { id: "pkg-2", name: "小炒鸡套餐", price: 25, description: "现炒鸡块、时蔬、米饭,默认微辣", image: "https://images.unsplash.com/photo-1601050690597-df0568f70950?auto=format&fit=crop&w=900&q=80" }, + { id: "pkg-3", name: "番茄牛腩套餐", price: 32, description: "番茄牛腩、时蔬、米饭", image: "https://images.unsplash.com/photo-1604908176997-125f25cc6f3d?auto=format&fit=crop&w=900&q=80" } + ], + portfolio: [ + { title: "六人家庭聚餐", category: "家庭聚餐", image: "https://images.unsplash.com/photo-1515003197210-e0cd71810b5f?auto=format&fit=crop&w=900&q=80" }, + { title: "春季家常菜单", category: "季节菜单", image: "https://images.unsplash.com/photo-1543353071-873f17a7a088?auto=format&fit=crop&w=900&q=80" }, + { title: "朋友小聚八菜一汤", category: "私厨案例", image: "https://images.unsplash.com/photo-1504674900247-0877df9cc836?auto=format&fit=crop&w=900&q=80" } + ], + feedback: [ + { name: "周同学", content: "分量很足,提前说了少辣,口味刚好。" }, + { name: "林女士", content: "家庭聚餐菜单沟通得很细,老人孩子都吃得惯。" } + ], + orders: [ + { id: "AC20260918001", type: "package", customer: "陈同学", summary: "红烧肉双拼套餐 x2", amount: 56, fulfillment: "配送 · 3号男生宿舍楼", contact: "无需联系", status: "received", unread: true, tags: ["普通套餐", "配送"], note: "不要香菜", createdAt: "今天 09:42" }, + { id: "AC20260917003", type: "private_chef", customer: "王女士", summary: "8人家庭聚餐 · 预算 800-1000", amount: null, fulfillment: "滨江区,9月21日晚餐", contact: "微信联系", status: "contacting", unread: false, tags: ["私厨", "重点跟进"], note: "有两位老人,口味清淡;一人花生过敏", createdAt: "昨天 18:10" }, + { id: "AC20260916008", type: "package", customer: "刘同学", summary: "番茄牛腩套餐 x1", amount: 32, fulfillment: "自取 · 18:20", contact: "电话联系", status: "confirmed", unread: false, tags: ["普通套餐", "自取", "熟客"], note: "", createdAt: "9月16日 16:32" } + ] +}; diff --git a/prototype/styles.css b/prototype/styles.css new file mode 100644 index 0000000..444b633 --- /dev/null +++ b/prototype/styles.css @@ -0,0 +1,151 @@ +:root { + --ink: #202522; + --muted: #667069; + --line: #d9dedb; + --surface: #ffffff; + --canvas: #f2f4f2; + --brand: #b2382f; + --brand-dark: #84271f; + --green: #2f684a; + --yellow: #f1c75b; + --blue: #315f78; + --radius: 8px; +} + +* { box-sizing: border-box; } +body { margin: 0; color: var(--ink); background: var(--canvas); font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; } +button, input, select, textarea { font: inherit; letter-spacing: 0; } +button { cursor: pointer; } +.prototype-bar { height: 64px; padding: 0 24px; display: flex; align-items: center; justify-content: space-between; color: white; background: #202522; } +.prototype-bar strong { display: block; font-size: 15px; } +.prototype-bar span { color: #b8c1bb; font-size: 12px; } +.mode-switch { display: flex; border: 1px solid #56605a; border-radius: 6px; overflow: hidden; } +.mode-button { border: 0; padding: 8px 14px; color: #dce1de; background: transparent; } +.mode-button.active { color: #202522; background: white; } +.mode-panel { display: none; min-height: calc(100vh - 64px); } +.mode-panel.active { display: grid; place-items: center; } +.phone-shell { width: min(390px, 100vw); height: min(780px, calc(100vh - 88px)); min-height: 620px; position: relative; overflow: hidden; background: white; border: 1px solid #bcc4bf; border-radius: 24px; box-shadow: 0 12px 36px rgba(31, 40, 34, .12); } +.phone-status { height: 30px; padding: 0 16px; display: flex; align-items: center; justify-content: space-between; font-size: 11px; border-bottom: 1px solid #edf0ee; } +.phone-content { height: calc(100% - 88px); overflow-y: auto; padding-bottom: 24px; } +.tabbar { height: 58px; display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line); background: white; } +.tabbar button { border: 0; color: var(--muted); background: white; font-size: 11px; } +.tabbar button span { display: block; height: 24px; font-size: 19px; line-height: 22px; } +.tabbar button.active { color: var(--brand); font-weight: 700; } +.page-header { position: sticky; top: 0; z-index: 4; min-height: 50px; display: flex; align-items: center; gap: 10px; padding: 10px 16px; background: rgba(255,255,255,.96); border-bottom: 1px solid #edf0ee; } +.page-header h1 { margin: 0; font-size: 17px; } +.back-button { width: 30px; height: 30px; border: 0; border-radius: 50%; background: #edf0ee; } +.hero { min-height: 260px; padding: 24px 20px; color: white; display: flex; flex-direction: column; justify-content: flex-end; background: linear-gradient(0deg, rgba(20,25,22,.78), rgba(20,25,22,.08)), url('https://images.unsplash.com/photo-1556911220-bff31c812dba?auto=format&fit=crop&w=1000&q=80') center/cover; } +.hero-badge { width: max-content; padding: 4px 8px; margin-bottom: 8px; color: #222; background: var(--yellow); border-radius: 4px; font-size: 12px; font-weight: 700; } +.hero h1 { margin: 0; font-size: 30px; line-height: 1.18; } +.hero p { margin: 8px 0 18px; color: #f1f3f1; } +.button-row { display: flex; gap: 8px; } +.primary-button, .secondary-button { min-height: 42px; padding: 0 16px; border-radius: 6px; font-weight: 700; } +.primary-button { border: 1px solid var(--brand); color: white; background: var(--brand); } +.primary-button:hover { background: var(--brand-dark); } +.secondary-button { border: 1px solid var(--line); color: var(--ink); background: white; } +.block { padding: 20px 16px 0; } +.block-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } +.block-heading h2 { margin: 0; font-size: 18px; } +.text-button { border: 0; color: var(--brand); background: transparent; } +.service-list { display: grid; gap: 10px; } +.service-card { display: grid; grid-template-columns: 92px 1fr; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius); background: white; } +.service-card img { width: 92px; height: 100%; min-height: 92px; object-fit: cover; } +.service-card div { padding: 11px; } +.service-card h3 { margin: 0 0 4px; font-size: 15px; } +.service-card p { margin: 0 0 8px; color: var(--muted); font-size: 12px; } +.price { color: var(--brand); font-size: 16px; font-weight: 800; } +.portfolio-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } +.portfolio-item { overflow: hidden; border-radius: var(--radius); background: #e8ece9; } +.portfolio-item img { width: 100%; aspect-ratio: 4/3; display: block; object-fit: cover; } +.portfolio-item div { padding: 8px; } +.portfolio-item strong, .portfolio-item span { display: block; } +.portfolio-item span { color: var(--muted); font-size: 11px; } +.quote { padding: 12px; margin-bottom: 8px; border-left: 3px solid var(--green); background: #f4f7f5; } +.quote p { margin: 0 0 5px; } +.quote span { color: var(--muted); font-size: 12px; } +.form-page { padding: 0 16px 28px; } +.form-section { padding: 16px 0; border-bottom: 1px solid #edf0ee; } +.form-section h2 { margin: 0 0 12px; font-size: 15px; } +.field { display: grid; gap: 5px; margin-bottom: 12px; } +.field label { font-size: 12px; font-weight: 700; } +.field input, .field select, .field textarea { width: 100%; min-height: 42px; padding: 9px 10px; border: 1px solid #c8cfca; border-radius: 6px; background: white; } +.field textarea { min-height: 76px; resize: vertical; } +.segmented { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; } +.segmented button { min-height: 40px; border: 1px solid var(--line); border-radius: 6px; background: white; } +.segmented button.active { color: var(--brand); border-color: var(--brand); background: #fff5f3; } +.qty-row { display: grid; grid-template-columns: 1fr 92px; align-items: center; gap: 8px; padding: 10px 0; border-bottom: 1px solid #edf0ee; } +.stepper { display: grid; grid-template-columns: 28px 32px 28px; align-items: center; text-align: center; } +.stepper button { width: 28px; height: 28px; border: 1px solid var(--line); background: white; } +.summary-box { padding: 12px; margin: 14px 0; background: #f7f3e8; border: 1px solid #eadcad; border-radius: 6px; } +.summary-box strong { float: right; color: var(--brand); font-size: 18px; } +.helper { color: var(--muted); font-size: 12px; } +.order-card { padding: 14px; margin-bottom: 10px; border: 1px solid var(--line); border-radius: var(--radius); background: white; } +.order-card-head { display: flex; justify-content: space-between; gap: 10px; } +.order-card h3 { margin: 5px 0; font-size: 15px; } +.order-card p { margin: 4px 0; color: var(--muted); font-size: 12px; } +.status { width: max-content; padding: 3px 7px; border-radius: 4px; color: #614900; background: #fff1b7; font-size: 11px; font-weight: 700; } +.status.contacting { color: #254d64; background: #e6f2f8; } +.status.confirmed { color: #24513a; background: #e4f3e9; } +.status.completed { color: #4c5350; background: #e9eeeb; } +.status.cancelled { color: #7c2b27; background: #f8e8e6; } +.empty-state { padding: 36px 20px; text-align: center; color: var(--muted); } +.admin-app { width: min(1400px, calc(100vw - 40px)); height: calc(100vh - 104px); min-height: 650px; display: grid; grid-template-columns: 220px 1fr; overflow: hidden; border: 1px solid #cbd2ce; background: white; } +.sidebar { color: #e9edea; background: #26312b; } +.admin-brand { height: 68px; display: flex; align-items: center; gap: 10px; padding: 0 18px; border-bottom: 1px solid #3c4841; } +.admin-brand span { width: 32px; height: 32px; display: grid; place-items: center; color: #222; background: var(--yellow); border-radius: 6px; font-weight: 800; } +.sidebar nav { padding: 12px 8px; } +.sidebar nav button { width: 100%; min-height: 42px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border: 0; border-radius: 5px; color: #cdd5d0; background: transparent; text-align: left; } +.sidebar nav button.active { color: white; background: #3b4941; } +.sidebar nav b { min-width: 20px; padding: 1px 6px; color: white; background: var(--brand); border-radius: 10px; text-align: center; font-size: 11px; } +.admin-main { min-width: 0; overflow: auto; background: #f5f7f5; } +.admin-topbar { height: 68px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; border-bottom: 1px solid var(--line); background: white; } +.quiet-button { border: 0; color: var(--muted); background: transparent; } +.admin-page { padding: 24px; } +.admin-page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; } +.admin-page-header h1 { margin: 0; font-size: 24px; } +.metric-grid { display: grid; grid-template-columns: repeat(3, minmax(160px, 1fr)); gap: 12px; margin-bottom: 20px; } +.metric { padding: 18px; border: 1px solid var(--line); border-radius: var(--radius); background: white; } +.metric span { color: var(--muted); } +.metric strong { display: block; margin-top: 7px; font-size: 28px; } +.panel { border: 1px solid var(--line); border-radius: var(--radius); background: white; } +.panel-header { padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); } +.panel-header h2 { margin: 0; font-size: 16px; } +.filter-row { display: flex; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); } +.filter-row button { min-height: 32px; padding: 0 10px; border: 1px solid var(--line); border-radius: 5px; background: white; } +.filter-row button.active { color: white; border-color: var(--green); background: var(--green); } +.order-table { width: 100%; border-collapse: collapse; } +.order-table th, .order-table td { padding: 12px 14px; border-bottom: 1px solid #edf0ee; text-align: left; vertical-align: top; } +.order-table th { color: var(--muted); background: #fafbfa; font-size: 12px; } +.order-table tbody tr { cursor: pointer; } +.order-table tbody tr:hover { background: #fafcfb; } +.unread-dot { width: 7px; height: 7px; display: inline-block; margin-right: 6px; border-radius: 50%; background: var(--brand); } +.tag { display: inline-block; padding: 2px 6px; margin: 2px; color: #34423a; background: #e8eeea; border-radius: 4px; font-size: 11px; } +.detail-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(260px, .8fr); gap: 16px; } +.detail-section { padding: 16px; border-bottom: 1px solid var(--line); } +.detail-section:last-child { border-bottom: 0; } +.detail-section h3 { margin: 0 0 12px; font-size: 15px; } +.key-value { display: grid; grid-template-columns: 110px 1fr; gap: 8px; margin: 7px 0; } +.key-value span { color: var(--muted); } +.tag-picker { display: flex; flex-wrap: wrap; gap: 6px; } +.tag-picker button { padding: 5px 8px; border: 1px solid var(--line); border-radius: 4px; background: white; } +.tag-picker button.active { color: #24513a; border-color: #86aa93; background: #e4f3e9; } +.toast { position: fixed; left: 50%; bottom: 24px; z-index: 20; padding: 10px 14px; color: white; background: #202522; border-radius: 6px; opacity: 0; pointer-events: none; transform: translate(-50%, 10px); transition: .2s; } +.toast.show { opacity: 1; transform: translate(-50%, 0); } +.hidden { display: none !important; } + +@media (max-width: 800px) { + .prototype-bar { height: auto; min-height: 72px; padding: 10px 12px; gap: 8px; } + .prototype-bar span { display: none; } + .mode-button { padding: 7px 9px; } + .mode-panel { min-height: calc(100vh - 72px); } + .phone-shell { height: calc(100vh - 72px); min-height: 560px; border: 0; border-radius: 0; } + .admin-app { width: 100vw; height: calc(100vh - 72px); min-height: 560px; grid-template-columns: 76px 1fr; border: 0; } + .admin-brand { justify-content: center; padding: 0; } + .admin-brand strong { display: none; } + .sidebar nav button { justify-content: center; padding: 8px 4px; font-size: 11px; text-align: center; } + .sidebar nav b { display: none; } + .admin-page { padding: 14px; } + .metric-grid { grid-template-columns: 1fr; } + .detail-grid { grid-template-columns: 1fr; } + .order-table th:nth-child(3), .order-table td:nth-child(3), .order-table th:nth-child(5), .order-table td:nth-child(5) { display: none; } +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..fd67055 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..701a104 --- /dev/null +++ b/turbo.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**"] }, + "dev": { "cache": false, "persistent": true }, + "lint": { "dependsOn": ["^lint"], "outputs": [] }, + "typecheck": { "dependsOn": ["^typecheck"], "outputs": [] }, + "clean": { "cache": false } + } +}