diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1faa033..cf2fa62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,51 @@ jobs: if-no-files-found: error retention-days: 7 + public-conversion-e2e: + name: Public conversion Playwright E2E + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 11.9.0 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Playwright Chromium + run: pnpm exec playwright install --with-deps chromium + + - name: Run public conversion acceptance tests + run: >- + pnpm exec playwright test + e2e/playwright/public-conversion.spec.js + --grep-invert @visual + + - name: Upload Playwright failure artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: international-chinese-platform-public-conversion-e2e-failure + path: | + playwright-report + test-results + if-no-files-found: warn + retention-days: 7 + e2e: name: Cross-role browser E2E runs-on: ubuntu-latest diff --git a/README.en.md b/README.en.md index 1fae69a..02b410a 100644 --- a/README.en.md +++ b/README.en.md @@ -15,6 +15,8 @@ · Learning journeys · + Public experience + · Quick start · Architecture @@ -112,6 +114,38 @@ flowchart TB | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Discover verified teachers and published courses; request lessons; submit work; practice persistent Chinese dialogue; read notifications and grades. | Maintain a professional profile; submit courses; manage appointments; join classrooms; publish, close, and grade assignments. | Verify teachers; review courses; inspect aggregated data, audit activity, and platform-level workflow state. | + + +## Public discovery and student conversion + +The public beta no longer treats sign-in as a stranger's first experience. The homepage explains the product first, and visitors can browse verified teachers and published courses returned by the real API without an account. Authentication is requested only when a visitor is ready to book. After a student signs in or registers, the application restores the original teacher page and booking intent. + +```mermaid +flowchart LR + Home["Public homepage / "] --> Teachers["Teacher directory /teachers"] + Home --> Courses["Course directory /courses"] + Teachers --> Teacher["Teacher detail /teachers/:teacherId"] + Courses --> Course["Course detail /courses/:courseId"] + Teacher -->|"Request a lesson"| Auth["Sign in or register /login"] + Auth -->|"student + validated same-origin redirect"| Booking["Return to teacher and continue booking"] + Auth -->|"teacher or administrator"| Workspace["Open the role's workspace"] +``` + +| Route | Authentication | Purpose | +| -------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| / | No | Bilingual value proposition, verified teachers, and featured courses. Teacher and course resources fail independently instead of blocking the homepage. | +| /teachers, /teachers/:teacherId | No | Browse active, verified teachers. Search, specialty, minimum rating, and page state remain in the URL. | +| /courses, /courses/:courseId | No | Browse published courses taught by verified teachers. Search, category, and page state remain in the URL. | +| /login?mode=login\|register&redirect=... | No | Semantic sign-in or three-step registration. A student returns to a validated booking path after authentication. | +| /legal/terms, /legal/privacy | No | Versioned public-beta terms and privacy notices. | +| /account-recovery, /403, /404 | No | A real support route plus explicit access-denied and not-found states. | + +### Locale, redirect, and booking semantics + +- Locale priority: use the visitor's saved localStorage preference first; otherwise inspect the browser's preferred language. Chinese-language browsers start in Chinese and all others start in English. Switching locale also updates html lang and page metadata. +- Safe return: redirect accepts only same-origin paths beginning with one /. Protocol-relative, backslash, cross-origin, and login-loop destinations are rejected. Only a student resumes a booking target; teachers and administrators enter their own workspace with a role explanation. +- Booking is not a purchase: displayed prices are references only and the beta does not collect payment. A request becomes an appointment only after the teacher confirms it. On a time conflict, the form preserves input and lets the student choose again instead of creating a duplicate request or pretending to know the teacher's live availability. + ## Quick local start @@ -199,8 +233,11 @@ pnpm db:migrate # apply pending numbered PostgreSQL migrations pnpm db:seed # import idempotent demo data outside production pnpm db:reset # rebuild local development data; drops the local public schema pnpm admin:bootstrap # create the first production administrator once +pnpm test:unit # Vitest locale selection, message catalog, and client unit tests pnpm test:api # Node API, database, and security tests -pnpm check # ESLint + Prettier + API tests + production build +pnpm test:e2e # Playwright public discovery, auth, booking, accessibility, and responsive checks +python e2e/test_workflows.py # Python Playwright cross-role workflows against a production build +pnpm check # ESLint + Prettier + unit/API tests + production build pnpm backup:create # after production backup setup, create an encrypted PostgreSQL backup pnpm backup:restore # after production restore setup, restore only with explicit CONFIRM_RESTORE ``` @@ -258,23 +295,30 @@ For local development, Vite, Fastify, PostgreSQL, and MinIO replace Vercel, Rail ### What CI actually proves -pnpm check runs ESLint, Prettier, the complete Node API/database/security suite, and a Vite production build. The repository currently contains 71 Node tests. GitHub Actions also starts isolated PostgreSQL and MinIO services, builds the production app, and exercises four cross-role browser journeys: +pnpm check runs ESLint, Prettier, Vitest unit tests, the complete Node API/database/security suite, and a Vite production build. The repository currently contains 71 Node API tests. GitHub Actions adds two complementary browser gates: + +- pnpm test:e2e -- e2e/playwright/public-conversion.spec.js --grep-invert @visual verifies the public homepage, anonymous teacher and course discovery, booking-intent recovery after authentication, recoverable 409 conflicts, registration drafts that never persist passwords or verification codes, keyboard and touch targets, axe results, and horizontal-overflow behavior across breakpoints. +- python e2e/test_workflows.py runs against PostgreSQL, MinIO, and the production build to verify four cross-role workflows: 1. Course submission → rejection → revision → approval → student catalog visibility. 2. Student request → teacher acceptance → dedicated classroom → classroom completion. 3. Teacher publishes → student submits → teacher grades → student sees feedback. 4. Administrator revokes verification → student cannot discover or book → administrator reapproves → teacher becomes public again. -To reproduce browser E2E locally, install e2e/requirements.txt and Playwright Chromium, start the service with the same PostgreSQL, MinIO, and production environment used by the [CI workflow](./.github/workflows/ci.yml), then run python e2e/test_workflows.py. See the [Node tests](./server/test) and [browser E2E](./e2e/test_workflows.py). +The public-conversion suite starts Vite through its Playwright configuration; run pnpm test:e2e:install once to install Chromium. To reproduce the cross-role suite, install e2e/requirements.txt and Python Playwright Chromium, then start the service with the same PostgreSQL, MinIO, and production environment used by the [CI workflow](./.github/workflows/ci.yml). See the [Node tests](./server/test), [public-conversion E2E](./e2e/playwright/public-conversion.spec.js), and [cross-role E2E](./e2e/test_workflows.py). + +### Front-end performance and experience budgets + +For the public beta, these are release gates rather than aspirational clean-up items: initial JavaScript must remain below 200 KiB gzip, and critical above-the-fold resources below 800 KiB. Mobile Lighthouse targets are LCP below 2.5 seconds, CLS below 0.1, and INP below 200 milliseconds. The interface is checked at 360, 390, 768, 1024, 1280, and 1440 px without page-level horizontal scrolling. Core controls provide at least a 44 × 44 px touch target and respect prefers-reduced-motion. ## Deployment, operations, and beta scope The recommended public-beta topology is Vercel for the Vue SPA and mail-relay function; Railway for one Fastify API instance, PostgreSQL, and an independent backup Cron; and Cloudflare R2 for private files and encrypted backups. Secrets, R2 CORS, migrations, administrator bootstrap, monitoring, restore drills, and rollback are documented in the [operations manual](./docs/operations.md). -| A good fit today | Not in the current scope | -| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Course projects, portfolio demonstrations, small self-hosted teaching collaboration, and public-beta validation | Payments, formal enrollment and seat deduction, recording, external certificate OCR/verification, paid AI, multi-instance real-time fan-out, and high availability | -| Classrooms that can connect directly or have a configured TURN service | Reliable audio/video behind strict NAT, because TURN is not bundled yet | +| A good fit today | Not in the current scope | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Course projects, portfolio demonstrations, small self-hosted teaching collaboration, and public-beta validation | Payments, formal enrollment and seat deduction, recording, real teacher-certificate upload and external authenticity checking, paid AI, multi-instance real-time fan-out, and high availability | +| Classrooms that can connect directly or have a configured TURN service | Reliable audio/video behind strict NAT, because TURN is not bundled yet | Course price and capacity are informational only; they are not a payment or seat-allocation system. Until a formal enrollment model exists, assignments for published courses are visible to and submittable by every signed-in student; there is no roster or enrollment authorization layer yet. Before the system can scale horizontally, classroom presence needs cross-instance broadcast and coordination. diff --git a/README.md b/README.md index e6ea0af..9637d04 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ · 教学闭环 · + 公开体验 + · 快速开始 · 系统架构 @@ -112,6 +114,38 @@ flowchart TB | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------ | | 发现已认证教师和已发布课程;预约课堂;提交作业;练习持久化中文对话;查看通知与评分。 | 维护专业资料;提交课程审核;处理预约;参与课堂;发布、关闭和评分作业。 | 核验教师;审核课程;查看聚合指标、审计活动与平台级工作状态。 | + + +## 公开发现与学生转化路径 + +公开 Beta 不再把登录页当作陌生访客的第一站。首页先解释平台价值,访客无需账户即可查看真实 API 返回的已认证教师与已发布课程;当访客准备预约时,平台才要求登录或注册,并在学生身份验证成功后恢复原来的教师详情与预约意图。 + +```mermaid +flowchart LR + Home["公开首页 / "] --> Teachers["教师目录 /teachers"] + Home --> Courses["课程目录 /courses"] + Teachers --> Teacher["教师详情 /teachers/:teacherId"] + Courses --> Course["课程详情 /courses/:courseId"] + Teacher -->|"发起预约"| Auth["登录或注册 /login"] + Auth -->|"学生 + 已校验同源 redirect"| Booking["返回教师详情并继续预约"] + Auth -->|"教师或管理员"| Workspace["进入对应角色工作区"] +``` + +| 路由 | 是否需要登录 | 用途 | +| -------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------- | +| / | 否 | 双语价值主张、认证教师与精选课程;教师和课程资源独立加载,单个 API 失败不会拖垮整个首页。 | +| /teachers/teachers/:teacherId | 否 | 浏览已启用、已认证教师;搜索、专长、最低评分和分页条件保存在 URL 中。 | +| /courses/courses/:courseId | 否 | 浏览已发布且教师已认证的课程;搜索、分类和分页条件保存在 URL 中。 | +| /login?mode=login\|register&redirect=... | 否 | 登录或三步注册;学生登录后恢复经过校验的预约路径。 | +| /legal/terms/legal/privacy | 否 | 版本化 Beta 条款与隐私说明。 | +| /account-recovery/403/404 | 否 | 真实支持入口,以及明确的权限不足和资源未找到状态。 | + +### 语言、跳转与预约语义 + +- 语言选择:优先使用保存在 localStorage 的用户选择;没有历史选择时读取浏览器首选语言,中文环境使用中文,其余环境默认英文。切换语言会同步更新 html lang 与页面元数据。 +- 安全返回redirect 只接受以单个 / 开头的同源站内路径,拒绝协议相对地址、反斜杠、跨域目标和再次跳回登录页,避免开放重定向。只有学生身份会恢复预约目标;教师和管理员进入各自工作区并收到角色说明。 +- 预约不是购买:参考价格仅供沟通,当前不会收取费用;预约必须由教师确认后才成立。发生时段冲突时,表单保留用户输入并允许重新选择,而不是产生重复请求或虚构可用时段。 + ## 快速本地启动 @@ -199,8 +233,11 @@ pnpm db:migrate # 应用尚未执行的 PostgreSQL 编号迁移 pnpm db:seed # 导入幂等演示数据(非生产) pnpm db:reset # 重建本地开发数据库(会删除本地 public schema) pnpm admin:bootstrap # 一次性创建首个生产管理员 -pnpm test:api # Node API / 数据库 / 安全测试 -pnpm check # ESLint + Prettier + API 测试 + 生产构建 +pnpm test:unit # Vitest:语言选择、文案目录与前端单元测试 +pnpm test:api # Node:API / PostgreSQL / 安全集成测试 +pnpm test:e2e # Playwright:公开发现、Auth、预约、可访问性与响应式验收 +python e2e/test_workflows.py # Python Playwright:四条跨角色生产构建工作流 +pnpm check # ESLint + Prettier + unit/API 测试 + 生产构建 pnpm backup:create # 生产备份配置完成后,创建加密 PostgreSQL 备份 pnpm backup:restore # 生产恢复配置完成后,受 CONFIRM_RESTORE 保护地恢复 ``` @@ -258,23 +295,30 @@ flowchart LR ### CI 实际验证什么 -pnpm check 会运行 ESLint、Prettier、完整 Node API/数据库/安全测试与 Vite 生产构建。本仓库当前包含 71 个 Node 测试;GitHub Actions 还会启动隔离 PostgreSQL 和 MinIO,构建生产应用,并在真实浏览器中验证四条跨角色流程: +pnpm check 会运行 ESLint、Prettier、Vitest 单元测试、完整 Node API/数据库/安全测试与 Vite 生产构建。本仓库当前包含 71 个 Node API 测试;GitHub Actions 另设两套互补的浏览器门禁: + +- pnpm test:e2e -- e2e/playwright/public-conversion.spec.js --grep-invert @visual 验证公开首页、匿名教师/课程发现、登录后预约意图恢复、409 冲突恢复、注册草稿不保存密码或验证码、键盘/触控目标、axe 与多断点无横向滚动。 +- python e2e/test_workflows.py 在 PostgreSQL、MinIO 和生产构建上验证以下四条跨角色业务流程: 1. 课程“提交 → 驳回 → 修改重提 → 批准 → 学生目录可见”。 2. 预约“学生申请 → 教师接受 → 专属课堂 → 学生完成课堂”。 3. 作业“教师发布 → 学生提交 → 教师评分 → 学生查看反馈”。 4. 教师认证“管理员撤销 → 学生无法发现/预约 → 管理员重新批准 → 重新公开”。 -若要本地复现浏览器 E2E,请先安装 e2e/requirements.txt 与 Playwright Chromium,以与 [CI 工作流](./.github/workflows/ci.yml) 相同的 PostgreSQL、MinIO 和生产环境变量启动服务,再运行 python e2e/test_workflows.py。查看 [Node 测试](./server/test) 与 [浏览器 E2E](./e2e/test_workflows.py)。 +公开转化测试会由 Playwright 配置自动启动 Vite;第一次运行前执行 pnpm test:e2e:install 安装 Chromium。跨角色测试需要先安装 e2e/requirements.txt 与 Python Playwright Chromium,并以与 [CI 工作流](./.github/workflows/ci.yml) 相同的 PostgreSQL、MinIO 和生产环境变量启动服务。查看 [Node 测试](./server/test)、[公开转化 E2E](./e2e/playwright/public-conversion.spec.js) 与 [跨角色 E2E](./e2e/test_workflows.py)。 + +### 前端性能与体验预算 + +公开 Beta 将以下指标作为发布门槛,而不是事后优化建议:初始 JavaScript gzip 小于 200 KiB,关键首屏资源小于 800 KiB;移动端 Lighthouse 目标为 LCP 小于 2.5 秒、CLS 小于 0.1、INP 小于 200 毫秒。支持 360、390、768、1024、1280 与 1440 px 宽度,页面不应出现整体横向滚动;核心交互触控目标至少为 44 × 44 px,并尊重 prefers-reduced-motion。 ## 部署、运维与适用范围 推荐的公开 Beta 生产拓扑为:Vercel 托管 Vue 单页应用与邮件中继函数;Railway 托管一个 Fastify API 实例、PostgreSQL 与独立备份 Cron;Cloudflare R2 保存私有文件和加密备份。完整的 Secret、R2 CORS、迁移、管理员初始化、监控、恢复演练和回滚步骤在 [运维手册](./docs/operations.md)。 -| 现在适合 | 当前未覆盖 | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| 课程项目、作品集展示、小规模自部署教学协作、公开 Beta 验证 | 支付结算、正式入学与席位扣减、课堂录制、外部证书 OCR/真实性核验、付费 AI、多实例实时广播、高可用 | -| 可直连或已配置 TURN 的课堂环境 | 严格 NAT 下的可靠音视频(尚未内置 TURN) | +| 现在适合 | 当前未覆盖 | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| 课程项目、作品集展示、小规模自部署教学协作、公开 Beta 验证 | 支付结算、正式入学与席位扣减、课堂录制、教师证书真实文件上传与外部真实性核验、付费 AI、多实例实时广播、高可用 | +| 可直连或已配置 TURN 的课堂环境 | 严格 NAT 下的可靠音视频(尚未内置 TURN) | 价格和容量目前是课程信息,并不代表支付或正式席位占用。在正式选课模型上线前,已发布课程的作业对所有登录学生可见并可提交;当前没有 roster 或 enrollment 权限层。扩展为多实例前,需要先为课堂成员状态引入跨实例广播与协调层。 diff --git a/e2e/playwright/public-conversion.spec.js b/e2e/playwright/public-conversion.spec.js new file mode 100644 index 0000000..1682c4a --- /dev/null +++ b/e2e/playwright/public-conversion.spec.js @@ -0,0 +1,1072 @@ +import AxeBuilder from '@axe-core/playwright' +import { expect, test } from '@playwright/test' + +test.use({ browserName: 'chromium' }) + +const teacher = { + id: '00000000-0000-4000-8000-000000000002', + displayName: 'Li Mei', + title: 'Practical Chinese teacher', + school: 'Shanghai Language Institute', + location: 'Shanghai, China', + verified: true, + bio: 'I help adult learners use Chinese confidently in everyday situations.', + specialties: ['Conversation', 'HSK preparation'], + languages: ['Chinese', 'English'], + teachingStyle: ['Scenario practice', 'Clear feedback'], + experienceYears: 8, + publishedCourseCount: 1, + hourlyRateCents: 18800, + rating: 4.9 +} + +const course = { + id: '00000000-0000-4000-8000-000000000101', + title: 'Chinese for Everyday Life', + summary: 'Build practical speaking confidence for daily life in China.', + description: + 'Learn useful language for introductions, shopping, transport and social conversations.', + category: 'Conversation', + level: 'elementary', + durationMinutes: 60, + capacity: 6, + priceCents: 12800, + teacher: { + id: teacher.id, + displayName: teacher.displayName, + title: teacher.title, + school: teacher.school, + verified: true + } +} + +const teacherDetail = { + ...teacher, + courses: [{ ...course, teacher: undefined }] +} + +const studentProfile = { + id: '00000000-0000-4000-8000-000000000001', + role: 'student', + email: 'learner@example.com', + displayName: 'Alex Morgan', + chineseLevel: 'HSK3', + mustResetPassword: false +} + +const appointment = { + id: '00000000-0000-4000-8000-000000000201', + status: 'accepted', + topic: 'Everyday conversation practice', + scheduledStart: '2026-08-12T02:00:00.000Z', + scheduledEnd: '2026-08-12T03:00:00.000Z', + durationMinutes: 60, + teacher, + classroom: { + id: '00000000-0000-4000-8000-000000000301' + } +} + +const assignment = { + id: '00000000-0000-4000-8000-000000000401', + courseId: course.id, + title: 'Introduce your neighbourhood', + dueAt: '2026-08-15T12:00:00.000Z', + status: 'published', + submission: null +} + +const json = (data, status = 200) => ({ + status, + contentType: 'application/json', + body: JSON.stringify(data) +}) + +async function mockPublicApi( + page, + { + authenticated = false, + sessionDelayMs = 0, + conflictOnce = false, + includeStudentWorkspace = false, + cataloguePages = 1 + } = {} +) { + const state = { + authenticated, + appointmentRequests: [], + appointmentAttempts: 0 + } + + await page.route('**/api/v1/**', async (route) => { + const request = route.request() + const { pathname } = new URL(request.url()) + const method = request.method() + + if (pathname === '/api/v1/auth/session') { + if (sessionDelayMs > 0) { + await new Promise((resolve) => setTimeout(resolve, sessionDelayMs)) + } + if (state.authenticated) { + await route.fulfill(json({ code: 0, data: studentProfile })) + } else { + await route.fulfill( + json({ code: 401, msg: 'Authentication required' }, 401) + ) + } + return + } + + if (pathname === '/api/v1/auth/login' && method === 'POST') { + state.authenticated = true + await route.fulfill(json({ code: 0, data: studentProfile })) + return + } + + if (pathname === '/api/v1/appointments' && method === 'POST') { + state.appointmentAttempts += 1 + state.appointmentRequests.push(request.postDataJSON()) + if (conflictOnce && state.appointmentAttempts === 1) { + await route.fulfill( + json({ code: 409, msg: 'The requested time conflicts' }, 409) + ) + return + } + await route.fulfill( + json( + { + code: 0, + data: { + ...appointment, + status: 'pending', + classroom: null, + ...request.postDataJSON() + } + }, + 201 + ) + ) + return + } + + if (pathname === '/api/v1/appointments' && method === 'GET') { + await route.fulfill( + json({ + code: 0, + data: { + items: includeStudentWorkspace ? [appointment] : [], + pagination: { + page: 1, + pageSize: 50, + total: includeStudentWorkspace ? 1 : 0, + totalPages: includeStudentWorkspace ? 1 : 0 + } + } + }) + ) + return + } + + if (pathname === '/api/v1/notifications' && method === 'GET') { + await route.fulfill( + json({ + code: 0, + data: { + items: [], + unread: 0, + pagination: { page: 1, pageSize: 1, total: 0, totalPages: 0 } + } + }) + ) + return + } + + if (pathname === '/api/v1/teachers') { + await route.fulfill( + json({ + code: 200, + data: { + items: [teacher], + pagination: { + page: 1, + pageSize: 9, + total: cataloguePages === 1 ? 1 : cataloguePages * 9, + totalPages: cataloguePages + } + } + }) + ) + return + } + + if (pathname === `/api/v1/teachers/${teacher.id}`) { + await route.fulfill(json({ code: 200, data: teacherDetail })) + return + } + + if (pathname === '/api/v1/courses') { + await route.fulfill( + json({ + code: 200, + data: { + items: [course], + pagination: { + page: 1, + pageSize: 9, + total: cataloguePages === 1 ? 1 : cataloguePages * 9, + totalPages: cataloguePages + } + } + }) + ) + return + } + + if (pathname === `/api/v1/courses/${course.id}`) { + await route.fulfill(json({ code: 200, data: course })) + return + } + + if (pathname === `/api/v1/courses/${course.id}/assignments`) { + await route.fulfill( + json({ + code: 0, + data: { + items: includeStudentWorkspace ? [assignment] : [], + pagination: { + page: 1, + pageSize: 20, + total: includeStudentWorkspace ? 1 : 0, + totalPages: includeStudentWorkspace ? 1 : 0 + } + } + }) + ) + return + } + + await route.fulfill(json({ code: 404, msg: 'Not found' }, 404)) + }) + + return state +} + +async function mockAnonymousPublicApi(page) { + return mockPublicApi(page) +} + +async function useEnglish(page) { + await page.addInitScript(() => { + if (window.localStorage.getItem('lang') === null) { + window.localStorage.setItem('lang', 'en') + } + }) +} + +async function preparePublicPage(page) { + await mockAnonymousPublicApi(page) + await useEnglish(page) +} + +async function resetPublicApi(page, options = {}) { + await page.unroute('**/api/v1/**') + return mockPublicApi(page, options) +} + +async function expectNoHighImpactA11yViolations(page) { + await page.addStyleTag({ + content: + '*, *::before, *::after { animation: none !important; transition: none !important; }' + }) + await page.evaluate( + () => + new Promise((resolve) => + window.requestAnimationFrame(() => + window.requestAnimationFrame(resolve) + ) + ) + ) + + const result = await new AxeBuilder({ page }) + .include('#app') + .exclude('.vue-devtools__anchor-btn') + .analyze() + const highImpact = result.violations.filter(({ impact }) => + ['serious', 'critical'].includes(impact) + ) + expect(highImpact, JSON.stringify(highImpact, null, 2)).toEqual([]) +} + +function addDaysToLocalDateTime(value, days) { + const date = new Date(value) + date.setDate(date.getDate() + days) + const part = (number) => String(number).padStart(2, '0') + return [ + date.getFullYear(), + '-', + part(date.getMonth() + 1), + '-', + part(date.getDate()), + 'T', + part(date.getHours()), + ':', + part(date.getMinutes()) + ].join('') +} + +async function expectMinimumTouchTarget(locator, label) { + await expect(locator, label + ' should be visible').toBeVisible() + await locator.evaluate(async (element) => { + const animationRoot = element.closest('[role="dialog"]') || element + await Promise.allSettled( + animationRoot + .getAnimations({ subtree: true }) + .map((animation) => animation.finished) + ) + await new Promise((resolve) => + window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)) + ) + }) + const box = await locator.boundingBox() + expect(box, label + ' should have a rendered box').not.toBeNull() + expect(box.width, label + ' width').toBeGreaterThanOrEqual(44) + expect(box.height, label + ' height').toBeGreaterThanOrEqual(44) +} +async function expectVisualBaseline(page, name, width) { + await page.locator('.vue-devtools__anchor-btn').evaluateAll((elements) => + elements.forEach((element) => { + element.style.display = 'none' + }) + ) + + await expect(page).toHaveScreenshot(name + '-' + width + '.png', { + animations: 'disabled', + caret: 'hide', + fullPage: true, + maxDiffPixelRatio: 0.02 + }) +} +async function expectNoHorizontalOverflow(page) { + const dimensions = await page.evaluate(() => ({ + bodyClientWidth: document.body.clientWidth, + bodyScrollWidth: document.body.scrollWidth, + rootClientWidth: document.documentElement.clientWidth, + rootScrollWidth: document.documentElement.scrollWidth + })) + + expect( + dimensions.rootScrollWidth, + `document overflow: ${JSON.stringify(dimensions)}` + ).toBeLessThanOrEqual(dimensions.rootClientWidth + 1) + expect( + dimensions.bodyScrollWidth, + `body overflow: ${JSON.stringify(dimensions)}` + ).toBeLessThanOrEqual(dimensions.bodyClientWidth + 1) +} + +test.beforeEach(async ({ page }) => { + await preparePublicPage(page) +}) + +test('public homepage explains the product and renders independent catalogue data', async ({ + page +}) => { + await page.goto('/') + + await expect( + page.getByRole('heading', { + level: 1, + name: 'Learn Chinese for real life, with teachers you can trust.' + }) + ).toBeVisible() + await expect( + page.getByRole('heading', { name: teacher.displayName }) + ).toBeVisible() + await expect(page.getByRole('heading', { name: course.title })).toBeVisible() + await expect( + page.getByRole('link', { name: 'Find a teacher' }).first() + ).toHaveAttribute('href', '/teachers') +}) + +test('teacher filters live in the URL and a public profile opens without authentication', async ({ + page +}) => { + await page.goto('/teachers') + await expect( + page.getByRole('heading', { + level: 1, + name: 'Find a teacher you can trust' + }) + ).toBeVisible() + + await page.getByRole('searchbox', { name: 'Search teachers' }).fill('Li') + await page.getByRole('textbox', { name: 'Speciality' }).fill('Conversation') + await page + .getByRole('combobox', { name: 'Minimum rating' }) + .selectOption('4.5') + await page.getByRole('button', { name: 'Apply filters' }).click() + + await expect(page).toHaveURL( + /\/teachers\?q=Li&specialty=Conversation&minRating=4\.5$/ + ) + await page + .getByRole('link', { name: `View ${teacher.displayName}'s profile` }) + .click() + + await expect(page).toHaveURL(`/teachers/${teacher.id}`) + await expect( + page.getByRole('heading', { level: 1, name: teacher.displayName }) + ).toBeVisible() + await expect(page.getByText('Verified teacher').first()).toBeVisible() +}) + +test('course filters live in the URL and a published course opens publicly', async ({ + page +}) => { + await page.goto('/courses') + await expect( + page.getByRole('heading', { + level: 1, + name: 'Choose a course for the Chinese you need' + }) + ).toBeVisible() + + await page.getByRole('searchbox', { name: 'Search courses' }).fill('Everyday') + await page.locator('input[name="category"]').fill('Conversation') + await page.getByRole('button', { name: 'Apply filters' }).click() + await expect(page).toHaveURL(/\/courses\?q=Everyday&category=Conversation$/) + + await page + .getByRole('link', { name: course.title, exact: true }) + .first() + .click() + await expect(page).toHaveURL(`/courses/${course.id}`) + await expect( + page.getByRole('heading', { level: 1, name: course.title }) + ).toBeVisible() + await expect( + page.getByText(/does not process course purchases/) + ).toBeVisible() +}) + +test('language choice updates the document and survives a reload', async ({ + page +}) => { + await page.goto('/') + await page.getByRole('button', { name: 'ZH' }).click() + + await expect(page.locator('html')).toHaveAttribute('lang', 'zh-CN') + await expect( + page.getByRole('heading', { + level: 1, + name: '和可信赖的老师一起,把中文学到真实场景里。' + }) + ).toBeVisible() + await expect + .poll(() => page.evaluate(() => localStorage.getItem('lang'))) + .toBe('zh') + + await page.reload() + await expect(page.locator('html')).toHaveAttribute('lang', 'zh-CN') + await expect( + page.getByRole('heading', { + level: 1, + name: '和可信赖的老师一起,把中文学到真实场景里。' + }) + ).toBeVisible() +}) + +test('an anonymous lesson request preserves a validated booking intent', async ({ + page +}) => { + await page.goto(`/teachers/${teacher.id}`) + await page + .getByRole('button', { name: 'Sign in to request a lesson' }) + .click() + + await expect(page).toHaveURL(/\/login\?/) + const destination = new URL(page.url()) + expect(destination.searchParams.get('mode')).toBe('login') + expect(destination.searchParams.get('redirect')).toBe( + `/teachers/${teacher.id}?intent=book` + ) +}) + +test('slow session recovery never blocks the public value proposition', async ({ + page +}) => { + await resetPublicApi(page, { sessionDelayMs: 3000 }) + const sessionResponse = page.waitForResponse( + (response) => new URL(response.url()).pathname === '/api/v1/auth/session' + ) + + await page.goto('/', { waitUntil: 'domcontentloaded' }) + await expect( + page.getByRole('heading', { + level: 1, + name: 'Learn Chinese for real life, with teachers you can trust.' + }) + ).toBeVisible({ timeout: 1500 }) + + await sessionResponse +}) + +test('student sign-in restores the booking intent and submits the request', async ({ + page +}) => { + const state = await resetPublicApi(page) + await page.goto(`/teachers/${teacher.id}`) + await page + .getByRole('button', { name: 'Sign in to request a lesson' }) + .click() + + await page.getByLabel('Email address').fill(studentProfile.email) + await page.locator('#login-password').fill('Correct-horse-123') + await page + .getByRole('main') + .getByRole('button', { name: 'Sign in', exact: true }) + .click() + + const dialog = page.getByRole('dialog', { name: 'Request a lesson' }) + await expect(dialog).toBeVisible() + await expect(page).toHaveURL(`/teachers/${teacher.id}`) + await dialog.getByLabel('Learning topic').fill('Ordering food politely') + await dialog + .getByLabel('Message to the teacher (optional)') + .fill('Please help me practise a restaurant conversation.') + await dialog.getByRole('button', { name: 'Send request' }).click() + + await expect( + page.getByRole('dialog', { name: 'Lesson request sent' }) + ).toBeVisible() + expect(state.appointmentRequests).toHaveLength(1) + expect(state.appointmentRequests[0]).toMatchObject({ + teacherId: teacher.id, + topic: 'Ordering food politely', + durationMinutes: 60 + }) + expect( + Number.isFinite(Date.parse(state.appointmentRequests[0].scheduledStart)) + ).toBe(true) +}) + +test('a 409 booking conflict stays recoverable and can be resubmitted', async ({ + page +}) => { + const state = await resetPublicApi(page, { + authenticated: true, + conflictOnce: true + }) + await page.goto(`/teachers/${teacher.id}`) + const trigger = page.getByRole('button', { name: 'Request a lesson' }) + await expect(trigger).toBeVisible() + await trigger.click() + + const dialog = page.getByRole('dialog', { name: 'Request a lesson' }) + const start = dialog.getByLabel('Preferred start time') + await dialog.getByRole('button', { name: 'Send request' }).click() + await expect(dialog.getByRole('alert')).toContainText( + 'That time is no longer available' + ) + await expect(start).toBeEnabled() + + await start.fill(addDaysToLocalDateTime(await start.inputValue(), 1)) + await dialog.getByRole('button', { name: 'Send request' }).click() + await expect( + page.getByRole('dialog', { name: 'Lesson request sent' }) + ).toBeVisible() + expect(state.appointmentAttempts).toBe(2) +}) + +test('Auth and the booking dialog pass axe and preserve keyboard focus', async ({ + page +}) => { + await page.goto('/login') + await expect(page.getByRole('heading', { level: 1 })).toBeVisible() + await expectNoHighImpactA11yViolations(page) + + await page.goto('/login?mode=register') + await expect( + page.getByRole('heading', { + level: 2, + name: 'Start with a learning profile that fits you' + }) + ).toBeVisible() + await expectNoHighImpactA11yViolations(page) + + await resetPublicApi(page, { authenticated: true }) + await page.goto(`/teachers/${teacher.id}`) + const trigger = page.getByRole('button', { name: 'Request a lesson' }) + await trigger.click() + const dialog = page.getByRole('dialog', { name: 'Request a lesson' }) + await expect(dialog).toBeVisible() + await expect(dialog.getByLabel('Course')).toBeFocused() + await expectNoHighImpactA11yViolations(page) + + await page.keyboard.press('Escape') + await expect(dialog).toBeHidden() + await expect(trigger).toBeFocused() +}) + +test('unknown routes show an explicit 404 instead of returning to sign-in', async ({ + page +}) => { + await page.goto('/this-page-does-not-exist') + + await expect(page).toHaveURL(/\/404\?from=/) + await expect(page.locator('.system-state__code')).toHaveText('404') + await expect(page.getByRole('heading', { level: 1 })).toBeVisible() +}) + +test('core public navigation and filtering work from the keyboard', async ({ + page +}, testInfo) => { + await page.goto('/') + + for (let attempt = 0; attempt < 6; attempt += 1) { + await page.keyboard.press('Tab') + const skipIsFocused = await page + .locator('.public-shell__skip') + .evaluate((element) => document.activeElement === element) + if (skipIsFocused) break + } + await expect(page.locator('.public-shell__skip')).toBeFocused() + await page.keyboard.press('Enter') + await expect(page).toHaveURL(/#public-main$/) + + const mobile = testInfo.project.name === 'mobile-chromium' + if (mobile) { + const menuButton = page.getByRole('button', { + name: 'Toggle navigation menu' + }) + await menuButton.focus() + await page.keyboard.press('Enter') + await expect(menuButton).toHaveAttribute('aria-expanded', 'true') + } + + const teachersLink = page + .getByRole('navigation', { + name: mobile ? 'Mobile navigation' : 'Primary navigation' + }) + .getByRole('link', { name: 'Teachers' }) + await teachersLink.focus() + await page.keyboard.press('Enter') + await expect(page).toHaveURL('/teachers') + + const search = page.getByRole('searchbox', { name: 'Search teachers' }) + await search.focus() + await page.keyboard.type('HSK') + await page.keyboard.press('Enter') + await expect(page).toHaveURL(/\/teachers\?q=HSK$/) +}) + +test('public pages have no serious or critical automated accessibility violations', async ({ + page +}) => { + const routes = [ + '/', + '/teachers', + `/teachers/${teacher.id}`, + '/courses', + `/courses/${course.id}`, + '/404' + ] + const problems = [] + + for (const path of routes) { + await page.goto(path) + await expect(page.getByRole('heading', { level: 1 })).toBeVisible() + + const result = await new AxeBuilder({ page }) + .include('#app') + .exclude('.vue-devtools__anchor-btn') + .analyze() + const highImpact = result.violations.filter(({ impact }) => + ['serious', 'critical'].includes(impact) + ) + + problems.push( + ...highImpact.map(({ id, impact, nodes }) => ({ + path, + id, + impact, + targets: nodes.map(({ target }) => target) + })) + ) + } + + expect(problems, JSON.stringify(problems, null, 2)).toEqual([]) +}) + +test('key public and Auth routes never overflow the supported viewport matrix', async ({ + page +}, testInfo) => { + test.skip( + testInfo.project.name !== 'desktop-chromium', + 'A single Chromium project covers the CSS breakpoint matrix.' + ) + test.setTimeout(120_000) + + const widths = [360, 768, 1024, 1280] + const publicRoutes = [ + '/', + '/teachers', + '/teachers/' + teacher.id, + '/courses', + '/courses/' + course.id, + '/login', + '/login?mode=register', + '/404' + ] + + for (const width of widths) { + await page.setViewportSize({ + width, + height: width <= 768 ? 900 : 1000 + }) + await resetPublicApi(page) + + for (const path of publicRoutes) { + await page.goto(path) + await expect(page.getByRole('heading', { level: 1 })).toBeVisible() + await expectNoHorizontalOverflow(page) + } + + await resetPublicApi(page, { + authenticated: true, + includeStudentWorkspace: true + }) + await page.goto('/student/home') + await expect( + page.getByRole('heading', { level: 1, name: /Good to see you/ }) + ).toBeVisible() + await expectNoHorizontalOverflow(page) + } +}) + +test('registration draft survives reload without persisting secrets', async ({ + page +}, testInfo) => { + test.skip( + testInfo.project.name !== 'desktop-chromium', + 'Registration persistence only needs one browser implementation.' + ) + + await page.goto('/login?mode=register') + await page.evaluate(() => { + window.sessionStorage.removeItem('gcep-registration-draft-v2') + }) + await page.reload() + + await page.getByRole('radio', { name: /^Student/ }).check() + await page.getByLabel('Email address').fill('learner@example.com') + await page.getByLabel('Verification code').fill('123456') + await page.locator('#register-password').fill('Correct-horse-123') + await page.getByLabel('Confirm password').fill('Correct-horse-123') + await page + .getByRole('checkbox', { + name: /I agree to the Terms of Service and Privacy Notice/ + }) + .check() + await page.getByRole('button', { name: 'Continue' }).click() + + await page.getByLabel('Full name').fill('Alex Morgan') + await page.getByLabel('Nationality').fill('Canadian') + await page.getByLabel('Country or region').fill('Shanghai, China') + await page.locator('#register-age').fill('24') + await page.getByRole('button', { name: 'Continue' }).click() + + await page.getByText('HSK 3', { exact: true }).click() + await page + .getByLabel('What would you like to do in Chinese?') + .fill('Speak confidently with colleagues and neighbours.') + + await expect + .poll(() => + page.evaluate(() => { + const value = window.sessionStorage.getItem( + 'gcep-registration-draft-v2' + ) + return value ? JSON.parse(value) : null + }) + ) + .toMatchObject({ + currentStep: 3, + role: 'student', + member: { + name: 'Alex Morgan', + nationality: 'Canadian', + region: 'Shanghai, China', + age: '24' + }, + studentLevel: 'HSK3', + learningGoal: 'Speak confidently with colleagues and neighbours.' + }) + + const storedDraft = await page.evaluate(() => + JSON.parse( + window.sessionStorage.getItem('gcep-registration-draft-v2') || '{}' + ) + ) + expect(storedDraft).not.toHaveProperty('password') + expect(storedDraft).not.toHaveProperty('rePassword') + expect(storedDraft).not.toHaveProperty('code') + expect(JSON.stringify(storedDraft)).not.toContain('Correct-horse-123') + expect(JSON.stringify(storedDraft)).not.toContain('123456') + + await page.reload() + await expect( + page.getByRole('heading', { + level: 3, + name: 'Shape your learning path' + }) + ).toBeVisible() + await expect(page.getByRole('radio', { name: /^HSK 3/ })).toBeChecked() + await expect( + page.getByLabel('What would you like to do in Chinese?') + ).toHaveValue('Speak confidently with colleagues and neighbours.') + + await page.getByRole('button', { name: 'Back' }).click() + await expect(page.getByLabel('Full name')).toHaveValue('Alex Morgan') + await expect(page.getByLabel('Nationality')).toHaveValue('Canadian') + await expect(page.getByLabel('Country or region')).toHaveValue( + 'Shanghai, China' + ) + await expect(page.locator('#register-age')).toHaveValue('24') +}) + +test('critical mobile interactions provide at least 44px touch targets', async ({ + page +}, testInfo) => { + test.skip( + testInfo.project.name !== 'desktop-chromium', + 'One Chromium project exercises the 390px CSS viewport.' + ) + test.setTimeout(90_000) + await page.setViewportSize({ width: 390, height: 844 }) + + await page.goto('/') + await expectMinimumTouchTarget( + page.getByRole('button', { name: 'ZH', exact: true }), + 'public language switch' + ) + await expectMinimumTouchTarget( + page.getByRole('button', { name: 'Toggle navigation menu' }), + 'public navigation menu' + ) + await expectMinimumTouchTarget( + page.getByRole('link', { name: 'Find a teacher' }).first(), + 'homepage primary CTA' + ) + + await page.goto('/login') + const authModeNavigation = page.getByRole('navigation', { + name: 'Sign-in and registration' + }) + await expectMinimumTouchTarget( + authModeNavigation.getByRole('button', { name: 'Sign in' }), + 'Auth sign-in mode' + ) + await expectMinimumTouchTarget( + authModeNavigation.getByRole('button', { name: 'Register' }), + 'Auth register mode' + ) + await expectMinimumTouchTarget( + page + .getByRole('main') + .getByRole('button', { name: 'Sign in', exact: true }), + 'Auth submit' + ) + await expectMinimumTouchTarget( + page.getByRole('link', { name: 'Need help signing in?' }), + 'Auth recovery link' + ) + + await resetPublicApi(page, { cataloguePages: 2 }) + await page.goto('/teachers') + for (const [locator, label] of [ + [ + page.getByRole('searchbox', { name: 'Search teachers' }), + 'teacher search' + ], + [page.getByRole('textbox', { name: 'Speciality' }), 'teacher speciality'], + [page.getByRole('combobox', { name: 'Minimum rating' }), 'teacher rating'], + [ + page.getByRole('button', { name: 'Apply filters' }), + 'apply teacher filters' + ], + [ + page.getByRole('button', { name: 'Clear filters' }), + 'clear teacher filters' + ], + [ + page + .getByRole('navigation', { name: 'Pagination' }) + .getByRole('button', { name: 'Next' }), + 'teacher pagination' + ] + ]) { + await expectMinimumTouchTarget(locator, label) + } + + await page.goto('/courses') + for (const [locator, label] of [ + [page.getByRole('searchbox', { name: 'Search courses' }), 'course search'], + [page.getByRole('combobox', { name: 'Category' }), 'course category'], + [ + page.getByRole('button', { name: 'Apply filters' }), + 'apply course filters' + ], + [ + page.getByRole('button', { name: 'Clear filters' }), + 'clear course filters' + ], + [ + page + .getByRole('navigation', { name: 'Pagination' }) + .getByRole('button', { name: 'Next' }), + 'course pagination' + ] + ]) { + await expectMinimumTouchTarget(locator, label) + } + + await resetPublicApi(page, { authenticated: true }) + await page.goto('/teachers/' + teacher.id) + const trigger = page.getByRole('button', { name: 'Request a lesson' }) + await expectMinimumTouchTarget(trigger, 'booking trigger') + await trigger.click() + + const dialog = page.getByRole('dialog', { name: 'Request a lesson' }) + for (const [locator, label] of [ + [dialog.getByLabel('Course'), 'booking course'], + [dialog.getByLabel('Learning topic'), 'booking topic'], + [dialog.getByLabel('Preferred start time'), 'booking start'], + [dialog.getByLabel('Message to the teacher (optional)'), 'booking message'], + [dialog.getByText('60 minutes', { exact: true }), 'booking duration'], + [dialog.getByRole('button', { name: 'Cancel' }), 'booking cancel'], + [dialog.getByRole('button', { name: 'Send request' }), 'booking submit'] + ]) { + await expectMinimumTouchTarget(locator, label) + } + + // Scope intentionally excludes inline Terms/Privacy prose links. WCAG 2.2 + // permits inline targets when adequate target spacing is provided. +}) +test('conversion journey visual baselines @visual', async ({ + page +}, testInfo) => { + test.skip( + testInfo.project.name !== 'desktop-chromium', + 'One deterministic browser owns the visual reference images.' + ) + test.setTimeout(180_000) + await page.clock.setFixedTime(new Date('2026-08-09T08:00:00.000Z')) + + for (const width of [390, 1440]) { + await page.setViewportSize({ + width, + height: width === 390 ? 844 : 1000 + }) + await resetPublicApi(page) + + await page.goto('/') + await expect( + page.getByRole('heading', { + level: 1, + name: 'Learn Chinese for real life, with teachers you can trust.' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'homepage', width) + + await page.goto('/login') + await expect( + page.getByRole('heading', { + level: 2, + name: 'Continue your learning' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'login', width) + + await page.evaluate(() => { + window.sessionStorage.removeItem('gcep-registration-draft-v2') + }) + await page.goto('/login?mode=register') + await expect( + page.getByRole('heading', { + level: 3, + name: 'Secure your account' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'register-account', width) + + await page.getByRole('radio', { name: /^Student/ }).check() + await page.getByLabel('Email address').fill('learner@example.com') + await page.getByLabel('Verification code').fill('123456') + await page.locator('#register-password').fill('Correct-horse-123') + await page.getByLabel('Confirm password').fill('Correct-horse-123') + await page + .getByRole('checkbox', { + name: /I agree to the Terms of Service and Privacy Notice/ + }) + .check() + await page.getByRole('button', { name: 'Continue' }).click() + + await expect( + page.getByRole('heading', { + level: 3, + name: 'Tell us who you are' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'register-profile', width) + + await page.getByLabel('Full name').fill('Alex Morgan') + await page.getByLabel('Nationality').fill('Canadian') + await page.getByLabel('Country or region').fill('Shanghai, China') + await page.locator('#register-age').fill('24') + await page.getByRole('button', { name: 'Continue' }).click() + + await expect( + page.getByRole('heading', { + level: 3, + name: 'Shape your learning path' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'register-learning', width) + + await page.goto('/teachers') + await expect( + page.getByRole('heading', { + level: 1, + name: 'Find a teacher you can trust' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'teachers', width) + + await page.goto('/teachers/' + teacher.id) + await expect( + page.getByRole('heading', { level: 1, name: teacher.displayName }) + ).toBeVisible() + await expectVisualBaseline(page, 'teacher-detail', width) + + await page.goto('/courses') + await expect( + page.getByRole('heading', { + level: 1, + name: 'Choose a course for the Chinese you need' + }) + ).toBeVisible() + await expectVisualBaseline(page, 'courses', width) + + await resetPublicApi(page, { + authenticated: true, + includeStudentWorkspace: true + }) + await page.goto('/student/home') + await expect( + page.getByRole('heading', { level: 1, name: /Good to see you/ }) + ).toBeVisible() + await expect(page.locator('article.lesson-card')).toBeVisible() + await expectVisualBaseline(page, 'student-home', width) + } +}) diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/courses-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/courses-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..a7ea643 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/courses-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/courses-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/courses-390-desktop-chromium-win32.png new file mode 100644 index 0000000..d6c1ebf Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/courses-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/homepage-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/homepage-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..d68145f Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/homepage-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/homepage-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/homepage-390-desktop-chromium-win32.png new file mode 100644 index 0000000..37b7f2c Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/homepage-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/login-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/login-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..18de8ae Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/login-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/login-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/login-390-desktop-chromium-win32.png new file mode 100644 index 0000000..9c3c00b Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/login-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-account-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-account-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..cecd5aa Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-account-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-account-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-account-390-desktop-chromium-win32.png new file mode 100644 index 0000000..eb44b51 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-account-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..dcea758 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-390-desktop-chromium-win32.png new file mode 100644 index 0000000..9f3c19d Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-learning-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..7abf2cf Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-390-desktop-chromium-win32.png new file mode 100644 index 0000000..5bf3b47 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/register-profile-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/student-home-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/student-home-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..29bab86 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/student-home-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/student-home-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/student-home-390-desktop-chromium-win32.png new file mode 100644 index 0000000..5423589 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/student-home-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..9e72021 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-390-desktop-chromium-win32.png new file mode 100644 index 0000000..9d93e07 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/teacher-detail-390-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/teachers-1440-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/teachers-1440-desktop-chromium-win32.png new file mode 100644 index 0000000..b43a858 Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/teachers-1440-desktop-chromium-win32.png differ diff --git a/e2e/playwright/public-conversion.spec.js-snapshots/teachers-390-desktop-chromium-win32.png b/e2e/playwright/public-conversion.spec.js-snapshots/teachers-390-desktop-chromium-win32.png new file mode 100644 index 0000000..a6e993c Binary files /dev/null and b/e2e/playwright/public-conversion.spec.js-snapshots/teachers-390-desktop-chromium-win32.png differ diff --git a/e2e/test_workflows.py b/e2e/test_workflows.py index 46ae37f..2fb94e4 100644 --- a/e2e/test_workflows.py +++ b/e2e/test_workflows.py @@ -143,14 +143,17 @@ def open_page(page: Page, path: str) -> None: def login_through_ui(page: Page, role: str) -> dict[str, Any]: - email, role_label, expected_path = ACCOUNTS[role] + email, _legacy_role_label, expected_path = ACCOUNTS[role] step(f"logging in {role} through the UI") open_page(page, "/login") - page.locator("main").get_by_text(role_label, exact=True).click() - page.locator("#email").fill(email) - page.locator("#password").fill(DEMO_PASSWORD) - page.locator("main input[type='checkbox']").check() + role_input = page.locator(f'input[name="role"][value="{role}"]') + page.locator( + f'label.role-option:has(input[name="role"][value="{role}"])' + ).click() + expect(role_input).to_be_checked() + page.locator("#login-email").fill(email) + page.locator("#login-password").fill(DEMO_PASSWORD) page.locator("main").get_by_role( "button", name="登录", exact=True ).click() @@ -186,6 +189,9 @@ def __init__(self, playwright: Playwright) -> None: timezone_id="Asia/Shanghai", ) context.tracing.start(screenshots=True, snapshots=True, sources=True) + context.add_init_script( + "window.localStorage.setItem('lang', 'zh')" + ) page = context.new_page() page.set_default_timeout(20_000) page.on( @@ -410,7 +416,7 @@ def appointment_classroom_workflow( step("appointment: student sees the booking and enters through the UI") open_page(student_page, "/student/home") - student_card = student_page.locator(".appointment-list article").filter( + student_card = student_page.locator("article.lesson-card").filter( has_text=topic ) expect(student_card).to_be_visible() @@ -620,9 +626,7 @@ def restore_verified_state() -> None: "the revoked teacher" ) open_page(student_page, "/student/order") - student_search = student_page.get_by_placeholder( - "搜索教师、学校或教学方向" - ) + student_search = student_page.locator('input[name="teacher-search"]') student_search.fill(teacher_display_name) student_search.press("Enter") hidden_card = student_page.locator("article.teacher-card").filter( @@ -631,7 +635,7 @@ def restore_verified_state() -> None: expect(hidden_card).to_have_count(0) expect( student_page.get_by_text( - "没有找到符合条件的教师", exact=True + "没有符合条件的教师", exact=True ) ).to_be_visible() @@ -640,7 +644,7 @@ def restore_verified_state() -> None: f"/student/teacherDetail?teacherId={teacher_id}", ) expect( - student_page.get_by_text("教师资料无法加载", exact=True) + student_page.get_by_text("没有找到教师资料", exact=True) ).to_be_visible() booking_response = student.fetch( @@ -691,9 +695,7 @@ def restore_verified_state() -> None: step("teacher verification: student discovers the teacher again") open_page(student_page, "/student/order") - student_search = student_page.get_by_placeholder( - "搜索教师、学校或教学方向" - ) + student_search = student_page.locator('input[name="teacher-search"]') student_search.fill(teacher_display_name) student_search.press("Enter") restored_card = student_page.locator("article.teacher-card").filter( @@ -701,7 +703,7 @@ def restore_verified_state() -> None: ) expect(restored_card).to_be_visible() expect( - restored_card.get_by_text("已认证", exact=True) + restored_card.get_by_text("认证教师", exact=True) ).to_be_visible() finally: restore_verified_state() diff --git a/eslint.config.js b/eslint.config.js index 28c64dc..75b7ec8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,6 +15,8 @@ export default [ '**/dist/**', '**/dist-ssr/**', '**/coverage/**', + '**/playwright-report/**', + '**/test-results/**', 'postcss.config.cjs', 'tailwind.config.cjs' ] @@ -50,7 +52,7 @@ export default [ semi: false, printWidth: 80, trailingComma: 'none', - endOfLine: 'auto' + endOfLine: 'lf' } ], 'vue/multi-word-component-names': [ diff --git a/index.html b/index.html index daef0b6..0ff8481 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,27 @@ - + - - 国际中文教育平台 + + + + + + + International Chinese Education Platform
diff --git a/package.json b/package.json index da497d1..9b75064 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,15 @@ "backup:restore": "node --env-file-if-exists=.env --env-file-if-exists=.env.local server/ops/restore.js", "test": "pnpm test:api", "test:api": "node --test \"server/test/*.test.js\"", + "test:unit": "vitest run", + "test:unit:watch": "vitest", + "test:e2e": "playwright test", + "test:e2e:install": "playwright install chromium", "lint": "eslint . --fix", "lint:check": "eslint .", - "format": "prettier --write src/ server/ docs/ README.md README.en.md .github/", - "format:check": "prettier --check src/ server/ docs/ README.md README.en.md .github/", - "check": "pnpm lint:check && pnpm format:check && pnpm test:api && pnpm build" + "format": "prettier --write src/ server/ docs/ e2e/ test/ README.md README.en.md .github/ index.html package.json eslint.config.js vite.config.js vitest.config.js playwright.config.js vercel.json", + "format:check": "prettier --check src/ server/ docs/ e2e/ test/ README.md README.en.md .github/ index.html package.json eslint.config.js vite.config.js vitest.config.js playwright.config.js vercel.json", + "check": "pnpm lint:check && pnpm format:check && pnpm test:unit && pnpm test:api && pnpm build" }, "dependencies": { "@aws-sdk/client-s3": "^3.1090.0", @@ -42,7 +46,7 @@ "@fastify/helmet": "^13.1.0", "@fastify/multipart": "^10.1.0", "@fastify/rate-limit": "^11.1.0", - "@fastify/static": "^10.1.0", + "@fastify/static": "^10.1.3", "@fastify/websocket": "^11.3.0", "axios": "^1.8.4", "element-plus": "^2.9.5", @@ -58,20 +62,27 @@ "zod": "^4.4.3" }, "devDependencies": { + "@axe-core/playwright": "^4.12.1", "@eslint/js": "^9.18.0", + "@playwright/test": "^1.62.1", "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-prettier": "^10.1.0", + "@vue/test-utils": "^2.4.11", "autoprefixer": "^10.4.20", "concurrently": "^10.0.3", "eslint": "^9.20.1", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-vue": "^9.32.0", - "postcss": "^8.5.2", + "jsdom": "^30.0.1", + "postcss": "^8.5.23", "prettier": "^3.5.1", "sass": "^1.85.0", "tailwindcss": "~3.0.24", + "unplugin-auto-import": "^21.1.0", + "unplugin-vue-components": "^32.1.0", "vite": "^6.0.11", "vite-plugin-vue-devtools": "^7.7.1", + "vitest": "^4.1.10", "yaml": "^2.4.2" } } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..f4f5259 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,43 @@ +import { defineConfig, devices } from '@playwright/test' +import process from 'node:process' + +export default defineConfig({ + testDir: './e2e/playwright', + timeout: 30_000, + expect: { + timeout: 8_000 + }, + fullyParallel: true, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 2 : 0, + reporter: [['list'], ['html', { open: 'never' }]], + use: { + baseURL: 'http://127.0.0.1:5173', + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + reducedMotion: 'reduce' + }, + projects: [ + { + name: 'desktop-chromium', + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1440, height: 1000 } + } + }, + { + name: 'mobile-chromium', + use: { + ...devices['iPhone 13'], + viewport: { width: 390, height: 844 } + } + } + ], + webServer: { + command: 'pnpm dev:web --host 127.0.0.1', + url: 'http://127.0.0.1:5173', + reuseExistingServer: !process.env.CI, + timeout: 120_000 + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef4b0d4..0e6cd8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: dependencies: '@aws-sdk/client-s3': specifier: ^3.1090.0 - version: 3.1090.0 + version: 3.1106.0 '@aws-sdk/s3-request-presigner': specifier: ^3.1090.0 - version: 3.1090.0 + version: 3.1106.0 '@element-plus/icons-vue': specifier: ^2.3.1 - version: 2.3.2(vue@3.5.40) + version: 2.3.2(vue@3.5.41) '@fastify/cookie': specifier: ^11.1.2 version: 11.1.2 @@ -31,92 +31,113 @@ importers: version: 10.1.0 '@fastify/rate-limit': specifier: ^11.1.0 - version: 11.1.0 + version: 11.2.0 '@fastify/static': - specifier: ^10.1.0 - version: 10.1.0 + specifier: ^10.1.3 + version: 10.1.3 '@fastify/websocket': specifier: ^11.3.0 version: 11.3.0 axios: specifier: ^1.8.4 - version: 1.18.1 + version: 1.19.0 element-plus: specifier: ^2.9.5 - version: 2.14.3(vue@3.5.40) + version: 2.14.4(vue@3.5.41) fastify: specifier: ^5.10.0 - version: 5.10.0 + version: 5.11.3 nodemailer: specifier: ^9.0.3 - version: 9.0.3 + version: 9.0.5 pg: specifier: ^8.22.0 - version: 8.22.0 + version: 8.23.0 pinia: specifier: ^2.3.1 - version: 2.3.1(vue@3.5.40) + version: 2.3.1(vue@3.5.41) pinia-plugin-persistedstate: specifier: 4.2.0 - version: 4.2.0(pinia@2.3.1(vue@3.5.40)) + version: 4.2.0(pinia@2.3.1(vue@3.5.41)) vue: specifier: ^3.5.13 - version: 3.5.40 + version: 3.5.41 vue-i18n: specifier: ^11.4.6 - version: 11.4.6(vue@3.5.40) + version: 11.4.8(vue@3.5.41) vue-router: specifier: ^4.5.0 - version: 4.6.4(vue@3.5.40) + version: 4.6.4(vue@3.5.41) ws: specifier: ^8.21.1 - version: 8.21.1 + version: 8.21.3 zod: specifier: ^4.4.3 version: 4.4.3 devDependencies: + '@axe-core/playwright': + specifier: ^4.12.1 + version: 4.12.1(playwright-core@1.62.1) '@eslint/js': specifier: ^9.18.0 version: 9.39.5 + '@playwright/test': + specifier: ^1.62.1 + version: 1.62.1 '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.1(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40) + version: 5.2.1(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) '@vue/eslint-config-prettier': specifier: ^10.1.0 - version: 10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.5) + version: 10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6) + '@vue/test-utils': + specifier: ^2.4.11 + version: 2.4.11(@vue/compiler-dom@3.5.41)(@vue/server-renderer@3.5.41)(vue@3.5.41) autoprefixer: specifier: ^10.4.20 - version: 10.5.4(postcss@8.5.19) + version: 10.5.4(postcss@8.5.26) concurrently: specifier: ^10.0.3 - version: 10.0.3 + version: 10.0.4 eslint: specifier: ^9.20.1 version: 9.39.5(jiti@2.7.0) eslint-plugin-prettier: specifier: ^5.2.3 - version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.5) + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6) eslint-plugin-vue: specifier: ^9.32.0 version: 9.32.0(eslint@9.39.5(jiti@2.7.0)) + jsdom: + specifier: ^30.0.1 + version: 30.0.1 postcss: - specifier: ^8.5.2 - version: 8.5.19 + specifier: ^8.5.23 + version: 8.5.26 prettier: specifier: ^3.5.1 - version: 3.9.5 + version: 3.9.6 sass: specifier: ^1.85.0 - version: 1.101.0 + version: 1.102.0 tailwindcss: specifier: ~3.0.24 - version: 3.0.24(postcss@8.5.19) + version: 3.0.24(postcss@8.5.26) + unplugin-auto-import: + specifier: ^21.1.0 + version: 21.1.0(@vueuse/core@14.3.0(vue@3.5.41))(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + unplugin-vue-components: + specifier: ^32.1.0 + version: 32.1.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) vite: specifier: ^6.0.11 - version: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) + version: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) vite-plugin-vue-devtools: specifier: ^7.7.1 - version: 7.7.10(rollup@4.62.2)(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40) + version: 7.7.10(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) + vitest: + specifier: ^4.1.10 + version: 4.1.10(jsdom@30.0.1)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) yaml: specifier: ^2.4.2 version: 2.9.0 @@ -126,82 +147,95 @@ packages: '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - '@aws-sdk/checksums@3.1000.18': - resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==} + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@aws-sdk/checksums@3.1000.26': + resolution: {integrity: sha512-CGznePoL+1oWCSzqmkvlYMpWQEZohPR3LntjKXXfVH+oh6Kd8d+yHLkjpiAGwPIHAxFe4OAq3aKfRnv/wqWIyA==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-s3@3.1090.0': - resolution: {integrity: sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==} + '@aws-sdk/client-s3@3.1106.0': + resolution: {integrity: sha512-hUTlnyRRGlVdfvJLL3hCEnMm7CmunSzc/lxFVRX8g1fjJTMUVbyQCfhfMhp7dZ7JBftLU6OYresu3Hje4nvkJw==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.975.3': - resolution: {integrity: sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==} + '@aws-sdk/core@3.977.6': + resolution: {integrity: sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.59': - resolution: {integrity: sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==} + '@aws-sdk/credential-provider-env@3.972.67': + resolution: {integrity: sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.61': - resolution: {integrity: sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==} + '@aws-sdk/credential-provider-http@3.972.69': + resolution: {integrity: sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.4': - resolution: {integrity: sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==} + '@aws-sdk/credential-provider-ini@3.973.12': + resolution: {integrity: sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.66': - resolution: {integrity: sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==} + '@aws-sdk/credential-provider-login@3.972.74': + resolution: {integrity: sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.70': - resolution: {integrity: sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==} + '@aws-sdk/credential-provider-node@3.972.78': + resolution: {integrity: sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.59': - resolution: {integrity: sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==} + '@aws-sdk/credential-provider-process@3.972.67': + resolution: {integrity: sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.3': - resolution: {integrity: sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==} + '@aws-sdk/credential-provider-sso@3.973.11': + resolution: {integrity: sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.65': - resolution: {integrity: sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==} + '@aws-sdk/credential-provider-web-identity@3.972.73': + resolution: {integrity: sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.64': - resolution: {integrity: sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==} + '@aws-sdk/middleware-sdk-s3@3.972.72': + resolution: {integrity: sha512-lSAoVPvQxX1d8TOM6waKDBQrvvZcm4w6pCldFAsRUffEaXq6lYY0pPyew3KlLu6Xqb74DXI42hGvSsbGBLljlw==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.33': - resolution: {integrity: sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==} + '@aws-sdk/nested-clients@3.997.41': + resolution: {integrity: sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/s3-request-presigner@3.1090.0': - resolution: {integrity: sha512-DaYXp8GMptWJUDbvpoB51xZztOw6tcbpXjYAZMXTu17E0aR+5g8FWBU3t3t0KEhhrUiKyfLpVi6uNQxNSXfbhg==} + '@aws-sdk/s3-request-presigner@3.1106.0': + resolution: {integrity: sha512-ZI5SCkyz8jB3Qr6NPSJ7R4A/PkniQvbD1DO8APwhMsubFcfwPSJMMUxrkv9k1E20ikRk7skpM9itZh+0wI9K/w==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.41': - resolution: {integrity: sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==} + '@aws-sdk/signature-v4-multi-region@3.996.43': + resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1088.0': - resolution: {integrity: sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==} + '@aws-sdk/token-providers@3.1103.0': + resolution: {integrity: sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.974.2': resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} engines: {node: '>=20.0.0'} - '@aws-sdk/xml-builder@3.972.36': - resolution: {integrity: sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==} + '@aws-sdk/xml-builder@3.972.37': + resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.3.0': resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} + '@axe-core/playwright@4.12.1': + resolution: {integrity: sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==} + peerDependencies: + playwright-core: '>= 1.0.0' + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -214,8 +248,8 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.29.7': @@ -284,8 +318,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true @@ -334,14 +368,54 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.10': + resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7': + resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@ctrl/tinycolor@4.2.0': resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} engines: {node: '>=14'} @@ -501,8 +575,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + '@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 @@ -539,11 +613,20 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fastify/accept-negotiator@2.0.1': - resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@fastify/accept-negotiator@2.1.0': + resolution: {integrity: sha512-F3EVbzWt+xcnVaOHmWyIlpuFtbxOln7HDZQsh09MtMmMm/CipMayNt8hnIL8VQi54u2ZociDbf+iluGYkf7B1A==} - '@fastify/ajv-compiler@4.0.5': - resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + '@fastify/ajv-compiler@4.0.6': + resolution: {integrity: sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==} '@fastify/busboy@3.2.0': resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} @@ -560,8 +643,8 @@ packages: '@fastify/fast-json-stringify-compiler@5.1.0': resolution: {integrity: sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==} - '@fastify/forwarded@3.0.1': - resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} + '@fastify/forwarded@3.0.2': + resolution: {integrity: sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==} '@fastify/helmet@13.1.0': resolution: {integrity: sha512-SvVOU0IrzYJW1BvSkfq9G1WUdW3dnaRUvg6m0BtgGMBmML62No0VmSu087jecH58SFbicbREgZTPJ89mAguupA==} @@ -575,14 +658,14 @@ packages: '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} - '@fastify/rate-limit@11.1.0': - resolution: {integrity: sha512-BeJ9tizLvmTXGD7deYU5G04OtHhwk5uHxbpEPVp09gKvUBIXmau/4Bshxhu9ci54MvVWfGjCEx4RzvsTntojwA==} + '@fastify/rate-limit@11.2.0': + resolution: {integrity: sha512-X7osJd4XSvMoejYrnJkSZYYjY1eNYoBqhjlzf1RakC2204qExFqZFTKj5+T7VuzA/iUI9Z3UoSqQRkB2HpG0oQ==} - '@fastify/send@4.1.0': - resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} + '@fastify/send@4.1.1': + resolution: {integrity: sha512-BYo+EiaKwlxH+WetGk6hAs1d39iP0y1gqB8lGF/qwkJ9ZZ/cBY1vx5NvExb9Sc3yRMFjD5X4Eyh4e4+TzRkzdw==} - '@fastify/static@10.1.0': - resolution: {integrity: sha512-iK/8TvRM/EgNOyQL+EpWu+x3aR6o4GWt+UI+27zmE7w6t/6d80mXqOtWLdEKQ13vL/g1Jry0ae2icj6GP7tGzA==} + '@fastify/static@10.1.3': + resolution: {integrity: sha512-W6jqajYS974XjPjB5hQWoxPM8NKM4+p8YmQT6G5IbCa4uhdWSVadZUv75siy1wEA/3ty8RYdpBydfWeu9AqAqQ==} '@fastify/websocket@11.3.0': resolution: {integrity: sha512-g89ag4BCcD9YP5wBZXixzoLnuf5j89p/sXFcfpCiv2pdEkYYukBEoK3heVzqsp0EAtszVDc2BBZG0KZqeAShIA==} @@ -616,20 +699,20 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@intlify/core-base@11.4.6': - resolution: {integrity: sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==} + '@intlify/core-base@11.4.8': + resolution: {integrity: sha512-A+Q7SKm5oEcy1E/cghqd7n/St4XjTqLhiiyDuieNcMrJcrHlkY5n0jp7Q9dD3txvVHzvsmBVV5M9wD5/s1zfzw==} engines: {node: '>= 22'} - '@intlify/devtools-types@11.4.6': - resolution: {integrity: sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==} + '@intlify/devtools-types@11.4.8': + resolution: {integrity: sha512-MGpID+rlfzGUbNcnC20bm5NMSBHPrvx0atLTfv9dftn3kjXw1hGKDcIcwrO99tSrZEc2i+hczRL7ks8qXsHPkQ==} engines: {node: '>= 22'} - '@intlify/message-compiler@11.4.6': - resolution: {integrity: sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==} + '@intlify/message-compiler@11.4.8': + resolution: {integrity: sha512-vbzk17dYwduYiv52EK61+FDCyhfVg1uPUtPmiD/d45W99uJIcXywrweOBcHv7n9/iEqmXiMGT52bgJbZDQqK3w==} engines: {node: '>= 22'} - '@intlify/shared@11.4.6': - resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==} + '@intlify/shared@11.4.8': + resolution: {integrity: sha512-XbRgrv+XEuvDr7UCY55oibVrh+o4u+A0VB6nSL0F5Z8LcZxE/8j573LYG6bCrOigIcHdGpSNI7Rh5UpC5/B/eg==} engines: {node: '>= 22'} '@isaacs/cliui@8.0.2': @@ -676,6 +759,13 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -688,96 +778,93 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nuxt/kit@3.21.8': - resolution: {integrity: sha512-kg63DUPY5AHPn+9XM7u8rYcdWHXjzwfUscgRDuiC5YUciQ+xdLRhdwXelYFxEAx2nxJHossliiQXbMm/Fleivw==} + '@nuxt/kit@3.21.11': + resolution: {integrity: sha512-0Xi3tgwN77w43Q8GCPIrvWmF1J7Peehkts44E0uKNIml9lB8WoUn8YxyUjxBv47XtVR86NWoWALAT+/IEMHJEA==} engines: {node: '>=18.12.0'} - '@parcel/watcher-android-arm64@2.5.6': - resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@parcel/watcher-android-arm64@2.6.0': + resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.5.6': - resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + '@parcel/watcher-darwin-arm64@2.6.0': + resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.5.6': - resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + '@parcel/watcher-darwin-x64@2.6.0': + resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.5.6': - resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + '@parcel/watcher-freebsd-x64@2.6.0': + resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.5.6': - resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + '@parcel/watcher-linux-arm-glibc@2.6.0': + resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] libc: [glibc] - '@parcel/watcher-linux-arm-musl@2.5.6': - resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + '@parcel/watcher-linux-arm-musl@2.6.0': + resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] libc: [musl] - '@parcel/watcher-linux-arm64-glibc@2.5.6': - resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + '@parcel/watcher-linux-arm64-glibc@2.6.0': + resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@parcel/watcher-linux-arm64-musl@2.5.6': - resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + '@parcel/watcher-linux-arm64-musl@2.6.0': + resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@parcel/watcher-linux-x64-glibc@2.5.6': - resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + '@parcel/watcher-linux-x64-glibc@2.6.0': + resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@parcel/watcher-linux-x64-musl@2.5.6': - resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + '@parcel/watcher-linux-x64-musl@2.6.0': + resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@parcel/watcher-win32-arm64@2.5.6': - resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + '@parcel/watcher-win32-arm64@2.6.0': + resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-ia32@2.5.6': - resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.6': - resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + '@parcel/watcher-win32-x64@2.6.0': + resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.5.6': - resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + '@parcel/watcher@2.6.0': + resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} engines: {node: '>= 10.0.0'} '@pinojs/redact@0.4.0': @@ -791,6 +878,11 @@ packages: resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} + hasBin: true + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -803,141 +895,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.62.2': - resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + '@rollup/rollup-android-arm-eabi@4.62.4': + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.2': - resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + '@rollup/rollup-android-arm64@4.62.4': + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.2': - resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + '@rollup/rollup-darwin-arm64@4.62.4': + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.2': - resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + '@rollup/rollup-darwin-x64@4.62.4': + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.2': - resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + '@rollup/rollup-freebsd-arm64@4.62.4': + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.2': - resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + '@rollup/rollup-freebsd-x64@4.62.4': + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.62.2': - resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.62.2': - resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + '@rollup/rollup-linux-arm64-gnu@4.62.4': + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.2': - resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + '@rollup/rollup-linux-arm64-musl@4.62.4': + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.2': - resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + '@rollup/rollup-linux-loong64-gnu@4.62.4': + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.62.2': - resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + '@rollup/rollup-linux-loong64-musl@4.62.4': + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.62.2': - resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.2': - resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + '@rollup/rollup-linux-ppc64-musl@4.62.4': + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.62.2': - resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.62.2': - resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + '@rollup/rollup-linux-riscv64-musl@4.62.4': + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.62.2': - resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + '@rollup/rollup-linux-s390x-gnu@4.62.4': + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.2': - resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + '@rollup/rollup-linux-x64-gnu@4.62.4': + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.2': - resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + '@rollup/rollup-linux-x64-musl@4.62.4': + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.2': - resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + '@rollup/rollup-openbsd-x64@4.62.4': + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.62.2': - resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + '@rollup/rollup-openharmony-arm64@4.62.4': + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.2': - resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + '@rollup/rollup-win32-arm64-msvc@4.62.4': + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.2': - resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + '@rollup/rollup-win32-ia32-msvc@4.62.4': + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.2': - resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + '@rollup/rollup-win32-x64-gnu@4.62.4': + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.2': - resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + '@rollup/rollup-win32-x64-msvc@4.62.4': + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} cpu: [x64] os: [win32] @@ -948,33 +1040,42 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@smithy/core@3.29.5': - resolution: {integrity: sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==} + '@smithy/core@3.31.1': + resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.4.10': - resolution: {integrity: sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==} + '@smithy/credential-provider-imds@4.4.16': + resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.6.7': - resolution: {integrity: sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==} + '@smithy/fetch-http-handler@5.6.13': + resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.9.7': - resolution: {integrity: sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==} + '@smithy/node-http-handler@4.9.13': + resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.6.6': - resolution: {integrity: sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==} + '@smithy/signature-v4@5.6.12': + resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==} engines: {node: '>=18.0.0'} '@smithy/types@4.16.1': resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} engines: {node: '>=18.0.0'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@sxzz/popperjs-es@2.11.8': resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -984,8 +1085,8 @@ packages: '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - '@types/lodash@4.17.24': - resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -997,6 +1098,35 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@vue/babel-helper-vue-transform-on@1.1.5': resolution: {integrity: sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==} @@ -1005,17 +1135,17 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@vue/compiler-core@3.5.40': - resolution: {integrity: sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==} + '@vue/compiler-core@3.5.41': + resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==} - '@vue/compiler-dom@3.5.40': - resolution: {integrity: sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==} + '@vue/compiler-dom@3.5.41': + resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==} - '@vue/compiler-sfc@3.5.40': - resolution: {integrity: sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==} + '@vue/compiler-sfc@3.5.41': + resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==} - '@vue/compiler-ssr@3.5.40': - resolution: {integrity: sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==} + '@vue/compiler-ssr@3.5.41': + resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==} '@vue/devtools-api@6.5.0': resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} @@ -1043,20 +1173,30 @@ packages: eslint: '>= 8.21.0' prettier: '>= 3.0.0' - '@vue/reactivity@3.5.40': - resolution: {integrity: sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==} + '@vue/reactivity@3.5.41': + resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==} - '@vue/runtime-core@3.5.40': - resolution: {integrity: sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==} + '@vue/runtime-core@3.5.41': + resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==} - '@vue/runtime-dom@3.5.40': - resolution: {integrity: sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==} + '@vue/runtime-dom@3.5.41': + resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==} - '@vue/server-renderer@3.5.40': - resolution: {integrity: sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==} + '@vue/server-renderer@3.5.41': + resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==} - '@vue/shared@3.5.40': - resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} + '@vue/shared@3.5.41': + resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==} + + '@vue/test-utils@2.4.11': + resolution: {integrity: sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==} + peerDependencies: + '@vue/compiler-dom': 3.x + '@vue/server-renderer': 3.x + vue: 3.x + peerDependenciesMeta: + '@vue/server-renderer': + optional: true '@vueuse/core@14.3.0': resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==} @@ -1071,6 +1211,9 @@ packages: peerDependencies: vue: ^3.5.0 + abbrev@1.0.3: + resolution: {integrity: sha512-s07HMJf6O5iTLVDx9cH7c9VbOdrmzxE+AzWz9CPi94zVNBQQA3jIwIZKTrHQj4dGR1T/MdwMnVJzSpjaVEXtXw==} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -1091,8 +1234,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -1122,12 +1265,20 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + ansi-styles@4.0.0: + resolution: {integrity: sha512-8zjUtFJ3db/QoPXuuEMloS2AUf79/yeyttJ7Abr3hteopJu9HK8vsgGviGUMq+zyA6cZZO6gAyZoMTF6TgaEjA==} + engines: {node: '>=8'} + + ansi-styles@4.1.0: + resolution: {integrity: sha512-Qts4KCLKG+waHc9C4m07weIY8qyeixoS0h6RnbsNVD6Fw+pEZGW3vTyObL3WXpE09Mq4Oi7/lBEyLmOiLtlYWQ==} engines: {node: '>=8'} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + ansi-styles@6.1.0: + resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} + engines: {node: '>=12'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} anymatch@3.1.3: @@ -1140,9 +1291,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} @@ -1164,8 +1315,12 @@ packages: avvio@9.3.0: resolution: {integrity: sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==} - axios@1.18.1: - resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + axe-core@4.12.1: + resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + engines: {node: '>=4'} + + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} balanced-match@1.0.0: resolution: {integrity: sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==} @@ -1174,59 +1329,46 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.43: - resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} engines: {node: '>=6.0.0'} hasBin: true - better-sqlite3@12.11.1: - resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - birpc@2.3.0: resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} - bl@4.0.3: - resolution: {integrity: sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - brace-expansion@2.1.2: - resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.6: - resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1255,8 +1397,12 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1274,9 +1420,6 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -1295,11 +1438,15 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@10.0.0: + resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} + engines: {node: '>=14'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@10.0.3: - resolution: {integrity: sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==} + concurrently@10.0.4: + resolution: {integrity: sha512-trZql+7l/0+WRAsAnEdctr4+iiOS6ZrViI6H8QWcCF9MFS/LT0dKpe8vluB1to6it+OxSI4VospFTIFMW8DJRw==} engines: {node: '>=22'} hasBin: true @@ -1309,6 +1456,9 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -1336,6 +1486,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1344,6 +1498,10 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} @@ -1356,13 +1514,8 @@ packages: supports-color: optional: true - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1432,11 +1585,16 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.393: - resolution: {integrity: sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==} + editorconfig@1.0.7: + resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} + engines: {node: '>=14'} + hasBin: true + + electron-to-chromium@1.5.403: + resolution: {integrity: sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==} - element-plus@2.14.3: - resolution: {integrity: sha512-pJcvxcpZjYruNzuJhAeVwnbYjfNgzBKnWHwSVEhwzM2/kcLI3brzmtIBxtPqd4hQWJfD1PRnjoc1WipLw2eBGg==} + element-plus@2.14.4: + resolution: {integrity: sha512-vMKR9tFcLeNrJgFXA3zhUn6YuRKUQW9d0btakBR8U1Iq8MfzkjMOGcgs5de2VgiLldHt69brmuBHpxc3bK4ZgQ==} peerDependencies: vue: ^3.3.7 @@ -1456,11 +1614,15 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + error-stack-parser-es@0.1.5: resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==} - errx@0.1.0: - resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + errx@0.1.2: + resolution: {integrity: sha512-chfpPHmCerdo/rXr/nNvPZRkV4WwDRwzwnsJ0Uzz3tVi8Z41tDctRjduYy1138ii77AFlts1qvWtX3g/Acg91Q==} es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} @@ -1470,6 +1632,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -1494,6 +1659,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -1558,11 +1727,6 @@ packages: resolution: {integrity: sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1589,12 +1753,12 @@ packages: resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} - exsolve@1.1.0: - resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -1621,17 +1785,17 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} - fast-uri@4.1.0: - resolution: {integrity: sha512-ZodJ2cRiLVWGi9IgPb3mbgSqM4CD3LexCHkuv0FfBXHJI1ADfucTD06m6clO2Cy5RZYsw/SiCVl/dyrFI/SYWA==} + fast-uri@4.1.2: + resolution: {integrity: sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==} fastify-plugin@6.0.0: resolution: {integrity: sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==} - fastify@5.10.0: - resolution: {integrity: sha512-A9L0ziuWGQHgEEVgF3davQ9vbD93IuX+lo2IsxapQmu5b/Y/ynn9m9K5JHt9dvyJXOFc5iN0Zk5GHEOqnzhWjg==} + fastify@5.11.3: + resolution: {integrity: sha512-W6hzDP8s0iSeL7LGwY6Oc/ZxuXWOvFEMs6p2L0Si415YRo27W5pBKdOTXxhemBDeSTAcpYf5evRA9onF2OYhPA==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1653,15 +1817,12 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-my-way@9.6.0: - resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} engines: {node: '>=20'} find-up@5.0.0: @@ -1672,8 +1833,8 @@ packages: resolution: {integrity: sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==} engines: {node: '>=16'} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} @@ -1695,13 +1856,18 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + fs-extra@11.4.0: + resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} engines: {node: '>=14.14'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1734,13 +1900,10 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - giget@3.3.0: - resolution: {integrity: sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==} + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} hasBin: true - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1758,6 +1921,11 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -1796,6 +1964,10 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -1812,9 +1984,6 @@ packages: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} @@ -1834,14 +2003,22 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ipaddr.js@2.4.0: - resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + ip-address@10.4.0: + resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==} + engines: {node: '>= 12'} + + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} engines: {node: '>= 10'} is-binary-path@2.1.0: @@ -1882,6 +2059,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -1909,13 +2089,30 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + js-beautify@1.14.9: + resolution: {integrity: sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==} + engines: {node: '>=12'} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1968,6 +2165,10 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2006,10 +2207,16 @@ packages: resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==} engines: {node: '>=12'} + magic-string@1.1.0: + resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} @@ -2034,17 +2241,17 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + 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==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.9: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} @@ -2059,9 +2266,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -2072,8 +2276,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2082,27 +2286,25 @@ packages: engines: {node: ^18 || >=20} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-abi@3.94.0: - resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==} - engines: {node: '>=10'} - node-addon-api@7.0.0: resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==} - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} - nodemailer@9.0.3: - resolution: {integrity: sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==} + nodemailer@9.0.5: + resolution: {integrity: sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==} engines: {node: '>=6.0.0'} + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2121,6 +2323,10 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -2162,6 +2368,9 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2209,15 +2418,15 @@ packages: peerDependencies: pg: '>=8.0' - pg-protocol@1.15.0: - resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.22.0: - resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==} + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -2275,6 +2484,16 @@ packages: pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} + hasBin: true + postcss-js@4.0.0: resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} engines: {node: ^12 || ^14 || >= 16} @@ -2306,8 +2525,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -2326,12 +2545,6 @@ packages: resolution: {integrity: sha512-txJzyrdoPw5F06xk3BEtzl46JCr6qU2DM8gfulxBFgqGKmfREbrcFyVqllttHuqWtafoBmowgJtGjm6kPGA/bg==} engines: {node: '>=0.10.0'} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. - hasBin: true - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2340,8 +2553,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -2349,23 +2562,26 @@ packages: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} - process-warning@4.0.1: - resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + process-warning@4.0.0: + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + + process-warning@5.1.0: + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2379,10 +2595,6 @@ packages: rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2391,8 +2603,8 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} real-require@0.2.0: @@ -2402,10 +2614,6 @@ packages: real-require@1.0.0: resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} - recast@0.23.12: - resolution: {integrity: sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==} - engines: {node: '>= 4'} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -2435,8 +2643,8 @@ packages: engines: {node: '>=14'} hasBin: true - rollup@4.62.2: - resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + rollup@4.62.4: + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2461,11 +2669,15 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} - sass@1.101.0: - resolution: {integrity: sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==} + sass@1.102.0: + resolution: {integrity: sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==} engines: {node: '>=20.19.0'} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -2495,20 +2707,17 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.4: - resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -2520,10 +2729,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} @@ -2532,10 +2737,16 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} @@ -2566,14 +2777,13 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@4.0.0: + resolution: {integrity: sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw==} + superjson@2.2.6: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} @@ -2593,6 +2803,9 @@ packages: svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.11.13: resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2604,24 +2817,32 @@ packages: peerDependencies: postcss: ^8.0.9 - tar-fs@2.1.5: - resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} - - tar-stream@2.1.4: - resolution: {integrity: sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==} - engines: {node: '>=6'} - thread-stream@4.2.0: resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} engines: {node: '>=20'} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} + + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2638,6 +2859,14 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2645,9 +2874,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2662,24 +2888,99 @@ packages: unctx@2.5.0: resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unimport@6.4.0: + resolution: {integrity: sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ==} + engines: {node: '>=18.12.0'} + peerDependencies: + oxc-parser: '*' + rolldown: ^1.0.0 + peerDependenciesMeta: + oxc-parser: + optional: true + rolldown: + optional: true + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unplugin-auto-import@21.1.0: + resolution: {integrity: sha512-EzrSqWIBulEqCuP7idADXH+tVKYrbwKlR5+r/lOWik0o+Ksny1kitmtryHGNSv7puzzORlcIwT/x2JStiszlHA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nuxt/kit': ^4.0.0 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-utils@0.3.2: + resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@32.1.0: + resolution: {integrity: sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + unplugin@3.3.0: + resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + untyped@2.0.0: resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} hasBin: true - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.0: + resolution: {integrity: sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2756,8 +3057,49 @@ packages: yaml: optional: true - vue-component-type-helpers@3.3.7: - resolution: {integrity: sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vue-component-type-helpers@3.3.9: + resolution: {integrity: sha512-3c/UfMe0SqyEfcGTyH7mfshHagJ9QTCbppCb0/uGpHZpFug7+If3GeGZN7I0YheKEExemx3xldQPoO7PQSOLQg==} vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} @@ -2776,8 +3118,8 @@ packages: peerDependencies: eslint: '>=6.0.0' - vue-i18n@11.4.6: - resolution: {integrity: sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==} + vue-i18n@11.4.8: + resolution: {integrity: sha512-0ULeHP6Z9CGvAm67S77ZEp41cfGXIREGL8qfhos2BMgcQQewtQcDKuojt6jjasAD/S8GwfTp2ySPmDSpwvrCMQ==} engines: {node: '>= 22'} peerDependencies: vue: ^3.0.0 @@ -2787,22 +3129,47 @@ packages: peerDependencies: vue: ^3.5.0 - vue@3.5.40: - resolution: {integrity: sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==} + vue@3.5.41: + resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + which@2.0.1: resolution: {integrity: sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2822,8 +3189,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.21.1: - resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2842,6 +3209,13 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -2870,8 +3244,8 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} engines: {node: '>=18'} zod@4.4.3: @@ -2881,165 +3255,180 @@ snapshots: '@antfu/utils@0.7.10': {} - '@aws-sdk/checksums@3.1000.18': + '@asamuzakjp/css-color@6.0.7': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + + '@aws-sdk/checksums@3.1000.26': dependencies: - '@aws-sdk/core': 3.975.3 + '@aws-sdk/core': 3.977.6 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1090.0': + '@aws-sdk/client-s3@3.1106.0': dependencies: - '@aws-sdk/checksums': 3.1000.18 - '@aws-sdk/core': 3.975.3 - '@aws-sdk/credential-provider-node': 3.972.70 - '@aws-sdk/middleware-sdk-s3': 3.972.64 - '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/checksums': 3.1000.26 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-node': 3.972.78 + '@aws-sdk/middleware-sdk-s3': 3.972.72 + '@aws-sdk/signature-v4-multi-region': 3.996.43 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 - '@smithy/fetch-http-handler': 5.6.7 - '@smithy/node-http-handler': 4.9.7 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/core@3.975.3': + '@aws-sdk/core@3.977.6': dependencies: '@aws-sdk/types': 3.974.2 - '@aws-sdk/xml-builder': 3.972.36 + '@aws-sdk/xml-builder': 3.972.37 '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.29.5 - '@smithy/signature-v4': 5.6.6 + '@smithy/core': 3.31.1 + '@smithy/signature-v4': 5.6.12 '@smithy/types': 4.16.1 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.59': + '@aws-sdk/credential-provider-env@3.972.67': dependencies: - '@aws-sdk/core': 3.975.3 + '@aws-sdk/core': 3.977.6 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.61': + '@aws-sdk/credential-provider-http@3.972.69': dependencies: - '@aws-sdk/core': 3.975.3 + '@aws-sdk/core': 3.977.6 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 - '@smithy/fetch-http-handler': 5.6.7 - '@smithy/node-http-handler': 4.9.7 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.4': + '@aws-sdk/credential-provider-ini@3.973.12': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/credential-provider-env': 3.972.59 - '@aws-sdk/credential-provider-http': 3.972.61 - '@aws-sdk/credential-provider-login': 3.972.66 - '@aws-sdk/credential-provider-process': 3.972.59 - '@aws-sdk/credential-provider-sso': 3.973.3 - '@aws-sdk/credential-provider-web-identity': 3.972.65 - '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-login': 3.972.74 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 + '@aws-sdk/nested-clients': 3.997.41 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 - '@smithy/credential-provider-imds': 4.4.10 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.66': + '@aws-sdk/credential-provider-login@3.972.74': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.70': + '@aws-sdk/credential-provider-node@3.972.78': dependencies: - '@aws-sdk/credential-provider-env': 3.972.59 - '@aws-sdk/credential-provider-http': 3.972.61 - '@aws-sdk/credential-provider-ini': 3.973.4 - '@aws-sdk/credential-provider-process': 3.972.59 - '@aws-sdk/credential-provider-sso': 3.973.3 - '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-ini': 3.973.12 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 - '@smithy/credential-provider-imds': 4.4.10 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.59': + '@aws-sdk/credential-provider-process@3.972.67': dependencies: - '@aws-sdk/core': 3.975.3 + '@aws-sdk/core': 3.977.6 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.3': + '@aws-sdk/credential-provider-sso@3.973.11': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/nested-clients': 3.997.33 - '@aws-sdk/token-providers': 3.1088.0 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/token-providers': 3.1103.0 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.65': + '@aws-sdk/credential-provider-web-identity@3.972.73': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.64': + '@aws-sdk/middleware-sdk-s3@3.972.72': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.33': + '@aws-sdk/nested-clients@3.997.41': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 - '@smithy/fetch-http-handler': 5.6.7 - '@smithy/node-http-handler': 4.9.7 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.1090.0': + '@aws-sdk/s3-request-presigner@3.1106.0': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.41': + '@aws-sdk/signature-v4-multi-region@3.996.43': dependencies: '@aws-sdk/types': 3.974.2 - '@smithy/signature-v4': 5.6.6 + '@smithy/signature-v4': 5.6.12 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1088.0': + '@aws-sdk/token-providers@3.1103.0': dependencies: - '@aws-sdk/core': 3.975.3 - '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 @@ -3048,13 +3437,18 @@ snapshots: '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.36': + '@aws-sdk/xml-builder@3.972.37': dependencies: '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws/lambda-invoke-store@0.3.0': {} + '@axe-core/playwright@4.12.1(playwright-core@1.62.1)': + dependencies: + axe-core: 4.12.1 + playwright-core: 1.62.1 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -3066,14 +3460,14 @@ snapshots: '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -3083,23 +3477,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-compilation-targets@7.29.7': dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.6 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 @@ -3111,7 +3505,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3120,15 +3514,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -3137,13 +3531,13 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-plugin-utils@7.29.7': {} @@ -3152,14 +3546,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -3172,11 +3566,11 @@ snapshots: '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.7': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': dependencies: @@ -3226,31 +3620,59 @@ snapshots: '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@ctrl/tinycolor@4.2.0': {} - '@element-plus/icons-vue@2.3.2(vue@3.5.40)': + '@element-plus/icons-vue@2.3.2(vue@3.5.41)': dependencies: - vue: 3.5.40 + vue: 3.5.41 '@esbuild/aix-ppc64@0.25.0': optional: true @@ -3327,7 +3749,7 @@ snapshots: '@esbuild/win32-x64@0.25.0': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))': dependencies: eslint: 9.39.5(jiti@2.7.0) eslint-visitor-keys: 3.4.3 @@ -3358,7 +3780,7 @@ snapshots: globals: 14.0.0 ignore: 5.2.0 import-fresh: 3.2.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3373,13 +3795,15 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@fastify/accept-negotiator@2.0.1': {} + '@exodus/bytes@1.15.1': {} - '@fastify/ajv-compiler@4.0.5': + '@fastify/accept-negotiator@2.1.0': {} + + '@fastify/ajv-compiler@4.0.6': dependencies: ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) - fast-uri: 3.1.3 + fast-uri: 4.1.2 '@fastify/busboy@3.2.0': {} @@ -3396,7 +3820,7 @@ snapshots: dependencies: fast-json-stringify: 7.0.1 - '@fastify/forwarded@3.0.1': {} + '@fastify/forwarded@3.0.2': {} '@fastify/helmet@13.1.0': dependencies: @@ -3417,16 +3841,17 @@ snapshots: '@fastify/proxy-addr@5.1.0': dependencies: - '@fastify/forwarded': 3.0.1 - ipaddr.js: 2.4.0 + '@fastify/forwarded': 3.0.2 + ipaddr.js: 2.5.0 - '@fastify/rate-limit@11.1.0': + '@fastify/rate-limit@11.2.0': dependencies: '@lukeed/ms': 2.0.2 fastify-plugin: 6.0.0 + ip-address: 10.4.0 toad-cache: 3.7.4 - '@fastify/send@4.1.0': + '@fastify/send@4.1.1': dependencies: '@lukeed/ms': 2.0.2 escape-html: 1.0.3 @@ -3434,11 +3859,11 @@ snapshots: http-errors: 2.0.1 mime: 3.0.0 - '@fastify/static@10.1.0': + '@fastify/static@10.1.3': dependencies: - '@fastify/accept-negotiator': 2.0.1 + '@fastify/accept-negotiator': 2.1.0 '@fastify/error': 4.2.0 - '@fastify/send': 4.1.0 + '@fastify/send': 4.1.1 content-disposition: 2.0.1 fastify-plugin: 6.0.0 fastq: 1.20.1 @@ -3448,7 +3873,7 @@ snapshots: dependencies: duplexify: 4.1.3 fastify-plugin: 6.0.0 - ws: 8.21.1 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3480,23 +3905,23 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@intlify/core-base@11.4.6': + '@intlify/core-base@11.4.8': dependencies: - '@intlify/devtools-types': 11.4.6 - '@intlify/message-compiler': 11.4.6 - '@intlify/shared': 11.4.6 + '@intlify/devtools-types': 11.4.8 + '@intlify/message-compiler': 11.4.8 + '@intlify/shared': 11.4.8 - '@intlify/devtools-types@11.4.6': + '@intlify/devtools-types@11.4.8': dependencies: - '@intlify/core-base': 11.4.6 - '@intlify/shared': 11.4.6 + '@intlify/core-base': 11.4.8 + '@intlify/shared': 11.4.8 - '@intlify/message-compiler@11.4.6': + '@intlify/message-compiler@11.4.8': dependencies: - '@intlify/shared': 11.4.6 + '@intlify/shared': 11.4.8 source-map-js: 1.2.1 - '@intlify/shared@11.4.6': {} + '@intlify/shared@11.4.8': {} '@isaacs/cliui@8.0.2': dependencies: @@ -3544,6 +3969,9 @@ snapshots: '@lukeed/ms@2.0.2': {} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3556,14 +3984,14 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nuxt/kit@3.21.8': + '@nuxt/kit@3.21.11': dependencies: c12: 3.3.4 consola: 3.4.2 defu: 6.1.7 destr: 2.0.5 - errx: 0.1.0 - exsolve: 1.1.0 + errx: 0.1.2 + exsolve: 1.1.1 ignore: 7.0.6 jiti: 2.7.0 klona: 2.0.6 @@ -3582,65 +4010,63 @@ snapshots: transitivePeerDependencies: - magicast - '@parcel/watcher-android-arm64@2.5.6': - optional: true + '@one-ini/wasm@0.1.1': {} - '@parcel/watcher-darwin-arm64@2.5.6': + '@parcel/watcher-android-arm64@2.6.0': optional: true - '@parcel/watcher-darwin-x64@2.5.6': + '@parcel/watcher-darwin-arm64@2.6.0': optional: true - '@parcel/watcher-freebsd-x64@2.5.6': + '@parcel/watcher-darwin-x64@2.6.0': optional: true - '@parcel/watcher-linux-arm-glibc@2.5.6': + '@parcel/watcher-freebsd-x64@2.6.0': optional: true - '@parcel/watcher-linux-arm-musl@2.5.6': + '@parcel/watcher-linux-arm-glibc@2.6.0': optional: true - '@parcel/watcher-linux-arm64-glibc@2.5.6': + '@parcel/watcher-linux-arm-musl@2.6.0': optional: true - '@parcel/watcher-linux-arm64-musl@2.5.6': + '@parcel/watcher-linux-arm64-glibc@2.6.0': optional: true - '@parcel/watcher-linux-x64-glibc@2.5.6': + '@parcel/watcher-linux-arm64-musl@2.6.0': optional: true - '@parcel/watcher-linux-x64-musl@2.5.6': + '@parcel/watcher-linux-x64-glibc@2.6.0': optional: true - '@parcel/watcher-win32-arm64@2.5.6': + '@parcel/watcher-linux-x64-musl@2.6.0': optional: true - '@parcel/watcher-win32-ia32@2.5.6': + '@parcel/watcher-win32-arm64@2.6.0': optional: true - '@parcel/watcher-win32-x64@2.5.6': + '@parcel/watcher-win32-x64@2.6.0': optional: true - '@parcel/watcher@2.5.6': + '@parcel/watcher@2.6.0': dependencies: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.0.0 picomatch: 4.0.5 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.6 - '@parcel/watcher-darwin-arm64': 2.5.6 - '@parcel/watcher-darwin-x64': 2.5.6 - '@parcel/watcher-freebsd-x64': 2.5.6 - '@parcel/watcher-linux-arm-glibc': 2.5.6 - '@parcel/watcher-linux-arm-musl': 2.5.6 - '@parcel/watcher-linux-arm64-glibc': 2.5.6 - '@parcel/watcher-linux-arm64-musl': 2.5.6 - '@parcel/watcher-linux-x64-glibc': 2.5.6 - '@parcel/watcher-linux-x64-musl': 2.5.6 - '@parcel/watcher-win32-arm64': 2.5.6 - '@parcel/watcher-win32-ia32': 2.5.6 - '@parcel/watcher-win32-x64': 2.5.6 + '@parcel/watcher-android-arm64': 2.6.0 + '@parcel/watcher-darwin-arm64': 2.6.0 + '@parcel/watcher-darwin-x64': 2.6.0 + '@parcel/watcher-freebsd-x64': 2.6.0 + '@parcel/watcher-linux-arm-glibc': 2.6.0 + '@parcel/watcher-linux-arm-musl': 2.6.0 + '@parcel/watcher-linux-arm64-glibc': 2.6.0 + '@parcel/watcher-linux-arm64-musl': 2.6.0 + '@parcel/watcher-linux-x64-glibc': 2.6.0 + '@parcel/watcher-linux-x64-musl': 2.6.0 + '@parcel/watcher-win32-arm64': 2.6.0 + '@parcel/watcher-win32-x64': 2.6.0 optional: true '@pinojs/redact@0.4.0': {} @@ -3650,121 +4076,125 @@ snapshots: '@pkgr/core@0.3.6': {} + '@playwright/test@1.62.1': + dependencies: + playwright: 1.62.1 + '@polka/url@1.0.0-next.29': {} - '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + '@rollup/pluginutils@5.4.0(rollup@4.62.4)': dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.5 optionalDependencies: - rollup: 4.62.2 + rollup: 4.62.4 - '@rollup/rollup-android-arm-eabi@4.62.2': + '@rollup/rollup-android-arm-eabi@4.62.4': optional: true - '@rollup/rollup-android-arm64@4.62.2': + '@rollup/rollup-android-arm64@4.62.4': optional: true - '@rollup/rollup-darwin-arm64@4.62.2': + '@rollup/rollup-darwin-arm64@4.62.4': optional: true - '@rollup/rollup-darwin-x64@4.62.2': + '@rollup/rollup-darwin-x64@4.62.4': optional: true - '@rollup/rollup-freebsd-arm64@4.62.2': + '@rollup/rollup-freebsd-arm64@4.62.4': optional: true - '@rollup/rollup-freebsd-x64@4.62.2': + '@rollup/rollup-freebsd-x64@4.62.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.2': + '@rollup/rollup-linux-arm-musleabihf@4.62.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.2': + '@rollup/rollup-linux-arm64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.2': + '@rollup/rollup-linux-arm64-musl@4.62.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.2': + '@rollup/rollup-linux-loong64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.2': + '@rollup/rollup-linux-loong64-musl@4.62.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.2': + '@rollup/rollup-linux-ppc64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.2': + '@rollup/rollup-linux-ppc64-musl@4.62.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.2': + '@rollup/rollup-linux-riscv64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.2': + '@rollup/rollup-linux-riscv64-musl@4.62.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.2': + '@rollup/rollup-linux-s390x-gnu@4.62.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.2': + '@rollup/rollup-linux-x64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-x64-musl@4.62.2': + '@rollup/rollup-linux-x64-musl@4.62.4': optional: true - '@rollup/rollup-openbsd-x64@4.62.2': + '@rollup/rollup-openbsd-x64@4.62.4': optional: true - '@rollup/rollup-openharmony-arm64@4.62.2': + '@rollup/rollup-openharmony-arm64@4.62.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.2': + '@rollup/rollup-win32-arm64-msvc@4.62.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.2': + '@rollup/rollup-win32-ia32-msvc@4.62.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.2': + '@rollup/rollup-win32-x64-gnu@4.62.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.2': + '@rollup/rollup-win32-x64-msvc@4.62.4': optional: true '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} - '@smithy/core@3.29.5': + '@smithy/core@3.31.1': dependencies: '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.4.10': + '@smithy/credential-provider-imds@4.4.16': dependencies: - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.6.7': + '@smithy/fetch-http-handler@5.6.13': dependencies: - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.9.7': + '@smithy/node-http-handler@4.9.13': dependencies: - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/signature-v4@5.6.6': + '@smithy/signature-v4@5.6.12': dependencies: - '@smithy/core': 3.29.5 + '@smithy/core': 3.31.1 '@smithy/types': 4.16.1 tslib: 2.8.1 @@ -3772,24 +4202,74 @@ snapshots: dependencies: tslib: 2.8.1 + '@standard-schema/spec@1.1.0': {} + '@sxzz/popperjs-es@2.11.8': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} '@types/lodash-es@4.17.12': dependencies: - '@types/lodash': 4.17.24 + '@types/lodash': 4.17.25 - '@types/lodash@4.17.24': {} + '@types/lodash@4.17.25': {} '@types/web-bluetooth@0.0.21': {} - '@vitejs/plugin-vue@5.2.1(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40)': + '@vitejs/plugin-vue@5.2.1(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41)': + dependencies: + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) + vue: 3.5.41 + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.10(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': dependencies: - vite: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) - vue: 3.5.40 + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 '@vue/babel-helper-vue-transform-on@1.1.5': {} @@ -3799,8 +4279,8 @@ snapshots: '@babel/helper-module-imports': 7.29.7 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@vue/babel-helper-vue-transform-on': 1.1.5 camelcase: 6.3.0 html-tags: 3.3.1 @@ -3808,35 +4288,35 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/compiler-core@3.5.40': + '@vue/compiler-core@3.5.41': dependencies: - '@babel/parser': 7.29.7 - '@vue/shared': 3.5.40 + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.41 entities: 7.0.1 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.40': + '@vue/compiler-dom@3.5.41': dependencies: - '@vue/compiler-core': 3.5.40 - '@vue/shared': 3.5.40 + '@vue/compiler-core': 3.5.41 + '@vue/shared': 3.5.41 - '@vue/compiler-sfc@3.5.40': + '@vue/compiler-sfc@3.5.41': dependencies: - '@babel/parser': 7.29.7 - '@vue/compiler-core': 3.5.40 - '@vue/compiler-dom': 3.5.40 - '@vue/compiler-ssr': 3.5.40 - '@vue/shared': 3.5.40 + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.41 + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-ssr': 3.5.41 + '@vue/shared': 3.5.41 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.19 + postcss: 8.5.26 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.40': + '@vue/compiler-ssr@3.5.41': dependencies: - '@vue/compiler-dom': 3.5.40 - '@vue/shared': 3.5.40 + '@vue/compiler-dom': 3.5.41 + '@vue/shared': 3.5.41 '@vue/devtools-api@6.5.0': {} @@ -3844,15 +4324,15 @@ snapshots: '@vue/devtools-api@6.6.4': {} - '@vue/devtools-core@7.7.10(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40)': + '@vue/devtools-core@7.7.10(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41)': dependencies: '@vue/devtools-kit': 7.7.10 '@vue/devtools-shared': 7.7.10 mitt: 3.0.1 nanoid: 5.1.16 pathe: 2.0.3 - vite-hot-client: 2.2.0(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)) - vue: 3.5.40 + vite-hot-client: 2.2.0(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + vue: 3.5.41 transitivePeerDependencies: - vite @@ -3870,57 +4350,68 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/eslint-config-prettier@10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.5)': + '@vue/eslint-config-prettier@10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6)': dependencies: eslint: 9.39.5(jiti@2.7.0) eslint-config-prettier: 10.1.8(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.5) - prettier: 3.9.5 + eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6) + prettier: 3.9.6 transitivePeerDependencies: - '@types/eslint' - '@vue/reactivity@3.5.40': + '@vue/reactivity@3.5.41': dependencies: - '@vue/shared': 3.5.40 + '@vue/shared': 3.5.41 - '@vue/runtime-core@3.5.40': + '@vue/runtime-core@3.5.41': dependencies: - '@vue/reactivity': 3.5.40 - '@vue/shared': 3.5.40 + '@vue/reactivity': 3.5.41 + '@vue/shared': 3.5.41 - '@vue/runtime-dom@3.5.40': + '@vue/runtime-dom@3.5.41': dependencies: - '@vue/reactivity': 3.5.40 - '@vue/runtime-core': 3.5.40 - '@vue/shared': 3.5.40 + '@vue/reactivity': 3.5.41 + '@vue/runtime-core': 3.5.41 + '@vue/shared': 3.5.41 csstype: 3.2.3 - '@vue/server-renderer@3.5.40': + '@vue/server-renderer@3.5.41': dependencies: - '@vue/compiler-ssr': 3.5.40 - '@vue/runtime-dom': 3.5.40 - '@vue/shared': 3.5.40 + '@vue/compiler-ssr': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/shared': 3.5.41 - '@vue/shared@3.5.40': {} + '@vue/shared@3.5.41': {} - '@vueuse/core@14.3.0(vue@3.5.40)': + '@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.41)(@vue/server-renderer@3.5.41)(vue@3.5.41)': + dependencies: + '@vue/compiler-dom': 3.5.41 + js-beautify: 1.14.9 + vue: 3.5.41 + vue-component-type-helpers: 3.3.9 + optionalDependencies: + '@vue/server-renderer': 3.5.41 + + '@vueuse/core@14.3.0(vue@3.5.41)': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 14.3.0 - '@vueuse/shared': 14.3.0(vue@3.5.40) - vue: 3.5.40 + '@vueuse/shared': 14.3.0(vue@3.5.41) + vue: 3.5.41 '@vueuse/metadata@14.3.0': {} - '@vueuse/shared@14.3.0(vue@3.5.40)': + '@vueuse/shared@14.3.0(vue@3.5.41)': dependencies: - vue: 3.5.40 + vue: 3.5.41 + + abbrev@1.0.3: {} abstract-logging@2.0.1: {} - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn-node@1.8.2: dependencies: @@ -3932,7 +4423,7 @@ snapshots: acorn@7.0.0: {} - acorn@8.17.0: {} + acorn@8.18.0: {} agent-base@6.0.0: dependencies: @@ -3954,7 +4445,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3962,11 +4453,17 @@ snapshots: ansi-regex@6.2.2: {} - ansi-styles@4.3.0: + ansi-styles@4.0.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.3: {} + ansi-styles@4.1.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.1.0: {} + + ansi-styles@6.2.1: {} anymatch@3.1.3: dependencies: @@ -3977,9 +4474,7 @@ snapshots: argparse@2.0.1: {} - ast-types@0.16.1: - dependencies: - tslib: 2.8.1 + assertion-error@2.0.1: {} async-validator@4.2.5: {} @@ -3987,13 +4482,13 @@ snapshots: atomic-sleep@1.0.0: {} - autoprefixer@10.5.4(postcss@8.5.19): + autoprefixer@10.5.4(postcss@8.5.26): dependencies: - browserslist: 4.28.6 - caniuse-lite: 1.0.30001806 + browserslist: 4.28.8 + caniuse-lite: 1.0.30001809 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.19 + postcss: 8.5.26 postcss-value-parser: 4.2.0 avvio@9.3.0: @@ -4001,7 +4496,9 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.20.1 - axios@1.18.1: + axe-core@4.12.1: {} + + axios@1.19.0: dependencies: follow-redirects: 1.16.0 form-data: 4.0.6 @@ -4015,43 +4512,30 @@ snapshots: balanced-match@4.0.4: {} - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.43: {} + baseline-browser-mapping@2.11.13: {} - better-sqlite3@12.11.1: + bidi-js@1.0.3: dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 + require-from-string: 2.0.2 binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - birpc@2.3.0: {} - bl@4.0.3: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - boolbase@1.0.0: {} bowser@2.14.1: {} - brace-expansion@1.1.16: + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.0 concat-map: 0.0.1 - brace-expansion@2.1.2: + brace-expansion@2.1.4: dependencies: balanced-match: 1.0.0 - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -4059,18 +4543,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.6: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.10.43 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.393 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.6) - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.403 + node-releases: 2.0.53 + update-browserslist-db: 1.3.0(browserslist@4.28.8) bundle-name@4.1.0: dependencies: @@ -4082,8 +4561,8 @@ snapshots: confbox: 0.2.4 defu: 6.1.7 dotenv: 17.4.2 - exsolve: 1.1.0 - giget: 3.3.0 + exsolve: 1.1.1 + giget: 3.3.1 jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 @@ -4102,11 +4581,13 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001809: {} + + chai@6.2.2: {} chalk@4.1.2: dependencies: - ansi-styles: 4.3.0 + ansi-styles: 4.1.0 supports-color: 7.2.0 chalk@5.6.2: {} @@ -4125,9 +4606,7 @@ snapshots: chokidar@5.0.0: dependencies: - readdirp: 5.0.0 - - chownr@1.1.4: {} + readdirp: 5.1.1 citty@0.1.6: dependencies: @@ -4149,13 +4628,15 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@10.0.0: {} + concat-map@0.0.1: {} - concurrently@10.0.3: + concurrently@10.0.4: dependencies: chalk: 5.6.2 rxjs: 7.8.2 - shell-quote: 1.8.4 + shell-quote: 1.9.0 supports-color: 10.2.2 tree-kill: 1.2.2 yargs: 18.0.0 @@ -4164,6 +4645,11 @@ snapshots: confbox@0.2.4: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + consola@3.4.2: {} content-disposition@2.0.1: {} @@ -4184,21 +4670,29 @@ snapshots: shebang-command: 2.0.0 which: 2.0.1 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + cssesc@3.0.0: {} csstype@3.2.3: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + dayjs@1.11.21: {} debug@4.4.3: dependencies: ms: 2.1.3 - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-extend@0.6.0: {} + decimal.js@10.6.0: {} deep-is@0.1.4: {} @@ -4225,7 +4719,8 @@ snapshots: destr@2.0.5: {} - detect-libc@2.1.2: {} + detect-libc@2.1.2: + optional: true detective@5.2.1: dependencies: @@ -4254,17 +4749,24 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.393: {} + editorconfig@1.0.7: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.0 + minimatch: 9.0.9 + semver: 7.8.5 + + electron-to-chromium@1.5.403: {} - element-plus@2.14.3(vue@3.5.40): + element-plus@2.14.4(vue@3.5.41): dependencies: '@ctrl/tinycolor': 4.2.0 - '@element-plus/icons-vue': 2.3.2(vue@3.5.40) + '@element-plus/icons-vue': 2.3.2(vue@3.5.41) '@floating-ui/dom': 1.8.0 '@popperjs/core': '@sxzz/popperjs-es@2.11.8' - '@types/lodash': 4.17.24 + '@types/lodash': 4.17.25 '@types/lodash-es': 4.17.12 - '@vueuse/core': 14.3.0(vue@3.5.40) + '@vueuse/core': 14.3.0(vue@3.5.41) async-validator: 4.2.5 dayjs: 1.11.21 lodash: 4.18.1 @@ -4272,8 +4774,8 @@ snapshots: lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) memoize-one: 6.0.0 normalize-wheel-es: 1.2.0 - vue: 3.5.40 - vue-component-type-helpers: 3.3.7 + vue: 3.5.41 + vue-component-type-helpers: 3.3.9 emoji-regex@10.6.0: {} @@ -4287,14 +4789,18 @@ snapshots: entities@7.0.1: {} + entities@8.0.0: {} + error-stack-parser-es@0.1.5: {} - errx@0.1.0: {} + errx@0.1.2: {} es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -4340,14 +4846,16 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)): dependencies: eslint: 9.39.5(jiti@2.7.0) - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.5): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6): dependencies: eslint: 9.39.5(jiti@2.7.0) - prettier: 3.9.5 + prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: @@ -4355,7 +4863,7 @@ snapshots: eslint-plugin-vue@9.32.0(eslint@9.39.5(jiti@2.7.0)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) eslint: 9.39.5(jiti@2.7.0) globals: 13.24.0 natural-compare: 1.4.0 @@ -4385,7 +4893,7 @@ snapshots: eslint@9.39.5(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 @@ -4426,18 +4934,16 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 4.2.1 espree@9.3.1: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 3.3.0 - esprima@4.0.1: {} - esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -4469,11 +4975,11 @@ snapshots: pretty-ms: 9.3.0 signal-exit: 4.1.0 strip-final-newline: 4.0.0 - yoctocolors: 2.1.2 + yoctocolors: 2.2.0 - expand-template@2.0.3: {} + expect-type@1.4.0: {} - exsolve@1.1.0: {} + exsolve@1.1.1: {} fast-decode-uri-component@1.0.1: {} @@ -4496,7 +5002,7 @@ snapshots: '@fastify/merge-json-schemas': 0.2.1 ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) - fast-uri: 4.1.0 + fast-uri: 4.1.2 json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 @@ -4506,25 +5012,25 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 - fast-uri@3.1.3: {} + fast-uri@3.1.5: {} - fast-uri@4.1.0: {} + fast-uri@4.1.2: {} fastify-plugin@6.0.0: {} - fastify@5.10.0: + fastify@5.11.3: dependencies: - '@fastify/ajv-compiler': 4.0.5 + '@fastify/ajv-compiler': 4.0.6 '@fastify/error': 4.2.0 '@fastify/fast-json-stringify-compiler': 5.1.0 '@fastify/proxy-addr': 5.1.0 abstract-logging: 2.0.1 avvio: 9.3.0 fast-json-stringify: 7.0.1 - find-my-way: 9.6.0 + find-my-way: 9.7.0 light-my-request: 6.6.0 pino: 10.3.1 - process-warning: 5.0.0 + process-warning: 5.1.0 rfdc: 1.4.1 secure-json-parse: 4.1.0 semver: 7.8.5 @@ -4546,13 +5052,11 @@ snapshots: dependencies: flat-cache: 4.0.0 - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-my-way@9.6.0: + find-my-way@9.7.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 @@ -4565,11 +5069,11 @@ snapshots: flat-cache@4.0.0: dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 rimraf: 5.0.5 - flatted@3.4.2: {} + flatted@3.4.4: {} follow-redirects@1.16.0: {} @@ -4588,14 +5092,17 @@ snapshots: fraction.js@5.3.4: {} - fs-constants@1.0.0: {} - - fs-extra@11.3.6: + fs-extra@11.4.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 universalify: 2.0.1 + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -4630,9 +5137,7 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - giget@3.3.0: {} - - github-from-package@0.0.0: {} + giget@3.3.1: {} glob-parent@5.1.2: dependencies: @@ -4653,10 +5158,18 @@ snapshots: glob@13.0.6: dependencies: - minimatch: 10.2.5 + minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -4683,6 +5196,12 @@ snapshots: hookable@5.5.3: {} + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + html-tags@3.3.1: {} http-errors@2.0.1: @@ -4702,8 +5221,6 @@ snapshots: human-signals@8.0.1: {} - ieee754@1.2.1: {} - ignore@5.2.0: {} ignore@7.0.6: {} @@ -4717,11 +5234,18 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} ini@1.3.8: {} - ipaddr.js@2.4.0: {} + ip-address@10.4.0: {} + + ipaddr.js@2.5.0: {} is-binary-path@2.1.0: dependencies: @@ -4749,6 +5273,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-stream@4.0.1: {} is-unicode-supported@2.1.0: {} @@ -4769,12 +5295,47 @@ snapshots: jiti@2.7.0: {} + js-beautify@1.14.9: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.7 + glob: 8.1.0 + nopt: 6.0.0 + + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 + jsdom@30.0.1: + dependencies: + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.10.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -4815,11 +5376,17 @@ snapshots: light-my-request@6.6.0: dependencies: cookie: 1.0.1 - process-warning: 4.0.1 + process-warning: 4.0.0 set-cookie-parser: 2.6.0 lilconfig@2.1.0: {} + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4852,8 +5419,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@1.1.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + memoize-one@6.0.0: {} merge2@1.4.1: {} @@ -4871,19 +5444,21 @@ snapshots: mime@3.0.0: {} - mimic-response@3.1.0: {} - - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.18 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.4 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.1.4 minimist@1.2.8: {} @@ -4891,11 +5466,9 @@ snapshots: mitt@3.0.1: {} - mkdirp-classic@0.5.3: {} - mlly@1.8.2: dependencies: - acorn: 8.17.0 + acorn: 8.18.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.4 @@ -4904,24 +5477,22 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.16: {} + nanoid@3.3.18: {} nanoid@5.1.16: {} - napi-build-utils@2.0.0: {} - natural-compare@1.4.0: {} - node-abi@3.94.0: - dependencies: - semver: 7.8.5 - node-addon-api@7.0.0: optional: true - node-releases@2.0.51: {} + node-releases@2.0.53: {} - nodemailer@9.0.3: {} + nodemailer@9.0.5: {} + + nopt@6.0.0: + dependencies: + abbrev: 1.0.3 normalize-path@3.0.0: {} @@ -4938,6 +5509,8 @@ snapshots: object-hash@3.0.0: {} + obug@2.1.4: {} + ohash@2.0.11: {} on-exit-leak-free@2.1.2: {} @@ -4980,6 +5553,10 @@ snapshots: parse-ms@4.0.0: {} + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -5011,11 +5588,11 @@ snapshots: pg-int8@1.0.1: {} - pg-pool@3.14.0(pg@8.22.0): + pg-pool@3.14.0(pg@8.23.0): dependencies: - pg: 8.22.0 + pg: 8.23.0 - pg-protocol@1.15.0: {} + pg-protocol@1.16.0: {} pg-types@2.2.0: dependencies: @@ -5025,11 +5602,11 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.1.0 - pg@8.22.0: + pg@8.23.0: dependencies: pg-connection-string: 2.14.0 - pg-pool: 3.14.0(pg@8.22.0) - pg-protocol: 1.15.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -5045,22 +5622,22 @@ snapshots: picomatch@4.0.5: {} - pinia-plugin-persistedstate@4.2.0(pinia@2.3.1(vue@3.5.40)): + pinia-plugin-persistedstate@4.2.0(pinia@2.3.1(vue@3.5.41)): dependencies: - '@nuxt/kit': 3.21.8 + '@nuxt/kit': 3.21.11 deep-pick-omit: 1.2.1 defu: 6.1.7 destr: 2.0.5 optionalDependencies: - pinia: 2.3.1(vue@3.5.40) + pinia: 2.3.1(vue@3.5.41) transitivePeerDependencies: - magicast - pinia@2.3.1(vue@3.5.40): + pinia@2.3.1(vue@3.5.41): dependencies: '@vue/devtools-api': 6.6.3 - vue: 3.5.40 - vue-demi: 0.14.10(vue@3.5.40) + vue: 3.5.41 + vue-demi: 0.14.10(vue@3.5.41) transitivePeerDependencies: - '@vue/composition-api' @@ -5077,7 +5654,7 @@ snapshots: on-exit-leak-free: 2.1.2 pino-abstract-transport: 3.0.0 pino-std-serializers: 7.1.0 - process-warning: 5.0.0 + process-warning: 5.1.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 @@ -5093,24 +5670,32 @@ snapshots: pkg-types@2.3.1: dependencies: confbox: 0.2.4 - exsolve: 1.1.0 + exsolve: 1.1.1 pathe: 2.0.3 - postcss-js@4.0.0(postcss@8.5.19): + playwright-core@1.62.1: {} + + playwright@1.62.1: + dependencies: + playwright-core: 1.62.1 + optionalDependencies: + fsevents: 2.3.2 + + postcss-js@4.0.0(postcss@8.5.26): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.19 + postcss: 8.5.26 - postcss-load-config@3.1.4(postcss@8.5.19): + postcss-load-config@3.1.4(postcss@8.5.26): dependencies: lilconfig: 2.1.0 yaml: 1.10.3 optionalDependencies: - postcss: 8.5.19 + postcss: 8.5.26 - postcss-nested@5.0.6(postcss@8.5.19): + postcss-nested@5.0.6(postcss@8.5.26): dependencies: - postcss: 8.5.19 + postcss: 8.5.26 postcss-selector-parser: 6.1.4 postcss-selector-parser@6.1.4: @@ -5120,9 +5705,9 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.19: + postcss@8.5.26: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -5136,46 +5721,30 @@ snapshots: dependencies: xtend: 4.0.2 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.1.2 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.94.0 - pump: 3.0.4 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.5 - tunnel-agent: 0.6.0 - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 - prettier@3.9.5: {} + prettier@3.9.6: {} pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 - process-warning@4.0.1: {} + process-warning@4.0.0: {} - process-warning@5.0.0: {} + process-warning@5.1.0: {} - proxy-from-env@2.1.0: {} + proto-list@1.2.4: {} - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 + proxy-from-env@2.1.0: {} punycode@2.3.1: {} + quansync@0.2.11: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -5187,13 +5756,6 @@ snapshots: defu: 6.1.7 destr: 2.0.5 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -5204,20 +5766,12 @@ snapshots: dependencies: picomatch: 2.3.2 - readdirp@5.0.0: {} + readdirp@5.1.1: {} real-require@0.2.0: {} real-require@1.0.0: {} - recast@0.23.12: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - require-from-string@2.0.2: {} resolve-from@4.0.0: {} @@ -5239,35 +5793,36 @@ snapshots: dependencies: glob: 10.5.0 - rollup@4.62.2: + rollup@4.62.4: dependencies: '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.62.2 - '@rollup/rollup-android-arm64': 4.62.2 - '@rollup/rollup-darwin-arm64': 4.62.2 - '@rollup/rollup-darwin-x64': 4.62.2 - '@rollup/rollup-freebsd-arm64': 4.62.2 - '@rollup/rollup-freebsd-x64': 4.62.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 - '@rollup/rollup-linux-arm-musleabihf': 4.62.2 - '@rollup/rollup-linux-arm64-gnu': 4.62.2 - '@rollup/rollup-linux-arm64-musl': 4.62.2 - '@rollup/rollup-linux-loong64-gnu': 4.62.2 - '@rollup/rollup-linux-loong64-musl': 4.62.2 - '@rollup/rollup-linux-ppc64-gnu': 4.62.2 - '@rollup/rollup-linux-ppc64-musl': 4.62.2 - '@rollup/rollup-linux-riscv64-gnu': 4.62.2 - '@rollup/rollup-linux-riscv64-musl': 4.62.2 - '@rollup/rollup-linux-s390x-gnu': 4.62.2 - '@rollup/rollup-linux-x64-gnu': 4.62.2 - '@rollup/rollup-linux-x64-musl': 4.62.2 - '@rollup/rollup-openbsd-x64': 4.62.2 - '@rollup/rollup-openharmony-arm64': 4.62.2 - '@rollup/rollup-win32-arm64-msvc': 4.62.2 - '@rollup/rollup-win32-ia32-msvc': 4.62.2 - '@rollup/rollup-win32-x64-gnu': 4.62.2 - '@rollup/rollup-win32-x64-msvc': 4.62.2 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.4 + '@rollup/rollup-android-arm64': 4.62.4 + '@rollup/rollup-darwin-arm64': 4.62.4 + '@rollup/rollup-darwin-x64': 4.62.4 + '@rollup/rollup-freebsd-arm64': 4.62.4 + '@rollup/rollup-freebsd-x64': 4.62.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4 + '@rollup/rollup-linux-arm64-gnu': 4.62.4 + '@rollup/rollup-linux-arm64-musl': 4.62.4 + '@rollup/rollup-linux-loong64-gnu': 4.62.4 + '@rollup/rollup-linux-loong64-musl': 4.62.4 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4 + '@rollup/rollup-linux-ppc64-musl': 4.62.4 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4 + '@rollup/rollup-linux-riscv64-musl': 4.62.4 + '@rollup/rollup-linux-s390x-gnu': 4.62.4 + '@rollup/rollup-linux-x64-gnu': 4.62.4 + '@rollup/rollup-linux-x64-musl': 4.62.4 + '@rollup/rollup-openbsd-x64': 4.62.4 + '@rollup/rollup-openharmony-arm64': 4.62.4 + '@rollup/rollup-win32-arm64-msvc': 4.62.4 + '@rollup/rollup-win32-ia32-msvc': 4.62.4 + '@rollup/rollup-win32-x64-gnu': 4.62.4 + '@rollup/rollup-win32-x64-msvc': 4.62.4 fsevents: 2.3.3 run-applescript@7.1.0: {} @@ -5288,13 +5843,17 @@ snapshots: safe-stable-stringify@2.5.0: {} - sass@1.101.0: + sass@1.102.0: dependencies: chokidar: 5.0.0 immutable: 5.1.9 source-map-js: 1.2.1 optionalDependencies: - '@parcel/watcher': 2.5.6 + '@parcel/watcher': 2.6.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 scule@1.3.0: {} @@ -5314,17 +5873,11 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.4: {} + shell-quote@1.9.0: {} - signal-exit@4.1.0: {} - - simple-concat@1.0.1: {} + siginfo@2.0.0: {} - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 + signal-exit@4.1.0: {} sirv@3.0.2: dependencies: @@ -5338,14 +5891,16 @@ snapshots: source-map-js@1.2.1: {} - source-map@0.6.1: {} - speakingurl@14.0.1: {} split2@4.2.0: {} + stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.2.0: {} + stream-shift@1.0.3: {} string-width@4.2.0: @@ -5380,10 +5935,12 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} + strip-literal@4.0.0: + dependencies: + js-tokens: 10.0.0 + superjson@2.2.6: dependencies: copy-anything: 4.0.5 @@ -5398,11 +5955,13 @@ snapshots: svg-tags@1.0.0: {} + symbol-tree@3.2.4: {} + synckit@0.11.13: dependencies: '@pkgr/core': 0.3.6 - tailwindcss@3.0.24(postcss@8.5.19): + tailwindcss@3.0.24(postcss@8.5.26): dependencies: arg: 5.0.2 chokidar: 3.6.0 @@ -5417,10 +5976,10 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.19 - postcss-js: 4.0.0(postcss@8.5.19) - postcss-load-config: 3.1.4(postcss@8.5.19) - postcss-nested: 5.0.6(postcss@8.5.19) + postcss: 8.5.26 + postcss-js: 4.0.0(postcss@8.5.26) + postcss-load-config: 3.1.4(postcss@8.5.26) + postcss-nested: 5.0.6(postcss@8.5.26) postcss-selector-parser: 6.1.4 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 @@ -5428,32 +5987,27 @@ snapshots: transitivePeerDependencies: - ts-node - tar-fs@2.1.5: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.4 - tar-stream: 2.1.4 - - tar-stream@2.1.4: - dependencies: - bl: 4.0.3 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - thread-stream@4.2.0: dependencies: real-require: 1.0.0 - tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinyrainbow@3.1.1: {} + + tldts-core@7.4.10: {} + + tldts@7.4.10: + dependencies: + tldts-core: 7.4.10 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -5464,14 +6018,18 @@ snapshots: totalist@3.0.1: {} + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.10 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} tslib@2.8.1: {} - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5482,21 +6040,109 @@ snapshots: unctx@2.5.0: dependencies: - acorn: 8.17.0 + acorn: 8.18.0 estree-walker: 3.0.3 magic-string: 0.30.21 unplugin: 2.3.11 + undici@8.10.0: {} + unicorn-magic@0.3.0: {} + unimport@6.4.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): + dependencies: + acorn: 8.18.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.2.1 + magic-string: 1.1.0 + mlly: 1.8.2 + pathe: 2.0.3 + picomatch: 4.0.5 + pkg-types: 2.3.1 + scule: 1.3.0 + strip-literal: 4.0.0 + tinyglobby: 0.2.17 + unplugin: 3.3.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rollup + - unloader + - vite + - webpack + universalify@2.0.1: {} + unplugin-auto-import@21.1.0(@vueuse/core@14.3.0(vue@3.5.41))(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): + dependencies: + local-pkg: 1.2.1 + magic-string: 1.1.0 + picomatch: 4.0.5 + unimport: 6.4.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + optionalDependencies: + '@vueuse/core': 14.3.0(vue@3.5.41) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - oxc-parser + - rolldown + - rollup + - unloader + - vite + - webpack + + unplugin-utils@0.3.2: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.5 + + unplugin-vue-components@32.1.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41): + dependencies: + chokidar: 5.0.0 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + obug: 2.1.4 + picomatch: 4.0.5 + tinyglobby: 0.2.17 + unplugin: 3.3.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + vue: 3.5.41 + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - unloader + - vite + - webpack + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 - acorn: 8.17.0 + acorn: 8.18.0 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + + unplugin@3.3.0(esbuild@0.25.0)(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): + dependencies: + '@jridgewell/remapping': 2.3.5 picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 + optionalDependencies: + esbuild: 0.25.0 + rollup: 4.62.4 + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) untyped@2.0.0: dependencies: @@ -5506,9 +6152,9 @@ snapshots: knitwork: 1.3.0 scule: 1.3.0 - update-browserslist-db@1.2.3(browserslist@4.28.6): + update-browserslist-db@1.3.0(browserslist@4.28.8): dependencies: - browserslist: 4.28.6 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 @@ -5518,43 +6164,43 @@ snapshots: util-deprecate@1.0.2: {} - vite-hot-client@2.2.0(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)): + vite-hot-client@2.2.0(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): dependencies: - vite: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) - vite-plugin-inspect@0.8.9(rollup@4.62.2)(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)): + vite-plugin-inspect@0.8.9(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): dependencies: '@antfu/utils': 0.7.10 - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@rollup/pluginutils': 5.4.0(rollup@4.62.4) debug: 4.4.3 error-stack-parser-es: 0.1.5 - fs-extra: 11.3.6 + fs-extra: 11.4.0 open: 10.2.0 perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 3.0.2 - vite: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) transitivePeerDependencies: - rollup - supports-color - vite-plugin-vue-devtools@7.7.10(rollup@4.62.2)(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40): + vite-plugin-vue-devtools@7.7.10(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41): dependencies: - '@vue/devtools-core': 7.7.10(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0))(vue@3.5.40) + '@vue/devtools-core': 7.7.10(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) '@vue/devtools-kit': 7.7.10 '@vue/devtools-shared': 7.7.10 execa: 9.5.2 sirv: 3.0.2 - vite: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) - vite-plugin-inspect: 0.8.9(rollup@4.62.2)(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)) - vite-plugin-vue-inspector: 5.4.0(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)) + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) + vite-plugin-inspect: 0.8.9(rollup@4.62.4)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + vite-plugin-vue-inspector: 5.4.0(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) transitivePeerDependencies: - '@nuxt/kit' - rollup - supports-color - vue - vite-plugin-vue-inspector@5.4.0(vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0)): + vite-plugin-vue-inspector@5.4.0(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) @@ -5562,32 +6208,59 @@ snapshots: '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.29.7) - '@vue/compiler-dom': 3.5.40 + '@vue/compiler-dom': 3.5.41 kolorist: 1.8.0 magic-string: 0.30.4 - vite: 6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0) + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - vite@6.4.3(jiti@2.7.0)(sass@1.101.0)(yaml@2.9.0): + vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0): dependencies: esbuild: 0.25.0 fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 - postcss: 8.5.19 - rollup: 4.62.2 + postcss: 8.5.26 + rollup: 4.62.4 tinyglobby: 0.2.17 optionalDependencies: fsevents: 2.3.3 jiti: 2.7.0 - sass: 1.101.0 + sass: 1.102.0 yaml: 2.9.0 - vue-component-type-helpers@3.3.7: {} + vitest@4.1.10(jsdom@30.0.1)(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 6.4.3(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + jsdom: 30.0.1 + transitivePeerDependencies: + - msw - vue-demi@0.14.10(vue@3.5.40): + vue-component-type-helpers@3.3.9: {} + + vue-demi@0.14.10(vue@3.5.41): dependencies: - vue: 3.5.40 + vue: 3.5.41 vue-eslint-parser@9.4.3(eslint@9.39.5(jiti@2.7.0)): dependencies: @@ -5602,56 +6275,85 @@ snapshots: transitivePeerDependencies: - supports-color - vue-i18n@11.4.6(vue@3.5.40): + vue-i18n@11.4.8(vue@3.5.41): dependencies: - '@intlify/core-base': 11.4.6 - '@intlify/devtools-types': 11.4.6 - '@intlify/shared': 11.4.6 + '@intlify/core-base': 11.4.8 + '@intlify/devtools-types': 11.4.8 + '@intlify/shared': 11.4.8 '@vue/devtools-api': 6.5.0 - vue: 3.5.40 + vue: 3.5.41 - vue-router@4.6.4(vue@3.5.40): + vue-router@4.6.4(vue@3.5.41): dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.40 + vue: 3.5.41 + + vue@3.5.41: + dependencies: + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-sfc': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/server-renderer': 3.5.41 + '@vue/shared': 3.5.41 - vue@3.5.40: + w3c-xmlserializer@5.0.0: dependencies: - '@vue/compiler-dom': 3.5.40 - '@vue/compiler-sfc': 3.5.40 - '@vue/runtime-dom': 3.5.40 - '@vue/server-renderer': 3.5.40 - '@vue/shared': 3.5.40 + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} webpack-virtual-modules@0.6.2: {} + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.1: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: dependencies: - ansi-styles: 4.3.0 + ansi-styles: 4.0.0 string-width: 4.2.0 strip-ansi: 6.0.1 wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.3 + ansi-styles: 6.1.0 string-width: 5.1.2 strip-ansi: 7.2.0 wrap-ansi@9.0.0: dependencies: - ansi-styles: 6.2.3 + ansi-styles: 6.2.1 string-width: 7.2.0 strip-ansi: 7.2.0 wrappy@1.0.2: {} - ws@8.21.1: {} + ws@8.21.3: {} wsl-utils@0.1.0: dependencies: @@ -5659,6 +6361,10 @@ snapshots: xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xtend@4.0.2: {} y18n@5.0.8: {} @@ -5680,6 +6386,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 - yoctocolors@2.1.2: {} + yoctocolors@2.2.0: {} zod@4.4.3: {} diff --git a/src/App.vue b/src/App.vue index f0526b0..b54dda9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,22 +1,107 @@ + + diff --git a/src/assets/main.scss b/src/assets/main.scss index a31e444..a35f577 100644 --- a/src/assets/main.scss +++ b/src/assets/main.scss @@ -1,3 +1,30 @@ -@import 'tailwindcss/base'; -@import 'tailwindcss/components'; -@import 'tailwindcss/utilities'; +@use '../styles/tokens'; +@use '../styles/base'; + +.el-button, +.el-input__wrapper, +.el-select__wrapper, +.el-dialog, +.el-message-box { + font-family: var(--font-body); +} + +.el-button { + min-height: 44px; + border-radius: var(--radius-sm); + font-weight: 650; +} + +.el-input__wrapper, +.el-select__wrapper { + min-height: 44px; + border-radius: var(--radius-sm); +} + +.el-dialog, +.el-message-box { + border: 1px solid var(--color-line); + border-radius: var(--radius-md); + background: var(--color-surface); + box-shadow: var(--shadow-lg); +} diff --git a/src/assets/student/avatar-default.svg b/src/assets/student/avatar-default.svg new file mode 100644 index 0000000..36f351b --- /dev/null +++ b/src/assets/student/avatar-default.svg @@ -0,0 +1 @@ +Default profile portrait diff --git a/src/components/account/PasswordForm.vue b/src/components/account/PasswordForm.vue index 771715e..eae1de8 100644 --- a/src/components/account/PasswordForm.vue +++ b/src/components/account/PasswordForm.vue @@ -1,10 +1,12 @@ + + + + diff --git a/src/components/app/AppShell.vue b/src/components/app/AppShell.vue new file mode 100644 index 0000000..65b563c --- /dev/null +++ b/src/components/app/AppShell.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/app/PageContainer.vue b/src/components/app/PageContainer.vue new file mode 100644 index 0000000..ce445ed --- /dev/null +++ b/src/components/app/PageContainer.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/app/RoleNavigation.vue b/src/components/app/RoleNavigation.vue new file mode 100644 index 0000000..223e14c --- /dev/null +++ b/src/components/app/RoleNavigation.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/src/components/basic/CustomModal.vue b/src/components/basic/CustomModal.vue index aeb182b..1423a72 100644 --- a/src/components/basic/CustomModal.vue +++ b/src/components/basic/CustomModal.vue @@ -1,70 +1,254 @@ diff --git a/src/components/basic/InputComponent.vue b/src/components/basic/InputComponent.vue index b9e4099..5757d4d 100644 --- a/src/components/basic/InputComponent.vue +++ b/src/components/basic/InputComponent.vue @@ -1,21 +1,31 @@ + diff --git a/src/components/basic/MyButton.vue b/src/components/basic/MyButton.vue index 55be0f2..27235de 100644 --- a/src/components/basic/MyButton.vue +++ b/src/components/basic/MyButton.vue @@ -5,18 +5,18 @@ const props = defineProps({ type: { type: String, default: 'default', - validator: (val) => { - return ['default', 'primary', 'success', 'warning', 'danger'].includes( - val - ) - } + validator: (value) => + ['default', 'primary', 'success', 'warning', 'danger'].includes(value) }, size: { type: String, default: 'medium', - validator: (val) => { - return ['small', 'medium', 'large'].includes(val) - } + validator: (value) => ['small', 'medium', 'large'].includes(value) + }, + nativeType: { + type: String, + default: 'button', + validator: (value) => ['button', 'submit', 'reset'].includes(value) }, disabled: { type: Boolean, @@ -25,69 +25,164 @@ const props = defineProps({ loading: { type: Boolean, default: false + }, + ariaLabel: { + type: String, + default: '' } }) -const buttonClass = computed(() => { - const baseClass = 'mx-1 rounded-full transition-all duration-300 ease-in-out' - const typeClass = { - default: - 'bg-gray-500 text-white hover:ring hover:ring-gray-800 hover:ring-opacity-50', - primary: - 'bg-blue-200 text-white hover:ring hover:ring-blue-800 hover:ring-opacity-50', - success: - 'bg-green-500 text-white hover:ring hover:ring-green-800 hover:ring-opacity-50', - warning: - 'bg-yellow-500 text-white hover:ring hover:ring-yellow-800 hover:ring-opacity-50', - danger: - 'bg-red-300 text-white hover:ring hover:ring-red-800 hover:ring-opacity-50' - }[props.type] - - const sizeClass = { - small: 'w-12 h-6', - medium: 'text-base', - large: 'w-16 h-8' - }[props.size] - - return `${baseClass} ${typeClass} ${sizeClass} ${ - props.disabled ? 'cursor-not-allowed opacity-50' : '' - }` -}) - -//给组件定义一个click事件 const emit = defineEmits(['click']) +const buttonClass = computed(() => [ + 'app-button', + `app-button--${props.type}`, + `app-button--${props.size}` +]) + const handleClick = (event) => { - if (props.disabled || props.loading) { - return - } + if (props.disabled || props.loading) return emit('click', event) } + diff --git a/src/components/didi/DidiClassBox.vue b/src/components/didi/DidiClassBox.vue index 5ba381d..bf9d876 100644 --- a/src/components/didi/DidiClassBox.vue +++ b/src/components/didi/DidiClassBox.vue @@ -2,7 +2,7 @@ import { ref, onMounted, onUnmounted } from 'vue' import { useRouter } from 'vue-router' import { useStudentPersonStore } from '@/stores/modules/studentPerson' -import studentAvatar from '@/assets/student/avatar.png' +import studentAvatar from '@/assets/student/avatar-default.svg' import teacherAvatar from '@/assets/icon/teacher.png' const studentPersonStore = useStudentPersonStore() diff --git a/src/components/public/BrandMark.vue b/src/components/public/BrandMark.vue new file mode 100644 index 0000000..82b15da --- /dev/null +++ b/src/components/public/BrandMark.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/public/EditorialCover.vue b/src/components/public/EditorialCover.vue new file mode 100644 index 0000000..5bd52c9 --- /dev/null +++ b/src/components/public/EditorialCover.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/src/components/public/PublicAsyncState.vue b/src/components/public/PublicAsyncState.vue new file mode 100644 index 0000000..67c37d1 --- /dev/null +++ b/src/components/public/PublicAsyncState.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/src/components/public/PublicCourseCard.vue b/src/components/public/PublicCourseCard.vue new file mode 100644 index 0000000..baf8d1e --- /dev/null +++ b/src/components/public/PublicCourseCard.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/src/components/public/PublicShell.vue b/src/components/public/PublicShell.vue new file mode 100644 index 0000000..5c0d9c7 --- /dev/null +++ b/src/components/public/PublicShell.vue @@ -0,0 +1,586 @@ + + + + + diff --git a/src/components/public/PublicTeacherCard.vue b/src/components/public/PublicTeacherCard.vue new file mode 100644 index 0000000..072ea6c --- /dev/null +++ b/src/components/public/PublicTeacherCard.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/src/components/public/usePublicMeta.js b/src/components/public/usePublicMeta.js new file mode 100644 index 0000000..989aa6e --- /dev/null +++ b/src/components/public/usePublicMeta.js @@ -0,0 +1,39 @@ +import { watchEffect } from 'vue' +import { useI18n } from 'vue-i18n' + +function ensureMeta(selector, attributes) { + let element = document.head.querySelector(selector) + if (!element) { + element = document.createElement('meta') + Object.entries(attributes).forEach(([key, value]) => { + element.setAttribute(key, value) + }) + document.head.appendChild(element) + } + return element +} + +export function usePublicMeta(titleKey, descriptionKey) { + const { t, locale } = useI18n() + + watchEffect(() => { + if (typeof document === 'undefined') return + + const title = t(titleKey) + const description = t(descriptionKey) + const brand = t('public.brand.name') + + document.documentElement.lang = locale.value === 'zh' ? 'zh-CN' : 'en' + document.title = title === brand ? title : `${title} · ${brand}` + + ensureMeta('meta[name="description"]', { + name: 'description' + }).setAttribute('content', description) + ensureMeta('meta[property="og:title"]', { + property: 'og:title' + }).setAttribute('content', document.title) + ensureMeta('meta[property="og:description"]', { + property: 'og:description' + }).setAttribute('content', description) + }) +} diff --git a/src/components/service/LanguageToggle.vue b/src/components/service/LanguageToggle.vue index edd6cfb..02b4c16 100644 --- a/src/components/service/LanguageToggle.vue +++ b/src/components/service/LanguageToggle.vue @@ -1,10 +1,12 @@ - + diff --git a/src/components/service/logoComponent.vue b/src/components/service/logoComponent.vue index e78efe8..6581567 100644 --- a/src/components/service/logoComponent.vue +++ b/src/components/service/logoComponent.vue @@ -1,12 +1,124 @@ - + + + diff --git a/src/i18n/catalog.test.js b/src/i18n/catalog.test.js new file mode 100644 index 0000000..7ded12c --- /dev/null +++ b/src/i18n/catalog.test.js @@ -0,0 +1,62 @@ +import { createI18n } from 'vue-i18n' +import { describe, expect, it } from 'vitest' + +import en from '@/i18n/locales/en.json' +import zh from '@/i18n/locales/zh.json' + +const flattenKeys = (value, prefix = '') => + Object.entries(value).flatMap(([key, child]) => { + const path = prefix ? `${prefix}.${key}` : key + return child && typeof child === 'object' + ? flattenKeys(child, path) + : [path] + }) + +const interpolation = { + count: 1, + course: 'Course', + date: '2030-01-01', + due: '2030-01-01', + email: 'learner@example.com', + learners: 1, + minutes: 60, + name: 'Learner', + page: 1, + publishedCourses: 1, + results: 1, + seconds: 60, + teacher: 'Teacher', + time: '10:00', + timezone: 'Asia/Shanghai', + title: 'Title', + topic: 'Topic', + total: 1, + years: 1 +} + +describe('i18n catalogue', () => { + it('keeps the English and Chinese catalogue structures identical', () => { + expect(flattenKeys(en).sort()).toEqual(flattenKeys(zh).sort()) + }) + + it.each([ + ['en', en], + ['zh', zh] + ])( + 'compiles every %s message without runtime syntax errors', + (locale, messages) => { + const i18n = createI18n({ + legacy: false, + locale, + fallbackLocale: 'en', + messages: { en, zh } + }) + + for (const key of flattenKeys(messages)) { + expect(() => i18n.global.t(key, interpolation), key).not.toThrow() + } + + expect(i18n.global.t('auth.placeholders.email')).toBe('you@example.com') + } + ) +}) diff --git a/src/i18n/index.js b/src/i18n/index.js index 8287fa7..14f53d1 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -2,16 +2,39 @@ import { createI18n } from 'vue-i18n' import zh from '@/i18n/locales/zh.json' import en from '@/i18n/locales/en.json' -const messages = { - zh, - en +export const supportedLocales = ['zh', 'en'] + +export function normalizeLocale(value) { + const locale = String(value || '').toLowerCase() + return locale.startsWith('zh') ? 'zh' : locale.startsWith('en') ? 'en' : null +} + +export function detectInitialLocale() { + if (typeof window === 'undefined') return 'en' + + try { + const stored = normalizeLocale(window.localStorage.getItem('lang')) + if (stored) return stored + } catch { + // Storage may be unavailable in privacy mode; browser detection still works. + } + + const candidates = Array.isArray(window.navigator.languages) + ? window.navigator.languages + : [window.navigator.language] + return candidates.some((locale) => normalizeLocale(locale) === 'zh') + ? 'zh' + : 'en' } +const initialLocale = detectInitialLocale() + const i18n = createI18n({ legacy: false, globalInjection: true, - locale: 'zh', // 默认语言 - messages + locale: initialLocale, + fallbackLocale: 'en', + messages: { zh, en } }) export default i18n diff --git a/src/i18n/index.test.js b/src/i18n/index.test.js new file mode 100644 index 0000000..c3c15ec --- /dev/null +++ b/src/i18n/index.test.js @@ -0,0 +1,42 @@ +import { beforeEach, describe, expect, it } from 'vitest' +import { detectInitialLocale, normalizeLocale } from './index' + +describe('locale detection', () => { + beforeEach(() => { + window.localStorage.clear() + }) + + it('normalizes supported locale variants', () => { + expect(normalizeLocale('zh-CN')).toBe('zh') + expect(normalizeLocale('en-US')).toBe('en') + expect(normalizeLocale('fr-FR')).toBeNull() + }) + + it('prefers a saved locale over browser languages', () => { + window.localStorage.setItem('lang', 'zh') + Object.defineProperty(window.navigator, 'languages', { + configurable: true, + value: ['en-US'] + }) + + expect(detectInitialLocale()).toBe('zh') + }) + + it('uses English for a first visit outside a Chinese browser', () => { + Object.defineProperty(window.navigator, 'languages', { + configurable: true, + value: ['fr-FR', 'en-US'] + }) + + expect(detectInitialLocale()).toBe('en') + }) + + it('uses Chinese when any preferred browser language is Chinese', () => { + Object.defineProperty(window.navigator, 'languages', { + configurable: true, + value: ['en-US', 'zh-CN'] + }) + + expect(detectInitialLocale()).toBe('zh') + }) +}) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 17ef7bd..bc1c5ef 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -7,6 +7,708 @@ "submit": "Submit", "cancel": "Cancel" }, - "login": "Login", - "register": "Register" + "login": "Sign in", + "register": "Register", + "common": { + "skipToContent": "Skip to main content", + "requestFailed": "The request could not be completed. Please try again." + }, + "a11y": { + "skipToContent": "Skip to main content" + }, + "system": { + "errorBoundary": { + "eyebrow": "Something went wrong", + "title": "This page could not be displayed", + "description": "Your data is safe. Reload the page to try again, or return later if the problem continues.", + "reload": "Reload page" + } + }, + "shell": { + "brandHome": "Go to your workspace home", + "menu": { + "open": "Open navigation menu", + "close": "Close navigation menu", + "drawer": "Workspace navigation" + }, + "account": "Account", + "notifications": "Notifications", + "profile": "Profile", + "logout": "Sign out", + "loggingOut": "Signing out…" + }, + "roles": { + "student": { + "label": "Student" + }, + "teacher": { + "label": "Teacher" + }, + "administrator": { + "label": "Administrator" + } + }, + "nav": { + "student": { + "home": "Home", + "teachers": "Teachers", + "courses": "Courses", + "conversation": "Chinese practice" + }, + "teacher": { + "home": "Home", + "requests": "Teaching requests", + "courses": "Courses" + }, + "administrator": { + "operations": "Operations", + "reviews": "Reviews", + "data": "Data" + } + }, + "teacher": { + "account": { + "profile": "Profile", + "security": "Security", + "notices": "Notifications", + "navigationLabel": "Teacher account settings" + } + }, + "auth": { + "brandShort": "International Chinese", + "brandEnglish": "Learning Platform", + "brandHomeLabel": "Go to the public homepage", + "modeNavigation": "Sign-in and registration", + "legalNavigation": "Legal information", + "eyebrow": "Learn beyond the textbook", + "heroTitle": "Make Chinese part of your real life.", + "heroDescription": "Meet verified teachers, choose a learning path that fits your goals, and practise Chinese in situations that matter.", + "quoteCaption": "A thoughtful learning path begins with the right teacher.", + "loginEyebrow": "Welcome back", + "loginTitle": "Continue your learning", + "loginDescription": "Sign in to return to your courses, bookings and conversations.", + "roleLegend": "Choose your role", + "recoveryLink": "Need help signing in?", + "noAccount": "New to the platform?", + "haveAccount": "Already have an account?", + "betaLabel": "Public Beta", + "terms": "Terms of Service", + "privacy": "Privacy Notice", + "trust": { + "verified": "Verified teacher profiles", + "firstParty": "Secure first-party sessions", + "realLearning": "Learning built around real situations" + }, + "roles": { + "student": "Student", + "teacher": "Teacher", + "administrator": "Administrator" + }, + "fields": { + "email": "Email address", + "password": "Password", + "verificationCode": "Verification code", + "confirmPassword": "Confirm password", + "name": "Full name", + "nationality": "Nationality", + "region": "Country or region", + "age": "Age" + }, + "placeholders": { + "email": "you{'@'}example.com", + "password": "Enter your password", + "fullEmail": "Enter your complete email address", + "verificationCode": "Enter the 6-digit code", + "newPassword": "Create a strong password", + "confirmPassword": "Enter the password again", + "name": "How should we address you?", + "nationality": "e.g. Canadian", + "region": "Where do you currently live?", + "age": "Your age" + }, + "actions": { + "login": "Sign in", + "createAccount": "Create account", + "sendCode": "Send code", + "sendingCode": "Sending…", + "next": "Continue", + "back": "Back" + }, + "messages": { + "loginSuccess": "Welcome back.", + "registerSuccess": "Your account has been created.", + "codeSent": "Verification code sent. Check your inbox.", + "codeSentDevelopment": "Verification code generated for this development environment.", + "roleRedirected": "This action is for student accounts. We have taken you to your workspace instead." + }, + "errors": { + "emailRequired": "Enter your email address.", + "emailInvalid": "Enter a valid email address.", + "passwordRequired": "Enter your password.", + "passwordStrength": "Use at least 8 characters with uppercase, lowercase and a number.", + "passwordConfirmationRequired": "Enter your password again.", + "passwordMismatch": "The passwords do not match.", + "invalidProfile": "We could not load your account profile.", + "loginFailed": "Sign-in failed. Check your details and try again.", + "registerFailed": "We could not create your account. Please try again.", + "roleRequired": "Choose whether you are registering as a student or teacher.", + "codeInvalid": "Enter the complete verification code.", + "codeSendFailed": "The code could not be sent. You can retry now.", + "termsRequired": "Please agree to the Terms of Service and Privacy Notice.", + "nameRequired": "Enter your name.", + "nationalityRequired": "Enter your nationality.", + "regionRequired": "Enter your country or region.", + "ageInvalid": "Enter an age between 6 and 120.", + "levelRequired": "Choose your current Chinese level.", + "teacherReviewRequired": "Confirm that you understand the teacher review process." + }, + "register": { + "eyebrow": "Create your account", + "title": "Start with a learning profile that fits you", + "description": "Three short steps help us prepare the right workspace. You can go back at any time.", + "progressLabel": "Registration progress", + "accountTitle": "Secure your account", + "accountDescription": "Choose a role and verify the email you will use to sign in.", + "profileTitle": "Tell us who you are", + "profileDescription": "We use these details to personalise your experience and support your account.", + "studentStepTitle": "Shape your learning path", + "studentStepDescription": "Choose your current level and tell teachers what you hope to achieve.", + "teacherStepTitle": "Prepare your teacher application", + "teacherStepDescription": "Tell us about relevant qualifications. An administrator will review your profile before it becomes public.", + "passwordHint": "Use at least 8 characters, including uppercase and lowercase letters and a number.", + "agreePrefix": "I agree to the", + "and": "and", + "codeCountdown": "Send again in {seconds}s", + "codeSentTo": "Code sent to {email}", + "codeValidity": "The code is valid for 10 minutes.", + "privacyNoteTitle": "Why we ask", + "privacyNote": "These details support account safety, teacher matching and platform operations. They are handled as described in our Privacy Notice.", + "levelLegend": "Current Chinese level", + "learningGoalLabel": "What would you like to do in Chinese?", + "learningGoalPlaceholder": "For example: speak confidently at work, prepare for HSK 4, or talk with family.", + "certificateLegend": "Relevant qualifications", + "certificateHelp": "Select qualifications you hold. This declaration is not a document upload and does not mean your profile is verified.", + "teacherReviewTitle": "Teacher profiles are reviewed", + "teacherReviewDescription": "Your profile will remain private until an administrator reviews the information you submit. We may contact you for supporting documents.", + "teacherReviewAcknowledgement": "I understand that declaring a qualification does not complete verification.", + "steps": { + "account": "Account", + "profile": "Profile", + "learning": "Learning" + }, + "roleDescriptions": { + "student": "Find teachers, book lessons and manage your learning.", + "teacher": "Apply to teach and manage courses after verification." + }, + "levels": { + "beginner": "I am completely new to Chinese.", + "hsk1": "I understand a few familiar words and simple phrases.", + "hsk2": "I can handle basic everyday exchanges.", + "hsk3": "I can communicate in many familiar situations.", + "hsk4": "I can discuss a broad range of everyday and study topics.", + "hsk5": "I can read, listen and communicate with good fluency.", + "hsk6": "I can understand and express complex ideas with confidence." + }, + "certificates": { + "international": "Certificate for Teachers of Chinese to Speakers of Other Languages", + "tcsl": "Teaching Chinese as a Second Language qualification", + "ability": "Certificate of Ability in Teaching Chinese as a Foreign Language", + "mandarin": "Putonghua Proficiency Test certificate" + } + }, + "session": { + "offline": "We could not reach the service. Check your connection and try signing in again.", + "expired": "Your session has expired. Sign in again to continue where you left off.", + "authenticationRequired": "Sign in to continue. We will return you to the page you requested." + }, + "quote": "Across the world, a true friend still feels close." + }, + "public": { + "a11y": { + "skipToContent": "Skip to main content" + }, + "brand": { + "name": "International Chinese", + "tagline": "Learn for real life" + }, + "language": { + "label": "Choose language" + }, + "nav": { + "home": "Home", + "teachers": "Teachers", + "courses": "Courses", + "dashboard": "My workspace", + "signIn": "Sign in", + "createAccount": "Create account", + "primary": "Primary navigation", + "mobile": "Mobile navigation", + "toggleMenu": "Toggle navigation menu" + }, + "footer": { + "mission": "A public Beta platform connecting Chinese learners with verified teachers and practical learning experiences.", + "explore": "Explore", + "legal": "Legal", + "support": "Account support", + "terms": "Terms of Service", + "privacy": "Privacy Notice", + "beta": "Public Beta · Features may change" + }, + "common": { + "applyFilters": "Apply filters", + "clearFilters": "Clear filters", + "teacherLoadErrorTitle": "Teachers could not be loaded", + "courseLoadErrorTitle": "Courses could not be loaded", + "loadErrorBody": "Check your connection and try again. Other parts of the platform may still be available.", + "loadingTeachers": "Loading verified teachers…", + "loadingCourses": "Loading published courses…", + "retry": "Try again", + "pagination": "Pagination", + "previous": "Previous", + "next": "Next", + "pageOf": "Page {page} of {total}", + "breadcrumb": "Breadcrumb" + }, + "teacher": { + "unnamed": "Teacher profile", + "verified": "Verified teacher", + "profilePending": "Professional profile coming soon", + "bioPending": "This teacher is still preparing their introduction.", + "experience": "Experience", + "years": "{count} years", + "courses": "Courses", + "languages": "Teaching languages", + "referenceRate": "Reference rate", + "priceOnRequest": "Ask the teacher", + "viewProfile": "View profile", + "viewNamed": "View {name}'s profile" + }, + "course": { + "untitled": "Untitled course", + "summaryPending": "Course introduction coming soon.", + "teacher": "Teacher", + "duration": "Lesson length", + "minutes": "{count} min", + "referencePrice": "Reference price", + "free": "No fee listed", + "viewDetails": "View course", + "levels": { + "beginner": "Beginner", + "elementary": "Elementary", + "intermediate": "Intermediate", + "advanced": "Advanced", + "all": "All levels" + } + }, + "home": { + "metaTitle": "Learn Chinese for real life", + "metaDescription": "Discover verified Chinese teachers and published courses, then continue from browsing to booking in one secure platform.", + "eyebrow": "International Chinese · Public Beta", + "title": "Learn Chinese for real life, with teachers you can trust.", + "lead": "Explore verified teacher profiles and practical courses before you create an account. When you are ready, continue straight to a booking request.", + "findTeacher": "Find a teacher", + "exploreCourses": "Explore courses", + "trustNote": "Browse first. Registration is only needed when you want to book or join a learning space.", + "stepsEyebrow": "A clear learning path", + "stepsTitle": "From curiosity to conversation in three steps", + "stepsLead": "Choose with real information, agree on a lesson with your teacher, and keep your learning work together.", + "stepOneTitle": "Discover", + "stepOneBody": "Compare verified profiles, teaching languages, specialities and published courses.", + "stepTwoTitle": "Request", + "stepTwoBody": "Send a booking request in your own time zone. The teacher confirms before a lesson is scheduled.", + "stepThreeTitle": "Learn", + "stepThreeBody": "Meet in class, complete assignments and continue practising Chinese between lessons.", + "teachersEyebrow": "People who teach", + "teachersTitle": "Meet verified teachers", + "teachersLead": "Every public profile belongs to an enabled teacher who has passed the platform review.", + "viewAllTeachers": "View all teachers", + "noTeachersTitle": "Our first teachers are preparing", + "noTeachersBody": "The production catalogue is intentionally honest: verified profiles will appear here as teachers complete review.", + "joinAsTeacher": "Apply as a teacher", + "coursesEyebrow": "Ways to learn", + "coursesTitle": "Explore published courses", + "coursesLead": "See lesson focus, level, suggested duration and reference price before deciding what fits.", + "viewAllCourses": "View all courses", + "noCoursesTitle": "Published courses are coming", + "noCoursesBody": "Verified teachers are preparing the first public course descriptions. No sample listings are shown as real inventory.", + "valuesEyebrow": "Designed for both sides", + "valuesTitle": "A calmer way to begin", + "valuesLead": "Learners get clarity before committing; teachers get a professional space built around genuine teaching work.", + "studentValueTitle": "For learners", + "studentValueBody": "Browse openly, keep bookings and assignments organised, and practise Chinese with context.", + "startLearning": "Create a learner account", + "teacherValueTitle": "For teachers", + "teacherValueBody": "Present your expertise, publish courses after review and manage requests in one workspace.", + "finalEyebrow": "Your next conversation", + "finalTitle": "Start by finding the person you want to learn with.", + "finalBody": "Explore public profiles now. Creating an account takes only a few minutes when you are ready to request a lesson." + }, + "teachers": { + "metaTitle": "Verified Chinese teachers", + "metaDescription": "Search verified Chinese teachers by speciality and rating, with transparent profiles and reference rates.", + "eyebrow": "Teacher directory", + "title": "Find a teacher you can trust", + "lead": "Browse enabled, verified profiles and choose by expertise, language and teaching approach.", + "availableCount": "Verified profiles", + "searchLabel": "Search teachers", + "searchPlaceholder": "Name, language or teaching focus", + "specialtyLabel": "Speciality", + "specialtyPlaceholder": "e.g. conversation or HSK", + "ratingLabel": "Minimum rating", + "anyRating": "Any rating", + "ratingFour": "4.0 and above", + "ratingFourFive": "4.5 and above", + "ratingFourEight": "4.8 and above", + "emptyTitle": "No teachers match these filters", + "emptyBody": "Try a broader search or clear the filters. New verified teachers will appear as they complete review.", + "results": "{count} teachers", + "verifiedOnly": "Verified profiles only" + }, + "courses": { + "metaTitle": "Published Chinese courses", + "metaDescription": "Browse published Chinese courses by topic and level before creating an account.", + "eyebrow": "Course directory", + "title": "Choose a course for the Chinese you need", + "lead": "Explore published courses from verified teachers, with clear focus, level and reference pricing.", + "publishedCount": "Published courses", + "searchLabel": "Search courses", + "searchPlaceholder": "Course title, skill or topic", + "categoryLabel": "Category", + "categoryPlaceholder": "e.g. conversation or HSK", + "emptyTitle": "No courses match these filters", + "emptyBody": "Try a broader search or clear the filters. Newly approved courses will appear here.", + "results": "{count} courses", + "publishedOnly": "Published courses only" + }, + "teacherDetail": { + "metaTitle": "Teacher profile", + "metaDescription": "Review this verified Chinese teacher's experience, teaching approach, courses and reference rate.", + "loading": "Loading teacher profile…", + "notFoundTitle": "Teacher profile not found", + "notFoundBody": "This profile may no longer be public, or the address may be incorrect.", + "backToTeachers": "Back to teachers", + "signInToBook": "Sign in to request a lesson", + "requestBooking": "Request a lesson", + "returnToWorkspace": "Return to your workspace", + "confirmationRequired": "The teacher must confirm every booking request.", + "noCharge": "No payment is taken during the public Beta.", + "timezone": "Times are shown in your device time zone: {timezone}.", + "aboutEyebrow": "Teacher profile", + "aboutTitle": "About this teacher", + "location": "Location", + "methodEyebrow": "Teaching approach", + "methodTitle": "How lessons are taught", + "methodPending": "This teacher is still preparing a detailed teaching statement.", + "coursesEyebrow": "Published learning", + "coursesTitle": "Courses from this teacher", + "publishedCourses": "{count} published courses", + "noCoursesTitle": "No published courses yet", + "noCoursesBody": "You can still review the teacher's profile. Their courses will appear after platform review." + }, + "courseDetail": { + "metaTitle": "Course details", + "metaDescription": "Review this published Chinese course's focus, level, teacher, duration and reference price.", + "loading": "Loading course details…", + "notFoundTitle": "Course not found", + "notFoundBody": "This course may no longer be published, or the address may be incorrect.", + "backToCourses": "Back to courses", + "signInToContinue": "Sign in to continue", + "openLearningSpace": "Open learning space", + "returnToWorkspace": "Return to your workspace", + "suggestedCapacity": "Suggested group size", + "learners": "{count} learners", + "meetTeacher": "View teacher profile", + "noPurchaseNotice": "This public Beta does not process course purchases. Prices are shown for reference only.", + "overviewEyebrow": "Course overview", + "overviewTitle": "What you will work on", + "descriptionPending": "The teacher is still preparing the full course description.", + "teacherEyebrow": "Your teacher", + "verifiedTeacherNote": "This course is published by a verified, enabled teacher.", + "viewTeacherProfile": "View full teacher profile", + "nextEyebrow": "Ready to continue?", + "nextTitle": "Choose the teacher before the timetable", + "nextBody": "Review the teacher's profile and request a lesson when you are signed in." + }, + "recovery": { + "metaTitle": "Account support", + "metaDescription": "Contact real account support for sign-in and recovery help during the public Beta.", + "emailSubject": "International Chinese account support", + "eyebrow": "Account support", + "title": "Get back to your learning safely", + "lead": "Automated password reset is not available in this Beta. Our support contact can help verify your request.", + "stepOneTitle": "Use your registered email", + "stepOneBody": "Contact us from the email address connected to your account whenever possible.", + "stepTwoTitle": "Describe the issue", + "stepTwoBody": "Tell us whether you cannot sign in, changed email access, or noticed unusual activity. Never send your password.", + "stepThreeTitle": "Wait for verification", + "stepThreeBody": "Support may ask for non-sensitive account details before making any change.", + "securityTitle": "Protect your credentials", + "securityBody": "We will never ask for your password, verification code or Gmail app password by email.", + "emailSupport": "Email account support", + "backToSignIn": "Back to sign in", + "supportAddress": "Support address:" + }, + "system": { + "403": { + "eyebrow": "Access restricted", + "title": "This area belongs to another role", + "body": "Your account is signed in, but it does not have permission to open this page. Return to your workspace or go back." + }, + "404": { + "eyebrow": "Page not found", + "title": "We could not find that page", + "body": "The address may be outdated or incomplete. Explore the public teacher and course directories instead." + }, + "backHome": "Back to homepage", + "goBack": "Go back" + }, + "legal": { + "eyebrow": "Public Beta information", + "versionLabel": "Version", + "version": "Beta 1.0", + "updatedLabel": "Last updated", + "updatedDate": "9 August 2026", + "onThisPage": "On this page", + "betaNoticeTitle": "Please review before public promotion", + "betaNoticeBody": "This Beta text describes the platform's current behaviour and is provided for product transparency. It is not a substitute for professional legal review.", + "contactTitle": "Questions or account requests", + "contactBody": "Use the support page to reach the current platform contact. Do not include passwords or verification codes.", + "contactAction": "Contact support", + "terms": { + "title": "Terms of Service", + "lead": "These terms explain the practical rules for using the International Chinese public Beta.", + "sections": { + "service": { + "title": "The Beta service", + "body": "The platform supports account registration, public teacher and course discovery, booking requests, classrooms, assignments, notifications and Chinese practice. Beta features may change, pause or be withdrawn as the service develops." + }, + "accounts": { + "title": "Accounts and roles", + "body": "You must provide accurate information, protect your credentials and use only the role assigned to you. Teacher profiles and courses remain non-public until the relevant platform review is complete." + }, + "publicContent": { + "title": "Public profiles and courses", + "body": "Only enabled, verified teachers and published courses are intended to appear publicly. Reference prices, suggested capacity and descriptions are informational and do not create a purchase or guaranteed place." + }, + "bookings": { + "title": "Booking requests", + "body": "A learner's request is not confirmed until the teacher accepts it. The current Beta does not take payment. Times use the learner's device time zone and should be checked by both parties." + }, + "conduct": { + "title": "Respectful use", + "body": "Do not misuse another person's account, disrupt classes, upload unlawful material, harass users or attempt to bypass access controls. We may restrict an account to protect users and the service." + }, + "availability": { + "title": "Availability and limitations", + "body": "The Beta is provided on an evolving basis without guaranteed uninterrupted availability. Real-time audio and video may not connect on strict networks because TURN relay service is not yet included." + } + } + }, + "privacy": { + "title": "Privacy Notice", + "lead": "This notice summarises what the public Beta currently stores and why.", + "sections": { + "data": { + "title": "Information we collect", + "body": "We store registration details, role and profile information, session records, learning activity, bookings, classes, assignments, messages, notifications and files you intentionally provide." + }, + "use": { + "title": "How information is used", + "body": "Information is used to operate accounts, verify teachers, publish approved content, coordinate learning, protect the service and provide account support. The Beta does not use a paid external AI service." + }, + "sessions": { + "title": "Sessions and security", + "body": "Authentication uses database-backed sessions and secure HttpOnly cookies in production. Verification codes expire, and sensitive service credentials are not stored in browser code." + }, + "files": { + "title": "Files and object storage", + "body": "Private uploads are stored in access-controlled object storage. Downloads require an authorisation check before a short-lived signed link is issued. File type, size and quota rules apply." + }, + "retention": { + "title": "Retention and Beta operations", + "body": "Data is retained while needed to operate the Beta, maintain security and meet reasonable support obligations. Backups may remain for their scheduled retention period after an operational change." + }, + "rights": { + "title": "Questions and requests", + "body": "To ask about your data or request an account change, use the published support contact. We may need to verify that the request belongs to the account holder before acting." + } + } + } + }, + "booking": { + "noChargeNotice": "No payment is taken during the public Beta.", + "submitting": "Sending…", + "dialogTitle": "Request a lesson", + "successBody": "Your request has been sent to {teacher}. It is not confirmed until the teacher accepts it.", + "courseLabel": "Course", + "durationOption": "{minutes} minutes", + "intro": "Send {teacher} a lesson request. You can review the details before submitting.", + "defaultTopic": "Practical Chinese lesson", + "durationLegend": "Lesson duration", + "cancel": "Cancel", + "messageLabel": "Message to the teacher (optional)", + "pendingStatus": "Waiting for teacher confirmation", + "messagePlaceholder": "Share your goals, questions or anything the teacher should know.", + "topicPlaceholder": "For example: workplace introductions or HSK 4 speaking", + "errors": { + "startInvalid": "Choose a valid start time.", + "startTooLate": "That date is too far ahead. Choose an earlier time.", + "topicRequired": "Tell the teacher what you would like to work on.", + "conflict": "That time is no longer available. Choose another time and try again.", + "startPast": "Choose a time in the future.", + "submitFailed": "Your request could not be sent. Check your connection and try again." + }, + "startLabel": "Preferred start time", + "successTime": "Requested time: {time}", + "successTitle": "Lesson request sent", + "submit": "Send request", + "confirmationNotice": "The teacher must confirm this request before it becomes a scheduled lesson.", + "timezoneHelp": "Times are shown in your device time zone: {timezone}.", + "customCourseOption": "A custom lesson with this teacher", + "topicLabel": "Learning topic", + "goToStudentHome": "Go to student home", + "stayHere": "Stay on this page", + "statusLabel": "Request status" + } + }, + "account": { + "password": { + "mustResetDescription": "For your security, create a new password before continuing to your workspace.", + "description": "Choose a strong password that you do not use for another service.", + "new": "New password", + "mustResetTitle": "Change your temporary password", + "confirm": "Confirm new password", + "eyebrow": "Account security", + "rules": { + "number": "At least one number", + "letter": "Uppercase and lowercase letters", + "length": "At least 8 characters" + }, + "errors": { + "strength": "Your new password does not meet all security requirements.", + "failed": "The password could not be changed. Check your current password and try again.", + "mismatch": "The new passwords do not match." + }, + "success": "Password updated successfully.", + "submit": "Update password", + "title": "Change password", + "current": "Current password" + } + }, + "studentHome": { + "learner": "Learner", + "common": { + "notAvailable": "Not available", + "priceOnRequest": "Ask the teacher", + "teacherFallback": "Chinese teacher" + }, + "header": { + "title": "Good to see you, {name}.", + "lead": "Your workspace brings the next useful step forward, without turning learning into a wall of numbers.", + "timezone": "Times use your device time zone: {timezone}" + }, + "focus": { + "seal": "NOW", + "lessonEyebrow": "Today's focus · Live lesson", + "lessonTitle": "Your next conversation: {topic}", + "lessonDescription": "Meet {teacher} at {time}. Your confirmed classroom is ready.", + "assignmentEyebrow": "Today's focus · Assignment", + "assignmentTitle": "Continue “{title}”", + "assignmentDescription": "From {course} · due {due}. Your saved progress stays with the assignment.", + "requestEyebrow": "Today's focus · Booking", + "requestTitle": "Your lesson request is with the teacher", + "requestDescription": "{teacher} is reviewing your request for {time}. You can check the teacher profile while you wait.", + "discoverEyebrow": "Today's focus · Discover", + "discoverTitle": "Find the teacher who fits your Chinese", + "discoverDescription": "Browse verified profiles and choose by teaching focus, language and reference rate." + }, + "actions": { + "enterClassroom": "Enter classroom", + "continueAssignment": "Continue assignment", + "viewTeacher": "View teacher", + "findTeacher": "Find a teacher", + "retry": "Try again", + "confirmCancel": "Cancel booking", + "keepBooking": "Keep booking", + "cancelling": "Cancelling…", + "cancelBooking": "Cancel", + "openAssignments": "Open assignments", + "viewAllTeachers": "View all teachers", + "viewAllCourses": "View all courses", + "viewCourse": "View course" + }, + "appointments": { + "eyebrow": "Next confirmed lesson", + "title": "Your next live class", + "minutes": "{count} min", + "defaultTopic": "Chinese conversation practice", + "cancelTitle": "Cancel this booking?", + "cancelConfirm": "Cancel “{topic}”? The teacher will be notified.", + "emptyMark": "OPEN", + "emptyTitle": "No confirmed lesson yet", + "emptyBody": "Choose a verified teacher when you are ready to schedule your next conversation.", + "pendingTitle": "Waiting for teacher confirmation", + "status": { + "pending": "Awaiting confirmation", + "accepted": "Confirmed", + "rejected": "Not accepted", + "cancelled": "Cancelled", + "completed": "Completed", + "unknown": "Status unavailable" + } + }, + "assignments": { + "eyebrow": "Learning work", + "title": "Assignments to finish", + "noDeadline": "No deadline", + "defaultTitle": "Learning assignment", + "courseFallback": "Published course", + "due": "Due {date}", + "draft": "Saved draft", + "notStarted": "Not started", + "draftMark": "D", + "taskMark": "A", + "emptyMark": "CLEAR", + "emptyTitle": "You are clear for now", + "emptyBody": "Published assignments that still need your work will appear here." + }, + "teachers": { + "eyebrow": "Verified guidance", + "title": "Teachers worth meeting", + "lead": "These are real, enabled teacher profiles that have passed platform review.", + "avatarAlt": "Portrait of {name}", + "seal": "T", + "verified": "Verified", + "profilePending": "Professional profile in progress", + "experience": "Experience", + "years": "{count} years", + "referenceRate": "Reference rate", + "emptyTitle": "Verified teachers are preparing", + "emptyBody": "No sample profiles are shown as real teachers. Approved profiles will appear here." + }, + "courses": { + "eyebrow": "Published learning", + "title": "Courses to explore", + "lead": "Review the focus, teacher, lesson length and reference price before choosing.", + "coverAlt": "Cover for {title}", + "coverMark": "CHINESE · IN CONTEXT", + "defaultTitle": "Chinese course", + "categoryFallback": "General Chinese", + "summaryPending": "The teacher is preparing the course introduction.", + "minutes": "{count} min", + "emptyMark": "C", + "emptyTitle": "Published courses are coming", + "emptyBody": "Courses will appear after a verified teacher publishes them and platform review is complete." + }, + "messages": { + "cancelled": "The booking has been cancelled." + }, + "errors": { + "schedule": "Your schedule could not be loaded. Other learning tools are still available.", + "assignments": "Assignments could not be loaded. Try this section again.", + "teachers": "Teacher recommendations could not be loaded. Course discovery still works.", + "courses": "Course recommendations could not be loaded. Teacher discovery still works.", + "cancel": "The booking could not be cancelled. Please try again." + } + } } diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 4b826f6..f780979 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -8,5 +8,707 @@ "cancel": "取消" }, "login": "登录", - "register": "注册" + "register": "注册", + "common": { + "skipToContent": "跳到主要内容", + "requestFailed": "请求未能完成,请稍后重试。" + }, + "a11y": { + "skipToContent": "跳到主要内容" + }, + "system": { + "errorBoundary": { + "eyebrow": "页面出现问题", + "title": "暂时无法显示此页面", + "description": "你的数据不会丢失。请重新加载页面;如果问题仍然存在,可以稍后再试。", + "reload": "重新加载" + } + }, + "shell": { + "brandHome": "返回工作台首页", + "menu": { + "open": "打开导航菜单", + "close": "关闭导航菜单", + "drawer": "工作台导航" + }, + "account": "账户", + "notifications": "通知", + "profile": "个人资料", + "logout": "退出登录", + "loggingOut": "正在退出…" + }, + "roles": { + "student": { + "label": "学生" + }, + "teacher": { + "label": "教师" + }, + "administrator": { + "label": "管理员" + } + }, + "nav": { + "student": { + "home": "首页", + "teachers": "找教师", + "courses": "选课程", + "conversation": "中文练习" + }, + "teacher": { + "home": "首页", + "requests": "教学对接", + "courses": "课程管理" + }, + "administrator": { + "operations": "平台运营", + "reviews": "审核中心", + "data": "数据中心" + } + }, + "teacher": { + "account": { + "profile": "个人资料", + "security": "安全设置", + "notices": "消息通知", + "navigationLabel": "教师账户设置" + } + }, + "auth": { + "brandShort": "国际中文教育", + "brandEnglish": "International Chinese", + "brandHomeLabel": "返回公开首页", + "modeNavigation": "登录与注册", + "legalNavigation": "法律信息", + "eyebrow": "走出课本,走进真实生活", + "heroTitle": "让中文真正走进你的生活。", + "heroDescription": "认识经过认证的教师,选择适合目标的学习路径,在真正重要的场景里使用中文。", + "quoteCaption": "合适的教师,是一段好学习旅程的起点。", + "loginEyebrow": "欢迎回来", + "loginTitle": "继续你的中文学习", + "loginDescription": "登录后回到课程、预约与中文练习。", + "roleLegend": "选择登录身份", + "recoveryLink": "登录遇到问题?", + "noAccount": "第一次使用平台?", + "haveAccount": "已经有账户?", + "betaLabel": "公开测试版", + "terms": "服务条款", + "privacy": "隐私说明", + "trust": { + "verified": "教师资料经过平台认证", + "firstParty": "安全的第一方会话", + "realLearning": "围绕真实场景学习" + }, + "roles": { + "student": "学生", + "teacher": "教师", + "administrator": "管理员" + }, + "fields": { + "email": "邮箱", + "password": "密码", + "verificationCode": "验证码", + "confirmPassword": "确认密码", + "name": "姓名", + "nationality": "国籍", + "region": "国家或地区", + "age": "年龄" + }, + "placeholders": { + "email": "you{'@'}example.com", + "password": "请输入密码", + "fullEmail": "请输入完整邮箱地址", + "verificationCode": "请输入 6 位验证码", + "newPassword": "设置一个强密码", + "confirmPassword": "请再次输入密码", + "name": "我们应该如何称呼你?", + "nationality": "例如:加拿大", + "region": "你目前居住在哪里?", + "age": "请输入年龄" + }, + "actions": { + "login": "登录", + "createAccount": "创建账户", + "sendCode": "发送验证码", + "sendingCode": "正在发送…", + "next": "继续", + "back": "返回" + }, + "messages": { + "loginSuccess": "欢迎回来。", + "registerSuccess": "账户创建成功。", + "codeSent": "验证码已发送,请查看邮箱。", + "codeSentDevelopment": "开发环境验证码已生成。", + "roleRedirected": "该操作仅面向学生账户,已为你返回对应工作台。" + }, + "errors": { + "emailRequired": "请输入邮箱。", + "emailInvalid": "请输入有效的邮箱地址。", + "passwordRequired": "请输入密码。", + "passwordStrength": "密码至少 8 位,并包含大小写字母和数字。", + "passwordConfirmationRequired": "请再次输入密码。", + "passwordMismatch": "两次输入的密码不一致。", + "invalidProfile": "暂时无法读取账户资料。", + "loginFailed": "登录失败,请检查信息后重试。", + "registerFailed": "账户创建失败,请稍后重试。", + "roleRequired": "请选择学生或教师身份。", + "codeInvalid": "请输入完整验证码。", + "codeSendFailed": "验证码发送失败,可以立即重试。", + "termsRequired": "请先同意服务条款和隐私说明。", + "nameRequired": "请输入姓名。", + "nationalityRequired": "请输入国籍。", + "regionRequired": "请输入国家或地区。", + "ageInvalid": "请输入 6 至 120 之间的年龄。", + "levelRequired": "请选择当前中文水平。", + "teacherReviewRequired": "请确认你已了解教师审核流程。" + }, + "register": { + "eyebrow": "创建账户", + "title": "从一份适合你的学习档案开始", + "description": "三个简短步骤即可建立工作台,过程中可以随时返回修改。", + "progressLabel": "注册进度", + "accountTitle": "保护你的账户", + "accountDescription": "选择身份,并验证今后用于登录的邮箱。", + "profileTitle": "介绍一下你自己", + "profileDescription": "这些信息用于个性化体验、教师匹配和账户支持。", + "studentStepTitle": "规划你的学习路径", + "studentStepDescription": "选择当前水平,并告诉教师你希望实现的目标。", + "teacherStepTitle": "准备教师申请", + "teacherStepDescription": "声明相关资质。管理员审核通过后,资料才会公开。", + "passwordHint": "至少 8 位,并包含大小写字母和数字。", + "agreePrefix": "我已阅读并同意", + "and": "和", + "codeCountdown": "{seconds} 秒后可重新发送", + "codeSentTo": "验证码已发送至 {email}", + "codeValidity": "验证码 10 分钟内有效。", + "privacyNoteTitle": "为什么需要这些信息", + "privacyNote": "这些资料用于账户安全、教师匹配与平台运营,具体处理方式见隐私说明。", + "levelLegend": "当前中文水平", + "learningGoalLabel": "你希望用中文完成什么?", + "learningGoalPlaceholder": "例如:在工作中自信交流、准备 HSK 4,或与家人沟通。", + "certificateLegend": "相关资质", + "certificateHelp": "请选择你已取得的资质。这里仅作声明,不是证书上传,也不代表已经通过认证。", + "teacherReviewTitle": "教师资料需要审核", + "teacherReviewDescription": "管理员审核前,你的资料不会公开。平台可能联系你补充证明材料。", + "teacherReviewAcknowledgement": "我明白,声明资质不等于完成教师认证。", + "steps": { + "account": "账户", + "profile": "资料", + "learning": "学习" + }, + "roleDescriptions": { + "student": "寻找教师、预约课程并管理学习进度。", + "teacher": "申请成为教师,认证后管理课程与教学。" + }, + "levels": { + "beginner": "我还没有学过中文。", + "hsk1": "我能理解少量常用词和简单短语。", + "hsk2": "我能完成基础日常交流。", + "hsk3": "我能应对许多熟悉的生活场景。", + "hsk4": "我能讨论较广泛的生活和学习话题。", + "hsk5": "我的听读和表达较为流畅。", + "hsk6": "我能理解并自信表达复杂观点。" + }, + "certificates": { + "international": "国际中文教师证书", + "tcsl": "对外汉语教师资格证", + "ability": "汉语作为外语教学能力证书", + "mandarin": "普通话水平测试证书" + } + }, + "session": { + "offline": "暂时无法连接服务,请检查网络后重新登录。", + "expired": "登录会话已过期,请重新登录后继续刚才的操作。", + "authenticationRequired": "请先登录,完成后会返回你刚才访问的页面。" + }, + "quote": "海内存知己,天涯若比邻。" + }, + "public": { + "a11y": { + "skipToContent": "跳到主要内容" + }, + "brand": { + "name": "国际中文教育", + "tagline": "学以致用,走进生活" + }, + "language": { + "label": "选择语言" + }, + "nav": { + "home": "首页", + "teachers": "找教师", + "courses": "选课程", + "dashboard": "我的工作台", + "signIn": "登录", + "createAccount": "创建账户", + "primary": "主导航", + "mobile": "移动端导航", + "toggleMenu": "展开或收起导航菜单" + }, + "footer": { + "mission": "连接中文学习者、认证教师与真实学习场景的公开测试平台。", + "explore": "探索", + "legal": "法律信息", + "support": "账户支持", + "terms": "服务条款", + "privacy": "隐私说明", + "beta": "公开测试版 · 功能可能调整" + }, + "common": { + "applyFilters": "应用筛选", + "clearFilters": "清除筛选", + "teacherLoadErrorTitle": "暂时无法加载教师", + "courseLoadErrorTitle": "暂时无法加载课程", + "loadErrorBody": "请检查网络后重试,平台其他功能可能仍可使用。", + "loadingTeachers": "正在加载认证教师…", + "loadingCourses": "正在加载已发布课程…", + "retry": "重试", + "pagination": "分页", + "previous": "上一页", + "next": "下一页", + "pageOf": "第 {page} 页,共 {total} 页", + "breadcrumb": "面包屑导航" + }, + "teacher": { + "unnamed": "教师资料", + "verified": "认证教师", + "profilePending": "专业资料正在完善", + "bioPending": "教师正在准备个人介绍。", + "experience": "教学经验", + "years": "{count} 年", + "courses": "课程", + "languages": "授课语言", + "referenceRate": "参考价格", + "priceOnRequest": "请咨询教师", + "viewProfile": "查看资料", + "viewNamed": "查看 {name} 的资料" + }, + "course": { + "untitled": "未命名课程", + "summaryPending": "课程简介正在完善。", + "teacher": "授课教师", + "duration": "课时长度", + "minutes": "{count} 分钟", + "referencePrice": "参考价格", + "free": "暂未标价", + "viewDetails": "查看课程", + "levels": { + "beginner": "入门", + "elementary": "初级", + "intermediate": "中级", + "advanced": "高级", + "all": "不限水平" + } + }, + "home": { + "metaTitle": "把中文学到真实场景里", + "metaDescription": "浏览认证中文教师与已发布课程,从公开了解一路顺畅进入注册和预约。", + "eyebrow": "国际中文教育 · 公开测试版", + "title": "和可信赖的老师一起,把中文学到真实场景里。", + "lead": "注册前即可查看认证教师与实用课程;决定继续时,可以直接发起预约请求。", + "findTeacher": "寻找教师", + "exploreCourses": "浏览课程", + "trustNote": "先浏览,再决定。只有预约或进入学习空间时才需要注册。", + "stepsEyebrow": "清晰的学习路径", + "stepsTitle": "从好奇到开口,只需三步", + "stepsLead": "用真实信息做选择,与教师确认课堂,并把后续学习集中管理。", + "stepOneTitle": "发现", + "stepOneBody": "比较认证资料、授课语言、教学专长与已发布课程。", + "stepTwoTitle": "预约", + "stepTwoBody": "按你的时区提交预约,由教师确认后才会正式排课。", + "stepThreeTitle": "学习", + "stepThreeBody": "进入课堂、完成作业,并在课间持续练习中文。", + "teachersEyebrow": "认识教师", + "teachersTitle": "找到值得信赖的教师", + "teachersLead": "所有公开资料都来自已启用并通过平台审核的教师。", + "viewAllTeachers": "查看全部教师", + "noTeachersTitle": "首批教师正在准备", + "noTeachersBody": "生产环境不会用演示资料充数;教师通过审核后,真实资料会显示在这里。", + "joinAsTeacher": "申请成为教师", + "coursesEyebrow": "选择学习方式", + "coursesTitle": "探索已发布课程", + "coursesLead": "先了解课程重点、水平、建议时长和参考价格,再判断是否适合。", + "viewAllCourses": "查看全部课程", + "noCoursesTitle": "课程正在准备", + "noCoursesBody": "认证教师正在完善首批公开课程。平台不会把演示内容伪装成真实课程。", + "valuesEyebrow": "兼顾学习与教学", + "valuesTitle": "从容地开始一段学习", + "valuesLead": "学生在行动前看清信息,教师则拥有围绕真实教学工作的专业空间。", + "studentValueTitle": "面向学习者", + "studentValueBody": "开放浏览,集中管理预约与作业,在具体语境中练习中文。", + "startLearning": "创建学生账户", + "teacherValueTitle": "面向教师", + "teacherValueBody": "展示专业能力,审核后发布课程,并在同一工作台处理教学请求。", + "finalEyebrow": "下一次中文交流", + "finalTitle": "先找到那个你愿意跟随学习的人。", + "finalBody": "现在就浏览公开资料;准备预约时,几分钟即可完成账户创建。" + }, + "teachers": { + "metaTitle": "认证中文教师", + "metaDescription": "按专长与评分寻找认证中文教师,查看透明资料和参考价格。", + "eyebrow": "教师目录", + "title": "寻找值得信赖的教师", + "lead": "浏览已启用、已认证的教师资料,按专长、语言与教学方式选择。", + "availableCount": "认证教师", + "searchLabel": "搜索教师", + "searchPlaceholder": "姓名、语言或教学方向", + "specialtyLabel": "教学专长", + "specialtyPlaceholder": "例如:口语或 HSK", + "ratingLabel": "最低评分", + "anyRating": "不限评分", + "ratingFour": "4.0 分及以上", + "ratingFourFive": "4.5 分及以上", + "ratingFourEight": "4.8 分及以上", + "emptyTitle": "没有符合条件的教师", + "emptyBody": "请扩大搜索范围或清除筛选。新教师通过认证后会出现在这里。", + "results": "共 {count} 位教师", + "verifiedOnly": "仅显示认证资料" + }, + "courses": { + "metaTitle": "已发布中文课程", + "metaDescription": "注册前即可按主题与水平浏览认证教师发布的中文课程。", + "eyebrow": "课程目录", + "title": "选择真正用得上的中文课程", + "lead": "查看认证教师发布的课程,清楚了解重点、水平与参考价格。", + "publishedCount": "已发布课程", + "searchLabel": "搜索课程", + "searchPlaceholder": "课程名称、技能或主题", + "categoryLabel": "课程分类", + "categoryPlaceholder": "例如:口语或 HSK", + "emptyTitle": "没有符合条件的课程", + "emptyBody": "请扩大搜索范围或清除筛选。新课程审核通过后会显示在这里。", + "results": "共 {count} 门课程", + "publishedOnly": "仅显示已发布课程" + }, + "teacherDetail": { + "metaTitle": "教师资料", + "metaDescription": "查看认证中文教师的经验、教学方式、课程与参考价格。", + "loading": "正在加载教师资料…", + "notFoundTitle": "没有找到教师资料", + "notFoundBody": "该资料可能已不再公开,或访问地址有误。", + "backToTeachers": "返回教师目录", + "signInToBook": "登录后预约", + "requestBooking": "发起预约", + "returnToWorkspace": "返回工作台", + "confirmationRequired": "所有预约都需要教师确认。", + "noCharge": "公开测试期间不会收取费用。", + "timezone": "时间按你的设备时区显示:{timezone}。", + "aboutEyebrow": "教师资料", + "aboutTitle": "关于这位教师", + "location": "所在地区", + "methodEyebrow": "教学方式", + "methodTitle": "课堂如何进行", + "methodPending": "教师正在完善详细的教学说明。", + "coursesEyebrow": "已发布内容", + "coursesTitle": "这位教师的课程", + "publishedCourses": "共 {count} 门已发布课程", + "noCoursesTitle": "暂时没有已发布课程", + "noCoursesBody": "你仍可先了解教师资料;课程通过平台审核后会显示在这里。" + }, + "courseDetail": { + "metaTitle": "课程详情", + "metaDescription": "查看已发布中文课程的重点、水平、教师、时长与参考价格。", + "loading": "正在加载课程详情…", + "notFoundTitle": "没有找到课程", + "notFoundBody": "该课程可能已不再发布,或访问地址有误。", + "backToCourses": "返回课程目录", + "signInToContinue": "登录后继续", + "openLearningSpace": "进入学习空间", + "returnToWorkspace": "返回工作台", + "suggestedCapacity": "建议人数", + "learners": "{count} 人", + "meetTeacher": "查看教师资料", + "noPurchaseNotice": "公开测试版暂不处理课程购买,页面价格仅供参考。", + "overviewEyebrow": "课程概览", + "overviewTitle": "你会学习什么", + "descriptionPending": "教师正在完善完整课程说明。", + "teacherEyebrow": "授课教师", + "verifiedTeacherNote": "该课程由已启用的认证教师发布。", + "viewTeacherProfile": "查看完整教师资料", + "nextEyebrow": "准备继续?", + "nextTitle": "先选择教师,再确认时间", + "nextBody": "了解教师资料,登录后即可发起课堂预约。" + }, + "recovery": { + "metaTitle": "账户支持", + "metaDescription": "公开测试期间,通过真实支持邮箱获取登录与账户恢复帮助。", + "emailSubject": "国际中文教育平台账户支持", + "eyebrow": "账户支持", + "title": "安全地回到你的学习", + "lead": "测试版暂未提供自动重置密码。你可以联系真实支持人员核验请求。", + "stepOneTitle": "使用注册邮箱", + "stepOneBody": "请尽量使用账户绑定的邮箱联系我们。", + "stepTwoTitle": "说明问题", + "stepTwoBody": "请说明无法登录、邮箱失效或异常活动等情况,但不要发送密码。", + "stepThreeTitle": "等待身份核验", + "stepThreeBody": "修改账户前,支持人员可能要求提供非敏感的账户信息。", + "securityTitle": "保护你的凭据", + "securityBody": "我们不会通过邮件索要密码、验证码或 Gmail 应用专用密码。", + "emailSupport": "发送邮件给账户支持", + "backToSignIn": "返回登录", + "supportAddress": "支持邮箱:" + }, + "system": { + "403": { + "eyebrow": "访问受限", + "title": "这个页面属于其他角色", + "body": "你的账户已登录,但没有权限打开此页面。请返回自己的工作台或上一页。" + }, + "404": { + "eyebrow": "页面不存在", + "title": "没有找到这个页面", + "body": "地址可能已失效或不完整。你可以继续浏览公开教师和课程目录。" + }, + "backHome": "返回首页", + "goBack": "返回上一页" + }, + "legal": { + "eyebrow": "公开测试版信息", + "versionLabel": "版本", + "version": "Beta 1.0", + "updatedLabel": "更新日期", + "updatedDate": "2026 年 8 月 9 日", + "onThisPage": "本页内容", + "betaNoticeTitle": "正式推广前请完成专业复核", + "betaNoticeBody": "本文案按平台当前真实行为说明测试版能力,用于产品透明度,不替代专业法律意见。", + "contactTitle": "问题与账户请求", + "contactBody": "请通过支持页面联系当前平台负责人,不要提交密码或验证码。", + "contactAction": "联系支持", + "terms": { + "title": "服务条款", + "lead": "本条款说明国际中文教育平台公开测试版的基本使用规则。", + "sections": { + "service": { + "title": "测试版服务", + "body": "平台目前支持账户注册、公开浏览教师与课程、提交预约、在线课堂、作业、通知和中文练习。随着产品完善,测试功能可能调整、暂停或下线。" + }, + "accounts": { + "title": "账户与角色", + "body": "你应提供准确信息、妥善保管登录凭据,并仅使用账户对应的角色权限。教师资料和课程只有在完成相应平台审核后才会公开。" + }, + "publicContent": { + "title": "公开资料与课程", + "body": "平台仅应公开已启用、已认证的教师和已发布课程。参考价格、建议人数与课程描述仅作信息展示,不构成购买、录取或席位保证。" + }, + "bookings": { + "title": "预约请求", + "body": "学生提交请求后,只有教师接受才算确认。当前测试版不收取费用。时间按学生设备时区显示,双方都应在上课前再次核对。" + }, + "conduct": { + "title": "合理使用", + "body": "请勿冒用账户、干扰课堂、上传违法内容、骚扰用户或绕过权限控制。为保护用户与服务,平台可以限制存在风险的账户。" + }, + "availability": { + "title": "可用性与限制", + "body": "测试版持续迭代,不保证服务始终不中断。由于尚未部署 TURN 中继,严格网络环境下可能无法建立实时音视频连接。" + } + } + }, + "privacy": { + "title": "隐私说明", + "lead": "本说明概括公开测试版当前保存的数据及其用途。", + "sections": { + "data": { + "title": "我们收集的信息", + "body": "平台会保存注册信息、角色与资料、会话记录、学习活动、预约、课堂、作业、消息、通知,以及你主动提供的文件。" + }, + "use": { + "title": "信息用途", + "body": "这些信息用于运营账户、认证教师、发布已审核内容、协作学习、保护服务和提供账户支持。测试版目前不使用付费外部 AI 服务。" + }, + "sessions": { + "title": "会话与安全", + "body": "生产环境使用数据库会话和安全的 HttpOnly Cookie 完成认证。验证码会过期,敏感服务凭据不会写入浏览器代码。" + }, + "files": { + "title": "文件与对象存储", + "body": "私有文件保存在受权限控制的对象存储中。下载前会先核验权限,再签发短时有效链接;文件类型、大小和配额均有限制。" + }, + "retention": { + "title": "保留与测试运营", + "body": "数据会在运营测试版、维护安全和处理合理支持请求所需期间保留。运维变更后,备份仍可能保留至既定周期结束。" + }, + "rights": { + "title": "查询与请求", + "body": "如需查询数据或变更账户,请使用公开支持渠道。执行操作前,平台可能需要核验请求人确为账户持有人。" + } + } + } + }, + "booking": { + "noChargeNotice": "公开测试期间不会收取费用。", + "submitting": "正在发送…", + "dialogTitle": "发起课堂预约", + "successBody": "预约已发送给 {teacher};教师接受前,该课堂尚未确认。", + "courseLabel": "关联课程", + "durationOption": "{minutes} 分钟", + "intro": "向 {teacher} 提交课堂请求,发送前可以核对全部信息。", + "defaultTopic": "实用中文课堂", + "durationLegend": "课堂时长", + "cancel": "取消", + "messageLabel": "给教师留言(选填)", + "pendingStatus": "等待教师确认", + "messagePlaceholder": "可以补充学习目标、问题或希望教师提前了解的信息。", + "topicPlaceholder": "例如:职场自我介绍或 HSK 4 口语", + "errors": { + "startInvalid": "请选择有效的开始时间。", + "startTooLate": "预约日期距离现在太远,请选择更早的时间。", + "topicRequired": "请告诉教师你希望学习的内容。", + "conflict": "该时段已不可用,请重新选择时间后再试。", + "startPast": "请选择未来的时间。", + "submitFailed": "预约发送失败,请检查网络后重试。" + }, + "startLabel": "期望开始时间", + "successTime": "预约时间:{time}", + "successTitle": "预约请求已发送", + "submit": "发送预约", + "confirmationNotice": "教师接受后,预约才会成为正式课堂。", + "timezoneHelp": "时间按你的设备时区显示:{timezone}。", + "customCourseOption": "与这位教师定制一节课", + "topicLabel": "学习主题", + "goToStudentHome": "前往学生首页", + "stayHere": "留在当前页面", + "statusLabel": "预约状态" + } + }, + "account": { + "password": { + "mustResetDescription": "为保护账户安全,请设置新密码后再进入工作台。", + "description": "请设置一个未在其他服务中使用的强密码。", + "new": "新密码", + "mustResetTitle": "请先修改临时密码", + "confirm": "确认新密码", + "eyebrow": "账户安全", + "rules": { + "number": "至少包含一个数字", + "letter": "同时包含大写和小写字母", + "length": "至少 8 个字符" + }, + "errors": { + "strength": "新密码未满足全部安全要求。", + "failed": "密码修改失败,请检查当前密码后重试。", + "mismatch": "两次输入的新密码不一致。" + }, + "success": "密码修改成功。", + "submit": "更新密码", + "title": "修改密码", + "current": "当前密码" + } + }, + "studentHome": { + "learner": "学习者", + "common": { + "notAvailable": "暂无信息", + "priceOnRequest": "请咨询教师", + "teacherFallback": "中文教师" + }, + "header": { + "title": "{name},今天继续把中文用起来。", + "lead": "首页只把最值得处理的下一步放到最前面,让学习不被一排数字淹没。", + "timezone": "时间按你的设备时区显示:{timezone}" + }, + "focus": { + "seal": "今", + "lessonEyebrow": "今日重点 · 实时课堂", + "lessonTitle": "下一次对话:{topic}", + "lessonDescription": "{time} 与 {teacher} 见面,已确认的课堂可以直接进入。", + "assignmentEyebrow": "今日重点 · 学习作业", + "assignmentTitle": "继续完成《{title}》", + "assignmentDescription": "来自《{course}》· 截止 {due},已保存的进度会留在作业中。", + "requestEyebrow": "今日重点 · 预约确认", + "requestTitle": "预约请求正在等待教师确认", + "requestDescription": "{teacher} 正在查看你提交的 {time} 预约;等待期间可以继续了解教师资料。", + "discoverEyebrow": "今日重点 · 寻找教师", + "discoverTitle": "找到适合你的中文教师", + "discoverDescription": "浏览已认证资料,按教学方向、授课语言和参考价格做出选择。" + }, + "actions": { + "enterClassroom": "进入课堂", + "continueAssignment": "继续作业", + "viewTeacher": "查看教师", + "findTeacher": "寻找教师", + "retry": "重试", + "confirmCancel": "确认取消", + "keepBooking": "保留预约", + "cancelling": "正在取消…", + "cancelBooking": "取消预约", + "openAssignments": "打开作业", + "viewAllTeachers": "查看全部教师", + "viewAllCourses": "查看全部课程", + "viewCourse": "查看课程" + }, + "appointments": { + "eyebrow": "下一堂已确认课", + "title": "你的下一次实时课堂", + "minutes": "{count} 分钟", + "defaultTopic": "中文情景对话", + "cancelTitle": "取消这次预约?", + "cancelConfirm": "确定取消“{topic}”吗?教师会收到通知。", + "emptyMark": "待", + "emptyTitle": "还没有已确认的课堂", + "emptyBody": "准备好安排下一次对话时,可以从已认证教师中选择。", + "pendingTitle": "等待教师确认", + "status": { + "pending": "等待确认", + "accepted": "已确认", + "rejected": "未被接受", + "cancelled": "已取消", + "completed": "已完成", + "unknown": "状态未知" + } + }, + "assignments": { + "eyebrow": "学习任务", + "title": "待完成作业", + "noDeadline": "长期有效", + "defaultTitle": "学习作业", + "courseFallback": "已发布课程", + "due": "截止 {date}", + "draft": "已保存草稿", + "notStarted": "尚未开始", + "draftMark": "稿", + "taskMark": "作", + "emptyMark": "清", + "emptyTitle": "当前没有待完成作业", + "emptyBody": "教师发布且仍需完成的作业会显示在这里。" + }, + "teachers": { + "eyebrow": "认证指导", + "title": "值得认识的教师", + "lead": "这里展示的都是真实启用、已经通过平台审核的教师资料。", + "avatarAlt": "{name} 的头像", + "seal": "师", + "verified": "已认证", + "profilePending": "专业资料正在完善", + "experience": "教学经验", + "years": "{count} 年", + "referenceRate": "参考价格", + "emptyTitle": "认证教师正在准备", + "emptyBody": "平台不会用演示账户充数;真实教师通过审核后会显示在这里。" + }, + "courses": { + "eyebrow": "已发布学习内容", + "title": "可以探索的课程", + "lead": "先了解课程重点、教师、时长和参考价格,再决定是否适合。", + "coverAlt": "《{title}》课程封面", + "coverMark": "中文 · 在场景中", + "defaultTitle": "中文课程", + "categoryFallback": "通用中文", + "summaryPending": "教师正在完善课程简介。", + "minutes": "{count} 分钟", + "emptyMark": "课", + "emptyTitle": "已发布课程正在准备", + "emptyBody": "认证教师发布并通过平台审核后,真实课程会显示在这里。" + }, + "messages": { + "cancelled": "预约已取消。" + }, + "errors": { + "schedule": "暂时无法加载学习安排,其他学习功能仍可继续使用。", + "assignments": "暂时无法加载作业,请单独重试这一部分。", + "teachers": "暂时无法加载教师推荐,课程发现仍可使用。", + "courses": "暂时无法加载课程推荐,教师发现仍可使用。", + "cancel": "取消预约失败,请稍后重试。" + } + } } diff --git a/src/main.js b/src/main.js index f7fc5da..b0e4787 100644 --- a/src/main.js +++ b/src/main.js @@ -1,17 +1,15 @@ import { createApp } from 'vue' import pinia from '@/stores/index' -import '@/assets/style/tailwind.css' // 引入 Tailwind CSS -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import i18n from '@/i18n' // 引入 i18n +import '@/assets/style/tailwind.css' +import '@/assets/main.scss' +import i18n from '@/i18n' import App from './App.vue' import router from './router' const app = createApp(App) app.use(pinia) -app.use(ElementPlus) app.use(router) -app.use(i18n) // 使用 i18n +app.use(i18n) app.mount('#app') diff --git a/src/router/index.js b/src/router/index.js index 6a96890..a14319c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,4 +1,5 @@ import { createRouter, createWebHistory } from 'vue-router' + import { useStudentStore, useUserStore } from '@/stores' const roleHome = { @@ -8,9 +9,18 @@ const roleHome = { } const passwordResetRoute = { - student: { path: '/student/center/changePassword' }, - teacher: { path: '/teacher/user', query: { tab: 'security' } }, - administrator: { path: '/administrator/center/changePasswordAdmin' } + student: { + path: '/student/center/changePassword', + query: { reason: 'temporary-password' } + }, + teacher: { + path: '/teacher/user', + query: { tab: 'security', reason: 'temporary-password' } + }, + administrator: { + path: '/administrator/center/changePasswordAdmin', + query: { reason: 'temporary-password' } + } } const router = createRouter({ @@ -18,68 +28,111 @@ const router = createRouter({ routes: [ { path: '/', - redirect: '/login' + component: () => import('@/components/public/PublicShell.vue'), + children: [ + { + path: '', + name: 'publicHome', + component: () => import('@/views/public/PublicHome.vue') + }, + { + path: 'teachers', + name: 'publicTeachers', + component: () => import('@/views/public/TeacherDirectory.vue') + }, + { + path: 'teachers/:teacherId', + name: 'publicTeacherDetail', + component: () => import('@/views/public/PublicTeacherDetail.vue') + }, + { + path: 'courses', + name: 'publicCourses', + component: () => import('@/views/public/CourseDirectory.vue') + }, + { + path: 'courses/:courseId', + name: 'publicCourseDetail', + component: () => import('@/views/public/PublicCourseDetail.vue') + }, + { + path: 'legal/terms', + name: 'terms', + component: () => import('@/views/public/LegalPage.vue') + }, + { + path: 'legal/privacy', + name: 'privacy', + component: () => import('@/views/public/LegalPage.vue') + }, + { + path: 'account-recovery', + name: 'accountRecovery', + component: () => import('@/views/public/AccountRecovery.vue') + }, + { + path: '403', + name: 'forbidden', + component: () => import('@/views/public/SystemStatePage.vue'), + props: { status: '403' } + }, + { + path: '404', + name: 'notFound', + component: () => import('@/views/public/SystemStatePage.vue'), + props: { status: '404' } + } + ] }, { path: '/login', + name: 'login', component: () => import('@/views/login/loginPage.vue') }, { - // 学生端架子模块 path: '/student', component: () => import('@/views/student/studentLayout/studentLayout.vue'), meta: { requiresAuth: true, role: 'student' }, - redirect: '/student/home', // 重定向到首页 + redirect: '/student/home', children: [ - // 子路由配置 { - // 学生首页模块 - path: 'home', // 子路由路径 + path: 'home', name: 'studentHome', component: () => import('@/views/student/studentHomePage/studentHomePage.vue') }, { - // 学生预约老师模块 path: 'order', name: 'orderTeacher', - component: () => - import('@/views/student/orderTeacher/orderTeacher.vue') + component: () => import('@/views/public/TeacherDirectory.vue') }, { - // 学生网络课程模块 path: 'course', name: 'onlineCourse', component: () => import('@/views/student/onlineCourses/onlineCourses.vue') }, { - // 学生个人中心模块 path: 'center', name: 'personalCenter', - redirect: '/student/center/info', // 重定向到个人信息模块 + redirect: '/student/center/info', component: () => import('@/views/student/personalCenter/personalCenter.vue'), - - // 个人中心子路由 children: [ { - // 个人信息模块 path: 'info', name: 'personalInfo', component: () => import('@/views/student/personalCenter/personalInfo.vue') }, { - // 修改密码模块 path: 'changePassword', name: 'changePassword', component: () => import('@/views/student/personalCenter/changePassword.vue') }, { - // 消息通知模块 path: 'message', name: 'message', component: () => @@ -88,23 +141,25 @@ const router = createRouter({ ] }, { - //话轮页面 path: 'chatTurn', name: 'chatTurn', component: () => import('@/views/student/chatTurn/chatTurn.vue') }, { - //作业页面 path: 'homeWork', name: 'homeWork', component: () => import('@/views/student/homeWork/homeWork.vue') }, { - //老师详情页面 path: 'teacherDetail', name: 'teacherDetail', - component: () => - import('@/views/student/orderTeacher/teacherDetail.vue') + redirect: (to) => { + const teacherId = + typeof to.query.teacherId === 'string' ? to.query.teacherId : '' + return teacherId + ? { name: 'publicTeacherDetail', params: { teacherId } } + : { name: 'publicTeachers' } + } }, { path: 'liveClass', @@ -123,44 +178,37 @@ const router = createRouter({ path: '/teacher', component: () => import('@/views/teacher/LayoutPage/LayoutPage.vue'), meta: { requiresAuth: true, role: 'teacher' }, - redirect: '/teacher/home', // 重定向到首页 + redirect: '/teacher/home', children: [ - // 首页 { path: 'home', name: 'home', component: () => import('@/views/teacher/teacherHomePage/teacherHomePage.vue') }, - // 授课对接模块 { path: 'teachingDocking', name: 'teachingDocking', component: () => import('@/views/teacher/TeachingDockingPage/teachingDockingPage.vue') }, - // 网络课程模块 { path: 'onlineCourses', name: 'onlineCourses', component: () => - import('@/views/teacher/OnlineCoursesPage/OnlineCoursesPage.vue'), - children: [] + import('@/views/teacher/OnlineCoursesPage/OnlineCoursesPage.vue') }, - // 个人中心模块 { path: 'user', name: 'user', component: () => import('@/views/teacher/UserPage/UserPage.vue') }, - // 上传课程模块 { path: 'uploadCourses', name: 'uploadCourses', component: () => import('@/views/teacher/UploadCoursesPage/UploadCoursesPage.vue') }, - // 课程详情模块 { path: 'courseDetails', name: 'courseDetails', @@ -179,48 +227,40 @@ const router = createRouter({ component: () => import('@/views/administrator/administratorLayout/administratorLayout.vue'), meta: { requiresAuth: true, role: 'administrator' }, - redirect: '/administrator/courseDocking', // 重定向到首页 + redirect: '/administrator/courseDocking', children: [ - // 子路由配置 { - //课程对接模块 - path: 'courseDocking', // 子路由路径 + path: 'courseDocking', name: 'courseDocking', component: () => import('@/views/administrator/courseDocking/courseDocking.vue') }, { - //审核中心模块 path: 'auditCenter', name: 'auditCenter', component: () => import('@/views/administrator/auditCenter/auditCenter.vue') }, { - //数据中心模块 path: 'dataCenter', name: 'dataCenter', component: () => import('@/views/administrator/dataCenter/dataCenter.vue') }, { - //个人中心模块 path: 'center', name: 'center', redirect: '/administrator/center/changePasswordAdmin', component: () => import('@/views/administrator/personalCenter/personalCenter.vue'), - // 个人中心子路由 children: [ { - // 修改密码模块 path: 'changePasswordAdmin', name: 'changePasswordAdmin', component: () => import('@/views/administrator/personalCenter/changePassword.vue') }, { - // 消息通知模块 path: 'AdminMessage', name: 'AdminMessage', component: () => @@ -232,7 +272,10 @@ const router = createRouter({ }, { path: '/:pathMatch(.*)*', - redirect: '/login' + redirect: (to) => ({ + path: '/404', + query: { from: to.fullPath } + }) } ] }) @@ -240,7 +283,14 @@ const router = createRouter({ router.beforeEach(async (to) => { const userStore = useUserStore() const studentStore = useStudentStore() - await userStore.restoreSession() + const authRecord = to.matched.find((record) => record.meta.requiresAuth) + const needsSynchronousSession = Boolean(authRecord) || to.path === '/login' + + if (needsSynchronousSession) { + await userStore.restoreSession() + } else if (!userStore.sessionRestored) { + void userStore.restoreSession() + } const currentRole = userStore.role @@ -258,29 +308,32 @@ router.beforeEach(async (to) => { if (userStore.isAuthenticated && currentRole === 'student') { studentStore.setUserInfo(userStore.profile) - } else { + } else if (!userStore.isAuthenticated) { studentStore.clearUserInfo() } if (to.path === '/login' && userStore.isAuthenticated && currentRole) { - return roleHome[currentRole] || '/login' + return roleHome[currentRole] || '/' } - const authRecord = to.matched.find((record) => record.meta.requiresAuth) - if (!authRecord) { - return true - } + if (!authRecord) return true if (!userStore.isAuthenticated || !currentRole) { return { path: '/login', - query: { redirect: to.fullPath } + query: { + redirect: to.fullPath, + reason: userStore.restoreError ? 'offline' : 'authentication-required' + } } } const expectedRole = authRecord.meta.role if (expectedRole && expectedRole !== currentRole) { - return roleHome[currentRole] || '/login' + return { + path: '/403', + query: { from: to.fullPath, role: currentRole } + } } return true @@ -289,7 +342,7 @@ router.beforeEach(async (to) => { let unauthorizedRedirectInProgress = false if (typeof window !== 'undefined') { - window.addEventListener('auth:unauthorized', async () => { + window.addEventListener('auth:unauthorized', async (event) => { if (unauthorizedRedirectInProgress) return unauthorizedRedirectInProgress = true @@ -304,7 +357,10 @@ if (typeof window !== 'undefined') { if (currentRoute.path !== '/login') { await router.replace({ path: '/login', - query: { redirect: currentRoute.fullPath } + query: { + redirect: currentRoute.fullPath, + reason: event.detail?.reason || 'expired' + } }) } } finally { @@ -313,4 +369,21 @@ if (typeof window !== 'undefined') { }) } +let chunkRecoveryAttempted = false +router.onError((error) => { + const message = String(error?.message || '') + const isChunkFailure = + message.includes('Failed to fetch dynamically imported module') || + message.includes('Importing a module script failed') + + if ( + isChunkFailure && + !chunkRecoveryAttempted && + typeof window !== 'undefined' + ) { + chunkRecoveryAttempted = true + window.location.reload() + } +}) + export default router diff --git a/src/stores/modules/localeStore.js b/src/stores/modules/localeStore.js index 002cb1b..1a33b31 100644 --- a/src/stores/modules/localeStore.js +++ b/src/stores/modules/localeStore.js @@ -1,15 +1,32 @@ import { defineStore } from 'pinia' -import i18n from '@/i18n' +import i18n, { detectInitialLocale, normalizeLocale } from '@/i18n' + +function applyDocumentLanguage(locale) { + if (typeof document === 'undefined') return + document.documentElement.lang = locale === 'zh' ? 'zh-CN' : 'en' +} export const useLocaleStore = defineStore('locale', { state: () => ({ - locale: 'zh' // 默认语言 + locale: detectInitialLocale() }), actions: { - setLocale(locale) { - this.locale = locale - localStorage.setItem('lang', locale) - i18n.global.locale.value = locale // 同步到 vue-i18n + initialize() { + this.setLocale(this.locale, { persist: false }) + }, + setLocale(locale, { persist = true } = {}) { + const nextLocale = normalizeLocale(locale) || 'en' + this.locale = nextLocale + i18n.global.locale.value = nextLocale + applyDocumentLanguage(nextLocale) + + if (persist && typeof window !== 'undefined') { + try { + window.localStorage.setItem('lang', nextLocale) + } catch { + // The active locale still works when persistent storage is blocked. + } + } } } }) diff --git a/src/stores/modules/user.js b/src/stores/modules/user.js index 034dc2a..3c6d3cb 100644 --- a/src/stores/modules/user.js +++ b/src/stores/modules/user.js @@ -33,6 +33,7 @@ export const useUserStore = defineStore('big-store', () => { const profile = ref(null) const sessionRestored = ref(false) const isRestoring = ref(false) + const restoreError = ref(null) let restorePromise = null const isAuthenticated = computed(() => @@ -55,6 +56,7 @@ export const useUserStore = defineStore('big-store', () => { role.value = resolvedRole profile.value = { ...nextProfile, role: resolvedRole } + restoreError.value = null sessionRestored.value = true return profile.value } @@ -70,11 +72,21 @@ export const useUserStore = defineStore('big-store', () => { if (restorePromise) return restorePromise isRestoring.value = true + restoreError.value = null restorePromise = getSession() .then((response) => setSession(response.data?.data)) - .catch(() => { - clearSession() - return null + .catch((error) => { + if (Number(error?.response?.status) === 401) { + clearSession() + return null + } + + restoreError.value = + error?.response?.data?.msg || + error?.message || + 'Unable to restore the session' + sessionRestored.value = true + return profile.value }) .finally(() => { isRestoring.value = false @@ -107,6 +119,7 @@ export const useUserStore = defineStore('big-store', () => { profile, sessionRestored, isRestoring, + restoreError, isAuthenticated, setSession, clearSession, diff --git a/src/styles/base.scss b/src/styles/base.scss new file mode 100644 index 0000000..0088935 --- /dev/null +++ b/src/styles/base.scss @@ -0,0 +1,77 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + min-width: 320px; + min-height: 100%; + scroll-behavior: smooth; + text-size-adjust: 100%; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; + color: var(--color-ink); + background: var(--color-paper); + font-family: var(--font-body); + line-height: 1.6; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +button, +input, +select, +textarea { + color: inherit; + font: inherit; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button:not(:disabled), +[role='button']:not([aria-disabled='true']) { + cursor: pointer; +} + +img, +svg { + display: block; + max-width: 100%; +} + +a { + color: inherit; +} + +:focus-visible { + outline: 3px solid var(--color-focus); + outline-offset: 3px; +} + +::selection { + color: var(--color-surface); + background: var(--color-cinnabar); +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/src/styles/tokens.scss b/src/styles/tokens.scss new file mode 100644 index 0000000..8fd76aa --- /dev/null +++ b/src/styles/tokens.scss @@ -0,0 +1,75 @@ +:root { + color-scheme: light; + + --color-paper: #f5f1e8; + --color-surface: #fffdf7; + --color-ink: #102a2f; + --color-ink-soft: #173a40; + --color-jade: #2f746b; + --color-cinnabar: #b64738; + --color-gold: #b88a3b; + --color-muted: #62757a; + --color-focus: #1769d2; + --color-line: rgba(16, 42, 47, 0.14); + --color-line-strong: rgba(16, 42, 47, 0.24); + --color-success-soft: #e4f0ea; + --color-danger-soft: #f8e9e5; + + /* Public discovery components are also embedded in authenticated shells. */ + --public-paper: var(--color-paper); + --public-surface: var(--color-surface); + --public-ink: var(--color-ink); + --public-ink-soft: var(--color-ink-soft); + --public-jade: var(--color-jade); + --public-vermilion: var(--color-cinnabar); + --public-gold: var(--color-gold); + --public-muted: var(--color-muted); + --public-line: #d8d3c7; + --public-focus: var(--color-focus); + --public-radius-sm: var(--radius-sm); + --public-radius-md: var(--radius-md); + --public-radius-lg: var(--radius-lg); + + --font-display: 'Songti SC', 'STSong', 'Noto Serif CJK SC', Georgia, serif; + --font-body: + 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Segoe UI', sans-serif; + --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; + + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + --radius-sm: 10px; + --radius-md: 16px; + --radius-lg: 24px; + --radius-pill: 999px; + + --shadow-sm: + 0 1px 2px rgba(16, 42, 47, 0.04), 0 7px 20px rgba(16, 42, 47, 0.06); + --shadow-md: 0 16px 42px rgba(16, 42, 47, 0.11); + --shadow-lg: 0 28px 80px rgba(16, 42, 47, 0.18); + + --content-max: 1280px; + --header-height: 72px; + --transition-fast: 160ms ease; + --transition-base: 240ms cubic-bezier(0.2, 0.8, 0.2, 1); + + --el-color-primary: var(--color-jade); + --el-color-primary-light-3: #659990; + --el-color-primary-light-5: #8fb7af; + --el-color-primary-light-7: #bdd3cd; + --el-color-primary-light-8: #d4e3df; + --el-color-primary-light-9: #eaf1ef; + --el-color-primary-dark-2: #255d56; + --el-border-radius-base: var(--radius-sm); + --el-border-color: var(--color-line); + --el-text-color-primary: var(--color-ink); + --el-text-color-regular: var(--color-muted); + --el-bg-color: var(--color-surface); +} diff --git a/src/utils/request.js b/src/utils/request.js index cab3658..787f94f 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,6 +1,6 @@ import axios from 'axios' -import { ElMessage } from 'element-plus' import { API_BASE_URL } from '@/config/runtime' +import i18n from '@/i18n' const AUTH_ENDPOINTS = new Set([ '/auth/login', @@ -11,6 +11,22 @@ const AUTH_ENDPOINTS = new Set([ ]) let unauthorizedEventPending = false +let messageComponentPromise = null + +function showErrorMessage(message) { + if (!messageComponentPromise) { + messageComponentPromise = Promise.all([ + import('element-plus/es/components/message/index'), + import('element-plus/theme-chalk/el-message.css') + ]).then(([module]) => module.ElMessage) + } + + messageComponentPromise + .then((ElMessage) => ElMessage.error(message)) + .catch(() => { + console.error(message) + }) +} const instance = axios.create({ baseURL: API_BASE_URL, @@ -48,6 +64,15 @@ function handleErrorResponse(error) { responseData?.code ?? error?.response?.status ?? error?.status const requestPath = String(error?.config?.url || '').split('?')[0] + if (!error?.response && typeof window !== 'undefined') { + error.isNetworkError = true + window.dispatchEvent( + new CustomEvent('network:unavailable', { + detail: { requestPath } + }) + ) + } + if ( Number(errorCode) === 401 && !AUTH_ENDPOINTS.has(requestPath) && @@ -55,15 +80,22 @@ function handleErrorResponse(error) { typeof window !== 'undefined' ) { unauthorizedEventPending = true - window.dispatchEvent(new CustomEvent('auth:unauthorized')) + window.dispatchEvent( + new CustomEvent('auth:unauthorized', { + detail: { reason: 'expired', requestPath } + }) + ) window.setTimeout(() => { unauthorizedEventPending = false }, 0) } - if (Number(errorCode) >= 400 && Number(errorCode) !== 401) { - const message = responseData?.msg || responseData?.message || '服务异常' - ElMessage.error(String(message)) + if ( + Number(errorCode) >= 400 && + Number(errorCode) !== 401 && + !AUTH_ENDPOINTS.has(requestPath) + ) { + showErrorMessage(i18n.global.t('common.requestFailed')) error.messageShown = true } diff --git a/src/views/administrator/administratorLayout/administratorLayout.vue b/src/views/administrator/administratorLayout/administratorLayout.vue index e5200c5..c877523 100644 --- a/src/views/administrator/administratorLayout/administratorLayout.vue +++ b/src/views/administrator/administratorLayout/administratorLayout.vue @@ -1,37 +1,82 @@ diff --git a/src/views/login/loginPage.vue b/src/views/login/loginPage.vue index 710eda0..0ece08e 100644 --- a/src/views/login/loginPage.vue +++ b/src/views/login/loginPage.vue @@ -1,68 +1,127 @@ diff --git a/src/views/public/AccountRecovery.vue b/src/views/public/AccountRecovery.vue new file mode 100644 index 0000000..1442f2c --- /dev/null +++ b/src/views/public/AccountRecovery.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/src/views/public/CourseDirectory.vue b/src/views/public/CourseDirectory.vue new file mode 100644 index 0000000..cac3b21 --- /dev/null +++ b/src/views/public/CourseDirectory.vue @@ -0,0 +1,474 @@ + + + + + diff --git a/src/views/public/LegalPage.vue b/src/views/public/LegalPage.vue new file mode 100644 index 0000000..fe124ef --- /dev/null +++ b/src/views/public/LegalPage.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/src/views/public/PublicCourseDetail.vue b/src/views/public/PublicCourseDetail.vue new file mode 100644 index 0000000..9145939 --- /dev/null +++ b/src/views/public/PublicCourseDetail.vue @@ -0,0 +1,626 @@ + + + + + diff --git a/src/views/public/PublicHome.vue b/src/views/public/PublicHome.vue new file mode 100644 index 0000000..f550bcd --- /dev/null +++ b/src/views/public/PublicHome.vue @@ -0,0 +1,825 @@ + + + + + diff --git a/src/views/public/PublicTeacherDetail.vue b/src/views/public/PublicTeacherDetail.vue new file mode 100644 index 0000000..811da05 --- /dev/null +++ b/src/views/public/PublicTeacherDetail.vue @@ -0,0 +1,1310 @@ + + + + + diff --git a/src/views/public/SystemStatePage.vue b/src/views/public/SystemStatePage.vue new file mode 100644 index 0000000..add5621 --- /dev/null +++ b/src/views/public/SystemStatePage.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/views/public/TeacherDirectory.vue b/src/views/public/TeacherDirectory.vue new file mode 100644 index 0000000..47e929f --- /dev/null +++ b/src/views/public/TeacherDirectory.vue @@ -0,0 +1,491 @@ + + + + + diff --git a/src/views/student/digitalHuman/TeachDetails.vue b/src/views/student/digitalHuman/TeachDetails.vue index 2f4b785..e1887f0 100644 --- a/src/views/student/digitalHuman/TeachDetails.vue +++ b/src/views/student/digitalHuman/TeachDetails.vue @@ -3,7 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' import { ElMessage } from 'element-plus' import { useRoute, useRouter } from 'vue-router' import teacherAvatar from '@/assets/icon/teacher.png' -import studentAvatar from '@/assets/student/avatar.png' +import studentAvatar from '@/assets/student/avatar-default.svg' import { createDialogue, getDialogue, diff --git a/src/views/student/studentHomePage/studentHomePage.vue b/src/views/student/studentHomePage/studentHomePage.vue index 4f9bd7e..8a48c0d 100644 --- a/src/views/student/studentHomePage/studentHomePage.vue +++ b/src/views/student/studentHomePage/studentHomePage.vue @@ -1,117 +1,356 @@ diff --git a/src/views/student/studentLayout/studentLayout.vue b/src/views/student/studentLayout/studentLayout.vue index be4c6b5..2e6e78c 100644 --- a/src/views/student/studentLayout/studentLayout.vue +++ b/src/views/student/studentLayout/studentLayout.vue @@ -1,30 +1,64 @@ diff --git a/src/views/teacher/UserPage/UserPage.vue b/src/views/teacher/UserPage/UserPage.vue index 23ac10a..1b16611 100644 --- a/src/views/teacher/UserPage/UserPage.vue +++ b/src/views/teacher/UserPage/UserPage.vue @@ -1,26 +1,44 @@