diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..241cdcb
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,84 @@
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ node: true,
+ },
+ plugins: ['@typescript-eslint', 'simple-import-sort', 'unused-imports'],
+ extends: [
+ 'eslint:recommended',
+ 'next',
+ 'next/core-web-vitals',
+ 'plugin:@typescript-eslint/recommended',
+ 'prettier',
+ ],
+ rules: {
+ 'no-unused-vars': 'off',
+ 'no-console': 'warn',
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
+ 'react/no-unescaped-entities': 'off',
+
+ 'react/display-name': 'off',
+ 'react/jsx-curly-brace-presence': [
+ 'warn',
+ { props: 'never', children: 'never' },
+ ],
+
+ //#region //*=========== Unused Import ===========
+ '@typescript-eslint/no-unused-vars': 'off',
+ 'unused-imports/no-unused-imports': 'warn',
+ 'unused-imports/no-unused-vars': [
+ 'warn',
+ {
+ vars: 'all',
+ varsIgnorePattern: '^_',
+ args: 'after-used',
+ argsIgnorePattern: '^_',
+ },
+ ],
+ //#endregion //*======== Unused Import ===========
+
+ //#region //*=========== Import Sort ===========
+ 'simple-import-sort/exports': 'warn',
+ 'simple-import-sort/imports': [
+ 'warn',
+ {
+ groups: [
+ // ext library & side effect imports
+ ['^@?\\w', '^\\u0000'],
+ // {s}css files
+ ['^.+\\.s?css$'],
+ // Lib and hooks
+ ['^@/lib', '^@/hooks'],
+ // static data
+ ['^@/data'],
+ // components
+ ['^@/components', '^@/container'],
+ // zustand store
+ ['^@/store'],
+ // Other imports
+ ['^@/'],
+ // relative paths up until 3 level
+ [
+ '^\\./?$',
+ '^\\.(?!/?$)',
+ '^\\.\\./?$',
+ '^\\.\\.(?!/?$)',
+ '^\\.\\./\\.\\./?$',
+ '^\\.\\./\\.\\.(?!/?$)',
+ '^\\.\\./\\.\\./\\.\\./?$',
+ '^\\.\\./\\.\\./\\.\\.(?!/?$)',
+ ],
+ ['^@/types'],
+ // other that didnt fit in
+ ['^'],
+ ],
+ },
+ ],
+ //#endregion //*======== Import Sort ===========
+ },
+ globals: {
+ React: true,
+ JSX: true,
+ },
+};
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
new file mode 100644
index 0000000..e811045
--- /dev/null
+++ b/.github/workflows/docker-image.yml
@@ -0,0 +1,151 @@
+name: Build & Push Docker image
+
+on:
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches:
+ - main
+ paths-ignore:
+ - '**.md'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write
+ packages: write
+ actions: write
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ include:
+ - platform: linux/amd64
+ os: ubuntu-latest
+ - platform: linux/arm64
+ os: ubuntu-24.04-arm
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Prepare platform name
+ run: |
+ echo "PLATFORM_NAME=${{ matrix.platform }}" | sed 's|/|-|g' >> $GITHUB_ENV
+
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set lowercase repository owner
+ id: lowercase
+ run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ghcr.io/${{ steps.lowercase.outputs.owner }}/moontv
+ tags: |
+ type=ref,event=pr
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push by digest
+ id: build
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: ${{ matrix.platform }}
+ labels: ${{ steps.meta.outputs.labels }}
+ outputs: type=image,name=ghcr.io/${{ steps.lowercase.outputs.owner }}/moontv,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
+
+ - name: Export digest
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+
+ - name: Upload digest
+ uses: actions/upload-artifact@v4
+ with:
+ name: digests-${{ env.PLATFORM_NAME }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ runs-on: ubuntu-latest
+ needs:
+ - build
+ if: github.event_name != 'pull_request'
+ steps:
+ - name: Download digests
+ uses: actions/download-artifact@v4
+ with:
+ path: /tmp/digests
+ pattern: digests-*
+ merge-multiple: true
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set lowercase repository owner
+ id: lowercase
+ run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ghcr.io/${{ steps.lowercase.outputs.owner }}/moontv
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=sha
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Create manifest list and push
+ working-directory: /tmp/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf 'ghcr.io/${{ steps.lowercase.outputs.owner }}/moontv@sha256:%s ' *)
+
+ - name: Inspect image
+ run: |
+ docker buildx imagetools inspect ghcr.io/${{ steps.lowercase.outputs.owner }}/moontv:${{ steps.meta.outputs.version }}
+
+ cleanup:
+ runs-on: ubuntu-latest
+ needs:
+ - merge
+ if: always() && github.event_name != 'pull_request'
+ steps:
+ - name: Delete workflow runs
+ uses: Mattraks/delete-workflow-runs@main
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repository: ${{ github.repository }}
+ retain_days: 0
+ keep_minimum_runs: 2
diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml
new file mode 100644
index 0000000..08adfbd
--- /dev/null
+++ b/.github/workflows/sync.yml
@@ -0,0 +1,45 @@
+name: Upstream Sync
+
+on:
+ schedule:
+ - cron: "0 */6 * * *" # run every 6 hours
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ actions: write
+
+jobs:
+ sync_latest_from_upstream:
+ name: Sync latest commits from upstream repo
+ runs-on: ubuntu-latest
+ if: ${{ github.event.repository.fork }}
+
+ steps:
+ # Step 1: run a standard checkout action
+ - name: Checkout target repo
+ uses: actions/checkout@v4
+
+ # Step 2: run the sync action
+ - name: Sync upstream changes
+ id: sync
+ uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
+ with:
+ upstream_sync_repo: senshinya/MoonTV
+ upstream_sync_branch: main
+ target_sync_branch: main
+ target_repo_token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Sync check
+ if: failure()
+ run: |
+ echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork."
+ exit 1
+
+ - name: Delete workflow runs
+ uses: Mattraks/delete-workflow-runs@main
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repository: ${{ github.repository }}
+ retain_days: 0
+ keep_minimum_runs: 2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d2a80c9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,45 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# next-sitemap
+sitemap.xml
+sitemap-*.xml
+
+# generated files
+src/lib/runtime.ts
+public/manifest.json
\ No newline at end of file
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..0bd658f
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx --no-install commitlint --edit "$1"
diff --git a/.husky/post-merge b/.husky/post-merge
new file mode 100644
index 0000000..1fd4a5b
--- /dev/null
+++ b/.husky/post-merge
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+pnpm install
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..74bd7d6
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,11 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# 生成版本号
+pnpm gen:version
+
+# 自动添加修改的版本文件
+git add src/lib/version.ts
+git add VERSION.txt
+
+npx lint-staged
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..e69de29
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..790e110
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v20.10.0
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..b04160b
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+.next
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
+
+# changelog
+CHANGELOG.md
+
+pnpm-lock.yaml
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..5c34464
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ arrowParens: 'always',
+ singleQuote: true,
+ jsxSingleQuote: true,
+ tabWidth: 2,
+ semi: true,
+};
diff --git a/.vscode/css.code-snippets b/.vscode/css.code-snippets
new file mode 100644
index 0000000..82cc815
--- /dev/null
+++ b/.vscode/css.code-snippets
@@ -0,0 +1,10 @@
+{
+ "Region CSS": {
+ "prefix": "regc",
+ "body": [
+ "/* #region /**=========== ${1} =========== */",
+ "$0",
+ "/* #endregion /**======== ${1} =========== */"
+ ]
+ }
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..44bf298
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+ "recommendations": [
+ // Tailwind CSS Intellisense
+ "bradlc.vscode-tailwindcss",
+ "esbenp.prettier-vscode",
+ "dbaeumer.vscode-eslint",
+ "aaron-bond.better-comments"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..a775463
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,17 @@
+{
+ "css.validate": false,
+ "editor.formatOnSave": true,
+ "editor.tabSize": 2,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit"
+ },
+ "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ // Tailwind CSS Autocomplete, add more if used in projects
+ "tailwindCSS.classAttributes": [
+ "class",
+ "className",
+ "classNames",
+ "containerClassName"
+ ],
+ "typescript.preferences.importModuleSpecifier": "non-relative"
+}
diff --git a/.vscode/typescriptreact.code-snippets b/.vscode/typescriptreact.code-snippets
new file mode 100644
index 0000000..1fce740
--- /dev/null
+++ b/.vscode/typescriptreact.code-snippets
@@ -0,0 +1,193 @@
+{
+ //#region //*=========== React ===========
+ "import React": {
+ "prefix": "ir",
+ "body": ["import * as React from 'react';"]
+ },
+ "React.useState": {
+ "prefix": "us",
+ "body": [
+ "const [${1}, set${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}] = React.useState<$3>(${2:initial${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}})$0"
+ ]
+ },
+ "React.useEffect": {
+ "prefix": "uf",
+ "body": ["React.useEffect(() => {", " $0", "}, []);"]
+ },
+ "React.useReducer": {
+ "prefix": "ur",
+ "body": [
+ "const [state, dispatch] = React.useReducer(${0:someReducer}, {",
+ " ",
+ "})"
+ ]
+ },
+ "React.useRef": {
+ "prefix": "urf",
+ "body": ["const ${1:someRef} = React.useRef($0)"]
+ },
+ "React Functional Component": {
+ "prefix": "rc",
+ "body": [
+ "import * as React from 'react';\n",
+ "export default function ${1:${TM_FILENAME_BASE}}() {",
+ " return (",
+ "
",
+ " $0",
+ "
",
+ " )",
+ "}"
+ ]
+ },
+ "React Functional Component with Props": {
+ "prefix": "rcp",
+ "body": [
+ "import * as React from 'react';\n",
+ "import clsxm from '@/lib/clsxm';\n",
+ "type ${1:${TM_FILENAME_BASE}}Props= {\n",
+ "} & React.ComponentPropsWithoutRef<'div'>\n",
+ "export default function ${1:${TM_FILENAME_BASE}}({className, ...rest}: ${1:${TM_FILENAME_BASE}}Props) {",
+ " return (",
+ " ",
+ " $0",
+ "
",
+ " )",
+ "}"
+ ]
+ },
+ //#endregion //*======== React ===========
+
+ //#region //*=========== Commons ===========
+ "Region": {
+ "prefix": "reg",
+ "scope": "javascript, typescript, javascriptreact, typescriptreact",
+ "body": [
+ "//#region //*=========== ${1} ===========",
+ "${TM_SELECTED_TEXT}$0",
+ "//#endregion //*======== ${1} ==========="
+ ]
+ },
+ "Region CSS": {
+ "prefix": "regc",
+ "scope": "css, scss",
+ "body": [
+ "/* #region /**=========== ${1} =========== */",
+ "${TM_SELECTED_TEXT}$0",
+ "/* #endregion /**======== ${1} =========== */"
+ ]
+ },
+ //#endregion //*======== Commons ===========
+
+ //#region //*=========== Next.js ===========
+ "Next Pages": {
+ "prefix": "np",
+ "body": [
+ "import * as React from 'react';\n",
+ "import Layout from '@/components/layout/Layout';",
+ "import Seo from '@/components/Seo';\n",
+ "export default function ${1:${TM_FILENAME_BASE/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}}Page() {",
+ " return (",
+ " ",
+ " \n",
+ " \n",
+ " ",
+ " ",
+ " $0",
+ "
",
+ " ",
+ " ",
+ " ",
+ " )",
+ "}"
+ ]
+ },
+ "Next API": {
+ "prefix": "napi",
+ "body": [
+ "import { NextApiRequest, NextApiResponse } from 'next';\n",
+ "export default async function handler(req: NextApiRequest, res: NextApiResponse) {",
+ " if (req.method === 'GET') {",
+ " res.status(200).json({ name: 'Bambang' });",
+ " } else {",
+ " res.status(405).json({ message: 'Method Not Allowed' });",
+ " }",
+ "}"
+ ]
+ },
+ "Get Static Props": {
+ "prefix": "gsp",
+ "body": [
+ "export const getStaticProps = async (context: GetStaticPropsContext) => {",
+ " return {",
+ " props: {}",
+ " };",
+ "}"
+ ]
+ },
+ "Get Static Paths": {
+ "prefix": "gspa",
+ "body": [
+ "export const getStaticPaths: GetStaticPaths = async () => {",
+ " return {",
+ " paths: [",
+ " { params: { $1 }}",
+ " ],",
+ " fallback: ",
+ " };",
+ "}"
+ ]
+ },
+ "Get Server Side Props": {
+ "prefix": "gssp",
+ "body": [
+ "export const getServerSideProps = async (context: GetServerSidePropsContext) => {",
+ " return {",
+ " props: {}",
+ " };",
+ "}"
+ ]
+ },
+ "Infer Get Static Props": {
+ "prefix": "igsp",
+ "body": "InferGetStaticPropsType"
+ },
+ "Infer Get Server Side Props": {
+ "prefix": "igssp",
+ "body": "InferGetServerSidePropsType"
+ },
+ "Import useRouter": {
+ "prefix": "imust",
+ "body": ["import { useRouter } from 'next/router';"]
+ },
+ "Import Next Image": {
+ "prefix": "imimg",
+ "body": ["import Image from 'next/image';"]
+ },
+ "Import Next Link": {
+ "prefix": "iml",
+ "body": ["import Link from 'next/link';"]
+ },
+ //#endregion //*======== Next.js ===========
+
+ //#region //*=========== Snippet Wrap ===========
+ "Wrap with Fragment": {
+ "prefix": "ff",
+ "body": ["<>", "\t${TM_SELECTED_TEXT}", ">"]
+ },
+ "Wrap with clsx": {
+ "prefix": "cx",
+ "body": ["{clsx([${TM_SELECTED_TEXT}$0])}"]
+ },
+ "Wrap with clsxm": {
+ "prefix": "cxm",
+ "body": ["{clsxm([${TM_SELECTED_TEXT}$0, className])}"]
+ },
+ //#endregion //*======== Snippet Wrap ===========
+
+ "Logger": {
+ "prefix": "lg",
+ "body": [
+ "logger({ ${1:${CLIPBOARD}} }, '${TM_FILENAME} line ${TM_LINE_NUMBER}')"
+ ]
+ }
+}
diff --git a/D1初始化.md b/D1初始化.md
new file mode 100644
index 0000000..5c80853
--- /dev/null
+++ b/D1初始化.md
@@ -0,0 +1,75 @@
+```sql
+CREATE TABLE IF NOT EXISTS users (
+ username TEXT PRIMARY KEY,
+ password TEXT NOT NULL,
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
+);
+
+CREATE TABLE IF NOT EXISTS play_records (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ key TEXT NOT NULL,
+ title TEXT NOT NULL,
+ source_name TEXT NOT NULL,
+ cover TEXT NOT NULL,
+ year TEXT NOT NULL,
+ index_episode INTEGER NOT NULL,
+ total_episodes INTEGER NOT NULL,
+ play_time INTEGER NOT NULL,
+ total_time INTEGER NOT NULL,
+ save_time INTEGER NOT NULL,
+ search_title TEXT,
+ UNIQUE(username, key)
+);
+
+CREATE TABLE IF NOT EXISTS favorites (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ key TEXT NOT NULL,
+ title TEXT NOT NULL,
+ source_name TEXT NOT NULL,
+ cover TEXT NOT NULL,
+ year TEXT NOT NULL,
+ total_episodes INTEGER NOT NULL,
+ save_time INTEGER NOT NULL,
+ UNIQUE(username, key)
+);
+
+CREATE TABLE IF NOT EXISTS search_history (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ keyword TEXT NOT NULL,
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
+ UNIQUE(username, keyword)
+);
+
+CREATE TABLE IF NOT EXISTS admin_config (
+ id INTEGER PRIMARY KEY DEFAULT 1,
+ config TEXT NOT NULL,
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
+);
+
+-- 基本索引
+CREATE INDEX IF NOT EXISTS idx_play_records_username ON play_records(username);
+CREATE INDEX IF NOT EXISTS idx_favorites_username ON favorites(username);
+CREATE INDEX IF NOT EXISTS idx_search_history_username ON search_history(username);
+
+-- 复合索引优化查询性能
+-- 播放记录:用户名+键值的复合索引,用于快速查找特定记录
+CREATE INDEX IF NOT EXISTS idx_play_records_username_key ON play_records(username, key);
+-- 播放记录:用户名+保存时间的复合索引,用于按时间排序的查询
+CREATE INDEX IF NOT EXISTS idx_play_records_username_save_time ON play_records(username, save_time DESC);
+
+-- 收藏:用户名+键值的复合索引,用于快速查找特定收藏
+CREATE INDEX IF NOT EXISTS idx_favorites_username_key ON favorites(username, key);
+-- 收藏:用户名+保存时间的复合索引,用于按时间排序的查询
+CREATE INDEX IF NOT EXISTS idx_favorites_username_save_time ON favorites(username, save_time DESC);
+
+-- 搜索历史:用户名+关键词的复合索引,用于快速查找/删除特定搜索记录
+CREATE INDEX IF NOT EXISTS idx_search_history_username_keyword ON search_history(username, keyword);
+-- 搜索历史:用户名+创建时间的复合索引,用于按时间排序的查询
+CREATE INDEX IF NOT EXISTS idx_search_history_username_created_at ON search_history(username, created_at DESC);
+
+-- 搜索历史清理查询的优化索引
+CREATE INDEX IF NOT EXISTS idx_search_history_username_id_created_at ON search_history(username, id, created_at DESC);
+```
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..82c9788
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,66 @@
+# ---- 第 1 阶段:安装依赖 ----
+FROM node:20-alpine AS deps
+
+# 启用 corepack 并激活 pnpm(Node20 默认提供 corepack)
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+WORKDIR /app
+
+# 仅复制依赖清单,提高构建缓存利用率
+COPY package.json pnpm-lock.yaml ./
+
+# 安装所有依赖(含 devDependencies,后续会裁剪)
+RUN pnpm install --frozen-lockfile
+
+# ---- 第 2 阶段:构建项目 ----
+FROM node:20-alpine AS builder
+RUN corepack enable && corepack prepare pnpm@latest --activate
+WORKDIR /app
+
+# 复制依赖
+COPY --from=deps /app/node_modules ./node_modules
+# 复制全部源代码
+COPY . .
+
+# 在构建阶段也显式设置 DOCKER_ENV,
+# 确保 Next.js 在编译时即选择 Node Runtime 而不是 Edge Runtime
+RUN find ./src -type f -name "route.ts" -print0 \
+ | xargs -0 sed -i "s/export const runtime = 'edge';/export const runtime = 'nodejs';/g"
+ENV DOCKER_ENV=true
+
+# For Docker builds, force dynamic rendering to read runtime environment variables.
+RUN sed -i "/const inter = Inter({ subsets: \['latin'] });/a export const dynamic = 'force-dynamic';" src/app/layout.tsx
+
+# 生成生产构建
+RUN pnpm run build
+
+# ---- 第 3 阶段:生成运行时镜像 ----
+FROM node:20-alpine AS runner
+
+# 创建非 root 用户
+RUN addgroup -g 1001 -S nodejs && adduser -u 1001 -S nextjs -G nodejs
+
+WORKDIR /app
+ENV NODE_ENV=production
+ENV HOSTNAME=0.0.0.0
+ENV PORT=3000
+ENV DOCKER_ENV=true
+
+# 从构建器中复制 standalone 输出
+COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
+# 从构建器中复制 scripts 目录
+COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts
+# 从构建器中复制 start.js
+COPY --from=builder --chown=nextjs:nodejs /app/start.js ./start.js
+# 从构建器中复制 public 和 .next/static 目录
+COPY --from=builder --chown=nextjs:nodejs /app/public ./public
+COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+COPY --from=builder --chown=nextjs:nodejs /app/config.json ./config.json
+
+# 切换到非特权用户
+USER nextjs
+
+EXPOSE 3000
+
+# 使用自定义启动脚本,先预加载配置再启动服务器
+CMD ["node", "start.js"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 261eeb9..19b74a3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,21 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
+MIT License
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+Copyright (c) 2025 senshinya
- 1. Definitions.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..afea800
--- /dev/null
+++ b/README.md
@@ -0,0 +1,309 @@
+# MoonTV
+
+
+
+
+
+> 🎬 **MoonTV** 是一个开箱即用的、跨平台的影视聚合播放器。它基于 **Next.js 14** + **Tailwind CSS** + **TypeScript** 构建,支持多资源搜索、在线播放、收藏同步、播放记录、本地/云端存储,让你可以随时随地畅享海量免费影视内容。
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## ✨ 功能特性
+
+- 🔍 **多源聚合搜索**:内置数十个免费资源站点,一次搜索立刻返回全源结果。
+- 📄 **丰富详情页**:支持剧集列表、演员、年份、简介等完整信息展示。
+- ▶️ **流畅在线播放**:集成 HLS.js & ArtPlayer。
+- ❤️ **收藏 + 继续观看**:支持 Redis/D1 存储,多端同步进度。
+- 📱 **PWA**:离线缓存、安装到桌面/主屏,移动端原生体验。
+- 🌗 **响应式布局**:桌面侧边栏 + 移动底部导航,自适应各种屏幕尺寸。
+- 🚀 **极简部署**:一条 Docker 命令即可将完整服务跑起来,或免费部署到 Vercel 和 Cloudflare。
+- 👿 **智能去广告**:自动跳过视频中的切片广告(实验性)
+
+
+ 点击查看项目截图
+
+
+
+
+
+## 🗺 目录
+
+- [技术栈](#技术栈)
+- [部署](#部署)
+- [Docker Compose 最佳实践](#Docker-Compose-最佳实践)
+- [环境变量](#环境变量)
+- [配置说明](#配置说明)
+- [管理员配置](#管理员配置)
+- [AndroidTV 使用](#AndroidTV-使用)
+- [Roadmap](#roadmap)
+- [安全与隐私提醒](#安全与隐私提醒)
+- [License](#license)
+- [致谢](#致谢)
+
+## 技术栈
+
+| 分类 | 主要依赖 |
+| --------- | ----------------------------------------------------------------------------------------------------- |
+| 前端框架 | [Next.js 14](https://nextjs.org/) · App Router |
+| UI & 样式 | [Tailwind CSS 3](https://tailwindcss.com/) |
+| 语言 | TypeScript 4 |
+| 播放器 | [ArtPlayer](https://github.com/zhw2590582/ArtPlayer) · [HLS.js](https://github.com/video-dev/hls.js/) |
+| 代码质量 | ESLint · Prettier · Jest |
+| 部署 | Docker · Vercel · CloudFlare pages |
+
+## 部署
+
+本项目**支持 Vercel、Docker 和 Cloudflare** 部署。
+
+存储支持矩阵
+
+| | Docker | Vercel | Cloudflare |
+| :-----------: | :----: | :----: | :--------: |
+| localstorage | ✅ | ✅ | ✅ |
+| 原生 redis | ✅ | | |
+| Cloudflare D1 | | | ✅ |
+| Upstash Redis | ☑️ | ✅ | ☑️ |
+
+✅:经测试支持
+
+☑️:理论上支持,未测试
+
+除 localstorage 方式外,其他方式都支持多账户、记录同步和管理页面
+
+### Vercel 部署
+
+#### 普通部署(localstorage)
+
+1. **Fork** 本仓库到你的 GitHub 账户。
+2. 登陆 [Vercel](https://vercel.com/),点击 **Add New → Project**,选择 Fork 后的仓库。
+3. 设置 PASSWORD 环境变量。
+4. 保持默认设置完成首次部署。
+5. 如需自定义 `config.json`,请直接修改 Fork 后仓库中该文件。
+6. 每次 Push 到 `main` 分支将自动触发重新构建。
+
+部署完成后即可通过分配的域名访问,也可以绑定自定义域名。
+
+#### Upstash Redis 支持
+
+0. 完成普通部署并成功访问。
+1. 在 [upstash](https://upstash.com/) 注册账号并新建一个 Redis 实例,名称任意。
+2. 复制新数据库的 **HTTPS ENDPOINT 和 TOKEN**
+3. 返回你的 Vercel 项目,新增环境变量 **UPSTASH_URL 和 UPSTASH_TOKEN**,值为第二步复制的 endpoint 和 token
+4. 设置环境变量 NEXT_PUBLIC_STORAGE_TYPE,值为 **upstash**;设置 USERNAME 和 PASSWORD 作为站长账号
+5. 重试部署
+
+### Cloudflare 部署
+
+**Cloudflare Pages 的环境变量尽量设置为密钥而非文本**
+
+#### 普通部署(localstorage)
+
+1. **Fork** 本仓库到你的 GitHub 账户。
+2. 登陆 [Cloudflare](https://cloudflare.com),点击 **计算(Workers)-> Workers 和 Pages**,点击创建
+3. 选择 Pages,导入现有的 Git 存储库,选择 Fork 后的仓库
+4. 构建命令填写 **pnpm install --frozen-lockfile && pnpm run pages:build**,预设框架为无,构建输出目录为 `.vercel/output/static`
+5. 保持默认设置完成首次部署。进入设置,将兼容性标志设置为 `nodejs_compat`
+6. 首次部署完成后进入设置,新增 PASSWORD 密钥(变量和机密下),而后重试部署。
+7. 如需自定义 `config.json`,请直接修改 Fork 后仓库中该文件。
+8. 每次 Push 到 `main` 分支将自动触发重新构建。
+
+#### D1 支持
+
+0. 完成普通部署并成功访问
+1. 点击 **存储和数据库 -> D1 SQL 数据库**,创建一个新的数据库,名称随意
+2. 进入刚创建的数据库,点击左上角的 Explore Data,将[D1 初始化](D1初始化.md) 中的内容粘贴到 Query 窗口后点击 **Run All**,等待运行完成
+3. 返回你的 pages 项目,进入 **设置 -> 绑定**,添加绑定 D1 数据库,选择你刚创建的数据库,变量名称填 **DB**
+4. 设置环境变量 NEXT_PUBLIC_STORAGE_TYPE,值为 **d1**;设置 USERNAME 和 PASSWORD 作为站长账号
+5. 重试部署
+
+### Docker 部署
+
+#### 1. 直接运行(最简单)
+
+```bash
+# 拉取预构建镜像
+docker pull ghcr.io/senshinya/moontv:latest
+
+# 运行容器
+# -d: 后台运行 -p: 映射端口 3000 -> 3000
+docker run -d --name moontv -p 3000:3000 --env PASSWORD=your_password ghcr.io/senshinya/moontv:latest
+```
+
+访问 `http://服务器 IP:3000` 即可。(需自行到服务器控制台放通 `3000` 端口)
+
+## Docker Compose 最佳实践
+
+若你使用 docker compose 部署,以下是一些 compose 示例
+
+### local storage 版本
+
+```yaml
+services:
+ moontv:
+ image: ghcr.io/senshinya/moontv:latest
+ container_name: moontv
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ environment:
+ - PASSWORD=your_password
+ # 如需自定义配置,可挂载文件
+ # volumes:
+ # - ./config.json:/app/config.json:ro
+```
+
+### Redis 版本(推荐,多账户数据隔离,跨设备同步)
+
+```yaml
+services:
+ moontv-core:
+ image: ghcr.io/senshinya/moontv:latest
+ container_name: moontv
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
+ environment:
+ - USERNAME=admin
+ - PASSWORD=admin_password
+ - NEXT_PUBLIC_STORAGE_TYPE=redis
+ - REDIS_URL=redis://moontv-redis:6379
+ - NEXT_PUBLIC_ENABLE_REGISTER=true
+ networks:
+ - moontv-network
+ depends_on:
+ - moontv-redis
+ # 如需自定义配置,可挂载文件
+ # volumes:
+ # - ./config.json:/app/config.json:ro
+ moontv-redis:
+ image: redis
+ container_name: moontv-redis
+ restart: unless-stopped
+ networks:
+ - moontv-network
+ # 如需持久化
+ # volumes:
+ # - ./data:/data
+networks:
+ moontv-network:
+ driver: bridge
+```
+
+## 自动同步最近更改
+
+建议在 fork 的仓库中启用本仓库自带的 GitHub Actions 自动同步功能(见 `.github/workflows/sync.yml`)。
+
+如需手动同步主仓库更新,也可以使用 GitHub 官方的 [Sync fork](https://docs.github.com/cn/github/collaborating-with-issues-and-pull-requests/syncing-a-fork) 功能。
+
+## 环境变量
+
+| 变量 | 说明 | 可选值 | 默认值 |
+| --------------------------- | ----------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
+| USERNAME | redis 部署时的管理员账号 | 任意字符串 | (空) |
+| PASSWORD | 默认部署时为唯一访问密码,redis 部署时为管理员密码 | 任意字符串 | (空) |
+| SITE_NAME | 站点名称 | 任意字符串 | MoonTV |
+| ANNOUNCEMENT | 站点公告 | 任意字符串 | 本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。 |
+| NEXT_PUBLIC_STORAGE_TYPE | 播放记录/收藏的存储方式 | localstorage、redis、d1、upstash | localstorage |
+| REDIS_URL | redis 连接 url,若 NEXT_PUBLIC_STORAGE_TYPE 为 redis 则必填 | 连接 url | 空 |
+| UPSTASH_URL | upstash redis 连接 url | 连接 url | 空 |
+| UPSTASH_TOKEN | upstash redis 连接 token | 连接 token | 空 |
+| NEXT_PUBLIC_ENABLE_REGISTER | 是否开放注册,仅在非 localstorage 部署时生效 | true / false | false |
+| NEXT_PUBLIC_SEARCH_MAX_PAGE | 搜索接口可拉取的最大页数 | 1-50 | 5 |
+| NEXT_PUBLIC_IMAGE_PROXY | 默认的浏览器端图片代理 | url prefix | (空) |
+| NEXT_PUBLIC_DOUBAN_PROXY | 默认的浏览器端豆瓣数据代理 | url prefix | (空) |
+
+## 配置说明
+
+所有可自定义项集中在根目录的 `config.json` 中:
+
+```json
+{
+ "cache_time": 7200,
+ "api_site": {
+ "dyttzy": {
+ "api": "http://caiji.dyttzyapi.com/api.php/provide/vod",
+ "name": "电影天堂资源",
+ "detail": "http://caiji.dyttzyapi.com"
+ }
+ // ...更多站点
+ }
+}
+```
+
+- `cache_time`:接口缓存时间(秒)。
+- `api_site`:你可以增删或替换任何资源站,字段说明:
+ - `key`:唯一标识,保持小写字母/数字。
+ - `api`:资源站提供的 `vod` JSON API 根地址。
+ - `name`:在人机界面中展示的名称。
+ - `detail`:(可选)部分无法通过 API 获取剧集详情的站点,需要提供网页详情根 URL,用于爬取。
+
+MoonTV 支持标准的苹果 CMS V10 API 格式。
+
+修改后 **无需重新构建**,服务会在启动时读取一次。
+
+## 管理员配置
+
+**该特性目前仅支持通过非 localstorage 存储的部署方式使用**
+
+支持在运行时动态变更服务配置
+
+设置环境变量 USERNAME 和 PASSWORD 即为站长用户,站长可设置用户为管理员
+
+站长或管理员访问 `/admin` 即可进行管理员配置
+
+## AndroidTV 使用
+
+目前该项目可以配合 [OrionTV](https://github.com/zimplexing/OrionTV) 在 Android TV 上使用,可以直接作为 OrionTV 后端
+
+暂时收藏夹与播放记录和网页端隔离,后续会支持同步用户数据
+
+## Roadmap
+
+- [x] 深色模式
+- [x] 持久化存储
+- [x] 多账户
+
+## 安全与隐私提醒
+
+### 强烈建议设置密码保护
+
+为了您的安全和避免潜在的法律风险,我们**强烈建议**在部署时设置密码保护:
+
+- **避免公开访问**:不设置密码的实例任何人都可以访问,可能被恶意利用
+- **防范版权风险**:公开的视频搜索服务可能面临版权方的投诉举报
+- **保护个人隐私**:设置密码可以限制访问范围,保护您的使用记录
+
+### 部署建议
+
+1. **设置环境变量 `PASSWORD`**:为您的实例设置一个强密码
+2. **仅供个人使用**:请勿将您的实例链接公开分享或传播
+3. **遵守当地法律**:请确保您的使用行为符合当地法律法规
+
+### 重要声明
+
+- 本项目仅供学习和个人使用
+- 请勿将部署的实例用于商业用途或公开服务
+- 如因公开分享导致的任何法律问题,用户需自行承担责任
+- 项目开发者不对用户的使用行为承担任何法律责任
+
+## License
+
+[MIT](LICENSE) © 2025 MoonTV & Contributors
+
+## 致谢
+
+- [ts-nextjs-tailwind-starter](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter) — 项目最初基于该脚手架。
+- [LibreTV](https://github.com/LibreSpark/LibreTV) — 由此启发,站在巨人的肩膀上。
+- [ArtPlayer](https://github.com/zhw2590582/ArtPlayer) — 提供强大的网页视频播放器。
+- [HLS.js](https://github.com/video-dev/hls.js) — 实现 HLS 流媒体在浏览器中的播放支持。
+- 感谢所有提供免费影视接口的站点。
diff --git a/VERSION.txt b/VERSION.txt
new file mode 100644
index 0000000..c3eaf9c
--- /dev/null
+++ b/VERSION.txt
@@ -0,0 +1 @@
+20250928125318
\ No newline at end of file
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..3bf488d
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,24 @@
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ // TODO Add Scope Enum Here
+ // 'scope-enum': [2, 'always', ['yourscope', 'yourscope']],
+ 'type-enum': [
+ 2,
+ 'always',
+ [
+ 'feat',
+ 'fix',
+ 'docs',
+ 'chore',
+ 'style',
+ 'refactor',
+ 'ci',
+ 'test',
+ 'perf',
+ 'revert',
+ 'vercel',
+ ],
+ ],
+ },
+};
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..00db9fe
--- /dev/null
+++ b/config.json
@@ -0,0 +1,89 @@
+{
+ "cache_time": 7200,
+ "api_site": {
+ "dyttzy": {
+ "api": "http://caiji.dyttzyapi.com/api.php/provide/vod",
+ "name": "电影天堂资源",
+ "detail": "http://caiji.dyttzyapi.com"
+ },
+ "heimuer": {
+ "api": "https://json.heimuer.xyz/api.php/provide/vod",
+ "name": "黑木耳",
+ "detail": "https://heimuer.tv"
+ },
+ "ruyi": {
+ "api": "https://cj.rycjapi.com/api.php/provide/vod",
+ "name": "如意资源"
+ },
+ "bfzy": {
+ "api": "https://bfzyapi.com/api.php/provide/vod",
+ "name": "暴风资源"
+ },
+ "tyyszy": {
+ "api": "https://tyyszy.com/api.php/provide/vod",
+ "name": "天涯资源"
+ },
+ "ffzy": {
+ "api": "http://ffzy5.tv/api.php/provide/vod",
+ "name": "非凡影视",
+ "detail": "http://ffzy5.tv"
+ },
+ "zy360": {
+ "api": "https://360zy.com/api.php/provide/vod",
+ "name": "360资源"
+ },
+ "maotaizy": {
+ "api": "https://caiji.maotaizy.cc/api.php/provide/vod",
+ "name": "茅台资源"
+ },
+ "wolong": {
+ "api": "https://wolongzyw.com/api.php/provide/vod",
+ "name": "卧龙资源"
+ },
+ "jisu": {
+ "api": "https://jszyapi.com/api.php/provide/vod",
+ "name": "极速资源",
+ "detail": "https://jszyapi.com"
+ },
+ "dbzy": {
+ "api": "https://dbzy.tv/api.php/provide/vod",
+ "name": "豆瓣资源"
+ },
+ "mozhua": {
+ "api": "https://mozhuazy.com/api.php/provide/vod",
+ "name": "魔爪资源"
+ },
+ "mdzy": {
+ "api": "https://www.mdzyapi.com/api.php/provide/vod",
+ "name": "魔都资源"
+ },
+ "zuid": {
+ "api": "https://api.zuidapi.com/api.php/provide/vod",
+ "name": "最大资源"
+ },
+ "yinghua": {
+ "api": "https://m3u8.apiyhzy.com/api.php/provide/vod",
+ "name": "樱花资源"
+ },
+ "wujin": {
+ "api": "https://api.wujinapi.me/api.php/provide/vod",
+ "name": "无尽资源"
+ },
+ "wwzy": {
+ "api": "https://wwzy.tv/api.php/provide/vod",
+ "name": "旺旺短剧"
+ },
+ "ikun": {
+ "api": "https://ikunzyapi.com/api.php/provide/vod",
+ "name": "iKun资源"
+ },
+ "lzi": {
+ "api": "https://cj.lziapi.com/api.php/provide/vod",
+ "name": "量子资源站"
+ },
+ "xiaomaomi": {
+ "api": "https://zy.xmm.hk/api.php/provide/vod",
+ "name": "小猫咪资源"
+ }
+ }
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..10886cb
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,30 @@
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const nextJest = require('next/jest');
+
+const createJestConfig = nextJest({
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
+ dir: './',
+});
+
+// Add any custom config to be passed to Jest
+const customJestConfig = {
+ // Add more setup options before each test is run
+ setupFilesAfterEnv: ['/jest.setup.js'],
+
+ // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
+ moduleDirectories: ['node_modules', '/'],
+
+ testEnvironment: 'jest-environment-jsdom',
+
+ /**
+ * Absolute imports and Module Path Aliases
+ */
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
+ '^~/(.*)$': '/public/$1',
+ '^.+\\.(svg)$': '/src/__mocks__/svg.tsx',
+ },
+};
+
+// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
+module.exports = createJestConfig(customJestConfig);
diff --git a/jest.setup.js b/jest.setup.js
new file mode 100644
index 0000000..3f1e9e1
--- /dev/null
+++ b/jest.setup.js
@@ -0,0 +1,5 @@
+import '@testing-library/jest-dom/extend-expect';
+
+// Allow router mocks.
+// eslint-disable-next-line no-undef
+jest.mock('next/router', () => require('next-router-mock'));
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..db8e508
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,74 @@
+/** @type {import('next').NextConfig} */
+/* eslint-disable @typescript-eslint/no-var-requires */
+const nextConfig = {
+ output: 'standalone',
+ eslint: {
+ dirs: ['src'],
+ },
+
+ reactStrictMode: false,
+ swcMinify: true,
+
+ // Uncoment to add domain whitelist
+ images: {
+ unoptimized: true,
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: '**',
+ },
+ {
+ protocol: 'http',
+ hostname: '**',
+ },
+ ],
+ },
+
+ webpack(config) {
+ // Grab the existing rule that handles SVG imports
+ const fileLoaderRule = config.module.rules.find((rule) =>
+ rule.test?.test?.('.svg')
+ );
+
+ config.module.rules.push(
+ // Reapply the existing rule, but only for svg imports ending in ?url
+ {
+ ...fileLoaderRule,
+ test: /\.svg$/i,
+ resourceQuery: /url/, // *.svg?url
+ },
+ // Convert all other *.svg imports to React components
+ {
+ test: /\.svg$/i,
+ issuer: { not: /\.(css|scss|sass)$/ },
+ resourceQuery: { not: /url/ }, // exclude if *.svg?url
+ loader: '@svgr/webpack',
+ options: {
+ dimensions: false,
+ titleProp: true,
+ },
+ }
+ );
+
+ // Modify the file loader rule to ignore *.svg, since we have it handled now.
+ fileLoaderRule.exclude = /\.svg$/i;
+
+ config.resolve.fallback = {
+ ...config.resolve.fallback,
+ net: false,
+ tls: false,
+ crypto: false,
+ };
+
+ return config;
+ },
+};
+
+const withPWA = require('next-pwa')({
+ dest: 'public',
+ disable: process.env.NODE_ENV === 'development',
+ register: true,
+ skipWaiting: true,
+});
+
+module.exports = withPWA(nextConfig);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9f0add8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,92 @@
+{
+ "name": "moontv",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "pnpm gen:runtime && pnpm gen:manifest && next dev -H 0.0.0.0",
+ "build": "pnpm gen:runtime && pnpm gen:manifest && next build",
+ "start": "next start",
+ "lint": "next lint",
+ "lint:fix": "eslint src --fix && pnpm format",
+ "lint:strict": "eslint --max-warnings=0 src",
+ "typecheck": "tsc --noEmit --incremental false",
+ "test:watch": "jest --watch",
+ "test": "jest",
+ "format": "prettier -w .",
+ "format:check": "prettier -c .",
+ "gen:runtime": "node scripts/convert-config.js",
+ "gen:manifest": "node scripts/generate-manifest.js",
+ "gen:version": "node scripts/generate-version.js",
+ "postbuild": "echo 'Build completed - sitemap generation disabled'",
+ "prepare": "husky install",
+ "pages:build": "pnpm gen:runtime && pnpm gen:manifest && next build && npx @cloudflare/next-on-pages --experimental-minify"
+ },
+ "dependencies": {
+ "@cloudflare/next-on-pages": "^1.13.12",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@headlessui/react": "^2.2.4",
+ "@heroicons/react": "^2.2.0",
+ "@upstash/redis": "^1.25.0",
+ "@vidstack/react": "^1.12.13",
+ "artplayer": "^5.2.3",
+ "clsx": "^2.0.0",
+ "framer-motion": "^12.18.1",
+ "hls.js": "^1.6.6",
+ "lucide-react": "^0.438.0",
+ "media-icons": "^1.1.5",
+ "next": "^14.2.23",
+ "next-pwa": "^5.6.0",
+ "next-themes": "^0.4.6",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-icons": "^5.4.0",
+ "redis": "^4.6.7",
+ "sweetalert2": "^11.11.0",
+ "swiper": "^11.2.8",
+ "tailwind-merge": "^2.6.0",
+ "vidstack": "^0.6.15",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@commitlint/cli": "^16.3.0",
+ "@commitlint/config-conventional": "^16.2.4",
+ "@svgr/webpack": "^8.1.0",
+ "@tailwindcss/forms": "^0.5.10",
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^15.0.7",
+ "@types/node": "24.0.3",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^19.1.6",
+ "@types/testing-library__jest-dom": "^5.14.9",
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
+ "@typescript-eslint/parser": "^5.62.0",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^8.57.1",
+ "eslint-config-next": "^14.2.23",
+ "eslint-config-prettier": "^8.10.0",
+ "eslint-plugin-simple-import-sort": "^7.0.0",
+ "eslint-plugin-unused-imports": "^2.0.0",
+ "husky": "^7.0.4",
+ "jest": "^27.5.1",
+ "lint-staged": "^12.5.0",
+ "next-router-mock": "^0.9.0",
+ "postcss": "^8.5.1",
+ "prettier": "^2.8.8",
+ "prettier-plugin-tailwindcss": "^0.5.0",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^4.9.5"
+ },
+ "lint-staged": {
+ "**/*.{js,jsx,ts,tsx}": [
+ "eslint --max-warnings=0",
+ "prettier -w"
+ ],
+ "**/*.{json,css,scss,md,webmanifest}": [
+ "prettier -w"
+ ]
+ },
+ "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..20569b3
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,13450 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@cloudflare/next-on-pages':
+ specifier: ^1.13.12
+ version: 1.13.12(vercel@44.2.7(rollup@2.79.2))(wrangler@4.22.0)
+ '@dnd-kit/core':
+ specifier: ^6.3.1
+ version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@dnd-kit/modifiers':
+ specifier: ^9.0.0
+ version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ '@dnd-kit/sortable':
+ specifier: ^10.0.0
+ version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ '@dnd-kit/utilities':
+ specifier: ^3.2.2
+ version: 3.2.2(react@18.3.1)
+ '@headlessui/react':
+ specifier: ^2.2.4
+ version: 2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@heroicons/react':
+ specifier: ^2.2.0
+ version: 2.2.0(react@18.3.1)
+ '@upstash/redis':
+ specifier: ^1.25.0
+ version: 1.35.1
+ '@vidstack/react':
+ specifier: ^1.12.13
+ version: 1.12.13(@types/react@18.3.23)(react@18.3.1)
+ artplayer:
+ specifier: ^5.2.3
+ version: 5.2.3
+ clsx:
+ specifier: ^2.0.0
+ version: 2.1.1
+ framer-motion:
+ specifier: ^12.18.1
+ version: 12.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ hls.js:
+ specifier: ^1.6.6
+ version: 1.6.6
+ lucide-react:
+ specifier: ^0.438.0
+ version: 0.438.0(react@18.3.1)
+ media-icons:
+ specifier: ^1.1.5
+ version: 1.1.5
+ next:
+ specifier: ^14.2.23
+ version: 14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next-pwa:
+ specifier: ^5.6.0
+ version: 5.6.0(@babel/core@7.27.4)(@types/babel__core@7.20.5)(next@14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.99.9)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-icons:
+ specifier: ^5.4.0
+ version: 5.5.0(react@18.3.1)
+ redis:
+ specifier: ^4.6.7
+ version: 4.7.1
+ sweetalert2:
+ specifier: ^11.11.0
+ version: 11.22.2
+ swiper:
+ specifier: ^11.2.8
+ version: 11.2.8
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
+ vidstack:
+ specifier: ^0.6.15
+ version: 0.6.15
+ zod:
+ specifier: ^3.24.1
+ version: 3.25.67
+ devDependencies:
+ '@commitlint/cli':
+ specifier: ^16.3.0
+ version: 16.3.0
+ '@commitlint/config-conventional':
+ specifier: ^16.2.4
+ version: 16.2.4
+ '@svgr/webpack':
+ specifier: ^8.1.0
+ version: 8.1.0(typescript@4.9.5)
+ '@tailwindcss/forms':
+ specifier: ^0.5.10
+ version: 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)))
+ '@testing-library/jest-dom':
+ specifier: ^5.17.0
+ version: 5.17.0
+ '@testing-library/react':
+ specifier: ^15.0.7
+ version: 15.0.7(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@types/node':
+ specifier: 24.0.3
+ version: 24.0.3
+ '@types/react':
+ specifier: ^18.3.18
+ version: 18.3.23
+ '@types/react-dom':
+ specifier: ^19.1.6
+ version: 19.1.6(@types/react@18.3.23)
+ '@types/testing-library__jest-dom':
+ specifier: ^5.14.9
+ version: 5.14.9
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^5.62.0
+ version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
+ '@typescript-eslint/parser':
+ specifier: ^5.62.0
+ version: 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.21(postcss@8.5.6)
+ eslint:
+ specifier: ^8.57.1
+ version: 8.57.1
+ eslint-config-next:
+ specifier: ^14.2.23
+ version: 14.2.30(eslint@8.57.1)(typescript@4.9.5)
+ eslint-config-prettier:
+ specifier: ^8.10.0
+ version: 8.10.0(eslint@8.57.1)
+ eslint-plugin-simple-import-sort:
+ specifier: ^7.0.0
+ version: 7.0.0(eslint@8.57.1)
+ eslint-plugin-unused-imports:
+ specifier: ^2.0.0
+ version: 2.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)
+ husky:
+ specifier: ^7.0.4
+ version: 7.0.4
+ jest:
+ specifier: ^27.5.1
+ version: 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ lint-staged:
+ specifier: ^12.5.0
+ version: 12.5.0
+ next-router-mock:
+ specifier: ^0.9.0
+ version: 0.9.13(next@14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ postcss:
+ specifier: ^8.5.1
+ version: 8.5.6
+ prettier:
+ specifier: ^2.8.8
+ version: 2.8.8
+ prettier-plugin-tailwindcss:
+ specifier: ^0.5.0
+ version: 0.5.14(prettier@2.8.8)
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ typescript:
+ specifier: ^4.9.5
+ version: 4.9.5
+
+packages:
+
+ '@adobe/css-tools@4.4.3':
+ resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==}
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@apideck/better-ajv-errors@0.3.6':
+ resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ ajv: '>=8'
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.27.5':
+ resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.27.4':
+ resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.27.5':
+ resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-create-class-features-plugin@7.27.1':
+ resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-create-regexp-features-plugin@7.27.1':
+ resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-define-polyfill-provider@0.6.4':
+ resolution: {integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.27.3':
+ resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-remap-async-to-generator@7.27.1':
+ resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-replace-supers@7.27.1':
+ resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.27.1':
+ resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-wrap-function@7.27.1':
+ resolution: {integrity: sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.27.6':
+ resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.27.5':
+ resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1':
+ resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1':
+ resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1':
+ resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1':
+ resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.13.0
+
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1':
+ resolution: {integrity: sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
+ resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-async-generators@7.8.4':
+ resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-bigint@7.8.3':
+ resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-properties@7.12.13':
+ resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-static-block@7.14.5':
+ resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-assertions@7.27.1':
+ resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-attributes@7.27.1':
+ resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-meta@7.10.4':
+ resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-json-strings@7.8.3':
+ resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-jsx@7.27.1':
+ resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
+ resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
+ resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4':
+ resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3':
+ resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3':
+ resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3':
+ resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5':
+ resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-top-level-await@7.14.5':
+ resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.27.1':
+ resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6':
+ resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-arrow-functions@7.27.1':
+ resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-async-generator-functions@7.27.1':
+ resolution: {integrity: sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-async-to-generator@7.27.1':
+ resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-block-scoped-functions@7.27.1':
+ resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-block-scoping@7.27.5':
+ resolution: {integrity: sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-class-properties@7.27.1':
+ resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-class-static-block@7.27.1':
+ resolution: {integrity: sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.12.0
+
+ '@babel/plugin-transform-classes@7.27.1':
+ resolution: {integrity: sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-computed-properties@7.27.1':
+ resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-destructuring@7.27.3':
+ resolution: {integrity: sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-dotall-regex@7.27.1':
+ resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-duplicate-keys@7.27.1':
+ resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1':
+ resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-dynamic-import@7.27.1':
+ resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-exponentiation-operator@7.27.1':
+ resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-export-namespace-from@7.27.1':
+ resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-for-of@7.27.1':
+ resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-function-name@7.27.1':
+ resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-json-strings@7.27.1':
+ resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-literals@7.27.1':
+ resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-logical-assignment-operators@7.27.1':
+ resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-member-expression-literals@7.27.1':
+ resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-amd@7.27.1':
+ resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1':
+ resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-systemjs@7.27.1':
+ resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-umd@7.27.1':
+ resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-named-capturing-groups-regex@7.27.1':
+ resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-new-target@7.27.1':
+ resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-nullish-coalescing-operator@7.27.1':
+ resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-numeric-separator@7.27.1':
+ resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-object-rest-spread@7.27.3':
+ resolution: {integrity: sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-object-super@7.27.1':
+ resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-optional-catch-binding@7.27.1':
+ resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-optional-chaining@7.27.1':
+ resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-parameters@7.27.1':
+ resolution: {integrity: sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-private-methods@7.27.1':
+ resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-private-property-in-object@7.27.1':
+ resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-property-literals@7.27.1':
+ resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-constant-elements@7.27.1':
+ resolution: {integrity: sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-display-name@7.27.1':
+ resolution: {integrity: sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-development@7.27.1':
+ resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx@7.27.1':
+ resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-pure-annotations@7.27.1':
+ resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-regenerator@7.27.5':
+ resolution: {integrity: sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-regexp-modifiers@7.27.1':
+ resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-reserved-words@7.27.1':
+ resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-shorthand-properties@7.27.1':
+ resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-spread@7.27.1':
+ resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-sticky-regex@7.27.1':
+ resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-template-literals@7.27.1':
+ resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typeof-symbol@7.27.1':
+ resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typescript@7.27.1':
+ resolution: {integrity: sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-escapes@7.27.1':
+ resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-property-regex@7.27.1':
+ resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-regex@7.27.1':
+ resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-sets-regex@7.27.1':
+ resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/preset-env@7.27.2':
+ resolution: {integrity: sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-modules@0.1.6-no-external-plugins':
+ resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
+
+ '@babel/preset-react@7.27.1':
+ resolution: {integrity: sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-typescript@7.27.1':
+ resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.27.6':
+ resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.27.4':
+ resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.27.6':
+ resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@bcoe/v8-coverage@0.2.3':
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+
+ '@cloudflare/kv-asset-handler@0.4.0':
+ resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
+ engines: {node: '>=18.0.0'}
+
+ '@cloudflare/next-on-pages@1.13.12':
+ resolution: {integrity: sha512-rPy7x9c2+0RDDdJ5o0TeRUwXJ1b7N1epnqF6qKSp5Wz1r9KHOyvaZh1ACoOC6Vu5k9su5WZOgy+8fPLIyrldMQ==}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^4.20240208.0
+ vercel: '>=30.0.0'
+ wrangler: ^3.28.2 || ^4.0.0
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+
+ '@cloudflare/unenv-preset@2.3.3':
+ resolution: {integrity: sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A==}
+ peerDependencies:
+ unenv: 2.0.0-rc.17
+ workerd: ^1.20250508.0
+ peerDependenciesMeta:
+ workerd:
+ optional: true
+
+ '@cloudflare/workerd-darwin-64@1.20250408.0':
+ resolution: {integrity: sha512-bxhIwBWxaNItZLXDNOKY2dCv0FHjDiDkfJFpwv4HvtvU5MKcrivZHVmmfDzLW85rqzfcDOmKbZeMPVfiKxdBZw==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-64@1.20250617.0':
+ resolution: {integrity: sha512-toG8JUKVLIks4oOJLe9FeuixE84pDpMZ32ip7mCpE7JaFc5BqGFvevk0YC/db3T71AQlialjRwioH3jS/dzItA==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20250408.0':
+ resolution: {integrity: sha512-5XZ2Oykr8bSo7zBmERtHh18h5BZYC/6H1YFWVxEj3PtalF3+6SHsO4KZsbGvDml9Pu7sHV277jiZE5eny8Hlyw==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20250617.0':
+ resolution: {integrity: sha512-JTX0exbC9/ZtMmQQA8tDZEZFMXZrxOpTUj2hHnsUkErWYkr5SSZH04RBhPg6dU4VL8bXuB5/eJAh7+P9cZAp7g==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-linux-64@1.20250408.0':
+ resolution: {integrity: sha512-WbgItXWln6G5d7GvYLWcuOzAVwafysZaWunH3UEfsm95wPuRofpYnlDD861gdWJX10IHSVgMStGESUcs7FLerQ==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-64@1.20250617.0':
+ resolution: {integrity: sha512-8jkSoVRJ+1bOx3tuWlZCGaGCV2ew7/jFMl6V3CPXOoEtERUHsZBQLVkQIGKcmC/LKSj7f/mpyBUeu2EPTo2HEg==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20250408.0':
+ resolution: {integrity: sha512-pAhEywPPvr92SLylnQfZEPgXz+9pOG9G9haAPLpEatncZwYiYd9yiR6HYWhKp2erzCoNrOqKg9IlQwU3z1IDiw==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20250617.0':
+ resolution: {integrity: sha512-YAzcOyu897z5dQKFzme1oujGWMGEJCR7/Wrrm1nSP6dqutxFPTubRADM8BHn2CV3ij//vaPnAeLmZE3jVwOwig==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-windows-64@1.20250408.0':
+ resolution: {integrity: sha512-nJ3RjMKGae2aF2rZ/CNeBvQPM+W5V1SUK0FYWG/uomyr7uQ2l4IayHna1ODg/OHHTEgIjwom0Mbn58iXb0WOcQ==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cloudflare/workerd-windows-64@1.20250617.0':
+ resolution: {integrity: sha512-XWM/6sagDrO0CYDKhXhPjM23qusvIN1ju9ZEml6gOQs8tNOFnq6Cn6X9FAmnyapRFCGUSEC3HZYJAm7zwVKaMA==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@commitlint/cli@16.3.0':
+ resolution: {integrity: sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA==}
+ engines: {node: '>=v12'}
+ hasBin: true
+
+ '@commitlint/config-conventional@16.2.4':
+ resolution: {integrity: sha512-av2UQJa3CuE5P0dzxj/o/B9XVALqYzEViHrMXtDrW9iuflrqCStWBAioijppj9URyz6ONpohJKAtSdgAOE0gkA==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/config-validator@16.2.1':
+ resolution: {integrity: sha512-hogSe0WGg7CKmp4IfNbdNES3Rq3UEI4XRPB8JL4EPgo/ORq5nrGTVzxJh78omibNuB8Ho4501Czb1Er1MoDWpw==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/ensure@16.2.1':
+ resolution: {integrity: sha512-/h+lBTgf1r5fhbDNHOViLuej38i3rZqTQnBTk+xEg+ehOwQDXUuissQ5GsYXXqI5uGy+261ew++sT4EA3uBJ+A==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/execute-rule@16.2.1':
+ resolution: {integrity: sha512-oSls82fmUTLM6cl5V3epdVo4gHhbmBFvCvQGHBRdQ50H/690Uq1Dyd7hXMuKITCIdcnr9umyDkr8r5C6HZDF3g==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/format@16.2.1':
+ resolution: {integrity: sha512-Yyio9bdHWmNDRlEJrxHKglamIk3d6hC0NkEUW6Ti6ipEh2g0BAhy8Od6t4vLhdZRa1I2n+gY13foy+tUgk0i1Q==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/is-ignored@16.2.4':
+ resolution: {integrity: sha512-Lxdq9aOAYCOOOjKi58ulbwK/oBiiKz+7Sq0+/SpFIEFwhHkIVugvDvWjh2VRBXmRC/x5lNcjDcYEwS/uYUvlYQ==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/lint@16.2.4':
+ resolution: {integrity: sha512-AUDuwOxb2eGqsXbTMON3imUGkc1jRdtXrbbohiLSCSk3jFVXgJLTMaEcr39pR00N8nE9uZ+V2sYaiILByZVmxQ==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/load@16.3.0':
+ resolution: {integrity: sha512-3tykjV/iwbkv2FU9DG+NZ/JqmP0Nm3b7aDwgCNQhhKV5P74JAuByULkafnhn+zsFGypG1qMtI5u+BZoa9APm0A==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/message@16.2.1':
+ resolution: {integrity: sha512-2eWX/47rftViYg7a3axYDdrgwKv32mxbycBJT6OQY/MJM7SUfYNYYvbMFOQFaA4xIVZt7t2Alyqslbl6blVwWw==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/parse@16.2.1':
+ resolution: {integrity: sha512-2NP2dDQNL378VZYioLrgGVZhWdnJO4nAxQl5LXwYb08nEcN+cgxHN1dJV8OLJ5uxlGJtDeR8UZZ1mnQ1gSAD/g==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/read@16.2.1':
+ resolution: {integrity: sha512-tViXGuaxLTrw2r7PiYMQOFA2fueZxnnt0lkOWqKyxT+n2XdEMGYcI9ID5ndJKXnfPGPppD0w/IItKsIXlZ+alw==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/resolve-extends@16.2.1':
+ resolution: {integrity: sha512-NbbCMPKTFf2J805kwfP9EO+vV+XvnaHRcBy6ud5dF35dxMsvdJqke54W3XazXF1ZAxC4a3LBy4i/GNVBAthsEg==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/rules@16.2.4':
+ resolution: {integrity: sha512-rK5rNBIN2ZQNQK+I6trRPK3dWa0MtaTN4xnwOma1qxa4d5wQMQJtScwTZjTJeallFxhOgbNOgr48AMHkdounVg==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/to-lines@16.2.1':
+ resolution: {integrity: sha512-9/VjpYj5j1QeY3eiog1zQWY6axsdWAc0AonUUfyZ7B0MVcRI0R56YsHAfzF6uK/g/WwPZaoe4Lb1QCyDVnpVaQ==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/top-level@16.2.1':
+ resolution: {integrity: sha512-lS6GSieHW9y6ePL73ied71Z9bOKyK+Ib9hTkRsB8oZFAyQZcyRwq2w6nIa6Fngir1QW51oKzzaXfJL94qwImyw==}
+ engines: {node: '>=v12'}
+
+ '@commitlint/types@16.2.1':
+ resolution: {integrity: sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA==}
+ engines: {node: '>=v12'}
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@dnd-kit/accessibility@3.1.1':
+ resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@dnd-kit/core@6.3.1':
+ resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@dnd-kit/modifiers@9.0.0':
+ resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==}
+ peerDependencies:
+ '@dnd-kit/core': ^6.3.0
+ react: '>=16.8.0'
+
+ '@dnd-kit/sortable@10.0.0':
+ resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
+ peerDependencies:
+ '@dnd-kit/core': ^6.3.0
+ react: '>=16.8.0'
+
+ '@dnd-kit/utilities@3.2.2':
+ resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@edge-runtime/format@2.2.1':
+ resolution: {integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==}
+ engines: {node: '>=16'}
+
+ '@edge-runtime/node-utils@2.3.0':
+ resolution: {integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==}
+ engines: {node: '>=16'}
+
+ '@edge-runtime/ponyfill@2.4.2':
+ resolution: {integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==}
+ engines: {node: '>=16'}
+
+ '@edge-runtime/primitives@4.1.0':
+ resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==}
+ engines: {node: '>=16'}
+
+ '@edge-runtime/vm@3.2.0':
+ resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==}
+ engines: {node: '>=16'}
+
+ '@emnapi/core@1.4.3':
+ resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
+
+ '@emnapi/runtime@1.4.3':
+ resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
+
+ '@emnapi/wasi-threads@1.0.2':
+ resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==}
+
+ '@esbuild/aix-ppc64@0.25.4':
+ resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.4':
+ resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.15.18':
+ resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.4':
+ resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.4':
+ resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.4':
+ resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.4':
+ resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.4':
+ resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.4':
+ resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.4':
+ resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.4':
+ resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.4':
+ resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.15.18':
+ resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.4':
+ resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.4':
+ resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.4':
+ resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.4':
+ resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.4':
+ resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.4':
+ resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.4':
+ resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.4':
+ resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.4':
+ resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.4':
+ resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.25.4':
+ resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.4':
+ resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.4':
+ resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.4':
+ resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.7.0':
+ resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.1':
+ resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/eslintrc@2.1.4':
+ resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@eslint/js@8.57.1':
+ resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@fastify/busboy@2.1.1':
+ resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
+ engines: {node: '>=14'}
+
+ '@floating-ui/core@1.7.1':
+ resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==}
+
+ '@floating-ui/dom@1.7.1':
+ resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==}
+
+ '@floating-ui/react-dom@2.1.3':
+ resolution: {integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/react@0.26.28':
+ resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.9':
+ resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+
+ '@headlessui/react@2.2.4':
+ resolution: {integrity: sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+
+ '@heroicons/react@2.2.0':
+ resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
+ peerDependencies:
+ react: '>= 16 || ^19.0.0-rc'
+
+ '@humanwhocodes/config-array@0.13.0':
+ resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
+ engines: {node: '>=10.10.0'}
+ deprecated: Use @eslint/config-array instead
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/object-schema@2.0.3':
+ resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+ deprecated: Use @eslint/object-schema instead
+
+ '@img/sharp-darwin-arm64@0.33.5':
+ resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.33.5':
+ resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.0.4':
+ resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.0.4':
+ resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.0.4':
+ resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.0.5':
+ resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.0.4':
+ resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.0.4':
+ resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
+ resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.0.4':
+ resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.33.5':
+ resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.33.5':
+ resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.33.5':
+ resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.33.5':
+ resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.33.5':
+ resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.33.5':
+ resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.33.5':
+ resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-ia32@0.33.5':
+ resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.33.5':
+ resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
+ engines: {node: '>=8'}
+
+ '@istanbuljs/schema@0.1.3':
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+
+ '@jest/console@27.5.1':
+ resolution: {integrity: sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/core@27.5.1':
+ resolution: {integrity: sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/diff-sequences@30.0.0':
+ resolution: {integrity: sha512-xMbtoCeKJDto86GW6AiwVv7M4QAuI56R7dVBr1RNGYbOT44M2TIzOiske2RxopBqkumDY+A1H55pGvuribRY9A==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/environment@27.5.1':
+ resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/expect-utils@30.0.0':
+ resolution: {integrity: sha512-UiWfsqNi/+d7xepfOv8KDcbbzcYtkWBe3a3kVDtg6M1kuN6CJ7b4HzIp5e1YHrSaQaVS8sdCoyCMCZClTLNKFQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/fake-timers@27.5.1':
+ resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/get-type@30.0.0':
+ resolution: {integrity: sha512-VZWMjrBzqfDKngQ7sUctKeLxanAbsBFoZnPxNIG6CmxK7Gv6K44yqd0nzveNIBfuhGZMmk1n5PGbvdSTOu0yTg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/globals@27.5.1':
+ resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/pattern@30.0.0':
+ resolution: {integrity: sha512-k+TpEThzLVXMkbdxf8KHjZ83Wl+G54ytVJoDIGWwS96Ql4xyASRjc6SU1hs5jHVql+hpyK9G8N7WuFhLpGHRpQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/reporters@27.5.1':
+ resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/schemas@30.0.0':
+ resolution: {integrity: sha512-NID2VRyaEkevCRz6badhfqYwri/RvMbiHY81rk3AkK/LaiB0LSxi1RdVZ7MpZdTjNugtZeGfpL0mLs9Kp3MrQw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/source-map@27.5.1':
+ resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/test-result@27.5.1':
+ resolution: {integrity: sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/test-sequencer@27.5.1':
+ resolution: {integrity: sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/transform@27.5.1':
+ resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/types@27.5.1':
+ resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ '@jest/types@30.0.0':
+ resolution: {integrity: sha512-1Nox8mAL52PKPfEnUQWBvKU/bp8FTT6AiDu76bFDEJj/qsRFSAVSldfCH3XYMqialti2zHXKvD5gN0AaHc0yKA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jridgewell/gen-mapping@0.3.8':
+ resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/source-map@0.3.6':
+ resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@mapbox/node-pre-gyp@2.0.0':
+ resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ '@maverick-js/signals@5.11.5':
+ resolution: {integrity: sha512-/GO94awrwN9ROYZDMTeByordjvbhcm3CMvB/2aL/sEUy9Va8nM/2GmNgOOe+rrooTGnz8/DzO73xomuBRrnYWw==}
+
+ '@napi-rs/wasm-runtime@0.2.11':
+ resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==}
+
+ '@next/env@14.2.30':
+ resolution: {integrity: sha512-KBiBKrDY6kxTQWGzKjQB7QirL3PiiOkV7KW98leHFjtVRKtft76Ra5qSA/SL75xT44dp6hOcqiiJ6iievLOYug==}
+
+ '@next/eslint-plugin-next@14.2.30':
+ resolution: {integrity: sha512-mvVsMIutMxQ4NGZEMZ1kiBNc+la8Xmlk30bKUmCPQz2eFkmsLv54Mha8QZarMaCtSPkkFA1TMD+FIZk0l/PpzA==}
+
+ '@next/swc-darwin-arm64@14.2.30':
+ resolution: {integrity: sha512-EAqfOTb3bTGh9+ewpO/jC59uACadRHM6TSA9DdxJB/6gxOpyV+zrbqeXiFTDy9uV6bmipFDkfpAskeaDcO+7/g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@14.2.30':
+ resolution: {integrity: sha512-TyO7Wz1IKE2kGv8dwQ0bmPL3s44EKVencOqwIY69myoS3rdpO1NPg5xPM5ymKu7nfX4oYJrpMxv8G9iqLsnL4A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@14.2.30':
+ resolution: {integrity: sha512-I5lg1fgPJ7I5dk6mr3qCH1hJYKJu1FsfKSiTKoYwcuUf53HWTrEkwmMI0t5ojFKeA6Vu+SfT2zVy5NS0QLXV4Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@14.2.30':
+ resolution: {integrity: sha512-8GkNA+sLclQyxgzCDs2/2GSwBc92QLMrmYAmoP2xehe5MUKBLB2cgo34Yu242L1siSkwQkiV4YLdCnjwc/Micw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@14.2.30':
+ resolution: {integrity: sha512-8Ly7okjssLuBoe8qaRCcjGtcMsv79hwzn/63wNeIkzJVFVX06h5S737XNr7DZwlsbTBDOyI6qbL2BJB5n6TV/w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@14.2.30':
+ resolution: {integrity: sha512-dBmV1lLNeX4mR7uI7KNVHsGQU+OgTG5RGFPi3tBJpsKPvOPtg9poyav/BYWrB3GPQL4dW5YGGgalwZ79WukbKQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@14.2.30':
+ resolution: {integrity: sha512-6MMHi2Qc1Gkq+4YLXAgbYslE1f9zMGBikKMdmQRHXjkGPot1JY3n5/Qrbg40Uvbi8//wYnydPnyvNhI1DMUW1g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-ia32-msvc@14.2.30':
+ resolution: {integrity: sha512-pVZMnFok5qEX4RT59mK2hEVtJX+XFfak+/rjHpyFh7juiT52r177bfFKhnlafm0UOSldhXjj32b+LZIOdswGTg==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@14.2.30':
+ resolution: {integrity: sha512-4KCo8hMZXMjpTzs3HOqOGYYwAXymXIy7PEPAXNEcEOyKqkjiDlECumrWziy+JEF0Oi4ILHGxzgQ3YiMGG2t/Lg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@nolyfill/is-core-module@1.0.39':
+ resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
+ engines: {node: '>=12.4.0'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@react-aria/focus@3.20.5':
+ resolution: {integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/interactions@3.25.3':
+ resolution: {integrity: sha512-J1bhlrNtjPS/fe5uJQ+0c7/jiXniwa4RQlP+Emjfc/iuqpW2RhbF9ou5vROcLzWIyaW8tVMZ468J68rAs/aZ5A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/ssr@3.9.9':
+ resolution: {integrity: sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/utils@3.29.1':
+ resolution: {integrity: sha512-yXMFVJ73rbQ/yYE/49n5Uidjw7kh192WNN9PNQGV0Xoc7EJUlSOxqhnpHmYTyO0EotJ8fdM1fMH8durHjUSI8g==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-stately/flags@3.1.2':
+ resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==}
+
+ '@react-stately/utils@3.10.7':
+ resolution: {integrity: sha512-cWvjGAocvy4abO9zbr6PW6taHgF24Mwy/LbQ4TC4Aq3tKdKDntxyD+sh7AkSRfJRT2ccMVaHVv2+FfHThd3PKQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-types/shared@3.30.0':
+ resolution: {integrity: sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@redis/bloom@1.2.0':
+ resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
+ peerDependencies:
+ '@redis/client': ^1.0.0
+
+ '@redis/client@1.6.1':
+ resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==}
+ engines: {node: '>=14'}
+
+ '@redis/graph@1.1.1':
+ resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
+ peerDependencies:
+ '@redis/client': ^1.0.0
+
+ '@redis/json@1.0.7':
+ resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
+ peerDependencies:
+ '@redis/client': ^1.0.0
+
+ '@redis/search@1.2.0':
+ resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
+ peerDependencies:
+ '@redis/client': ^1.0.0
+
+ '@redis/time-series@1.1.0':
+ resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
+ peerDependencies:
+ '@redis/client': ^1.0.0
+
+ '@rollup/plugin-babel@5.3.1':
+ resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
+ engines: {node: '>= 10.0.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ '@types/babel__core': ^7.1.9
+ rollup: ^1.20.0||^2.0.0
+ peerDependenciesMeta:
+ '@types/babel__core':
+ optional: true
+
+ '@rollup/plugin-node-resolve@11.2.1':
+ resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==}
+ engines: {node: '>= 10.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0
+
+ '@rollup/plugin-replace@2.4.2':
+ resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==}
+ peerDependencies:
+ rollup: ^1.20.0 || ^2.0.0
+
+ '@rollup/pluginutils@3.1.0':
+ resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
+ engines: {node: '>= 8.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0
+
+ '@rollup/pluginutils@5.2.0':
+ resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@rushstack/eslint-patch@1.11.0':
+ resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
+
+ '@sinclair/typebox@0.25.24':
+ resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==}
+
+ '@sinclair/typebox@0.34.35':
+ resolution: {integrity: sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==}
+
+ '@sinonjs/commons@1.8.6':
+ resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
+
+ '@sinonjs/fake-timers@8.1.0':
+ resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
+
+ '@surma/rollup-plugin-off-main-thread@2.2.3':
+ resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
+
+ '@svgr/babel-plugin-add-jsx-attribute@8.0.0':
+ resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-remove-jsx-attribute@8.0.0':
+ resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0':
+ resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0':
+ resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-svg-dynamic-title@8.0.0':
+ resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-svg-em-dimensions@8.0.0':
+ resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-transform-react-native-svg@8.1.0':
+ resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-plugin-transform-svg-component@8.0.0':
+ resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/babel-preset@8.1.0':
+ resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@svgr/core@8.1.0':
+ resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
+ engines: {node: '>=14'}
+
+ '@svgr/hast-util-to-babel-ast@8.0.0':
+ resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
+ engines: {node: '>=14'}
+
+ '@svgr/plugin-jsx@8.1.0':
+ resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@svgr/core': '*'
+
+ '@svgr/plugin-svgo@8.1.0':
+ resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@svgr/core': '*'
+
+ '@svgr/webpack@8.1.0':
+ resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==}
+ engines: {node: '>=14'}
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/helpers@0.5.5':
+ resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+
+ '@tailwindcss/forms@0.5.10':
+ resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
+
+ '@tanstack/react-virtual@3.13.10':
+ resolution: {integrity: sha512-nvrzk4E9mWB4124YdJ7/yzwou7IfHxlSef6ugCFcBfRmsnsma3heciiiV97sBNxyc3VuwtZvmwXd0aB5BpucVw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/virtual-core@3.13.10':
+ resolution: {integrity: sha512-sPEDhXREou5HyZYqSWIqdU580rsF6FGeN7vpzijmP3KTiOGjOMZASz4Y6+QKjiFQwhWrR58OP8izYaNGVxvViA==}
+
+ '@testing-library/dom@10.4.0':
+ resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
+ engines: {node: '>=18'}
+
+ '@testing-library/jest-dom@5.17.0':
+ resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==}
+ engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
+
+ '@testing-library/react@15.0.7':
+ resolution: {integrity: sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/react': ^18.0.0
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@tootallnate/once@1.1.2':
+ resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
+ engines: {node: '>= 6'}
+
+ '@tootallnate/once@2.0.0':
+ resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
+ engines: {node: '>= 10'}
+
+ '@trysound/sax@0.2.0':
+ resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+ engines: {node: '>=10.13.0'}
+
+ '@ts-morph/common@0.11.1':
+ resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==}
+
+ '@tsconfig/node10@1.0.11':
+ resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+ '@tybys/wasm-util@0.9.0':
+ resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.20.7':
+ resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
+
+ '@types/eslint-scope@3.7.7':
+ resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
+
+ '@types/eslint@9.6.1':
+ resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
+
+ '@types/estree@0.0.39':
+ resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/glob@7.2.0':
+ resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
+
+ '@types/graceful-fs@4.1.9':
+ resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+
+ '@types/istanbul-lib-coverage@2.0.6':
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+
+ '@types/istanbul-lib-report@3.0.3':
+ resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
+
+ '@types/istanbul-reports@3.0.4':
+ resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+
+ '@types/jest@30.0.0':
+ resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/minimatch@5.1.2':
+ resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
+
+ '@types/minimist@1.2.5':
+ resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
+
+ '@types/node@16.18.11':
+ resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==}
+
+ '@types/node@24.0.3':
+ resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==}
+
+ '@types/normalize-package-data@2.4.4':
+ resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
+
+ '@types/parse-json@4.0.2':
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
+ '@types/prettier@2.7.3':
+ resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react-dom@19.1.6':
+ resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
+ '@types/react@18.3.23':
+ resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
+
+ '@types/resolve@1.17.1':
+ resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
+
+ '@types/semver@7.7.0':
+ resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
+
+ '@types/stack-utils@2.0.3':
+ resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+
+ '@types/testing-library__jest-dom@5.14.9':
+ resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==}
+
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+ '@types/yargs-parser@21.0.3':
+ resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+ '@types/yargs@16.0.9':
+ resolution: {integrity: sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==}
+
+ '@types/yargs@17.0.33':
+ resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
+
+ '@typescript-eslint/eslint-plugin@5.62.0':
+ resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^5.0.0
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/parser@5.62.0':
+ resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/scope-manager@5.62.0':
+ resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@typescript-eslint/type-utils@5.62.0':
+ resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/types@5.62.0':
+ resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@typescript-eslint/typescript-estree@5.62.0':
+ resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/utils@5.62.0':
+ resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ '@typescript-eslint/visitor-keys@5.62.0':
+ resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.9.0':
+ resolution: {integrity: sha512-h1T2c2Di49ekF2TE8ZCoJkb+jwETKUIPDJ/nO3tJBKlLFPu+fyd93f0rGP/BvArKx2k2HlRM4kqkNarj3dvZlg==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.9.0':
+ resolution: {integrity: sha512-sG1NHtgXtX8owEkJ11yn34vt0Xqzi3k9TJ8zppDmyG8GZV4kVWw44FHwKwHeEFl07uKPeC4ZoyuQaGh5ruJYPA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.9.0':
+ resolution: {integrity: sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.9.0':
+ resolution: {integrity: sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.9.0':
+ resolution: {integrity: sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.0':
+ resolution: {integrity: sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.9.0':
+ resolution: {integrity: sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.9.0':
+ resolution: {integrity: sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.9.0':
+ resolution: {integrity: sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.9.0':
+ resolution: {integrity: sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.9.0':
+ resolution: {integrity: sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.9.0':
+ resolution: {integrity: sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.9.0':
+ resolution: {integrity: sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.9.0':
+ resolution: {integrity: sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.9.0':
+ resolution: {integrity: sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.9.0':
+ resolution: {integrity: sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.9.0':
+ resolution: {integrity: sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.9.0':
+ resolution: {integrity: sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.9.0':
+ resolution: {integrity: sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==}
+ cpu: [x64]
+ os: [win32]
+
+ '@upstash/redis@1.35.1':
+ resolution: {integrity: sha512-sIMuAMU9IYbE2bkgDby8KLoQKRiBMXn0moXxqLvUmQ7VUu2CvulZLtK8O0x3WQZFvvZhU5sRC2/lOVZdGfudkA==}
+
+ '@vercel/blob@1.0.2':
+ resolution: {integrity: sha512-Im/KeFH4oPx7UsM+QiteimnE07bIUD7JK6CBafI9Z0jRFogaialTBMiZj8EKk/30ctUYsrpIIyP9iIY1YxWnUQ==}
+ engines: {node: '>=16.14'}
+
+ '@vercel/build-utils@10.6.1':
+ resolution: {integrity: sha512-E6O45bInBcKFDtliPADlNpIMutPjzGepYVfV2GyXdxf+00k6wMAlTQ/HbgWhvErOvy7TkZxFxrkRghAWnGK+UA==}
+
+ '@vercel/error-utils@2.0.3':
+ resolution: {integrity: sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==}
+
+ '@vercel/fun@1.1.6':
+ resolution: {integrity: sha512-xDiM+bD0fSZyzcjsAua3D+guXclvHOSTzr03UcZEQwYzIjwWjLduT7bl2gAaeNIe7fASAIZd0P00clcj0On4rQ==}
+ engines: {node: '>= 18'}
+
+ '@vercel/gatsby-plugin-vercel-analytics@1.0.11':
+ resolution: {integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==}
+
+ '@vercel/gatsby-plugin-vercel-builder@2.0.84':
+ resolution: {integrity: sha512-iQW+4zng32XrBnXqia1pocFweI8YPcUn7i7evLHRhFSSKWRn+6FmQsGPEqzw1cVqwl2ute5+sx0R/J0nr0v0Xw==}
+
+ '@vercel/go@3.2.1':
+ resolution: {integrity: sha512-ezjmuUvLigH9V4egEaX0SZ+phILx8lb+Zkp1iTqKI+yl/ibPAtVo5o+dLSRAXU9U01LBmaLu3O8Oxd/JpWYCOw==}
+
+ '@vercel/hydrogen@1.2.2':
+ resolution: {integrity: sha512-PRA3r1/ZRcklGgs/hczprQZ27jX9Avyq/iEbtmzAFNbFovkTlkE0Wy93pVKJfJ4ISCBzBgUSMktX9+6wgjs32A==}
+
+ '@vercel/next@4.9.2':
+ resolution: {integrity: sha512-1vnmJg5c6AsJEMYZTHOXSXG232LO2YHNLGhfAPLaEd7nrAf8bnC9TSbYoGkksTw719w1O5WYhMXay3QHnJoGjw==}
+
+ '@vercel/nft@0.29.2':
+ resolution: {integrity: sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ '@vercel/node@5.3.0':
+ resolution: {integrity: sha512-NeE5c7dRt9PXUzq7zUA+rj94l7AoXBw2cE+xK0hIoYDcWbIJVYBhbkBtzNdZx8CGncUJ2wMq01gn8pCwoQ0xYA==}
+
+ '@vercel/python@4.7.2':
+ resolution: {integrity: sha512-i2QBNMvNxUZQ2e5vLIL7mUkLg5Qkl9nqxUNXCYezdyvk2Ql6xYKjg7tMhpK/uiy094KfZSOECpDbDxkIN0jUSw==}
+
+ '@vercel/redwood@2.3.3':
+ resolution: {integrity: sha512-9Dfith+CYNNt/5Mkrklu7xWroWgSJVR4uh7mwu/2IvuCiJMNa24ReR9xtQNyGFAwAjdeweQ/nHfImz+12ORfpQ==}
+
+ '@vercel/remix-builder@5.4.9':
+ resolution: {integrity: sha512-+fWdMjVI6bO0GUBJbw2seBDnLvPi2dd9aBQHVG2TCbJobBPfXgyEMgRWDS+4gjhXn4jLatX4B5C5iJykkeMqNQ==}
+
+ '@vercel/ruby@2.2.0':
+ resolution: {integrity: sha512-FJF9gKVNHAljGOgV6zS5ou2N7ZgjOqMMtcPA5lsJEUI5/AZzVDWCmtcowTP80wEtHuupkd7d7M399FA082kXYQ==}
+
+ '@vercel/static-build@2.7.10':
+ resolution: {integrity: sha512-qH5WrNXDVMn6RtdzCzLK5Eqeq9ABkL+FsJTYyeS35Y4Sd9FYR6QsCSANm1Go0MMv3RLa5j1Jtje/9N7QaU4TKg==}
+
+ '@vercel/static-config@3.1.1':
+ resolution: {integrity: sha512-IRtKnm9N1Uqd2ayIbLPjRtdwcl1GTWvqF1PuEVNm9O43kmoI+m9VpGlW8oga+5LQq1LmJ2Y67zHr7NbjrH1rrw==}
+
+ '@vidstack/react@1.12.13':
+ resolution: {integrity: sha512-zyNydy1+HtoK6cJ8EmqFNkPPGHIFMrr2KH+ef3654EqXx4IcJ8A5LCNMXBuALQE8IMxtk040JMoR9OKyeXjBOQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/react': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+
+ '@webassemblyjs/ast@1.14.1':
+ resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
+
+ '@webassemblyjs/floating-point-hex-parser@1.13.2':
+ resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
+
+ '@webassemblyjs/helper-api-error@1.13.2':
+ resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
+
+ '@webassemblyjs/helper-buffer@1.14.1':
+ resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
+
+ '@webassemblyjs/helper-numbers@1.13.2':
+ resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
+
+ '@webassemblyjs/helper-wasm-bytecode@1.13.2':
+ resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
+
+ '@webassemblyjs/helper-wasm-section@1.14.1':
+ resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
+
+ '@webassemblyjs/ieee754@1.13.2':
+ resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==}
+
+ '@webassemblyjs/leb128@1.13.2':
+ resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==}
+
+ '@webassemblyjs/utf8@1.13.2':
+ resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==}
+
+ '@webassemblyjs/wasm-edit@1.14.1':
+ resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==}
+
+ '@webassemblyjs/wasm-gen@1.14.1':
+ resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==}
+
+ '@webassemblyjs/wasm-opt@1.14.1':
+ resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==}
+
+ '@webassemblyjs/wasm-parser@1.14.1':
+ resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==}
+
+ '@webassemblyjs/wast-printer@1.14.1':
+ resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
+
+ '@xtuc/ieee754@1.2.0':
+ resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
+
+ '@xtuc/long@4.2.2':
+ resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
+
+ JSONStream@1.3.5:
+ resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
+ hasBin: true
+
+ abab@2.0.6:
+ resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
+ deprecated: Use your platform's native atob() and btoa() methods instead
+
+ abbrev@3.0.1:
+ resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+
+ acorn-globals@6.0.0:
+ resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
+
+ acorn-import-attributes@1.9.5:
+ resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
+ peerDependencies:
+ acorn: ^8
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn-walk@7.2.0:
+ resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
+ engines: {node: '>=0.4.0'}
+
+ acorn-walk@8.3.2:
+ resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
+ engines: {node: '>=0.4.0'}
+
+ acorn-walk@8.3.4:
+ resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@7.4.1:
+ resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ acorn@8.14.0:
+ resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ agent-base@7.1.3:
+ resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
+ engines: {node: '>= 14'}
+
+ aggregate-error@3.1.0:
+ resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+ engines: {node: '>=8'}
+
+ ajv-formats@2.1.1:
+ resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv-keywords@3.5.2:
+ resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
+ peerDependencies:
+ ajv: ^6.9.1
+
+ ajv-keywords@5.1.0:
+ resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
+ peerDependencies:
+ ajv: ^8.8.2
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ajv@8.17.1:
+ resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+
+ ajv@8.6.3:
+ resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==}
+
+ ansi-escapes@4.3.2:
+ resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.1.0:
+ resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ ansi-styles@6.2.1:
+ resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ engines: {node: '>=12'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@4.1.0:
+ resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==}
+
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-ify@1.0.0:
+ resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array-union@1.0.2:
+ resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==}
+ engines: {node: '>=0.10.0'}
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ array-uniq@1.0.3:
+ resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==}
+ engines: {node: '>=0.10.0'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.6:
+ resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ arrify@1.0.1:
+ resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==}
+ engines: {node: '>=0.10.0'}
+
+ artplayer@5.2.3:
+ resolution: {integrity: sha512-WaOZQrpZn/L+GgI2f0TEsoAL3Wb+v16Mu0JmWh7qKFYuvr11WNt3dWhWeIaCfoHy3NtkCWM9jTP+xwwsxdElZQ==}
+
+ as-table@1.0.55:
+ resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ ast-types@0.14.2:
+ resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==}
+ engines: {node: '>=4'}
+
+ astral-regex@2.0.0:
+ resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
+ engines: {node: '>=8'}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ async-listen@1.2.0:
+ resolution: {integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==}
+
+ async-listen@3.0.0:
+ resolution: {integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==}
+ engines: {node: '>= 14'}
+
+ async-listen@3.0.1:
+ resolution: {integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==}
+ engines: {node: '>= 14'}
+
+ async-retry@1.3.3:
+ resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
+
+ async-sema@3.1.1:
+ resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==}
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ at-least-node@1.0.0:
+ resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
+ engines: {node: '>= 4.0.0'}
+
+ autoprefixer@10.4.21:
+ resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axe-core@4.10.3:
+ resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
+ engines: {node: '>=4'}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ babel-jest@27.5.1:
+ resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ peerDependencies:
+ '@babel/core': ^7.8.0
+
+ babel-loader@8.4.1:
+ resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==}
+ engines: {node: '>= 8.9'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ webpack: '>=2'
+
+ babel-plugin-istanbul@6.1.1:
+ resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
+ engines: {node: '>=8'}
+
+ babel-plugin-jest-hoist@27.5.1:
+ resolution: {integrity: sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ babel-plugin-polyfill-corejs2@0.4.13:
+ resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-corejs3@0.11.1:
+ resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-regenerator@0.6.4:
+ resolution: {integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-preset-current-node-syntax@1.1.0:
+ resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ babel-preset-jest@27.5.1:
+ resolution: {integrity: sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ big.js@5.2.2:
+ resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
+
+ 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==}
+
+ blake3-wasm@2.1.5:
+ resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browser-process-hrtime@1.0.0:
+ resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
+
+ browserslist@4.25.0:
+ resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ bser@2.1.1:
+ resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
+
+ buffer-crc32@0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ builtin-modules@3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+
+ busboy@1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+
+ bytes@3.1.0:
+ resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
+ camelcase-keys@6.2.2:
+ resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
+ engines: {node: '>=8'}
+
+ camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
+ caniuse-lite@1.0.30001723:
+ resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
+
+ chalk@3.0.0:
+ resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
+ engines: {node: '>=8'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chalk@5.4.1:
+ resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ char-regex@1.0.2:
+ resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+ engines: {node: '>=10'}
+
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ chokidar@4.0.0:
+ resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==}
+ engines: {node: '>= 14.16.0'}
+
+ chownr@2.0.0:
+ resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
+ engines: {node: '>=10'}
+
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ chrome-trace-event@1.0.4:
+ resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
+ engines: {node: '>=6.0'}
+
+ ci-info@3.9.0:
+ resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
+ engines: {node: '>=8'}
+
+ ci-info@4.2.0:
+ resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==}
+ engines: {node: '>=8'}
+
+ cjs-module-lexer@1.2.3:
+ resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==}
+
+ cjs-module-lexer@1.4.3:
+ resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+ clean-stack@2.2.0:
+ resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+ engines: {node: '>=6'}
+
+ clean-webpack-plugin@4.0.0:
+ resolution: {integrity: sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ webpack: '>=4.0.0 <6.0.0'
+
+ cli-cursor@3.1.0:
+ resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
+ engines: {node: '>=8'}
+
+ cli-truncate@2.1.0:
+ resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==}
+ engines: {node: '>=8'}
+
+ cli-truncate@3.1.0:
+ resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ cliui@7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ cluster-key-slot@1.1.2:
+ resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
+ engines: {node: '>=0.10.0'}
+
+ co@4.6.0:
+ resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
+ engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+
+ code-block-writer@10.1.1:
+ resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==}
+
+ collect-v8-coverage@1.0.2:
+ resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ color-string@1.9.1:
+ resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
+
+ color@4.2.3:
+ resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
+ engines: {node: '>=12.5.0'}
+
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@11.1.0:
+ resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
+ engines: {node: '>=16'}
+
+ commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ commander@7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
+ commander@9.5.0:
+ resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
+ engines: {node: ^12.20.0 || >=14}
+
+ common-tags@1.8.2:
+ resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
+ engines: {node: '>=4.0.0'}
+
+ commondir@1.0.1:
+ resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
+
+ compare-func@2.0.0:
+ resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ content-type@1.0.4:
+ resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
+ engines: {node: '>= 0.6'}
+
+ conventional-changelog-angular@5.0.13:
+ resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==}
+ engines: {node: '>=10'}
+
+ conventional-changelog-conventionalcommits@4.6.3:
+ resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==}
+ engines: {node: '>=10'}
+
+ conventional-commits-parser@3.2.4:
+ resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ convert-hrtime@3.0.0:
+ resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==}
+ engines: {node: '>=8'}
+
+ convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie@0.5.0:
+ resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+ engines: {node: '>= 0.6'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ core-js-compat@3.43.0:
+ resolution: {integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==}
+
+ cosmiconfig-typescript-loader@2.0.2:
+ resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==}
+ engines: {node: '>=12', npm: '>=6'}
+ peerDependencies:
+ '@types/node': '*'
+ cosmiconfig: '>=7'
+ typescript: '>=3'
+
+ cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+
+ cosmiconfig@8.3.6:
+ resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ crypto-random-string@2.0.0:
+ resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
+ engines: {node: '>=8'}
+
+ css-select@5.1.0:
+ resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+
+ css-tree@2.2.1:
+ resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+ css-tree@2.3.1:
+ resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+ css-what@6.1.0:
+ resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+ engines: {node: '>= 6'}
+
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csso@5.0.5:
+ resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+ cssom@0.3.8:
+ resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
+
+ cssom@0.4.4:
+ resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==}
+
+ cssstyle@2.3.0:
+ resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==}
+ engines: {node: '>=8'}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ dargs@7.0.0:
+ resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==}
+ engines: {node: '>=8'}
+
+ data-uri-to-buffer@2.0.2:
+ resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
+
+ data-urls@2.0.0:
+ resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==}
+ engines: {node: '>=10'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.1:
+ resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decamelize-keys@1.1.1:
+ resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
+ engines: {node: '>=0.10.0'}
+
+ decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+
+ decimal.js@10.5.0:
+ resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
+
+ dedent@0.7.0:
+ resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+ del@4.1.1:
+ resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==}
+ engines: {node: '>=6'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ depd@1.1.2:
+ resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
+ engines: {node: '>= 0.6'}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.0.4:
+ resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
+ engines: {node: '>=8'}
+
+ detect-newline@3.1.0:
+ resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
+ engines: {node: '>=8'}
+
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ diff-sequences@27.5.1:
+ resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ diff@4.0.2:
+ resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+ engines: {node: '>=0.3.1'}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ doctrine@3.0.0:
+ resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+ engines: {node: '>=6.0.0'}
+
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domexception@2.0.1:
+ resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==}
+ engines: {node: '>=8'}
+ deprecated: Use your platform's native DOMException instead
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
+ dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
+ dot-prop@5.3.0:
+ resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
+ engines: {node: '>=8'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ edge-runtime@2.5.9:
+ resolution: {integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ ejs@3.1.10:
+ resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+
+ electron-to-chromium@1.5.168:
+ resolution: {integrity: sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==}
+
+ emittery@0.8.1:
+ resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==}
+ engines: {node: '>=10'}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ emojis-list@3.0.0:
+ resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
+ engines: {node: '>= 4'}
+
+ end-of-stream@1.1.0:
+ resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==}
+
+ enhanced-resolve@5.18.2:
+ resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
+ engines: {node: '>=10.13.0'}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ error-ex@1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+
+ es-abstract@1.24.0:
+ resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.2.1:
+ resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@1.4.1:
+ resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ esbuild-android-64@0.14.47:
+ resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ esbuild-android-64@0.15.18:
+ resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ esbuild-android-arm64@0.14.47:
+ resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ esbuild-android-arm64@0.15.18:
+ resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ esbuild-darwin-64@0.14.47:
+ resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ esbuild-darwin-64@0.15.18:
+ resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ esbuild-darwin-arm64@0.14.47:
+ resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ esbuild-darwin-arm64@0.15.18:
+ resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ esbuild-freebsd-64@0.14.47:
+ resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ esbuild-freebsd-64@0.15.18:
+ resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ esbuild-freebsd-arm64@0.14.47:
+ resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-freebsd-arm64@0.15.18:
+ resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-linux-32@0.14.47:
+ resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-32@0.15.18:
+ resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-64@0.14.47:
+ resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-64@0.15.18:
+ resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.14.47:
+ resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.15.18:
+ resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm@0.14.47:
+ resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-arm@0.15.18:
+ resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.14.47:
+ resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.15.18:
+ resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.14.47:
+ resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.15.18:
+ resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.14.47:
+ resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.15.18:
+ resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-s390x@0.14.47:
+ resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-linux-s390x@0.15.18:
+ resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-netbsd-64@0.14.47:
+ resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-netbsd-64@0.15.18:
+ resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-openbsd-64@0.14.47:
+ resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ esbuild-openbsd-64@0.15.18:
+ resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ esbuild-sunos-64@0.14.47:
+ resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-sunos-64@0.15.18:
+ resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-windows-32@0.14.47:
+ resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-32@0.15.18:
+ resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-64@0.14.47:
+ resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-64@0.15.18:
+ resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.14.47:
+ resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.15.18:
+ resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild@0.14.47:
+ resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.15.18:
+ resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.25.4:
+ resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@2.0.0:
+ resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
+ engines: {node: '>=8'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ escodegen@2.1.0:
+ resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
+ engines: {node: '>=6.0'}
+ hasBin: true
+
+ eslint-config-next@14.2.30:
+ resolution: {integrity: sha512-4pTMb3wfpI+piVeEz3TWG1spjuXJJBZaYabi2H08z2ZTk6/N304POEovHdFmK6EZb4QlKpETulBNaRIITA0+xg==}
+ peerDependencies:
+ eslint: ^7.23.0 || ^8.0.0
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ eslint-config-prettier@8.10.0:
+ resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+
+ eslint-import-resolver-typescript@3.10.1:
+ resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ eslint-plugin-import-x: '*'
+ peerDependenciesMeta:
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
+
+ eslint-module-utils@2.12.0:
+ resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
+ eslint-plugin-import@2.31.0:
+ resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+
+ eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705:
+ resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-plugin-simple-import-sort@7.0.0:
+ resolution: {integrity: sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw==}
+ peerDependencies:
+ eslint: '>=5.0.0'
+
+ eslint-plugin-unused-imports@2.0.0:
+ resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ '@typescript-eslint/eslint-plugin': ^5.0.0
+ eslint: ^8.0.0
+ peerDependenciesMeta:
+ '@typescript-eslint/eslint-plugin':
+ optional: true
+
+ eslint-rule-composer@0.3.0:
+ resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==}
+ engines: {node: '>=4.0.0'}
+
+ eslint-scope@5.1.1:
+ resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+ engines: {node: '>=8.0.0'}
+
+ eslint-scope@7.2.2:
+ resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint@8.57.1:
+ resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
+ hasBin: true
+
+ espree@9.6.1:
+ resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+ 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.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@4.3.0:
+ resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ estree-walker@1.0.1:
+ resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ events-intercept@2.0.0:
+ resolution: {integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==}
+
+ events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
+ exit-hook@2.2.1:
+ resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
+ engines: {node: '>=6'}
+
+ exit@0.1.2:
+ resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
+ engines: {node: '>= 0.8.0'}
+
+ expect@27.5.1:
+ resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ expect@30.0.0:
+ resolution: {integrity: sha512-xCdPp6gwiR9q9lsPCHANarIkFTN/IMZso6Kkq03sOm9IIGtzK/UJqml0dkhHibGh8HKOj8BIDIpZ0BZuU7QK6w==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ exsolve@1.0.7:
+ resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fast-uri@3.0.6:
+ resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
+
+ fastq@1.19.1:
+ resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+ fb-watchman@2.0.2:
+ resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+
+ fd-slicer@1.1.0:
+ resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+
+ fdir@6.4.6:
+ resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@6.0.1:
+ resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+
+ file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
+ filelist@1.0.4:
+ resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-cache-dir@3.3.2:
+ resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
+ engines: {node: '>=8'}
+
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@3.2.0:
+ resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ form-data@3.0.3:
+ resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==}
+ engines: {node: '>= 6'}
+
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+ framer-motion@12.18.1:
+ resolution: {integrity: sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
+ fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+
+ fs-extra@11.1.0:
+ resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==}
+ engines: {node: '>=14.14'}
+
+ fs-extra@9.1.0:
+ resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
+ engines: {node: '>=10'}
+
+ fs-minipass@2.1.0:
+ resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
+ engines: {node: '>= 8'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generic-pool@3.4.2:
+ resolution: {integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==}
+ engines: {node: '>= 4'}
+
+ generic-pool@3.9.0:
+ resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
+ engines: {node: '>= 4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-own-enumerable-property-symbols@3.0.2:
+ resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
+
+ get-package-type@0.1.0:
+ resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
+ engines: {node: '>=8.0.0'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-source@2.0.12:
+ resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.10.1:
+ resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
+
+ git-raw-commits@2.0.11:
+ resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob-to-regexp@0.4.1:
+ resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
+
+ glob@10.3.10:
+ resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+
+ global-dirs@0.1.1:
+ resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
+ engines: {node: '>=4'}
+
+ globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+
+ globals@13.24.0:
+ resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+ engines: {node: '>=8'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ globby@6.1.0:
+ resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==}
+ engines: {node: '>=0.10.0'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+ hard-rejection@2.1.0:
+ resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
+ engines: {node: '>=6'}
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hls.js@1.6.6:
+ resolution: {integrity: sha512-S4uTCwTHOtImW+/jxMjzG7udbHy5z682YQRbm/4f7VXuVNEoGBRjPJnD3Fxrufomdhzdtv24KnxRhPMXSvL6Fw==}
+
+ hosted-git-info@2.8.9:
+ resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
+
+ hosted-git-info@4.1.0:
+ resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
+ engines: {node: '>=10'}
+
+ html-encoding-sniffer@2.0.1:
+ resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==}
+ engines: {node: '>=10'}
+
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+ http-errors@1.4.0:
+ resolution: {integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==}
+ engines: {node: '>= 0.6'}
+
+ http-errors@1.7.3:
+ resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==}
+ engines: {node: '>= 0.6'}
+
+ http-proxy-agent@4.0.1:
+ resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
+ husky@7.0.4:
+ resolution: {integrity: sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
+ idb@7.1.1:
+ resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ import-local@3.2.0:
+ resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ 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.1:
+ resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-arrayish@0.3.2:
+ resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-buffer@2.0.5:
+ resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
+ engines: {node: '>=4'}
+
+ is-bun-module@2.0.0:
+ resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-fullwidth-code-point@4.0.0:
+ resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
+ engines: {node: '>=12'}
+
+ is-generator-fn@2.1.0:
+ resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
+ engines: {node: '>=6'}
+
+ is-generator-function@1.1.0:
+ resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-module@1.0.0:
+ resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-node-process@1.2.0:
+ resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-obj@1.0.1:
+ resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
+ engines: {node: '>=0.10.0'}
+
+ is-obj@2.0.0:
+ resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
+ engines: {node: '>=8'}
+
+ is-path-cwd@2.2.0:
+ resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
+ engines: {node: '>=6'}
+
+ is-path-in-cwd@2.1.0:
+ resolution: {integrity: sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==}
+ engines: {node: '>=6'}
+
+ is-path-inside@2.1.0:
+ resolution: {integrity: sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==}
+ engines: {node: '>=6'}
+
+ is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+
+ is-plain-obj@1.1.0:
+ resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
+ engines: {node: '>=0.10.0'}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-regexp@1.0.0:
+ resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
+ engines: {node: '>=0.10.0'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-text-path@1.0.1:
+ resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==}
+ engines: {node: '>=0.10.0'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-typedarray@1.0.0:
+ resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@0.0.1:
+ resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-instrument@5.2.1:
+ resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@4.0.1:
+ resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.1.7:
+ resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
+ engines: {node: '>=8'}
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jackspeak@2.3.6:
+ resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
+ engines: {node: '>=14'}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jake@10.9.2:
+ resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ jest-changed-files@27.5.1:
+ resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-circus@27.5.1:
+ resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-cli@27.5.1:
+ resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest-config@27.5.1:
+ resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ peerDependencies:
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ ts-node:
+ optional: true
+
+ jest-diff@27.5.1:
+ resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-diff@30.0.0:
+ resolution: {integrity: sha512-TgT1+KipV8JTLXXeFX0qSvIJR/UXiNNojjxb/awh3vYlBZyChU/NEmyKmq+wijKjWEztyrGJFL790nqMqNjTHA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-docblock@27.5.1:
+ resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-each@27.5.1:
+ resolution: {integrity: sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-environment-jsdom@27.5.1:
+ resolution: {integrity: sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-environment-node@27.5.1:
+ resolution: {integrity: sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-get-type@27.5.1:
+ resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-haste-map@27.5.1:
+ resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-jasmine2@27.5.1:
+ resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-leak-detector@27.5.1:
+ resolution: {integrity: sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-matcher-utils@27.5.1:
+ resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-matcher-utils@30.0.0:
+ resolution: {integrity: sha512-m5mrunqopkrqwG1mMdJxe1J4uGmS9AHHKYUmoxeQOxBcLjEvirIrIDwuKmUYrecPHVB/PUBpXs2gPoeA2FSSLQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-message-util@27.5.1:
+ resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-message-util@30.0.0:
+ resolution: {integrity: sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-mock@27.5.1:
+ resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-mock@30.0.0:
+ resolution: {integrity: sha512-W2sRA4ALXILrEetEOh2ooZG6fZ01iwVs0OWMKSSWRcUlaLr4ESHuiKXDNTg+ZVgOq8Ei5445i/Yxrv59VT+XkA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-pnp-resolver@1.2.3:
+ resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
+ engines: {node: '>=6'}
+ peerDependencies:
+ jest-resolve: '*'
+ peerDependenciesMeta:
+ jest-resolve:
+ optional: true
+
+ jest-regex-util@27.5.1:
+ resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-regex-util@30.0.0:
+ resolution: {integrity: sha512-rT84010qRu/5OOU7a9TeidC2Tp3Qgt9Sty4pOZ/VSDuEmRupIjKZAb53gU3jr4ooMlhwScrgC9UixJxWzVu9oQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-resolve-dependencies@27.5.1:
+ resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-resolve@27.5.1:
+ resolution: {integrity: sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-runner@27.5.1:
+ resolution: {integrity: sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-runtime@27.5.1:
+ resolution: {integrity: sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-serializer@27.5.1:
+ resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-snapshot@27.5.1:
+ resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-util@27.5.1:
+ resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-util@30.0.0:
+ resolution: {integrity: sha512-fhNBBM9uSUbd4Lzsf8l/kcAdaHD/4SgoI48en3HXcBEMwKwoleKFMZ6cYEYs21SB779PRuRCyNLmymApAm8tZw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-validate@27.5.1:
+ resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-watcher@27.5.1:
+ resolution: {integrity: sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ jest-worker@26.6.2:
+ resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
+ engines: {node: '>= 10.13.0'}
+
+ jest-worker@27.5.1:
+ resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
+ engines: {node: '>= 10.13.0'}
+
+ jest@27.5.1:
+ resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
+ jose@5.9.6:
+ resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@3.14.1:
+ resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
+ hasBin: true
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsdom@16.7.0:
+ resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ canvas: ^2.5.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsesc@3.0.2:
+ resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json-schema-to-ts@1.6.4:
+ resolution: {integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.1.0:
+ resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+
+ jsonparse@1.3.1:
+ resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
+ engines: {'0': node >= 0.2.0}
+
+ jsonpointer@5.0.1:
+ resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+ engines: {node: '>=0.10.0'}
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
+ leven@3.1.0:
+ resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+ engines: {node: '>=6'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lilconfig@2.0.5:
+ resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==}
+ engines: {node: '>=10'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ lint-staged@12.5.0:
+ resolution: {integrity: sha512-BKLUjWDsKquV/JuIcoQW4MSAI3ggwEImF1+sB4zaKvyVx1wBk3FsG7UK9bpnmBTN1pm7EH2BBcMwINJzCRv12g==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
+ listr2@4.0.5:
+ resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ enquirer: '>= 2.3.0 < 3'
+ peerDependenciesMeta:
+ enquirer:
+ optional: true
+
+ loader-runner@4.3.0:
+ resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
+ engines: {node: '>=6.11.5'}
+
+ loader-utils@2.0.4:
+ resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
+ engines: {node: '>=8.9.0'}
+
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.debounce@4.0.8:
+ resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash.sortby@4.7.0:
+ resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ log-update@4.0.0:
+ resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
+ engines: {node: '>=10'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+
+ lucide-react@0.438.0:
+ resolution: {integrity: sha512-uq6yCB+IzVfgIPMK8ibkecXSWTTSOMs9UjUgZigfrDCVqgdwkpIgYg1fSYnf0XXF2AoSyCJZhoZXQwzoai7VGw==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
+
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
+ magic-string@0.25.9:
+ resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+
+ make-dir@3.1.0:
+ resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
+ engines: {node: '>=8'}
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ makeerror@1.0.12:
+ resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+
+ map-obj@1.0.1:
+ resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
+ engines: {node: '>=0.10.0'}
+
+ map-obj@4.3.0:
+ resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
+ engines: {node: '>=8'}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ maverick.js@0.37.0:
+ resolution: {integrity: sha512-1Dk/9rienLiihlktVvH04ADC2UJTMflC1fOMVQCCaQAaz7hgzDI5i0p/arFbDM52hFFiIcq4RdXtYz47SgsLgw==}
+ engines: {node: '>=16'}
+
+ mdn-data@2.0.28:
+ resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
+
+ mdn-data@2.0.30:
+ resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
+
+ media-captions@0.0.18:
+ resolution: {integrity: sha512-JW18P6FuHdyLSGwC4TQ0kF3WdNj/+wMw2cKOb8BnmY6vSJGtnwJ+vkYj+IjHOV34j3XMc70HDeB/QYKR7E7fuQ==}
+ engines: {node: '>=16'}
+
+ media-captions@1.0.4:
+ resolution: {integrity: sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==}
+ engines: {node: '>=16'}
+
+ media-icons@1.1.5:
+ resolution: {integrity: sha512-zu3CjKRs63ybLLWPomRRgTDyYiSrk2bRRgw97ZmN3FGXuo9qUUhBSfYwnjmvSdLG2JOJfAwzDz99bPATSY81DQ==}
+ engines: {node: '>=16'}
+
+ meow@8.1.2:
+ resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
+ engines: {node: '>=10'}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micro@9.3.5-canary.3:
+ resolution: {integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==}
+ engines: {node: '>= 8.0.0'}
+ hasBin: true
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime@3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
+ mini-svg-data-uri@1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+
+ miniflare@3.20250408.2:
+ resolution: {integrity: sha512-uTs7cGWFErgJTKtBdmtctwhuoxniuCQqDT8+xaEiJdEC8d+HsaZVYfZwIX2NuSmdAiHMe7NtbdZYjFMbIXtJsQ==}
+ engines: {node: '>=16.13'}
+ hasBin: true
+
+ miniflare@4.20250617.4:
+ resolution: {integrity: sha512-IAoApFKxOJlaaFkym5ETstVX3qWzVt3xyqCDj6vSSTgEH3zxZJ5417jZGg8iQfMHosKCcQH1doPPqqnOZm/yrw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist-options@4.1.0:
+ resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
+ engines: {node: '>= 6'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@3.3.6:
+ resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
+ engines: {node: '>=8'}
+
+ minipass@5.0.0:
+ resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
+ engines: {node: '>=8'}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@2.1.2:
+ resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
+ engines: {node: '>= 8'}
+
+ minizlib@3.0.2:
+ resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
+ engines: {node: '>= 18'}
+
+ mkdirp@1.0.4:
+ resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ mkdirp@3.0.1:
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ motion-dom@12.18.1:
+ resolution: {integrity: sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==}
+
+ motion-utils@12.18.1:
+ resolution: {integrity: sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==}
+
+ mri@1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+
+ ms@2.1.1:
+ resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==}
+
+ ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mustache@4.2.0:
+ resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
+ hasBin: true
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ napi-postinstall@0.2.4:
+ resolution: {integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare-lite@1.4.0:
+ resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ neo-async@2.6.2:
+ resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+ next-pwa@5.6.0:
+ resolution: {integrity: sha512-XV8g8C6B7UmViXU8askMEYhWwQ4qc/XqJGnexbLV68hzKaGHZDMtHsm2TNxFcbR7+ypVuth/wwpiIlMwpRJJ5A==}
+ peerDependencies:
+ next: '>=9.0.0'
+
+ next-router-mock@0.9.13:
+ resolution: {integrity: sha512-906n2RRaE6Y28PfYJbaz5XZeJ6Tw8Xz1S6E31GGwZ0sXB6/XjldD1/2azn1ZmBmRk5PQRkzjg+n+RHZe5xQzWA==}
+ peerDependencies:
+ next: '>=10.0.0'
+ react: '>=17.0.0'
+
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
+ next@14.2.30:
+ resolution: {integrity: sha512-+COdu6HQrHHFQ1S/8BBsCag61jZacmvbuL2avHvQFbWa2Ox7bE+d8FyNgxRLjXQ5wtPyQwEmk85js/AuaG2Sbg==}
+ engines: {node: '>=18.17.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.41.2
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ sass:
+ optional: true
+
+ no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
+ node-fetch@2.6.7:
+ resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-fetch@2.6.9:
+ resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-gyp-build@4.8.4:
+ resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
+ hasBin: true
+
+ node-int64@0.4.0:
+ resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
+
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ nopt@8.1.0:
+ resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+ hasBin: true
+
+ normalize-package-data@2.5.0:
+ resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
+
+ normalize-package-data@3.0.3:
+ resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==}
+ engines: {node: '>=10'}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+ nwsapi@2.2.20:
+ resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.groupby@1.0.3:
+ resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
+ once@1.3.3:
+ resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ option-validator@2.0.6:
+ resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ os-paths@4.4.0:
+ resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==}
+ engines: {node: '>= 6.0'}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ p-map@2.1.0:
+ resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
+ engines: {node: '>=6'}
+
+ p-map@4.0.0:
+ resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+ engines: {node: '>=10'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ package-manager-manager@0.2.0:
+ resolution: {integrity: sha512-V02gl0bafXJ2gcY6j+5IHM7UdnYwmF+2OsFZuqVcha6iMSStD4dpIOBOsypnUIwOi4jLcPz6RQuyifmAE3mG8g==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parse-ms@2.1.0:
+ resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==}
+ engines: {node: '>=6'}
+
+ parse5@6.0.1:
+ resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
+
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-is-inside@1.0.2:
+ resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-match@1.2.4:
+ resolution: {integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==}
+ deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-to-regexp@1.9.0:
+ resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==}
+
+ path-to-regexp@6.1.0:
+ resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==}
+
+ path-to-regexp@6.3.0:
+ resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pcre-to-regexp@1.1.0:
+ resolution: {integrity: sha512-KF9XxmUQJ2DIlMj3TqNqY1AWvyvTuIuq11CuuekxyaYMiFuMKGgQrePYMX5bXKLhLG3sDI4CsGAYHPaT7VV7+g==}
+
+ pend@1.2.0:
+ resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
+
+ picocolors@1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.2:
+ resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+ engines: {node: '>=12'}
+
+ pidtree@0.5.0:
+ resolution: {integrity: sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
+ pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+
+ pify@4.0.1:
+ resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+ engines: {node: '>=6'}
+
+ pinkie-promise@2.0.1:
+ resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==}
+ engines: {node: '>=0.10.0'}
+
+ pinkie@2.0.4:
+ resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==}
+ engines: {node: '>=0.10.0'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-dir@4.2.0:
+ resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+ engines: {node: '>=8'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.0.1:
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@4.0.2:
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-plugin-tailwindcss@0.5.14:
+ resolution: {integrity: sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==}
+ engines: {node: '>=14.21.3'}
+ peerDependencies:
+ '@ianvs/prettier-plugin-sort-imports': '*'
+ '@prettier/plugin-pug': '*'
+ '@shopify/prettier-plugin-liquid': '*'
+ '@trivago/prettier-plugin-sort-imports': '*'
+ '@zackad/prettier-plugin-twig-melody': '*'
+ prettier: ^3.0
+ prettier-plugin-astro: '*'
+ prettier-plugin-css-order: '*'
+ prettier-plugin-import-sort: '*'
+ prettier-plugin-jsdoc: '*'
+ prettier-plugin-marko: '*'
+ prettier-plugin-organize-attributes: '*'
+ prettier-plugin-organize-imports: '*'
+ prettier-plugin-sort-imports: '*'
+ prettier-plugin-style-order: '*'
+ prettier-plugin-svelte: '*'
+ peerDependenciesMeta:
+ '@ianvs/prettier-plugin-sort-imports':
+ optional: true
+ '@prettier/plugin-pug':
+ optional: true
+ '@shopify/prettier-plugin-liquid':
+ optional: true
+ '@trivago/prettier-plugin-sort-imports':
+ optional: true
+ '@zackad/prettier-plugin-twig-melody':
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-css-order:
+ optional: true
+ prettier-plugin-import-sort:
+ optional: true
+ prettier-plugin-jsdoc:
+ optional: true
+ prettier-plugin-marko:
+ optional: true
+ prettier-plugin-organize-attributes:
+ optional: true
+ prettier-plugin-organize-imports:
+ optional: true
+ prettier-plugin-sort-imports:
+ optional: true
+ prettier-plugin-style-order:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+
+ prettier@2.8.8:
+ resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+
+ pretty-bytes@5.6.0:
+ resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
+ engines: {node: '>=6'}
+
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ pretty-format@30.0.0:
+ resolution: {integrity: sha512-18NAOUr4ZOQiIR+BgI5NhQE7uREdx4ZyV0dyay5izh4yfQ+1T7BSvggxvRGoXocrRyevqW5OhScUjbi9GB8R8Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ pretty-ms@7.0.1:
+ resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==}
+ engines: {node: '>=10'}
+
+ printable-characters@1.0.42:
+ resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
+
+ promisepipe@3.0.0:
+ resolution: {integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ psl@1.15.0:
+ resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ q@1.5.1:
+ resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==}
+ engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
+ deprecated: |-
+ You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
+
+ (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
+
+ querystringify@2.2.0:
+ resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ quick-lru@4.0.1:
+ resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
+ engines: {node: '>=8'}
+
+ randombytes@2.1.0:
+ resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+
+ raw-body@2.4.1:
+ resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==}
+ engines: {node: '>= 0.8'}
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-icons@5.5.0:
+ resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
+ peerDependencies:
+ react: '*'
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+ read-pkg-up@7.0.1:
+ resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
+ engines: {node: '>=8'}
+
+ read-pkg@5.2.0:
+ resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
+ engines: {node: '>=8'}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
+ redis@4.7.1:
+ resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regenerate-unicode-properties@10.2.0:
+ resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==}
+ engines: {node: '>=4'}
+
+ regenerate@1.4.2:
+ resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ regexpu-core@6.2.0:
+ resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==}
+ engines: {node: '>=4'}
+
+ reghex@1.0.2:
+ resolution: {integrity: sha512-bYtyDmFGHxn1Y4gxIs12+AUQ1WRDNvaIhn6ZuKc5KUbSVcmm6U6vx/RA66s26xGhTWBErKKDKK7lorkvvIBB5g==}
+
+ regjsgen@0.8.0:
+ resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
+
+ regjsparser@0.12.0:
+ resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
+ hasBin: true
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ requires-port@1.0.0:
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+ resolve-cwd@3.0.0:
+ resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
+ engines: {node: '>=8'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-global@1.0.0:
+ resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve.exports@1.1.1:
+ resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==}
+ engines: {node: '>=10'}
+
+ resolve@1.22.10:
+ resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.5:
+ resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+ hasBin: true
+
+ restore-cursor@3.1.0:
+ resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
+ engines: {node: '>=8'}
+
+ retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
+ rimraf@2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rollup-plugin-terser@7.0.2:
+ resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
+ deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
+ peerDependencies:
+ rollup: ^2.0.0
+
+ rollup@2.79.2:
+ resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ saxes@5.0.1:
+ resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
+ engines: {node: '>=10'}
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ schema-utils@2.7.1:
+ resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==}
+ engines: {node: '>= 8.9.0'}
+
+ schema-utils@4.3.2:
+ resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==}
+ engines: {node: '>= 10.13.0'}
+
+ semver@5.7.2:
+ resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+ hasBin: true
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.3.7:
+ resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ semver@7.5.4:
+ resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ semver@7.7.2:
+ resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ serialize-javascript@4.0.0:
+ resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==}
+
+ serialize-javascript@6.0.2:
+ resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ setprototypeof@1.1.1:
+ resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==}
+
+ sharp@0.33.5:
+ resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ shellac@0.8.0:
+ resolution: {integrity: sha512-M3F2vzYIM7frKOs0+kgs/ITMlXhGpgtqs9HxDPciz3bckzAqqfd4LrBn+CCmSbICyJS+Jz5UDkmkR1jE+m+g+Q==}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.0.2:
+ resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==}
+ engines: {node: '>=14'}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ simple-swizzle@0.2.2:
+ resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ slice-ansi@3.0.0:
+ resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
+ engines: {node: '>=8'}
+
+ slice-ansi@4.0.0:
+ resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
+ engines: {node: '>=10'}
+
+ slice-ansi@5.0.0:
+ resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
+ engines: {node: '>=12'}
+
+ snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+
+ source-list-map@2.0.1:
+ resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.7.4:
+ resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
+ engines: {node: '>= 8'}
+
+ source-map@0.8.0-beta.0:
+ resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
+ engines: {node: '>= 8'}
+
+ sourcemap-codec@1.4.8:
+ resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+ deprecated: Please use @jridgewell/sourcemap-codec instead
+
+ spdx-correct@3.2.0:
+ resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
+
+ spdx-exceptions@2.5.0:
+ resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
+
+ spdx-expression-parse@3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+
+ spdx-license-ids@3.0.21:
+ resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==}
+
+ split2@3.2.2:
+ resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ stable-hash@0.0.5:
+ resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+
+ stack-utils@2.0.6:
+ resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
+ engines: {node: '>=10'}
+
+ stacktracey@2.1.8:
+ resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
+
+ stat-mode@0.3.0:
+ resolution: {integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==}
+
+ statuses@1.5.0:
+ resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
+ engines: {node: '>= 0.6'}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ stoppable@1.1.0:
+ resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
+ engines: {node: '>=4', npm: '>=6'}
+
+ stream-to-array@2.3.0:
+ resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==}
+
+ stream-to-promise@2.2.0:
+ resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==}
+
+ streamsearch@1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
+
+ string-length@4.0.2:
+ resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
+ engines: {node: '>=10'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ stringify-object@3.3.0:
+ resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
+ engines: {node: '>=4'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.0:
+ resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ engines: {node: '>=12'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ strip-bom@4.0.0:
+ resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+ engines: {node: '>=8'}
+
+ strip-comments@2.0.1:
+ resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
+ engines: {node: '>=10'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ styled-jsx@5.1.1:
+ resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-color@9.4.0:
+ resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==}
+ engines: {node: '>=12'}
+
+ supports-hyperlinks@2.3.0:
+ resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ svg-parser@2.0.4:
+ resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
+
+ svgo@3.3.2:
+ resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ sweetalert2@11.22.2:
+ resolution: {integrity: sha512-GFQGzw8ZXF23PO79WMAYXLl4zYmLiaKqYJwcp5eBF07wiI5BYPbZtKi2pcvVmfUQK+FqL1risJAMxugcPbGIyg==}
+
+ swiper@11.2.8:
+ resolution: {integrity: sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==}
+ engines: {node: '>= 4.7.0'}
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+
+ tailwind-merge@2.6.0:
+ resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
+
+ tailwindcss@3.4.17:
+ resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ tapable@2.2.2:
+ resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
+ engines: {node: '>=6'}
+
+ tar@6.2.1:
+ resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
+ engines: {node: '>=10'}
+
+ tar@7.4.3:
+ resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
+ engines: {node: '>=18'}
+
+ temp-dir@2.0.0:
+ resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
+ engines: {node: '>=8'}
+
+ tempy@0.6.0:
+ resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==}
+ engines: {node: '>=10'}
+
+ terminal-link@2.1.1:
+ resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
+ engines: {node: '>=8'}
+
+ terser-webpack-plugin@5.3.14:
+ resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
+ engines: {node: '>= 10.13.0'}
+ peerDependencies:
+ '@swc/core': '*'
+ esbuild: '*'
+ uglify-js: '*'
+ webpack: ^5.1.0
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ esbuild:
+ optional: true
+ uglify-js:
+ optional: true
+
+ terser@5.43.1:
+ resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+
+ text-extensions@1.9.0:
+ resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==}
+ engines: {node: '>=0.10'}
+
+ text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ throat@6.0.2:
+ resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==}
+
+ throttleit@2.1.0:
+ resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
+ engines: {node: '>=18'}
+
+ through2@4.0.2:
+ resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
+
+ through@2.3.8:
+ resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+
+ time-span@4.0.0:
+ resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==}
+ engines: {node: '>=10'}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.14:
+ resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
+ engines: {node: '>=12.0.0'}
+
+ tmpl@1.0.5:
+ resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.0:
+ resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==}
+ engines: {node: '>=0.6'}
+
+ tough-cookie@4.1.4:
+ resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
+ engines: {node: '>=6'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ tr46@1.0.1:
+ resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
+
+ tr46@2.1.0:
+ resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==}
+ engines: {node: '>=8'}
+
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
+ trim-newlines@3.0.1:
+ resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
+ engines: {node: '>=8'}
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ ts-morph@12.0.0:
+ resolution: {integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==}
+
+ ts-node@10.9.1:
+ resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
+ ts-toolbelt@6.15.5:
+ resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==}
+
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+
+ tslib@1.14.1:
+ resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsutils@3.21.0:
+ resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+ engines: {node: '>= 6'}
+ peerDependencies:
+ typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+
+ type-fest@0.16.0:
+ resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==}
+ engines: {node: '>=10'}
+
+ type-fest@0.18.1:
+ resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}
+ engines: {node: '>=10'}
+
+ type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+
+ type-fest@0.21.3:
+ resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
+ engines: {node: '>=10'}
+
+ type-fest@0.6.0:
+ resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}
+ engines: {node: '>=8'}
+
+ type-fest@0.8.1:
+ resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
+ engines: {node: '>=8'}
+
+ type-fest@3.13.1:
+ resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==}
+ engines: {node: '>=14.16'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typedarray-to-buffer@3.1.5:
+ resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
+
+ typescript@4.9.5:
+ resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+
+ ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+
+ uid-promise@1.0.0:
+ resolution: {integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==}
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ uncrypto@0.1.3:
+ resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+
+ undici-types@7.8.0:
+ resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
+
+ undici@5.28.4:
+ resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
+ engines: {node: '>=14.0'}
+
+ undici@5.29.0:
+ resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
+ engines: {node: '>=14.0'}
+
+ unenv@2.0.0-rc.17:
+ resolution: {integrity: sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==}
+
+ unicode-canonical-property-names-ecmascript@2.0.1:
+ resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
+ engines: {node: '>=4'}
+
+ unicode-match-property-ecmascript@2.0.0:
+ resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
+ engines: {node: '>=4'}
+
+ unicode-match-property-value-ecmascript@2.2.0:
+ resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==}
+ engines: {node: '>=4'}
+
+ unicode-property-aliases-ecmascript@2.1.0:
+ resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
+ engines: {node: '>=4'}
+
+ unique-string@2.0.0:
+ resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
+ engines: {node: '>=8'}
+
+ universalify@0.2.0:
+ resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+ engines: {node: '>= 4.0.0'}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ unrs-resolver@1.9.0:
+ resolution: {integrity: sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==}
+
+ upath@1.2.0:
+ resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
+ engines: {node: '>=4'}
+
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ url-parse@1.5.10:
+ resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+
+ use-sync-external-store@1.5.0:
+ resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+ v8-to-istanbul@8.1.1:
+ resolution: {integrity: sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==}
+ engines: {node: '>=10.12.0'}
+
+ validate-npm-package-license@3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+
+ vercel@44.2.7:
+ resolution: {integrity: sha512-Y4RvCHxYSL82K5qp7l8jmAkHohJO/dBBuXb736RhD+LK8hD8ivOs2fGb9cvuEikkv57CK7iG2RsBZw7kAh22eg==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
+ vidstack@0.6.15:
+ resolution: {integrity: sha512-pI2aixBuOpu/LSnRgNJ40tU/KFW+x1X+O2bW1hz946ZZShDM5oqRXF9pavDOuckHAHPgUN9HYUr9vUNTBUPF1Q==}
+ engines: {node: '>=16'}
+
+ w3c-hr-time@1.0.2:
+ resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
+ deprecated: Use your platform's native performance.now() and performance.timeOrigin.
+
+ w3c-xmlserializer@2.0.0:
+ resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==}
+ engines: {node: '>=10'}
+
+ walker@1.0.8:
+ resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+
+ watchpack@2.4.4:
+ resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
+ engines: {node: '>=10.13.0'}
+
+ web-vitals@0.2.4:
+ resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webidl-conversions@4.0.2:
+ resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+
+ webidl-conversions@5.0.0:
+ resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==}
+ engines: {node: '>=8'}
+
+ webidl-conversions@6.1.0:
+ resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==}
+ engines: {node: '>=10.4'}
+
+ webpack-sources@1.4.3:
+ resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==}
+
+ webpack-sources@3.3.3:
+ resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
+ engines: {node: '>=10.13.0'}
+
+ webpack@5.99.9:
+ resolution: {integrity: sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ peerDependencies:
+ webpack-cli: '*'
+ peerDependenciesMeta:
+ webpack-cli:
+ optional: true
+
+ whatwg-encoding@1.0.5:
+ resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
+
+ whatwg-mimetype@2.3.0:
+ resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ whatwg-url@7.1.0:
+ resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
+
+ whatwg-url@8.7.0:
+ resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==}
+ engines: {node: '>=10'}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.19:
+ resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ workbox-background-sync@6.6.0:
+ resolution: {integrity: sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==}
+
+ workbox-broadcast-update@6.6.0:
+ resolution: {integrity: sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==}
+
+ workbox-build@6.6.0:
+ resolution: {integrity: sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==}
+ engines: {node: '>=10.0.0'}
+
+ workbox-cacheable-response@6.6.0:
+ resolution: {integrity: sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==}
+ deprecated: workbox-background-sync@6.6.0
+
+ workbox-core@6.6.0:
+ resolution: {integrity: sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==}
+
+ workbox-expiration@6.6.0:
+ resolution: {integrity: sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==}
+
+ workbox-google-analytics@6.6.0:
+ resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==}
+ deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
+
+ workbox-navigation-preload@6.6.0:
+ resolution: {integrity: sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==}
+
+ workbox-precaching@6.6.0:
+ resolution: {integrity: sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==}
+
+ workbox-range-requests@6.6.0:
+ resolution: {integrity: sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==}
+
+ workbox-recipes@6.6.0:
+ resolution: {integrity: sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==}
+
+ workbox-routing@6.6.0:
+ resolution: {integrity: sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==}
+
+ workbox-strategies@6.6.0:
+ resolution: {integrity: sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==}
+
+ workbox-streams@6.6.0:
+ resolution: {integrity: sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==}
+
+ workbox-sw@6.6.0:
+ resolution: {integrity: sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==}
+
+ workbox-webpack-plugin@6.6.0:
+ resolution: {integrity: sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ webpack: ^4.4.0 || ^5.9.0
+
+ workbox-window@6.6.0:
+ resolution: {integrity: sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==}
+
+ workerd@1.20250408.0:
+ resolution: {integrity: sha512-bBUX+UsvpzAqiWFNeZrlZmDGddiGZdBBbftZJz2wE6iUg/cIAJeVQYTtS/3ahaicguoLBz4nJiDo8luqM9fx1A==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ workerd@1.20250617.0:
+ resolution: {integrity: sha512-Uv6p0PYUHp/W/aWfUPLkZVAoAjapisM27JJlwcX9wCPTfCfnuegGOxFMvvlYpmNaX4YCwEdLCwuNn3xkpSkuZw==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ wrangler@4.22.0:
+ resolution: {integrity: sha512-m8qVO3YxhUTII+4U889G/f5UuLSvMkUkCNatupV2f/SJ+iqaWtP1QbuQII8bs2J/O4rqxsz46Wu2S50u7tKB5Q==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^4.20250617.0
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+
+ wrap-ansi@6.2.0:
+ resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+ engines: {node: '>=8'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ write-file-atomic@3.0.3:
+ resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
+
+ ws@7.5.10:
+ resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+ engines: {node: '>=8.3.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ ws@8.18.0:
+ resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xdg-app-paths@5.1.0:
+ resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==}
+ engines: {node: '>=6'}
+
+ xdg-portable@7.3.0:
+ resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==}
+ engines: {node: '>= 6.0'}
+
+ xml-name-validator@3.0.0:
+ resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
+ yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+
+ yaml@2.8.0:
+ resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yargs-parser@20.2.9:
+ resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+ engines: {node: '>=10'}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@16.2.0:
+ resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ engines: {node: '>=10'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yauzl-clone@1.0.4:
+ resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==}
+ engines: {node: '>=6'}
+
+ yauzl-promise@2.1.3:
+ resolution: {integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==}
+ engines: {node: '>=6'}
+
+ yauzl@2.10.0:
+ resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
+
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ youch@3.3.4:
+ resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==}
+
+ zod@3.22.3:
+ resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
+
+ zod@3.25.67:
+ resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
+
+snapshots:
+
+ '@adobe/css-tools@4.4.3': {}
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)':
+ dependencies:
+ ajv: 8.17.1
+ json-schema: 0.4.0
+ jsonpointer: 5.0.1
+ leven: 3.1.0
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.27.5': {}
+
+ '@babel/core@7.27.4':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.27.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
+ '@babel/helpers': 7.27.6
+ '@babel/parser': 7.27.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ convert-source-map: 2.0.0
+ debug: 4.4.1(supports-color@9.4.0)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.27.5':
+ dependencies:
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.27.6
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.27.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.25.0
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.27.4
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ regexpu-core: 6.2.0
+ semver: 6.3.1
+
+ '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-plugin-utils': 7.27.1
+ debug: 4.4.1(supports-color@9.4.0)
+ lodash.debounce: 4.0.8
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.27.6
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-wrap-function': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-replace-supers@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.27.1': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helper-wrap-function@7.27.1':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helpers@7.27.6':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.27.6
+
+ '@babel/parser@7.27.5':
+ dependencies:
+ '@babel/types': 7.27.6
+
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-async-generator-functions@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.27.4)
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-block-scoping@7.27.5(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-classes@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.4)
+ '@babel/traverse': 7.27.4
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/template': 7.27.2
+
+ '@babel/plugin-transform-destructuring@7.27.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-literals@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.27.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-object-rest-spread@7.27.3(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-transform-destructuring': 7.27.3(@babel/core@7.27.4)
+ '@babel/plugin-transform-parameters': 7.27.1(@babel/core@7.27.4)
+
+ '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-parameters@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-display-name@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4)
+ '@babel/types': 7.27.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-regenerator@7.27.5(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-spread@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-typescript@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.27.4)
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/preset-env@7.27.2(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/compat-data': 7.27.5
+ '@babel/core': 7.27.4
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.27.4)
+ '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.27.4)
+ '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-async-generator-functions': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-block-scoping': 7.27.5(@babel/core@7.27.4)
+ '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-classes': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-destructuring': 7.27.3(@babel/core@7.27.4)
+ '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-object-rest-spread': 7.27.3(@babel/core@7.27.4)
+ '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-parameters': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-regenerator': 7.27.5(@babel/core@7.27.4)
+ '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.27.4)
+ '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.27.4)
+ babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.27.4)
+ babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.27.4)
+ babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.27.4)
+ core-js-compat: 3.43.0
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/types': 7.27.6
+ esutils: 2.0.3
+
+ '@babel/preset-react@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-transform-react-display-name': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-typescript@7.27.1(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/runtime@7.27.6': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
+
+ '@babel/traverse@7.27.4':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.27.5
+ '@babel/parser': 7.27.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.27.6
+ debug: 4.4.1(supports-color@9.4.0)
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.27.6':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+
+ '@bcoe/v8-coverage@0.2.3': {}
+
+ '@cloudflare/kv-asset-handler@0.4.0':
+ dependencies:
+ mime: 3.0.0
+
+ '@cloudflare/next-on-pages@1.13.12(vercel@44.2.7(rollup@2.79.2))(wrangler@4.22.0)':
+ dependencies:
+ acorn: 8.15.0
+ ast-types: 0.14.2
+ chalk: 5.4.1
+ chokidar: 3.6.0
+ commander: 11.1.0
+ cookie: 0.5.0
+ esbuild: 0.15.18
+ js-yaml: 4.1.0
+ miniflare: 3.20250408.2
+ package-manager-manager: 0.2.0
+ pcre-to-regexp: 1.1.0
+ semver: 7.7.2
+ vercel: 44.2.7(rollup@2.79.2)
+ wrangler: 4.22.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@cloudflare/unenv-preset@2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250617.0)':
+ dependencies:
+ unenv: 2.0.0-rc.17
+ optionalDependencies:
+ workerd: 1.20250617.0
+
+ '@cloudflare/workerd-darwin-64@1.20250408.0':
+ optional: true
+
+ '@cloudflare/workerd-darwin-64@1.20250617.0':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20250408.0':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20250617.0':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20250408.0':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20250617.0':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20250408.0':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20250617.0':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20250408.0':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20250617.0':
+ optional: true
+
+ '@commitlint/cli@16.3.0':
+ dependencies:
+ '@commitlint/format': 16.2.1
+ '@commitlint/lint': 16.2.4
+ '@commitlint/load': 16.3.0
+ '@commitlint/read': 16.2.1
+ '@commitlint/types': 16.2.1
+ lodash: 4.17.21
+ resolve-from: 5.0.0
+ resolve-global: 1.0.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+
+ '@commitlint/config-conventional@16.2.4':
+ dependencies:
+ conventional-changelog-conventionalcommits: 4.6.3
+
+ '@commitlint/config-validator@16.2.1':
+ dependencies:
+ '@commitlint/types': 16.2.1
+ ajv: 6.12.6
+
+ '@commitlint/ensure@16.2.1':
+ dependencies:
+ '@commitlint/types': 16.2.1
+ lodash: 4.17.21
+
+ '@commitlint/execute-rule@16.2.1': {}
+
+ '@commitlint/format@16.2.1':
+ dependencies:
+ '@commitlint/types': 16.2.1
+ chalk: 4.1.2
+
+ '@commitlint/is-ignored@16.2.4':
+ dependencies:
+ '@commitlint/types': 16.2.1
+ semver: 7.3.7
+
+ '@commitlint/lint@16.2.4':
+ dependencies:
+ '@commitlint/is-ignored': 16.2.4
+ '@commitlint/parse': 16.2.1
+ '@commitlint/rules': 16.2.4
+ '@commitlint/types': 16.2.1
+
+ '@commitlint/load@16.3.0':
+ dependencies:
+ '@commitlint/config-validator': 16.2.1
+ '@commitlint/execute-rule': 16.2.1
+ '@commitlint/resolve-extends': 16.2.1
+ '@commitlint/types': 16.2.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ cosmiconfig: 7.1.0
+ cosmiconfig-typescript-loader: 2.0.2(@types/node@24.0.3)(cosmiconfig@7.1.0)(typescript@4.9.5)
+ lodash: 4.17.21
+ resolve-from: 5.0.0
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+
+ '@commitlint/message@16.2.1': {}
+
+ '@commitlint/parse@16.2.1':
+ dependencies:
+ '@commitlint/types': 16.2.1
+ conventional-changelog-angular: 5.0.13
+ conventional-commits-parser: 3.2.4
+
+ '@commitlint/read@16.2.1':
+ dependencies:
+ '@commitlint/top-level': 16.2.1
+ '@commitlint/types': 16.2.1
+ fs-extra: 10.1.0
+ git-raw-commits: 2.0.11
+
+ '@commitlint/resolve-extends@16.2.1':
+ dependencies:
+ '@commitlint/config-validator': 16.2.1
+ '@commitlint/types': 16.2.1
+ import-fresh: 3.3.1
+ lodash: 4.17.21
+ resolve-from: 5.0.0
+ resolve-global: 1.0.0
+
+ '@commitlint/rules@16.2.4':
+ dependencies:
+ '@commitlint/ensure': 16.2.1
+ '@commitlint/message': 16.2.1
+ '@commitlint/to-lines': 16.2.1
+ '@commitlint/types': 16.2.1
+ execa: 5.1.1
+
+ '@commitlint/to-lines@16.2.1': {}
+
+ '@commitlint/top-level@16.2.1':
+ dependencies:
+ find-up: 5.0.0
+
+ '@commitlint/types@16.2.1':
+ dependencies:
+ chalk: 4.1.2
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@dnd-kit/accessibility@3.1.1(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ tslib: 2.8.1
+
+ '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@dnd-kit/accessibility': 3.1.1(react@18.3.1)
+ '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tslib: 2.8.1
+
+ '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+ react: 18.3.1
+ tslib: 2.8.1
+
+ '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+ react: 18.3.1
+ tslib: 2.8.1
+
+ '@dnd-kit/utilities@3.2.2(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ tslib: 2.8.1
+
+ '@edge-runtime/format@2.2.1': {}
+
+ '@edge-runtime/node-utils@2.3.0': {}
+
+ '@edge-runtime/ponyfill@2.4.2': {}
+
+ '@edge-runtime/primitives@4.1.0': {}
+
+ '@edge-runtime/vm@3.2.0':
+ dependencies:
+ '@edge-runtime/primitives': 4.1.0
+
+ '@emnapi/core@1.4.3':
+ dependencies:
+ '@emnapi/wasi-threads': 1.0.2
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.4.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.0.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.4':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/android-arm@0.15.18':
+ optional: true
+
+ '@esbuild/android-arm@0.25.4':
+ optional: true
+
+ '@esbuild/android-x64@0.25.4':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.4':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.4':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.4':
+ optional: true
+
+ '@esbuild/linux-loong64@0.15.18':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.4':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.4':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.4':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.4':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.4':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.4':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)':
+ dependencies:
+ eslint: 8.57.1
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
+
+ '@eslint/eslintrc@2.1.4':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.1(supports-color@9.4.0)
+ espree: 9.6.1
+ globals: 13.24.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@8.57.1': {}
+
+ '@fastify/busboy@2.1.1': {}
+
+ '@floating-ui/core@1.7.1':
+ dependencies:
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/dom@1.7.1':
+ dependencies:
+ '@floating-ui/core': 1.7.1
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/react-dom@2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@floating-ui/react@0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@floating-ui/utils': 0.2.9
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tabbable: 6.2.0
+
+ '@floating-ui/utils@0.2.9': {}
+
+ '@headlessui/react@2.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/focus': 3.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/interactions': 3.25.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tanstack/react-virtual': 3.13.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ use-sync-external-store: 1.5.0(react@18.3.1)
+
+ '@heroicons/react@2.2.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@humanwhocodes/config-array@0.13.0':
+ dependencies:
+ '@humanwhocodes/object-schema': 2.0.3
+ debug: 4.4.1(supports-color@9.4.0)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/object-schema@2.0.3': {}
+
+ '@img/sharp-darwin-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.0.5':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.0.5
+ optional: true
+
+ '@img/sharp-linux-s390x@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.0.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-wasm32@0.33.5':
+ dependencies:
+ '@emnapi/runtime': 1.4.3
+ optional: true
+
+ '@img/sharp-win32-ia32@0.33.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.33.5':
+ optional: true
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ dependencies:
+ camelcase: 5.3.1
+ find-up: 4.1.0
+ get-package-type: 0.1.0
+ js-yaml: 3.14.1
+ resolve-from: 5.0.0
+
+ '@istanbuljs/schema@0.1.3': {}
+
+ '@jest/console@27.5.1':
+ dependencies:
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ jest-message-util: 27.5.1
+ jest-util: 27.5.1
+ slash: 3.0.0
+
+ '@jest/core@27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))':
+ dependencies:
+ '@jest/console': 27.5.1
+ '@jest/reporters': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ emittery: 0.8.1
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 27.5.1
+ jest-config: 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ jest-haste-map: 27.5.1
+ jest-message-util: 27.5.1
+ jest-regex-util: 27.5.1
+ jest-resolve: 27.5.1
+ jest-resolve-dependencies: 27.5.1
+ jest-runner: 27.5.1
+ jest-runtime: 27.5.1
+ jest-snapshot: 27.5.1
+ jest-util: 27.5.1
+ jest-validate: 27.5.1
+ jest-watcher: 27.5.1
+ micromatch: 4.0.8
+ rimraf: 3.0.2
+ slash: 3.0.0
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - ts-node
+ - utf-8-validate
+
+ '@jest/diff-sequences@30.0.0': {}
+
+ '@jest/environment@27.5.1':
+ dependencies:
+ '@jest/fake-timers': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ jest-mock: 27.5.1
+
+ '@jest/expect-utils@30.0.0':
+ dependencies:
+ '@jest/get-type': 30.0.0
+
+ '@jest/fake-timers@27.5.1':
+ dependencies:
+ '@jest/types': 27.5.1
+ '@sinonjs/fake-timers': 8.1.0
+ '@types/node': 24.0.3
+ jest-message-util: 27.5.1
+ jest-mock: 27.5.1
+ jest-util: 27.5.1
+
+ '@jest/get-type@30.0.0': {}
+
+ '@jest/globals@27.5.1':
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/types': 27.5.1
+ expect: 27.5.1
+
+ '@jest/pattern@30.0.0':
+ dependencies:
+ '@types/node': 24.0.3
+ jest-regex-util: 30.0.0
+
+ '@jest/reporters@27.5.1':
+ dependencies:
+ '@bcoe/v8-coverage': 0.2.3
+ '@jest/console': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ collect-v8-coverage: 1.0.2
+ exit: 0.1.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-instrument: 5.2.1
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 4.0.1
+ istanbul-reports: 3.1.7
+ jest-haste-map: 27.5.1
+ jest-resolve: 27.5.1
+ jest-util: 27.5.1
+ jest-worker: 27.5.1
+ slash: 3.0.0
+ source-map: 0.6.1
+ string-length: 4.0.2
+ terminal-link: 2.1.1
+ v8-to-istanbul: 8.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/schemas@30.0.0':
+ dependencies:
+ '@sinclair/typebox': 0.34.35
+
+ '@jest/source-map@27.5.1':
+ dependencies:
+ callsites: 3.1.0
+ graceful-fs: 4.2.11
+ source-map: 0.6.1
+
+ '@jest/test-result@27.5.1':
+ dependencies:
+ '@jest/console': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/istanbul-lib-coverage': 2.0.6
+ collect-v8-coverage: 1.0.2
+
+ '@jest/test-sequencer@27.5.1':
+ dependencies:
+ '@jest/test-result': 27.5.1
+ graceful-fs: 4.2.11
+ jest-haste-map: 27.5.1
+ jest-runtime: 27.5.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/transform@27.5.1':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@jest/types': 27.5.1
+ babel-plugin-istanbul: 6.1.1
+ chalk: 4.1.2
+ convert-source-map: 1.9.0
+ fast-json-stable-stringify: 2.1.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 27.5.1
+ jest-regex-util: 27.5.1
+ jest-util: 27.5.1
+ micromatch: 4.0.8
+ pirates: 4.0.7
+ slash: 3.0.0
+ source-map: 0.6.1
+ write-file-atomic: 3.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/types@27.5.1':
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
+ '@types/node': 24.0.3
+ '@types/yargs': 16.0.9
+ chalk: 4.1.2
+
+ '@jest/types@30.0.0':
+ dependencies:
+ '@jest/pattern': 30.0.0
+ '@jest/schemas': 30.0.0
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
+ '@types/node': 24.0.3
+ '@types/yargs': 17.0.33
+ chalk: 4.1.2
+
+ '@jridgewell/gen-mapping@0.3.8':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/set-array@1.2.1': {}
+
+ '@jridgewell/source-map@0.3.6':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/sourcemap-codec@1.5.0': {}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@mapbox/node-pre-gyp@2.0.0':
+ dependencies:
+ consola: 3.4.2
+ detect-libc: 2.0.4
+ https-proxy-agent: 7.0.6
+ node-fetch: 2.7.0
+ nopt: 8.1.0
+ semver: 7.7.2
+ tar: 7.4.3
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@maverick-js/signals@5.11.5': {}
+
+ '@napi-rs/wasm-runtime@0.2.11':
+ dependencies:
+ '@emnapi/core': 1.4.3
+ '@emnapi/runtime': 1.4.3
+ '@tybys/wasm-util': 0.9.0
+ optional: true
+
+ '@next/env@14.2.30': {}
+
+ '@next/eslint-plugin-next@14.2.30':
+ dependencies:
+ glob: 10.3.10
+
+ '@next/swc-darwin-arm64@14.2.30':
+ optional: true
+
+ '@next/swc-darwin-x64@14.2.30':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@14.2.30':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@14.2.30':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@14.2.30':
+ optional: true
+
+ '@next/swc-linux-x64-musl@14.2.30':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@14.2.30':
+ optional: true
+
+ '@next/swc-win32-ia32-msvc@14.2.30':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@14.2.30':
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@nolyfill/is-core-module@1.0.39': {}
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@react-aria/focus@3.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/interactions': 3.25.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/utils': 3.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-types/shared': 3.30.0(react@18.3.1)
+ '@swc/helpers': 0.5.5
+ clsx: 2.1.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-aria/interactions@3.25.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.9(react@18.3.1)
+ '@react-aria/utils': 3.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-stately/flags': 3.1.2
+ '@react-types/shared': 3.30.0(react@18.3.1)
+ '@swc/helpers': 0.5.5
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-aria/ssr@3.9.9(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.5
+ react: 18.3.1
+
+ '@react-aria/utils@3.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.9(react@18.3.1)
+ '@react-stately/flags': 3.1.2
+ '@react-stately/utils': 3.10.7(react@18.3.1)
+ '@react-types/shared': 3.30.0(react@18.3.1)
+ '@swc/helpers': 0.5.5
+ clsx: 2.1.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-stately/flags@3.1.2':
+ dependencies:
+ '@swc/helpers': 0.5.5
+
+ '@react-stately/utils@3.10.7(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.5
+ react: 18.3.1
+
+ '@react-types/shared@3.30.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@redis/bloom@1.2.0(@redis/client@1.6.1)':
+ dependencies:
+ '@redis/client': 1.6.1
+
+ '@redis/client@1.6.1':
+ dependencies:
+ cluster-key-slot: 1.1.2
+ generic-pool: 3.9.0
+ yallist: 4.0.0
+
+ '@redis/graph@1.1.1(@redis/client@1.6.1)':
+ dependencies:
+ '@redis/client': 1.6.1
+
+ '@redis/json@1.0.7(@redis/client@1.6.1)':
+ dependencies:
+ '@redis/client': 1.6.1
+
+ '@redis/search@1.2.0(@redis/client@1.6.1)':
+ dependencies:
+ '@redis/client': 1.6.1
+
+ '@redis/time-series@1.1.0(@redis/client@1.6.1)':
+ dependencies:
+ '@redis/client': 1.6.1
+
+ '@rollup/plugin-babel@5.3.1(@babel/core@7.27.4)(@types/babel__core@7.20.5)(rollup@2.79.2)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-module-imports': 7.27.1
+ '@rollup/pluginutils': 3.1.0(rollup@2.79.2)
+ rollup: 2.79.2
+ optionalDependencies:
+ '@types/babel__core': 7.20.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@rollup/plugin-node-resolve@11.2.1(rollup@2.79.2)':
+ dependencies:
+ '@rollup/pluginutils': 3.1.0(rollup@2.79.2)
+ '@types/resolve': 1.17.1
+ builtin-modules: 3.3.0
+ deepmerge: 4.3.1
+ is-module: 1.0.0
+ resolve: 1.22.10
+ rollup: 2.79.2
+
+ '@rollup/plugin-replace@2.4.2(rollup@2.79.2)':
+ dependencies:
+ '@rollup/pluginutils': 3.1.0(rollup@2.79.2)
+ magic-string: 0.25.9
+ rollup: 2.79.2
+
+ '@rollup/pluginutils@3.1.0(rollup@2.79.2)':
+ dependencies:
+ '@types/estree': 0.0.39
+ estree-walker: 1.0.1
+ picomatch: 2.3.1
+ rollup: 2.79.2
+
+ '@rollup/pluginutils@5.2.0(rollup@2.79.2)':
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-walker: 2.0.2
+ picomatch: 4.0.2
+ optionalDependencies:
+ rollup: 2.79.2
+
+ '@rtsao/scc@1.1.0': {}
+
+ '@rushstack/eslint-patch@1.11.0': {}
+
+ '@sinclair/typebox@0.25.24': {}
+
+ '@sinclair/typebox@0.34.35': {}
+
+ '@sinonjs/commons@1.8.6':
+ dependencies:
+ type-detect: 4.0.8
+
+ '@sinonjs/fake-timers@8.1.0':
+ dependencies:
+ '@sinonjs/commons': 1.8.6
+
+ '@surma/rollup-plugin-off-main-thread@2.2.3':
+ dependencies:
+ ejs: 3.1.10
+ json5: 2.2.3
+ magic-string: 0.25.9
+ string.prototype.matchall: 4.0.12
+
+ '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+
+ '@svgr/babel-preset@8.1.0(@babel/core@7.27.4)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.27.4)
+ '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.27.4)
+
+ '@svgr/core@8.1.0(typescript@4.9.5)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4)
+ camelcase: 6.3.0
+ cosmiconfig: 8.3.6(typescript@4.9.5)
+ snake-case: 3.0.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@svgr/hast-util-to-babel-ast@8.0.0':
+ dependencies:
+ '@babel/types': 7.27.6
+ entities: 4.5.0
+
+ '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@4.9.5))':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4)
+ '@svgr/core': 8.1.0(typescript@4.9.5)
+ '@svgr/hast-util-to-babel-ast': 8.0.0
+ svg-parser: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@4.9.5))(typescript@4.9.5)':
+ dependencies:
+ '@svgr/core': 8.1.0(typescript@4.9.5)
+ cosmiconfig: 8.3.6(typescript@4.9.5)
+ deepmerge: 4.3.1
+ svgo: 3.3.2
+ transitivePeerDependencies:
+ - typescript
+
+ '@svgr/webpack@8.1.0(typescript@4.9.5)':
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.27.4)
+ '@babel/preset-env': 7.27.2(@babel/core@7.27.4)
+ '@babel/preset-react': 7.27.1(@babel/core@7.27.4)
+ '@babel/preset-typescript': 7.27.1(@babel/core@7.27.4)
+ '@svgr/core': 8.1.0(typescript@4.9.5)
+ '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@4.9.5))
+ '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@4.9.5))(typescript@4.9.5)
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@swc/counter@0.1.3': {}
+
+ '@swc/helpers@0.5.5':
+ dependencies:
+ '@swc/counter': 0.1.3
+ tslib: 2.8.1
+
+ '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)))':
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+
+ '@tanstack/react-virtual@3.13.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/virtual-core': 3.13.10
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@tanstack/virtual-core@3.13.10': {}
+
+ '@testing-library/dom@10.4.0':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/runtime': 7.27.6
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ chalk: 4.1.2
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@5.17.0':
+ dependencies:
+ '@adobe/css-tools': 4.4.3
+ '@babel/runtime': 7.27.6
+ '@types/testing-library__jest-dom': 5.14.9
+ aria-query: 5.3.2
+ chalk: 3.0.0
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.5.16
+ lodash: 4.17.21
+ redent: 3.0.0
+
+ '@testing-library/react@15.0.7(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.27.6
+ '@testing-library/dom': 10.4.0
+ '@types/react-dom': 18.3.7(@types/react@18.3.23)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.23
+
+ '@tootallnate/once@1.1.2': {}
+
+ '@tootallnate/once@2.0.0': {}
+
+ '@trysound/sax@0.2.0': {}
+
+ '@ts-morph/common@0.11.1':
+ dependencies:
+ fast-glob: 3.3.3
+ minimatch: 3.1.2
+ mkdirp: 1.0.4
+ path-browserify: 1.0.1
+
+ '@tsconfig/node10@1.0.11': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
+
+ '@tsconfig/node16@1.0.4': {}
+
+ '@tybys/wasm-util@0.9.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/aria-query@5.0.4': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.7
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.27.6
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.27.5
+ '@babel/types': 7.27.6
+
+ '@types/babel__traverse@7.20.7':
+ dependencies:
+ '@babel/types': 7.27.6
+
+ '@types/eslint-scope@3.7.7':
+ dependencies:
+ '@types/eslint': 9.6.1
+ '@types/estree': 1.0.8
+
+ '@types/eslint@9.6.1':
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/json-schema': 7.0.15
+
+ '@types/estree@0.0.39': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/glob@7.2.0':
+ dependencies:
+ '@types/minimatch': 5.1.2
+ '@types/node': 24.0.3
+
+ '@types/graceful-fs@4.1.9':
+ dependencies:
+ '@types/node': 24.0.3
+
+ '@types/istanbul-lib-coverage@2.0.6': {}
+
+ '@types/istanbul-lib-report@3.0.3':
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+
+ '@types/istanbul-reports@3.0.4':
+ dependencies:
+ '@types/istanbul-lib-report': 3.0.3
+
+ '@types/jest@30.0.0':
+ dependencies:
+ expect: 30.0.0
+ pretty-format: 30.0.0
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/json5@0.0.29': {}
+
+ '@types/minimatch@5.1.2': {}
+
+ '@types/minimist@1.2.5': {}
+
+ '@types/node@16.18.11': {}
+
+ '@types/node@24.0.3':
+ dependencies:
+ undici-types: 7.8.0
+
+ '@types/normalize-package-data@2.4.4': {}
+
+ '@types/parse-json@4.0.2': {}
+
+ '@types/prettier@2.7.3': {}
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.23)':
+ dependencies:
+ '@types/react': 18.3.23
+
+ '@types/react-dom@19.1.6(@types/react@18.3.23)':
+ dependencies:
+ '@types/react': 18.3.23
+
+ '@types/react@18.3.23':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.1.3
+
+ '@types/resolve@1.17.1':
+ dependencies:
+ '@types/node': 24.0.3
+
+ '@types/semver@7.7.0': {}
+
+ '@types/stack-utils@2.0.3': {}
+
+ '@types/testing-library__jest-dom@5.14.9':
+ dependencies:
+ '@types/jest': 30.0.0
+
+ '@types/trusted-types@2.0.7': {}
+
+ '@types/yargs-parser@21.0.3': {}
+
+ '@types/yargs@16.0.9':
+ dependencies:
+ '@types/yargs-parser': 21.0.3
+
+ '@types/yargs@17.0.33':
+ dependencies:
+ '@types/yargs-parser': 21.0.3
+
+ '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ debug: 4.4.1(supports-color@9.4.0)
+ eslint: 8.57.1
+ graphemer: 1.4.0
+ ignore: 5.3.2
+ natural-compare-lite: 1.4.0
+ semver: 7.7.2
+ tsutils: 3.21.0(typescript@4.9.5)
+ optionalDependencies:
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+ debug: 4.4.1(supports-color@9.4.0)
+ eslint: 8.57.1
+ optionalDependencies:
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@5.62.0':
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+
+ '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+ '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ debug: 4.4.1(supports-color@9.4.0)
+ eslint: 8.57.1
+ tsutils: 3.21.0(typescript@4.9.5)
+ optionalDependencies:
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@5.62.0': {}
+
+ '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)':
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ debug: 4.4.1(supports-color@9.4.0)
+ globby: 11.1.0
+ is-glob: 4.0.3
+ semver: 7.7.2
+ tsutils: 3.21.0(typescript@4.9.5)
+ optionalDependencies:
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.7.0
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+ eslint: 8.57.1
+ eslint-scope: 5.1.1
+ semver: 7.7.2
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@typescript-eslint/visitor-keys@5.62.0':
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ eslint-visitor-keys: 3.4.3
+
+ '@ungap/structured-clone@1.3.0': {}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.9.0':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.11
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.9.0':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.9.0':
+ optional: true
+
+ '@upstash/redis@1.35.1':
+ dependencies:
+ uncrypto: 0.1.3
+
+ '@vercel/blob@1.0.2':
+ dependencies:
+ async-retry: 1.3.3
+ is-buffer: 2.0.5
+ is-node-process: 1.2.0
+ throttleit: 2.1.0
+ undici: 5.29.0
+
+ '@vercel/build-utils@10.6.1': {}
+
+ '@vercel/error-utils@2.0.3': {}
+
+ '@vercel/fun@1.1.6':
+ dependencies:
+ '@tootallnate/once': 2.0.0
+ async-listen: 1.2.0
+ debug: 4.3.4
+ generic-pool: 3.4.2
+ micro: 9.3.5-canary.3
+ ms: 2.1.1
+ node-fetch: 2.6.7
+ path-match: 1.2.4
+ promisepipe: 3.0.0
+ semver: 7.5.4
+ stat-mode: 0.3.0
+ stream-to-promise: 2.2.0
+ tar: 6.2.1
+ tinyexec: 0.3.2
+ tree-kill: 1.2.2
+ uid-promise: 1.0.0
+ xdg-app-paths: 5.1.0
+ yauzl-promise: 2.1.3
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@vercel/gatsby-plugin-vercel-analytics@1.0.11':
+ dependencies:
+ web-vitals: 0.2.4
+
+ '@vercel/gatsby-plugin-vercel-builder@2.0.84':
+ dependencies:
+ '@sinclair/typebox': 0.25.24
+ '@vercel/build-utils': 10.6.1
+ esbuild: 0.14.47
+ etag: 1.8.1
+ fs-extra: 11.1.0
+
+ '@vercel/go@3.2.1': {}
+
+ '@vercel/hydrogen@1.2.2':
+ dependencies:
+ '@vercel/static-config': 3.1.1
+ ts-morph: 12.0.0
+
+ '@vercel/next@4.9.2(rollup@2.79.2)':
+ dependencies:
+ '@vercel/nft': 0.29.2(rollup@2.79.2)
+ transitivePeerDependencies:
+ - encoding
+ - rollup
+ - supports-color
+
+ '@vercel/nft@0.29.2(rollup@2.79.2)':
+ dependencies:
+ '@mapbox/node-pre-gyp': 2.0.0
+ '@rollup/pluginutils': 5.2.0(rollup@2.79.2)
+ acorn: 8.15.0
+ acorn-import-attributes: 1.9.5(acorn@8.15.0)
+ async-sema: 3.1.1
+ bindings: 1.5.0
+ estree-walker: 2.0.2
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ node-gyp-build: 4.8.4
+ picomatch: 4.0.2
+ resolve-from: 5.0.0
+ transitivePeerDependencies:
+ - encoding
+ - rollup
+ - supports-color
+
+ '@vercel/node@5.3.0(rollup@2.79.2)':
+ dependencies:
+ '@edge-runtime/node-utils': 2.3.0
+ '@edge-runtime/primitives': 4.1.0
+ '@edge-runtime/vm': 3.2.0
+ '@types/node': 16.18.11
+ '@vercel/build-utils': 10.6.1
+ '@vercel/error-utils': 2.0.3
+ '@vercel/nft': 0.29.2(rollup@2.79.2)
+ '@vercel/static-config': 3.1.1
+ async-listen: 3.0.0
+ cjs-module-lexer: 1.2.3
+ edge-runtime: 2.5.9
+ es-module-lexer: 1.4.1
+ esbuild: 0.14.47
+ etag: 1.8.1
+ node-fetch: 2.6.9
+ path-to-regexp: 6.1.0
+ path-to-regexp-updated: path-to-regexp@6.3.0
+ ts-morph: 12.0.0
+ ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5)
+ typescript: 4.9.5
+ undici: 5.28.4
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+ - encoding
+ - rollup
+ - supports-color
+
+ '@vercel/python@4.7.2': {}
+
+ '@vercel/redwood@2.3.3(rollup@2.79.2)':
+ dependencies:
+ '@vercel/nft': 0.29.2(rollup@2.79.2)
+ '@vercel/static-config': 3.1.1
+ semver: 6.3.1
+ ts-morph: 12.0.0
+ transitivePeerDependencies:
+ - encoding
+ - rollup
+ - supports-color
+
+ '@vercel/remix-builder@5.4.9(rollup@2.79.2)':
+ dependencies:
+ '@vercel/error-utils': 2.0.3
+ '@vercel/nft': 0.29.2(rollup@2.79.2)
+ '@vercel/static-config': 3.1.1
+ path-to-regexp: 6.1.0
+ path-to-regexp-updated: path-to-regexp@6.3.0
+ ts-morph: 12.0.0
+ transitivePeerDependencies:
+ - encoding
+ - rollup
+ - supports-color
+
+ '@vercel/ruby@2.2.0': {}
+
+ '@vercel/static-build@2.7.10':
+ dependencies:
+ '@vercel/gatsby-plugin-vercel-analytics': 1.0.11
+ '@vercel/gatsby-plugin-vercel-builder': 2.0.84
+ '@vercel/static-config': 3.1.1
+ ts-morph: 12.0.0
+
+ '@vercel/static-config@3.1.1':
+ dependencies:
+ ajv: 8.6.3
+ json-schema-to-ts: 1.6.4
+ ts-morph: 12.0.0
+
+ '@vidstack/react@1.12.13(@types/react@18.3.23)(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.1
+ '@types/react': 18.3.23
+ media-captions: 1.0.4
+ react: 18.3.1
+
+ '@webassemblyjs/ast@1.14.1':
+ dependencies:
+ '@webassemblyjs/helper-numbers': 1.13.2
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+
+ '@webassemblyjs/floating-point-hex-parser@1.13.2': {}
+
+ '@webassemblyjs/helper-api-error@1.13.2': {}
+
+ '@webassemblyjs/helper-buffer@1.14.1': {}
+
+ '@webassemblyjs/helper-numbers@1.13.2':
+ dependencies:
+ '@webassemblyjs/floating-point-hex-parser': 1.13.2
+ '@webassemblyjs/helper-api-error': 1.13.2
+ '@xtuc/long': 4.2.2
+
+ '@webassemblyjs/helper-wasm-bytecode@1.13.2': {}
+
+ '@webassemblyjs/helper-wasm-section@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/wasm-gen': 1.14.1
+
+ '@webassemblyjs/ieee754@1.13.2':
+ dependencies:
+ '@xtuc/ieee754': 1.2.0
+
+ '@webassemblyjs/leb128@1.13.2':
+ dependencies:
+ '@xtuc/long': 4.2.2
+
+ '@webassemblyjs/utf8@1.13.2': {}
+
+ '@webassemblyjs/wasm-edit@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/helper-wasm-section': 1.14.1
+ '@webassemblyjs/wasm-gen': 1.14.1
+ '@webassemblyjs/wasm-opt': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ '@webassemblyjs/wast-printer': 1.14.1
+
+ '@webassemblyjs/wasm-gen@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/ieee754': 1.13.2
+ '@webassemblyjs/leb128': 1.13.2
+ '@webassemblyjs/utf8': 1.13.2
+
+ '@webassemblyjs/wasm-opt@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-buffer': 1.14.1
+ '@webassemblyjs/wasm-gen': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+
+ '@webassemblyjs/wasm-parser@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/helper-api-error': 1.13.2
+ '@webassemblyjs/helper-wasm-bytecode': 1.13.2
+ '@webassemblyjs/ieee754': 1.13.2
+ '@webassemblyjs/leb128': 1.13.2
+ '@webassemblyjs/utf8': 1.13.2
+
+ '@webassemblyjs/wast-printer@1.14.1':
+ dependencies:
+ '@webassemblyjs/ast': 1.14.1
+ '@xtuc/long': 4.2.2
+
+ '@xtuc/ieee754@1.2.0': {}
+
+ '@xtuc/long@4.2.2': {}
+
+ JSONStream@1.3.5:
+ dependencies:
+ jsonparse: 1.3.1
+ through: 2.3.8
+
+ abab@2.0.6: {}
+
+ abbrev@3.0.1: {}
+
+ acorn-globals@6.0.0:
+ dependencies:
+ acorn: 7.4.1
+ acorn-walk: 7.2.0
+
+ acorn-import-attributes@1.9.5(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn-walk@7.2.0: {}
+
+ acorn-walk@8.3.2: {}
+
+ acorn-walk@8.3.4:
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@7.4.1: {}
+
+ acorn@8.14.0: {}
+
+ acorn@8.15.0: {}
+
+ agent-base@6.0.2:
+ dependencies:
+ debug: 4.4.1(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.3: {}
+
+ aggregate-error@3.1.0:
+ dependencies:
+ clean-stack: 2.2.0
+ indent-string: 4.0.0
+
+ ajv-formats@2.1.1(ajv@8.17.1):
+ optionalDependencies:
+ ajv: 8.17.1
+
+ ajv-keywords@3.5.2(ajv@6.12.6):
+ dependencies:
+ ajv: 6.12.6
+
+ ajv-keywords@5.1.0(ajv@8.17.1):
+ dependencies:
+ ajv: 8.17.1
+ fast-deep-equal: 3.1.3
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.17.1:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.0.6
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ ajv@8.6.3:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+
+ ansi-escapes@4.3.2:
+ dependencies:
+ type-fest: 0.21.3
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.1.0: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ ansi-styles@6.2.1: {}
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ arg@4.1.0: {}
+
+ arg@4.1.3: {}
+
+ arg@5.0.2: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-ify@1.0.0: {}
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array-union@1.0.2:
+ dependencies:
+ array-uniq: 1.0.3
+
+ array-union@2.1.0: {}
+
+ array-uniq@1.0.3: {}
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.findlastindex@1.2.6:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ arrify@1.0.1: {}
+
+ artplayer@5.2.3:
+ dependencies:
+ option-validator: 2.0.6
+
+ as-table@1.0.55:
+ dependencies:
+ printable-characters: 1.0.42
+
+ ast-types-flow@0.0.8: {}
+
+ ast-types@0.14.2:
+ dependencies:
+ tslib: 2.8.1
+
+ astral-regex@2.0.0: {}
+
+ async-function@1.0.0: {}
+
+ async-listen@1.2.0: {}
+
+ async-listen@3.0.0: {}
+
+ async-listen@3.0.1: {}
+
+ async-retry@1.3.3:
+ dependencies:
+ retry: 0.13.1
+
+ async-sema@3.1.1: {}
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ at-least-node@1.0.0: {}
+
+ autoprefixer@10.4.21(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.25.0
+ caniuse-lite: 1.0.30001723
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ axe-core@4.10.3: {}
+
+ axobject-query@4.1.0: {}
+
+ babel-jest@27.5.1(@babel/core@7.27.4):
+ dependencies:
+ '@babel/core': 7.27.4
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/babel__core': 7.20.5
+ babel-plugin-istanbul: 6.1.1
+ babel-preset-jest: 27.5.1(@babel/core@7.27.4)
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-loader@8.4.1(@babel/core@7.27.4)(webpack@5.99.9):
+ dependencies:
+ '@babel/core': 7.27.4
+ find-cache-dir: 3.3.2
+ loader-utils: 2.0.4
+ make-dir: 3.1.0
+ schema-utils: 2.7.1
+ webpack: 5.99.9
+
+ babel-plugin-istanbul@6.1.1:
+ dependencies:
+ '@babel/helper-plugin-utils': 7.27.1
+ '@istanbuljs/load-nyc-config': 1.1.0
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-instrument: 5.2.1
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-jest-hoist@27.5.1:
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.27.6
+ '@types/babel__core': 7.20.5
+ '@types/babel__traverse': 7.20.7
+
+ babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.27.4):
+ dependencies:
+ '@babel/compat-data': 7.27.5
+ '@babel/core': 7.27.4
+ '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4)
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.27.4):
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4)
+ core-js-compat: 3.43.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.27.4):
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-preset-current-node-syntax@1.1.0(@babel/core@7.27.4):
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.27.4)
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.27.4)
+ '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.27.4)
+ '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.4)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.4)
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.27.4)
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.27.4)
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.27.4)
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.27.4)
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.27.4)
+
+ babel-preset-jest@27.5.1(@babel/core@7.27.4):
+ dependencies:
+ '@babel/core': 7.27.4
+ babel-plugin-jest-hoist: 27.5.1
+ babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4)
+
+ balanced-match@1.0.2: {}
+
+ big.js@5.2.2: {}
+
+ binary-extensions@2.3.0: {}
+
+ bindings@1.5.0:
+ dependencies:
+ file-uri-to-path: 1.0.0
+
+ blake3-wasm@2.1.5: {}
+
+ boolbase@1.0.0: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browser-process-hrtime@1.0.0: {}
+
+ browserslist@4.25.0:
+ dependencies:
+ caniuse-lite: 1.0.30001723
+ electron-to-chromium: 1.5.168
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.25.0)
+
+ bser@2.1.1:
+ dependencies:
+ node-int64: 0.4.0
+
+ buffer-crc32@0.2.13: {}
+
+ buffer-from@1.1.2: {}
+
+ builtin-modules@3.3.0: {}
+
+ busboy@1.6.0:
+ dependencies:
+ streamsearch: 1.1.0
+
+ bytes@3.1.0: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camelcase-css@2.0.1: {}
+
+ camelcase-keys@6.2.2:
+ dependencies:
+ camelcase: 5.3.1
+ map-obj: 4.3.0
+ quick-lru: 4.0.1
+
+ camelcase@5.3.1: {}
+
+ camelcase@6.3.0: {}
+
+ caniuse-lite@1.0.30001723: {}
+
+ chalk@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@5.4.1: {}
+
+ char-regex@1.0.2: {}
+
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ chokidar@4.0.0:
+ dependencies:
+ readdirp: 4.1.2
+
+ chownr@2.0.0: {}
+
+ chownr@3.0.0: {}
+
+ chrome-trace-event@1.0.4: {}
+
+ ci-info@3.9.0: {}
+
+ ci-info@4.2.0: {}
+
+ cjs-module-lexer@1.2.3: {}
+
+ cjs-module-lexer@1.4.3: {}
+
+ clean-stack@2.2.0: {}
+
+ clean-webpack-plugin@4.0.0(webpack@5.99.9):
+ dependencies:
+ del: 4.1.1
+ webpack: 5.99.9
+
+ cli-cursor@3.1.0:
+ dependencies:
+ restore-cursor: 3.1.0
+
+ cli-truncate@2.1.0:
+ dependencies:
+ slice-ansi: 3.0.0
+ string-width: 4.2.3
+
+ cli-truncate@3.1.0:
+ dependencies:
+ slice-ansi: 5.0.0
+ string-width: 5.1.2
+
+ client-only@0.0.1: {}
+
+ cliui@7.0.4:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ clsx@2.1.1: {}
+
+ cluster-key-slot@1.1.2: {}
+
+ co@4.6.0: {}
+
+ code-block-writer@10.1.1: {}
+
+ collect-v8-coverage@1.0.2: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ color-string@1.9.1:
+ dependencies:
+ color-name: 1.1.4
+ simple-swizzle: 0.2.2
+
+ color@4.2.3:
+ dependencies:
+ color-convert: 2.0.1
+ color-string: 1.9.1
+
+ colorette@2.0.20: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@11.1.0: {}
+
+ commander@2.20.3: {}
+
+ commander@4.1.1: {}
+
+ commander@7.2.0: {}
+
+ commander@9.5.0: {}
+
+ common-tags@1.8.2: {}
+
+ commondir@1.0.1: {}
+
+ compare-func@2.0.0:
+ dependencies:
+ array-ify: 1.0.0
+ dot-prop: 5.3.0
+
+ concat-map@0.0.1: {}
+
+ consola@3.4.2: {}
+
+ content-type@1.0.4: {}
+
+ conventional-changelog-angular@5.0.13:
+ dependencies:
+ compare-func: 2.0.0
+ q: 1.5.1
+
+ conventional-changelog-conventionalcommits@4.6.3:
+ dependencies:
+ compare-func: 2.0.0
+ lodash: 4.17.21
+ q: 1.5.1
+
+ conventional-commits-parser@3.2.4:
+ dependencies:
+ JSONStream: 1.3.5
+ is-text-path: 1.0.1
+ lodash: 4.17.21
+ meow: 8.1.2
+ split2: 3.2.2
+ through2: 4.0.2
+
+ convert-hrtime@3.0.0: {}
+
+ convert-source-map@1.9.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@0.5.0: {}
+
+ cookie@0.7.2: {}
+
+ core-js-compat@3.43.0:
+ dependencies:
+ browserslist: 4.25.0
+
+ cosmiconfig-typescript-loader@2.0.2(@types/node@24.0.3)(cosmiconfig@7.1.0)(typescript@4.9.5):
+ dependencies:
+ '@types/node': 24.0.3
+ cosmiconfig: 7.1.0
+ ts-node: 10.9.2(@types/node@24.0.3)(typescript@4.9.5)
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+
+ cosmiconfig@7.1.0:
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.1
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.2
+
+ cosmiconfig@8.3.6(typescript@4.9.5):
+ dependencies:
+ import-fresh: 3.3.1
+ js-yaml: 4.1.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ optionalDependencies:
+ typescript: 4.9.5
+
+ create-require@1.1.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ crypto-random-string@2.0.0: {}
+
+ css-select@5.1.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.1.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ nth-check: 2.1.1
+
+ css-tree@2.2.1:
+ dependencies:
+ mdn-data: 2.0.28
+ source-map-js: 1.2.1
+
+ css-tree@2.3.1:
+ dependencies:
+ mdn-data: 2.0.30
+ source-map-js: 1.2.1
+
+ css-what@6.1.0: {}
+
+ css.escape@1.5.1: {}
+
+ cssesc@3.0.0: {}
+
+ csso@5.0.5:
+ dependencies:
+ css-tree: 2.2.1
+
+ cssom@0.3.8: {}
+
+ cssom@0.4.4: {}
+
+ cssstyle@2.3.0:
+ dependencies:
+ cssom: 0.3.8
+
+ csstype@3.1.3: {}
+
+ damerau-levenshtein@1.0.8: {}
+
+ dargs@7.0.0: {}
+
+ data-uri-to-buffer@2.0.2: {}
+
+ data-urls@2.0.0:
+ dependencies:
+ abab: 2.0.6
+ whatwg-mimetype: 2.3.0
+ whatwg-url: 8.7.0
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.3.4:
+ dependencies:
+ ms: 2.1.2
+
+ debug@4.4.1(supports-color@9.4.0):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 9.4.0
+
+ decamelize-keys@1.1.1:
+ dependencies:
+ decamelize: 1.2.0
+ map-obj: 1.0.1
+
+ decamelize@1.2.0: {}
+
+ decimal.js@10.5.0: {}
+
+ dedent@0.7.0: {}
+
+ deep-is@0.1.4: {}
+
+ deepmerge@4.3.1: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ defu@6.1.4: {}
+
+ del@4.1.1:
+ dependencies:
+ '@types/glob': 7.2.0
+ globby: 6.1.0
+ is-path-cwd: 2.2.0
+ is-path-in-cwd: 2.1.0
+ p-map: 2.1.0
+ pify: 4.0.1
+ rimraf: 2.7.1
+
+ delayed-stream@1.0.0: {}
+
+ depd@1.1.2: {}
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.0.4: {}
+
+ detect-newline@3.1.0: {}
+
+ didyoumean@1.2.2: {}
+
+ diff-sequences@27.5.1: {}
+
+ diff@4.0.2: {}
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ dlv@1.1.3: {}
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ doctrine@3.0.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dom-accessibility-api@0.5.16: {}
+
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domexception@2.0.1:
+ dependencies:
+ webidl-conversions: 5.0.0
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
+ dot-case@3.0.4:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.8.1
+
+ dot-prop@5.3.0:
+ dependencies:
+ is-obj: 2.0.0
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ eastasianwidth@0.2.0: {}
+
+ edge-runtime@2.5.9:
+ dependencies:
+ '@edge-runtime/format': 2.2.1
+ '@edge-runtime/ponyfill': 2.4.2
+ '@edge-runtime/vm': 3.2.0
+ async-listen: 3.0.1
+ mri: 1.2.0
+ picocolors: 1.0.0
+ pretty-ms: 7.0.1
+ signal-exit: 4.0.2
+ time-span: 4.0.0
+
+ ejs@3.1.10:
+ dependencies:
+ jake: 10.9.2
+
+ electron-to-chromium@1.5.168: {}
+
+ emittery@0.8.1: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ emojis-list@3.0.0: {}
+
+ end-of-stream@1.1.0:
+ dependencies:
+ once: 1.3.3
+
+ enhanced-resolve@5.18.2:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.2
+
+ entities@4.5.0: {}
+
+ error-ex@1.3.2:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ es-abstract@1.24.0:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.19
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.2.1:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ safe-array-concat: 1.1.3
+
+ es-module-lexer@1.4.1: {}
+
+ es-module-lexer@1.7.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ esbuild-android-64@0.14.47:
+ optional: true
+
+ esbuild-android-64@0.15.18:
+ optional: true
+
+ esbuild-android-arm64@0.14.47:
+ optional: true
+
+ esbuild-android-arm64@0.15.18:
+ optional: true
+
+ esbuild-darwin-64@0.14.47:
+ optional: true
+
+ esbuild-darwin-64@0.15.18:
+ optional: true
+
+ esbuild-darwin-arm64@0.14.47:
+ optional: true
+
+ esbuild-darwin-arm64@0.15.18:
+ optional: true
+
+ esbuild-freebsd-64@0.14.47:
+ optional: true
+
+ esbuild-freebsd-64@0.15.18:
+ optional: true
+
+ esbuild-freebsd-arm64@0.14.47:
+ optional: true
+
+ esbuild-freebsd-arm64@0.15.18:
+ optional: true
+
+ esbuild-linux-32@0.14.47:
+ optional: true
+
+ esbuild-linux-32@0.15.18:
+ optional: true
+
+ esbuild-linux-64@0.14.47:
+ optional: true
+
+ esbuild-linux-64@0.15.18:
+ optional: true
+
+ esbuild-linux-arm64@0.14.47:
+ optional: true
+
+ esbuild-linux-arm64@0.15.18:
+ optional: true
+
+ esbuild-linux-arm@0.14.47:
+ optional: true
+
+ esbuild-linux-arm@0.15.18:
+ optional: true
+
+ esbuild-linux-mips64le@0.14.47:
+ optional: true
+
+ esbuild-linux-mips64le@0.15.18:
+ optional: true
+
+ esbuild-linux-ppc64le@0.14.47:
+ optional: true
+
+ esbuild-linux-ppc64le@0.15.18:
+ optional: true
+
+ esbuild-linux-riscv64@0.14.47:
+ optional: true
+
+ esbuild-linux-riscv64@0.15.18:
+ optional: true
+
+ esbuild-linux-s390x@0.14.47:
+ optional: true
+
+ esbuild-linux-s390x@0.15.18:
+ optional: true
+
+ esbuild-netbsd-64@0.14.47:
+ optional: true
+
+ esbuild-netbsd-64@0.15.18:
+ optional: true
+
+ esbuild-openbsd-64@0.14.47:
+ optional: true
+
+ esbuild-openbsd-64@0.15.18:
+ optional: true
+
+ esbuild-sunos-64@0.14.47:
+ optional: true
+
+ esbuild-sunos-64@0.15.18:
+ optional: true
+
+ esbuild-windows-32@0.14.47:
+ optional: true
+
+ esbuild-windows-32@0.15.18:
+ optional: true
+
+ esbuild-windows-64@0.14.47:
+ optional: true
+
+ esbuild-windows-64@0.15.18:
+ optional: true
+
+ esbuild-windows-arm64@0.14.47:
+ optional: true
+
+ esbuild-windows-arm64@0.15.18:
+ optional: true
+
+ esbuild@0.14.47:
+ optionalDependencies:
+ esbuild-android-64: 0.14.47
+ esbuild-android-arm64: 0.14.47
+ esbuild-darwin-64: 0.14.47
+ esbuild-darwin-arm64: 0.14.47
+ esbuild-freebsd-64: 0.14.47
+ esbuild-freebsd-arm64: 0.14.47
+ esbuild-linux-32: 0.14.47
+ esbuild-linux-64: 0.14.47
+ esbuild-linux-arm: 0.14.47
+ esbuild-linux-arm64: 0.14.47
+ esbuild-linux-mips64le: 0.14.47
+ esbuild-linux-ppc64le: 0.14.47
+ esbuild-linux-riscv64: 0.14.47
+ esbuild-linux-s390x: 0.14.47
+ esbuild-netbsd-64: 0.14.47
+ esbuild-openbsd-64: 0.14.47
+ esbuild-sunos-64: 0.14.47
+ esbuild-windows-32: 0.14.47
+ esbuild-windows-64: 0.14.47
+ esbuild-windows-arm64: 0.14.47
+
+ esbuild@0.15.18:
+ optionalDependencies:
+ '@esbuild/android-arm': 0.15.18
+ '@esbuild/linux-loong64': 0.15.18
+ esbuild-android-64: 0.15.18
+ esbuild-android-arm64: 0.15.18
+ esbuild-darwin-64: 0.15.18
+ esbuild-darwin-arm64: 0.15.18
+ esbuild-freebsd-64: 0.15.18
+ esbuild-freebsd-arm64: 0.15.18
+ esbuild-linux-32: 0.15.18
+ esbuild-linux-64: 0.15.18
+ esbuild-linux-arm: 0.15.18
+ esbuild-linux-arm64: 0.15.18
+ esbuild-linux-mips64le: 0.15.18
+ esbuild-linux-ppc64le: 0.15.18
+ esbuild-linux-riscv64: 0.15.18
+ esbuild-linux-s390x: 0.15.18
+ esbuild-netbsd-64: 0.15.18
+ esbuild-openbsd-64: 0.15.18
+ esbuild-sunos-64: 0.15.18
+ esbuild-windows-32: 0.15.18
+ esbuild-windows-64: 0.15.18
+ esbuild-windows-arm64: 0.15.18
+
+ esbuild@0.25.4:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.4
+ '@esbuild/android-arm': 0.25.4
+ '@esbuild/android-arm64': 0.25.4
+ '@esbuild/android-x64': 0.25.4
+ '@esbuild/darwin-arm64': 0.25.4
+ '@esbuild/darwin-x64': 0.25.4
+ '@esbuild/freebsd-arm64': 0.25.4
+ '@esbuild/freebsd-x64': 0.25.4
+ '@esbuild/linux-arm': 0.25.4
+ '@esbuild/linux-arm64': 0.25.4
+ '@esbuild/linux-ia32': 0.25.4
+ '@esbuild/linux-loong64': 0.25.4
+ '@esbuild/linux-mips64el': 0.25.4
+ '@esbuild/linux-ppc64': 0.25.4
+ '@esbuild/linux-riscv64': 0.25.4
+ '@esbuild/linux-s390x': 0.25.4
+ '@esbuild/linux-x64': 0.25.4
+ '@esbuild/netbsd-arm64': 0.25.4
+ '@esbuild/netbsd-x64': 0.25.4
+ '@esbuild/openbsd-arm64': 0.25.4
+ '@esbuild/openbsd-x64': 0.25.4
+ '@esbuild/sunos-x64': 0.25.4
+ '@esbuild/win32-arm64': 0.25.4
+ '@esbuild/win32-ia32': 0.25.4
+ '@esbuild/win32-x64': 0.25.4
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@2.0.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ escodegen@2.1.0:
+ dependencies:
+ esprima: 4.0.1
+ estraverse: 5.3.0
+ esutils: 2.0.3
+ optionalDependencies:
+ source-map: 0.6.1
+
+ eslint-config-next@14.2.30(eslint@8.57.1)(typescript@4.9.5):
+ dependencies:
+ '@next/eslint-plugin-next': 14.2.30
+ '@rushstack/eslint-patch': 1.11.0
+ '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ eslint: 8.57.1
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
+ eslint-plugin-react: 7.37.5(eslint@8.57.1)
+ eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
+ optionalDependencies:
+ typescript: 4.9.5
+ transitivePeerDependencies:
+ - eslint-import-resolver-webpack
+ - eslint-plugin-import-x
+ - supports-color
+
+ eslint-config-prettier@8.10.0(eslint@8.57.1):
+ dependencies:
+ eslint: 8.57.1
+
+ eslint-import-resolver-node@0.3.9:
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.16.1
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
+ dependencies:
+ '@nolyfill/is-core-module': 1.0.39
+ debug: 4.4.1(supports-color@9.4.0)
+ eslint: 8.57.1
+ get-tsconfig: 4.10.1
+ is-bun-module: 2.0.0
+ stable-hash: 0.0.5
+ tinyglobby: 0.2.14
+ unrs-resolver: 1.9.0
+ optionalDependencies:
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ eslint: 8.57.1
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
+ dependencies:
+ '@rtsao/scc': 1.1.0
+ array-includes: 3.1.9
+ array.prototype.findlastindex: 1.2.6
+ array.prototype.flat: 1.3.3
+ array.prototype.flatmap: 1.3.3
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 8.57.1
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
+ hasown: 2.0.2
+ is-core-module: 2.16.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.8
+ object.groupby: 1.0.3
+ object.values: 1.2.1
+ semver: 6.3.1
+ string.prototype.trimend: 1.0.9
+ tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1):
+ dependencies:
+ aria-query: 5.3.2
+ array-includes: 3.1.9
+ array.prototype.flatmap: 1.3.3
+ ast-types-flow: 0.0.8
+ axe-core: 4.10.3
+ axobject-query: 4.1.0
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ eslint: 8.57.1
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.2
+ object.fromentries: 2.0.8
+ safe-regex-test: 1.1.0
+ string.prototype.includes: 2.0.1
+
+ eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1):
+ dependencies:
+ eslint: 8.57.1
+
+ eslint-plugin-react@7.37.5(eslint@8.57.1):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.2.1
+ eslint: 8.57.1
+ estraverse: 5.3.0
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.2
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.5
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-plugin-simple-import-sort@7.0.0(eslint@8.57.1):
+ dependencies:
+ eslint: 8.57.1
+
+ eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1):
+ dependencies:
+ eslint: 8.57.1
+ eslint-rule-composer: 0.3.0
+ optionalDependencies:
+ '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
+
+ eslint-rule-composer@0.3.0: {}
+
+ eslint-scope@5.1.1:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 4.3.0
+
+ eslint-scope@7.2.2:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint@8.57.1:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/eslintrc': 2.1.4
+ '@eslint/js': 8.57.1
+ '@humanwhocodes/config-array': 0.13.0
+ '@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
+ '@ungap/structured-clone': 1.3.0
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.1(supports-color@9.4.0)
+ doctrine: 3.0.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 6.0.1
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ globals: 13.24.0
+ graphemer: 1.4.0
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-yaml: 4.1.0
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ strip-ansi: 6.0.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@9.6.1:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 3.4.3
+
+ esprima@4.0.1: {}
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@4.3.0: {}
+
+ estraverse@5.3.0: {}
+
+ estree-walker@1.0.1: {}
+
+ estree-walker@2.0.2: {}
+
+ esutils@2.0.3: {}
+
+ etag@1.8.1: {}
+
+ events-intercept@2.0.0: {}
+
+ events@3.3.0: {}
+
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ exit-hook@2.2.1: {}
+
+ exit@0.1.2: {}
+
+ expect@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ jest-get-type: 27.5.1
+ jest-matcher-utils: 27.5.1
+ jest-message-util: 27.5.1
+
+ expect@30.0.0:
+ dependencies:
+ '@jest/expect-utils': 30.0.0
+ '@jest/get-type': 30.0.0
+ jest-matcher-utils: 30.0.0
+ jest-message-util: 30.0.0
+ jest-mock: 30.0.0
+ jest-util: 30.0.0
+
+ exsolve@1.0.7: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fast-uri@3.0.6: {}
+
+ fastq@1.19.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fb-watchman@2.0.2:
+ dependencies:
+ bser: 2.1.1
+
+ fd-slicer@1.1.0:
+ dependencies:
+ pend: 1.2.0
+
+ fdir@6.4.6(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
+ file-entry-cache@6.0.1:
+ dependencies:
+ flat-cache: 3.2.0
+
+ file-uri-to-path@1.0.0: {}
+
+ filelist@1.0.4:
+ dependencies:
+ minimatch: 5.1.6
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-cache-dir@3.3.2:
+ dependencies:
+ commondir: 1.0.1
+ make-dir: 3.1.0
+ pkg-dir: 4.2.0
+
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@3.2.0:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+ rimraf: 3.0.2
+
+ flatted@3.3.3: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data@3.0.3:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ mime-types: 2.1.35
+
+ fraction.js@4.3.7: {}
+
+ framer-motion@12.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ motion-dom: 12.18.1
+ motion-utils: 12.18.1
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+
+ fs-extra@11.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+
+ fs-extra@9.1.0:
+ dependencies:
+ at-least-node: 1.0.0
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+
+ fs-minipass@2.1.0:
+ dependencies:
+ minipass: 3.3.6
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generic-pool@3.4.2: {}
+
+ generic-pool@3.9.0: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-own-enumerable-property-symbols@3.0.2: {}
+
+ get-package-type@0.1.0: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-source@2.0.12:
+ dependencies:
+ data-uri-to-buffer: 2.0.2
+ source-map: 0.6.1
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-tsconfig@4.10.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ git-raw-commits@2.0.11:
+ dependencies:
+ dargs: 7.0.0
+ lodash: 4.17.21
+ meow: 8.1.2
+ split2: 3.2.2
+ through2: 4.0.2
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-to-regexp@0.4.1: {}
+
+ glob@10.3.10:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 2.3.6
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ path-scurry: 1.11.1
+
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ global-dirs@0.1.1:
+ dependencies:
+ ini: 1.3.8
+
+ globals@11.12.0: {}
+
+ globals@13.24.0:
+ dependencies:
+ type-fest: 0.20.2
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ globby@6.1.0:
+ dependencies:
+ array-union: 1.0.2
+ glob: 7.2.3
+ object-assign: 4.1.1
+ pify: 2.3.0
+ pinkie-promise: 2.0.1
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ graphemer@1.4.0: {}
+
+ hard-rejection@2.1.0: {}
+
+ has-bigints@1.1.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hls.js@1.6.6: {}
+
+ hosted-git-info@2.8.9: {}
+
+ hosted-git-info@4.1.0:
+ dependencies:
+ lru-cache: 6.0.0
+
+ html-encoding-sniffer@2.0.1:
+ dependencies:
+ whatwg-encoding: 1.0.5
+
+ html-escaper@2.0.2: {}
+
+ http-errors@1.4.0:
+ dependencies:
+ inherits: 2.0.1
+ statuses: 1.5.0
+
+ http-errors@1.7.3:
+ dependencies:
+ depd: 1.1.2
+ inherits: 2.0.4
+ setprototypeof: 1.1.1
+ statuses: 1.5.0
+ toidentifier: 1.0.0
+
+ http-proxy-agent@4.0.1:
+ dependencies:
+ '@tootallnate/once': 1.1.2
+ agent-base: 6.0.2
+ debug: 4.4.1(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@5.0.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.4.1(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.4.1(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@2.1.0: {}
+
+ husky@7.0.4: {}
+
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ idb@7.1.1: {}
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ import-local@3.2.0:
+ dependencies:
+ pkg-dir: 4.2.0
+ resolve-cwd: 3.0.0
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@4.0.0: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.1: {}
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-arrayish@0.2.1: {}
+
+ is-arrayish@0.3.2: {}
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-buffer@2.0.5: {}
+
+ is-bun-module@2.0.0:
+ dependencies:
+ semver: 7.7.2
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-fullwidth-code-point@4.0.0: {}
+
+ is-generator-fn@2.1.0: {}
+
+ is-generator-function@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-map@2.0.3: {}
+
+ is-module@1.0.0: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-node-process@1.2.0: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-obj@1.0.1: {}
+
+ is-obj@2.0.0: {}
+
+ is-path-cwd@2.2.0: {}
+
+ is-path-in-cwd@2.1.0:
+ dependencies:
+ is-path-inside: 2.1.0
+
+ is-path-inside@2.1.0:
+ dependencies:
+ path-is-inside: 1.0.2
+
+ is-path-inside@3.0.3: {}
+
+ is-plain-obj@1.1.0: {}
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-regexp@1.0.0: {}
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@2.0.1: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-text-path@1.0.1:
+ dependencies:
+ text-extensions: 1.9.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.19
+
+ is-typedarray@1.0.0: {}
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@0.0.1: {}
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-instrument@5.2.1:
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/parser': 7.27.5
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-coverage: 3.2.2
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@4.0.1:
+ dependencies:
+ debug: 4.4.1(supports-color@9.4.0)
+ istanbul-lib-coverage: 3.2.2
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.1.7:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jackspeak@2.3.6:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jake@10.9.2:
+ dependencies:
+ async: 3.2.6
+ chalk: 4.1.2
+ filelist: 1.0.4
+ minimatch: 3.1.2
+
+ jest-changed-files@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ execa: 5.1.1
+ throat: 6.0.2
+
+ jest-circus@27.5.1:
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ co: 4.6.0
+ dedent: 0.7.0
+ expect: 27.5.1
+ is-generator-fn: 2.1.0
+ jest-each: 27.5.1
+ jest-matcher-utils: 27.5.1
+ jest-message-util: 27.5.1
+ jest-runtime: 27.5.1
+ jest-snapshot: 27.5.1
+ jest-util: 27.5.1
+ pretty-format: 27.5.1
+ slash: 3.0.0
+ stack-utils: 2.0.6
+ throat: 6.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-cli@27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)):
+ dependencies:
+ '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ '@jest/test-result': 27.5.1
+ '@jest/types': 27.5.1
+ chalk: 4.1.2
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ import-local: 3.2.0
+ jest-config: 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ jest-util: 27.5.1
+ jest-validate: 27.5.1
+ prompts: 2.4.2
+ yargs: 16.2.0
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - ts-node
+ - utf-8-validate
+
+ jest-config@27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)):
+ dependencies:
+ '@babel/core': 7.27.4
+ '@jest/test-sequencer': 27.5.1
+ '@jest/types': 27.5.1
+ babel-jest: 27.5.1(@babel/core@7.27.4)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 27.5.1
+ jest-environment-jsdom: 27.5.1
+ jest-environment-node: 27.5.1
+ jest-get-type: 27.5.1
+ jest-jasmine2: 27.5.1
+ jest-regex-util: 27.5.1
+ jest-resolve: 27.5.1
+ jest-runner: 27.5.1
+ jest-util: 27.5.1
+ jest-validate: 27.5.1
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 27.5.1
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ ts-node: 10.9.2(@types/node@24.0.3)(typescript@4.9.5)
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - utf-8-validate
+
+ jest-diff@27.5.1:
+ dependencies:
+ chalk: 4.1.2
+ diff-sequences: 27.5.1
+ jest-get-type: 27.5.1
+ pretty-format: 27.5.1
+
+ jest-diff@30.0.0:
+ dependencies:
+ '@jest/diff-sequences': 30.0.0
+ '@jest/get-type': 30.0.0
+ chalk: 4.1.2
+ pretty-format: 30.0.0
+
+ jest-docblock@27.5.1:
+ dependencies:
+ detect-newline: 3.1.0
+
+ jest-each@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ chalk: 4.1.2
+ jest-get-type: 27.5.1
+ jest-util: 27.5.1
+ pretty-format: 27.5.1
+
+ jest-environment-jsdom@27.5.1:
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/fake-timers': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ jest-mock: 27.5.1
+ jest-util: 27.5.1
+ jsdom: 16.7.0
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - utf-8-validate
+
+ jest-environment-node@27.5.1:
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/fake-timers': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ jest-mock: 27.5.1
+ jest-util: 27.5.1
+
+ jest-get-type@27.5.1: {}
+
+ jest-haste-map@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ '@types/graceful-fs': 4.1.9
+ '@types/node': 24.0.3
+ anymatch: 3.1.3
+ fb-watchman: 2.0.2
+ graceful-fs: 4.2.11
+ jest-regex-util: 27.5.1
+ jest-serializer: 27.5.1
+ jest-util: 27.5.1
+ jest-worker: 27.5.1
+ micromatch: 4.0.8
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-jasmine2@27.5.1:
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/source-map': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ co: 4.6.0
+ expect: 27.5.1
+ is-generator-fn: 2.1.0
+ jest-each: 27.5.1
+ jest-matcher-utils: 27.5.1
+ jest-message-util: 27.5.1
+ jest-runtime: 27.5.1
+ jest-snapshot: 27.5.1
+ jest-util: 27.5.1
+ pretty-format: 27.5.1
+ throat: 6.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-leak-detector@27.5.1:
+ dependencies:
+ jest-get-type: 27.5.1
+ pretty-format: 27.5.1
+
+ jest-matcher-utils@27.5.1:
+ dependencies:
+ chalk: 4.1.2
+ jest-diff: 27.5.1
+ jest-get-type: 27.5.1
+ pretty-format: 27.5.1
+
+ jest-matcher-utils@30.0.0:
+ dependencies:
+ '@jest/get-type': 30.0.0
+ chalk: 4.1.2
+ jest-diff: 30.0.0
+ pretty-format: 30.0.0
+
+ jest-message-util@27.5.1:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@jest/types': 27.5.1
+ '@types/stack-utils': 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 27.5.1
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-message-util@30.0.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@jest/types': 30.0.0
+ '@types/stack-utils': 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 30.0.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-mock@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+
+ jest-mock@30.0.0:
+ dependencies:
+ '@jest/types': 30.0.0
+ '@types/node': 24.0.3
+ jest-util: 30.0.0
+
+ jest-pnp-resolver@1.2.3(jest-resolve@27.5.1):
+ optionalDependencies:
+ jest-resolve: 27.5.1
+
+ jest-regex-util@27.5.1: {}
+
+ jest-regex-util@30.0.0: {}
+
+ jest-resolve-dependencies@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ jest-regex-util: 27.5.1
+ jest-snapshot: 27.5.1
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-resolve@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ jest-haste-map: 27.5.1
+ jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1)
+ jest-util: 27.5.1
+ jest-validate: 27.5.1
+ resolve: 1.22.10
+ resolve.exports: 1.1.1
+ slash: 3.0.0
+
+ jest-runner@27.5.1:
+ dependencies:
+ '@jest/console': 27.5.1
+ '@jest/environment': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ emittery: 0.8.1
+ graceful-fs: 4.2.11
+ jest-docblock: 27.5.1
+ jest-environment-jsdom: 27.5.1
+ jest-environment-node: 27.5.1
+ jest-haste-map: 27.5.1
+ jest-leak-detector: 27.5.1
+ jest-message-util: 27.5.1
+ jest-resolve: 27.5.1
+ jest-runtime: 27.5.1
+ jest-util: 27.5.1
+ jest-worker: 27.5.1
+ source-map-support: 0.5.21
+ throat: 6.0.2
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - utf-8-validate
+
+ jest-runtime@27.5.1:
+ dependencies:
+ '@jest/environment': 27.5.1
+ '@jest/fake-timers': 27.5.1
+ '@jest/globals': 27.5.1
+ '@jest/source-map': 27.5.1
+ '@jest/test-result': 27.5.1
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ chalk: 4.1.2
+ cjs-module-lexer: 1.4.3
+ collect-v8-coverage: 1.0.2
+ execa: 5.1.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-haste-map: 27.5.1
+ jest-message-util: 27.5.1
+ jest-mock: 27.5.1
+ jest-regex-util: 27.5.1
+ jest-resolve: 27.5.1
+ jest-snapshot: 27.5.1
+ jest-util: 27.5.1
+ slash: 3.0.0
+ strip-bom: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-serializer@27.5.1:
+ dependencies:
+ '@types/node': 24.0.3
+ graceful-fs: 4.2.11
+
+ jest-snapshot@27.5.1:
+ dependencies:
+ '@babel/core': 7.27.4
+ '@babel/generator': 7.27.5
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4)
+ '@babel/traverse': 7.27.4
+ '@babel/types': 7.27.6
+ '@jest/transform': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/babel__traverse': 7.20.7
+ '@types/prettier': 2.7.3
+ babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4)
+ chalk: 4.1.2
+ expect: 27.5.1
+ graceful-fs: 4.2.11
+ jest-diff: 27.5.1
+ jest-get-type: 27.5.1
+ jest-haste-map: 27.5.1
+ jest-matcher-utils: 27.5.1
+ jest-message-util: 27.5.1
+ jest-util: 27.5.1
+ natural-compare: 1.4.0
+ pretty-format: 27.5.1
+ semver: 7.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-util@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ graceful-fs: 4.2.11
+ picomatch: 2.3.1
+
+ jest-util@30.0.0:
+ dependencies:
+ '@jest/types': 30.0.0
+ '@types/node': 24.0.3
+ chalk: 4.1.2
+ ci-info: 4.2.0
+ graceful-fs: 4.2.11
+ picomatch: 4.0.2
+
+ jest-validate@27.5.1:
+ dependencies:
+ '@jest/types': 27.5.1
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ jest-get-type: 27.5.1
+ leven: 3.1.0
+ pretty-format: 27.5.1
+
+ jest-watcher@27.5.1:
+ dependencies:
+ '@jest/test-result': 27.5.1
+ '@jest/types': 27.5.1
+ '@types/node': 24.0.3
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ jest-util: 27.5.1
+ string-length: 4.0.2
+
+ jest-worker@26.6.2:
+ dependencies:
+ '@types/node': 24.0.3
+ merge-stream: 2.0.0
+ supports-color: 7.2.0
+
+ jest-worker@27.5.1:
+ dependencies:
+ '@types/node': 24.0.3
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest@27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)):
+ dependencies:
+ '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ import-local: 3.2.0
+ jest-cli: 27.5.1(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - ts-node
+ - utf-8-validate
+
+ jiti@1.21.7: {}
+
+ jose@5.9.6: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@3.14.1:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsdom@16.7.0:
+ dependencies:
+ abab: 2.0.6
+ acorn: 8.15.0
+ acorn-globals: 6.0.0
+ cssom: 0.4.4
+ cssstyle: 2.3.0
+ data-urls: 2.0.0
+ decimal.js: 10.5.0
+ domexception: 2.0.1
+ escodegen: 2.1.0
+ form-data: 3.0.3
+ html-encoding-sniffer: 2.0.1
+ http-proxy-agent: 4.0.1
+ https-proxy-agent: 5.0.1
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.20
+ parse5: 6.0.1
+ saxes: 5.0.1
+ symbol-tree: 3.2.4
+ tough-cookie: 4.1.4
+ w3c-hr-time: 1.0.2
+ w3c-xmlserializer: 2.0.0
+ webidl-conversions: 6.1.0
+ whatwg-encoding: 1.0.5
+ whatwg-mimetype: 2.3.0
+ whatwg-url: 8.7.0
+ ws: 7.5.10
+ xml-name-validator: 3.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsesc@3.0.2: {}
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json-schema-to-ts@1.6.4:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ ts-toolbelt: 6.15.5
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-schema@0.4.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ json5@2.2.3: {}
+
+ jsonfile@6.1.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsonparse@1.3.1: {}
+
+ jsonpointer@5.0.1: {}
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ kind-of@6.0.3: {}
+
+ kleur@3.0.3: {}
+
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
+ leven@3.1.0: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lilconfig@2.0.5: {}
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ lint-staged@12.5.0:
+ dependencies:
+ cli-truncate: 3.1.0
+ colorette: 2.0.20
+ commander: 9.5.0
+ debug: 4.4.1(supports-color@9.4.0)
+ execa: 5.1.1
+ lilconfig: 2.0.5
+ listr2: 4.0.5
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-inspect: 1.13.4
+ pidtree: 0.5.0
+ string-argv: 0.3.2
+ supports-color: 9.4.0
+ yaml: 1.10.2
+ transitivePeerDependencies:
+ - enquirer
+
+ listr2@4.0.5:
+ dependencies:
+ cli-truncate: 2.1.0
+ colorette: 2.0.20
+ log-update: 4.0.0
+ p-map: 4.0.0
+ rfdc: 1.4.1
+ rxjs: 7.8.2
+ through: 2.3.8
+ wrap-ansi: 7.0.0
+
+ loader-runner@4.3.0: {}
+
+ loader-utils@2.0.4:
+ dependencies:
+ big.js: 5.2.2
+ emojis-list: 3.0.0
+ json5: 2.2.3
+
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.debounce@4.0.8: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash.sortby@4.7.0: {}
+
+ lodash@4.17.21: {}
+
+ log-update@4.0.0:
+ dependencies:
+ ansi-escapes: 4.3.2
+ cli-cursor: 3.1.0
+ slice-ansi: 4.0.0
+ wrap-ansi: 6.2.0
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lower-case@2.0.2:
+ dependencies:
+ tslib: 2.8.1
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+
+ lucide-react@0.438.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ lz-string@1.5.0: {}
+
+ magic-string@0.25.9:
+ dependencies:
+ sourcemap-codec: 1.4.8
+
+ make-dir@3.1.0:
+ dependencies:
+ semver: 6.3.1
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.7.2
+
+ make-error@1.3.6: {}
+
+ makeerror@1.0.12:
+ dependencies:
+ tmpl: 1.0.5
+
+ map-obj@1.0.1: {}
+
+ map-obj@4.3.0: {}
+
+ math-intrinsics@1.1.0: {}
+
+ maverick.js@0.37.0:
+ dependencies:
+ '@maverick-js/signals': 5.11.5
+ type-fest: 3.13.1
+
+ mdn-data@2.0.28: {}
+
+ mdn-data@2.0.30: {}
+
+ media-captions@0.0.18: {}
+
+ media-captions@1.0.4: {}
+
+ media-icons@1.1.5: {}
+
+ meow@8.1.2:
+ dependencies:
+ '@types/minimist': 1.2.5
+ camelcase-keys: 6.2.2
+ decamelize-keys: 1.1.1
+ hard-rejection: 2.1.0
+ minimist-options: 4.1.0
+ normalize-package-data: 3.0.3
+ read-pkg-up: 7.0.1
+ redent: 3.0.0
+ trim-newlines: 3.0.1
+ type-fest: 0.18.1
+ yargs-parser: 20.2.9
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micro@9.3.5-canary.3:
+ dependencies:
+ arg: 4.1.0
+ content-type: 1.0.4
+ raw-body: 2.4.1
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime@3.0.0: {}
+
+ mimic-fn@2.1.0: {}
+
+ min-indent@1.0.1: {}
+
+ mini-svg-data-uri@1.4.4: {}
+
+ miniflare@3.20250408.2:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ acorn: 8.14.0
+ acorn-walk: 8.3.2
+ exit-hook: 2.2.1
+ glob-to-regexp: 0.4.1
+ stoppable: 1.1.0
+ undici: 5.29.0
+ workerd: 1.20250408.0
+ ws: 8.18.0
+ youch: 3.3.4
+ zod: 3.22.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ miniflare@4.20250617.4:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ acorn: 8.14.0
+ acorn-walk: 8.3.2
+ exit-hook: 2.2.1
+ glob-to-regexp: 0.4.1
+ sharp: 0.33.5
+ stoppable: 1.1.0
+ undici: 5.29.0
+ workerd: 1.20250617.0
+ ws: 8.18.0
+ youch: 3.3.4
+ zod: 3.22.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimatch@5.1.6:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist-options@4.1.0:
+ dependencies:
+ arrify: 1.0.1
+ is-plain-obj: 1.1.0
+ kind-of: 6.0.3
+
+ minimist@1.2.8: {}
+
+ minipass@3.3.6:
+ dependencies:
+ yallist: 4.0.0
+
+ minipass@5.0.0: {}
+
+ minipass@7.1.2: {}
+
+ minizlib@2.1.2:
+ dependencies:
+ minipass: 3.3.6
+ yallist: 4.0.0
+
+ minizlib@3.0.2:
+ dependencies:
+ minipass: 7.1.2
+
+ mkdirp@1.0.4: {}
+
+ mkdirp@3.0.1: {}
+
+ motion-dom@12.18.1:
+ dependencies:
+ motion-utils: 12.18.1
+
+ motion-utils@12.18.1: {}
+
+ mri@1.2.0: {}
+
+ ms@2.1.1: {}
+
+ ms@2.1.2: {}
+
+ ms@2.1.3: {}
+
+ mustache@4.2.0: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.11: {}
+
+ napi-postinstall@0.2.4: {}
+
+ natural-compare-lite@1.4.0: {}
+
+ natural-compare@1.4.0: {}
+
+ neo-async@2.6.2: {}
+
+ next-pwa@5.6.0(@babel/core@7.27.4)(@types/babel__core@7.20.5)(next@14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.99.9):
+ dependencies:
+ babel-loader: 8.4.1(@babel/core@7.27.4)(webpack@5.99.9)
+ clean-webpack-plugin: 4.0.0(webpack@5.99.9)
+ globby: 11.1.0
+ next: 14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ terser-webpack-plugin: 5.3.14(webpack@5.99.9)
+ workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.99.9)
+ workbox-window: 6.6.0
+ transitivePeerDependencies:
+ - '@babel/core'
+ - '@swc/core'
+ - '@types/babel__core'
+ - esbuild
+ - supports-color
+ - uglify-js
+ - webpack
+
+ next-router-mock@0.9.13(next@14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
+ dependencies:
+ next: 14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+
+ next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ next@14.2.30(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@next/env': 14.2.30
+ '@swc/helpers': 0.5.5
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001723
+ graceful-fs: 4.2.11
+ postcss: 8.4.31
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ styled-jsx: 5.1.1(@babel/core@7.27.4)(react@18.3.1)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 14.2.30
+ '@next/swc-darwin-x64': 14.2.30
+ '@next/swc-linux-arm64-gnu': 14.2.30
+ '@next/swc-linux-arm64-musl': 14.2.30
+ '@next/swc-linux-x64-gnu': 14.2.30
+ '@next/swc-linux-x64-musl': 14.2.30
+ '@next/swc-win32-arm64-msvc': 14.2.30
+ '@next/swc-win32-ia32-msvc': 14.2.30
+ '@next/swc-win32-x64-msvc': 14.2.30
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ no-case@3.0.4:
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.8.1
+
+ node-fetch@2.6.7:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-fetch@2.6.9:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-gyp-build@4.8.4: {}
+
+ node-int64@0.4.0: {}
+
+ node-releases@2.0.19: {}
+
+ nopt@8.1.0:
+ dependencies:
+ abbrev: 3.0.1
+
+ normalize-package-data@2.5.0:
+ dependencies:
+ hosted-git-info: 2.8.9
+ resolve: 1.22.10
+ semver: 5.7.2
+ validate-npm-package-license: 3.0.4
+
+ normalize-package-data@3.0.3:
+ dependencies:
+ hosted-git-info: 4.1.0
+ is-core-module: 2.16.1
+ semver: 7.7.2
+ validate-npm-package-license: 3.0.4
+
+ normalize-path@3.0.0: {}
+
+ normalize-range@0.1.2: {}
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
+ nwsapi@2.2.20: {}
+
+ object-assign@4.1.1: {}
+
+ object-hash@3.0.0: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-object-atoms: 1.1.1
+
+ object.groupby@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ ohash@2.0.11: {}
+
+ once@1.3.3:
+ dependencies:
+ wrappy: 1.0.2
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ option-validator@2.0.6:
+ dependencies:
+ kind-of: 6.0.3
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ os-paths@4.4.0: {}
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ p-map@2.1.0: {}
+
+ p-map@4.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+
+ p-try@2.2.0: {}
+
+ package-json-from-dist@1.0.1: {}
+
+ package-manager-manager@0.2.0:
+ dependencies:
+ js-yaml: 4.1.0
+ shellac: 0.8.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ error-ex: 1.3.2
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parse-ms@2.1.0: {}
+
+ parse5@6.0.1: {}
+
+ path-browserify@1.0.1: {}
+
+ path-exists@4.0.0: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-is-inside@1.0.2: {}
+
+ path-key@3.1.1: {}
+
+ path-match@1.2.4:
+ dependencies:
+ http-errors: 1.4.0
+ path-to-regexp: 1.9.0
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ path-to-regexp@1.9.0:
+ dependencies:
+ isarray: 0.0.1
+
+ path-to-regexp@6.1.0: {}
+
+ path-to-regexp@6.3.0: {}
+
+ path-type@4.0.0: {}
+
+ pathe@2.0.3: {}
+
+ pcre-to-regexp@1.1.0: {}
+
+ pend@1.2.0: {}
+
+ picocolors@1.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.2: {}
+
+ pidtree@0.5.0: {}
+
+ pify@2.3.0: {}
+
+ pify@4.0.1: {}
+
+ pinkie-promise@2.0.1:
+ dependencies:
+ pinkie: 2.0.4
+
+ pinkie@2.0.4: {}
+
+ pirates@4.0.7: {}
+
+ pkg-dir@4.2.0:
+ dependencies:
+ find-up: 4.1.0
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-import@15.1.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.10
+
+ postcss-js@4.0.1(postcss@8.5.6):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.6
+
+ postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.8.0
+ optionalDependencies:
+ postcss: 8.5.6
+ ts-node: 10.9.2(@types/node@24.0.3)(typescript@4.9.5)
+
+ postcss-nested@6.2.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+ postcss-selector-parser: 6.1.2
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prettier-plugin-tailwindcss@0.5.14(prettier@2.8.8):
+ dependencies:
+ prettier: 2.8.8
+
+ prettier@2.8.8: {}
+
+ pretty-bytes@5.6.0: {}
+
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+ pretty-format@30.0.0:
+ dependencies:
+ '@jest/schemas': 30.0.0
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
+ pretty-ms@7.0.1:
+ dependencies:
+ parse-ms: 2.1.0
+
+ printable-characters@1.0.42: {}
+
+ promisepipe@3.0.0: {}
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ psl@1.15.0:
+ dependencies:
+ punycode: 2.3.1
+
+ punycode@2.3.1: {}
+
+ q@1.5.1: {}
+
+ querystringify@2.2.0: {}
+
+ queue-microtask@1.2.3: {}
+
+ quick-lru@4.0.1: {}
+
+ randombytes@2.1.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ raw-body@2.4.1:
+ dependencies:
+ bytes: 3.1.0
+ http-errors: 1.7.3
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-icons@5.5.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-is@18.3.1: {}
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ read-cache@1.0.0:
+ dependencies:
+ pify: 2.3.0
+
+ read-pkg-up@7.0.1:
+ dependencies:
+ find-up: 4.1.0
+ read-pkg: 5.2.0
+ type-fest: 0.8.1
+
+ read-pkg@5.2.0:
+ dependencies:
+ '@types/normalize-package-data': 2.4.4
+ normalize-package-data: 2.5.0
+ parse-json: 5.2.0
+ type-fest: 0.6.0
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
+ readdirp@4.1.2: {}
+
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
+ redis@4.7.1:
+ dependencies:
+ '@redis/bloom': 1.2.0(@redis/client@1.6.1)
+ '@redis/client': 1.6.1
+ '@redis/graph': 1.1.1(@redis/client@1.6.1)
+ '@redis/json': 1.0.7(@redis/client@1.6.1)
+ '@redis/search': 1.2.0(@redis/client@1.6.1)
+ '@redis/time-series': 1.1.0(@redis/client@1.6.1)
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regenerate-unicode-properties@10.2.0:
+ dependencies:
+ regenerate: 1.4.2
+
+ regenerate@1.4.2: {}
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ regexpu-core@6.2.0:
+ dependencies:
+ regenerate: 1.4.2
+ regenerate-unicode-properties: 10.2.0
+ regjsgen: 0.8.0
+ regjsparser: 0.12.0
+ unicode-match-property-ecmascript: 2.0.0
+ unicode-match-property-value-ecmascript: 2.2.0
+
+ reghex@1.0.2: {}
+
+ regjsgen@0.8.0: {}
+
+ regjsparser@0.12.0:
+ dependencies:
+ jsesc: 3.0.2
+
+ require-directory@2.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ requires-port@1.0.0: {}
+
+ resolve-cwd@3.0.0:
+ dependencies:
+ resolve-from: 5.0.0
+
+ resolve-from@4.0.0: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve-global@1.0.0:
+ dependencies:
+ global-dirs: 0.1.1
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve.exports@1.1.1: {}
+
+ resolve@1.22.10:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.5:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ restore-cursor@3.1.0:
+ dependencies:
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+
+ retry@0.13.1: {}
+
+ reusify@1.1.0: {}
+
+ rfdc@1.4.1: {}
+
+ rimraf@2.7.1:
+ dependencies:
+ glob: 7.2.3
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+
+ rollup-plugin-terser@7.0.2(rollup@2.79.2):
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ jest-worker: 26.6.2
+ rollup: 2.79.2
+ serialize-javascript: 4.0.0
+ terser: 5.43.1
+
+ rollup@2.79.2:
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
+ safe-array-concat@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safer-buffer@2.1.2: {}
+
+ saxes@5.0.1:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ schema-utils@2.7.1:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ ajv: 6.12.6
+ ajv-keywords: 3.5.2(ajv@6.12.6)
+
+ schema-utils@4.3.2:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ ajv: 8.17.1
+ ajv-formats: 2.1.1(ajv@8.17.1)
+ ajv-keywords: 5.1.0(ajv@8.17.1)
+
+ semver@5.7.2: {}
+
+ semver@6.3.1: {}
+
+ semver@7.3.7:
+ dependencies:
+ lru-cache: 6.0.0
+
+ semver@7.5.4:
+ dependencies:
+ lru-cache: 6.0.0
+
+ semver@7.7.2: {}
+
+ serialize-javascript@4.0.0:
+ dependencies:
+ randombytes: 2.1.0
+
+ serialize-javascript@6.0.2:
+ dependencies:
+ randombytes: 2.1.0
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ setprototypeof@1.1.1: {}
+
+ sharp@0.33.5:
+ dependencies:
+ color: 4.2.3
+ detect-libc: 2.0.4
+ semver: 7.7.2
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.33.5
+ '@img/sharp-darwin-x64': 0.33.5
+ '@img/sharp-libvips-darwin-arm64': 1.0.4
+ '@img/sharp-libvips-darwin-x64': 1.0.4
+ '@img/sharp-libvips-linux-arm': 1.0.5
+ '@img/sharp-libvips-linux-arm64': 1.0.4
+ '@img/sharp-libvips-linux-s390x': 1.0.4
+ '@img/sharp-libvips-linux-x64': 1.0.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.0.4
+ '@img/sharp-linux-arm': 0.33.5
+ '@img/sharp-linux-arm64': 0.33.5
+ '@img/sharp-linux-s390x': 0.33.5
+ '@img/sharp-linux-x64': 0.33.5
+ '@img/sharp-linuxmusl-arm64': 0.33.5
+ '@img/sharp-linuxmusl-x64': 0.33.5
+ '@img/sharp-wasm32': 0.33.5
+ '@img/sharp-win32-ia32': 0.33.5
+ '@img/sharp-win32-x64': 0.33.5
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ shellac@0.8.0:
+ dependencies:
+ reghex: 1.0.2
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.0.2: {}
+
+ signal-exit@4.1.0: {}
+
+ simple-swizzle@0.2.2:
+ dependencies:
+ is-arrayish: 0.3.2
+
+ sisteransi@1.0.5: {}
+
+ slash@3.0.0: {}
+
+ slice-ansi@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+
+ slice-ansi@4.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+
+ slice-ansi@5.0.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ is-fullwidth-code-point: 4.0.0
+
+ snake-case@3.0.4:
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.8.1
+
+ source-list-map@2.0.1: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
+ source-map@0.7.4: {}
+
+ source-map@0.8.0-beta.0:
+ dependencies:
+ whatwg-url: 7.1.0
+
+ sourcemap-codec@1.4.8: {}
+
+ spdx-correct@3.2.0:
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.21
+
+ spdx-exceptions@2.5.0: {}
+
+ spdx-expression-parse@3.0.1:
+ dependencies:
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.21
+
+ spdx-license-ids@3.0.21: {}
+
+ split2@3.2.2:
+ dependencies:
+ readable-stream: 3.6.2
+
+ sprintf-js@1.0.3: {}
+
+ stable-hash@0.0.5: {}
+
+ stack-utils@2.0.6:
+ dependencies:
+ escape-string-regexp: 2.0.0
+
+ stacktracey@2.1.8:
+ dependencies:
+ as-table: 1.0.55
+ get-source: 2.0.12
+
+ stat-mode@0.3.0: {}
+
+ statuses@1.5.0: {}
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ stoppable@1.1.0: {}
+
+ stream-to-array@2.3.0:
+ dependencies:
+ any-promise: 1.3.0
+
+ stream-to-promise@2.2.0:
+ dependencies:
+ any-promise: 1.3.0
+ end-of-stream: 1.1.0
+ stream-to-array: 2.3.0
+
+ streamsearch@1.1.0: {}
+
+ string-argv@0.3.2: {}
+
+ string-length@4.0.2:
+ dependencies:
+ char-regex: 1.0.2
+ strip-ansi: 6.0.1
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.0
+
+ string.prototype.includes@2.0.1:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.0
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.0
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ stringify-object@3.3.0:
+ dependencies:
+ get-own-enumerable-property-symbols: 3.0.2
+ is-obj: 1.0.1
+ is-regexp: 1.0.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.0:
+ dependencies:
+ ansi-regex: 6.1.0
+
+ strip-bom@3.0.0: {}
+
+ strip-bom@4.0.0: {}
+
+ strip-comments@2.0.1: {}
+
+ strip-final-newline@2.0.0: {}
+
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
+ strip-json-comments@3.1.1: {}
+
+ styled-jsx@5.1.1(@babel/core@7.27.4)(react@18.3.1):
+ dependencies:
+ client-only: 0.0.1
+ react: 18.3.1
+ optionalDependencies:
+ '@babel/core': 7.27.4
+
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ ts-interface-checker: 0.1.13
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@9.4.0: {}
+
+ supports-hyperlinks@2.3.0:
+ dependencies:
+ has-flag: 4.0.0
+ supports-color: 7.2.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ svg-parser@2.0.4: {}
+
+ svgo@3.3.2:
+ dependencies:
+ '@trysound/sax': 0.2.0
+ commander: 7.2.0
+ css-select: 5.1.0
+ css-tree: 2.3.1
+ css-what: 6.1.0
+ csso: 5.0.5
+ picocolors: 1.1.1
+
+ sweetalert2@11.22.2: {}
+
+ swiper@11.2.8: {}
+
+ symbol-tree@3.2.4: {}
+
+ tabbable@6.2.0: {}
+
+ tailwind-merge@2.6.0: {}
+
+ tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5)):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-import: 15.1.0(postcss@8.5.6)
+ postcss-js: 4.0.1(postcss@8.5.6)
+ postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5))
+ postcss-nested: 6.2.0(postcss@8.5.6)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
+ tapable@2.2.2: {}
+
+ tar@6.2.1:
+ dependencies:
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ minipass: 5.0.0
+ minizlib: 2.1.2
+ mkdirp: 1.0.4
+ yallist: 4.0.0
+
+ tar@7.4.3:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.0.2
+ mkdirp: 3.0.1
+ yallist: 5.0.0
+
+ temp-dir@2.0.0: {}
+
+ tempy@0.6.0:
+ dependencies:
+ is-stream: 2.0.1
+ temp-dir: 2.0.0
+ type-fest: 0.16.0
+ unique-string: 2.0.0
+
+ terminal-link@2.1.1:
+ dependencies:
+ ansi-escapes: 4.3.2
+ supports-hyperlinks: 2.3.0
+
+ terser-webpack-plugin@5.3.14(webpack@5.99.9):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.25
+ jest-worker: 27.5.1
+ schema-utils: 4.3.2
+ serialize-javascript: 6.0.2
+ terser: 5.43.1
+ webpack: 5.99.9
+
+ terser@5.43.1:
+ dependencies:
+ '@jridgewell/source-map': 0.3.6
+ acorn: 8.15.0
+ commander: 2.20.3
+ source-map-support: 0.5.21
+
+ test-exclude@6.0.0:
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+
+ text-extensions@1.9.0: {}
+
+ text-table@0.2.0: {}
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ throat@6.0.2: {}
+
+ throttleit@2.1.0: {}
+
+ through2@4.0.2:
+ dependencies:
+ readable-stream: 3.6.2
+
+ through@2.3.8: {}
+
+ time-span@4.0.0:
+ dependencies:
+ convert-hrtime: 3.0.0
+
+ tinyexec@0.3.2: {}
+
+ tinyglobby@0.2.14:
+ dependencies:
+ fdir: 6.4.6(picomatch@4.0.2)
+ picomatch: 4.0.2
+
+ tmpl@1.0.5: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.0: {}
+
+ tough-cookie@4.1.4:
+ dependencies:
+ psl: 1.15.0
+ punycode: 2.3.1
+ universalify: 0.2.0
+ url-parse: 1.5.10
+
+ tr46@0.0.3: {}
+
+ tr46@1.0.1:
+ dependencies:
+ punycode: 2.3.1
+
+ tr46@2.1.0:
+ dependencies:
+ punycode: 2.3.1
+
+ tree-kill@1.2.2: {}
+
+ trim-newlines@3.0.1: {}
+
+ ts-interface-checker@0.1.13: {}
+
+ ts-morph@12.0.0:
+ dependencies:
+ '@ts-morph/common': 0.11.1
+ code-block-writer: 10.1.1
+
+ ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 16.18.11
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.9.5
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
+ ts-node@10.9.2(@types/node@24.0.3)(typescript@4.9.5):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 24.0.3
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 4.9.5
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
+ ts-toolbelt@6.15.5: {}
+
+ tsconfig-paths@3.15.0:
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tslib@1.14.1: {}
+
+ tslib@2.8.1: {}
+
+ tsutils@3.21.0(typescript@4.9.5):
+ dependencies:
+ tslib: 1.14.1
+ typescript: 4.9.5
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-detect@4.0.8: {}
+
+ type-fest@0.16.0: {}
+
+ type-fest@0.18.1: {}
+
+ type-fest@0.20.2: {}
+
+ type-fest@0.21.3: {}
+
+ type-fest@0.6.0: {}
+
+ type-fest@0.8.1: {}
+
+ type-fest@3.13.1: {}
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typedarray-to-buffer@3.1.5:
+ dependencies:
+ is-typedarray: 1.0.0
+
+ typescript@4.9.5: {}
+
+ ufo@1.6.1: {}
+
+ uid-promise@1.0.0: {}
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ uncrypto@0.1.3: {}
+
+ undici-types@7.8.0: {}
+
+ undici@5.28.4:
+ dependencies:
+ '@fastify/busboy': 2.1.1
+
+ undici@5.29.0:
+ dependencies:
+ '@fastify/busboy': 2.1.1
+
+ unenv@2.0.0-rc.17:
+ dependencies:
+ defu: 6.1.4
+ exsolve: 1.0.7
+ ohash: 2.0.11
+ pathe: 2.0.3
+ ufo: 1.6.1
+
+ unicode-canonical-property-names-ecmascript@2.0.1: {}
+
+ unicode-match-property-ecmascript@2.0.0:
+ dependencies:
+ unicode-canonical-property-names-ecmascript: 2.0.1
+ unicode-property-aliases-ecmascript: 2.1.0
+
+ unicode-match-property-value-ecmascript@2.2.0: {}
+
+ unicode-property-aliases-ecmascript@2.1.0: {}
+
+ unique-string@2.0.0:
+ dependencies:
+ crypto-random-string: 2.0.0
+
+ universalify@0.2.0: {}
+
+ universalify@2.0.1: {}
+
+ unpipe@1.0.0: {}
+
+ unrs-resolver@1.9.0:
+ dependencies:
+ napi-postinstall: 0.2.4
+ optionalDependencies:
+ '@unrs/resolver-binding-android-arm-eabi': 1.9.0
+ '@unrs/resolver-binding-android-arm64': 1.9.0
+ '@unrs/resolver-binding-darwin-arm64': 1.9.0
+ '@unrs/resolver-binding-darwin-x64': 1.9.0
+ '@unrs/resolver-binding-freebsd-x64': 1.9.0
+ '@unrs/resolver-binding-linux-arm-gnueabihf': 1.9.0
+ '@unrs/resolver-binding-linux-arm-musleabihf': 1.9.0
+ '@unrs/resolver-binding-linux-arm64-gnu': 1.9.0
+ '@unrs/resolver-binding-linux-arm64-musl': 1.9.0
+ '@unrs/resolver-binding-linux-ppc64-gnu': 1.9.0
+ '@unrs/resolver-binding-linux-riscv64-gnu': 1.9.0
+ '@unrs/resolver-binding-linux-riscv64-musl': 1.9.0
+ '@unrs/resolver-binding-linux-s390x-gnu': 1.9.0
+ '@unrs/resolver-binding-linux-x64-gnu': 1.9.0
+ '@unrs/resolver-binding-linux-x64-musl': 1.9.0
+ '@unrs/resolver-binding-wasm32-wasi': 1.9.0
+ '@unrs/resolver-binding-win32-arm64-msvc': 1.9.0
+ '@unrs/resolver-binding-win32-ia32-msvc': 1.9.0
+ '@unrs/resolver-binding-win32-x64-msvc': 1.9.0
+
+ upath@1.2.0: {}
+
+ update-browserslist-db@1.1.3(browserslist@4.25.0):
+ dependencies:
+ browserslist: 4.25.0
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ url-parse@1.5.10:
+ dependencies:
+ querystringify: 2.2.0
+ requires-port: 1.0.0
+
+ use-sync-external-store@1.5.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ util-deprecate@1.0.2: {}
+
+ v8-compile-cache-lib@3.0.1: {}
+
+ v8-to-istanbul@8.1.1:
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+ convert-source-map: 1.9.0
+ source-map: 0.7.4
+
+ validate-npm-package-license@3.0.4:
+ dependencies:
+ spdx-correct: 3.2.0
+ spdx-expression-parse: 3.0.1
+
+ vercel@44.2.7(rollup@2.79.2):
+ dependencies:
+ '@vercel/blob': 1.0.2
+ '@vercel/build-utils': 10.6.1
+ '@vercel/fun': 1.1.6
+ '@vercel/go': 3.2.1
+ '@vercel/hydrogen': 1.2.2
+ '@vercel/next': 4.9.2(rollup@2.79.2)
+ '@vercel/node': 5.3.0(rollup@2.79.2)
+ '@vercel/python': 4.7.2
+ '@vercel/redwood': 2.3.3(rollup@2.79.2)
+ '@vercel/remix-builder': 5.4.9(rollup@2.79.2)
+ '@vercel/ruby': 2.2.0
+ '@vercel/static-build': 2.7.10
+ chokidar: 4.0.0
+ jose: 5.9.6
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+ - encoding
+ - rollup
+ - supports-color
+
+ vidstack@0.6.15:
+ dependencies:
+ maverick.js: 0.37.0
+ media-captions: 0.0.18
+ type-fest: 3.13.1
+
+ w3c-hr-time@1.0.2:
+ dependencies:
+ browser-process-hrtime: 1.0.0
+
+ w3c-xmlserializer@2.0.0:
+ dependencies:
+ xml-name-validator: 3.0.0
+
+ walker@1.0.8:
+ dependencies:
+ makeerror: 1.0.12
+
+ watchpack@2.4.4:
+ dependencies:
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+
+ web-vitals@0.2.4: {}
+
+ webidl-conversions@3.0.1: {}
+
+ webidl-conversions@4.0.2: {}
+
+ webidl-conversions@5.0.0: {}
+
+ webidl-conversions@6.1.0: {}
+
+ webpack-sources@1.4.3:
+ dependencies:
+ source-list-map: 2.0.1
+ source-map: 0.6.1
+
+ webpack-sources@3.3.3: {}
+
+ webpack@5.99.9:
+ dependencies:
+ '@types/eslint-scope': 3.7.7
+ '@types/estree': 1.0.8
+ '@types/json-schema': 7.0.15
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/wasm-edit': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ acorn: 8.15.0
+ browserslist: 4.25.0
+ chrome-trace-event: 1.0.4
+ enhanced-resolve: 5.18.2
+ es-module-lexer: 1.7.0
+ eslint-scope: 5.1.1
+ events: 3.3.0
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+ json-parse-even-better-errors: 2.3.1
+ loader-runner: 4.3.0
+ mime-types: 2.1.35
+ neo-async: 2.6.2
+ schema-utils: 4.3.2
+ tapable: 2.2.2
+ terser-webpack-plugin: 5.3.14(webpack@5.99.9)
+ watchpack: 2.4.4
+ webpack-sources: 3.3.3
+ transitivePeerDependencies:
+ - '@swc/core'
+ - esbuild
+ - uglify-js
+
+ whatwg-encoding@1.0.5:
+ dependencies:
+ iconv-lite: 0.4.24
+
+ whatwg-mimetype@2.3.0: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ whatwg-url@7.1.0:
+ dependencies:
+ lodash.sortby: 4.7.0
+ tr46: 1.0.1
+ webidl-conversions: 4.0.2
+
+ whatwg-url@8.7.0:
+ dependencies:
+ lodash: 4.17.21
+ tr46: 2.1.0
+ webidl-conversions: 6.1.0
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.0
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.19
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.19:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ workbox-background-sync@6.6.0:
+ dependencies:
+ idb: 7.1.1
+ workbox-core: 6.6.0
+
+ workbox-broadcast-update@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-build@6.6.0(@types/babel__core@7.20.5):
+ dependencies:
+ '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1)
+ '@babel/core': 7.27.4
+ '@babel/preset-env': 7.27.2(@babel/core@7.27.4)
+ '@babel/runtime': 7.27.6
+ '@rollup/plugin-babel': 5.3.1(@babel/core@7.27.4)(@types/babel__core@7.20.5)(rollup@2.79.2)
+ '@rollup/plugin-node-resolve': 11.2.1(rollup@2.79.2)
+ '@rollup/plugin-replace': 2.4.2(rollup@2.79.2)
+ '@surma/rollup-plugin-off-main-thread': 2.2.3
+ ajv: 8.17.1
+ common-tags: 1.8.2
+ fast-json-stable-stringify: 2.1.0
+ fs-extra: 9.1.0
+ glob: 7.2.3
+ lodash: 4.17.21
+ pretty-bytes: 5.6.0
+ rollup: 2.79.2
+ rollup-plugin-terser: 7.0.2(rollup@2.79.2)
+ source-map: 0.8.0-beta.0
+ stringify-object: 3.3.0
+ strip-comments: 2.0.1
+ tempy: 0.6.0
+ upath: 1.2.0
+ workbox-background-sync: 6.6.0
+ workbox-broadcast-update: 6.6.0
+ workbox-cacheable-response: 6.6.0
+ workbox-core: 6.6.0
+ workbox-expiration: 6.6.0
+ workbox-google-analytics: 6.6.0
+ workbox-navigation-preload: 6.6.0
+ workbox-precaching: 6.6.0
+ workbox-range-requests: 6.6.0
+ workbox-recipes: 6.6.0
+ workbox-routing: 6.6.0
+ workbox-strategies: 6.6.0
+ workbox-streams: 6.6.0
+ workbox-sw: 6.6.0
+ workbox-window: 6.6.0
+ transitivePeerDependencies:
+ - '@types/babel__core'
+ - supports-color
+
+ workbox-cacheable-response@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-core@6.6.0: {}
+
+ workbox-expiration@6.6.0:
+ dependencies:
+ idb: 7.1.1
+ workbox-core: 6.6.0
+
+ workbox-google-analytics@6.6.0:
+ dependencies:
+ workbox-background-sync: 6.6.0
+ workbox-core: 6.6.0
+ workbox-routing: 6.6.0
+ workbox-strategies: 6.6.0
+
+ workbox-navigation-preload@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-precaching@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+ workbox-routing: 6.6.0
+ workbox-strategies: 6.6.0
+
+ workbox-range-requests@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-recipes@6.6.0:
+ dependencies:
+ workbox-cacheable-response: 6.6.0
+ workbox-core: 6.6.0
+ workbox-expiration: 6.6.0
+ workbox-precaching: 6.6.0
+ workbox-routing: 6.6.0
+ workbox-strategies: 6.6.0
+
+ workbox-routing@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-strategies@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+
+ workbox-streams@6.6.0:
+ dependencies:
+ workbox-core: 6.6.0
+ workbox-routing: 6.6.0
+
+ workbox-sw@6.6.0: {}
+
+ workbox-webpack-plugin@6.6.0(@types/babel__core@7.20.5)(webpack@5.99.9):
+ dependencies:
+ fast-json-stable-stringify: 2.1.0
+ pretty-bytes: 5.6.0
+ upath: 1.2.0
+ webpack: 5.99.9
+ webpack-sources: 1.4.3
+ workbox-build: 6.6.0(@types/babel__core@7.20.5)
+ transitivePeerDependencies:
+ - '@types/babel__core'
+ - supports-color
+
+ workbox-window@6.6.0:
+ dependencies:
+ '@types/trusted-types': 2.0.7
+ workbox-core: 6.6.0
+
+ workerd@1.20250408.0:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20250408.0
+ '@cloudflare/workerd-darwin-arm64': 1.20250408.0
+ '@cloudflare/workerd-linux-64': 1.20250408.0
+ '@cloudflare/workerd-linux-arm64': 1.20250408.0
+ '@cloudflare/workerd-windows-64': 1.20250408.0
+
+ workerd@1.20250617.0:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20250617.0
+ '@cloudflare/workerd-darwin-arm64': 1.20250617.0
+ '@cloudflare/workerd-linux-64': 1.20250617.0
+ '@cloudflare/workerd-linux-arm64': 1.20250617.0
+ '@cloudflare/workerd-windows-64': 1.20250617.0
+
+ wrangler@4.22.0:
+ dependencies:
+ '@cloudflare/kv-asset-handler': 0.4.0
+ '@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250617.0)
+ blake3-wasm: 2.1.5
+ esbuild: 0.25.4
+ miniflare: 4.20250617.4
+ path-to-regexp: 6.3.0
+ unenv: 2.0.0-rc.17
+ workerd: 1.20250617.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ wrap-ansi@6.2.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 5.1.2
+ strip-ansi: 7.1.0
+
+ wrappy@1.0.2: {}
+
+ write-file-atomic@3.0.3:
+ dependencies:
+ imurmurhash: 0.1.4
+ is-typedarray: 1.0.0
+ signal-exit: 3.0.7
+ typedarray-to-buffer: 3.1.5
+
+ ws@7.5.10: {}
+
+ ws@8.18.0: {}
+
+ xdg-app-paths@5.1.0:
+ dependencies:
+ xdg-portable: 7.3.0
+
+ xdg-portable@7.3.0:
+ dependencies:
+ os-paths: 4.4.0
+
+ xml-name-validator@3.0.0: {}
+
+ xmlchars@2.2.0: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yallist@4.0.0: {}
+
+ yallist@5.0.0: {}
+
+ yaml@1.10.2: {}
+
+ yaml@2.8.0: {}
+
+ yargs-parser@20.2.9: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@16.2.0:
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.9
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yauzl-clone@1.0.4:
+ dependencies:
+ events-intercept: 2.0.0
+
+ yauzl-promise@2.1.3:
+ dependencies:
+ yauzl: 2.10.0
+ yauzl-clone: 1.0.4
+
+ yauzl@2.10.0:
+ dependencies:
+ buffer-crc32: 0.2.13
+ fd-slicer: 1.1.0
+
+ yn@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
+
+ youch@3.3.4:
+ dependencies:
+ cookie: 0.7.2
+ mustache: 4.2.0
+ stacktracey: 2.1.8
+
+ zod@3.22.3: {}
+
+ zod@3.25.67: {}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..12a703d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/proxy.worker.js b/proxy.worker.js
new file mode 100644
index 0000000..b31c97f
--- /dev/null
+++ b/proxy.worker.js
@@ -0,0 +1,240 @@
+/* eslint-disable */
+
+addEventListener('fetch', (event) => {
+ event.respondWith(handleRequest(event.request));
+});
+
+async function handleRequest(request) {
+ try {
+ const url = new URL(request.url);
+
+ // 如果访问根目录,返回HTML
+ if (url.pathname === '/') {
+ return new Response(getRootHtml(), {
+ headers: {
+ 'Content-Type': 'text/html; charset=utf-8',
+ },
+ });
+ }
+
+ // 从请求路径中提取目标 URL
+ let actualUrlStr = decodeURIComponent(url.pathname.replace('/', ''));
+
+ // 判断用户输入的 URL 是否带有协议
+ actualUrlStr = ensureProtocol(actualUrlStr, url.protocol);
+
+ // 保留查询参数
+ actualUrlStr += url.search;
+
+ // 创建新 Headers 对象,排除以 'cf-' 开头的请求头
+ const newHeaders = filterHeaders(
+ request.headers,
+ (name) => !name.startsWith('cf-')
+ );
+
+ // 创建一个新的请求以访问目标 URL
+ const modifiedRequest = new Request(actualUrlStr, {
+ headers: newHeaders,
+ method: request.method,
+ body: request.body,
+ redirect: 'manual',
+ });
+
+ // 发起对目标 URL 的请求
+ const response = await fetch(modifiedRequest);
+ let body = response.body;
+
+ // 处理重定向
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
+ body = response.body;
+ // 创建新的 Response 对象以修改 Location 头部
+ return handleRedirect(response, body);
+ } else if (response.headers.get('Content-Type')?.includes('text/html')) {
+ body = await handleHtmlContent(
+ response,
+ url.protocol,
+ url.host,
+ actualUrlStr
+ );
+ }
+
+ // 创建修改后的响应对象
+ const modifiedResponse = new Response(body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers: response.headers,
+ });
+
+ // 添加禁用缓存的头部
+ setNoCacheHeaders(modifiedResponse.headers);
+
+ // 添加 CORS 头部,允许跨域访问
+ setCorsHeaders(modifiedResponse.headers);
+
+ return modifiedResponse;
+ } catch (error) {
+ // 如果请求目标地址时出现错误,返回带有错误消息的响应和状态码 500(服务器错误)
+ return jsonResponse(
+ {
+ error: error.message,
+ },
+ 500
+ );
+ }
+}
+
+// 确保 URL 带有协议
+function ensureProtocol(url, defaultProtocol) {
+ return url.startsWith('http://') || url.startsWith('https://')
+ ? url
+ : defaultProtocol + '//' + url;
+}
+
+// 处理重定向
+function handleRedirect(response, body) {
+ const location = new URL(response.headers.get('location'));
+ const modifiedLocation = `/${encodeURIComponent(location.toString())}`;
+ return new Response(body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers: {
+ ...response.headers,
+ Location: modifiedLocation,
+ },
+ });
+}
+
+// 处理 HTML 内容中的相对路径
+async function handleHtmlContent(response, protocol, host, actualUrlStr) {
+ const originalText = await response.text();
+ const regex = new RegExp('((href|src|action)=["\'])/(?!/)', 'g');
+ let modifiedText = replaceRelativePaths(
+ originalText,
+ protocol,
+ host,
+ new URL(actualUrlStr).origin
+ );
+
+ return modifiedText;
+}
+
+// 替换 HTML 内容中的相对路径
+function replaceRelativePaths(text, protocol, host, origin) {
+ const regex = new RegExp('((href|src|action)=["\'])/(?!/)', 'g');
+ return text.replace(regex, `$1${protocol}//${host}/${origin}/`);
+}
+
+// 返回 JSON 格式的响应
+function jsonResponse(data, status) {
+ return new Response(JSON.stringify(data), {
+ status: status,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ });
+}
+
+// 过滤请求头
+function filterHeaders(headers, filterFunc) {
+ return new Headers([...headers].filter(([name]) => filterFunc(name)));
+}
+
+// 设置禁用缓存的头部
+function setNoCacheHeaders(headers) {
+ headers.set('Cache-Control', 'no-store');
+}
+
+// 设置 CORS 头部
+function setCorsHeaders(headers) {
+ headers.set('Access-Control-Allow-Origin', '*');
+ headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
+ headers.set('Access-Control-Allow-Headers', '*');
+}
+
+// 返回根目录的 HTML
+function getRootHtml() {
+ return `
+
+
+
+
+ Proxy Everything
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
link Proxy Everything
+
+
+
+
+
+
+
+
+
+
+`;
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..a5e93a4
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png
new file mode 100644
index 0000000..572a0cd
Binary files /dev/null and b/public/icons/icon-192x192.png differ
diff --git a/public/icons/icon-256x256.png b/public/icons/icon-256x256.png
new file mode 100644
index 0000000..4d3bf9a
Binary files /dev/null and b/public/icons/icon-256x256.png differ
diff --git a/public/icons/icon-384x384.png b/public/icons/icon-384x384.png
new file mode 100644
index 0000000..b0015e2
Binary files /dev/null and b/public/icons/icon-384x384.png differ
diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png
new file mode 100644
index 0000000..1bb5e97
Binary files /dev/null and b/public/icons/icon-512x512.png differ
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000..11f1730
Binary files /dev/null and b/public/logo.png differ
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..289efab
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# 禁止所有搜索引擎爬取
+User-agent: *
+Disallow: /
diff --git a/public/screenshot.png b/public/screenshot.png
new file mode 100644
index 0000000..cad2b1a
Binary files /dev/null and b/public/screenshot.png differ
diff --git a/public/screenshot1.png b/public/screenshot1.png
new file mode 100644
index 0000000..eb127d4
Binary files /dev/null and b/public/screenshot1.png differ
diff --git a/public/screenshot2.png b/public/screenshot2.png
new file mode 100644
index 0000000..53c1460
Binary files /dev/null and b/public/screenshot2.png differ
diff --git a/public/screenshot3.png b/public/screenshot3.png
new file mode 100644
index 0000000..32ca79c
Binary files /dev/null and b/public/screenshot3.png differ
diff --git a/scripts/convert-config.js b/scripts/convert-config.js
new file mode 100644
index 0000000..de31e22
--- /dev/null
+++ b/scripts/convert-config.js
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+/* eslint-disable */
+// AUTO-GENERATED SCRIPT: Converts config.json to TypeScript definition.
+// Usage: node scripts/convert-config.js
+
+const fs = require('fs');
+const path = require('path');
+
+// Resolve project root (one level up from scripts folder)
+const projectRoot = path.resolve(__dirname, '..');
+
+// Paths
+const configPath = path.join(projectRoot, 'config.json');
+const libDir = path.join(projectRoot, 'src', 'lib');
+const oldRuntimePath = path.join(libDir, 'runtime.ts');
+const newRuntimePath = path.join(libDir, 'runtime.ts');
+
+// Delete the old runtime.ts file if it exists
+if (fs.existsSync(oldRuntimePath)) {
+ fs.unlinkSync(oldRuntimePath);
+ console.log('旧的 runtime.ts 已删除');
+}
+
+// Read and parse config.json
+let rawConfig;
+try {
+ rawConfig = fs.readFileSync(configPath, 'utf8');
+} catch (err) {
+ console.error(`无法读取 ${configPath}:`, err);
+ process.exit(1);
+}
+
+let config;
+try {
+ config = JSON.parse(rawConfig);
+} catch (err) {
+ console.error('config.json 不是有效的 JSON:', err);
+ process.exit(1);
+}
+
+// Prepare TypeScript file content
+const tsContent =
+ `// 该文件由 scripts/convert-config.js 自动生成,请勿手动修改\n` +
+ `/* eslint-disable */\n\n` +
+ `export const config = ${JSON.stringify(config, null, 2)} as const;\n\n` +
+ `export type RuntimeConfig = typeof config;\n\n` +
+ `export default config;\n`;
+
+// Ensure lib directory exists
+if (!fs.existsSync(libDir)) {
+ fs.mkdirSync(libDir, { recursive: true });
+}
+
+// Write to runtime.ts
+try {
+ fs.writeFileSync(newRuntimePath, tsContent, 'utf8');
+ console.log('已生成 src/lib/runtime.ts');
+} catch (err) {
+ console.error('写入 runtime.ts 失败:', err);
+ process.exit(1);
+}
diff --git a/scripts/generate-manifest.js b/scripts/generate-manifest.js
new file mode 100644
index 0000000..8d6db2b
--- /dev/null
+++ b/scripts/generate-manifest.js
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+/* eslint-disable */
+// 根据 SITE_NAME 动态生成 manifest.json
+
+const fs = require('fs');
+const path = require('path');
+
+// 获取项目根目录
+const projectRoot = path.resolve(__dirname, '..');
+const publicDir = path.join(projectRoot, 'public');
+const manifestPath = path.join(publicDir, 'manifest.json');
+
+// 从环境变量获取站点名称
+const siteName = process.env.SITE_NAME || 'MoonTV';
+
+// manifest.json 模板
+const manifestTemplate = {
+ "name": siteName,
+ "short_name": siteName,
+ "description": "影视聚合",
+ "start_url": "/",
+ "scope": "/",
+ "display": "standalone",
+ "background_color": "#000000",
+ "apple-mobile-web-app-capable": "yes",
+ "apple-mobile-web-app-status-bar-style": "black",
+ "icons": [
+ {
+ "src": "/icons/icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/icon-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/icon-384x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+};
+
+try {
+ // 确保 public 目录存在
+ if (!fs.existsSync(publicDir)) {
+ fs.mkdirSync(publicDir, { recursive: true });
+ }
+
+ // 写入 manifest.json
+ fs.writeFileSync(manifestPath, JSON.stringify(manifestTemplate, null, 2));
+ console.log(`✅ Generated manifest.json with site name: ${siteName}`);
+} catch (error) {
+ console.error('❌ Error generating manifest.json:', error);
+ process.exit(1);
+}
diff --git a/scripts/generate-version.js b/scripts/generate-version.js
new file mode 100644
index 0000000..fb946ae
--- /dev/null
+++ b/scripts/generate-version.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+/* eslint-disable */
+
+const fs = require('fs');
+const path = require('path');
+
+// 获取当前时间并格式化为 YYYYMMDDHHMMSS 格式
+function generateVersion() {
+ const now = new Date();
+
+ const year = now.getFullYear();
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const day = String(now.getDate()).padStart(2, '0');
+ const hours = String(now.getHours()).padStart(2, '0');
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const seconds = String(now.getSeconds()).padStart(2, '0');
+
+ const version = `${year}${month}${day}${hours}${minutes}${seconds}`;
+
+ return version;
+}
+
+// 生成版本号
+const currentVersion = generateVersion();
+
+// 读取现有的 version.ts 文件
+const versionFilePath = path.join(__dirname, '..', 'src', 'lib', 'version.ts');
+let fileContent = fs.readFileSync(versionFilePath, 'utf8');
+
+// 使用正则表达式替换 CURRENT_VERSION 的值
+const updatedContent = fileContent.replace(
+ /const CURRENT_VERSION = '.*?'/,
+ `const CURRENT_VERSION = '${currentVersion}'`
+);
+
+// 写入更新后的内容
+fs.writeFileSync(versionFilePath, updatedContent, 'utf8');
+
+// 将版本号写入根目录下的 VERSION.txt 文件
+const versionTxtPath = path.join(__dirname, '..', 'VERSION.txt');
+fs.writeFileSync(versionTxtPath, currentVersion, 'utf8');
+
+console.log(`版本号已更新为: ${currentVersion}`);
+console.log(`文件已更新: ${versionFilePath}`);
+console.log(`VERSION.txt 已更新: ${versionTxtPath}`);
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
new file mode 100644
index 0000000..8e7ecf3
--- /dev/null
+++ b/src/app/admin/page.tsx
@@ -0,0 +1,1417 @@
+/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
+
+'use client';
+
+import {
+ closestCenter,
+ DndContext,
+ PointerSensor,
+ TouchSensor,
+ useSensor,
+ useSensors,
+} from '@dnd-kit/core';
+import {
+ restrictToParentElement,
+ restrictToVerticalAxis,
+} from '@dnd-kit/modifiers';
+import {
+ arrayMove,
+ SortableContext,
+ useSortable,
+ verticalListSortingStrategy,
+} from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { ChevronDown, ChevronUp, Settings, Users, Video } from 'lucide-react';
+import { GripVertical } from 'lucide-react';
+import { Suspense, useCallback, useEffect, useState } from 'react';
+import Swal from 'sweetalert2';
+
+import { AdminConfig, AdminConfigResult } from '@/lib/admin.types';
+import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
+
+import PageLayout from '@/components/PageLayout';
+
+// 统一弹窗方法(必须在首次使用前定义)
+const showError = (message: string) =>
+ Swal.fire({ icon: 'error', title: '错误', text: message });
+
+const showSuccess = (message: string) =>
+ Swal.fire({
+ icon: 'success',
+ title: '成功',
+ text: message,
+ timer: 2000,
+ showConfirmButton: false,
+ });
+
+// 新增站点配置类型
+interface SiteConfig {
+ SiteName: string;
+ Announcement: string;
+ SearchDownstreamMaxPage: number;
+ SiteInterfaceCacheTime: number;
+ ImageProxy: string;
+ DoubanProxy: string;
+}
+
+// 视频源数据类型
+interface DataSource {
+ name: string;
+ key: string;
+ api: string;
+ detail?: string;
+ disabled?: boolean;
+ from: 'config' | 'custom';
+}
+
+// 可折叠标签组件
+interface CollapsibleTabProps {
+ title: string;
+ icon?: React.ReactNode;
+ isExpanded: boolean;
+ onToggle: () => void;
+ children: React.ReactNode;
+}
+
+const CollapsibleTab = ({
+ title,
+ icon,
+ isExpanded,
+ onToggle,
+ children,
+}: CollapsibleTabProps) => {
+ return (
+
+
+
+ {icon}
+
+ {title}
+
+
+
+ {isExpanded ? : }
+
+
+
+ {isExpanded &&
{children}
}
+
+ );
+};
+
+// 用户配置组件
+interface UserConfigProps {
+ config: AdminConfig | null;
+ role: 'owner' | 'admin' | null;
+ refreshConfig: () => Promise;
+}
+
+const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
+ const [userSettings, setUserSettings] = useState({
+ enableRegistration: false,
+ });
+ const [showAddUserForm, setShowAddUserForm] = useState(false);
+ const [showChangePasswordForm, setShowChangePasswordForm] = useState(false);
+ const [newUser, setNewUser] = useState({
+ username: '',
+ password: '',
+ });
+ const [changePasswordUser, setChangePasswordUser] = useState({
+ username: '',
+ password: '',
+ });
+
+ // 当前登录用户名
+ const currentUsername = getAuthInfoFromBrowserCookie()?.username || null;
+
+ // 检测存储类型是否为 d1
+ const isD1Storage =
+ typeof window !== 'undefined' &&
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE === 'd1';
+ const isUpstashStorage =
+ typeof window !== 'undefined' &&
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE === 'upstash';
+
+ useEffect(() => {
+ if (config?.UserConfig) {
+ setUserSettings({
+ enableRegistration: config.UserConfig.AllowRegister,
+ });
+ }
+ }, [config]);
+
+ // 切换允许注册设置
+ const toggleAllowRegister = async (value: boolean) => {
+ try {
+ // 先更新本地 UI
+ setUserSettings((prev) => ({ ...prev, enableRegistration: value }));
+
+ const res = await fetch('/api/admin/user', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: 'setAllowRegister',
+ allowRegister: value,
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ throw new Error(data.error || `操作失败: ${res.status}`);
+ }
+
+ await refreshConfig();
+ } catch (err) {
+ showError(err instanceof Error ? err.message : '操作失败');
+ // revert toggle UI
+ setUserSettings((prev) => ({ ...prev, enableRegistration: !value }));
+ }
+ };
+
+ const handleBanUser = async (uname: string) => {
+ await handleUserAction('ban', uname);
+ };
+
+ const handleUnbanUser = async (uname: string) => {
+ await handleUserAction('unban', uname);
+ };
+
+ const handleSetAdmin = async (uname: string) => {
+ await handleUserAction('setAdmin', uname);
+ };
+
+ const handleRemoveAdmin = async (uname: string) => {
+ await handleUserAction('cancelAdmin', uname);
+ };
+
+ const handleAddUser = async () => {
+ if (!newUser.username || !newUser.password) return;
+ await handleUserAction('add', newUser.username, newUser.password);
+ setNewUser({ username: '', password: '' });
+ setShowAddUserForm(false);
+ };
+
+ const handleChangePassword = async () => {
+ if (!changePasswordUser.username || !changePasswordUser.password) return;
+ await handleUserAction(
+ 'changePassword',
+ changePasswordUser.username,
+ changePasswordUser.password
+ );
+ setChangePasswordUser({ username: '', password: '' });
+ setShowChangePasswordForm(false);
+ };
+
+ const handleShowChangePasswordForm = (username: string) => {
+ setChangePasswordUser({ username, password: '' });
+ setShowChangePasswordForm(true);
+ setShowAddUserForm(false); // 关闭添加用户表单
+ };
+
+ const handleDeleteUser = async (username: string) => {
+ const { isConfirmed } = await Swal.fire({
+ title: '确认删除用户',
+ text: `删除用户 ${username} 将同时删除其搜索历史、播放记录和收藏夹,此操作不可恢复!`,
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonText: '确认删除',
+ cancelButtonText: '取消',
+ confirmButtonColor: '#dc2626',
+ });
+
+ if (!isConfirmed) return;
+
+ await handleUserAction('deleteUser', username);
+ };
+
+ // 通用请求函数
+ const handleUserAction = async (
+ action:
+ | 'add'
+ | 'ban'
+ | 'unban'
+ | 'setAdmin'
+ | 'cancelAdmin'
+ | 'changePassword'
+ | 'deleteUser',
+ targetUsername: string,
+ targetPassword?: string
+ ) => {
+ try {
+ const res = await fetch('/api/admin/user', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ targetUsername,
+ ...(targetPassword ? { targetPassword } : {}),
+ action,
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ throw new Error(data.error || `操作失败: ${res.status}`);
+ }
+
+ // 成功后刷新配置(无需整页刷新)
+ await refreshConfig();
+ } catch (err) {
+ showError(err instanceof Error ? err.message : '操作失败');
+ }
+ };
+
+ if (!config) {
+ return (
+
+ 加载中...
+
+ );
+ }
+
+ return (
+
+ {/* 用户统计 */}
+
+
+ 用户统计
+
+
+
+ {config.UserConfig.Users.length}
+
+
+ 总用户数
+
+
+
+
+ {/* 注册设置 */}
+
+
+ 注册设置
+
+
+
+ 允许新用户注册
+ {isD1Storage && (
+
+ (D1 环境下请通过环境变量修改)
+
+ )}
+ {isUpstashStorage && (
+
+ (Upstash 环境下请通过环境变量修改)
+
+ )}
+
+
+ !isD1Storage &&
+ !isUpstashStorage &&
+ toggleAllowRegister(!userSettings.enableRegistration)
+ }
+ disabled={isD1Storage || isUpstashStorage}
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
+ userSettings.enableRegistration
+ ? 'bg-green-600'
+ : 'bg-gray-200 dark:bg-gray-700'
+ } ${
+ isD1Storage || isUpstashStorage
+ ? 'opacity-50 cursor-not-allowed'
+ : ''
+ }`}
+ >
+
+
+
+
+
+ {/* 用户列表 */}
+
+
+
+ 用户列表
+
+ {
+ setShowAddUserForm(!showAddUserForm);
+ if (showChangePasswordForm) {
+ setShowChangePasswordForm(false);
+ setChangePasswordUser({ username: '', password: '' });
+ }
+ }}
+ className='px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded-lg transition-colors'
+ >
+ {showAddUserForm ? '取消' : '添加用户'}
+
+
+
+ {/* 添加用户表单 */}
+ {showAddUserForm && (
+
+ )}
+
+ {/* 修改密码表单 */}
+ {showChangePasswordForm && (
+
+ )}
+
+ {/* 用户列表 */}
+
+
+
+
+
+ 用户名
+
+
+ 角色
+
+
+ 状态
+
+
+ 操作
+
+
+
+ {/* 按规则排序用户:自己 -> 站长(若非自己) -> 管理员 -> 其他 */}
+ {(() => {
+ const sortedUsers = [...config.UserConfig.Users].sort((a, b) => {
+ type UserInfo = (typeof config.UserConfig.Users)[number];
+ const priority = (u: UserInfo) => {
+ if (u.username === currentUsername) return 0;
+ if (u.role === 'owner') return 1;
+ if (u.role === 'admin') return 2;
+ return 3;
+ };
+ return priority(a) - priority(b);
+ });
+ return (
+
+ {sortedUsers.map((user) => {
+ // 修改密码权限:站长可修改管理员和普通用户密码,管理员可修改普通用户和自己的密码,但任何人都不能修改站长密码
+ const canChangePassword =
+ user.role !== 'owner' && // 不能修改站长密码
+ (role === 'owner' || // 站长可以修改管理员和普通用户密码
+ (role === 'admin' &&
+ (user.role === 'user' ||
+ user.username === currentUsername))); // 管理员可以修改普通用户和自己的密码
+
+ // 删除用户权限:站长可删除除自己外的所有用户,管理员仅可删除普通用户
+ const canDeleteUser =
+ user.username !== currentUsername &&
+ (role === 'owner' || // 站长可以删除除自己外的所有用户
+ (role === 'admin' && user.role === 'user')); // 管理员仅可删除普通用户
+
+ // 其他操作权限:不能操作自己,站长可操作所有用户,管理员可操作普通用户
+ const canOperate =
+ user.username !== currentUsername &&
+ (role === 'owner' ||
+ (role === 'admin' && user.role === 'user'));
+ return (
+
+
+ {user.username}
+
+
+
+ {user.role === 'owner'
+ ? '站长'
+ : user.role === 'admin'
+ ? '管理员'
+ : '普通用户'}
+
+
+
+
+ {!user.banned ? '正常' : '已封禁'}
+
+
+
+ {/* 修改密码按钮 */}
+ {canChangePassword && (
+
+ handleShowChangePasswordForm(user.username)
+ }
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 hover:bg-blue-200 dark:bg-blue-900/40 dark:hover:bg-blue-900/60 dark:text-blue-200 transition-colors'
+ >
+ 修改密码
+
+ )}
+ {canOperate && (
+ <>
+ {/* 其他操作按钮 */}
+ {user.role === 'user' && (
+ handleSetAdmin(user.username)}
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 hover:bg-purple-200 dark:bg-purple-900/40 dark:hover:bg-purple-900/60 dark:text-purple-200 transition-colors'
+ >
+ 设为管理
+
+ )}
+ {user.role === 'admin' && (
+
+ handleRemoveAdmin(user.username)
+ }
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 dark:bg-gray-700/40 dark:hover:bg-gray-700/60 dark:text-gray-200 transition-colors'
+ >
+ 取消管理
+
+ )}
+ {user.role !== 'owner' &&
+ (!user.banned ? (
+ handleBanUser(user.username)}
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-red-100 text-red-800 hover:bg-red-200 dark:bg-red-900/40 dark:hover:bg-red-900/60 dark:text-red-300 transition-colors'
+ >
+ 封禁
+
+ ) : (
+
+ handleUnbanUser(user.username)
+ }
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-green-100 text-green-800 hover:bg-green-200 dark:bg-green-900/40 dark:hover:bg-green-900/60 dark:text-green-300 transition-colors'
+ >
+ 解封
+
+ ))}
+ >
+ )}
+ {/* 删除用户按钮 - 放在最后,使用更明显的红色样式 */}
+ {canDeleteUser && (
+ handleDeleteUser(user.username)}
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-red-600 text-white hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-700 transition-colors'
+ >
+ 删除用户
+
+ )}
+
+
+ );
+ })}
+
+ );
+ })()}
+
+
+
+
+ );
+};
+
+// 视频源配置组件
+const VideoSourceConfig = ({
+ config,
+ refreshConfig,
+}: {
+ config: AdminConfig | null;
+ refreshConfig: () => Promise;
+}) => {
+ const [sources, setSources] = useState([]);
+ const [showAddForm, setShowAddForm] = useState(false);
+ const [orderChanged, setOrderChanged] = useState(false);
+ const [newSource, setNewSource] = useState({
+ name: '',
+ key: '',
+ api: '',
+ detail: '',
+ disabled: false,
+ from: 'config',
+ });
+
+ // dnd-kit 传感器
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 5, // 轻微位移即可触发
+ },
+ }),
+ useSensor(TouchSensor, {
+ activationConstraint: {
+ delay: 150, // 长按 150ms 后触发,避免与滚动冲突
+ tolerance: 5,
+ },
+ })
+ );
+
+ // 初始化
+ useEffect(() => {
+ if (config?.SourceConfig) {
+ setSources(config.SourceConfig);
+ // 进入时重置 orderChanged
+ setOrderChanged(false);
+ }
+ }, [config]);
+
+ // 通用 API 请求
+ const callSourceApi = async (body: Record) => {
+ try {
+ const resp = await fetch('/api/admin/source', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...body }),
+ });
+
+ if (!resp.ok) {
+ const data = await resp.json().catch(() => ({}));
+ throw new Error(data.error || `操作失败: ${resp.status}`);
+ }
+
+ // 成功后刷新配置
+ await refreshConfig();
+ } catch (err) {
+ showError(err instanceof Error ? err.message : '操作失败');
+ throw err; // 向上抛出方便调用处判断
+ }
+ };
+
+ const handleToggleEnable = (key: string) => {
+ const target = sources.find((s) => s.key === key);
+ if (!target) return;
+ const action = target.disabled ? 'enable' : 'disable';
+ callSourceApi({ action, key }).catch(() => {
+ console.error('操作失败', action, key);
+ });
+ };
+
+ const handleDelete = (key: string) => {
+ callSourceApi({ action: 'delete', key }).catch(() => {
+ console.error('操作失败', 'delete', key);
+ });
+ };
+
+ const handleAddSource = () => {
+ if (!newSource.name || !newSource.key || !newSource.api) return;
+ callSourceApi({
+ action: 'add',
+ key: newSource.key,
+ name: newSource.name,
+ api: newSource.api,
+ detail: newSource.detail,
+ })
+ .then(() => {
+ setNewSource({
+ name: '',
+ key: '',
+ api: '',
+ detail: '',
+ disabled: false,
+ from: 'custom',
+ });
+ setShowAddForm(false);
+ })
+ .catch(() => {
+ console.error('操作失败', 'add', newSource);
+ });
+ };
+
+ const handleDragEnd = (event: any) => {
+ const { active, over } = event;
+ if (!over || active.id === over.id) return;
+ const oldIndex = sources.findIndex((s) => s.key === active.id);
+ const newIndex = sources.findIndex((s) => s.key === over.id);
+ setSources((prev) => arrayMove(prev, oldIndex, newIndex));
+ setOrderChanged(true);
+ };
+
+ const handleSaveOrder = () => {
+ const order = sources.map((s) => s.key);
+ callSourceApi({ action: 'sort', order })
+ .then(() => {
+ setOrderChanged(false);
+ })
+ .catch(() => {
+ console.error('操作失败', 'sort', order);
+ });
+ };
+
+ // 可拖拽行封装 (dnd-kit)
+ const DraggableRow = ({ source }: { source: DataSource }) => {
+ const { attributes, listeners, setNodeRef, transform, transition } =
+ useSortable({ id: source.key });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ } as React.CSSProperties;
+
+ return (
+
+
+
+
+
+ {source.name}
+
+
+ {source.key}
+
+
+ {source.api}
+
+
+ {source.detail || '-'}
+
+
+
+ {!source.disabled ? '启用中' : '已禁用'}
+
+
+
+ handleToggleEnable(source.key)}
+ className={`inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium ${
+ !source.disabled
+ ? 'bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-300 hover:bg-red-200 dark:hover:bg-red-900/60'
+ : 'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-900/60'
+ } transition-colors`}
+ >
+ {!source.disabled ? '禁用' : '启用'}
+
+ {source.from !== 'config' && (
+ handleDelete(source.key)}
+ className='inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 dark:bg-gray-700/40 dark:hover:bg-gray-700/60 dark:text-gray-200 transition-colors'
+ >
+ 删除
+
+ )}
+
+
+ );
+ };
+
+ if (!config) {
+ return (
+
+ 加载中...
+
+ );
+ }
+
+ return (
+
+ {/* 添加视频源表单 */}
+
+
+ 视频源列表
+
+ setShowAddForm(!showAddForm)}
+ className='px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded-lg transition-colors'
+ >
+ {showAddForm ? '取消' : '添加视频源'}
+
+
+
+ {showAddForm && (
+
+ )}
+
+ {/* 视频源表格 */}
+
+
+
+
+
+
+ 名称
+
+
+ Key
+
+
+ API 地址
+
+
+ Detail 地址
+
+
+ 状态
+
+
+ 操作
+
+
+
+
+ s.key)}
+ strategy={verticalListSortingStrategy}
+ >
+
+ {sources.map((source) => (
+
+ ))}
+
+
+
+
+
+
+ {/* 保存排序按钮 */}
+ {orderChanged && (
+
+
+ 保存排序
+
+
+ )}
+
+ );
+};
+
+// 新增站点配置组件
+const SiteConfigComponent = ({ config }: { config: AdminConfig | null }) => {
+ const [siteSettings, setSiteSettings] = useState({
+ SiteName: '',
+ Announcement: '',
+ SearchDownstreamMaxPage: 1,
+ SiteInterfaceCacheTime: 7200,
+ ImageProxy: '',
+ DoubanProxy: '',
+ });
+ // 保存状态
+ const [saving, setSaving] = useState(false);
+
+ // 检测存储类型是否为 d1 或 upstash
+ const isD1Storage =
+ typeof window !== 'undefined' &&
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE === 'd1';
+ const isUpstashStorage =
+ typeof window !== 'undefined' &&
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE === 'upstash';
+
+ useEffect(() => {
+ if (config?.SiteConfig) {
+ setSiteSettings({
+ ...config.SiteConfig,
+ ImageProxy: config.SiteConfig.ImageProxy || '',
+ DoubanProxy: config.SiteConfig.DoubanProxy || '',
+ });
+ }
+ }, [config]);
+
+ // 保存站点配置
+ const handleSave = async () => {
+ try {
+ setSaving(true);
+ const resp = await fetch('/api/admin/site', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...siteSettings }),
+ });
+
+ if (!resp.ok) {
+ const data = await resp.json().catch(() => ({}));
+ throw new Error(data.error || `保存失败: ${resp.status}`);
+ }
+
+ showSuccess('保存成功, 请刷新页面');
+ } catch (err) {
+ showError(err instanceof Error ? err.message : '保存失败');
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ if (!config) {
+ return (
+
+ 加载中...
+
+ );
+ }
+
+ return (
+
+ {/* 站点名称 */}
+
+
+ 站点名称
+ {isD1Storage && (
+
+ (D1 环境下请通过环境变量修改)
+
+ )}
+ {isUpstashStorage && (
+
+ (Upstash 环境下请通过环境变量修改)
+
+ )}
+
+
+ !isD1Storage &&
+ !isUpstashStorage &&
+ setSiteSettings((prev) => ({ ...prev, SiteName: e.target.value }))
+ }
+ disabled={isD1Storage || isUpstashStorage}
+ className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent ${
+ isD1Storage || isUpstashStorage
+ ? 'opacity-50 cursor-not-allowed'
+ : ''
+ }`}
+ />
+
+
+ {/* 站点公告 */}
+
+
+ 站点公告
+ {isD1Storage && (
+
+ (D1 环境下请通过环境变量修改)
+
+ )}
+ {isUpstashStorage && (
+
+ (Upstash 环境下请通过环境变量修改)
+
+ )}
+
+
+
+ {/* 搜索接口可拉取最大页数 */}
+
+
+ 搜索接口可拉取最大页数
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ SearchDownstreamMaxPage: Number(e.target.value),
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+
+ {/* 站点接口缓存时间 */}
+
+
+ 站点接口缓存时间(秒)
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ SiteInterfaceCacheTime: Number(e.target.value),
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+
+ {/* 图片代理 */}
+
+
+ 图片代理前缀
+ {isD1Storage && (
+
+ (D1 环境下请通过环境变量修改)
+
+ )}
+ {isUpstashStorage && (
+
+ (Upstash 环境下请通过环境变量修改)
+
+ )}
+
+
+ !isD1Storage &&
+ !isUpstashStorage &&
+ setSiteSettings((prev) => ({
+ ...prev,
+ ImageProxy: e.target.value,
+ }))
+ }
+ disabled={isD1Storage || isUpstashStorage}
+ className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent ${
+ isD1Storage || isUpstashStorage
+ ? 'opacity-50 cursor-not-allowed'
+ : ''
+ }`}
+ />
+
+ 用于代理图片访问,解决跨域或访问限制问题。留空则不使用代理。
+
+
+
+ {/* 豆瓣代理设置 */}
+
+
+ 豆瓣代理地址
+ {isD1Storage && (
+
+ (D1 环境下请通过环境变量修改)
+
+ )}
+ {isUpstashStorage && (
+
+ (Upstash 环境下请通过环境变量修改)
+
+ )}
+
+
+ !isD1Storage &&
+ !isUpstashStorage &&
+ setSiteSettings((prev) => ({
+ ...prev,
+ DoubanProxy: e.target.value,
+ }))
+ }
+ disabled={isD1Storage || isUpstashStorage}
+ className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent ${
+ isD1Storage || isUpstashStorage
+ ? 'opacity-50 cursor-not-allowed'
+ : ''
+ }`}
+ />
+
+ 用于代理豆瓣数据访问,解决跨域或访问限制问题。留空则使用服务端API。
+
+
+
+ {/* 操作按钮 */}
+
+
+ {saving ? '保存中…' : '保存'}
+
+
+
+ );
+};
+
+function AdminPageClient() {
+ const [config, setConfig] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [role, setRole] = useState<'owner' | 'admin' | null>(null);
+ const [expandedTabs, setExpandedTabs] = useState<{ [key: string]: boolean }>({
+ userConfig: false,
+ videoSource: false,
+ siteConfig: false,
+ });
+
+ // 获取管理员配置
+ // showLoading 用于控制是否在请求期间显示整体加载骨架。
+ const fetchConfig = useCallback(async (showLoading = false) => {
+ try {
+ if (showLoading) {
+ setLoading(true);
+ }
+
+ const response = await fetch(`/api/admin/config`);
+
+ if (!response.ok) {
+ const data = (await response.json()) as any;
+ throw new Error(`获取配置失败: ${data.error}`);
+ }
+
+ const data = (await response.json()) as AdminConfigResult;
+ setConfig(data.Config);
+ setRole(data.Role);
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : '获取配置失败';
+ showError(msg);
+ setError(msg);
+ } finally {
+ if (showLoading) {
+ setLoading(false);
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ // 首次加载时显示骨架
+ fetchConfig(true);
+ }, [fetchConfig]);
+
+ // 切换标签展开状态
+ const toggleTab = (tabKey: string) => {
+ setExpandedTabs((prev) => ({
+ ...prev,
+ [tabKey]: !prev[tabKey],
+ }));
+ };
+
+ // 新增: 重置配置处理函数
+ const handleResetConfig = async () => {
+ const { isConfirmed } = await Swal.fire({
+ title: '确认重置配置',
+ text: '此操作将重置用户封禁和管理员设置、自定义视频源,站点配置将重置为默认值,是否继续?',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonText: '确认',
+ cancelButtonText: '取消',
+ });
+ if (!isConfirmed) return;
+
+ try {
+ const response = await fetch(`/api/admin/reset`);
+ if (!response.ok) {
+ throw new Error(`重置失败: ${response.status}`);
+ }
+ showSuccess('重置成功,请刷新页面!');
+ } catch (err) {
+ showError(err instanceof Error ? err.message : '重置失败');
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+
+ 管理员设置
+
+
+ {Array.from({ length: 3 }).map((_, index) => (
+
+ ))}
+
+
+
+
+ );
+ }
+
+ if (error) {
+ // 错误已通过 SweetAlert2 展示,此处直接返回空
+ return null;
+ }
+
+ return (
+
+
+
+ {/* 标题 + 重置配置按钮 */}
+
+
+ 管理员设置
+
+ {config && role === 'owner' && (
+
+ 重置配置
+
+ )}
+
+
+ {/* 站点配置标签 */}
+
+ }
+ isExpanded={expandedTabs.siteConfig}
+ onToggle={() => toggleTab('siteConfig')}
+ >
+
+
+
+
+ {/* 用户配置标签 */}
+
+ }
+ isExpanded={expandedTabs.userConfig}
+ onToggle={() => toggleTab('userConfig')}
+ >
+
+
+
+ {/* 视频源配置标签 */}
+
+ }
+ isExpanded={expandedTabs.videoSource}
+ onToggle={() => toggleTab('videoSource')}
+ >
+
+
+
+
+
+
+ );
+}
+
+export default function AdminPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts
new file mode 100644
index 0000000..f016459
--- /dev/null
+++ b/src/app/api/admin/config/route.ts
@@ -0,0 +1,63 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { AdminConfigResult } from '@/lib/admin.types';
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { getConfig } from '@/lib/config';
+
+export const runtime = 'edge';
+
+export async function GET(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储进行管理员配置',
+ },
+ { status: 400 }
+ );
+ }
+
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+ const username = authInfo.username;
+
+ try {
+ const config = await getConfig();
+ const result: AdminConfigResult = {
+ Role: 'owner',
+ Config: config,
+ };
+ if (username === process.env.USERNAME) {
+ result.Role = 'owner';
+ } else {
+ const user = config.UserConfig.Users.find((u) => u.username === username);
+ if (user && user.role === 'admin') {
+ result.Role = 'admin';
+ } else {
+ return NextResponse.json(
+ { error: '你是管理员吗你就访问?' },
+ { status: 401 }
+ );
+ }
+ }
+
+ return NextResponse.json(result, {
+ headers: {
+ 'Cache-Control': 'no-store', // 管理员配置不缓存
+ },
+ });
+ } catch (error) {
+ console.error('获取管理员配置失败:', error);
+ return NextResponse.json(
+ {
+ error: '获取管理员配置失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/admin/reset/route.ts b/src/app/api/admin/reset/route.ts
new file mode 100644
index 0000000..e5c7914
--- /dev/null
+++ b/src/app/api/admin/reset/route.ts
@@ -0,0 +1,51 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { resetConfig } from '@/lib/config';
+
+export const runtime = 'edge';
+
+export async function GET(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储进行管理员配置',
+ },
+ { status: 400 }
+ );
+ }
+
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+ const username = authInfo.username;
+
+ if (username !== process.env.USERNAME) {
+ return NextResponse.json({ error: '仅支持站长重置配置' }, { status: 401 });
+ }
+
+ try {
+ await resetConfig();
+
+ return NextResponse.json(
+ { ok: true },
+ {
+ headers: {
+ 'Cache-Control': 'no-store', // 管理员配置不缓存
+ },
+ }
+ );
+ } catch (error) {
+ return NextResponse.json(
+ {
+ error: '重置管理员配置失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts
new file mode 100644
index 0000000..db1e080
--- /dev/null
+++ b/src/app/api/admin/site/route.ts
@@ -0,0 +1,106 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { getConfig } from '@/lib/config';
+import { getStorage } from '@/lib/db';
+
+export const runtime = 'edge';
+
+export async function POST(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储进行管理员配置',
+ },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const body = await request.json();
+
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+ const username = authInfo.username;
+
+ const {
+ SiteName,
+ Announcement,
+ SearchDownstreamMaxPage,
+ SiteInterfaceCacheTime,
+ ImageProxy,
+ DoubanProxy,
+ } = body as {
+ SiteName: string;
+ Announcement: string;
+ SearchDownstreamMaxPage: number;
+ SiteInterfaceCacheTime: number;
+ ImageProxy: string;
+ DoubanProxy: string;
+ };
+
+ // 参数校验
+ if (
+ typeof SiteName !== 'string' ||
+ typeof Announcement !== 'string' ||
+ typeof SearchDownstreamMaxPage !== 'number' ||
+ typeof SiteInterfaceCacheTime !== 'number' ||
+ typeof ImageProxy !== 'string' ||
+ typeof DoubanProxy !== 'string'
+ ) {
+ return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
+ }
+
+ const adminConfig = await getConfig();
+ const storage = getStorage();
+
+ // 权限校验
+ if (username !== process.env.USERNAME) {
+ // 管理员
+ const user = adminConfig.UserConfig.Users.find(
+ (u) => u.username === username
+ );
+ if (!user || user.role !== 'admin') {
+ return NextResponse.json({ error: '权限不足' }, { status: 401 });
+ }
+ }
+
+ // 更新缓存中的站点设置
+ adminConfig.SiteConfig = {
+ SiteName,
+ Announcement,
+ SearchDownstreamMaxPage,
+ SiteInterfaceCacheTime,
+ ImageProxy,
+ DoubanProxy,
+ };
+
+ // 写入数据库
+ if (storage && typeof (storage as any).setAdminConfig === 'function') {
+ await (storage as any).setAdminConfig(adminConfig);
+ }
+
+ return NextResponse.json(
+ { ok: true },
+ {
+ headers: {
+ 'Cache-Control': 'no-store', // 不缓存结果
+ },
+ }
+ );
+ } catch (error) {
+ console.error('更新站点配置失败:', error);
+ return NextResponse.json(
+ {
+ error: '更新站点配置失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/admin/source/route.ts b/src/app/api/admin/source/route.ts
new file mode 100644
index 0000000..716b4cc
--- /dev/null
+++ b/src/app/api/admin/source/route.ts
@@ -0,0 +1,169 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { getConfig } from '@/lib/config';
+import { getStorage } from '@/lib/db';
+import { IStorage } from '@/lib/types';
+
+export const runtime = 'edge';
+
+// 支持的操作类型
+type Action = 'add' | 'disable' | 'enable' | 'delete' | 'sort';
+
+interface BaseBody {
+ action?: Action;
+}
+
+export async function POST(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储进行管理员配置',
+ },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const body = (await request.json()) as BaseBody & Record;
+ const { action } = body;
+
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+ const username = authInfo.username;
+
+ // 基础校验
+ const ACTIONS: Action[] = ['add', 'disable', 'enable', 'delete', 'sort'];
+ if (!username || !action || !ACTIONS.includes(action)) {
+ return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
+ }
+
+ // 获取配置与存储
+ const adminConfig = await getConfig();
+ const storage: IStorage | null = getStorage();
+
+ // 权限与身份校验
+ if (username !== process.env.USERNAME) {
+ const userEntry = adminConfig.UserConfig.Users.find(
+ (u) => u.username === username
+ );
+ if (!userEntry || userEntry.role !== 'admin') {
+ return NextResponse.json({ error: '权限不足' }, { status: 401 });
+ }
+ }
+
+ switch (action) {
+ case 'add': {
+ const { key, name, api, detail } = body as {
+ key?: string;
+ name?: string;
+ api?: string;
+ detail?: string;
+ };
+ if (!key || !name || !api) {
+ return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
+ }
+ if (adminConfig.SourceConfig.some((s) => s.key === key)) {
+ return NextResponse.json({ error: '该源已存在' }, { status: 400 });
+ }
+ adminConfig.SourceConfig.push({
+ key,
+ name,
+ api,
+ detail,
+ from: 'custom',
+ disabled: false,
+ });
+ break;
+ }
+ case 'disable': {
+ const { key } = body as { key?: string };
+ if (!key)
+ return NextResponse.json({ error: '缺少 key 参数' }, { status: 400 });
+ const entry = adminConfig.SourceConfig.find((s) => s.key === key);
+ if (!entry)
+ return NextResponse.json({ error: '源不存在' }, { status: 404 });
+ entry.disabled = true;
+ break;
+ }
+ case 'enable': {
+ const { key } = body as { key?: string };
+ if (!key)
+ return NextResponse.json({ error: '缺少 key 参数' }, { status: 400 });
+ const entry = adminConfig.SourceConfig.find((s) => s.key === key);
+ if (!entry)
+ return NextResponse.json({ error: '源不存在' }, { status: 404 });
+ entry.disabled = false;
+ break;
+ }
+ case 'delete': {
+ const { key } = body as { key?: string };
+ if (!key)
+ return NextResponse.json({ error: '缺少 key 参数' }, { status: 400 });
+ const idx = adminConfig.SourceConfig.findIndex((s) => s.key === key);
+ if (idx === -1)
+ return NextResponse.json({ error: '源不存在' }, { status: 404 });
+ const entry = adminConfig.SourceConfig[idx];
+ if (entry.from === 'config') {
+ return NextResponse.json({ error: '该源不可删除' }, { status: 400 });
+ }
+ adminConfig.SourceConfig.splice(idx, 1);
+ break;
+ }
+ case 'sort': {
+ const { order } = body as { order?: string[] };
+ if (!Array.isArray(order)) {
+ return NextResponse.json(
+ { error: '排序列表格式错误' },
+ { status: 400 }
+ );
+ }
+ const map = new Map(adminConfig.SourceConfig.map((s) => [s.key, s]));
+ const newList: typeof adminConfig.SourceConfig = [];
+ order.forEach((k) => {
+ const item = map.get(k);
+ if (item) {
+ newList.push(item);
+ map.delete(k);
+ }
+ });
+ // 未在 order 中的保持原顺序
+ adminConfig.SourceConfig.forEach((item) => {
+ if (map.has(item.key)) newList.push(item);
+ });
+ adminConfig.SourceConfig = newList;
+ break;
+ }
+ default:
+ return NextResponse.json({ error: '未知操作' }, { status: 400 });
+ }
+
+ // 持久化到存储
+ if (storage && typeof (storage as any).setAdminConfig === 'function') {
+ await (storage as any).setAdminConfig(adminConfig);
+ }
+
+ return NextResponse.json(
+ { ok: true },
+ {
+ headers: {
+ 'Cache-Control': 'no-store',
+ },
+ }
+ );
+ } catch (error) {
+ console.error('视频源管理操作失败:', error);
+ return NextResponse.json(
+ {
+ error: '视频源管理操作失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/admin/user/route.ts b/src/app/api/admin/user/route.ts
new file mode 100644
index 0000000..9c1cd92
--- /dev/null
+++ b/src/app/api/admin/user/route.ts
@@ -0,0 +1,337 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console,@typescript-eslint/no-non-null-assertion */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { getConfig } from '@/lib/config';
+import { getStorage } from '@/lib/db';
+import { IStorage } from '@/lib/types';
+
+export const runtime = 'edge';
+
+// 支持的操作类型
+const ACTIONS = [
+ 'add',
+ 'ban',
+ 'unban',
+ 'setAdmin',
+ 'cancelAdmin',
+ 'setAllowRegister',
+ 'changePassword',
+ 'deleteUser',
+] as const;
+
+export async function POST(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储进行管理员配置',
+ },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const body = await request.json();
+
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+ const username = authInfo.username;
+
+ const {
+ targetUsername, // 目标用户名
+ targetPassword, // 目标用户密码(仅在添加用户时需要)
+ allowRegister,
+ action,
+ } = body as {
+ targetUsername?: string;
+ targetPassword?: string;
+ allowRegister?: boolean;
+ action?: (typeof ACTIONS)[number];
+ };
+
+ if (!action || !ACTIONS.includes(action)) {
+ return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
+ }
+
+ if (action !== 'setAllowRegister' && !targetUsername) {
+ return NextResponse.json({ error: '缺少目标用户名' }, { status: 400 });
+ }
+
+ if (
+ action !== 'setAllowRegister' &&
+ action !== 'changePassword' &&
+ action !== 'deleteUser' &&
+ username === targetUsername
+ ) {
+ return NextResponse.json(
+ { error: '无法对自己进行此操作' },
+ { status: 400 }
+ );
+ }
+
+ // 获取配置与存储
+ const adminConfig = await getConfig();
+ const storage: IStorage | null = getStorage();
+
+ // 判定操作者角色
+ let operatorRole: 'owner' | 'admin';
+ if (username === process.env.USERNAME) {
+ operatorRole = 'owner';
+ } else {
+ const userEntry = adminConfig.UserConfig.Users.find(
+ (u) => u.username === username
+ );
+ if (!userEntry || userEntry.role !== 'admin') {
+ return NextResponse.json({ error: '权限不足' }, { status: 401 });
+ }
+ operatorRole = 'admin';
+ }
+
+ // 查找目标用户条目
+ let targetEntry = adminConfig.UserConfig.Users.find(
+ (u) => u.username === targetUsername
+ );
+
+ if (
+ targetEntry &&
+ targetEntry.role === 'owner' &&
+ action !== 'changePassword'
+ ) {
+ return NextResponse.json({ error: '无法操作站长' }, { status: 400 });
+ }
+
+ // 权限校验逻辑
+ const isTargetAdmin = targetEntry?.role === 'admin';
+
+ if (action === 'setAllowRegister') {
+ if (typeof allowRegister !== 'boolean') {
+ return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
+ }
+ adminConfig.UserConfig.AllowRegister = allowRegister;
+ // 保存后直接返回成功(走后面的统一保存逻辑)
+ } else {
+ switch (action) {
+ case 'add': {
+ if (targetEntry) {
+ return NextResponse.json({ error: '用户已存在' }, { status: 400 });
+ }
+ if (!targetPassword) {
+ return NextResponse.json(
+ { error: '缺少目标用户密码' },
+ { status: 400 }
+ );
+ }
+ if (!storage || typeof storage.registerUser !== 'function') {
+ return NextResponse.json(
+ { error: '存储未配置用户注册' },
+ { status: 500 }
+ );
+ }
+ await storage.registerUser(targetUsername!, targetPassword);
+ // 更新配置
+ adminConfig.UserConfig.Users.push({
+ username: targetUsername!,
+ role: 'user',
+ });
+ targetEntry =
+ adminConfig.UserConfig.Users[
+ adminConfig.UserConfig.Users.length - 1
+ ];
+ break;
+ }
+ case 'ban': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+ if (isTargetAdmin) {
+ // 目标是管理员
+ if (operatorRole !== 'owner') {
+ return NextResponse.json(
+ { error: '仅站长可封禁管理员' },
+ { status: 401 }
+ );
+ }
+ }
+ targetEntry.banned = true;
+ break;
+ }
+ case 'unban': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+ if (isTargetAdmin) {
+ if (operatorRole !== 'owner') {
+ return NextResponse.json(
+ { error: '仅站长可操作管理员' },
+ { status: 401 }
+ );
+ }
+ }
+ targetEntry.banned = false;
+ break;
+ }
+ case 'setAdmin': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+ if (targetEntry.role === 'admin') {
+ return NextResponse.json(
+ { error: '该用户已是管理员' },
+ { status: 400 }
+ );
+ }
+ if (operatorRole !== 'owner') {
+ return NextResponse.json(
+ { error: '仅站长可设置管理员' },
+ { status: 401 }
+ );
+ }
+ targetEntry.role = 'admin';
+ break;
+ }
+ case 'cancelAdmin': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+ if (targetEntry.role !== 'admin') {
+ return NextResponse.json(
+ { error: '目标用户不是管理员' },
+ { status: 400 }
+ );
+ }
+ if (operatorRole !== 'owner') {
+ return NextResponse.json(
+ { error: '仅站长可取消管理员' },
+ { status: 401 }
+ );
+ }
+ targetEntry.role = 'user';
+ break;
+ }
+ case 'changePassword': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+ if (!targetPassword) {
+ return NextResponse.json({ error: '缺少新密码' }, { status: 400 });
+ }
+
+ // 权限检查:不允许修改站长密码
+ if (targetEntry.role === 'owner') {
+ return NextResponse.json(
+ { error: '无法修改站长密码' },
+ { status: 401 }
+ );
+ }
+
+ if (
+ isTargetAdmin &&
+ operatorRole !== 'owner' &&
+ username !== targetUsername
+ ) {
+ return NextResponse.json(
+ { error: '仅站长可修改其他管理员密码' },
+ { status: 401 }
+ );
+ }
+
+ if (!storage || typeof storage.changePassword !== 'function') {
+ return NextResponse.json(
+ { error: '存储未配置密码修改功能' },
+ { status: 500 }
+ );
+ }
+
+ await storage.changePassword(targetUsername!, targetPassword);
+ break;
+ }
+ case 'deleteUser': {
+ if (!targetEntry) {
+ return NextResponse.json(
+ { error: '目标用户不存在' },
+ { status: 404 }
+ );
+ }
+
+ // 权限检查:站长可删除所有用户(除了自己),管理员可删除普通用户
+ if (username === targetUsername) {
+ return NextResponse.json(
+ { error: '不能删除自己' },
+ { status: 400 }
+ );
+ }
+
+ if (isTargetAdmin && operatorRole !== 'owner') {
+ return NextResponse.json(
+ { error: '仅站长可删除管理员' },
+ { status: 401 }
+ );
+ }
+
+ if (!storage || typeof storage.deleteUser !== 'function') {
+ return NextResponse.json(
+ { error: '存储未配置用户删除功能' },
+ { status: 500 }
+ );
+ }
+
+ await storage.deleteUser(targetUsername!);
+
+ // 从配置中移除用户
+ const userIndex = adminConfig.UserConfig.Users.findIndex(
+ (u) => u.username === targetUsername
+ );
+ if (userIndex > -1) {
+ adminConfig.UserConfig.Users.splice(userIndex, 1);
+ }
+
+ break;
+ }
+ default:
+ return NextResponse.json({ error: '未知操作' }, { status: 400 });
+ }
+ }
+
+ // 将更新后的配置写入数据库
+ if (storage && typeof (storage as any).setAdminConfig === 'function') {
+ await (storage as any).setAdminConfig(adminConfig);
+ }
+
+ return NextResponse.json(
+ { ok: true },
+ {
+ headers: {
+ 'Cache-Control': 'no-store', // 管理员配置不缓存
+ },
+ }
+ );
+ } catch (error) {
+ console.error('用户管理操作失败:', error);
+ return NextResponse.json(
+ {
+ error: '用户管理操作失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/change-password/route.ts b/src/app/api/change-password/route.ts
new file mode 100644
index 0000000..66d3174
--- /dev/null
+++ b/src/app/api/change-password/route.ts
@@ -0,0 +1,72 @@
+/* eslint-disable no-console*/
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { getStorage } from '@/lib/db';
+import { IStorage } from '@/lib/types';
+
+export const runtime = 'edge';
+
+export async function POST(request: NextRequest) {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+
+ // 不支持 localstorage 模式
+ if (storageType === 'localstorage') {
+ return NextResponse.json(
+ {
+ error: '不支持本地存储模式修改密码',
+ },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const body = await request.json();
+ const { newPassword } = body;
+
+ // 获取认证信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ // 验证新密码
+ if (!newPassword || typeof newPassword !== 'string') {
+ return NextResponse.json({ error: '新密码不得为空' }, { status: 400 });
+ }
+
+ const username = authInfo.username;
+
+ // 不允许站长修改密码(站长用户名等于 process.env.USERNAME)
+ if (username === process.env.USERNAME) {
+ return NextResponse.json(
+ { error: '站长不能通过此接口修改密码' },
+ { status: 403 }
+ );
+ }
+
+ // 获取存储实例
+ const storage: IStorage | null = getStorage();
+ if (!storage || typeof storage.changePassword !== 'function') {
+ return NextResponse.json(
+ { error: '存储服务不支持修改密码' },
+ { status: 500 }
+ );
+ }
+
+ // 修改密码
+ await storage.changePassword(username, newPassword);
+
+ return NextResponse.json({ ok: true });
+ } catch (error) {
+ console.error('修改密码失败:', error);
+ return NextResponse.json(
+ {
+ error: '修改密码失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/cron/route.ts b/src/app/api/cron/route.ts
new file mode 100644
index 0000000..11ae39c
--- /dev/null
+++ b/src/app/api/cron/route.ts
@@ -0,0 +1,189 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { db } from '@/lib/db';
+import { fetchVideoDetail } from '@/lib/fetchVideoDetail';
+import { SearchResult } from '@/lib/types';
+
+export const runtime = 'edge';
+
+export async function GET(request: NextRequest) {
+ console.log(request.url);
+ try {
+ console.log('Cron job triggered:', new Date().toISOString());
+
+ refreshRecordAndFavorites();
+
+ return NextResponse.json({
+ success: true,
+ message: 'Cron job executed successfully',
+ timestamp: new Date().toISOString(),
+ });
+ } catch (error) {
+ console.error('Cron job failed:', error);
+
+ return NextResponse.json(
+ {
+ success: false,
+ message: 'Cron job failed',
+ error: error instanceof Error ? error.message : 'Unknown error',
+ timestamp: new Date().toISOString(),
+ },
+ { status: 500 }
+ );
+ }
+}
+
+async function refreshRecordAndFavorites() {
+ if (
+ (process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage') === 'localstorage'
+ ) {
+ console.log('跳过刷新:当前使用 localstorage 存储模式');
+ return;
+ }
+
+ try {
+ const users = await db.getAllUsers();
+ if (process.env.USERNAME && !users.includes(process.env.USERNAME)) {
+ users.push(process.env.USERNAME);
+ }
+ // 函数级缓存:key 为 `${source}+${id}`,值为 Promise
+ const detailCache = new Map>();
+
+ // 获取详情 Promise(带缓存和错误处理)
+ const getDetail = async (
+ source: string,
+ id: string,
+ fallbackTitle: string
+ ): Promise => {
+ const key = `${source}+${id}`;
+ let promise = detailCache.get(key);
+ if (!promise) {
+ promise = fetchVideoDetail({
+ source,
+ id,
+ fallbackTitle: fallbackTitle.trim(),
+ })
+ .then((detail) => {
+ // 成功时才缓存结果
+ const successPromise = Promise.resolve(detail);
+ detailCache.set(key, successPromise);
+ return detail;
+ })
+ .catch((err) => {
+ console.error(`获取视频详情失败 (${source}+${id}):`, err);
+ return null;
+ });
+ }
+ return promise;
+ };
+
+ for (const user of users) {
+ console.log(`开始处理用户: ${user}`);
+
+ // 播放记录
+ try {
+ const playRecords = await db.getAllPlayRecords(user);
+ const totalRecords = Object.keys(playRecords).length;
+ let processedRecords = 0;
+
+ for (const [key, record] of Object.entries(playRecords)) {
+ try {
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ console.warn(`跳过无效的播放记录键: ${key}`);
+ continue;
+ }
+
+ const detail = await getDetail(source, id, record.title);
+ if (!detail) {
+ console.warn(`跳过无法获取详情的播放记录: ${key}`);
+ continue;
+ }
+
+ const episodeCount = detail.episodes?.length || 0;
+ if (episodeCount > 0 && episodeCount !== record.total_episodes) {
+ await db.savePlayRecord(user, source, id, {
+ title: detail.title || record.title,
+ source_name: record.source_name,
+ cover: detail.poster || record.cover,
+ index: record.index,
+ total_episodes: episodeCount,
+ play_time: record.play_time,
+ year: detail.year || record.year,
+ total_time: record.total_time,
+ save_time: record.save_time,
+ search_title: record.search_title,
+ });
+ console.log(
+ `更新播放记录: ${record.title} (${record.total_episodes} -> ${episodeCount})`
+ );
+ }
+
+ processedRecords++;
+ } catch (err) {
+ console.error(`处理播放记录失败 (${key}):`, err);
+ // 继续处理下一个记录
+ }
+ }
+
+ console.log(`播放记录处理完成: ${processedRecords}/${totalRecords}`);
+ } catch (err) {
+ console.error(`获取用户播放记录失败 (${user}):`, err);
+ }
+
+ // 收藏
+ try {
+ const favorites = await db.getAllFavorites(user);
+ const totalFavorites = Object.keys(favorites).length;
+ let processedFavorites = 0;
+
+ for (const [key, fav] of Object.entries(favorites)) {
+ try {
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ console.warn(`跳过无效的收藏键: ${key}`);
+ continue;
+ }
+
+ const favDetail = await getDetail(source, id, fav.title);
+ if (!favDetail) {
+ console.warn(`跳过无法获取详情的收藏: ${key}`);
+ continue;
+ }
+
+ const favEpisodeCount = favDetail.episodes?.length || 0;
+ if (favEpisodeCount > 0 && favEpisodeCount !== fav.total_episodes) {
+ await db.saveFavorite(user, source, id, {
+ title: favDetail.title || fav.title,
+ source_name: fav.source_name,
+ cover: favDetail.poster || fav.cover,
+ year: favDetail.year || fav.year,
+ total_episodes: favEpisodeCount,
+ save_time: fav.save_time,
+ search_title: fav.search_title,
+ });
+ console.log(
+ `更新收藏: ${fav.title} (${fav.total_episodes} -> ${favEpisodeCount})`
+ );
+ }
+
+ processedFavorites++;
+ } catch (err) {
+ console.error(`处理收藏失败 (${key}):`, err);
+ // 继续处理下一个收藏
+ }
+ }
+
+ console.log(`收藏处理完成: ${processedFavorites}/${totalFavorites}`);
+ } catch (err) {
+ console.error(`获取用户收藏失败 (${user}):`, err);
+ }
+ }
+
+ console.log('刷新播放记录/收藏任务完成');
+ } catch (err) {
+ console.error('刷新播放记录/收藏任务启动失败', err);
+ }
+}
diff --git a/src/app/api/detail/route.ts b/src/app/api/detail/route.ts
new file mode 100644
index 0000000..6d3c71b
--- /dev/null
+++ b/src/app/api/detail/route.ts
@@ -0,0 +1,45 @@
+import { NextResponse } from 'next/server';
+
+import { getAvailableApiSites, getCacheTime } from '@/lib/config';
+import { getDetailFromApi } from '@/lib/downstream';
+
+export const runtime = 'edge';
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const id = searchParams.get('id');
+ const sourceCode = searchParams.get('source');
+
+ if (!id || !sourceCode) {
+ return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
+ }
+
+ if (!/^[\w-]+$/.test(id)) {
+ return NextResponse.json({ error: '无效的视频ID格式' }, { status: 400 });
+ }
+
+ try {
+ const apiSites = await getAvailableApiSites();
+ const apiSite = apiSites.find((site) => site.key === sourceCode);
+
+ if (!apiSite) {
+ return NextResponse.json({ error: '无效的API来源' }, { status: 400 });
+ }
+
+ const result = await getDetailFromApi(apiSite, id);
+ const cacheTime = await getCacheTime();
+
+ return NextResponse.json(result, {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: (error as Error).message },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/douban/categories/route.ts b/src/app/api/douban/categories/route.ts
new file mode 100644
index 0000000..ea85bc1
--- /dev/null
+++ b/src/app/api/douban/categories/route.ts
@@ -0,0 +1,133 @@
+import { NextResponse } from 'next/server';
+
+import { getCacheTime } from '@/lib/config';
+import { DoubanItem, DoubanResult } from '@/lib/types';
+
+interface DoubanCategoryApiResponse {
+ total: number;
+ items: Array<{
+ id: string;
+ title: string;
+ card_subtitle: string;
+ pic: {
+ large: string;
+ normal: string;
+ };
+ rating: {
+ value: number;
+ };
+ }>;
+}
+
+async function fetchDoubanData(
+ url: string
+): Promise {
+ // 添加超时控制
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
+
+ // 设置请求选项,包括信号和头部
+ const fetchOptions = {
+ signal: controller.signal,
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
+ Referer: 'https://movie.douban.com/',
+ Accept: 'application/json, text/plain, */*',
+ Origin: 'https://movie.douban.com',
+ },
+ };
+
+ try {
+ // 尝试直接访问豆瓣API
+ const response = await fetch(url, fetchOptions);
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ clearTimeout(timeoutId);
+ throw error;
+ }
+}
+
+export const runtime = 'edge';
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+
+ // 获取参数
+ const kind = searchParams.get('kind') || 'movie';
+ const category = searchParams.get('category');
+ const type = searchParams.get('type');
+ const pageLimit = parseInt(searchParams.get('limit') || '20');
+ const pageStart = parseInt(searchParams.get('start') || '0');
+
+ // 验证参数
+ if (!kind || !category || !type) {
+ return NextResponse.json(
+ { error: '缺少必要参数: kind 或 category 或 type' },
+ { status: 400 }
+ );
+ }
+
+ if (!['tv', 'movie'].includes(kind)) {
+ return NextResponse.json(
+ { error: 'kind 参数必须是 tv 或 movie' },
+ { status: 400 }
+ );
+ }
+
+ if (pageLimit < 1 || pageLimit > 100) {
+ return NextResponse.json(
+ { error: 'pageSize 必须在 1-100 之间' },
+ { status: 400 }
+ );
+ }
+
+ if (pageStart < 0) {
+ return NextResponse.json(
+ { error: 'pageStart 不能小于 0' },
+ { status: 400 }
+ );
+ }
+
+ const target = `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`;
+
+ try {
+ // 调用豆瓣 API
+ const doubanData = await fetchDoubanData(target);
+
+ // 转换数据格式
+ const list: DoubanItem[] = doubanData.items.map((item) => ({
+ id: item.id,
+ title: item.title,
+ poster: item.pic?.normal || item.pic?.large || '',
+ rate: item.rating?.value ? item.rating.value.toFixed(1) : '',
+ year: item.card_subtitle?.match(/(\d{4})/)?.[1] || '',
+ }));
+
+ const response: DoubanResult = {
+ code: 200,
+ message: '获取成功',
+ list: list,
+ };
+
+ const cacheTime = await getCacheTime();
+ return NextResponse.json(response, {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: '获取豆瓣数据失败', details: (error as Error).message },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/douban/route.ts b/src/app/api/douban/route.ts
new file mode 100644
index 0000000..e9eee27
--- /dev/null
+++ b/src/app/api/douban/route.ts
@@ -0,0 +1,206 @@
+import { NextResponse } from 'next/server';
+
+import { getCacheTime } from '@/lib/config';
+import { DoubanItem, DoubanResult } from '@/lib/types';
+
+interface DoubanApiResponse {
+ subjects: Array<{
+ id: string;
+ title: string;
+ cover: string;
+ rate: string;
+ }>;
+}
+
+async function fetchDoubanData(url: string): Promise {
+ // 添加超时控制
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
+
+ // 设置请求选项,包括信号和头部
+ const fetchOptions = {
+ signal: controller.signal,
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
+ Referer: 'https://movie.douban.com/',
+ Accept: 'application/json, text/plain, */*',
+ },
+ };
+
+ try {
+ // 尝试直接访问豆瓣API
+ const response = await fetch(url, fetchOptions);
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ clearTimeout(timeoutId);
+ throw error;
+ }
+}
+
+export const runtime = 'edge';
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+
+ // 获取参数
+ const type = searchParams.get('type');
+ const tag = searchParams.get('tag');
+ const pageSize = parseInt(searchParams.get('pageSize') || '16');
+ const pageStart = parseInt(searchParams.get('pageStart') || '0');
+
+ // 验证参数
+ if (!type || !tag) {
+ return NextResponse.json(
+ { error: '缺少必要参数: type 或 tag' },
+ { status: 400 }
+ );
+ }
+
+ if (!['tv', 'movie'].includes(type)) {
+ return NextResponse.json(
+ { error: 'type 参数必须是 tv 或 movie' },
+ { status: 400 }
+ );
+ }
+
+ if (pageSize < 1 || pageSize > 100) {
+ return NextResponse.json(
+ { error: 'pageSize 必须在 1-100 之间' },
+ { status: 400 }
+ );
+ }
+
+ if (pageStart < 0) {
+ return NextResponse.json(
+ { error: 'pageStart 不能小于 0' },
+ { status: 400 }
+ );
+ }
+
+ if (tag === 'top250') {
+ return handleTop250(pageStart);
+ }
+
+ const target = `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageSize}&page_start=${pageStart}`;
+
+ try {
+ // 调用豆瓣 API
+ const doubanData = await fetchDoubanData(target);
+
+ // 转换数据格式
+ const list: DoubanItem[] = doubanData.subjects.map((item) => ({
+ id: item.id,
+ title: item.title,
+ poster: item.cover,
+ rate: item.rate,
+ year: '',
+ }));
+
+ const response: DoubanResult = {
+ code: 200,
+ message: '获取成功',
+ list: list,
+ };
+
+ const cacheTime = await getCacheTime();
+ return NextResponse.json(response, {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: '获取豆瓣数据失败', details: (error as Error).message },
+ { status: 500 }
+ );
+ }
+}
+
+function handleTop250(pageStart: number) {
+ const target = `https://movie.douban.com/top250?start=${pageStart}&filter=`;
+
+ // 直接使用 fetch 获取 HTML 页面
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
+
+ const fetchOptions = {
+ signal: controller.signal,
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
+ Referer: 'https://movie.douban.com/',
+ Accept:
+ 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
+ },
+ };
+
+ return fetch(target, fetchOptions)
+ .then(async (fetchResponse) => {
+ clearTimeout(timeoutId);
+
+ if (!fetchResponse.ok) {
+ throw new Error(`HTTP error! Status: ${fetchResponse.status}`);
+ }
+
+ // 获取 HTML 内容
+ const html = await fetchResponse.text();
+
+ // 通过正则同时捕获影片 id、标题、封面以及评分
+ const moviePattern =
+ /[\s\S]*?
]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*? ]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]*)<\/span>[\s\S]*?<\/div>/g;
+ const movies: DoubanItem[] = [];
+ let match;
+
+ while ((match = moviePattern.exec(html)) !== null) {
+ const id = match[1];
+ const title = match[2];
+ const cover = match[3];
+ const rate = match[4] || '';
+
+ // 处理图片 URL,确保使用 HTTPS
+ const processedCover = cover.replace(/^http:/, 'https:');
+
+ movies.push({
+ id: id,
+ title: title,
+ poster: processedCover,
+ rate: rate,
+ year: '',
+ });
+ }
+
+ const apiResponse: DoubanResult = {
+ code: 200,
+ message: '获取成功',
+ list: movies,
+ };
+
+ const cacheTime = await getCacheTime();
+ return NextResponse.json(apiResponse, {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ });
+ })
+ .catch((error) => {
+ clearTimeout(timeoutId);
+ return NextResponse.json(
+ {
+ error: '获取豆瓣 Top250 数据失败',
+ details: (error as Error).message,
+ },
+ { status: 500 }
+ );
+ });
+}
diff --git a/src/app/api/favorites/route.ts b/src/app/api/favorites/route.ts
new file mode 100644
index 0000000..ff83038
--- /dev/null
+++ b/src/app/api/favorites/route.ts
@@ -0,0 +1,156 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { db } from '@/lib/db';
+import { Favorite } from '@/lib/types';
+
+export const runtime = 'edge';
+
+/**
+ * GET /api/favorites
+ *
+ * 支持两种调用方式:
+ * 1. 不带 query,返回全部收藏列表(Record)。
+ * 2. 带 key=source+id,返回单条收藏(Favorite | null)。
+ */
+export async function GET(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const { searchParams } = new URL(request.url);
+ const key = searchParams.get('key');
+
+ // 查询单条收藏
+ if (key) {
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ return NextResponse.json(
+ { error: 'Invalid key format' },
+ { status: 400 }
+ );
+ }
+ const fav = await db.getFavorite(authInfo.username, source, id);
+ return NextResponse.json(fav, { status: 200 });
+ }
+
+ // 查询全部收藏
+ const favorites = await db.getAllFavorites(authInfo.username);
+ return NextResponse.json(favorites, { status: 200 });
+ } catch (err) {
+ console.error('获取收藏失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * POST /api/favorites
+ * body: { key: string; favorite: Favorite }
+ */
+export async function POST(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const body = await request.json();
+ const { key, favorite }: { key: string; favorite: Favorite } = body;
+
+ if (!key || !favorite) {
+ return NextResponse.json(
+ { error: 'Missing key or favorite' },
+ { status: 400 }
+ );
+ }
+
+ // 验证必要字段
+ if (!favorite.title || !favorite.source_name) {
+ return NextResponse.json(
+ { error: 'Invalid favorite data' },
+ { status: 400 }
+ );
+ }
+
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ return NextResponse.json(
+ { error: 'Invalid key format' },
+ { status: 400 }
+ );
+ }
+
+ const finalFavorite = {
+ ...favorite,
+ save_time: favorite.save_time ?? Date.now(),
+ } as Favorite;
+
+ await db.saveFavorite(authInfo.username, source, id, finalFavorite);
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error('保存收藏失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * DELETE /api/favorites
+ *
+ * 1. 不带 query -> 清空全部收藏
+ * 2. 带 key=source+id -> 删除单条收藏
+ */
+export async function DELETE(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const username = authInfo.username;
+ const { searchParams } = new URL(request.url);
+ const key = searchParams.get('key');
+
+ if (key) {
+ // 删除单条
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ return NextResponse.json(
+ { error: 'Invalid key format' },
+ { status: 400 }
+ );
+ }
+ await db.deleteFavorite(username, source, id);
+ } else {
+ // 清空全部
+ const all = await db.getAllFavorites(username);
+ await Promise.all(
+ Object.keys(all).map(async (k) => {
+ const [s, i] = k.split('+');
+ if (s && i) await db.deleteFavorite(username, s, i);
+ })
+ );
+ }
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error('删除收藏失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/image-proxy/route.ts b/src/app/api/image-proxy/route.ts
new file mode 100644
index 0000000..493665c
--- /dev/null
+++ b/src/app/api/image-proxy/route.ts
@@ -0,0 +1,61 @@
+import { NextResponse } from 'next/server';
+
+export const runtime = 'edge';
+
+// OrionTV 兼容接口
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const imageUrl = searchParams.get('url');
+
+ if (!imageUrl) {
+ return NextResponse.json({ error: 'Missing image URL' }, { status: 400 });
+ }
+
+ try {
+ const imageResponse = await fetch(imageUrl, {
+ headers: {
+ Referer: 'https://movie.douban.com/',
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
+ },
+ });
+
+ if (!imageResponse.ok) {
+ return NextResponse.json(
+ { error: imageResponse.statusText },
+ { status: imageResponse.status }
+ );
+ }
+
+ const contentType = imageResponse.headers.get('content-type');
+
+ if (!imageResponse.body) {
+ return NextResponse.json(
+ { error: 'Image response has no body' },
+ { status: 500 }
+ );
+ }
+
+ // 创建响应头
+ const headers = new Headers();
+ if (contentType) {
+ headers.set('Content-Type', contentType);
+ }
+
+ // 设置缓存头(可选)
+ headers.set('Cache-Control', 'public, max-age=15720000, s-maxage=15720000'); // 缓存半年
+ headers.set('CDN-Cache-Control', 'public, s-maxage=15720000');
+ headers.set('Vercel-CDN-Cache-Control', 'public, s-maxage=15720000');
+
+ // 直接返回图片流
+ return new Response(imageResponse.body, {
+ status: 200,
+ headers,
+ });
+ } catch (error) {
+ return NextResponse.json(
+ { error: 'Error fetching image' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/login/route.ts b/src/app/api/login/route.ts
new file mode 100644
index 0000000..138c7c2
--- /dev/null
+++ b/src/app/api/login/route.ts
@@ -0,0 +1,209 @@
+/* eslint-disable no-console,@typescript-eslint/no-explicit-any */
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getConfig } from '@/lib/config';
+import { db } from '@/lib/db';
+
+export const runtime = 'edge';
+
+// 读取存储类型环境变量,默认 localstorage
+const STORAGE_TYPE =
+ (process.env.NEXT_PUBLIC_STORAGE_TYPE as
+ | 'localstorage'
+ | 'redis'
+ | 'd1'
+ | 'upstash'
+ | undefined) || 'localstorage';
+
+// 生成签名
+async function generateSignature(
+ data: string,
+ secret: string
+): Promise {
+ const encoder = new TextEncoder();
+ const keyData = encoder.encode(secret);
+ const messageData = encoder.encode(data);
+
+ // 导入密钥
+ const key = await crypto.subtle.importKey(
+ 'raw',
+ keyData,
+ { name: 'HMAC', hash: 'SHA-256' },
+ false,
+ ['sign']
+ );
+
+ // 生成签名
+ const signature = await crypto.subtle.sign('HMAC', key, messageData);
+
+ // 转换为十六进制字符串
+ return Array.from(new Uint8Array(signature))
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('');
+}
+
+// 生成认证Cookie(带签名)
+async function generateAuthCookie(
+ username?: string,
+ password?: string,
+ role?: 'owner' | 'admin' | 'user',
+ includePassword = false
+): Promise {
+ const authData: any = { role: role || 'user' };
+
+ // 只在需要时包含 password
+ if (includePassword && password) {
+ authData.password = password;
+ }
+
+ if (username && process.env.PASSWORD) {
+ authData.username = username;
+ // 使用密码作为密钥对用户名进行签名
+ const signature = await generateSignature(username, process.env.PASSWORD);
+ authData.signature = signature;
+ authData.timestamp = Date.now(); // 添加时间戳防重放攻击
+ }
+
+ return encodeURIComponent(JSON.stringify(authData));
+}
+
+export async function POST(req: NextRequest) {
+ try {
+ // 本地 / localStorage 模式——仅校验固定密码
+ if (STORAGE_TYPE === 'localstorage') {
+ const envPassword = process.env.PASSWORD;
+
+ // 未配置 PASSWORD 时直接放行
+ if (!envPassword) {
+ const response = NextResponse.json({ ok: true });
+
+ // 清除可能存在的认证cookie
+ response.cookies.set('auth', '', {
+ path: '/',
+ expires: new Date(0),
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+ }
+
+ const { password } = await req.json();
+ if (typeof password !== 'string') {
+ return NextResponse.json({ error: '密码不能为空' }, { status: 400 });
+ }
+
+ if (password !== envPassword) {
+ return NextResponse.json(
+ { ok: false, error: '密码错误' },
+ { status: 401 }
+ );
+ }
+
+ // 验证成功,设置认证cookie
+ const response = NextResponse.json({ ok: true });
+ const cookieValue = await generateAuthCookie(
+ undefined,
+ password,
+ 'user',
+ true
+ ); // localstorage 模式包含 password
+ const expires = new Date();
+ expires.setDate(expires.getDate() + 7); // 7天过期
+
+ response.cookies.set('auth', cookieValue, {
+ path: '/',
+ expires,
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+ }
+
+ // 数据库 / redis 模式——校验用户名并尝试连接数据库
+ const { username, password } = await req.json();
+
+ if (!username || typeof username !== 'string') {
+ return NextResponse.json({ error: '用户名不能为空' }, { status: 400 });
+ }
+ if (!password || typeof password !== 'string') {
+ return NextResponse.json({ error: '密码不能为空' }, { status: 400 });
+ }
+
+ // 可能是站长,直接读环境变量
+ if (
+ username === process.env.USERNAME &&
+ password === process.env.PASSWORD
+ ) {
+ // 验证成功,设置认证cookie
+ const response = NextResponse.json({ ok: true });
+ const cookieValue = await generateAuthCookie(
+ username,
+ password,
+ 'owner',
+ false
+ ); // 数据库模式不包含 password
+ const expires = new Date();
+ expires.setDate(expires.getDate() + 7); // 7天过期
+
+ response.cookies.set('auth', cookieValue, {
+ path: '/',
+ expires,
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+ } else if (username === process.env.USERNAME) {
+ return NextResponse.json({ error: '用户名或密码错误' }, { status: 401 });
+ }
+
+ const config = await getConfig();
+ const user = config.UserConfig.Users.find((u) => u.username === username);
+ if (user && user.banned) {
+ return NextResponse.json({ error: '用户被封禁' }, { status: 401 });
+ }
+
+ // 校验用户密码
+ try {
+ const pass = await db.verifyUser(username, password);
+ if (!pass) {
+ return NextResponse.json(
+ { error: '用户名或密码错误' },
+ { status: 401 }
+ );
+ }
+
+ // 验证成功,设置认证cookie
+ const response = NextResponse.json({ ok: true });
+ const cookieValue = await generateAuthCookie(
+ username,
+ password,
+ user?.role || 'user',
+ false
+ ); // 数据库模式不包含 password
+ const expires = new Date();
+ expires.setDate(expires.getDate() + 7); // 7天过期
+
+ response.cookies.set('auth', cookieValue, {
+ path: '/',
+ expires,
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+ } catch (err) {
+ console.error('数据库验证失败', err);
+ return NextResponse.json({ error: '数据库错误' }, { status: 500 });
+ }
+ } catch (error) {
+ console.error('登录接口异常', error);
+ return NextResponse.json({ error: '服务器错误' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts
new file mode 100644
index 0000000..ced68a1
--- /dev/null
+++ b/src/app/api/logout/route.ts
@@ -0,0 +1,18 @@
+import { NextResponse } from 'next/server';
+
+export const runtime = 'edge';
+
+export async function POST() {
+ const response = NextResponse.json({ ok: true });
+
+ // 清除认证cookie
+ response.cookies.set('auth', '', {
+ path: '/',
+ expires: new Date(0),
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+}
diff --git a/src/app/api/playrecords/route.ts b/src/app/api/playrecords/route.ts
new file mode 100644
index 0000000..aabdea0
--- /dev/null
+++ b/src/app/api/playrecords/route.ts
@@ -0,0 +1,125 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { db } from '@/lib/db';
+import { PlayRecord } from '@/lib/types';
+
+export const runtime = 'edge';
+
+export async function GET(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const records = await db.getAllPlayRecords(authInfo.username);
+ return NextResponse.json(records, { status: 200 });
+ } catch (err) {
+ console.error('获取播放记录失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const body = await request.json();
+ const { key, record }: { key: string; record: PlayRecord } = body;
+
+ if (!key || !record) {
+ return NextResponse.json(
+ { error: 'Missing key or record' },
+ { status: 400 }
+ );
+ }
+
+ // 验证播放记录数据
+ if (!record.title || !record.source_name || record.index < 1) {
+ return NextResponse.json(
+ { error: 'Invalid record data' },
+ { status: 400 }
+ );
+ }
+
+ // 从key中解析source和id
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ return NextResponse.json(
+ { error: 'Invalid key format' },
+ { status: 400 }
+ );
+ }
+
+ const finalRecord = {
+ ...record,
+ save_time: record.save_time ?? Date.now(),
+ } as PlayRecord;
+
+ await db.savePlayRecord(authInfo.username, source, id, finalRecord);
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error('保存播放记录失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+export async function DELETE(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const username = authInfo.username;
+ const { searchParams } = new URL(request.url);
+ const key = searchParams.get('key');
+
+ if (key) {
+ // 如果提供了 key,删除单条播放记录
+ const [source, id] = key.split('+');
+ if (!source || !id) {
+ return NextResponse.json(
+ { error: 'Invalid key format' },
+ { status: 400 }
+ );
+ }
+
+ await db.deletePlayRecord(username, source, id);
+ } else {
+ // 未提供 key,则清空全部播放记录
+ // 目前 DbManager 没有对应方法,这里直接遍历删除
+ const all = await db.getAllPlayRecords(username);
+ await Promise.all(
+ Object.keys(all).map(async (k) => {
+ const [s, i] = k.split('+');
+ if (s && i) await db.deletePlayRecord(username, s, i);
+ })
+ );
+ }
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error('删除播放记录失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/register/route.ts b/src/app/api/register/route.ts
new file mode 100644
index 0000000..c69e0b3
--- /dev/null
+++ b/src/app/api/register/route.ts
@@ -0,0 +1,130 @@
+/* eslint-disable no-console,@typescript-eslint/no-explicit-any */
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getConfig } from '@/lib/config';
+import { db } from '@/lib/db';
+
+export const runtime = 'edge';
+
+// 读取存储类型环境变量,默认 localstorage
+const STORAGE_TYPE =
+ (process.env.NEXT_PUBLIC_STORAGE_TYPE as
+ | 'localstorage'
+ | 'redis'
+ | 'd1'
+ | 'upstash'
+ | undefined) || 'localstorage';
+
+// 生成签名
+async function generateSignature(
+ data: string,
+ secret: string
+): Promise {
+ const encoder = new TextEncoder();
+ const keyData = encoder.encode(secret);
+ const messageData = encoder.encode(data);
+
+ // 导入密钥
+ const key = await crypto.subtle.importKey(
+ 'raw',
+ keyData,
+ { name: 'HMAC', hash: 'SHA-256' },
+ false,
+ ['sign']
+ );
+
+ // 生成签名
+ const signature = await crypto.subtle.sign('HMAC', key, messageData);
+
+ // 转换为十六进制字符串
+ return Array.from(new Uint8Array(signature))
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('');
+}
+
+// 生成认证Cookie(带签名)
+async function generateAuthCookie(username: string): Promise {
+ const authData: any = {
+ role: 'user',
+ username,
+ timestamp: Date.now(),
+ };
+
+ // 使用process.env.PASSWORD作为签名密钥,而不是用户密码
+ const signingKey = process.env.PASSWORD || '';
+ const signature = await generateSignature(username, signingKey);
+ authData.signature = signature;
+
+ return encodeURIComponent(JSON.stringify(authData));
+}
+
+export async function POST(req: NextRequest) {
+ try {
+ // localstorage 模式下不支持注册
+ if (STORAGE_TYPE === 'localstorage') {
+ return NextResponse.json(
+ { error: '当前模式不支持注册' },
+ { status: 400 }
+ );
+ }
+
+ const config = await getConfig();
+ // 校验是否开放注册
+ if (!config.UserConfig.AllowRegister) {
+ return NextResponse.json({ error: '当前未开放注册' }, { status: 400 });
+ }
+
+ const { username, password } = await req.json();
+
+ if (!username || typeof username !== 'string') {
+ return NextResponse.json({ error: '用户名不能为空' }, { status: 400 });
+ }
+ if (!password || typeof password !== 'string') {
+ return NextResponse.json({ error: '密码不能为空' }, { status: 400 });
+ }
+
+ // 检查是否和管理员重复
+ if (username === process.env.USERNAME) {
+ return NextResponse.json({ error: '用户已存在' }, { status: 400 });
+ }
+
+ try {
+ // 检查用户是否已存在
+ const exist = await db.checkUserExist(username);
+ if (exist) {
+ return NextResponse.json({ error: '用户已存在' }, { status: 400 });
+ }
+
+ await db.registerUser(username, password);
+
+ // 添加到配置中并保存
+ config.UserConfig.Users.push({
+ username,
+ role: 'user',
+ });
+ await db.saveAdminConfig(config);
+
+ // 注册成功,设置认证cookie
+ const response = NextResponse.json({ ok: true });
+ const cookieValue = await generateAuthCookie(username);
+ const expires = new Date();
+ expires.setDate(expires.getDate() + 7); // 7天过期
+
+ response.cookies.set('auth', cookieValue, {
+ path: '/',
+ expires,
+ sameSite: 'lax', // 改为 lax 以支持 PWA
+ httpOnly: false, // PWA 需要客户端可访问
+ secure: false, // 根据协议自动设置
+ });
+
+ return response;
+ } catch (err) {
+ console.error('数据库注册失败', err);
+ return NextResponse.json({ error: '数据库错误' }, { status: 500 });
+ }
+ } catch (error) {
+ console.error('注册接口异常', error);
+ return NextResponse.json({ error: '服务器错误' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/search/one/route.ts b/src/app/api/search/one/route.ts
new file mode 100644
index 0000000..8af2506
--- /dev/null
+++ b/src/app/api/search/one/route.ts
@@ -0,0 +1,76 @@
+import { NextResponse } from 'next/server';
+
+import { getAvailableApiSites, getCacheTime } from '@/lib/config';
+import { searchFromApi } from '@/lib/downstream';
+
+export const runtime = 'edge';
+
+// OrionTV 兼容接口
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const query = searchParams.get('q');
+ const resourceId = searchParams.get('resourceId');
+
+ if (!query || !resourceId) {
+ const cacheTime = await getCacheTime();
+ return NextResponse.json(
+ { result: null, error: '缺少必要参数: q 或 resourceId' },
+ {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ }
+ );
+ }
+
+ const apiSites = await getAvailableApiSites();
+
+ try {
+ // 根据 resourceId 查找对应的 API 站点
+ const targetSite = apiSites.find((site) => site.key === resourceId);
+ if (!targetSite) {
+ return NextResponse.json(
+ {
+ error: `未找到指定的视频源: ${resourceId}`,
+ result: null,
+ },
+ { status: 404 }
+ );
+ }
+
+ const results = await searchFromApi(targetSite, query);
+ const result = results.filter((r) => r.title === query);
+ const cacheTime = await getCacheTime();
+
+ if (result.length === 0) {
+ return NextResponse.json(
+ {
+ error: '未找到结果',
+ result: null,
+ },
+ { status: 404 }
+ );
+ } else {
+ return NextResponse.json(
+ { results: result },
+ {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ }
+ );
+ }
+ } catch (error) {
+ return NextResponse.json(
+ {
+ error: '搜索失败',
+ result: null,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/search/resources/route.ts b/src/app/api/search/resources/route.ts
new file mode 100644
index 0000000..fc351fd
--- /dev/null
+++ b/src/app/api/search/resources/route.ts
@@ -0,0 +1,23 @@
+import { NextResponse } from 'next/server';
+
+import { getAvailableApiSites, getCacheTime } from '@/lib/config';
+
+export const runtime = 'edge';
+
+// OrionTV 兼容接口
+export async function GET() {
+ try {
+ const apiSites = await getAvailableApiSites();
+ const cacheTime = await getCacheTime();
+
+ return NextResponse.json(apiSites, {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ });
+ } catch (error) {
+ return NextResponse.json({ error: '获取资源失败' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts
new file mode 100644
index 0000000..a9c64c3
--- /dev/null
+++ b/src/app/api/search/route.ts
@@ -0,0 +1,47 @@
+import { NextResponse } from 'next/server';
+
+import { getAvailableApiSites, getCacheTime } from '@/lib/config';
+import { searchFromApi } from '@/lib/downstream';
+
+export const runtime = 'edge';
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const query = searchParams.get('q');
+
+ if (!query) {
+ const cacheTime = await getCacheTime();
+ return NextResponse.json(
+ { results: [] },
+ {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ }
+ );
+ }
+
+ const apiSites = await getAvailableApiSites();
+ const searchPromises = apiSites.map((site) => searchFromApi(site, query));
+
+ try {
+ const results = await Promise.all(searchPromises);
+ const flattenedResults = results.flat();
+ const cacheTime = await getCacheTime();
+
+ return NextResponse.json(
+ { results: flattenedResults },
+ {
+ headers: {
+ 'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
+ 'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ 'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
+ },
+ }
+ );
+ } catch (error) {
+ return NextResponse.json({ error: '搜索失败' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/searchhistory/route.ts b/src/app/api/searchhistory/route.ts
new file mode 100644
index 0000000..3e372ba
--- /dev/null
+++ b/src/app/api/searchhistory/route.ts
@@ -0,0 +1,99 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getAuthInfoFromCookie } from '@/lib/auth';
+import { db } from '@/lib/db';
+
+export const runtime = 'edge';
+
+// 最大保存条数(与客户端保持一致)
+const HISTORY_LIMIT = 20;
+
+/**
+ * GET /api/searchhistory
+ * 返回 string[]
+ */
+export async function GET(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const history = await db.getSearchHistory(authInfo.username);
+ return NextResponse.json(history, { status: 200 });
+ } catch (err) {
+ console.error('获取搜索历史失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * POST /api/searchhistory
+ * body: { keyword: string }
+ */
+export async function POST(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const body = await request.json();
+ const keyword: string = body.keyword?.trim();
+
+ if (!keyword) {
+ return NextResponse.json(
+ { error: 'Keyword is required' },
+ { status: 400 }
+ );
+ }
+
+ await db.addSearchHistory(authInfo.username, keyword);
+
+ // 再次获取最新列表,确保客户端与服务端同步
+ const history = await db.getSearchHistory(authInfo.username);
+ return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 });
+ } catch (err) {
+ console.error('添加搜索历史失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * DELETE /api/searchhistory?keyword=
+ *
+ * 1. 不带 keyword -> 清空全部搜索历史
+ * 2. 带 keyword= -> 删除单条关键字
+ */
+export async function DELETE(request: NextRequest) {
+ try {
+ // 从 cookie 获取用户信息
+ const authInfo = getAuthInfoFromCookie(request);
+ if (!authInfo || !authInfo.username) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ const { searchParams } = new URL(request.url);
+ const kw = searchParams.get('keyword')?.trim();
+
+ await db.deleteSearchHistory(authInfo.username, kw || undefined);
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error('删除搜索历史失败', err);
+ return NextResponse.json(
+ { error: 'Internal Server Error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/server-config/route.ts b/src/app/api/server-config/route.ts
new file mode 100644
index 0000000..840a0a9
--- /dev/null
+++ b/src/app/api/server-config/route.ts
@@ -0,0 +1,18 @@
+/* eslint-disable no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getConfig } from '@/lib/config';
+
+export const runtime = 'edge';
+
+export async function GET(request: NextRequest) {
+ console.log('server-config called: ', request.url);
+
+ const config = await getConfig();
+ const result = {
+ SiteName: config.SiteConfig.SiteName,
+ StorageType: process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage',
+ };
+ return NextResponse.json(result);
+}
diff --git a/src/app/douban/page.tsx b/src/app/douban/page.tsx
new file mode 100644
index 0000000..8f5c18e
--- /dev/null
+++ b/src/app/douban/page.tsx
@@ -0,0 +1,355 @@
+/* eslint-disable no-console,react-hooks/exhaustive-deps */
+
+'use client';
+
+import { useSearchParams } from 'next/navigation';
+import { Suspense } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+import { getDoubanCategories } from '@/lib/douban.client';
+import { DoubanItem } from '@/lib/types';
+
+import DoubanCardSkeleton from '@/components/DoubanCardSkeleton';
+import DoubanSelector from '@/components/DoubanSelector';
+import PageLayout from '@/components/PageLayout';
+import VideoCard from '@/components/VideoCard';
+
+function DoubanPageClient() {
+ const searchParams = useSearchParams();
+ const [doubanData, setDoubanData] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [hasMore, setHasMore] = useState(true);
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
+ const [selectorsReady, setSelectorsReady] = useState(false);
+ const observerRef = useRef(null);
+ const loadingRef = useRef(null);
+ const debounceTimeoutRef = useRef(null);
+
+ const type = searchParams.get('type') || 'movie';
+
+ // 选择器状态 - 完全独立,不依赖URL参数
+ const [primarySelection, setPrimarySelection] = useState(() => {
+ return type === 'movie' ? '热门' : '';
+ });
+ const [secondarySelection, setSecondarySelection] = useState(() => {
+ if (type === 'movie') return '全部';
+ if (type === 'tv') return 'tv';
+ if (type === 'show') return 'show';
+ return '全部';
+ });
+
+ // 初始化时标记选择器为准备好状态
+ useEffect(() => {
+ // 短暂延迟确保初始状态设置完成
+ const timer = setTimeout(() => {
+ setSelectorsReady(true);
+ }, 50);
+
+ return () => clearTimeout(timer);
+ }, []); // 只在组件挂载时执行一次
+
+ // type变化时立即重置selectorsReady(最高优先级)
+ useEffect(() => {
+ setSelectorsReady(false);
+ setLoading(true); // 立即显示loading状态
+ }, [type]);
+
+ // 当type变化时重置选择器状态
+ useEffect(() => {
+ // 批量更新选择器状态
+ if (type === 'movie') {
+ setPrimarySelection('热门');
+ setSecondarySelection('全部');
+ } else if (type === 'tv') {
+ setPrimarySelection('');
+ setSecondarySelection('tv');
+ } else if (type === 'show') {
+ setPrimarySelection('');
+ setSecondarySelection('show');
+ } else {
+ setPrimarySelection('');
+ setSecondarySelection('全部');
+ }
+
+ // 使用短暂延迟确保状态更新完成后标记选择器准备好
+ const timer = setTimeout(() => {
+ setSelectorsReady(true);
+ }, 50);
+
+ return () => clearTimeout(timer);
+ }, [type]);
+
+ // 生成骨架屏数据
+ const skeletonData = Array.from({ length: 25 }, (_, index) => index);
+
+ // 生成API请求参数的辅助函数
+ const getRequestParams = useCallback(
+ (pageStart: number) => {
+ // 当type为tv或show时,kind统一为'tv',category使用type本身
+ if (type === 'tv' || type === 'show') {
+ return {
+ kind: 'tv' as const,
+ category: type,
+ type: secondarySelection,
+ pageLimit: 25,
+ pageStart,
+ };
+ }
+
+ // 电影类型保持原逻辑
+ return {
+ kind: type as 'tv' | 'movie',
+ category: primarySelection,
+ type: secondarySelection,
+ pageLimit: 25,
+ pageStart,
+ };
+ },
+ [type, primarySelection, secondarySelection]
+ );
+
+ // 防抖的数据加载函数
+ const loadInitialData = useCallback(async () => {
+ try {
+ setLoading(true);
+ const data = await getDoubanCategories(getRequestParams(0));
+
+ if (data.code === 200) {
+ setDoubanData(data.list);
+ setHasMore(data.list.length === 25);
+ setLoading(false);
+ } else {
+ throw new Error(data.message || '获取数据失败');
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ }, [type, primarySelection, secondarySelection, getRequestParams]);
+
+ // 只在选择器准备好后才加载数据
+ useEffect(() => {
+ // 只有在选择器准备好时才开始加载
+ if (!selectorsReady) {
+ return;
+ }
+
+ // 重置页面状态
+ setDoubanData([]);
+ setCurrentPage(0);
+ setHasMore(true);
+ setIsLoadingMore(false);
+
+ // 清除之前的防抖定时器
+ if (debounceTimeoutRef.current) {
+ clearTimeout(debounceTimeoutRef.current);
+ }
+
+ // 使用防抖机制加载数据,避免连续状态更新触发多次请求
+ debounceTimeoutRef.current = setTimeout(() => {
+ loadInitialData();
+ }, 100); // 100ms 防抖延迟
+
+ // 清理函数
+ return () => {
+ if (debounceTimeoutRef.current) {
+ clearTimeout(debounceTimeoutRef.current);
+ }
+ };
+ }, [
+ selectorsReady,
+ type,
+ primarySelection,
+ secondarySelection,
+ loadInitialData,
+ ]);
+
+ // 单独处理 currentPage 变化(加载更多)
+ useEffect(() => {
+ if (currentPage > 0) {
+ const fetchMoreData = async () => {
+ try {
+ setIsLoadingMore(true);
+
+ const data = await getDoubanCategories(
+ getRequestParams(currentPage * 25)
+ );
+
+ if (data.code === 200) {
+ setDoubanData((prev) => [...prev, ...data.list]);
+ setHasMore(data.list.length === 25);
+ } else {
+ throw new Error(data.message || '获取数据失败');
+ }
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setIsLoadingMore(false);
+ }
+ };
+
+ fetchMoreData();
+ }
+ }, [currentPage, type, primarySelection, secondarySelection]);
+
+ // 设置滚动监听
+ useEffect(() => {
+ // 如果没有更多数据或正在加载,则不设置监听
+ if (!hasMore || isLoadingMore || loading) {
+ return;
+ }
+
+ // 确保 loadingRef 存在
+ if (!loadingRef.current) {
+ return;
+ }
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting && hasMore && !isLoadingMore) {
+ setCurrentPage((prev) => prev + 1);
+ }
+ },
+ { threshold: 0.1 }
+ );
+
+ observer.observe(loadingRef.current);
+ observerRef.current = observer;
+
+ return () => {
+ if (observerRef.current) {
+ observerRef.current.disconnect();
+ }
+ };
+ }, [hasMore, isLoadingMore, loading]);
+
+ // 处理选择器变化
+ const handlePrimaryChange = useCallback(
+ (value: string) => {
+ // 只有当值真正改变时才设置loading状态
+ if (value !== primarySelection) {
+ setLoading(true);
+ setPrimarySelection(value);
+ }
+ },
+ [primarySelection]
+ );
+
+ const handleSecondaryChange = useCallback(
+ (value: string) => {
+ // 只有当值真正改变时才设置loading状态
+ if (value !== secondarySelection) {
+ setLoading(true);
+ setSecondarySelection(value);
+ }
+ },
+ [secondarySelection]
+ );
+
+ const getPageTitle = () => {
+ // 根据 type 生成标题
+ return type === 'movie' ? '电影' : type === 'tv' ? '电视剧' : '综艺';
+ };
+
+ const getActivePath = () => {
+ const params = new URLSearchParams();
+ if (type) params.set('type', type);
+
+ const queryString = params.toString();
+ const activePath = `/douban${queryString ? `?${queryString}` : ''}`;
+ return activePath;
+ };
+
+ return (
+
+
+ {/* 页面标题和选择器 */}
+
+ {/* 页面标题 */}
+
+
+ {getPageTitle()}
+
+
+ 来自豆瓣的精选内容
+
+
+
+ {/* 选择器组件 */}
+
+
+
+
+
+ {/* 内容展示区域 */}
+
+ {/* 内容网格 */}
+
+ {loading || !selectorsReady
+ ? // 显示骨架屏
+ skeletonData.map((index) =>
)
+ : // 显示实际数据
+ doubanData.map((item, index) => (
+
+
+
+ ))}
+
+
+ {/* 加载更多指示器 */}
+ {hasMore && !loading && (
+
{
+ if (el && el.offsetParent !== null) {
+ (
+ loadingRef as React.MutableRefObject
+ ).current = el;
+ }
+ }}
+ className='flex justify-center mt-12 py-8'
+ >
+ {isLoadingMore && (
+
+ )}
+
+ )}
+
+ {/* 没有更多数据提示 */}
+ {!hasMore && doubanData.length > 0 && (
+
已加载全部内容
+ )}
+
+ {/* 空状态 */}
+ {!loading && doubanData.length === 0 && (
+
暂无相关内容
+ )}
+
+
+
+ );
+}
+
+export default function DoubanPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..32e29dc
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,915 @@
+/* 加载骨架屏淡紫色主题 */
+.animate-pulse {
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: .5;
+ }
+}
+
+/* 页面进入动画 */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fade-in-up {
+ animation: fadeInUp 0.6s ease-out forwards;
+}
+
+/* KatelyaTV Logo 彩虹渐变动画 */
+.katelya-logo {
+ background: linear-gradient(
+ 45deg,
+ #ff6b6b,
+ #4ecdc4,
+ #45b7d1,
+ #96ceb4,
+ #ffc645,
+ #fd79a8,
+ #6c5ce7,
+ #a29bfe
+ );
+ background-size: 400% 400%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: rainbow-flow 4s ease-in-out infinite, logo-glow 2s ease-in-out infinite alternate;
+ position: relative;
+ font-weight: 800;
+ letter-spacing: -0.02em;
+}
+
+@keyframes rainbow-flow {
+ 0%, 100% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+}
+
+@keyframes logo-glow {
+ 0% {
+ filter: drop-shadow(0 0 5px rgba(108, 92, 231, 0.4));
+ }
+ 100% {
+ filter: drop-shadow(0 0 20px rgba(108, 92, 231, 0.8)) drop-shadow(0 0 30px rgba(255, 107, 107, 0.4));
+ }
+}
+
+/* 主内容区大型 KatelyaTV Logo 容器 */
+.main-logo-container {
+ padding: 3rem 0 4rem 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 2rem;
+}
+
+/* 背景光效 */
+.logo-background-glow {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 600px;
+ height: 400px;
+ background: radial-gradient(
+ ellipse,
+ rgba(147, 112, 219, 0.15) 0%,
+ rgba(255, 107, 107, 0.1) 30%,
+ rgba(76, 205, 196, 0.08) 60%,
+ transparent 100%
+ );
+ animation: glow-pulse 4s ease-in-out infinite, glow-rotate 20s linear infinite;
+ border-radius: 50%;
+ z-index: 0;
+}
+
+@keyframes glow-pulse {
+ 0%, 100% {
+ opacity: 0.6;
+ transform: translate(-50%, -50%) scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1.1);
+ }
+}
+
+@keyframes glow-rotate {
+ 0% {
+ transform: translate(-50%, -50%) rotate(0deg);
+ }
+ 100% {
+ transform: translate(-50%, -50%) rotate(360deg);
+ }
+}
+
+/* 主内容区大型 Logo */
+.main-katelya-logo {
+ font-size: 4rem;
+ font-weight: 900;
+ background: linear-gradient(
+ 45deg,
+ #ff6b6b,
+ #4ecdc4,
+ #45b7d1,
+ #96ceb4,
+ #ffc645,
+ #fd79a8,
+ #6c5ce7,
+ #a29bfe,
+ #ff6b6b
+ );
+ background-size: 600% 600%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: rainbow-flow-main 6s ease-in-out infinite, logo-float 3s ease-in-out infinite, logo-glow-main 4s ease-in-out infinite;
+ position: relative;
+ z-index: 2;
+ letter-spacing: 0.02em;
+ text-shadow: 0 10px 30px rgba(147, 112, 219, 0.3);
+ cursor: default;
+ user-select: none;
+}
+
+@keyframes rainbow-flow-main {
+ 0%, 100% {
+ background-position: 0% 50%;
+ }
+ 25% {
+ background-position: 100% 0%;
+ }
+ 50% {
+ background-position: 100% 100%;
+ }
+ 75% {
+ background-position: 0% 100%;
+ }
+}
+
+@keyframes logo-float {
+ 0%, 100% {
+ transform: translateY(0px) scale(1);
+ }
+ 50% {
+ transform: translateY(-8px) scale(1.02);
+ }
+}
+
+@keyframes logo-glow-main {
+ 0%, 100% {
+ filter: drop-shadow(0 0 20px rgba(147, 112, 219, 0.4)) drop-shadow(0 0 40px rgba(255, 107, 107, 0.2));
+ }
+ 33% {
+ filter: drop-shadow(0 0 30px rgba(76, 205, 196, 0.4)) drop-shadow(0 0 50px rgba(69, 183, 209, 0.3));
+ }
+ 66% {
+ filter: drop-shadow(0 0 25px rgba(255, 198, 69, 0.4)) drop-shadow(0 0 45px rgba(253, 121, 168, 0.3));
+ }
+}
+
+/* 主 Logo 副标题 */
+.main-logo-subtitle {
+ font-size: 1.1rem;
+ font-weight: 500;
+ color: rgba(147, 112, 219, 0.8);
+ animation: subtitle-shimmer 5s ease-in-out infinite;
+ position: relative;
+ z-index: 2;
+ letter-spacing: 0.1em;
+}
+
+.dark .main-logo-subtitle {
+ color: rgba(186, 85, 211, 0.9);
+}
+
+@keyframes subtitle-shimmer {
+ 0%, 100% {
+ opacity: 0.7;
+ transform: translateY(0px);
+ }
+ 50% {
+ opacity: 1;
+ transform: translateY(-2px);
+ }
+}
+
+/* Logo 装饰性粒子效果 */
+.logo-particles {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.particle {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0.6;
+ animation: particle-float 8s linear infinite;
+}
+
+.particle-1 {
+ top: 20%;
+ left: 15%;
+ width: 8px;
+ height: 8px;
+ background: linear-gradient(45deg, #ff6b6b, #fd79a8);
+ animation-delay: 0s;
+}
+
+.particle-2 {
+ top: 70%;
+ right: 20%;
+ width: 6px;
+ height: 6px;
+ background: linear-gradient(45deg, #4ecdc4, #45b7d1);
+ animation-delay: -2s;
+}
+
+.particle-3 {
+ bottom: 30%;
+ left: 25%;
+ width: 10px;
+ height: 10px;
+ background: linear-gradient(45deg, #96ceb4, #ffc645);
+ animation-delay: -4s;
+}
+
+.particle-4 {
+ top: 40%;
+ right: 30%;
+ width: 5px;
+ height: 5px;
+ background: linear-gradient(45deg, #6c5ce7, #a29bfe);
+ animation-delay: -1s;
+}
+
+.particle-5 {
+ top: 60%;
+ left: 10%;
+ width: 7px;
+ height: 7px;
+ background: linear-gradient(45deg, #fd79a8, #ffc645);
+ animation-delay: -3s;
+}
+
+.particle-6 {
+ bottom: 15%;
+ right: 15%;
+ width: 9px;
+ height: 9px;
+ background: linear-gradient(45deg, #a29bfe, #ff6b6b);
+ animation-delay: -5s;
+}
+
+@keyframes particle-float {
+ 0%, 100% {
+ transform: translateY(0px) translateX(0px) rotate(0deg);
+ opacity: 0.3;
+ }
+ 25% {
+ transform: translateY(-20px) translateX(10px) rotate(90deg);
+ opacity: 0.8;
+ }
+ 50% {
+ transform: translateY(-10px) translateX(-15px) rotate(180deg);
+ opacity: 0.6;
+ }
+ 75% {
+ transform: translateY(-25px) translateX(5px) rotate(270deg);
+ opacity: 0.9;
+ }
+}
+
+/* 移动端主内容区 Logo 适配 */
+@media (max-width: 768px) {
+ .main-logo-container {
+ padding: 2rem 0 3rem 0;
+ margin-bottom: 1.5rem;
+ }
+
+ .main-katelya-logo {
+ font-size: 2.5rem;
+ }
+
+ .main-logo-subtitle {
+ font-size: 0.9rem;
+ }
+
+ .logo-background-glow {
+ width: 400px;
+ height: 250px;
+ }
+
+ .particle {
+ transform: scale(0.8);
+ }
+}
+
+@media (max-width: 480px) {
+ .main-katelya-logo {
+ font-size: 2rem;
+ }
+
+ .main-logo-subtitle {
+ font-size: 0.8rem;
+ }
+
+ .logo-background-glow {
+ width: 300px;
+ height: 200px;
+ }
+}
+
+/* 底部 KatelyaTV Logo 容器 */
+.bottom-logo-container {
+ padding: 2rem 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.bottom-logo-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ rgba(147, 112, 219, 0.1),
+ transparent
+ );
+ animation: shimmer-sweep 3s ease-in-out infinite;
+}
+
+@keyframes shimmer-sweep {
+ 0% {
+ left: -100%;
+ }
+ 100% {
+ left: 100%;
+ }
+}
+
+.bottom-logo {
+ font-size: 2.5rem;
+ font-weight: 800;
+ background: linear-gradient(
+ 45deg,
+ #ff6b6b,
+ #4ecdc4,
+ #45b7d1,
+ #96ceb4,
+ #ffc645,
+ #fd79a8,
+ #6c5ce7,
+ #a29bfe,
+ #ff6b6b
+ );
+ background-size: 400% 400%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: rainbow-flow 3s ease-in-out infinite, pulse-scale 2s ease-in-out infinite;
+ position: relative;
+ z-index: 1;
+ letter-spacing: 0.05em;
+ text-shadow: 0 0 30px rgba(147, 112, 219, 0.5);
+}
+
+@keyframes pulse-scale {
+ 0%, 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.05);
+ }
+}
+
+/* 移动端底部 Logo 调整 */
+@media (max-width: 768px) {
+ .bottom-logo {
+ font-size: 1.8rem;
+ }
+
+ .bottom-logo-container {
+ padding: 1.5rem 0;
+ }
+}
+
+/* 按钮悬停效果增强 */
+.btn-purple {
+ background: linear-gradient(135deg, #9370db, #ba55d3);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+
+.btn-purple::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transition: left 0.5s;
+}
+
+.btn-purple:hover::before {
+ left: 100%;
+}
+
+.btn-purple:hover {
+ background: linear-gradient(135deg, #8a2be2, #9370db);
+ transform: translateY(-2px);
+ box-shadow: 0 8px 25px rgba(147, 112, 219, 0.4);
+}
+
+/* 导航项悬停动画 */
+.nav-item {
+ position: relative;
+ overflow: hidden;
+}
+
+.nav-item::before {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: -100%;
+ width: 100%;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, #9370db, transparent);
+ transition: left 0.3s ease;
+}
+
+.nav-item:hover::before {
+ left: 0;
+}
+
+/* 卡片悬停增强效果 */
+.card-hover {
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+}
+
+.card-hover::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(45deg, transparent, rgba(147, 112, 219, 0.1), transparent);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ pointer-events: none;
+}
+
+.card-hover:hover::after {
+ opacity: 1;
+}
+
+.card-hover:hover {
+ transform: translateY(-8px) scale(1.02);
+ box-shadow: 0 20px 40px rgba(147, 112, 219, 0.2);
+}
+
+/* 浮动几何图形 */
+.floating-shapes {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: -1;
+ overflow: hidden;
+}
+
+.shape {
+ position: absolute;
+ opacity: 0.1;
+ animation: float-rotate 20s linear infinite;
+}
+
+.shape:nth-child(1) {
+ top: 10%;
+ left: 10%;
+ width: 60px;
+ height: 60px;
+ background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
+ border-radius: 50%;
+ animation-delay: 0s;
+}
+
+.shape:nth-child(2) {
+ top: 70%;
+ right: 15%;
+ width: 40px;
+ height: 40px;
+ background: linear-gradient(45deg, #6c5ce7, #a29bfe);
+ transform: rotate(45deg);
+ animation-delay: -5s;
+}
+
+.shape:nth-child(3) {
+ bottom: 20%;
+ left: 20%;
+ width: 80px;
+ height: 20px;
+ background: linear-gradient(45deg, #ffc645, #fd79a8);
+ border-radius: 10px;
+ animation-delay: -10s;
+}
+
+.shape:nth-child(4) {
+ top: 30%;
+ right: 30%;
+ width: 50px;
+ height: 50px;
+ background: linear-gradient(45deg, #96ceb4, #45b7d1);
+ clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
+ animation-delay: -15s;
+}
+
+@keyframes float-rotate {
+ 0% {
+ transform: translateY(0px) rotate(0deg);
+ opacity: 0.1;
+ }
+ 50% {
+ opacity: 0.3;
+ }
+ 100% {
+ transform: translateY(-20px) rotate(360deg);
+ opacity: 0.1;
+ }
+}
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer utilities {
+ .scrollbar-hide {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
+
+ .scrollbar-hide::-webkit-scrollbar {
+ display: none; /* Chrome, Safari and Opera */
+ }
+}
+
+:root {
+ --foreground-rgb: 255, 255, 255;
+}
+
+html,
+body {
+ height: 100%;
+ overflow-x: hidden;
+ /* 阻止 iOS Safari 拉动回弹 */
+ overscroll-behavior: none;
+}
+
+body {
+ color: rgb(var(--foreground-rgb));
+ position: relative;
+}
+
+/* 动态背景特效 - 增强版 */
+body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -2;
+ pointer-events: none;
+ background: linear-gradient(45deg, #e6e6fa, #dda0dd, #c8a2c8, #f0e6ff, #e6e6fa, #d8bfd8, #e6e6fa);
+ background-size: 400% 400%;
+ animation: gradientFlow 12s ease infinite;
+}
+
+/* 流光背景动画 */
+@keyframes gradientFlow {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 33% {
+ background-position: 100% 0%;
+ }
+ 66% {
+ background-position: 0% 100%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+/* 暗色模式背景 */
+html.dark body::before {
+ background: linear-gradient(45deg, #2a0845, #4a0e4e, #1a0a2e, #16213e, #2a0845, #3d1f69, #2a0845);
+ background-size: 400% 400%;
+ animation: gradientFlow 12s ease infinite;
+}
+
+/* 增强的浮动装饰元素 */
+body::after {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ pointer-events: none;
+ background-image:
+ radial-gradient(3px 3px at 20px 30px, rgba(147, 112, 219, 0.5), transparent),
+ radial-gradient(2px 2px at 40px 70px, rgba(186, 85, 211, 0.4), transparent),
+ radial-gradient(1px 1px at 90px 40px, rgba(221, 160, 221, 0.5), transparent),
+ radial-gradient(2px 2px at 130px 80px, rgba(147, 112, 219, 0.4), transparent),
+ radial-gradient(3px 3px at 160px 30px, rgba(138, 43, 226, 0.5), transparent),
+ radial-gradient(1px 1px at 200px 90px, rgba(219, 112, 147, 0.4), transparent),
+ radial-gradient(2px 2px at 250px 50px, rgba(147, 112, 219, 0.5), transparent),
+ radial-gradient(4px 4px at 300px 120px, rgba(255, 107, 107, 0.3), transparent),
+ radial-gradient(2px 2px at 350px 40px, rgba(76, 205, 196, 0.4), transparent);
+ background-repeat: repeat;
+ background-size: 350px 250px;
+ animation: sparkle 25s linear infinite, float 18s ease-in-out infinite;
+}
+
+@keyframes sparkle {
+ 0%, 100% {
+ opacity: 0.6;
+ }
+ 25% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.8;
+ }
+ 75% {
+ opacity: 1;
+ }
+}
+
+@keyframes float {
+ 0%, 100% {
+ transform: translateY(0px) translateX(0px) rotate(0deg);
+ }
+ 25% {
+ transform: translateY(-15px) translateX(10px) rotate(90deg);
+ }
+ 50% {
+ transform: translateY(-8px) translateX(-8px) rotate(180deg);
+ }
+ 75% {
+ transform: translateY(-20px) translateX(5px) rotate(270deg);
+ }
+}
+
+/* 自定义滚动条样式 - 增强版 */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: rgba(147, 112, 219, 0.1);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: linear-gradient(180deg, rgba(147, 112, 219, 0.3), rgba(186, 85, 211, 0.4));
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: linear-gradient(180deg, rgba(147, 112, 219, 0.6), rgba(186, 85, 211, 0.7));
+}
+
+/* 视频卡片悬停效果 */
+.video-card-hover {
+ transition: transform 0.3s ease;
+}
+
+.video-card-hover:hover {
+ transform: scale(1.05);
+}
+
+/* 渐变遮罩 */
+.gradient-overlay {
+ background: linear-gradient(
+ to bottom,
+ rgba(0, 0, 0, 0) 0%,
+ rgba(0, 0, 0, 0.8) 100%
+ );
+}
+
+/* 优化的圆角容器样式 - 主内容区 - 修改透明度为50% */
+.rounded-container {
+ border-radius: 20px;
+ overflow: hidden;
+ /* 将透明度从0.85调整为0.5 (50%) */
+ background: rgba(255, 255, 255, 0.5);
+ backdrop-filter: blur(25px);
+ border: 1px solid rgba(147, 112, 219, 0.2);
+ box-shadow:
+ 0 8px 40px rgba(147, 112, 219, 0.12),
+ 0 1px 0 rgba(255, 255, 255, 0.9) inset,
+ 0 0 0 1px rgba(147, 112, 219, 0.05) inset;
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+ /* 确保容器占据完整的分配空间 */
+ height: 100%;
+ width: 100%;
+}
+
+.rounded-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, rgba(147, 112, 219, 0.5), rgba(255, 107, 107, 0.3), rgba(147, 112, 219, 0.5), transparent);
+ animation: shimmerTop 4s ease-in-out infinite;
+}
+
+@keyframes shimmerTop {
+ 0%, 100% {
+ opacity: 0;
+ transform: translateX(-100%);
+ }
+ 50% {
+ opacity: 1;
+ transform: translateX(100%);
+ }
+}
+
+.dark .rounded-container {
+ /* 暗色模式下也将透明度调整为0.5 (50%) */
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid rgba(147, 112, 219, 0.3);
+ box-shadow:
+ 0 8px 40px rgba(147, 112, 219, 0.25),
+ 0 1px 0 rgba(147, 112, 219, 0.15) inset,
+ 0 0 0 1px rgba(147, 112, 219, 0.1) inset;
+}
+
+.dark .rounded-container::before {
+ background: linear-gradient(90deg, transparent, rgba(147, 112, 219, 0.7), rgba(255, 107, 107, 0.4), rgba(147, 112, 219, 0.7), transparent);
+}
+
+/* 悬停效果 */
+.rounded-container:hover {
+ transform: translateY(-3px);
+ box-shadow:
+ 0 15px 50px rgba(147, 112, 219, 0.2),
+ 0 1px 0 rgba(255, 255, 255, 0.95) inset,
+ 0 0 0 1px rgba(147, 112, 219, 0.1) inset;
+}
+
+.dark .rounded-container:hover {
+ box-shadow:
+ 0 15px 50px rgba(147, 112, 219, 0.35),
+ 0 1px 0 rgba(147, 112, 219, 0.25) inset,
+ 0 0 0 1px rgba(147, 112, 219, 0.2) inset;
+}
+
+/* 响应式容器边距 */
+@media (max-width: 768px) {
+ .rounded-container {
+ border-radius: 16px;
+ /* 移动端时恢复全宽 */
+ width: 100% !important;
+ }
+}
+
+/* 隐藏移动端(<768px)垂直滚动条 */
+@media (max-width: 767px) {
+ html,
+ body {
+ -ms-overflow-style: none; /* IE & Edge */
+ scrollbar-width: none; /* Firefox */
+ }
+
+ html::-webkit-scrollbar,
+ body::-webkit-scrollbar {
+ display: none; /* Chrome Safari */
+ }
+}
+
+/* 隐藏所有滚动条(兼容 WebKit、Firefox、IE/Edge) */
+* {
+ -ms-overflow-style: none; /* IE & Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+*::-webkit-scrollbar {
+ display: none; /* Chrome, Safari, Opera */
+}
+
+/* View Transitions API 动画 */
+@keyframes slide-from-top {
+ from {
+ clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
+ }
+ to {
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
+ }
+}
+
+@keyframes slide-from-bottom {
+ from {
+ clip-path: polygon(0 100%, 100% 100%, 100% 100%, 0 100%);
+ }
+ to {
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
+ }
+}
+
+::view-transition-old(root),
+::view-transition-new(root) {
+ animation-duration: 0.8s;
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ animation-fill-mode: both;
+}
+
+/*
+ 切换时,旧的视图不应该有动画,它应该在下面,等待被新的视图覆盖。
+ 这可以防止在动画完成前,页面底部提前变色。
+*/
+::view-transition-old(root) {
+ animation: none;
+}
+
+/* 从浅色到深色:新内容(深色)从顶部滑入 */
+html.dark::view-transition-new(root) {
+ animation-name: slide-from-top;
+}
+
+/* 从深色到浅色:新内容(浅色)从底部滑入 */
+html:not(.dark)::view-transition-new(root) {
+ animation-name: slide-from-bottom;
+}
+
+/* 强制播放器内部的 video 元素高度为 100%,并保持内容完整显示 */
+div[data-media-provider] video {
+ height: 100%;
+ object-fit: contain;
+}
+
+.art-poster {
+ background-size: contain !important; /* 使图片完整展示 */
+ background-position: center center !important; /* 居中显示 */
+ background-repeat: no-repeat !important; /* 防止重复 */
+ background-color: #000 !important; /* 其余区域填充为黑色 */
+}
+
+/* 隐藏移动端竖屏时的 pip 按钮 */
+@media (max-width: 768px) {
+ .art-control-pip {
+ display: none !important;
+ }
+
+ .art-control-fullscreenWeb {
+ display: none !important;
+ }
+
+ .art-control-volume {
+ display: none !important;
+ }
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..6d0e4a0
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,110 @@
+import type { Metadata, Viewport } from 'next';
+import { Inter } from 'next/font/google';
+
+import './globals.css';
+import 'sweetalert2/dist/sweetalert2.min.css';
+
+import { getConfig } from '@/lib/config';
+
+import { SiteProvider } from '../components/SiteProvider';
+import { ThemeProvider } from '../components/ThemeProvider';
+
+const inter = Inter({ subsets: ['latin'] });
+
+// 动态生成 metadata,支持配置更新后的标题变化
+export async function generateMetadata(): Promise {
+ let siteName = process.env.SITE_NAME || 'KatelyaTV';
+ if (
+ process.env.NEXT_PUBLIC_STORAGE_TYPE !== 'd1' &&
+ process.env.NEXT_PUBLIC_STORAGE_TYPE !== 'upstash'
+ ) {
+ const config = await getConfig();
+ siteName = config.SiteConfig.SiteName;
+ }
+
+ return {
+ title: siteName,
+ description: '影视聚合',
+ manifest: '/manifest.json',
+ };
+}
+
+export const viewport: Viewport = {
+ themeColor: '#000000',
+};
+
+// 浮动几何形状组件
+const FloatingShapes = () => {
+ return (
+
+ );
+};
+
+export default async function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ let siteName = process.env.SITE_NAME || 'KatelyaTV';
+ let announcement =
+ process.env.ANNOUNCEMENT ||
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。Link Me TG:@katelya77';
+ let enableRegister = process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true';
+ let imageProxy = process.env.NEXT_PUBLIC_IMAGE_PROXY || '';
+ let doubanProxy = process.env.NEXT_PUBLIC_DOUBAN_PROXY || '';
+ if (
+ process.env.NEXT_PUBLIC_STORAGE_TYPE !== 'd1' &&
+ process.env.NEXT_PUBLIC_STORAGE_TYPE !== 'upstash'
+ ) {
+ const config = await getConfig();
+ siteName = config.SiteConfig.SiteName;
+ announcement = config.SiteConfig.Announcement;
+ enableRegister = config.UserConfig.AllowRegister;
+ imageProxy = config.SiteConfig.ImageProxy;
+ doubanProxy = config.SiteConfig.DoubanProxy;
+ }
+
+ // 将运行时配置注入到全局 window 对象,供客户端在运行时读取
+ const runtimeConfig = {
+ STORAGE_TYPE: process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage',
+ ENABLE_REGISTER: enableRegister,
+ IMAGE_PROXY: imageProxy,
+ DOUBAN_PROXY: doubanProxy,
+ };
+
+ return (
+
+
+ {/* 将配置序列化后直接写入脚本,浏览器端可通过 window.RUNTIME_CONFIG 获取 */}
+ {/* eslint-disable-next-line @next/next/no-sync-scripts */}
+
+
+
+ {/* 浮动几何形状装饰 */}
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
new file mode 100644
index 0000000..8a0086e
--- /dev/null
+++ b/src/app/login/page.tsx
@@ -0,0 +1,252 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+'use client';
+
+import { AlertCircle, CheckCircle } from 'lucide-react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { Suspense, useEffect, useState } from 'react';
+
+import { checkForUpdates, CURRENT_VERSION, UpdateStatus } from '@/lib/version';
+
+import { useSite } from '@/components/SiteProvider';
+import { ThemeToggle } from '@/components/ThemeToggle';
+
+// 版本显示组件
+function VersionDisplay() {
+ const [updateStatus, setUpdateStatus] = useState(null);
+ const [isChecking, setIsChecking] = useState(true);
+
+ useEffect(() => {
+ const checkUpdate = async () => {
+ try {
+ const status = await checkForUpdates();
+ setUpdateStatus(status);
+ } catch (_) {
+ // do nothing
+ } finally {
+ setIsChecking(false);
+ }
+ };
+
+ checkUpdate();
+ }, []);
+
+ return (
+
+ window.open('https://github.com/senshinya/MoonTV', '_blank')
+ }
+ className='absolute bottom-4 left-1/2 transform -translate-x-1/2 flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 transition-colors cursor-pointer'
+ >
+ v{CURRENT_VERSION}
+ {!isChecking && updateStatus !== UpdateStatus.FETCH_FAILED && (
+
+ {updateStatus === UpdateStatus.HAS_UPDATE && (
+ <>
+
+
有新版本
+ >
+ )}
+ {updateStatus === UpdateStatus.NO_UPDATE && (
+ <>
+
+
已是最新
+ >
+ )}
+
+ )}
+
+ );
+}
+
+function LoginPageClient() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [password, setPassword] = useState('');
+ const [username, setUsername] = useState('');
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [shouldAskUsername, setShouldAskUsername] = useState(false);
+ const [enableRegister, setEnableRegister] = useState(false);
+ const { siteName } = useSite();
+
+ // 在客户端挂载后设置配置
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const storageType = (window as any).RUNTIME_CONFIG?.STORAGE_TYPE;
+ setShouldAskUsername(storageType && storageType !== 'localstorage');
+ setEnableRegister(
+ Boolean((window as any).RUNTIME_CONFIG?.ENABLE_REGISTER)
+ );
+ }
+ }, []);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+
+ if (!password || (shouldAskUsername && !username)) return;
+
+ try {
+ setLoading(true);
+ const res = await fetch('/api/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ password,
+ ...(shouldAskUsername ? { username } : {}),
+ }),
+ });
+
+ if (res.ok) {
+ const redirect = searchParams.get('redirect') || '/';
+ router.replace(redirect);
+ } else if (res.status === 401) {
+ setError('密码错误');
+ } else {
+ const data = await res.json().catch(() => ({}));
+ setError(data.error ?? '服务器错误');
+ }
+ } catch (error) {
+ setError('网络错误,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 处理注册逻辑
+ const handleRegister = async () => {
+ setError(null);
+ if (!password || !username) return;
+
+ try {
+ setLoading(true);
+ const res = await fetch('/api/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password }),
+ });
+
+ if (res.ok) {
+ const redirect = searchParams.get('redirect') || '/';
+ router.replace(redirect);
+ } else {
+ const data = await res.json().catch(() => ({}));
+ setError(data.error ?? '服务器错误');
+ }
+ } catch (error) {
+ setError('网络错误,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {/* 渐变酷炫Logo */}
+
+
+ {siteName}
+
+ {/* 添加发光效果 */}
+
+ {siteName}
+
+
+
+
+
+ {/* 版本信息显示 */}
+
+
+ );
+}
+
+export default function LoginPage() {
+ return (
+ Loading... }>
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..95b472c
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,474 @@
+/* eslint-disable @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps, no-console */
+
+'use client';
+
+import { ChevronRight } from 'lucide-react';
+import Link from 'next/link';
+import { Suspense, useEffect, useState } from 'react';
+
+// 客户端收藏 API
+import {
+ clearAllFavorites,
+ getAllFavorites,
+ getAllPlayRecords,
+ subscribeToDataUpdates,
+} from '@/lib/db.client';
+import { getDoubanCategories } from '@/lib/douban.client';
+import { DoubanItem } from '@/lib/types';
+
+import CapsuleSwitch from '@/components/CapsuleSwitch';
+import ContinueWatching from '@/components/ContinueWatching';
+import PageLayout from '@/components/PageLayout';
+import ScrollableRow from '@/components/ScrollableRow';
+import { useSite } from '@/components/SiteProvider';
+import VideoCard from '@/components/VideoCard';
+
+// 主内容区大型 KatelyaTV Logo 组件
+const MainKatelyaLogo = () => {
+ return (
+
+ {/* 背景光效 */}
+
+
+ {/* 主 Logo */}
+
+ KatelyaTV
+
+
+ {/* 副标题 */}
+
+
+ {/* 装饰性粒子效果 */}
+
+
+ );
+};
+
+// KatelyaTV 底部 Logo 组件
+const BottomKatelyaLogo = () => {
+ return (
+
+ {/* 浮动几何形状装饰 */}
+
+
+
+
+ KatelyaTV
+
+
+ Powered by MoonTV Core
+
+
+
+ );
+};
+
+function HomeClient() {
+ const [activeTab, setActiveTab] = useState<'home' | 'favorites'>('home');
+ const [hotMovies, setHotMovies] = useState([]);
+ const [hotTvShows, setHotTvShows] = useState([]);
+ const [hotVarietyShows, setHotVarietyShows] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const { announcement } = useSite();
+
+ const [showAnnouncement, setShowAnnouncement] = useState(false);
+
+ // 检查公告弹窗状态
+ useEffect(() => {
+ if (typeof window !== 'undefined' && announcement) {
+ const hasSeenAnnouncement = localStorage.getItem('hasSeenAnnouncement');
+ if (hasSeenAnnouncement !== announcement) {
+ setShowAnnouncement(true);
+ } else {
+ setShowAnnouncement(Boolean(!hasSeenAnnouncement && announcement));
+ }
+ }
+ }, [announcement]);
+
+ // 收藏夹数据
+ type FavoriteItem = {
+ id: string;
+ source: string;
+ title: string;
+ poster: string;
+ episodes: number;
+ source_name: string;
+ currentEpisode?: number;
+ search_title?: string;
+ };
+
+ const [favoriteItems, setFavoriteItems] = useState([]);
+
+ useEffect(() => {
+ const fetchDoubanData = async () => {
+ try {
+ setLoading(true);
+
+ // 并行获取热门电影、热门剧集和热门综艺
+ const [moviesData, tvShowsData, varietyShowsData] = await Promise.all([
+ getDoubanCategories({
+ kind: 'movie',
+ category: '热门',
+ type: '全部',
+ }),
+ getDoubanCategories({ kind: 'tv', category: 'tv', type: 'tv' }),
+ getDoubanCategories({ kind: 'tv', category: 'show', type: 'show' }),
+ ]);
+
+ if (moviesData.code === 200) {
+ setHotMovies(moviesData.list);
+ }
+
+ if (tvShowsData.code === 200) {
+ setHotTvShows(tvShowsData.list);
+ }
+
+ if (varietyShowsData.code === 200) {
+ setHotVarietyShows(varietyShowsData.list);
+ }
+ } catch (error) {
+ console.error('获取豆瓣数据失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchDoubanData();
+ }, []);
+
+ // 处理收藏数据更新的函数
+ const updateFavoriteItems = async (allFavorites: Record) => {
+ const allPlayRecords = await getAllPlayRecords();
+
+ // 根据保存时间排序(从近到远)
+ const sorted = Object.entries(allFavorites)
+ .sort(([, a], [, b]) => b.save_time - a.save_time)
+ .map(([key, fav]) => {
+ const plusIndex = key.indexOf('+');
+ const source = key.slice(0, plusIndex);
+ const id = key.slice(plusIndex + 1);
+
+ // 查找对应的播放记录,获取当前集数
+ const playRecord = allPlayRecords[key];
+ const currentEpisode = playRecord?.index;
+
+ return {
+ id,
+ source,
+ title: fav.title,
+ year: fav.year,
+ poster: fav.cover,
+ episodes: fav.total_episodes,
+ source_name: fav.source_name,
+ currentEpisode,
+ search_title: fav?.search_title,
+ } as FavoriteItem;
+ });
+ setFavoriteItems(sorted);
+ };
+
+ // 当切换到收藏夹时加载收藏数据
+ useEffect(() => {
+ if (activeTab !== 'favorites') return;
+
+ const loadFavorites = async () => {
+ const allFavorites = await getAllFavorites();
+ await updateFavoriteItems(allFavorites);
+ };
+
+ loadFavorites();
+
+ // 监听收藏更新事件
+ const unsubscribe = subscribeToDataUpdates(
+ 'favoritesUpdated',
+ (newFavorites: Record) => {
+ updateFavoriteItems(newFavorites);
+ }
+ );
+
+ return unsubscribe;
+ }, [activeTab]);
+
+ const handleCloseAnnouncement = (announcement: string) => {
+ setShowAnnouncement(false);
+ localStorage.setItem('hasSeenAnnouncement', announcement); // 记录已查看弹窗
+ };
+
+ return (
+
+
+ {/* 主内容区大型 KatelyaTV Logo - 仅在首页显示 */}
+ {activeTab === 'home' &&
}
+
+ {/* 顶部 Tab 切换 */}
+
+ setActiveTab(value as 'home' | 'favorites')}
+ />
+
+
+ {/* 主内容区域 - 优化为完全居中布局 */}
+
+ {activeTab === 'favorites' ? (
+ // 收藏夹视图
+ <>
+
+
+
+ 我的收藏
+
+ {favoriteItems.length > 0 && (
+ {
+ await clearAllFavorites();
+ setFavoriteItems([]);
+ }}
+ >
+ 清空
+
+ )}
+
+ {/* 优化收藏夹网格布局,确保在新的居中布局下完美对齐 */}
+
+ {favoriteItems.map((item) => (
+
+ 1 ? 'tv' : ''}
+ />
+
+ ))}
+ {favoriteItems.length === 0 && (
+
+ 暂无收藏内容
+
+ )}
+
+
+
+ {/* 收藏夹页面底部 Logo */}
+
+ >
+ ) : (
+ // 首页视图
+ <>
+ {/* 继续观看 */}
+
+
+ {/* 热门电影 */}
+
+
+
+ 热门电影
+
+
+ 查看更多
+
+
+
+
+ {loading
+ ? // 加载状态显示灰色占位数据
+ Array.from({ length: 8 }).map((_, index) => (
+
+ ))
+ : // 显示真实数据
+ hotMovies.map((movie, index) => (
+
+
+
+ ))}
+
+
+
+ {/* 热门剧集 */}
+
+
+
+ 热门剧集
+
+
+ 查看更多
+
+
+
+
+ {loading
+ ? // 加载状态显示灰色占位数据
+ Array.from({ length: 8 }).map((_, index) => (
+
+ ))
+ : // 显示真实数据
+ hotTvShows.map((show, index) => (
+
+
+
+ ))}
+
+
+
+ {/* 热门综艺 */}
+
+
+
+ 热门综艺
+
+
+ 查看更多
+
+
+
+
+ {loading
+ ? // 加载状态显示灰色占位数据
+ Array.from({ length: 8 }).map((_, index) => (
+
+ ))
+ : // 显示真实数据
+ hotVarietyShows.map((show, index) => (
+
+
+
+ ))}
+
+
+
+ {/* 首页底部 Logo */}
+
+ >
+ )}
+
+
+ {announcement && showAnnouncement && (
+
+
+
+
+ 提示
+
+ handleCloseAnnouncement(announcement)}
+ className='text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-white transition-colors'
+ aria-label='关闭'
+ >
+
+
+
handleCloseAnnouncement(announcement)}
+ className='w-full rounded-lg bg-gradient-to-r from-purple-600 to-purple-700 px-4 py-3 text-white font-medium shadow-md hover:shadow-lg hover:from-purple-700 hover:to-purple-800 dark:from-purple-600 dark:to-purple-700 dark:hover:from-purple-700 dark:hover:to-purple-800 transition-all duration-300 transform hover:-translate-y-0.5'
+ >
+ 我知道了
+
+
+
+ )}
+
+ );
+}
+
+export default function Home() {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx
new file mode 100644
index 0000000..36b0783
--- /dev/null
+++ b/src/app/play/page.tsx
@@ -0,0 +1,1702 @@
+/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-explicit-any, react-hooks/exhaustive-deps, no-console, @next/next/no-img-element */
+
+'use client';
+
+import Artplayer from 'artplayer';
+import Hls from 'hls.js';
+import { Heart } from 'lucide-react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { Suspense, useEffect, useRef, useState } from 'react';
+
+import {
+ deleteFavorite,
+ deletePlayRecord,
+ generateStorageKey,
+ getAllPlayRecords,
+ isFavorited,
+ saveFavorite,
+ savePlayRecord,
+ subscribeToDataUpdates,
+} from '@/lib/db.client';
+import { SearchResult } from '@/lib/types';
+import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
+
+import EpisodeSelector from '@/components/EpisodeSelector';
+import PageLayout from '@/components/PageLayout';
+
+// 扩展 HTMLVideoElement 类型以支持 hls 属性
+declare global {
+ interface HTMLVideoElement {
+ hls?: any;
+ }
+}
+
+function PlayPageClient() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // -----------------------------------------------------------------------------
+ // 状态变量(State)
+ // -----------------------------------------------------------------------------
+ const [loading, setLoading] = useState(true);
+ const [loadingStage, setLoadingStage] = useState<
+ 'searching' | 'preferring' | 'fetching' | 'ready'
+ >('searching');
+ const [loadingMessage, setLoadingMessage] = useState('正在搜索播放源...');
+ const [error, setError] = useState(null);
+ const [detail, setDetail] = useState(null);
+
+ // 收藏状态
+ const [favorited, setFavorited] = useState(false);
+
+ // 去广告开关(从 localStorage 继承,默认 true)
+ const [blockAdEnabled, setBlockAdEnabled] = useState(() => {
+ if (typeof window !== 'undefined') {
+ const v = localStorage.getItem('enable_blockad');
+ if (v !== null) return v === 'true';
+ }
+ return true;
+ });
+ const blockAdEnabledRef = useRef(blockAdEnabled);
+ useEffect(() => {
+ blockAdEnabledRef.current = blockAdEnabled;
+ }, [blockAdEnabled]);
+
+ // 视频基本信息
+ const [videoTitle, setVideoTitle] = useState(searchParams.get('title') || '');
+ const [videoYear, setVideoYear] = useState(searchParams.get('year') || '');
+ const [videoCover, setVideoCover] = useState('');
+ // 当前源和ID
+ const [currentSource, setCurrentSource] = useState(
+ searchParams.get('source') || ''
+ );
+ const [currentId, setCurrentId] = useState(searchParams.get('id') || '');
+
+ // 搜索所需信息
+ const [searchTitle] = useState(searchParams.get('stitle') || '');
+ const [searchType] = useState(searchParams.get('stype') || '');
+
+ // 是否需要优选
+ const [needPrefer, setNeedPrefer] = useState(
+ searchParams.get('prefer') === 'true'
+ );
+ const needPreferRef = useRef(needPrefer);
+ useEffect(() => {
+ needPreferRef.current = needPrefer;
+ }, [needPrefer]);
+ // 集数相关
+ const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(0);
+
+ const currentSourceRef = useRef(currentSource);
+ const currentIdRef = useRef(currentId);
+ const videoTitleRef = useRef(videoTitle);
+ const videoYearRef = useRef(videoYear);
+ const detailRef = useRef(detail);
+ const currentEpisodeIndexRef = useRef(currentEpisodeIndex);
+
+ // 同步最新值到 refs
+ useEffect(() => {
+ currentSourceRef.current = currentSource;
+ currentIdRef.current = currentId;
+ detailRef.current = detail;
+ currentEpisodeIndexRef.current = currentEpisodeIndex;
+ videoTitleRef.current = videoTitle;
+ videoYearRef.current = videoYear;
+ }, [
+ currentSource,
+ currentId,
+ detail,
+ currentEpisodeIndex,
+ videoTitle,
+ videoYear,
+ ]);
+
+ // 视频播放地址
+ const [videoUrl, setVideoUrl] = useState('');
+
+ // 总集数
+ const totalEpisodes = detail?.episodes?.length || 0;
+
+ // 用于记录是否需要在播放器 ready 后跳转到指定进度
+ const resumeTimeRef = useRef(null);
+ // 上次使用的音量,默认 0.7
+ const lastVolumeRef = useRef(0.7);
+
+ // 换源相关状态
+ const [availableSources, setAvailableSources] = useState([]);
+ const [sourceSearchLoading, setSourceSearchLoading] = useState(false);
+ const [sourceSearchError, setSourceSearchError] = useState(
+ null
+ );
+
+ // 优选和测速开关
+ const [optimizationEnabled] = useState(() => {
+ if (typeof window !== 'undefined') {
+ const saved = localStorage.getItem('enableOptimization');
+ if (saved !== null) {
+ try {
+ return JSON.parse(saved);
+ } catch {
+ /* ignore */
+ }
+ }
+ }
+ return true;
+ });
+
+ // 保存优选时的测速结果,避免EpisodeSelector重复测速
+ const [precomputedVideoInfo, setPrecomputedVideoInfo] = useState<
+ Map
+ >(new Map());
+
+ // 折叠状态(仅在 lg 及以上屏幕有效)
+ const [isEpisodeSelectorCollapsed, setIsEpisodeSelectorCollapsed] =
+ useState(false);
+
+ // 换源加载状态
+ const [isVideoLoading, setIsVideoLoading] = useState(true);
+ const [videoLoadingStage, setVideoLoadingStage] = useState<
+ 'initing' | 'sourceChanging'
+ >('initing');
+
+ // 播放进度保存相关
+ const saveIntervalRef = useRef(null);
+ const lastSaveTimeRef = useRef(0);
+
+ const artPlayerRef = useRef(null);
+ const artRef = useRef(null);
+
+ // -----------------------------------------------------------------------------
+ // 工具函数(Utils)
+ // -----------------------------------------------------------------------------
+
+ // 播放源优选函数
+ const preferBestSource = async (
+ sources: SearchResult[]
+ ): Promise => {
+ if (sources.length === 1) return sources[0];
+
+ // 将播放源均分为两批,并发测速各批,避免一次性过多请求
+ const batchSize = Math.ceil(sources.length / 2);
+ const allResults: Array<{
+ source: SearchResult;
+ testResult: { quality: string; loadSpeed: string; pingTime: number };
+ } | null> = [];
+
+ for (let start = 0; start < sources.length; start += batchSize) {
+ const batchSources = sources.slice(start, start + batchSize);
+ const batchResults = await Promise.all(
+ batchSources.map(async (source) => {
+ try {
+ // 检查是否有第一集的播放地址
+ if (!source.episodes || source.episodes.length === 0) {
+ console.warn(`播放源 ${source.source_name} 没有可用的播放地址`);
+ return null;
+ }
+
+ const episodeUrl =
+ source.episodes.length > 1
+ ? source.episodes[1]
+ : source.episodes[0];
+ const testResult = await getVideoResolutionFromM3u8(episodeUrl);
+
+ return {
+ source,
+ testResult,
+ };
+ } catch (error) {
+ return null;
+ }
+ })
+ );
+ allResults.push(...batchResults);
+ }
+
+ // 等待所有测速完成,包含成功和失败的结果
+ // 保存所有测速结果到 precomputedVideoInfo,供 EpisodeSelector 使用(包含错误结果)
+ const newVideoInfoMap = new Map<
+ string,
+ {
+ quality: string;
+ loadSpeed: string;
+ pingTime: number;
+ hasError?: boolean;
+ }
+ >();
+ allResults.forEach((result, index) => {
+ const source = sources[index];
+ const sourceKey = `${source.source}-${source.id}`;
+
+ if (result) {
+ // 成功的结果
+ newVideoInfoMap.set(sourceKey, result.testResult);
+ }
+ });
+
+ // 过滤出成功的结果用于优选计算
+ const successfulResults = allResults.filter(Boolean) as Array<{
+ source: SearchResult;
+ testResult: { quality: string; loadSpeed: string; pingTime: number };
+ }>;
+
+ setPrecomputedVideoInfo(newVideoInfoMap);
+
+ if (successfulResults.length === 0) {
+ console.warn('所有播放源测速都失败,使用第一个播放源');
+ return sources[0];
+ }
+
+ // 找出所有有效速度的最大值,用于线性映射
+ const validSpeeds = successfulResults
+ .map((result) => {
+ const speedStr = result.testResult.loadSpeed;
+ if (speedStr === '未知' || speedStr === '测量中...') return 0;
+
+ const match = speedStr.match(/^([\d.]+)\s*(KB\/s|MB\/s)$/);
+ if (!match) return 0;
+
+ const value = parseFloat(match[1]);
+ const unit = match[2];
+ return unit === 'MB/s' ? value * 1024 : value; // 统一转换为 KB/s
+ })
+ .filter((speed) => speed > 0);
+
+ const maxSpeed = validSpeeds.length > 0 ? Math.max(...validSpeeds) : 1024; // 默认1MB/s作为基准
+
+ // 找出所有有效延迟的最小值和最大值,用于线性映射
+ const validPings = successfulResults
+ .map((result) => result.testResult.pingTime)
+ .filter((ping) => ping > 0);
+
+ const minPing = validPings.length > 0 ? Math.min(...validPings) : 50;
+ const maxPing = validPings.length > 0 ? Math.max(...validPings) : 1000;
+
+ // 计算每个结果的评分
+ const resultsWithScore = successfulResults.map((result) => ({
+ ...result,
+ score: calculateSourceScore(
+ result.testResult,
+ maxSpeed,
+ minPing,
+ maxPing
+ ),
+ }));
+
+ // 按综合评分排序,选择最佳播放源
+ resultsWithScore.sort((a, b) => b.score - a.score);
+
+ console.log('播放源评分排序结果:');
+ resultsWithScore.forEach((result, index) => {
+ console.log(
+ `${index + 1}. ${
+ result.source.source_name
+ } - 评分: ${result.score.toFixed(2)} (${result.testResult.quality}, ${
+ result.testResult.loadSpeed
+ }, ${result.testResult.pingTime}ms)`
+ );
+ });
+
+ return resultsWithScore[0].source;
+ };
+
+ // 计算播放源综合评分
+ const calculateSourceScore = (
+ testResult: {
+ quality: string;
+ loadSpeed: string;
+ pingTime: number;
+ },
+ maxSpeed: number,
+ minPing: number,
+ maxPing: number
+ ): number => {
+ let score = 0;
+
+ // 分辨率评分 (40% 权重)
+ const qualityScore = (() => {
+ switch (testResult.quality) {
+ case '4K':
+ return 100;
+ case '2K':
+ return 85;
+ case '1080p':
+ return 75;
+ case '720p':
+ return 60;
+ case '480p':
+ return 40;
+ case 'SD':
+ return 20;
+ default:
+ return 0;
+ }
+ })();
+ score += qualityScore * 0.4;
+
+ // 下载速度评分 (40% 权重) - 基于最大速度线性映射
+ const speedScore = (() => {
+ const speedStr = testResult.loadSpeed;
+ if (speedStr === '未知' || speedStr === '测量中...') return 30;
+
+ // 解析速度值
+ const match = speedStr.match(/^([\d.]+)\s*(KB\/s|MB\/s)$/);
+ if (!match) return 30;
+
+ const value = parseFloat(match[1]);
+ const unit = match[2];
+ const speedKBps = unit === 'MB/s' ? value * 1024 : value;
+
+ // 基于最大速度线性映射,最高100分
+ const speedRatio = speedKBps / maxSpeed;
+ return Math.min(100, Math.max(0, speedRatio * 100));
+ })();
+ score += speedScore * 0.4;
+
+ // 网络延迟评分 (20% 权重) - 基于延迟范围线性映射
+ const pingScore = (() => {
+ const ping = testResult.pingTime;
+ if (ping <= 0) return 0; // 无效延迟给默认分
+
+ // 如果所有延迟都相同,给满分
+ if (maxPing === minPing) return 100;
+
+ // 线性映射:最低延迟=100分,最高延迟=0分
+ const pingRatio = (maxPing - ping) / (maxPing - minPing);
+ return Math.min(100, Math.max(0, pingRatio * 100));
+ })();
+ score += pingScore * 0.2;
+
+ return Math.round(score * 100) / 100; // 保留两位小数
+ };
+
+ // 更新视频地址
+ const updateVideoUrl = (
+ detailData: SearchResult | null,
+ episodeIndex: number
+ ) => {
+ if (
+ !detailData ||
+ !detailData.episodes ||
+ episodeIndex >= detailData.episodes.length
+ ) {
+ setVideoUrl('');
+ return;
+ }
+ const newUrl = detailData?.episodes[episodeIndex] || '';
+ if (newUrl !== videoUrl) {
+ setVideoUrl(newUrl);
+ }
+ };
+
+ const ensureVideoSource = (video: HTMLVideoElement | null, url: string) => {
+ if (!video || !url) return;
+ const sources = Array.from(video.getElementsByTagName('source'));
+ const existed = sources.some((s) => s.src === url);
+ if (!existed) {
+ // 移除旧的 source,保持唯一
+ sources.forEach((s) => s.remove());
+ const sourceEl = document.createElement('source');
+ sourceEl.src = url;
+ video.appendChild(sourceEl);
+ }
+
+ // 始终允许远程播放(AirPlay / Cast)
+ video.disableRemotePlayback = false;
+ // 如果曾经有禁用属性,移除之
+ if (video.hasAttribute('disableRemotePlayback')) {
+ video.removeAttribute('disableRemotePlayback');
+ }
+ };
+
+ // 去广告相关函数
+ function filterAdsFromM3U8(m3u8Content: string): string {
+ if (!m3u8Content) return '';
+
+ // 按行分割M3U8内容
+ const lines = m3u8Content.split('\n');
+ const filteredLines = [];
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+
+ // 只过滤#EXT-X-DISCONTINUITY标识
+ if (!line.includes('#EXT-X-DISCONTINUITY')) {
+ filteredLines.push(line);
+ }
+ }
+
+ return filteredLines.join('\n');
+ }
+
+ class CustomHlsJsLoader extends Hls.DefaultConfig.loader {
+ constructor(config: any) {
+ super(config);
+ const load = this.load.bind(this);
+ this.load = function (context: any, config: any, callbacks: any) {
+ // 拦截manifest和level请求
+ if (
+ (context as any).type === 'manifest' ||
+ (context as any).type === 'level'
+ ) {
+ const onSuccess = callbacks.onSuccess;
+ callbacks.onSuccess = function (
+ response: any,
+ stats: any,
+ context: any
+ ) {
+ // 如果是m3u8文件,处理内容以移除广告分段
+ if (response.data && typeof response.data === 'string') {
+ // 过滤掉广告段 - 实现更精确的广告过滤逻辑
+ response.data = filterAdsFromM3U8(response.data);
+ }
+ return onSuccess(response, stats, context, null);
+ };
+ }
+ // 执行原始load方法
+ load(context, config, callbacks);
+ };
+ }
+ }
+
+ // 当集数索引变化时自动更新视频地址
+ useEffect(() => {
+ updateVideoUrl(detail, currentEpisodeIndex);
+ }, [detail, currentEpisodeIndex]);
+
+ // 进入页面时直接获取全部源信息
+ useEffect(() => {
+ const fetchSourceDetail = async (
+ source: string,
+ id: string
+ ): Promise => {
+ try {
+ const detailResponse = await fetch(
+ `/api/detail?source=${source}&id=${id}`
+ );
+ if (!detailResponse.ok) {
+ throw new Error('获取视频详情失败');
+ }
+ const detailData = (await detailResponse.json()) as SearchResult;
+ setAvailableSources([detailData]);
+ return [detailData];
+ } catch (err) {
+ console.error('获取视频详情失败:', err);
+ return [];
+ } finally {
+ setSourceSearchLoading(false);
+ }
+ };
+ const fetchSourcesData = async (query: string): Promise => {
+ // 根据搜索词获取全部源信息
+ try {
+ const response = await fetch(
+ `/api/search?q=${encodeURIComponent(query.trim())}`
+ );
+ if (!response.ok) {
+ throw new Error('搜索失败');
+ }
+ const data = await response.json();
+
+ // 处理搜索结果,根据规则过滤
+ const results = data.results.filter(
+ (result: SearchResult) =>
+ result.title.replaceAll(' ', '').toLowerCase() ===
+ videoTitleRef.current.replaceAll(' ', '').toLowerCase() &&
+ (videoYearRef.current
+ ? result.year.toLowerCase() === videoYearRef.current.toLowerCase()
+ : true) &&
+ (searchType
+ ? (searchType === 'tv' && result.episodes.length > 1) ||
+ (searchType === 'movie' && result.episodes.length === 1)
+ : true)
+ );
+ setAvailableSources(results);
+ return results;
+ } catch (err) {
+ setSourceSearchError(err instanceof Error ? err.message : '搜索失败');
+ setAvailableSources([]);
+ return [];
+ } finally {
+ setSourceSearchLoading(false);
+ }
+ };
+
+ const initAll = async () => {
+ if (!currentSource && !currentId && !videoTitle && !searchTitle) {
+ setError('缺少必要参数');
+ setLoading(false);
+ return;
+ }
+ setLoading(true);
+ setLoadingStage(currentSource && currentId ? 'fetching' : 'searching');
+ setLoadingMessage(
+ currentSource && currentId
+ ? '🎬 正在获取视频详情...'
+ : '🔍 正在搜索播放源...'
+ );
+
+ let sourcesInfo = await fetchSourcesData(searchTitle || videoTitle);
+ if (
+ currentSource &&
+ currentId &&
+ !sourcesInfo.some(
+ (source) => source.source === currentSource && source.id === currentId
+ )
+ ) {
+ sourcesInfo = await fetchSourceDetail(currentSource, currentId);
+ }
+ if (sourcesInfo.length === 0) {
+ setError('未找到匹配结果');
+ setLoading(false);
+ return;
+ }
+
+ let detailData: SearchResult = sourcesInfo[0];
+ // 指定源和id且无需优选
+ if (currentSource && currentId && !needPreferRef.current) {
+ const target = sourcesInfo.find(
+ (source) => source.source === currentSource && source.id === currentId
+ );
+ if (target) {
+ detailData = target;
+ } else {
+ setError('未找到匹配结果');
+ setLoading(false);
+ return;
+ }
+ }
+
+ // 未指定源和 id 或需要优选,且开启优选开关
+ if (
+ (!currentSource || !currentId || needPreferRef.current) &&
+ optimizationEnabled
+ ) {
+ setLoadingStage('preferring');
+ setLoadingMessage('⚡ 正在优选最佳播放源...');
+
+ detailData = await preferBestSource(sourcesInfo);
+ }
+
+ console.log(detailData.source, detailData.id);
+
+ setNeedPrefer(false);
+ setCurrentSource(detailData.source);
+ setCurrentId(detailData.id);
+ setVideoYear(detailData.year);
+ setVideoTitle(detailData.title || videoTitleRef.current);
+ setVideoCover(detailData.poster);
+ setDetail(detailData);
+ if (currentEpisodeIndex >= detailData.episodes.length) {
+ setCurrentEpisodeIndex(0);
+ }
+
+ // 规范URL参数
+ const newUrl = new URL(window.location.href);
+ newUrl.searchParams.set('source', detailData.source);
+ newUrl.searchParams.set('id', detailData.id);
+ newUrl.searchParams.set('year', detailData.year);
+ newUrl.searchParams.set('title', detailData.title);
+ newUrl.searchParams.delete('prefer');
+ window.history.replaceState({}, '', newUrl.toString());
+
+ setLoadingStage('ready');
+ setLoadingMessage('✨ 准备就绪,即将开始播放...');
+
+ // 短暂延迟让用户看到完成状态
+ setTimeout(() => {
+ setLoading(false);
+ }, 1000);
+ };
+
+ initAll();
+ }, []);
+
+ // 播放记录处理
+ useEffect(() => {
+ // 仅在初次挂载时检查播放记录
+ const initFromHistory = async () => {
+ if (!currentSource || !currentId) return;
+
+ try {
+ const allRecords = await getAllPlayRecords();
+ const key = generateStorageKey(currentSource, currentId);
+ const record = allRecords[key];
+
+ if (record) {
+ const targetIndex = record.index - 1;
+ const targetTime = record.play_time;
+
+ // 更新当前选集索引
+ if (targetIndex !== currentEpisodeIndex) {
+ setCurrentEpisodeIndex(targetIndex);
+ }
+
+ // 保存待恢复的播放进度,待播放器就绪后跳转
+ resumeTimeRef.current = targetTime;
+ }
+ } catch (err) {
+ console.error('读取播放记录失败:', err);
+ }
+ };
+
+ initFromHistory();
+ }, []);
+
+ // 处理换源
+ const handleSourceChange = async (
+ newSource: string,
+ newId: string,
+ newTitle: string
+ ) => {
+ try {
+ // 显示换源加载状态
+ setVideoLoadingStage('sourceChanging');
+ setIsVideoLoading(true);
+
+ // 记录当前播放进度(仅在同一集数切换时恢复)
+ const currentPlayTime = artPlayerRef.current?.currentTime || 0;
+ console.log('换源前当前播放时间:', currentPlayTime);
+
+ // 清除前一个历史记录
+ if (currentSourceRef.current && currentIdRef.current) {
+ try {
+ await deletePlayRecord(
+ currentSourceRef.current,
+ currentIdRef.current
+ );
+ console.log('已清除前一个播放记录');
+ } catch (err) {
+ console.error('清除播放记录失败:', err);
+ }
+ }
+
+ const newDetail = availableSources.find(
+ (source) => source.source === newSource && source.id === newId
+ );
+ if (!newDetail) {
+ setError('未找到匹配结果');
+ return;
+ }
+
+ // 尝试跳转到当前正在播放的集数
+ let targetIndex = currentEpisodeIndex;
+
+ // 如果当前集数超出新源的范围,则跳转到第一集
+ if (!newDetail.episodes || targetIndex >= newDetail.episodes.length) {
+ targetIndex = 0;
+ }
+
+ // 如果仍然是同一集数且播放进度有效,则在播放器就绪后恢复到原始进度
+ if (targetIndex !== currentEpisodeIndex) {
+ resumeTimeRef.current = 0;
+ } else if (
+ (!resumeTimeRef.current || resumeTimeRef.current === 0) &&
+ currentPlayTime > 1
+ ) {
+ resumeTimeRef.current = currentPlayTime;
+ }
+
+ // 更新URL参数(不刷新页面)
+ const newUrl = new URL(window.location.href);
+ newUrl.searchParams.set('source', newSource);
+ newUrl.searchParams.set('id', newId);
+ newUrl.searchParams.set('year', newDetail.year);
+ window.history.replaceState({}, '', newUrl.toString());
+
+ setVideoTitle(newDetail.title || newTitle);
+ setVideoYear(newDetail.year);
+ setVideoCover(newDetail.poster);
+ setCurrentSource(newSource);
+ setCurrentId(newId);
+ setDetail(newDetail);
+ setCurrentEpisodeIndex(targetIndex);
+ } catch (err) {
+ // 隐藏换源加载状态
+ setIsVideoLoading(false);
+ setError(err instanceof Error ? err.message : '换源失败');
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleKeyboardShortcuts);
+ return () => {
+ document.removeEventListener('keydown', handleKeyboardShortcuts);
+ };
+ }, []);
+
+ // ---------------------------------------------------------------------------
+ // 集数切换
+ // ---------------------------------------------------------------------------
+ // 处理集数切换
+ const handleEpisodeChange = (episodeNumber: number) => {
+ if (episodeNumber >= 0 && episodeNumber < totalEpisodes) {
+ // 在更换集数前保存当前播放进度
+ if (artPlayerRef.current && artPlayerRef.current.paused) {
+ saveCurrentPlayProgress();
+ }
+ setCurrentEpisodeIndex(episodeNumber);
+ }
+ };
+
+ const handlePreviousEpisode = () => {
+ const d = detailRef.current;
+ const idx = currentEpisodeIndexRef.current;
+ if (d && d.episodes && idx > 0) {
+ if (artPlayerRef.current && !artPlayerRef.current.paused) {
+ saveCurrentPlayProgress();
+ }
+ setCurrentEpisodeIndex(idx - 1);
+ }
+ };
+
+ const handleNextEpisode = () => {
+ const d = detailRef.current;
+ const idx = currentEpisodeIndexRef.current;
+ if (d && d.episodes && idx < d.episodes.length - 1) {
+ if (artPlayerRef.current && !artPlayerRef.current.paused) {
+ saveCurrentPlayProgress();
+ }
+ setCurrentEpisodeIndex(idx + 1);
+ }
+ };
+
+ // ---------------------------------------------------------------------------
+ // 键盘快捷键
+ // ---------------------------------------------------------------------------
+ // 处理全局快捷键
+ const handleKeyboardShortcuts = (e: KeyboardEvent) => {
+ // 忽略输入框中的按键事件
+ if (
+ (e.target as HTMLElement).tagName === 'INPUT' ||
+ (e.target as HTMLElement).tagName === 'TEXTAREA'
+ )
+ return;
+
+ // Alt + 左箭头 = 上一集
+ if (e.altKey && e.key === 'ArrowLeft') {
+ if (detailRef.current && currentEpisodeIndexRef.current > 0) {
+ handlePreviousEpisode();
+ e.preventDefault();
+ }
+ }
+
+ // Alt + 右箭头 = 下一集
+ if (e.altKey && e.key === 'ArrowRight') {
+ const d = detailRef.current;
+ const idx = currentEpisodeIndexRef.current;
+ if (d && idx < d.episodes.length - 1) {
+ handleNextEpisode();
+ e.preventDefault();
+ }
+ }
+
+ // 左箭头 = 快退
+ if (!e.altKey && e.key === 'ArrowLeft') {
+ if (artPlayerRef.current && artPlayerRef.current.currentTime > 5) {
+ artPlayerRef.current.currentTime -= 10;
+ e.preventDefault();
+ }
+ }
+
+ // 右箭头 = 快进
+ if (!e.altKey && e.key === 'ArrowRight') {
+ if (
+ artPlayerRef.current &&
+ artPlayerRef.current.currentTime < artPlayerRef.current.duration - 5
+ ) {
+ artPlayerRef.current.currentTime += 10;
+ e.preventDefault();
+ }
+ }
+
+ // 上箭头 = 音量+
+ if (e.key === 'ArrowUp') {
+ if (artPlayerRef.current && artPlayerRef.current.volume < 1) {
+ artPlayerRef.current.volume =
+ Math.round((artPlayerRef.current.volume + 0.1) * 10) / 10;
+ artPlayerRef.current.notice.show = `音量: ${Math.round(
+ artPlayerRef.current.volume * 100
+ )}`;
+ e.preventDefault();
+ }
+ }
+
+ // 下箭头 = 音量-
+ if (e.key === 'ArrowDown') {
+ if (artPlayerRef.current && artPlayerRef.current.volume > 0) {
+ artPlayerRef.current.volume =
+ Math.round((artPlayerRef.current.volume - 0.1) * 10) / 10;
+ artPlayerRef.current.notice.show = `音量: ${Math.round(
+ artPlayerRef.current.volume * 100
+ )}`;
+ e.preventDefault();
+ }
+ }
+
+ // 空格 = 播放/暂停
+ if (e.key === ' ') {
+ if (artPlayerRef.current) {
+ artPlayerRef.current.toggle();
+ e.preventDefault();
+ }
+ }
+
+ // f 键 = 切换全屏
+ if (e.key === 'f' || e.key === 'F') {
+ if (artPlayerRef.current) {
+ artPlayerRef.current.fullscreen = !artPlayerRef.current.fullscreen;
+ e.preventDefault();
+ }
+ }
+ };
+
+ // ---------------------------------------------------------------------------
+ // 播放记录相关
+ // ---------------------------------------------------------------------------
+ // 保存播放进度
+ const saveCurrentPlayProgress = async () => {
+ if (
+ !artPlayerRef.current ||
+ !currentSourceRef.current ||
+ !currentIdRef.current ||
+ !videoTitleRef.current ||
+ !detailRef.current?.source_name
+ ) {
+ return;
+ }
+
+ const player = artPlayerRef.current;
+ const currentTime = player.currentTime || 0;
+ const duration = player.duration || 0;
+
+ // 如果播放时间太短(少于5秒)或者视频时长无效,不保存
+ if (currentTime < 1 || !duration) {
+ return;
+ }
+
+ try {
+ await savePlayRecord(currentSourceRef.current, currentIdRef.current, {
+ title: videoTitleRef.current,
+ source_name: detailRef.current?.source_name || '',
+ year: detailRef.current?.year,
+ cover: detailRef.current?.poster || '',
+ index: currentEpisodeIndexRef.current + 1, // 转换为1基索引
+ total_episodes: detailRef.current?.episodes.length || 1,
+ play_time: Math.floor(currentTime),
+ total_time: Math.floor(duration),
+ save_time: Date.now(),
+ search_title: searchTitle,
+ });
+
+ lastSaveTimeRef.current = Date.now();
+ console.log('播放进度已保存:', {
+ title: videoTitleRef.current,
+ episode: currentEpisodeIndexRef.current + 1,
+ year: detailRef.current?.year,
+ progress: `${Math.floor(currentTime)}/${Math.floor(duration)}`,
+ });
+ } catch (err) {
+ console.error('保存播放进度失败:', err);
+ }
+ };
+
+ useEffect(() => {
+ // 页面即将卸载时保存播放进度
+ const handleBeforeUnload = () => {
+ saveCurrentPlayProgress();
+ };
+
+ // 页面可见性变化时保存播放进度
+ const handleVisibilityChange = () => {
+ if (document.visibilityState === 'hidden') {
+ saveCurrentPlayProgress();
+ }
+ };
+
+ // 添加事件监听器
+ window.addEventListener('beforeunload', handleBeforeUnload);
+ document.addEventListener('visibilitychange', handleVisibilityChange);
+
+ return () => {
+ // 清理事件监听器
+ window.removeEventListener('beforeunload', handleBeforeUnload);
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
+ };
+ }, [currentEpisodeIndex, detail, artPlayerRef.current]);
+
+ // 清理定时器
+ useEffect(() => {
+ return () => {
+ if (saveIntervalRef.current) {
+ clearInterval(saveIntervalRef.current);
+ }
+ };
+ }, []);
+
+ // ---------------------------------------------------------------------------
+ // 收藏相关
+ // ---------------------------------------------------------------------------
+ // 每当 source 或 id 变化时检查收藏状态
+ useEffect(() => {
+ if (!currentSource || !currentId) return;
+ (async () => {
+ try {
+ const fav = await isFavorited(currentSource, currentId);
+ setFavorited(fav);
+ } catch (err) {
+ console.error('检查收藏状态失败:', err);
+ }
+ })();
+ }, [currentSource, currentId]);
+
+ // 监听收藏数据更新事件
+ useEffect(() => {
+ if (!currentSource || !currentId) return;
+
+ const unsubscribe = subscribeToDataUpdates(
+ 'favoritesUpdated',
+ (favorites: Record) => {
+ const key = generateStorageKey(currentSource, currentId);
+ const isFav = !!favorites[key];
+ setFavorited(isFav);
+ }
+ );
+
+ return unsubscribe;
+ }, [currentSource, currentId]);
+
+ // 切换收藏
+ const handleToggleFavorite = async () => {
+ if (
+ !videoTitleRef.current ||
+ !detailRef.current ||
+ !currentSourceRef.current ||
+ !currentIdRef.current
+ )
+ return;
+
+ try {
+ if (favorited) {
+ // 如果已收藏,删除收藏
+ await deleteFavorite(currentSourceRef.current, currentIdRef.current);
+ setFavorited(false);
+ } else {
+ // 如果未收藏,添加收藏
+ await saveFavorite(currentSourceRef.current, currentIdRef.current, {
+ title: videoTitleRef.current,
+ source_name: detailRef.current?.source_name || '',
+ year: detailRef.current?.year,
+ cover: detailRef.current?.poster || '',
+ total_episodes: detailRef.current?.episodes.length || 1,
+ save_time: Date.now(),
+ search_title: searchTitle,
+ });
+ setFavorited(true);
+ }
+ } catch (err) {
+ console.error('切换收藏失败:', err);
+ }
+ };
+
+ useEffect(() => {
+ if (
+ !Artplayer ||
+ !Hls ||
+ !videoUrl ||
+ loading ||
+ currentEpisodeIndex === null ||
+ !artRef.current
+ ) {
+ return;
+ }
+
+ // 确保选集索引有效
+ if (
+ !detail ||
+ !detail.episodes ||
+ currentEpisodeIndex >= detail.episodes.length ||
+ currentEpisodeIndex < 0
+ ) {
+ setError(`选集索引无效,当前共 ${totalEpisodes} 集`);
+ return;
+ }
+
+ if (!videoUrl) {
+ setError('视频地址无效');
+ return;
+ }
+ console.log(videoUrl);
+
+ // 检测是否为WebKit浏览器
+ const isWebkit =
+ typeof window !== 'undefined' &&
+ typeof (window as any).webkitConvertPointFromNodeToPage === 'function';
+
+ // 非WebKit浏览器且播放器已存在,使用switch方法切换
+ if (!isWebkit && artPlayerRef.current) {
+ artPlayerRef.current.switch = videoUrl;
+ artPlayerRef.current.title = `${videoTitle} - 第${
+ currentEpisodeIndex + 1
+ }集`;
+ artPlayerRef.current.poster = videoCover;
+ if (artPlayerRef.current?.video) {
+ ensureVideoSource(
+ artPlayerRef.current.video as HTMLVideoElement,
+ videoUrl
+ );
+ }
+ return;
+ }
+
+ // WebKit浏览器或首次创建:销毁之前的播放器实例并创建新的
+ if (artPlayerRef.current) {
+ if (artPlayerRef.current.video && artPlayerRef.current.video.hls) {
+ artPlayerRef.current.video.hls.destroy();
+ }
+ // 销毁播放器实例
+ artPlayerRef.current.destroy();
+ artPlayerRef.current = null;
+ }
+
+ try {
+ // 创建新的播放器实例
+ Artplayer.PLAYBACK_RATE = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
+ Artplayer.USE_RAF = true;
+
+ artPlayerRef.current = new Artplayer({
+ container: artRef.current,
+ url: videoUrl,
+ poster: videoCover,
+ volume: 0.7,
+ isLive: false,
+ muted: false,
+ autoplay: true,
+ pip: true,
+ autoSize: false,
+ autoMini: false,
+ screenshot: false,
+ setting: true,
+ loop: false,
+ flip: false,
+ playbackRate: true,
+ aspectRatio: false,
+ fullscreen: true,
+ fullscreenWeb: true,
+ subtitleOffset: false,
+ miniProgressBar: false,
+ mutex: true,
+ playsInline: true,
+ autoPlayback: false,
+ airplay: true,
+ theme: '#22c55e',
+ lang: 'zh-cn',
+ hotkey: false,
+ fastForward: true,
+ autoOrientation: true,
+ lock: true,
+ moreVideoAttr: {
+ crossOrigin: 'anonymous',
+ },
+ // HLS 支持配置
+ customType: {
+ m3u8: function (video: HTMLVideoElement, url: string) {
+ if (!Hls) {
+ console.error('HLS.js 未加载');
+ return;
+ }
+
+ if (video.hls) {
+ video.hls.destroy();
+ }
+ const hls = new Hls({
+ debug: false, // 关闭日志
+ enableWorker: true, // WebWorker 解码,降低主线程压力
+ lowLatencyMode: true, // 开启低延迟 LL-HLS
+
+ /* 缓冲/内存相关 */
+ maxBufferLength: 30, // 前向缓冲最大 30s,过大容易导致高延迟
+ backBufferLength: 30, // 仅保留 30s 已播放内容,避免内存占用
+ maxBufferSize: 60 * 1000 * 1000, // 约 60MB,超出后触发清理
+
+ /* 自定义loader */
+ loader: blockAdEnabledRef.current
+ ? CustomHlsJsLoader
+ : Hls.DefaultConfig.loader,
+ });
+
+ hls.loadSource(url);
+ hls.attachMedia(video);
+ video.hls = hls;
+
+ ensureVideoSource(video, url);
+
+ hls.on(Hls.Events.ERROR, function (event: any, data: any) {
+ console.error('HLS Error:', event, data);
+ if (data.fatal) {
+ switch (data.type) {
+ case Hls.ErrorTypes.NETWORK_ERROR:
+ console.log('网络错误,尝试恢复...');
+ hls.startLoad();
+ break;
+ case Hls.ErrorTypes.MEDIA_ERROR:
+ console.log('媒体错误,尝试恢复...');
+ hls.recoverMediaError();
+ break;
+ default:
+ console.log('无法恢复的错误');
+ hls.destroy();
+ break;
+ }
+ }
+ });
+ },
+ },
+ icons: {
+ loading:
+ ' ',
+ },
+ settings: [
+ {
+ html: '去广告',
+ icon: 'AD ',
+ tooltip: blockAdEnabled ? '已开启' : '已关闭',
+ onClick() {
+ const newVal = !blockAdEnabled;
+ try {
+ localStorage.setItem('enable_blockad', String(newVal));
+ if (artPlayerRef.current) {
+ resumeTimeRef.current = artPlayerRef.current.currentTime;
+ if (
+ artPlayerRef.current.video &&
+ artPlayerRef.current.video.hls
+ ) {
+ artPlayerRef.current.video.hls.destroy();
+ }
+ artPlayerRef.current.destroy();
+ artPlayerRef.current = null;
+ }
+ setBlockAdEnabled(newVal);
+ } catch (_) {
+ // ignore
+ }
+ return newVal ? '当前开启' : '当前关闭';
+ },
+ },
+ ],
+ // 控制栏配置
+ controls: [
+ {
+ position: 'left',
+ index: 13,
+ html: ' ',
+ tooltip: '播放下一集',
+ click: function () {
+ handleNextEpisode();
+ },
+ },
+ ],
+ });
+
+ // 监听播放器事件
+ artPlayerRef.current.on('ready', () => {
+ setError(null);
+ });
+
+ artPlayerRef.current.on('video:volumechange', () => {
+ lastVolumeRef.current = artPlayerRef.current.volume;
+ });
+
+ // 监听视频可播放事件,这时恢复播放进度更可靠
+ artPlayerRef.current.on('video:canplay', () => {
+ // 若存在需要恢复的播放进度,则跳转
+ if (resumeTimeRef.current && resumeTimeRef.current > 0) {
+ try {
+ const duration = artPlayerRef.current.duration || 0;
+ let target = resumeTimeRef.current;
+ if (duration && target >= duration - 2) {
+ target = Math.max(0, duration - 5);
+ }
+ artPlayerRef.current.currentTime = target;
+ console.log('成功恢复播放进度到:', resumeTimeRef.current);
+ } catch (err) {
+ console.warn('恢复播放进度失败:', err);
+ }
+ }
+ resumeTimeRef.current = null;
+
+ setTimeout(() => {
+ if (
+ Math.abs(artPlayerRef.current.volume - lastVolumeRef.current) > 0.01
+ ) {
+ artPlayerRef.current.volume = lastVolumeRef.current;
+ }
+ artPlayerRef.current.notice.show = '';
+ }, 0);
+
+ // 隐藏换源加载状态
+ setIsVideoLoading(false);
+ });
+
+ artPlayerRef.current.on('error', (err: any) => {
+ console.error('播放器错误:', err);
+ if (artPlayerRef.current.currentTime > 0) {
+ return;
+ }
+ });
+
+ // 监听视频播放结束事件,自动播放下一集
+ artPlayerRef.current.on('video:ended', () => {
+ const d = detailRef.current;
+ const idx = currentEpisodeIndexRef.current;
+ if (d && d.episodes && idx < d.episodes.length - 1) {
+ setTimeout(() => {
+ setCurrentEpisodeIndex(idx + 1);
+ }, 1000);
+ }
+ });
+
+ artPlayerRef.current.on('video:timeupdate', () => {
+ const now = Date.now();
+ let interval = 5000;
+ if (process.env.NEXT_PUBLIC_STORAGE_TYPE === 'd1') {
+ interval = 10000;
+ }
+ if (process.env.NEXT_PUBLIC_STORAGE_TYPE === 'upstash') {
+ interval = 20000;
+ }
+ if (now - lastSaveTimeRef.current > interval) {
+ saveCurrentPlayProgress();
+ lastSaveTimeRef.current = now;
+ }
+ });
+
+ artPlayerRef.current.on('pause', () => {
+ saveCurrentPlayProgress();
+ });
+
+ if (artPlayerRef.current?.video) {
+ ensureVideoSource(
+ artPlayerRef.current.video as HTMLVideoElement,
+ videoUrl
+ );
+ }
+ } catch (err) {
+ console.error('创建播放器失败:', err);
+ setError('播放器初始化失败');
+ }
+ }, [Artplayer, Hls, videoUrl, loading, blockAdEnabled]);
+
+ // 当组件卸载时清理定时器
+ useEffect(() => {
+ return () => {
+ if (saveIntervalRef.current) {
+ clearInterval(saveIntervalRef.current);
+ }
+ };
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ {/* 动画影院图标 */}
+
+
+
+ {loadingStage === 'searching' && '🔍'}
+ {loadingStage === 'preferring' && '⚡'}
+ {loadingStage === 'fetching' && '🎬'}
+ {loadingStage === 'ready' && '✨'}
+
+ {/* 旋转光环 */}
+
+
+
+ {/* 浮动粒子效果 */}
+
+
+
+ {/* 进度指示器 */}
+
+
+ {/* 加载消息 */}
+
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+ {/* 错误图标 */}
+
+
+ {/* 错误信息 */}
+
+
+ 哎呀,出现了一些问题
+
+
+
+ 请检查网络连接或尝试刷新页面
+
+
+
+ {/* 操作按钮 */}
+
+
+ videoTitle
+ ? router.push(`/search?q=${encodeURIComponent(videoTitle)}`)
+ : router.back()
+ }
+ className='w-full px-6 py-3 bg-gradient-to-r from-green-500 to-emerald-600 text-white rounded-xl font-medium hover:from-green-600 hover:to-emerald-700 transform hover:scale-105 transition-all duration-200 shadow-lg hover:shadow-xl'
+ >
+ {videoTitle ? '🔍 返回搜索' : '← 返回上页'}
+
+
+ window.location.reload()}
+ className='w-full px-6 py-3 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-xl font-medium hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200'
+ >
+ 🔄 重新尝试
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* 第一行:影片标题 */}
+
+
+ {videoTitle || '影片标题'}
+ {totalEpisodes > 1 && (
+
+ {` > 第 ${currentEpisodeIndex + 1} 集`}
+
+ )}
+
+
+ {/* 第二行:播放器和选集 */}
+
+ {/* 折叠控制 - 仅在 lg 及以上屏幕显示 */}
+
+
+ setIsEpisodeSelectorCollapsed(!isEpisodeSelectorCollapsed)
+ }
+ className='group relative flex items-center space-x-1.5 px-3 py-1.5 rounded-full bg-white/80 hover:bg-white dark:bg-gray-800/80 dark:hover:bg-gray-800 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50 shadow-sm hover:shadow-md transition-all duration-200'
+ title={
+ isEpisodeSelectorCollapsed ? '显示选集面板' : '隐藏选集面板'
+ }
+ >
+
+
+
+
+ {isEpisodeSelectorCollapsed ? '显示' : '隐藏'}
+
+
+ {/* 精致的状态指示点 */}
+
+
+
+
+
+ {/* 播放器 */}
+
+
+
+
+ {/* 换源加载蒙层 */}
+ {isVideoLoading && (
+
+
+ {/* 动画影院图标 */}
+
+
+ {/* 换源消息 */}
+
+
+ {videoLoadingStage === 'sourceChanging'
+ ? '🔄 切换播放源...'
+ : '🔄 视频加载中...'}
+
+
+
+
+ )}
+
+
+
+ {/* 选集和换源 - 在移动端始终显示,在 lg 及以上可折叠 */}
+
+
+
+
+
+
+ {/* 详情展示 */}
+
+ {/* 文字区 */}
+
+
+ {/* 标题 */}
+
+ {videoTitle || '影片标题'}
+ {
+ e.stopPropagation();
+ handleToggleFavorite();
+ }}
+ className='ml-3 flex-shrink-0 hover:opacity-80 transition-opacity'
+ >
+
+
+
+
+ {/* 关键信息行 */}
+
+ {detail?.class && (
+
+ {detail.class}
+
+ )}
+ {(detail?.year || videoYear) && (
+ {detail?.year || videoYear}
+ )}
+ {detail?.source_name && (
+
+ {detail.source_name}
+
+ )}
+ {detail?.type_name && {detail.type_name} }
+
+ {/* 剧情简介 */}
+ {detail?.desc && (
+
+ {detail.desc}
+
+ )}
+
+
+
+ {/* 封面展示 */}
+
+
+
+ {videoCover ? (
+
+ ) : (
+
+ 封面图片
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+// FavoriteIcon 组件
+const FavoriteIcon = ({ filled }: { filled: boolean }) => {
+ if (filled) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ );
+};
+
+export default function PlayPage() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx
new file mode 100644
index 0000000..36dba7d
--- /dev/null
+++ b/src/app/search/page.tsx
@@ -0,0 +1,410 @@
+/* eslint-disable react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any */
+'use client';
+
+import { ChevronUp, Search, X } from 'lucide-react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { Suspense, useEffect, useMemo, useState } from 'react';
+
+import {
+ addSearchHistory,
+ clearSearchHistory,
+ deleteSearchHistory,
+ getSearchHistory,
+ subscribeToDataUpdates,
+} from '@/lib/db.client';
+import { SearchResult } from '@/lib/types';
+
+import PageLayout from '@/components/PageLayout';
+import VideoCard from '@/components/VideoCard';
+
+function SearchPageClient() {
+ // 搜索历史
+ const [searchHistory, setSearchHistory] = useState([]);
+ // 返回顶部按钮显示状态
+ const [showBackToTop, setShowBackToTop] = useState(false);
+
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [searchQuery, setSearchQuery] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [showResults, setShowResults] = useState(false);
+ const [searchResults, setSearchResults] = useState([]);
+
+ // 获取默认聚合设置:只读取用户本地设置,默认为 true
+ const getDefaultAggregate = () => {
+ if (typeof window !== 'undefined') {
+ const userSetting = localStorage.getItem('defaultAggregateSearch');
+ if (userSetting !== null) {
+ return JSON.parse(userSetting);
+ }
+ }
+ return true; // 默认启用聚合
+ };
+
+ const [viewMode, setViewMode] = useState<'agg' | 'all'>(() => {
+ return getDefaultAggregate() ? 'agg' : 'all';
+ });
+
+ // 聚合后的结果(按标题和年份分组)
+ const aggregatedResults = useMemo(() => {
+ const map = new Map();
+ searchResults.forEach((item) => {
+ // 使用 title + year + type 作为键,year 必然存在,但依然兜底 'unknown'
+ const key = `${item.title.replaceAll(' ', '')}-${
+ item.year || 'unknown'
+ }-${item.episodes.length === 1 ? 'movie' : 'tv'}`;
+ const arr = map.get(key) || [];
+ arr.push(item);
+ map.set(key, arr);
+ });
+ return Array.from(map.entries()).sort((a, b) => {
+ // 优先排序:标题与搜索词完全一致的排在前面
+ const aExactMatch = a[1][0].title
+ .replaceAll(' ', '')
+ .includes(searchQuery.trim().replaceAll(' ', ''));
+ const bExactMatch = b[1][0].title
+ .replaceAll(' ', '')
+ .includes(searchQuery.trim().replaceAll(' ', ''));
+
+ if (aExactMatch && !bExactMatch) return -1;
+ if (!aExactMatch && bExactMatch) return 1;
+
+ // 年份排序
+ if (a[1][0].year === b[1][0].year) {
+ return a[0].localeCompare(b[0]);
+ } else {
+ // 处理 unknown 的情况
+ const aYear = a[1][0].year;
+ const bYear = b[1][0].year;
+
+ if (aYear === 'unknown' && bYear === 'unknown') {
+ return 0;
+ } else if (aYear === 'unknown') {
+ return 1; // a 排在后面
+ } else if (bYear === 'unknown') {
+ return -1; // b 排在后面
+ } else {
+ // 都是数字年份,按数字大小排序(大的在前面)
+ return aYear > bYear ? -1 : 1;
+ }
+ }
+ });
+ }, [searchResults]);
+
+ useEffect(() => {
+ // 无搜索参数时聚焦搜索框
+ !searchParams.get('q') && document.getElementById('searchInput')?.focus();
+
+ // 初始加载搜索历史
+ getSearchHistory().then(setSearchHistory);
+
+ // 监听搜索历史更新事件
+ const unsubscribe = subscribeToDataUpdates(
+ 'searchHistoryUpdated',
+ (newHistory: string[]) => {
+ setSearchHistory(newHistory);
+ }
+ );
+
+ // 获取滚动位置的函数 - 专门针对 body 滚动
+ const getScrollTop = () => {
+ return document.body.scrollTop || 0;
+ };
+
+ // 使用 requestAnimationFrame 持续检测滚动位置
+ let isRunning = false;
+ const checkScrollPosition = () => {
+ if (!isRunning) return;
+
+ const scrollTop = getScrollTop();
+ const shouldShow = scrollTop > 300;
+ setShowBackToTop(shouldShow);
+
+ requestAnimationFrame(checkScrollPosition);
+ };
+
+ // 启动持续检测
+ isRunning = true;
+ checkScrollPosition();
+
+ // 监听 body 元素的滚动事件
+ const handleScroll = () => {
+ const scrollTop = getScrollTop();
+ setShowBackToTop(scrollTop > 300);
+ };
+
+ document.body.addEventListener('scroll', handleScroll, { passive: true });
+
+ return () => {
+ unsubscribe();
+ isRunning = false; // 停止 requestAnimationFrame 循环
+
+ // 移除 body 滚动事件监听器
+ document.body.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ useEffect(() => {
+ // 当搜索参数变化时更新搜索状态
+ const query = searchParams.get('q');
+ if (query) {
+ setSearchQuery(query);
+ fetchSearchResults(query);
+
+ // 保存到搜索历史 (事件监听会自动更新界面)
+ addSearchHistory(query);
+ } else {
+ setShowResults(false);
+ }
+ }, [searchParams]);
+
+ const fetchSearchResults = async (query: string) => {
+ try {
+ setIsLoading(true);
+ const response = await fetch(
+ `/api/search?q=${encodeURIComponent(query.trim())}`
+ );
+ const data = await response.json();
+ setSearchResults(
+ data.results.sort((a: SearchResult, b: SearchResult) => {
+ // 优先排序:标题与搜索词完全一致的排在前面
+ const aExactMatch = a.title === query.trim();
+ const bExactMatch = b.title === query.trim();
+
+ if (aExactMatch && !bExactMatch) return -1;
+ if (!aExactMatch && bExactMatch) return 1;
+
+ // 如果都匹配或都不匹配,则按原来的逻辑排序
+ if (a.year === b.year) {
+ return a.title.localeCompare(b.title);
+ } else {
+ // 处理 unknown 的情况
+ if (a.year === 'unknown' && b.year === 'unknown') {
+ return 0;
+ } else if (a.year === 'unknown') {
+ return 1; // a 排在后面
+ } else if (b.year === 'unknown') {
+ return -1; // b 排在后面
+ } else {
+ // 都是数字年份,按数字大小排序(大的在前面)
+ return parseInt(a.year) > parseInt(b.year) ? -1 : 1;
+ }
+ }
+ })
+ );
+ setShowResults(true);
+ } catch (error) {
+ setSearchResults([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSearch = (e: React.FormEvent) => {
+ e.preventDefault();
+ const trimmed = searchQuery.trim().replace(/\s+/g, ' ');
+ if (!trimmed) return;
+
+ // 回显搜索框
+ setSearchQuery(trimmed);
+ setIsLoading(true);
+ setShowResults(true);
+
+ router.push(`/search?q=${encodeURIComponent(trimmed)}`);
+ // 直接发请求
+ fetchSearchResults(trimmed);
+
+ // 保存到搜索历史 (事件监听会自动更新界面)
+ addSearchHistory(trimmed);
+ };
+
+ // 返回顶部功能
+ const scrollToTop = () => {
+ try {
+ // 根据调试结果,真正的滚动容器是 document.body
+ document.body.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ } catch (error) {
+ // 如果平滑滚动完全失败,使用立即滚动
+ document.body.scrollTop = 0;
+ }
+ };
+
+ return (
+
+
+ {/* 搜索框 */}
+
+
+ {/* 搜索结果或搜索历史 */}
+
+ {isLoading ? (
+
+ ) : showResults ? (
+
+ {/* 标题 + 聚合开关 */}
+
+
+ 搜索结果
+
+ {/* 聚合开关 */}
+
+
+ 聚合
+
+
+
+ setViewMode(viewMode === 'agg' ? 'all' : 'agg')
+ }
+ />
+
+
+
+
+
+
+ {viewMode === 'agg'
+ ? aggregatedResults.map(([mapKey, group]) => {
+ return (
+
+
+
+ );
+ })
+ : searchResults.map((item) => (
+
+ 1 ? 'tv' : 'movie'}
+ />
+
+ ))}
+ {searchResults.length === 0 && (
+
+ 未找到相关结果
+
+ )}
+
+
+ ) : searchHistory.length > 0 ? (
+ // 搜索历史
+
+
+ 搜索历史
+ {searchHistory.length > 0 && (
+ {
+ clearSearchHistory(); // 事件监听会自动更新界面
+ }}
+ className='ml-3 text-sm text-gray-500 hover:text-red-500 transition-colors dark:text-gray-400 dark:hover:text-red-500'
+ >
+ 清空
+
+ )}
+
+
+ {searchHistory.map((item) => (
+
+ {
+ setSearchQuery(item);
+ router.push(
+ `/search?q=${encodeURIComponent(item.trim())}`
+ );
+ }}
+ className='px-4 py-2 bg-gray-500/10 hover:bg-gray-300 rounded-full text-sm text-gray-700 transition-colors duration-200 dark:bg-gray-700/50 dark:hover:bg-gray-600 dark:text-gray-300'
+ >
+ {item}
+
+ {/* 删除按钮 */}
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ deleteSearchHistory(item); // 事件监听会自动更新界面
+ }}
+ className='absolute -top-1 -right-1 w-4 h-4 opacity-0 group-hover:opacity-100 bg-gray-400 hover:bg-red-500 text-white rounded-full flex items-center justify-center text-[10px] transition-colors'
+ >
+
+
+
+ ))}
+
+
+ ) : null}
+
+
+
+ {/* 返回顶部悬浮按钮 */}
+
+
+
+
+ );
+}
+
+export default function SearchPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/warning/page.tsx b/src/app/warning/page.tsx
new file mode 100644
index 0000000..b3dfa95
--- /dev/null
+++ b/src/app/warning/page.tsx
@@ -0,0 +1,97 @@
+import { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: '安全警告 - MoonTV',
+ description: '站点安全配置警告',
+};
+
+export default function WarningPage() {
+ return (
+
+
+ {/* 警告图标 */}
+
+
+ {/* 标题 */}
+
+
+ {/* 警告内容 */}
+
+
+
+ ⚠️ 安全风险提示
+
+
+ 检测到您的站点未配置访问控制,存在潜在的安全风险和法律合规问题。
+
+
+
+
+
+ 主要风险
+
+
+
+ •
+ 未经授权的访问可能导致内容被恶意传播
+
+
+ •
+ 服务器资源可能被滥用,影响正常服务
+
+
+ •
+ 可能收到相关权利方的法律通知
+
+
+ •
+ 服务提供商可能因合规问题终止服务
+
+
+
+
+
+
+ 🔒 安全配置建议
+
+
+ 请立即配置{' '}
+
+ PASSWORD
+
{' '}
+ 环境变量以启用访问控制。
+
+
+
+
+ {/* 底部装饰 */}
+
+
+
为确保系统安全性和合规性,请及时完成安全配置
+
+
+
+
+ );
+}
diff --git a/src/components/BackButton.tsx b/src/components/BackButton.tsx
new file mode 100644
index 0000000..149ee56
--- /dev/null
+++ b/src/components/BackButton.tsx
@@ -0,0 +1,13 @@
+import { ArrowLeft } from 'lucide-react';
+
+export function BackButton() {
+ return (
+ window.history.back()}
+ className='w-10 h-10 p-2 rounded-full flex items-center justify-center text-gray-600 hover:bg-gray-200/50 dark:text-gray-300 dark:hover:bg-gray-700/50 transition-colors'
+ aria-label='Back'
+ >
+
+
+ );
+}
diff --git a/src/components/CapsuleSwitch.tsx b/src/components/CapsuleSwitch.tsx
new file mode 100644
index 0000000..abaea15
--- /dev/null
+++ b/src/components/CapsuleSwitch.tsx
@@ -0,0 +1,103 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+
+import React, { useEffect, useRef, useState } from 'react';
+
+interface CapsuleSwitchProps {
+ options: { label: string; value: string }[];
+ active: string;
+ onChange: (value: string) => void;
+ className?: string;
+}
+
+const CapsuleSwitch: React.FC = ({
+ options,
+ active,
+ onChange,
+ className,
+}) => {
+ const containerRef = useRef(null);
+ const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
+ const [indicatorStyle, setIndicatorStyle] = useState<{
+ left: number;
+ width: number;
+ }>({ left: 0, width: 0 });
+
+ const activeIndex = options.findIndex((opt) => opt.value === active);
+
+ // 更新指示器位置
+ const updateIndicatorPosition = () => {
+ if (
+ activeIndex >= 0 &&
+ buttonRefs.current[activeIndex] &&
+ containerRef.current
+ ) {
+ const button = buttonRefs.current[activeIndex];
+ const container = containerRef.current;
+ if (button && container) {
+ const buttonRect = button.getBoundingClientRect();
+ const containerRect = container.getBoundingClientRect();
+
+ if (buttonRect.width > 0) {
+ setIndicatorStyle({
+ left: buttonRect.left - containerRect.left,
+ width: buttonRect.width,
+ });
+ }
+ }
+ }
+ };
+
+ // 组件挂载时立即计算初始位置
+ useEffect(() => {
+ const timeoutId = setTimeout(updateIndicatorPosition, 0);
+ return () => clearTimeout(timeoutId);
+ }, []);
+
+ // 监听选中项变化
+ useEffect(() => {
+ const timeoutId = setTimeout(updateIndicatorPosition, 0);
+ return () => clearTimeout(timeoutId);
+ }, [activeIndex]);
+
+ return (
+
+ {/* 滑动的白色背景指示器 */}
+ {indicatorStyle.width > 0 && (
+
+ )}
+
+ {options.map((opt, index) => {
+ const isActive = active === opt.value;
+ return (
+
{
+ buttonRefs.current[index] = el;
+ }}
+ onClick={() => onChange(opt.value)}
+ className={`relative z-10 w-16 px-3 py-1 text-xs sm:w-20 sm:py-2 sm:text-sm rounded-full font-medium transition-all duration-200 cursor-pointer ${
+ isActive
+ ? 'text-gray-900 dark:text-gray-100'
+ : 'text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100'
+ }`}
+ >
+ {opt.label}
+
+ );
+ })}
+
+ );
+};
+
+export default CapsuleSwitch;
diff --git a/src/components/ContinueWatching.tsx b/src/components/ContinueWatching.tsx
new file mode 100644
index 0000000..0a9d882
--- /dev/null
+++ b/src/components/ContinueWatching.tsx
@@ -0,0 +1,154 @@
+/* eslint-disable no-console */
+'use client';
+
+import { useEffect, useState } from 'react';
+
+import type { PlayRecord } from '@/lib/db.client';
+import {
+ clearAllPlayRecords,
+ getAllPlayRecords,
+ subscribeToDataUpdates,
+} from '@/lib/db.client';
+
+import ScrollableRow from '@/components/ScrollableRow';
+import VideoCard from '@/components/VideoCard';
+
+interface ContinueWatchingProps {
+ className?: string;
+}
+
+export default function ContinueWatching({ className }: ContinueWatchingProps) {
+ const [playRecords, setPlayRecords] = useState<
+ (PlayRecord & { key: string })[]
+ >([]);
+ const [loading, setLoading] = useState(true);
+
+ // 处理播放记录数据更新的函数
+ const updatePlayRecords = (allRecords: Record) => {
+ // 将记录转换为数组并根据 save_time 由近到远排序
+ const recordsArray = Object.entries(allRecords).map(([key, record]) => ({
+ ...record,
+ key,
+ }));
+
+ // 按 save_time 降序排序(最新的在前面)
+ const sortedRecords = recordsArray.sort(
+ (a, b) => b.save_time - a.save_time
+ );
+
+ setPlayRecords(sortedRecords);
+ };
+
+ useEffect(() => {
+ const fetchPlayRecords = async () => {
+ try {
+ setLoading(true);
+
+ // 从缓存或API获取所有播放记录
+ const allRecords = await getAllPlayRecords();
+ updatePlayRecords(allRecords);
+ } catch (error) {
+ console.error('获取播放记录失败:', error);
+ setPlayRecords([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchPlayRecords();
+
+ // 监听播放记录更新事件
+ const unsubscribe = subscribeToDataUpdates(
+ 'playRecordsUpdated',
+ (newRecords: Record) => {
+ updatePlayRecords(newRecords);
+ }
+ );
+
+ return unsubscribe;
+ }, []);
+
+ // 如果没有播放记录,则不渲染组件
+ if (!loading && playRecords.length === 0) {
+ return null;
+ }
+
+ // 计算播放进度百分比
+ const getProgress = (record: PlayRecord) => {
+ if (record.total_time === 0) return 0;
+ return (record.play_time / record.total_time) * 100;
+ };
+
+ // 从 key 中解析 source 和 id
+ const parseKey = (key: string) => {
+ const [source, id] = key.split('+');
+ return { source, id };
+ };
+
+ return (
+
+
+
+ 继续观看
+
+ {!loading && playRecords.length > 0 && (
+ {
+ await clearAllPlayRecords();
+ setPlayRecords([]);
+ }}
+ >
+ 清空
+
+ )}
+
+
+ {loading
+ ? // 加载状态显示灰色占位数据
+ Array.from({ length: 6 }).map((_, index) => (
+
+ ))
+ : // 显示真实数据
+ playRecords.map((record) => {
+ const { source, id } = parseKey(record.key);
+ return (
+
+
+ setPlayRecords((prev) =>
+ prev.filter((r) => r.key !== record.key)
+ )
+ }
+ type={record.total_episodes > 1 ? 'tv' : ''}
+ />
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/DoubanCardSkeleton.tsx b/src/components/DoubanCardSkeleton.tsx
new file mode 100644
index 0000000..1d4e80b
--- /dev/null
+++ b/src/components/DoubanCardSkeleton.tsx
@@ -0,0 +1,21 @@
+import { ImagePlaceholder } from '@/components/ImagePlaceholder';
+
+const DoubanCardSkeleton = () => {
+ return (
+
+
+ {/* 图片占位符 - 骨架屏效果 */}
+
+
+ {/* 信息层骨架 */}
+
+
+
+ );
+};
+
+export default DoubanCardSkeleton;
diff --git a/src/components/DoubanSelector.tsx b/src/components/DoubanSelector.tsx
new file mode 100644
index 0000000..cfe6e3c
--- /dev/null
+++ b/src/components/DoubanSelector.tsx
@@ -0,0 +1,330 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+
+'use client';
+
+import React, { useEffect, useRef, useState } from 'react';
+
+interface SelectorOption {
+ label: string;
+ value: string;
+}
+
+interface DoubanSelectorProps {
+ type: 'movie' | 'tv' | 'show';
+ primarySelection?: string;
+ secondarySelection?: string;
+ onPrimaryChange: (value: string) => void;
+ onSecondaryChange: (value: string) => void;
+}
+
+const DoubanSelector: React.FC = ({
+ type,
+ primarySelection,
+ secondarySelection,
+ onPrimaryChange,
+ onSecondaryChange,
+}) => {
+ // 为不同的选择器创建独立的refs和状态
+ const primaryContainerRef = useRef(null);
+ const primaryButtonRefs = useRef<(HTMLButtonElement | null)[]>([]);
+ const [primaryIndicatorStyle, setPrimaryIndicatorStyle] = useState<{
+ left: number;
+ width: number;
+ }>({ left: 0, width: 0 });
+
+ const secondaryContainerRef = useRef(null);
+ const secondaryButtonRefs = useRef<(HTMLButtonElement | null)[]>([]);
+ const [secondaryIndicatorStyle, setSecondaryIndicatorStyle] = useState<{
+ left: number;
+ width: number;
+ }>({ left: 0, width: 0 });
+
+ // 电影的一级选择器选项
+ const moviePrimaryOptions: SelectorOption[] = [
+ { label: '热门电影', value: '热门' },
+ { label: '最新电影', value: '最新' },
+ { label: '豆瓣高分', value: '豆瓣高分' },
+ { label: '冷门佳片', value: '冷门佳片' },
+ ];
+
+ // 电影的二级选择器选项
+ const movieSecondaryOptions: SelectorOption[] = [
+ { label: '全部', value: '全部' },
+ { label: '华语', value: '华语' },
+ { label: '欧美', value: '欧美' },
+ { label: '韩国', value: '韩国' },
+ { label: '日本', value: '日本' },
+ ];
+
+ // 电视剧选择器选项
+ const tvOptions: SelectorOption[] = [
+ { label: '全部', value: 'tv' },
+ { label: '国产', value: 'tv_domestic' },
+ { label: '欧美', value: 'tv_american' },
+ { label: '日本', value: 'tv_japanese' },
+ { label: '韩国', value: 'tv_korean' },
+ { label: '动漫', value: 'tv_animation' },
+ { label: '纪录片', value: 'tv_documentary' },
+ ];
+
+ // 综艺选择器选项
+ const showOptions: SelectorOption[] = [
+ { label: '全部', value: 'show' },
+ { label: '国内', value: 'show_domestic' },
+ { label: '国外', value: 'show_foreign' },
+ ];
+
+ // 更新指示器位置的通用函数
+ const updateIndicatorPosition = (
+ activeIndex: number,
+ containerRef: React.RefObject,
+ buttonRefs: React.MutableRefObject<(HTMLButtonElement | null)[]>,
+ setIndicatorStyle: React.Dispatch<
+ React.SetStateAction<{ left: number; width: number }>
+ >
+ ) => {
+ if (
+ activeIndex >= 0 &&
+ buttonRefs.current[activeIndex] &&
+ containerRef.current
+ ) {
+ const timeoutId = setTimeout(() => {
+ const button = buttonRefs.current[activeIndex];
+ const container = containerRef.current;
+ if (button && container) {
+ const buttonRect = button.getBoundingClientRect();
+ const containerRect = container.getBoundingClientRect();
+
+ if (buttonRect.width > 0) {
+ setIndicatorStyle({
+ left: buttonRect.left - containerRect.left,
+ width: buttonRect.width,
+ });
+ }
+ }
+ }, 0);
+ return () => clearTimeout(timeoutId);
+ }
+ };
+
+ // 组件挂载时立即计算初始位置
+ useEffect(() => {
+ // 主选择器初始位置
+ if (type === 'movie') {
+ const activeIndex = moviePrimaryOptions.findIndex(
+ (opt) =>
+ opt.value === (primarySelection || moviePrimaryOptions[0].value)
+ );
+ updateIndicatorPosition(
+ activeIndex,
+ primaryContainerRef,
+ primaryButtonRefs,
+ setPrimaryIndicatorStyle
+ );
+ }
+
+ // 副选择器初始位置
+ let secondaryActiveIndex = -1;
+ if (type === 'movie') {
+ secondaryActiveIndex = movieSecondaryOptions.findIndex(
+ (opt) =>
+ opt.value === (secondarySelection || movieSecondaryOptions[0].value)
+ );
+ } else if (type === 'tv') {
+ secondaryActiveIndex = tvOptions.findIndex(
+ (opt) => opt.value === (secondarySelection || tvOptions[0].value)
+ );
+ } else if (type === 'show') {
+ secondaryActiveIndex = showOptions.findIndex(
+ (opt) => opt.value === (secondarySelection || showOptions[0].value)
+ );
+ }
+
+ if (secondaryActiveIndex >= 0) {
+ updateIndicatorPosition(
+ secondaryActiveIndex,
+ secondaryContainerRef,
+ secondaryButtonRefs,
+ setSecondaryIndicatorStyle
+ );
+ }
+ }, [type]); // 只在type变化时重新计算
+
+ // 监听主选择器变化
+ useEffect(() => {
+ if (type === 'movie') {
+ const activeIndex = moviePrimaryOptions.findIndex(
+ (opt) => opt.value === primarySelection
+ );
+ const cleanup = updateIndicatorPosition(
+ activeIndex,
+ primaryContainerRef,
+ primaryButtonRefs,
+ setPrimaryIndicatorStyle
+ );
+ return cleanup;
+ }
+ }, [primarySelection]);
+
+ // 监听副选择器变化
+ useEffect(() => {
+ let activeIndex = -1;
+ let options: SelectorOption[] = [];
+
+ if (type === 'movie') {
+ activeIndex = movieSecondaryOptions.findIndex(
+ (opt) => opt.value === secondarySelection
+ );
+ options = movieSecondaryOptions;
+ } else if (type === 'tv') {
+ activeIndex = tvOptions.findIndex(
+ (opt) => opt.value === secondarySelection
+ );
+ options = tvOptions;
+ } else if (type === 'show') {
+ activeIndex = showOptions.findIndex(
+ (opt) => opt.value === secondarySelection
+ );
+ options = showOptions;
+ }
+
+ if (options.length > 0) {
+ const cleanup = updateIndicatorPosition(
+ activeIndex,
+ secondaryContainerRef,
+ secondaryButtonRefs,
+ setSecondaryIndicatorStyle
+ );
+ return cleanup;
+ }
+ }, [secondarySelection]);
+
+ // 渲染胶囊式选择器
+ const renderCapsuleSelector = (
+ options: SelectorOption[],
+ activeValue: string | undefined,
+ onChange: (value: string) => void,
+ isPrimary = false
+ ) => {
+ const containerRef = isPrimary
+ ? primaryContainerRef
+ : secondaryContainerRef;
+ const buttonRefs = isPrimary ? primaryButtonRefs : secondaryButtonRefs;
+ const indicatorStyle = isPrimary
+ ? primaryIndicatorStyle
+ : secondaryIndicatorStyle;
+
+ return (
+
+ {/* 滑动的白色背景指示器 */}
+ {indicatorStyle.width > 0 && (
+
+ )}
+
+ {options.map((option, index) => {
+ const isActive = activeValue === option.value;
+ return (
+
{
+ buttonRefs.current[index] = el;
+ }}
+ onClick={() => onChange(option.value)}
+ className={`relative z-10 px-2 py-1 sm:px-4 sm:py-2 text-xs sm:text-sm font-medium rounded-full transition-all duration-200 whitespace-nowrap ${
+ isActive
+ ? 'text-gray-900 dark:text-gray-100 cursor-default'
+ : 'text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 cursor-pointer'
+ }`}
+ >
+ {option.label}
+
+ );
+ })}
+
+ );
+ };
+
+ return (
+
+ {/* 电影类型 - 显示两级选择器 */}
+ {type === 'movie' && (
+
+ {/* 一级选择器 */}
+
+
+ 分类
+
+
+ {renderCapsuleSelector(
+ moviePrimaryOptions,
+ primarySelection || moviePrimaryOptions[0].value,
+ onPrimaryChange,
+ true
+ )}
+
+
+
+ {/* 二级选择器 */}
+
+
+ 地区
+
+
+ {renderCapsuleSelector(
+ movieSecondaryOptions,
+ secondarySelection || movieSecondaryOptions[0].value,
+ onSecondaryChange,
+ false
+ )}
+
+
+
+ )}
+
+ {/* 电视剧类型 - 只显示一级选择器 */}
+ {type === 'tv' && (
+
+
+ 类型
+
+
+ {renderCapsuleSelector(
+ tvOptions,
+ secondarySelection || tvOptions[0].value,
+ onSecondaryChange,
+ false
+ )}
+
+
+ )}
+
+ {/* 综艺类型 - 只显示一级选择器 */}
+ {type === 'show' && (
+
+
+ 类型
+
+
+ {renderCapsuleSelector(
+ showOptions,
+ secondarySelection || showOptions[0].value,
+ onSecondaryChange,
+ false
+ )}
+
+
+ )}
+
+ );
+};
+
+export default DoubanSelector;
diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx
new file mode 100644
index 0000000..5508ad8
--- /dev/null
+++ b/src/components/EpisodeSelector.tsx
@@ -0,0 +1,602 @@
+/* eslint-disable @next/next/no-img-element */
+
+import { useRouter } from 'next/navigation';
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+
+import { SearchResult } from '@/lib/types';
+import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
+
+// 定义视频信息类型
+interface VideoInfo {
+ quality: string;
+ loadSpeed: string;
+ pingTime: number;
+ hasError?: boolean; // 添加错误状态标识
+}
+
+interface EpisodeSelectorProps {
+ /** 总集数 */
+ totalEpisodes: number;
+ /** 每页显示多少集,默认 50 */
+ episodesPerPage?: number;
+ /** 当前选中的集数(1 开始) */
+ value?: number;
+ /** 用户点击选集后的回调 */
+ onChange?: (episodeNumber: number) => void;
+ /** 换源相关 */
+ onSourceChange?: (source: string, id: string, title: string) => void;
+ currentSource?: string;
+ currentId?: string;
+ videoTitle?: string;
+ videoYear?: string;
+ availableSources?: SearchResult[];
+ sourceSearchLoading?: boolean;
+ sourceSearchError?: string | null;
+ /** 预计算的测速结果,避免重复测速 */
+ precomputedVideoInfo?: Map;
+}
+
+/**
+ * 选集组件,支持分页、自动滚动聚焦当前分页标签,以及换源功能。
+ */
+const EpisodeSelector: React.FC = ({
+ totalEpisodes,
+ episodesPerPage = 50,
+ value = 1,
+ onChange,
+ onSourceChange,
+ currentSource,
+ currentId,
+ videoTitle,
+ availableSources = [],
+ sourceSearchLoading = false,
+ sourceSearchError = null,
+ precomputedVideoInfo,
+}) => {
+ const router = useRouter();
+ const pageCount = Math.ceil(totalEpisodes / episodesPerPage);
+
+ // 存储每个源的视频信息
+ const [videoInfoMap, setVideoInfoMap] = useState>(
+ new Map()
+ );
+ const [attemptedSources, setAttemptedSources] = useState>(
+ new Set()
+ );
+
+ // 使用 ref 来避免闭包问题
+ const attemptedSourcesRef = useRef>(new Set());
+ const videoInfoMapRef = useRef>(new Map());
+
+ // 同步状态到 ref
+ useEffect(() => {
+ attemptedSourcesRef.current = attemptedSources;
+ }, [attemptedSources]);
+
+ useEffect(() => {
+ videoInfoMapRef.current = videoInfoMap;
+ }, [videoInfoMap]);
+
+ // 主要的 tab 状态:'episodes' 或 'sources'
+ // 当只有一集时默认展示 "换源",并隐藏 "选集" 标签
+ const [activeTab, setActiveTab] = useState<'episodes' | 'sources'>(
+ totalEpisodes > 1 ? 'episodes' : 'sources'
+ );
+
+ // 当前分页索引(0 开始)
+ const initialPage = Math.floor((value - 1) / episodesPerPage);
+ const [currentPage, setCurrentPage] = useState(initialPage);
+
+ // 是否倒序显示
+ const [descending, setDescending] = useState(false);
+
+ // 获取视频信息的函数 - 移除 attemptedSources 依赖避免不必要的重新创建
+ const getVideoInfo = useCallback(async (source: SearchResult) => {
+ const sourceKey = `${source.source}-${source.id}`;
+
+ // 使用 ref 获取最新的状态,避免闭包问题
+ if (attemptedSourcesRef.current.has(sourceKey)) {
+ return;
+ }
+
+ // 获取第一集的URL
+ if (!source.episodes || source.episodes.length === 0) {
+ return;
+ }
+ const episodeUrl =
+ source.episodes.length > 1 ? source.episodes[1] : source.episodes[0];
+
+ // 标记为已尝试
+ setAttemptedSources((prev) => new Set(prev).add(sourceKey));
+
+ try {
+ const info = await getVideoResolutionFromM3u8(episodeUrl);
+ setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
+ } catch (error) {
+ // 失败时保存错误状态
+ setVideoInfoMap((prev) =>
+ new Map(prev).set(sourceKey, {
+ quality: '错误',
+ loadSpeed: '未知',
+ pingTime: 0,
+ hasError: true,
+ })
+ );
+ }
+ }, []);
+
+ // 当有预计算结果时,先合并到videoInfoMap中
+ useEffect(() => {
+ if (precomputedVideoInfo && precomputedVideoInfo.size > 0) {
+ // 原子性地更新两个状态,避免时序问题
+ setVideoInfoMap((prev) => {
+ const newMap = new Map(prev);
+ precomputedVideoInfo.forEach((value, key) => {
+ newMap.set(key, value);
+ });
+ return newMap;
+ });
+
+ setAttemptedSources((prev) => {
+ const newSet = new Set(prev);
+ precomputedVideoInfo.forEach((info, key) => {
+ if (!info.hasError) {
+ newSet.add(key);
+ }
+ });
+ return newSet;
+ });
+
+ // 同步更新 ref,确保 getVideoInfo 能立即看到更新
+ precomputedVideoInfo.forEach((info, key) => {
+ if (!info.hasError) {
+ attemptedSourcesRef.current.add(key);
+ }
+ });
+ }
+ }, [precomputedVideoInfo]);
+
+ // 读取本地“优选和测速”开关,默认开启
+ const [optimizationEnabled] = useState(() => {
+ if (typeof window !== 'undefined') {
+ const saved = localStorage.getItem('enableOptimization');
+ if (saved !== null) {
+ try {
+ return JSON.parse(saved);
+ } catch {
+ /* ignore */
+ }
+ }
+ }
+ return true;
+ });
+
+ // 当切换到换源tab并且有源数据时,异步获取视频信息 - 移除 attemptedSources 依赖避免循环触发
+ useEffect(() => {
+ const fetchVideoInfosInBatches = async () => {
+ if (
+ !optimizationEnabled || // 若关闭测速则直接退出
+ activeTab !== 'sources' ||
+ availableSources.length === 0
+ )
+ return;
+
+ // 筛选出尚未测速的播放源
+ const pendingSources = availableSources.filter((source) => {
+ const sourceKey = `${source.source}-${source.id}`;
+ return !attemptedSourcesRef.current.has(sourceKey);
+ });
+
+ if (pendingSources.length === 0) return;
+
+ const batchSize = Math.ceil(pendingSources.length / 2);
+
+ for (let start = 0; start < pendingSources.length; start += batchSize) {
+ const batch = pendingSources.slice(start, start + batchSize);
+ await Promise.all(batch.map(getVideoInfo));
+ }
+ };
+
+ fetchVideoInfosInBatches();
+ // 依赖项保持与之前一致
+ }, [activeTab, availableSources, getVideoInfo, optimizationEnabled]);
+
+ // 升序分页标签
+ const categoriesAsc = useMemo(() => {
+ return Array.from({ length: pageCount }, (_, i) => {
+ const start = i * episodesPerPage + 1;
+ const end = Math.min(start + episodesPerPage - 1, totalEpisodes);
+ return `${start}-${end}`;
+ });
+ }, [pageCount, episodesPerPage, totalEpisodes]);
+
+ // 分页标签始终保持升序
+ const categories = categoriesAsc;
+
+ const categoryContainerRef = useRef(null);
+ const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
+
+ // 当分页切换时,将激活的分页标签滚动到视口中间
+ useEffect(() => {
+ const btn = buttonRefs.current[currentPage];
+ const container = categoryContainerRef.current;
+ if (btn && container) {
+ // 手动计算滚动位置,只滚动分页标签容器
+ const containerRect = container.getBoundingClientRect();
+ const btnRect = btn.getBoundingClientRect();
+ const scrollLeft = container.scrollLeft;
+
+ // 计算按钮相对于容器的位置
+ const btnLeft = btnRect.left - containerRect.left + scrollLeft;
+ const btnWidth = btnRect.width;
+ const containerWidth = containerRect.width;
+
+ // 计算目标滚动位置,使按钮居中
+ const targetScrollLeft = btnLeft - (containerWidth - btnWidth) / 2;
+
+ // 平滑滚动到目标位置
+ container.scrollTo({
+ left: targetScrollLeft,
+ behavior: 'smooth',
+ });
+ }
+ }, [currentPage, pageCount]);
+
+ // 处理换源tab点击,只在点击时才搜索
+ const handleSourceTabClick = () => {
+ setActiveTab('sources');
+ };
+
+ const handleCategoryClick = useCallback((index: number) => {
+ setCurrentPage(index);
+ }, []);
+
+ const handleEpisodeClick = useCallback(
+ (episodeNumber: number) => {
+ onChange?.(episodeNumber);
+ },
+ [onChange]
+ );
+
+ const handleSourceClick = useCallback(
+ (source: SearchResult) => {
+ onSourceChange?.(source.source, source.id, source.title);
+ },
+ [onSourceChange]
+ );
+
+ const currentStart = currentPage * episodesPerPage + 1;
+ const currentEnd = Math.min(
+ currentStart + episodesPerPage - 1,
+ totalEpisodes
+ );
+
+ return (
+
+ {/* 主要的 Tab 切换 - 无缝融入设计 */}
+
+ {totalEpisodes > 1 && (
+
setActiveTab('episodes')}
+ className={`flex-1 py-3 px-6 text-center cursor-pointer transition-all duration-200 font-medium
+ ${
+ activeTab === 'episodes'
+ ? 'text-green-600 dark:text-green-400'
+ : 'text-gray-700 hover:text-green-600 bg-black/5 dark:bg-white/5 dark:text-gray-300 dark:hover:text-green-400 hover:bg-black/3 dark:hover:bg-white/3'
+ }
+ `.trim()}
+ >
+ 选集
+
+ )}
+
+ 换源
+
+
+
+ {/* 选集 Tab 内容 */}
+ {activeTab === 'episodes' && (
+ <>
+ {/* 分类标签 */}
+
+
+
+ {categories.map((label, idx) => {
+ const isActive = idx === currentPage;
+ return (
+
{
+ buttonRefs.current[idx] = el;
+ }}
+ onClick={() => handleCategoryClick(idx)}
+ className={`w-20 relative py-2 text-sm font-medium transition-colors whitespace-nowrap flex-shrink-0 text-center
+ ${
+ isActive
+ ? 'text-green-500 dark:text-green-400'
+ : 'text-gray-700 hover:text-green-600 dark:text-gray-300 dark:hover:text-green-400'
+ }
+ `.trim()}
+ >
+ {label}
+ {isActive && (
+
+ )}
+
+ );
+ })}
+
+
+ {/* 向上/向下按钮 */}
+
{
+ // 切换集数排序(正序/倒序)
+ setDescending((prev) => !prev);
+ }}
+ >
+
+
+
+
+
+
+ {/* 集数网格 */}
+
+ {(() => {
+ const len = currentEnd - currentStart + 1;
+ const episodes = Array.from({ length: len }, (_, i) =>
+ descending ? currentEnd - i : currentStart + i
+ );
+ return episodes;
+ })().map((episodeNumber) => {
+ const isActive = episodeNumber === value;
+ return (
+ handleEpisodeClick(episodeNumber - 1)}
+ className={`h-10 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200
+ ${
+ isActive
+ ? 'bg-green-500 text-white shadow-lg shadow-green-500/25 dark:bg-green-600'
+ : 'bg-gray-200 text-gray-700 hover:bg-gray-300 hover:scale-105 dark:bg-white/10 dark:text-gray-300 dark:hover:bg-white/20'
+ }`.trim()}
+ >
+ {episodeNumber}
+
+ );
+ })}
+
+ >
+ )}
+
+ {/* 换源 Tab 内容 */}
+ {activeTab === 'sources' && (
+
+ {sourceSearchLoading && (
+
+ )}
+
+ {sourceSearchError && (
+
+
+
⚠️
+
+ {sourceSearchError}
+
+
+
+ )}
+
+ {!sourceSearchLoading &&
+ !sourceSearchError &&
+ availableSources.length === 0 && (
+
+ )}
+
+ {!sourceSearchLoading &&
+ !sourceSearchError &&
+ availableSources.length > 0 && (
+
+ {availableSources
+ .sort((a, b) => {
+ const aIsCurrent =
+ a.source?.toString() === currentSource?.toString() &&
+ a.id?.toString() === currentId?.toString();
+ const bIsCurrent =
+ b.source?.toString() === currentSource?.toString() &&
+ b.id?.toString() === currentId?.toString();
+ if (aIsCurrent && !bIsCurrent) return -1;
+ if (!aIsCurrent && bIsCurrent) return 1;
+ return 0;
+ })
+ .map((source, index) => {
+ const isCurrentSource =
+ source.source?.toString() === currentSource?.toString() &&
+ source.id?.toString() === currentId?.toString();
+ return (
+
+ !isCurrentSource && handleSourceClick(source)
+ }
+ className={`flex items-start gap-3 px-2 py-3 rounded-lg transition-all select-none duration-200 relative
+ ${
+ isCurrentSource
+ ? 'bg-green-500/10 dark:bg-green-500/20 border-green-500/30 border'
+ : 'hover:bg-gray-200/50 dark:hover:bg-white/10 hover:scale-[1.02] cursor-pointer'
+ }`.trim()}
+ >
+ {/* 封面 */}
+
+ {source.episodes && source.episodes.length > 0 && (
+
{
+ const target = e.target as HTMLImageElement;
+ target.style.display = 'none';
+ }}
+ />
+ )}
+
+
+ {/* 信息区域 */}
+
+ {/* 标题和分辨率 - 顶部 */}
+
+
+
+ {source.title}
+
+ {/* 标题级别的 tooltip - 第一个元素不显示 */}
+ {index !== 0 && (
+
+ )}
+
+ {(() => {
+ const sourceKey = `${source.source}-${source.id}`;
+ const videoInfo = videoInfoMap.get(sourceKey);
+
+ if (videoInfo && videoInfo.quality !== '未知') {
+ if (videoInfo.hasError) {
+ return (
+
+ 检测失败
+
+ );
+ } else {
+ // 根据分辨率设置不同颜色:2K、4K为紫色,1080p、720p为绿色,其他为黄色
+ const isUltraHigh = ['4K', '2K'].includes(
+ videoInfo.quality
+ );
+ const isHigh = ['1080p', '720p'].includes(
+ videoInfo.quality
+ );
+ const textColorClasses = isUltraHigh
+ ? 'text-purple-600 dark:text-purple-400'
+ : isHigh
+ ? 'text-green-600 dark:text-green-400'
+ : 'text-yellow-600 dark:text-yellow-400';
+
+ return (
+
+ {videoInfo.quality}
+
+ );
+ }
+ }
+
+ return null;
+ })()}
+
+
+ {/* 源名称和集数信息 - 垂直居中 */}
+
+
+ {source.source_name}
+
+ {source.episodes.length > 1 && (
+
+ {source.episodes.length} 集
+
+ )}
+
+
+ {/* 网络信息 - 底部 */}
+
+ {(() => {
+ const sourceKey = `${source.source}-${source.id}`;
+ const videoInfo = videoInfoMap.get(sourceKey);
+ if (videoInfo) {
+ if (!videoInfo.hasError) {
+ return (
+
+
+ {videoInfo.loadSpeed}
+
+
+ {videoInfo.pingTime}ms
+
+
+ );
+ } else {
+ return (
+
+ 无测速数据
+
+ ); // 占位div
+ }
+ }
+ })()}
+
+
+
+ );
+ })}
+
+ {
+ if (videoTitle) {
+ router.push(
+ `/search?q=${encodeURIComponent(videoTitle)}`
+ );
+ }
+ }}
+ className='w-full text-center text-xs text-gray-500 dark:text-gray-400 hover:text-green-500 dark:hover:text-green-400 transition-colors py-2'
+ >
+ 影片匹配有误?点击去搜索
+
+
+
+ )}
+
+ )}
+
+ );
+};
+
+export default EpisodeSelector;
diff --git a/src/components/ImagePlaceholder.tsx b/src/components/ImagePlaceholder.tsx
new file mode 100644
index 0000000..8b2f329
--- /dev/null
+++ b/src/components/ImagePlaceholder.tsx
@@ -0,0 +1,40 @@
+// 图片占位符组件 - 实现骨架屏效果(支持暗色模式)
+const ImagePlaceholder = ({ aspectRatio }: { aspectRatio: string }) => (
+
+
+
+);
+
+export { ImagePlaceholder };
diff --git a/src/components/MobileBottomNav.tsx b/src/components/MobileBottomNav.tsx
new file mode 100644
index 0000000..3caa8ee
--- /dev/null
+++ b/src/components/MobileBottomNav.tsx
@@ -0,0 +1,109 @@
+'use client';
+
+import { Clover, Film, Home, Search, Tv } from 'lucide-react';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+
+interface MobileBottomNavProps {
+ /**
+ * 主动指定当前激活的路径。当未提供时,自动使用 usePathname() 获取的路径。
+ */
+ activePath?: string;
+}
+
+const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
+ const pathname = usePathname();
+
+ // 当前激活路径:优先使用传入的 activePath,否则回退到浏览器地址
+ const currentActive = activePath ?? pathname;
+
+ const navItems = [
+ { icon: Home, label: '首页', href: '/' },
+ { icon: Search, label: '搜索', href: '/search' },
+ {
+ icon: Film,
+ label: '电影',
+ href: '/douban?type=movie',
+ },
+ {
+ icon: Tv,
+ label: '剧集',
+ href: '/douban?type=tv',
+ },
+ {
+ icon: Clover,
+ label: '综艺',
+ href: '/douban?type=show',
+ },
+ ];
+
+ const isActive = (href: string) => {
+ const typeMatch = href.match(/type=([^&]+)/)?.[1];
+
+ // 解码URL以进行正确的比较
+ const decodedActive = decodeURIComponent(currentActive);
+ const decodedItemHref = decodeURIComponent(href);
+
+ return (
+ decodedActive === decodedItemHref ||
+ (decodedActive.startsWith('/douban') &&
+ decodedActive.includes(`type=${typeMatch}`))
+ );
+ };
+
+ return (
+
+ {/* 顶部装饰线 */}
+
+
+
+ {navItems.map((item) => {
+ const active = isActive(item.href);
+ return (
+
+
+ {/* 激活状态的背景光晕 */}
+ {active && (
+
+ )}
+
+
+
+ {item.label}
+
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default MobileBottomNav;
\ No newline at end of file
diff --git a/src/components/MobileHeader.tsx b/src/components/MobileHeader.tsx
new file mode 100644
index 0000000..b62d5e6
--- /dev/null
+++ b/src/components/MobileHeader.tsx
@@ -0,0 +1,44 @@
+'use client';
+
+import Link from 'next/link';
+
+import { BackButton } from './BackButton';
+import { useSite } from './SiteProvider';
+import { ThemeToggle } from './ThemeToggle';
+import { UserMenu } from './UserMenu';
+
+interface MobileHeaderProps {
+ showBackButton?: boolean;
+}
+
+const MobileHeader = ({ showBackButton = false }: MobileHeaderProps) => {
+ const { siteName } = useSite();
+ return (
+
+
+ {/* 左侧:返回按钮和设置按钮 */}
+
+ {showBackButton && }
+
+
+ {/* 右侧按钮 */}
+
+
+
+
+
+
+ {/* 中间:Logo(绝对居中)- 应用彩虹渐变效果 */}
+
+
+ {siteName}
+
+
+
+ );
+};
+
+export default MobileHeader;
\ No newline at end of file
diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx
new file mode 100644
index 0000000..4ea143a
--- /dev/null
+++ b/src/components/PageLayout.tsx
@@ -0,0 +1,220 @@
+import { Clover, Film, Home, Search, Tv } from 'lucide-react';
+import Link from 'next/link';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { useCallback, useEffect, useState } from 'react';
+
+import { BackButton } from './BackButton';
+import MobileBottomNav from './MobileBottomNav';
+import MobileHeader from './MobileHeader';
+import { useSite } from './SiteProvider';
+import { ThemeToggle } from './ThemeToggle';
+import { UserMenu } from './UserMenu';
+
+interface PageLayoutProps {
+ children: React.ReactNode;
+ activePath?: string;
+}
+
+// 内联顶部导航栏组件
+const TopNavbar = ({ activePath = '/' }: { activePath?: string }) => {
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const { siteName } = useSite();
+
+ const [active, setActive] = useState(activePath);
+
+ useEffect(() => {
+ // 优先使用传入的 activePath
+ if (activePath) {
+ setActive(activePath);
+ } else {
+ // 否则使用当前路径
+ const getCurrentFullPath = () => {
+ const queryString = searchParams.toString();
+ return queryString ? `${pathname}?${queryString}` : pathname;
+ };
+ const fullPath = getCurrentFullPath();
+ setActive(fullPath);
+ }
+ }, [activePath, pathname, searchParams]);
+
+ const handleSearchClick = useCallback(() => {
+ router.push('/search');
+ }, [router]);
+
+ const menuItems = [
+ {
+ icon: Home,
+ label: '首页',
+ href: '/',
+ },
+ {
+ icon: Search,
+ label: '搜索',
+ href: '/search',
+ },
+ {
+ icon: Film,
+ label: '电影',
+ href: '/douban?type=movie',
+ },
+ {
+ icon: Tv,
+ label: '剧集',
+ href: '/douban?type=tv',
+ },
+ {
+ icon: Clover,
+ label: '综艺',
+ href: '/douban?type=show',
+ },
+ ];
+
+ return (
+
+
+
+ {/* Logo区域 - 调整为更靠左 */}
+
+
+
+ {siteName}
+
+
+
+
+ {/* 导航菜单 */}
+
+
+ {menuItems.map((item) => {
+ // 检查当前路径是否匹配这个菜单项
+ const typeMatch = item.href.match(/type=([^&]+)/)?.[1];
+ const tagMatch = item.href.match(/tag=([^&]+)/)?.[1];
+
+ // 解码URL以进行正确的比较
+ const decodedActive = decodeURIComponent(active);
+ const decodedItemHref = decodeURIComponent(item.href);
+
+ const isActive =
+ decodedActive === decodedItemHref ||
+ (decodedActive.startsWith('/douban') &&
+ decodedActive.includes(`type=${typeMatch}`) &&
+ tagMatch &&
+ decodedActive.includes(`tag=${tagMatch}`));
+
+ const Icon = item.icon;
+
+ if (item.href === '/search') {
+ return (
+ {
+ e.preventDefault();
+ handleSearchClick();
+ setActive('/search');
+ }}
+ data-active={isActive}
+ className={`group flex items-center rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ${
+ isActive
+ ? 'bg-purple-500/20 text-purple-700 dark:bg-purple-500/10 dark:text-purple-400'
+ : 'text-gray-700 hover:bg-purple-100/30 hover:text-purple-600 dark:text-gray-300 dark:hover:text-purple-400 dark:hover:bg-purple-500/10'
+ }`}
+ >
+
+ {item.label}
+
+ );
+ }
+
+ return (
+ setActive(item.href)}
+ data-active={isActive}
+ className={`group flex items-center rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ${
+ isActive
+ ? 'bg-purple-500/20 text-purple-700 dark:bg-purple-500/10 dark:text-purple-400'
+ : 'text-gray-700 hover:bg-purple-100/30 hover:text-purple-600 dark:text-gray-300 dark:hover:text-purple-400 dark:hover:bg-purple-500/10'
+ }`}
+ >
+
+ {item.label}
+
+ );
+ })}
+
+
+
+ {/* 右侧按钮 - 调整为更靠右,增加间距实现对称效果 */}
+
+
+
+
+
+
+
+ );
+};
+
+const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => {
+ return (
+
+ {/* 移动端头部 */}
+
+
+ {/* 桌面端顶部导航栏 */}
+
+
+
+
+ {/* 主要布局容器 */}
+
+ {/* 主内容区域 */}
+
+ {/* 桌面端左上角返回按钮 */}
+ {['/play'].includes(activePath) && (
+
+
+
+ )}
+
+ {/* 主内容容器 - 修改布局实现完全居中:左右各留白1/6,主内容区占2/3 */}
+
+ {/* 使用flex布局实现三等分 */}
+
+ {/* 左侧留白区域 - 占1/6 */}
+
+
+ {/* 主内容区 - 占2/3 */}
+
+
+ {/* 右侧留白区域 - 占1/6 */}
+
+
+
+
+
+
+ {/* 移动端底部导航 */}
+
+
+
+
+ );
+};
+
+export default PageLayout;
\ No newline at end of file
diff --git a/src/components/ScrollableRow.tsx b/src/components/ScrollableRow.tsx
new file mode 100644
index 0000000..f140599
--- /dev/null
+++ b/src/components/ScrollableRow.tsx
@@ -0,0 +1,169 @@
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { useEffect, useRef, useState } from 'react';
+
+interface ScrollableRowProps {
+ children: React.ReactNode;
+ scrollDistance?: number;
+}
+
+export default function ScrollableRow({
+ children,
+ scrollDistance = 1000,
+}: ScrollableRowProps) {
+ const containerRef = useRef(null);
+ const [showLeftScroll, setShowLeftScroll] = useState(false);
+ const [showRightScroll, setShowRightScroll] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const checkScroll = () => {
+ if (containerRef.current) {
+ const { scrollWidth, clientWidth, scrollLeft } = containerRef.current;
+
+ // 计算是否需要左右滚动按钮
+ const threshold = 1; // 容差值,避免浮点误差
+ const canScrollRight =
+ scrollWidth - (scrollLeft + clientWidth) > threshold;
+ const canScrollLeft = scrollLeft > threshold;
+
+ setShowRightScroll(canScrollRight);
+ setShowLeftScroll(canScrollLeft);
+ }
+ };
+
+ useEffect(() => {
+ // 多次延迟检查,确保内容已完全渲染
+ checkScroll();
+
+ // 监听窗口大小变化
+ window.addEventListener('resize', checkScroll);
+
+ // 创建一个 ResizeObserver 来监听容器大小变化
+ const resizeObserver = new ResizeObserver(() => {
+ // 延迟执行检查
+ checkScroll();
+ });
+
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
+
+ return () => {
+ window.removeEventListener('resize', checkScroll);
+ resizeObserver.disconnect();
+ };
+ }, [children]); // 依赖 children,当子组件变化时重新检查
+
+ // 添加一个额外的效果来监听子组件的变化
+ useEffect(() => {
+ if (containerRef.current) {
+ // 监听 DOM 变化
+ const observer = new MutationObserver(() => {
+ setTimeout(checkScroll, 100);
+ });
+
+ observer.observe(containerRef.current, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['style', 'class'],
+ });
+
+ return () => observer.disconnect();
+ }
+ }, []);
+
+ const handleScrollRightClick = () => {
+ if (containerRef.current) {
+ containerRef.current.scrollBy({
+ left: scrollDistance,
+ behavior: 'smooth',
+ });
+ }
+ };
+
+ const handleScrollLeftClick = () => {
+ if (containerRef.current) {
+ containerRef.current.scrollBy({
+ left: -scrollDistance,
+ behavior: 'smooth',
+ });
+ }
+ };
+
+ return (
+ {
+ setIsHovered(true);
+ // 当鼠标进入时重新检查一次
+ checkScroll();
+ }}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+ {children}
+
+ {showLeftScroll && (
+
+ )}
+
+ {showRightScroll && (
+
+ )}
+
+ );
+}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
new file mode 100644
index 0000000..22a6b06
--- /dev/null
+++ b/src/components/Sidebar.tsx
@@ -0,0 +1,275 @@
+'use client';
+
+import { Clover, Film, Home, Menu, Search, Tv } from 'lucide-react';
+import Link from 'next/link';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useLayoutEffect,
+ useState,
+} from 'react';
+
+import { useSite } from './SiteProvider';
+
+interface SidebarContextType {
+ isCollapsed: boolean;
+}
+
+const SidebarContext = createContext({
+ isCollapsed: false,
+});
+
+export const useSidebar = () => useContext(SidebarContext);
+
+// Logo 组件 - 应用彩虹渐变效果
+const Logo = () => {
+ const { siteName } = useSite();
+ return (
+
+
+ {siteName}
+
+
+ );
+};
+
+interface SidebarProps {
+ onToggle?: (collapsed: boolean) => void;
+ activePath?: string;
+}
+
+// 在浏览器环境下通过全局变量缓存折叠状态,避免组件重新挂载时出现初始值闪烁
+declare global {
+ interface Window {
+ __sidebarCollapsed?: boolean;
+ }
+}
+
+const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ // 若同一次 SPA 会话中已经读取过折叠状态,则直接复用,避免闪烁
+ const [isCollapsed, setIsCollapsed] = useState(() => {
+ if (
+ typeof window !== 'undefined' &&
+ typeof window.__sidebarCollapsed === 'boolean'
+ ) {
+ return window.__sidebarCollapsed;
+ }
+ return false; // 默认展开
+ });
+
+ // 首次挂载时读取 localStorage,以便刷新后仍保持上次的折叠状态
+ useLayoutEffect(() => {
+ const saved = localStorage.getItem('sidebarCollapsed');
+ if (saved !== null) {
+ const val = JSON.parse(saved);
+ setIsCollapsed(val);
+ window.__sidebarCollapsed = val;
+ }
+ }, []);
+
+ // 当折叠状态变化时,同步到 data 属性,供首屏 CSS 使用
+ useLayoutEffect(() => {
+ if (typeof document !== 'undefined') {
+ if (isCollapsed) {
+ document.documentElement.dataset.sidebarCollapsed = 'true';
+ } else {
+ delete document.documentElement.dataset.sidebarCollapsed;
+ }
+ }
+ }, [isCollapsed]);
+
+ const [active, setActive] = useState(activePath);
+
+ useEffect(() => {
+ // 优先使用传入的 activePath
+ if (activePath) {
+ setActive(activePath);
+ } else {
+ // 否则使用当前路径
+ const getCurrentFullPath = () => {
+ const queryString = searchParams.toString();
+ return queryString ? `${pathname}?${queryString}` : pathname;
+ };
+ const fullPath = getCurrentFullPath();
+ setActive(fullPath);
+ }
+ }, [activePath, pathname, searchParams]);
+
+ const handleToggle = useCallback(() => {
+ const newState = !isCollapsed;
+ setIsCollapsed(newState);
+ localStorage.setItem('sidebarCollapsed', JSON.stringify(newState));
+ if (typeof window !== 'undefined') {
+ window.__sidebarCollapsed = newState;
+ }
+ onToggle?.(newState);
+ }, [isCollapsed, onToggle]);
+
+ const handleSearchClick = useCallback(() => {
+ router.push('/search');
+ }, [router]);
+
+ const contextValue = {
+ isCollapsed,
+ };
+
+ const menuItems = [
+ {
+ icon: Film,
+ label: '电影',
+ href: '/douban?type=movie',
+ },
+ {
+ icon: Tv,
+ label: '剧集',
+ href: '/douban?type=tv',
+ },
+ {
+ icon: Clover,
+ label: '综艺',
+ href: '/douban?type=show',
+ },
+ ];
+
+ return (
+
+ {/* 在移动端隐藏侧边栏 */}
+
+
+
+ {/* 顶部 Logo 区域 */}
+
+
+
+ {!isCollapsed && }
+
+
+
+
+
+
+
+ {/* 首页和搜索导航 */}
+
+ setActive('/')}
+ data-active={active === '/'}
+ className={`group flex items-center rounded-lg px-2 py-2 pl-4 text-gray-700 hover:bg-purple-100/30 hover:text-purple-600 data-[active=true]:bg-purple-500/20 data-[active=true]:text-purple-700 font-medium transition-colors duration-200 min-h-[40px] dark:text-gray-300 dark:hover:text-purple-400 dark:data-[active=true]:bg-purple-500/10 dark:data-[active=true]:text-purple-400 ${
+ isCollapsed ? 'w-full max-w-none mx-0' : 'mx-0'
+ } gap-3 justify-start`}
+ >
+
+
+
+ {!isCollapsed && (
+
+ 首页
+
+ )}
+
+ {
+ e.preventDefault();
+ handleSearchClick();
+ setActive('/search');
+ }}
+ data-active={active === '/search'}
+ className={`group flex items-center rounded-lg px-2 py-2 pl-4 text-gray-700 hover:bg-purple-100/30 hover:text-purple-600 data-[active=true]:bg-purple-500/20 data-[active=true]:text-purple-700 font-medium transition-colors duration-200 min-h-[40px] dark:text-gray-300 dark:hover:text-purple-400 dark:data-[active=true]:bg-purple-500/10 dark:data-[active=true]:text-purple-400 ${
+ isCollapsed ? 'w-full max-w-none mx-0' : 'mx-0'
+ } gap-3 justify-start`}
+ >
+
+
+
+ {!isCollapsed && (
+
+ 搜索
+
+ )}
+
+
+
+ {/* 菜单项 */}
+
+
+ {menuItems.map((item) => {
+ // 检查当前路径是否匹配这个菜单项
+ const typeMatch = item.href.match(/type=([^&]+)/)?.[1];
+ const tagMatch = item.href.match(/tag=([^&]+)/)?.[1];
+
+ // 解码URL以进行正确的比较
+ const decodedActive = decodeURIComponent(active);
+ const decodedItemHref = decodeURIComponent(item.href);
+
+ const isActive =
+ decodedActive === decodedItemHref ||
+ (decodedActive.startsWith('/douban') &&
+ decodedActive.includes(`type=${typeMatch}`) &&
+ tagMatch &&
+ decodedActive.includes(`tag=${tagMatch}`));
+ const Icon = item.icon;
+ return (
+
setActive(item.href)}
+ data-active={isActive}
+ className={`group flex items-center rounded-lg px-2 py-2 pl-4 text-sm text-gray-700 hover:bg-purple-100/30 hover:text-purple-600 data-[active=true]:bg-purple-500/20 data-[active=true]:text-purple-700 transition-colors duration-200 min-h-[40px] dark:text-gray-300 dark:hover:text-purple-400 dark:data-[active=true]:bg-purple-500/10 dark:data-[active=true]:text-purple-400 ${
+ isCollapsed ? 'w-full max-w-none mx-0' : 'mx-0'
+ } gap-3 justify-start`}
+ >
+
+
+
+ {!isCollapsed && (
+
+ {item.label}
+
+ )}
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/src/components/SiteProvider.tsx b/src/components/SiteProvider.tsx
new file mode 100644
index 0000000..3223a31
--- /dev/null
+++ b/src/components/SiteProvider.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { createContext, ReactNode, useContext } from 'react';
+
+const SiteContext = createContext<{ siteName: string; announcement?: string }>({
+ // 默认值
+ siteName: 'MoonTV',
+ announcement:
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
+});
+
+export const useSite = () => useContext(SiteContext);
+
+export function SiteProvider({
+ children,
+ siteName,
+ announcement,
+}: {
+ children: ReactNode;
+ siteName: string;
+ announcement?: string;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx
new file mode 100644
index 0000000..4ec0be5
--- /dev/null
+++ b/src/components/ThemeProvider.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import type { ThemeProviderProps } from 'next-themes';
+import { ThemeProvider as NextThemesProvider } from 'next-themes';
+import * as React from 'react';
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
new file mode 100644
index 0000000..6525407
--- /dev/null
+++ b/src/components/ThemeToggle.tsx
@@ -0,0 +1,62 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,react-hooks/exhaustive-deps */
+
+'use client';
+
+import { Moon, Sun } from 'lucide-react';
+import { useTheme } from 'next-themes';
+import { useEffect, useState } from 'react';
+
+export function ThemeToggle() {
+ const [mounted, setMounted] = useState(false);
+ const { setTheme, resolvedTheme } = useTheme();
+
+ const setThemeColor = (theme?: string) => {
+ const meta = document.querySelector('meta[name="theme-color"]');
+ if (!meta) {
+ const meta = document.createElement('meta');
+ meta.name = 'theme-color';
+ meta.content = theme === 'dark' ? '#0c111c' : '#f9fbfe';
+ document.head.appendChild(meta);
+ } else {
+ meta.setAttribute('content', theme === 'dark' ? '#0c111c' : '#f9fbfe');
+ }
+ };
+
+ useEffect(() => {
+ setMounted(true);
+ setThemeColor(resolvedTheme);
+ }, []);
+
+ if (!mounted) {
+ // 渲染一个占位符以避免布局偏移
+ return
;
+ }
+
+ const toggleTheme = () => {
+ // 检查浏览器是否支持 View Transitions API
+ const targetTheme = resolvedTheme === 'dark' ? 'light' : 'dark';
+ setThemeColor(targetTheme);
+ if (!(document as any).startViewTransition) {
+ setTheme(targetTheme);
+ return;
+ }
+
+ (document as any).startViewTransition(() => {
+ setTheme(targetTheme);
+ });
+ };
+
+ return (
+
+ {resolvedTheme === 'dark' ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx
new file mode 100644
index 0000000..260231e
--- /dev/null
+++ b/src/components/UserMenu.tsx
@@ -0,0 +1,749 @@
+/* eslint-disable no-console,@typescript-eslint/no-explicit-any */
+
+'use client';
+
+import { KeyRound, LogOut, Settings, Shield, User, X } from 'lucide-react';
+import { useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import { createPortal } from 'react-dom';
+
+import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
+import { checkForUpdates, CURRENT_VERSION, UpdateStatus } from '@/lib/version';
+
+interface AuthInfo {
+ username?: string;
+ role?: 'owner' | 'admin' | 'user';
+}
+
+export const UserMenu: React.FC = () => {
+ const router = useRouter();
+ const [isOpen, setIsOpen] = useState(false);
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
+ const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
+ const [authInfo, setAuthInfo] = useState(null);
+ const [storageType, setStorageType] = useState('localstorage');
+ const [mounted, setMounted] = useState(false);
+
+ // 设置相关状态
+ const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
+ const [doubanProxyUrl, setDoubanProxyUrl] = useState('');
+ const [imageProxyUrl, setImageProxyUrl] = useState('');
+ const [enableOptimization, setEnableOptimization] = useState(true);
+ const [enableImageProxy, setEnableImageProxy] = useState(false);
+ const [enableDoubanProxy, setEnableDoubanProxy] = useState(false);
+
+ // 修改密码相关状态
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [passwordLoading, setPasswordLoading] = useState(false);
+ const [passwordError, setPasswordError] = useState('');
+
+ // 版本检查相关状态
+ const [updateStatus, setUpdateStatus] = useState(null);
+ const [isChecking, setIsChecking] = useState(true);
+
+ // 确保组件已挂载
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ // 获取认证信息和存储类型
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const auth = getAuthInfoFromBrowserCookie();
+ setAuthInfo(auth);
+
+ const type =
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE || 'localstorage';
+ setStorageType(type);
+ }
+ }, []);
+
+ // 从 localStorage 读取设置
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const savedAggregateSearch = localStorage.getItem(
+ 'defaultAggregateSearch'
+ );
+ if (savedAggregateSearch !== null) {
+ setDefaultAggregateSearch(JSON.parse(savedAggregateSearch));
+ }
+
+ const savedEnableDoubanProxy = localStorage.getItem('enableDoubanProxy');
+ const defaultDoubanProxy =
+ (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || '';
+ if (savedEnableDoubanProxy !== null) {
+ setEnableDoubanProxy(JSON.parse(savedEnableDoubanProxy));
+ } else if (defaultDoubanProxy) {
+ setEnableDoubanProxy(true);
+ }
+
+ const savedDoubanProxyUrl = localStorage.getItem('doubanProxyUrl');
+ if (savedDoubanProxyUrl !== null) {
+ setDoubanProxyUrl(savedDoubanProxyUrl);
+ } else if (defaultDoubanProxy) {
+ setDoubanProxyUrl(defaultDoubanProxy);
+ }
+
+ const savedEnableImageProxy = localStorage.getItem('enableImageProxy');
+ const defaultImageProxy =
+ (window as any).RUNTIME_CONFIG?.IMAGE_PROXY || '';
+ if (savedEnableImageProxy !== null) {
+ setEnableImageProxy(JSON.parse(savedEnableImageProxy));
+ } else if (defaultImageProxy) {
+ setEnableImageProxy(true);
+ }
+
+ const savedImageProxyUrl = localStorage.getItem('imageProxyUrl');
+ if (savedImageProxyUrl !== null) {
+ setImageProxyUrl(savedImageProxyUrl);
+ } else if (defaultImageProxy) {
+ setImageProxyUrl(defaultImageProxy);
+ }
+
+ const savedEnableOptimization =
+ localStorage.getItem('enableOptimization');
+ if (savedEnableOptimization !== null) {
+ setEnableOptimization(JSON.parse(savedEnableOptimization));
+ }
+ }
+ }, []);
+
+ // 版本检查
+ useEffect(() => {
+ const checkUpdate = async () => {
+ try {
+ const status = await checkForUpdates();
+ setUpdateStatus(status);
+ } catch (error) {
+ console.warn('版本检查失败:', error);
+ } finally {
+ setIsChecking(false);
+ }
+ };
+
+ checkUpdate();
+ }, []);
+
+ const handleMenuClick = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const handleCloseMenu = () => {
+ setIsOpen(false);
+ };
+
+ const handleLogout = async () => {
+ try {
+ await fetch('/api/logout', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ } catch (error) {
+ console.error('注销请求失败:', error);
+ }
+ window.location.href = '/';
+ };
+
+ const handleAdminPanel = () => {
+ router.push('/admin');
+ };
+
+ const handleChangePassword = () => {
+ setIsOpen(false);
+ setIsChangePasswordOpen(true);
+ setNewPassword('');
+ setConfirmPassword('');
+ setPasswordError('');
+ };
+
+ const handleCloseChangePassword = () => {
+ setIsChangePasswordOpen(false);
+ setNewPassword('');
+ setConfirmPassword('');
+ setPasswordError('');
+ };
+
+ const handleSubmitChangePassword = async () => {
+ setPasswordError('');
+
+ // 验证密码
+ if (!newPassword) {
+ setPasswordError('新密码不得为空');
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ setPasswordError('两次输入的密码不一致');
+ return;
+ }
+
+ setPasswordLoading(true);
+
+ try {
+ const response = await fetch('/api/change-password', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ newPassword,
+ }),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ setPasswordError(data.error || '修改密码失败');
+ return;
+ }
+
+ // 修改成功,关闭弹窗并登出
+ setIsChangePasswordOpen(false);
+ await handleLogout();
+ } catch (error) {
+ setPasswordError('网络错误,请稍后重试');
+ } finally {
+ setPasswordLoading(false);
+ }
+ };
+
+ const handleSettings = () => {
+ setIsOpen(false);
+ setIsSettingsOpen(true);
+ };
+
+ const handleCloseSettings = () => {
+ setIsSettingsOpen(false);
+ };
+
+ // 设置相关的处理函数
+ const handleAggregateToggle = (value: boolean) => {
+ setDefaultAggregateSearch(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('defaultAggregateSearch', JSON.stringify(value));
+ }
+ };
+
+ const handleDoubanProxyUrlChange = (value: string) => {
+ setDoubanProxyUrl(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('doubanProxyUrl', value);
+ }
+ };
+
+ const handleImageProxyUrlChange = (value: string) => {
+ setImageProxyUrl(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('imageProxyUrl', value);
+ }
+ };
+
+ const handleOptimizationToggle = (value: boolean) => {
+ setEnableOptimization(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('enableOptimization', JSON.stringify(value));
+ }
+ };
+
+ const handleImageProxyToggle = (value: boolean) => {
+ setEnableImageProxy(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('enableImageProxy', JSON.stringify(value));
+ }
+ };
+
+ const handleDoubanProxyToggle = (value: boolean) => {
+ setEnableDoubanProxy(value);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('enableDoubanProxy', JSON.stringify(value));
+ }
+ };
+
+ const handleResetSettings = () => {
+ const defaultImageProxy = (window as any).RUNTIME_CONFIG?.IMAGE_PROXY || '';
+ const defaultDoubanProxy =
+ (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || '';
+
+ setDefaultAggregateSearch(true);
+ setEnableOptimization(true);
+ setDoubanProxyUrl(defaultDoubanProxy);
+ setEnableDoubanProxy(!!defaultDoubanProxy);
+ setEnableImageProxy(!!defaultImageProxy);
+ setImageProxyUrl(defaultImageProxy);
+
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('defaultAggregateSearch', JSON.stringify(true));
+ localStorage.setItem('enableOptimization', JSON.stringify(true));
+ localStorage.setItem('doubanProxyUrl', defaultDoubanProxy);
+ localStorage.setItem(
+ 'enableDoubanProxy',
+ JSON.stringify(!!defaultDoubanProxy)
+ );
+ localStorage.setItem(
+ 'enableImageProxy',
+ JSON.stringify(!!defaultImageProxy)
+ );
+ localStorage.setItem('imageProxyUrl', defaultImageProxy);
+ }
+ };
+
+ // 检查是否显示管理面板按钮
+ const showAdminPanel =
+ authInfo?.role === 'owner' || authInfo?.role === 'admin';
+
+ // 检查是否显示修改密码按钮
+ const showChangePassword =
+ authInfo?.role !== 'owner' && storageType !== 'localstorage';
+
+ // 角色中文映射
+ const getRoleText = (role?: string) => {
+ switch (role) {
+ case 'owner':
+ return '站长';
+ case 'admin':
+ return '管理员';
+ case 'user':
+ return '用户';
+ default:
+ return '';
+ }
+ };
+
+ // 菜单面板内容
+ const menuPanel = (
+ <>
+ {/* 背景遮罩 - 普通菜单无需模糊 */}
+
+
+ {/* 菜单面板 */}
+
+ {/* 用户信息区域 */}
+
+
+
+
+ 当前用户
+
+
+ {getRoleText(authInfo?.role || 'user')}
+
+
+
+
+ {authInfo?.username || 'default'}
+
+
+ 数据存储:
+ {storageType === 'localstorage' ? '本地' : storageType}
+
+
+
+
+
+ {/* 菜单项 */}
+
+ {/* 设置按钮 */}
+
+
+ 设置
+
+
+ {/* 管理面板按钮 */}
+ {showAdminPanel && (
+
+
+ 管理面板
+
+ )}
+
+ {/* 修改密码按钮 */}
+ {showChangePassword && (
+
+
+ 修改密码
+
+ )}
+
+ {/* 分割线 */}
+
+
+ {/* 登出按钮 */}
+
+
+ 登出
+
+
+ {/* 分割线 */}
+
+
+ {/* 版本信息 */}
+
+ window.open('https://github.com/senshinya/MoonTV', '_blank')
+ }
+ className='w-full px-3 py-2 text-center flex items-center justify-center text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors text-xs'
+ >
+
+
v{CURRENT_VERSION}
+ {!isChecking &&
+ updateStatus &&
+ updateStatus !== UpdateStatus.FETCH_FAILED && (
+
+ )}
+
+
+
+
+ >
+ );
+
+ // 设置面板内容
+ const settingsPanel = (
+ <>
+ {/* 背景遮罩 */}
+
+
+ {/* 设置面板 */}
+
+ {/* 标题栏 */}
+
+
+
+ 本地设置
+
+
+ 重置
+
+
+
+
+
+
+
+ {/* 设置项 */}
+
+ {/* 默认聚合搜索结果 */}
+
+
+
+ 默认聚合搜索结果
+
+
+ 搜索时默认按标题和年份聚合显示结果
+
+
+
+
+
handleAggregateToggle(e.target.checked)}
+ />
+
+
+
+
+
+
+ {/* 优选和测速 */}
+
+
+
+ 启用优选和测速
+
+
+ 如出现播放器劫持问题可关闭
+
+
+
+
+
handleOptimizationToggle(e.target.checked)}
+ />
+
+
+
+
+
+
+ {/* 分割线 */}
+
+
+ {/* 豆瓣代理开关 */}
+
+
+
+ 启用豆瓣代理
+
+
+ 启用后,豆瓣数据将通过代理服务器获取
+
+
+
+
+
handleDoubanProxyToggle(e.target.checked)}
+ />
+
+
+
+
+
+
+ {/* 豆瓣代理地址设置 */}
+
+
+
+ 豆瓣代理地址
+
+
+ 仅在启用豆瓣代理时生效,留空则使用服务器 API
+
+
+
handleDoubanProxyUrlChange(e.target.value)}
+ disabled={!enableDoubanProxy}
+ />
+
+
+ {/* 分割线 */}
+
+
+ {/* 图片代理开关 */}
+
+
+
+ 启用图片代理
+
+
+ 启用后,所有图片加载将通过代理服务器
+
+
+
+
+
handleImageProxyToggle(e.target.checked)}
+ />
+
+
+
+
+
+
+ {/* 图片代理地址设置 */}
+
+
+
+ 图片代理地址
+
+
+ 仅在启用图片代理时生效
+
+
+
handleImageProxyUrlChange(e.target.value)}
+ disabled={!enableImageProxy}
+ />
+
+
+
+ {/* 底部说明 */}
+
+
+ >
+ );
+
+ // 修改密码面板内容
+ const changePasswordPanel = (
+ <>
+ {/* 背景遮罩 */}
+
+
+ {/* 修改密码面板 */}
+
+ {/* 标题栏 */}
+
+
+ 修改密码
+
+
+
+
+
+
+ {/* 表单 */}
+
+ {/* 新密码输入 */}
+
+
+ 新密码
+
+ setNewPassword(e.target.value)}
+ disabled={passwordLoading}
+ />
+
+
+ {/* 确认密码输入 */}
+
+
+ 确认密码
+
+ setConfirmPassword(e.target.value)}
+ disabled={passwordLoading}
+ />
+
+
+ {/* 错误信息 */}
+ {passwordError && (
+
+ {passwordError}
+
+ )}
+
+
+ {/* 操作按钮 */}
+
+
+ 取消
+
+
+ {passwordLoading ? '修改中...' : '确认修改'}
+
+
+
+ {/* 底部说明 */}
+
+
+ >
+ );
+
+ return (
+ <>
+
+
+
+
+ {updateStatus === UpdateStatus.HAS_UPDATE && (
+
+ )}
+
+
+ {/* 使用 Portal 将菜单面板渲染到 document.body */}
+ {isOpen && mounted && createPortal(menuPanel, document.body)}
+
+ {/* 使用 Portal 将设置面板渲染到 document.body */}
+ {isSettingsOpen && mounted && createPortal(settingsPanel, document.body)}
+
+ {/* 使用 Portal 将修改密码面板渲染到 document.body */}
+ {isChangePasswordOpen &&
+ mounted &&
+ createPortal(changePasswordPanel, document.body)}
+ >
+ );
+};
diff --git a/src/components/VideoCard.tsx b/src/components/VideoCard.tsx
new file mode 100644
index 0000000..5f429ac
--- /dev/null
+++ b/src/components/VideoCard.tsx
@@ -0,0 +1,390 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import { CheckCircle, Heart, Link, PlayCircleIcon } from 'lucide-react';
+import Image from 'next/image';
+import { useRouter } from 'next/navigation';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+
+import {
+ deleteFavorite,
+ deletePlayRecord,
+ generateStorageKey,
+ isFavorited,
+ saveFavorite,
+ subscribeToDataUpdates,
+} from '@/lib/db.client';
+import { SearchResult } from '@/lib/types';
+import { processImageUrl } from '@/lib/utils';
+
+import { ImagePlaceholder } from '@/components/ImagePlaceholder';
+
+interface VideoCardProps {
+ id?: string;
+ source?: string;
+ title?: string;
+ query?: string;
+ poster?: string;
+ episodes?: number;
+ source_name?: string;
+ progress?: number;
+ year?: string;
+ from: 'playrecord' | 'favorite' | 'search' | 'douban';
+ currentEpisode?: number;
+ douban_id?: string;
+ onDelete?: () => void;
+ rate?: string;
+ items?: SearchResult[];
+ type?: string;
+}
+
+export default function VideoCard({
+ id,
+ title = '',
+ query = '',
+ poster = '',
+ episodes,
+ source,
+ source_name,
+ progress = 0,
+ year,
+ from,
+ currentEpisode,
+ douban_id,
+ onDelete,
+ rate,
+ items,
+ type = '',
+}: VideoCardProps) {
+ const router = useRouter();
+ const [favorited, setFavorited] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const isAggregate = from === 'search' && !!items?.length;
+
+ const aggregateData = useMemo(() => {
+ if (!isAggregate || !items) return null;
+ const countMap = new Map();
+ const episodeCountMap = new Map();
+ items.forEach((item) => {
+ if (item.douban_id && item.douban_id !== 0) {
+ countMap.set(item.douban_id, (countMap.get(item.douban_id) || 0) + 1);
+ }
+ const len = item.episodes?.length || 0;
+ if (len > 0) {
+ episodeCountMap.set(len, (episodeCountMap.get(len) || 0) + 1);
+ }
+ });
+
+ const getMostFrequent = (
+ map: Map
+ ) => {
+ let maxCount = 0;
+ let result: T | undefined;
+ map.forEach((cnt, key) => {
+ if (cnt > maxCount) {
+ maxCount = cnt;
+ result = key;
+ }
+ });
+ return result;
+ };
+
+ return {
+ first: items[0],
+ mostFrequentDoubanId: getMostFrequent(countMap),
+ mostFrequentEpisodes: getMostFrequent(episodeCountMap) || 0,
+ };
+ }, [isAggregate, items]);
+
+ const actualTitle = aggregateData?.first.title ?? title;
+ const actualPoster = aggregateData?.first.poster ?? poster;
+ const actualSource = aggregateData?.first.source ?? source;
+ const actualId = aggregateData?.first.id ?? id;
+ const actualDoubanId = String(
+ aggregateData?.mostFrequentDoubanId ?? douban_id
+ );
+ const actualEpisodes = aggregateData?.mostFrequentEpisodes ?? episodes;
+ const actualYear = aggregateData?.first.year ?? year;
+ const actualQuery = query || '';
+ const actualSearchType = isAggregate
+ ? aggregateData?.first.episodes?.length === 1
+ ? 'movie'
+ : 'tv'
+ : type;
+
+ // 获取收藏状态
+ useEffect(() => {
+ if (from === 'douban' || !actualSource || !actualId) return;
+
+ const fetchFavoriteStatus = async () => {
+ try {
+ const fav = await isFavorited(actualSource, actualId);
+ setFavorited(fav);
+ } catch (err) {
+ throw new Error('检查收藏状态失败');
+ }
+ };
+
+ fetchFavoriteStatus();
+
+ // 监听收藏状态更新事件
+ const storageKey = generateStorageKey(actualSource, actualId);
+ const unsubscribe = subscribeToDataUpdates(
+ 'favoritesUpdated',
+ (newFavorites: Record) => {
+ // 检查当前项目是否在新的收藏列表中
+ const isNowFavorited = !!newFavorites[storageKey];
+ setFavorited(isNowFavorited);
+ }
+ );
+
+ return unsubscribe;
+ }, [from, actualSource, actualId]);
+
+ const handleToggleFavorite = useCallback(
+ async (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (from === 'douban' || !actualSource || !actualId) return;
+ try {
+ if (favorited) {
+ // 如果已收藏,删除收藏
+ await deleteFavorite(actualSource, actualId);
+ setFavorited(false);
+ } else {
+ // 如果未收藏,添加收藏
+ await saveFavorite(actualSource, actualId, {
+ title: actualTitle,
+ source_name: source_name || '',
+ year: actualYear || '',
+ cover: actualPoster,
+ total_episodes: actualEpisodes ?? 1,
+ save_time: Date.now(),
+ });
+ setFavorited(true);
+ }
+ } catch (err) {
+ throw new Error('切换收藏状态失败');
+ }
+ },
+ [
+ from,
+ actualSource,
+ actualId,
+ actualTitle,
+ source_name,
+ actualYear,
+ actualPoster,
+ actualEpisodes,
+ favorited,
+ ]
+ );
+
+ const handleDeleteRecord = useCallback(
+ async (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (from !== 'playrecord' || !actualSource || !actualId) return;
+ try {
+ await deletePlayRecord(actualSource, actualId);
+ onDelete?.();
+ } catch (err) {
+ throw new Error('删除播放记录失败');
+ }
+ },
+ [from, actualSource, actualId, onDelete]
+ );
+
+ const handleClick = useCallback(() => {
+ if (from === 'douban') {
+ router.push(
+ `/play?title=${encodeURIComponent(actualTitle.trim())}${
+ actualYear ? `&year=${actualYear}` : ''
+ }${actualSearchType ? `&stype=${actualSearchType}` : ''}`
+ );
+ } else if (actualSource && actualId) {
+ router.push(
+ `/play?source=${actualSource}&id=${actualId}&title=${encodeURIComponent(
+ actualTitle
+ )}${actualYear ? `&year=${actualYear}` : ''}${
+ isAggregate ? '&prefer=true' : ''
+ }${
+ actualQuery ? `&stitle=${encodeURIComponent(actualQuery.trim())}` : ''
+ }${actualSearchType ? `&stype=${actualSearchType}` : ''}`
+ );
+ }
+ }, [
+ from,
+ actualSource,
+ actualId,
+ router,
+ actualTitle,
+ actualYear,
+ isAggregate,
+ actualQuery,
+ actualSearchType,
+ ]);
+
+ const config = useMemo(() => {
+ const configs = {
+ playrecord: {
+ showSourceName: true,
+ showProgress: true,
+ showPlayButton: true,
+ showHeart: true,
+ showCheckCircle: true,
+ showDoubanLink: false,
+ showRating: false,
+ },
+ favorite: {
+ showSourceName: true,
+ showProgress: false,
+ showPlayButton: true,
+ showHeart: true,
+ showCheckCircle: false,
+ showDoubanLink: false,
+ showRating: false,
+ },
+ search: {
+ showSourceName: true,
+ showProgress: false,
+ showPlayButton: true,
+ showHeart: !isAggregate,
+ showCheckCircle: false,
+ showDoubanLink: !!actualDoubanId,
+ showRating: false,
+ },
+ douban: {
+ showSourceName: false,
+ showProgress: false,
+ showPlayButton: true,
+ showHeart: false,
+ showCheckCircle: false,
+ showDoubanLink: true,
+ showRating: !!rate,
+ },
+ };
+ return configs[from] || configs.search;
+ }, [from, isAggregate, actualDoubanId, rate]);
+
+ return (
+
+ {/* 海报容器 */}
+
+
+ {/* 进度条 */}
+ {config.showProgress && progress !== undefined && (
+
+ )}
+
+ {/* 标题与来源 */}
+
+
+
+ {actualTitle}
+
+ {/* 自定义 tooltip */}
+
+
+ {config.showSourceName && source_name && (
+
+
+ {source_name}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts
new file mode 100644
index 0000000..56633b8
--- /dev/null
+++ b/src/lib/admin.types.ts
@@ -0,0 +1,31 @@
+export interface AdminConfig {
+ SiteConfig: {
+ SiteName: string;
+ Announcement: string;
+ SearchDownstreamMaxPage: number;
+ SiteInterfaceCacheTime: number;
+ ImageProxy: string;
+ DoubanProxy: string;
+ };
+ UserConfig: {
+ AllowRegister: boolean;
+ Users: {
+ username: string;
+ role: 'user' | 'admin' | 'owner';
+ banned?: boolean;
+ }[];
+ };
+ SourceConfig: {
+ key: string;
+ name: string;
+ api: string;
+ detail?: string;
+ from: 'config' | 'custom';
+ disabled?: boolean;
+ }[];
+}
+
+export interface AdminConfigResult {
+ Role: 'owner' | 'admin';
+ Config: AdminConfig;
+}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..463ae9e
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,72 @@
+import { NextRequest } from 'next/server';
+
+// 从cookie获取认证信息 (服务端使用)
+export function getAuthInfoFromCookie(request: NextRequest): {
+ password?: string;
+ username?: string;
+ signature?: string;
+ timestamp?: number;
+} | null {
+ const authCookie = request.cookies.get('auth');
+
+ if (!authCookie) {
+ return null;
+ }
+
+ try {
+ const decoded = decodeURIComponent(authCookie.value);
+ const authData = JSON.parse(decoded);
+ return authData;
+ } catch (error) {
+ return null;
+ }
+}
+
+// 从cookie获取认证信息 (客户端使用)
+export function getAuthInfoFromBrowserCookie(): {
+ password?: string;
+ username?: string;
+ signature?: string;
+ timestamp?: number;
+ role?: 'owner' | 'admin' | 'user';
+} | null {
+ if (typeof window === 'undefined') {
+ return null;
+ }
+
+ try {
+ // 解析 document.cookie
+ const cookies = document.cookie.split(';').reduce((acc, cookie) => {
+ const trimmed = cookie.trim();
+ const firstEqualIndex = trimmed.indexOf('=');
+
+ if (firstEqualIndex > 0) {
+ const key = trimmed.substring(0, firstEqualIndex);
+ const value = trimmed.substring(firstEqualIndex + 1);
+ if (key && value) {
+ acc[key] = value;
+ }
+ }
+
+ return acc;
+ }, {} as Record);
+
+ const authCookie = cookies['auth'];
+ if (!authCookie) {
+ return null;
+ }
+
+ // 处理可能的双重编码
+ let decoded = decodeURIComponent(authCookie);
+
+ // 如果解码后仍然包含 %,说明是双重编码,需要再次解码
+ if (decoded.includes('%')) {
+ decoded = decodeURIComponent(decoded);
+ }
+
+ const authData = JSON.parse(decoded);
+ return authData;
+ } catch (error) {
+ return null;
+ }
+}
diff --git a/src/lib/config.ts b/src/lib/config.ts
new file mode 100644
index 0000000..f800258
--- /dev/null
+++ b/src/lib/config.ts
@@ -0,0 +1,389 @@
+/* eslint-disable @typescript-eslint/no-explicit-any, no-console, @typescript-eslint/no-non-null-assertion */
+
+import { getStorage } from '@/lib/db';
+
+import { AdminConfig } from './admin.types';
+import runtimeConfig from './runtime';
+
+export interface ApiSite {
+ key: string;
+ api: string;
+ name: string;
+ detail?: string;
+}
+
+interface ConfigFileStruct {
+ cache_time?: number;
+ api_site: {
+ [key: string]: ApiSite;
+ };
+}
+
+export const API_CONFIG = {
+ search: {
+ path: '?ac=videolist&wd=',
+ pagePath: '?ac=videolist&wd={query}&pg={page}',
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
+ Accept: 'application/json',
+ },
+ },
+ detail: {
+ path: '?ac=videolist&ids=',
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
+ Accept: 'application/json',
+ },
+ },
+};
+
+// 在模块加载时根据环境决定配置来源
+let fileConfig: ConfigFileStruct;
+let cachedConfig: AdminConfig;
+
+async function initConfig() {
+ if (cachedConfig) {
+ return;
+ }
+
+ if (process.env.DOCKER_ENV === 'true') {
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
+ const _require = eval('require') as NodeRequire;
+ const fs = _require('fs') as typeof import('fs');
+ const path = _require('path') as typeof import('path');
+
+ const configPath = path.join(process.cwd(), 'config.json');
+ const raw = fs.readFileSync(configPath, 'utf-8');
+ fileConfig = JSON.parse(raw) as ConfigFileStruct;
+ console.log('load dynamic config success');
+ } else {
+ // 默认使用编译时生成的配置
+ fileConfig = runtimeConfig as unknown as ConfigFileStruct;
+ }
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (storageType !== 'localstorage') {
+ // 数据库存储,读取并补全管理员配置
+ const storage = getStorage();
+
+ try {
+ // 尝试从数据库获取管理员配置
+ let adminConfig: AdminConfig | null = null;
+ if (storage && typeof (storage as any).getAdminConfig === 'function') {
+ adminConfig = await (storage as any).getAdminConfig();
+ }
+
+ // 获取所有用户名,用于补全 Users
+ let userNames: string[] = [];
+ if (storage && typeof (storage as any).getAllUsers === 'function') {
+ try {
+ userNames = await (storage as any).getAllUsers();
+ } catch (e) {
+ console.error('获取用户列表失败:', e);
+ }
+ }
+
+ // 从文件中获取源信息,用于补全源
+ const apiSiteEntries = Object.entries(fileConfig.api_site);
+
+ if (adminConfig) {
+ // 补全 SourceConfig
+ const existed = new Set(
+ (adminConfig.SourceConfig || []).map((s) => s.key)
+ );
+ apiSiteEntries.forEach(([key, site]) => {
+ if (!existed.has(key)) {
+ adminConfig!.SourceConfig.push({
+ key,
+ name: site.name,
+ api: site.api,
+ detail: site.detail,
+ from: 'config',
+ disabled: false,
+ });
+ }
+ });
+
+ // 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom
+ const apiSiteKeys = new Set(apiSiteEntries.map(([key]) => key));
+ adminConfig.SourceConfig.forEach((source) => {
+ if (!apiSiteKeys.has(source.key)) {
+ source.from = 'custom';
+ }
+ });
+
+ const existedUsers = new Set(
+ (adminConfig.UserConfig.Users || []).map((u) => u.username)
+ );
+ userNames.forEach((uname) => {
+ if (!existedUsers.has(uname)) {
+ adminConfig!.UserConfig.Users.push({
+ username: uname,
+ role: 'user',
+ });
+ }
+ });
+ // 站长
+ const ownerUser = process.env.USERNAME;
+ if (ownerUser) {
+ adminConfig!.UserConfig.Users = adminConfig!.UserConfig.Users.filter(
+ (u) => u.username !== ownerUser
+ );
+ adminConfig!.UserConfig.Users.unshift({
+ username: ownerUser,
+ role: 'owner',
+ });
+ }
+ } else {
+ // 数据库中没有配置,创建新的管理员配置
+ let allUsers = userNames.map((uname) => ({
+ username: uname,
+ role: 'user',
+ }));
+ const ownerUser = process.env.USERNAME;
+ if (ownerUser) {
+ allUsers = allUsers.filter((u) => u.username !== ownerUser);
+ allUsers.unshift({
+ username: ownerUser,
+ role: 'owner',
+ });
+ }
+ adminConfig = {
+ SiteConfig: {
+ SiteName: process.env.SITE_NAME || 'MoonTV',
+ Announcement:
+ process.env.ANNOUNCEMENT ||
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
+ SearchDownstreamMaxPage:
+ Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
+ SiteInterfaceCacheTime: fileConfig.cache_time || 7200,
+ ImageProxy: process.env.NEXT_PUBLIC_IMAGE_PROXY || '',
+ DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '',
+ },
+ UserConfig: {
+ AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
+ Users: allUsers as any,
+ },
+ SourceConfig: apiSiteEntries.map(([key, site]) => ({
+ key,
+ name: site.name,
+ api: site.api,
+ detail: site.detail,
+ from: 'config',
+ disabled: false,
+ })),
+ };
+ }
+
+ // 写回数据库(更新/创建)
+ if (storage && typeof (storage as any).setAdminConfig === 'function') {
+ await (storage as any).setAdminConfig(adminConfig);
+ }
+
+ // 更新缓存
+ cachedConfig = adminConfig;
+ } catch (err) {
+ console.error('加载管理员配置失败:', err);
+ }
+ } else {
+ // 本地存储直接使用文件配置
+ cachedConfig = {
+ SiteConfig: {
+ SiteName: process.env.SITE_NAME || 'MoonTV',
+ Announcement:
+ process.env.ANNOUNCEMENT ||
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
+ SearchDownstreamMaxPage:
+ Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
+ SiteInterfaceCacheTime: fileConfig.cache_time || 7200,
+ ImageProxy: process.env.NEXT_PUBLIC_IMAGE_PROXY || '',
+ DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '',
+ },
+ UserConfig: {
+ AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
+ Users: [],
+ },
+ SourceConfig: Object.entries(fileConfig.api_site).map(([key, site]) => ({
+ key,
+ name: site.name,
+ api: site.api,
+ detail: site.detail,
+ from: 'config',
+ disabled: false,
+ })),
+ } as AdminConfig;
+ }
+}
+
+export async function getConfig(): Promise {
+ const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
+ if (process.env.DOCKER_ENV === 'true' || storageType === 'localstorage') {
+ await initConfig();
+ return cachedConfig;
+ }
+ // 非 docker 环境且 DB 存储,直接读 db 配置
+ const storage = getStorage();
+ let adminConfig: AdminConfig | null = null;
+ if (storage && typeof (storage as any).getAdminConfig === 'function') {
+ adminConfig = await (storage as any).getAdminConfig();
+ }
+ if (adminConfig) {
+ // 合并一些环境变量配置
+ adminConfig.SiteConfig.SiteName = process.env.SITE_NAME || 'MoonTV';
+ adminConfig.SiteConfig.Announcement =
+ process.env.ANNOUNCEMENT ||
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
+ adminConfig.UserConfig.AllowRegister =
+ process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true';
+ adminConfig.SiteConfig.ImageProxy =
+ process.env.NEXT_PUBLIC_IMAGE_PROXY || '';
+ adminConfig.SiteConfig.DoubanProxy =
+ process.env.NEXT_PUBLIC_DOUBAN_PROXY || '';
+
+ // 合并文件中的源信息
+ fileConfig = runtimeConfig as unknown as ConfigFileStruct;
+ const apiSiteEntries = Object.entries(fileConfig.api_site);
+ const existed = new Set((adminConfig.SourceConfig || []).map((s) => s.key));
+ apiSiteEntries.forEach(([key, site]) => {
+ if (!existed.has(key)) {
+ adminConfig!.SourceConfig.push({
+ key,
+ name: site.name,
+ api: site.api,
+ detail: site.detail,
+ from: 'config',
+ disabled: false,
+ });
+ }
+ });
+
+ // 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom
+ const apiSiteKeys = new Set(apiSiteEntries.map(([key]) => key));
+ adminConfig.SourceConfig.forEach((source) => {
+ if (!apiSiteKeys.has(source.key)) {
+ source.from = 'custom';
+ }
+ });
+
+ const ownerUser = process.env.USERNAME || '';
+ // 检查配置中的站长用户是否和 USERNAME 匹配,如果不匹配则降级为普通用户
+ let containOwner = false;
+ adminConfig.UserConfig.Users.forEach((user) => {
+ if (user.username !== ownerUser && user.role === 'owner') {
+ user.role = 'user';
+ }
+ if (user.username === ownerUser) {
+ containOwner = true;
+ user.role = 'owner';
+ }
+ });
+
+ // 如果不在则添加
+ if (!containOwner) {
+ adminConfig.UserConfig.Users.unshift({
+ username: ownerUser,
+ role: 'owner',
+ });
+ }
+ cachedConfig = adminConfig;
+ } else {
+ // DB 无配置,执行一次初始化
+ await initConfig();
+ }
+ return cachedConfig;
+}
+
+export async function resetConfig() {
+ const storage = getStorage();
+ // 获取所有用户名,用于补全 Users
+ let userNames: string[] = [];
+ if (storage && typeof (storage as any).getAllUsers === 'function') {
+ try {
+ userNames = await (storage as any).getAllUsers();
+ } catch (e) {
+ console.error('获取用户列表失败:', e);
+ }
+ }
+
+ if (process.env.DOCKER_ENV === 'true') {
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
+ const _require = eval('require') as NodeRequire;
+ const fs = _require('fs') as typeof import('fs');
+ const path = _require('path') as typeof import('path');
+
+ const configPath = path.join(process.cwd(), 'config.json');
+ const raw = fs.readFileSync(configPath, 'utf-8');
+ fileConfig = JSON.parse(raw) as ConfigFileStruct;
+ console.log('load dynamic config success');
+ } else {
+ // 默认使用编译时生成的配置
+ fileConfig = runtimeConfig as unknown as ConfigFileStruct;
+ }
+
+ // 从文件中获取源信息,用于补全源
+ const apiSiteEntries = Object.entries(fileConfig.api_site);
+ let allUsers = userNames.map((uname) => ({
+ username: uname,
+ role: 'user',
+ }));
+ const ownerUser = process.env.USERNAME;
+ if (ownerUser) {
+ allUsers = allUsers.filter((u) => u.username !== ownerUser);
+ allUsers.unshift({
+ username: ownerUser,
+ role: 'owner',
+ });
+ }
+ const adminConfig = {
+ SiteConfig: {
+ SiteName: process.env.SITE_NAME || 'MoonTV',
+ Announcement:
+ process.env.ANNOUNCEMENT ||
+ '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
+ SearchDownstreamMaxPage:
+ Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
+ SiteInterfaceCacheTime: fileConfig.cache_time || 7200,
+ ImageProxy: process.env.NEXT_PUBLIC_IMAGE_PROXY || '',
+ DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '',
+ },
+ UserConfig: {
+ AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
+ Users: allUsers as any,
+ },
+ SourceConfig: apiSiteEntries.map(([key, site]) => ({
+ key,
+ name: site.name,
+ api: site.api,
+ detail: site.detail,
+ from: 'config',
+ disabled: false,
+ })),
+ } as AdminConfig;
+
+ if (storage && typeof (storage as any).setAdminConfig === 'function') {
+ await (storage as any).setAdminConfig(adminConfig);
+ }
+ if (cachedConfig == null) {
+ // serverless 环境,直接使用 adminConfig
+ cachedConfig = adminConfig;
+ }
+ cachedConfig.SiteConfig = adminConfig.SiteConfig;
+ cachedConfig.UserConfig = adminConfig.UserConfig;
+ cachedConfig.SourceConfig = adminConfig.SourceConfig;
+}
+
+export async function getCacheTime(): Promise {
+ const config = await getConfig();
+ return config.SiteConfig.SiteInterfaceCacheTime || 7200;
+}
+
+export async function getAvailableApiSites(): Promise {
+ const config = await getConfig();
+ return config.SourceConfig.filter((s) => !s.disabled).map((s) => ({
+ key: s.key,
+ name: s.name,
+ api: s.api,
+ detail: s.detail,
+ }));
+}
diff --git a/src/lib/d1.db.ts b/src/lib/d1.db.ts
new file mode 100644
index 0000000..b4a9ddf
--- /dev/null
+++ b/src/lib/d1.db.ts
@@ -0,0 +1,476 @@
+/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
+
+import { AdminConfig } from './admin.types';
+import { Favorite, IStorage, PlayRecord } from './types';
+
+// 搜索历史最大条数
+const SEARCH_HISTORY_LIMIT = 20;
+
+// D1 数据库接口
+interface D1Database {
+ prepare(sql: string): D1PreparedStatement;
+ exec(sql: string): Promise;
+ batch(statements: D1PreparedStatement[]): Promise;
+}
+
+interface D1PreparedStatement {
+ bind(...values: any[]): D1PreparedStatement;
+ first(colName?: string): Promise;
+ run(): Promise;
+ all(): Promise>;
+}
+
+interface D1Result {
+ results: T[];
+ success: boolean;
+ error?: string;
+ meta: {
+ changed_db: boolean;
+ changes: number;
+ last_row_id: number;
+ duration: number;
+ };
+}
+
+interface D1ExecResult {
+ count: number;
+ duration: number;
+}
+
+// 获取全局D1数据库实例
+function getD1Database(): D1Database {
+ return (process.env as any).DB as D1Database;
+}
+
+export class D1Storage implements IStorage {
+ private db: D1Database | null = null;
+
+ private async getDatabase(): Promise {
+ if (!this.db) {
+ this.db = getD1Database();
+ }
+ return this.db;
+ }
+
+ // 播放记录相关
+ async getPlayRecord(
+ userName: string,
+ key: string
+ ): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT * FROM play_records WHERE username = ? AND key = ?')
+ .bind(userName, key)
+ .first();
+
+ if (!result) return null;
+
+ return {
+ title: result.title,
+ source_name: result.source_name,
+ cover: result.cover,
+ year: result.year,
+ index: result.index_episode,
+ total_episodes: result.total_episodes,
+ play_time: result.play_time,
+ total_time: result.total_time,
+ save_time: result.save_time,
+ search_title: result.search_title || undefined,
+ };
+ } catch (err) {
+ console.error('Failed to get play record:', err);
+ throw err;
+ }
+ }
+
+ async setPlayRecord(
+ userName: string,
+ key: string,
+ record: PlayRecord
+ ): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare(
+ `
+ INSERT OR REPLACE INTO play_records
+ (username, key, title, source_name, cover, year, index_episode, total_episodes, play_time, total_time, save_time, search_title)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `
+ )
+ .bind(
+ userName,
+ key,
+ record.title,
+ record.source_name,
+ record.cover,
+ record.year,
+ record.index,
+ record.total_episodes,
+ record.play_time,
+ record.total_time,
+ record.save_time,
+ record.search_title || null
+ )
+ .run();
+ } catch (err) {
+ console.error('Failed to set play record:', err);
+ throw err;
+ }
+ }
+
+ async getAllPlayRecords(
+ userName: string
+ ): Promise> {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare(
+ 'SELECT * FROM play_records WHERE username = ? ORDER BY save_time DESC'
+ )
+ .bind(userName)
+ .all();
+
+ const records: Record = {};
+
+ result.results.forEach((row: any) => {
+ records[row.key] = {
+ title: row.title,
+ source_name: row.source_name,
+ cover: row.cover,
+ year: row.year,
+ index: row.index_episode,
+ total_episodes: row.total_episodes,
+ play_time: row.play_time,
+ total_time: row.total_time,
+ save_time: row.save_time,
+ search_title: row.search_title || undefined,
+ };
+ });
+
+ return records;
+ } catch (err) {
+ console.error('Failed to get all play records:', err);
+ throw err;
+ }
+ }
+
+ async deletePlayRecord(userName: string, key: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare('DELETE FROM play_records WHERE username = ? AND key = ?')
+ .bind(userName, key)
+ .run();
+ } catch (err) {
+ console.error('Failed to delete play record:', err);
+ throw err;
+ }
+ }
+
+ // 收藏相关
+ async getFavorite(userName: string, key: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT * FROM favorites WHERE username = ? AND key = ?')
+ .bind(userName, key)
+ .first();
+
+ if (!result) return null;
+
+ return {
+ title: result.title,
+ source_name: result.source_name,
+ cover: result.cover,
+ year: result.year,
+ total_episodes: result.total_episodes,
+ save_time: result.save_time,
+ search_title: result.search_title,
+ };
+ } catch (err) {
+ console.error('Failed to get favorite:', err);
+ throw err;
+ }
+ }
+
+ async setFavorite(
+ userName: string,
+ key: string,
+ favorite: Favorite
+ ): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare(
+ `
+ INSERT OR REPLACE INTO favorites
+ (username, key, title, source_name, cover, year, total_episodes, save_time)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ `
+ )
+ .bind(
+ userName,
+ key,
+ favorite.title,
+ favorite.source_name,
+ favorite.cover,
+ favorite.year,
+ favorite.total_episodes,
+ favorite.save_time
+ )
+ .run();
+ } catch (err) {
+ console.error('Failed to set favorite:', err);
+ throw err;
+ }
+ }
+
+ async getAllFavorites(userName: string): Promise> {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare(
+ 'SELECT * FROM favorites WHERE username = ? ORDER BY save_time DESC'
+ )
+ .bind(userName)
+ .all();
+
+ const favorites: Record = {};
+
+ result.results.forEach((row: any) => {
+ favorites[row.key] = {
+ title: row.title,
+ source_name: row.source_name,
+ cover: row.cover,
+ year: row.year,
+ total_episodes: row.total_episodes,
+ save_time: row.save_time,
+ search_title: row.search_title,
+ };
+ });
+
+ return favorites;
+ } catch (err) {
+ console.error('Failed to get all favorites:', err);
+ throw err;
+ }
+ }
+
+ async deleteFavorite(userName: string, key: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare('DELETE FROM favorites WHERE username = ? AND key = ?')
+ .bind(userName, key)
+ .run();
+ } catch (err) {
+ console.error('Failed to delete favorite:', err);
+ throw err;
+ }
+ }
+
+ // 用户相关
+ async registerUser(userName: string, password: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare('INSERT INTO users (username, password) VALUES (?, ?)')
+ .bind(userName, password)
+ .run();
+ } catch (err) {
+ console.error('Failed to register user:', err);
+ throw err;
+ }
+ }
+
+ async verifyUser(userName: string, password: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT password FROM users WHERE username = ?')
+ .bind(userName)
+ .first<{ password: string }>();
+
+ return result?.password === password;
+ } catch (err) {
+ console.error('Failed to verify user:', err);
+ throw err;
+ }
+ }
+
+ async checkUserExist(userName: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT 1 FROM users WHERE username = ?')
+ .bind(userName)
+ .first();
+
+ return result !== null;
+ } catch (err) {
+ console.error('Failed to check user existence:', err);
+ throw err;
+ }
+ }
+
+ async changePassword(userName: string, newPassword: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare('UPDATE users SET password = ? WHERE username = ?')
+ .bind(newPassword, userName)
+ .run();
+ } catch (err) {
+ console.error('Failed to change password:', err);
+ throw err;
+ }
+ }
+
+ async deleteUser(userName: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ const statements = [
+ db.prepare('DELETE FROM users WHERE username = ?').bind(userName),
+ db
+ .prepare('DELETE FROM play_records WHERE username = ?')
+ .bind(userName),
+ db.prepare('DELETE FROM favorites WHERE username = ?').bind(userName),
+ db
+ .prepare('DELETE FROM search_history WHERE username = ?')
+ .bind(userName),
+ ];
+
+ await db.batch(statements);
+ } catch (err) {
+ console.error('Failed to delete user:', err);
+ throw err;
+ }
+ }
+
+ // 搜索历史相关
+ async getSearchHistory(userName: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare(
+ 'SELECT keyword FROM search_history WHERE username = ? ORDER BY created_at DESC LIMIT ?'
+ )
+ .bind(userName, SEARCH_HISTORY_LIMIT)
+ .all<{ keyword: string }>();
+
+ return result.results.map((row) => row.keyword);
+ } catch (err) {
+ console.error('Failed to get search history:', err);
+ throw err;
+ }
+ }
+
+ async addSearchHistory(userName: string, keyword: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ // 先删除可能存在的重复记录
+ await db
+ .prepare(
+ 'DELETE FROM search_history WHERE username = ? AND keyword = ?'
+ )
+ .bind(userName, keyword)
+ .run();
+
+ // 添加新记录
+ await db
+ .prepare('INSERT INTO search_history (username, keyword) VALUES (?, ?)')
+ .bind(userName, keyword)
+ .run();
+
+ // 保持历史记录条数限制
+ await db
+ .prepare(
+ `
+ DELETE FROM search_history
+ WHERE username = ? AND id NOT IN (
+ SELECT id FROM search_history
+ WHERE username = ?
+ ORDER BY created_at DESC
+ LIMIT ?
+ )
+ `
+ )
+ .bind(userName, userName, SEARCH_HISTORY_LIMIT)
+ .run();
+ } catch (err) {
+ console.error('Failed to add search history:', err);
+ throw err;
+ }
+ }
+
+ async deleteSearchHistory(userName: string, keyword?: string): Promise {
+ try {
+ const db = await this.getDatabase();
+ if (keyword) {
+ await db
+ .prepare(
+ 'DELETE FROM search_history WHERE username = ? AND keyword = ?'
+ )
+ .bind(userName, keyword)
+ .run();
+ } else {
+ await db
+ .prepare('DELETE FROM search_history WHERE username = ?')
+ .bind(userName)
+ .run();
+ }
+ } catch (err) {
+ console.error('Failed to delete search history:', err);
+ throw err;
+ }
+ }
+
+ // 用户列表
+ async getAllUsers(): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT username FROM users ORDER BY created_at ASC')
+ .all<{ username: string }>();
+
+ return result.results.map((row) => row.username);
+ } catch (err) {
+ console.error('Failed to get all users:', err);
+ throw err;
+ }
+ }
+
+ // 管理员配置相关
+ async getAdminConfig(): Promise {
+ try {
+ const db = await this.getDatabase();
+ const result = await db
+ .prepare('SELECT config FROM admin_config WHERE id = 1')
+ .first<{ config: string }>();
+
+ if (!result) return null;
+
+ return JSON.parse(result.config) as AdminConfig;
+ } catch (err) {
+ console.error('Failed to get admin config:', err);
+ throw err;
+ }
+ }
+
+ async setAdminConfig(config: AdminConfig): Promise {
+ try {
+ const db = await this.getDatabase();
+ await db
+ .prepare(
+ 'INSERT OR REPLACE INTO admin_config (id, config) VALUES (1, ?)'
+ )
+ .bind(JSON.stringify(config))
+ .run();
+ } catch (err) {
+ console.error('Failed to set admin config:', err);
+ throw err;
+ }
+ }
+}
diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts
new file mode 100644
index 0000000..2c42f9e
--- /dev/null
+++ b/src/lib/db.client.ts
@@ -0,0 +1,1244 @@
+/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function */
+'use client';
+
+/**
+ * 仅在浏览器端使用的数据库工具,目前基于 localStorage 实现。
+ * 之所以单独拆分文件,是为了避免在客户端 bundle 中引入 `fs`, `path` 等 Node.js 内置模块,
+ * 从而解决诸如 "Module not found: Can't resolve 'fs'" 的问题。
+ *
+ * 功能:
+ * 1. 获取全部播放记录(getAllPlayRecords)。
+ * 2. 保存播放记录(savePlayRecord)。
+ * 3. 数据库存储模式下的混合缓存策略,提升用户体验。
+ *
+ * 如后续需要在客户端读取收藏等其它数据,可按同样方式在此文件中补充实现。
+ */
+
+import { getAuthInfoFromBrowserCookie } from './auth';
+
+// ---- 类型 ----
+export interface PlayRecord {
+ title: string;
+ source_name: string;
+ year: string;
+ cover: string;
+ index: number; // 第几集
+ total_episodes: number; // 总集数
+ play_time: number; // 播放进度(秒)
+ total_time: number; // 总进度(秒)
+ save_time: number; // 记录保存时间(时间戳)
+ search_title?: string; // 搜索时使用的标题
+}
+
+// ---- 收藏类型 ----
+export interface Favorite {
+ title: string;
+ source_name: string;
+ year: string;
+ cover: string;
+ total_episodes: number;
+ save_time: number;
+ search_title?: string;
+}
+
+// ---- 缓存数据结构 ----
+interface CacheData {
+ data: T;
+ timestamp: number;
+ version: string;
+}
+
+interface UserCacheStore {
+ playRecords?: CacheData>;
+ favorites?: CacheData>;
+ searchHistory?: CacheData;
+}
+
+// ---- 常量 ----
+const PLAY_RECORDS_KEY = 'moontv_play_records';
+const FAVORITES_KEY = 'moontv_favorites';
+const SEARCH_HISTORY_KEY = 'moontv_search_history';
+
+// 缓存相关常量
+const CACHE_PREFIX = 'moontv_cache_';
+const CACHE_VERSION = '1.0.0';
+const CACHE_EXPIRE_TIME = 60 * 60 * 1000; // 一小时缓存过期
+
+// ---- 环境变量 ----
+const STORAGE_TYPE = (() => {
+ const raw =
+ (typeof window !== 'undefined' &&
+ (window as any).RUNTIME_CONFIG?.STORAGE_TYPE) ||
+ (process.env.STORAGE_TYPE as
+ | 'localstorage'
+ | 'redis'
+ | 'd1'
+ | 'upstash'
+ | undefined) ||
+ 'localstorage';
+ return raw;
+})();
+
+// ---------------- 搜索历史相关常量 ----------------
+// 搜索历史最大保存条数
+const SEARCH_HISTORY_LIMIT = 20;
+
+// ---- 缓存管理器 ----
+class HybridCacheManager {
+ private static instance: HybridCacheManager;
+
+ static getInstance(): HybridCacheManager {
+ if (!HybridCacheManager.instance) {
+ HybridCacheManager.instance = new HybridCacheManager();
+ }
+ return HybridCacheManager.instance;
+ }
+
+ /**
+ * 获取当前用户名
+ */
+ private getCurrentUsername(): string | null {
+ const authInfo = getAuthInfoFromBrowserCookie();
+ return authInfo?.username || null;
+ }
+
+ /**
+ * 生成用户专属的缓存key
+ */
+ private getUserCacheKey(username: string): string {
+ return `${CACHE_PREFIX}${username}`;
+ }
+
+ /**
+ * 获取用户缓存数据
+ */
+ private getUserCache(username: string): UserCacheStore {
+ if (typeof window === 'undefined') return {};
+
+ try {
+ const cacheKey = this.getUserCacheKey(username);
+ const cached = localStorage.getItem(cacheKey);
+ return cached ? JSON.parse(cached) : {};
+ } catch (error) {
+ console.warn('获取用户缓存失败:', error);
+ return {};
+ }
+ }
+
+ /**
+ * 保存用户缓存数据
+ */
+ private saveUserCache(username: string, cache: UserCacheStore): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ const cacheKey = this.getUserCacheKey(username);
+ localStorage.setItem(cacheKey, JSON.stringify(cache));
+ } catch (error) {
+ console.warn('保存用户缓存失败:', error);
+ }
+ }
+
+ /**
+ * 检查缓存是否有效
+ */
+ private isCacheValid(cache: CacheData): boolean {
+ const now = Date.now();
+ return (
+ cache.version === CACHE_VERSION &&
+ now - cache.timestamp < CACHE_EXPIRE_TIME
+ );
+ }
+
+ /**
+ * 创建缓存数据
+ */
+ private createCacheData(data: T): CacheData {
+ return {
+ data,
+ timestamp: Date.now(),
+ version: CACHE_VERSION,
+ };
+ }
+
+ /**
+ * 获取缓存的播放记录
+ */
+ getCachedPlayRecords(): Record | null {
+ const username = this.getCurrentUsername();
+ if (!username) return null;
+
+ const userCache = this.getUserCache(username);
+ const cached = userCache.playRecords;
+
+ if (cached && this.isCacheValid(cached)) {
+ return cached.data;
+ }
+
+ return null;
+ }
+
+ /**
+ * 缓存播放记录
+ */
+ cachePlayRecords(data: Record): void {
+ const username = this.getCurrentUsername();
+ if (!username) return;
+
+ const userCache = this.getUserCache(username);
+ userCache.playRecords = this.createCacheData(data);
+ this.saveUserCache(username, userCache);
+ }
+
+ /**
+ * 获取缓存的收藏
+ */
+ getCachedFavorites(): Record | null {
+ const username = this.getCurrentUsername();
+ if (!username) return null;
+
+ const userCache = this.getUserCache(username);
+ const cached = userCache.favorites;
+
+ if (cached && this.isCacheValid(cached)) {
+ return cached.data;
+ }
+
+ return null;
+ }
+
+ /**
+ * 缓存收藏
+ */
+ cacheFavorites(data: Record): void {
+ const username = this.getCurrentUsername();
+ if (!username) return;
+
+ const userCache = this.getUserCache(username);
+ userCache.favorites = this.createCacheData(data);
+ this.saveUserCache(username, userCache);
+ }
+
+ /**
+ * 获取缓存的搜索历史
+ */
+ getCachedSearchHistory(): string[] | null {
+ const username = this.getCurrentUsername();
+ if (!username) return null;
+
+ const userCache = this.getUserCache(username);
+ const cached = userCache.searchHistory;
+
+ if (cached && this.isCacheValid(cached)) {
+ return cached.data;
+ }
+
+ return null;
+ }
+
+ /**
+ * 缓存搜索历史
+ */
+ cacheSearchHistory(data: string[]): void {
+ const username = this.getCurrentUsername();
+ if (!username) return;
+
+ const userCache = this.getUserCache(username);
+ userCache.searchHistory = this.createCacheData(data);
+ this.saveUserCache(username, userCache);
+ }
+
+ /**
+ * 清除指定用户的所有缓存
+ */
+ clearUserCache(username?: string): void {
+ const targetUsername = username || this.getCurrentUsername();
+ if (!targetUsername) return;
+
+ try {
+ const cacheKey = this.getUserCacheKey(targetUsername);
+ localStorage.removeItem(cacheKey);
+ } catch (error) {
+ console.warn('清除用户缓存失败:', error);
+ }
+ }
+
+ /**
+ * 清除所有过期缓存
+ */
+ clearExpiredCaches(): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ const keysToRemove: string[] = [];
+
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ if (key?.startsWith(CACHE_PREFIX)) {
+ try {
+ const cache = JSON.parse(localStorage.getItem(key) || '{}');
+ // 检查是否有任何缓存数据过期
+ let hasValidData = false;
+ for (const [, cacheData] of Object.entries(cache)) {
+ if (cacheData && this.isCacheValid(cacheData as CacheData)) {
+ hasValidData = true;
+ break;
+ }
+ }
+ if (!hasValidData) {
+ keysToRemove.push(key);
+ }
+ } catch {
+ // 解析失败的缓存也删除
+ keysToRemove.push(key);
+ }
+ }
+ }
+
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
+ } catch (error) {
+ console.warn('清除过期缓存失败:', error);
+ }
+ }
+}
+
+// 获取缓存管理器实例
+const cacheManager = HybridCacheManager.getInstance();
+
+// ---- 错误处理辅助函数 ----
+/**
+ * 数据库操作失败时的通用错误处理
+ * 立即从数据库刷新对应类型的缓存以保持数据一致性
+ */
+async function handleDatabaseOperationFailure(
+ dataType: 'playRecords' | 'favorites' | 'searchHistory',
+ error: any
+): Promise {
+ console.error(`数据库操作失败 (${dataType}):`, error);
+
+ try {
+ let freshData: any;
+ let eventName: string;
+
+ switch (dataType) {
+ case 'playRecords':
+ freshData = await fetchFromApi>(
+ `/api/playrecords`
+ );
+ cacheManager.cachePlayRecords(freshData);
+ eventName = 'playRecordsUpdated';
+ break;
+ case 'favorites':
+ freshData = await fetchFromApi>(
+ `/api/favorites`
+ );
+ cacheManager.cacheFavorites(freshData);
+ eventName = 'favoritesUpdated';
+ break;
+ case 'searchHistory':
+ freshData = await fetchFromApi(`/api/searchhistory`);
+ cacheManager.cacheSearchHistory(freshData);
+ eventName = 'searchHistoryUpdated';
+ break;
+ }
+
+ // 触发更新事件通知组件
+ window.dispatchEvent(
+ new CustomEvent(eventName, {
+ detail: freshData,
+ })
+ );
+ } catch (refreshErr) {
+ console.error(`刷新${dataType}缓存失败:`, refreshErr);
+ }
+}
+
+// 页面加载时清理过期缓存
+if (typeof window !== 'undefined') {
+ setTimeout(() => cacheManager.clearExpiredCaches(), 1000);
+}
+
+// ---- 工具函数 ----
+async function fetchFromApi(path: string): Promise {
+ const res = await fetch(path);
+ if (!res.ok) throw new Error(`请求 ${path} 失败: ${res.status}`);
+ return (await res.json()) as T;
+}
+
+/**
+ * 生成存储key
+ */
+export function generateStorageKey(source: string, id: string): string {
+ return `${source}+${id}`;
+}
+
+// ---- API ----
+/**
+ * 读取全部播放记录。
+ * D1 存储模式下使用混合缓存策略:优先返回缓存数据,后台异步同步最新数据。
+ * 在服务端渲染阶段 (window === undefined) 时返回空对象,避免报错。
+ */
+export async function getAllPlayRecords(): Promise> {
+ // 服务器端渲染阶段直接返回空,交由客户端 useEffect 再行请求
+ if (typeof window === 'undefined') {
+ return {};
+ }
+
+ // 数据库存储模式:使用混合缓存策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 优先从缓存获取数据
+ const cachedData = cacheManager.getCachedPlayRecords();
+
+ if (cachedData) {
+ // 返回缓存数据,同时后台异步更新
+ fetchFromApi>(`/api/playrecords`)
+ .then((freshData) => {
+ // 只有数据真正不同时才更新缓存
+ if (JSON.stringify(cachedData) !== JSON.stringify(freshData)) {
+ cacheManager.cachePlayRecords(freshData);
+ // 触发数据更新事件,供组件监听
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: freshData,
+ })
+ );
+ }
+ })
+ .catch((err) => {
+ console.warn('后台同步播放记录失败:', err);
+ });
+
+ return cachedData;
+ } else {
+ // 缓存为空,直接从 API 获取并缓存
+ try {
+ const freshData = await fetchFromApi>(
+ `/api/playrecords`
+ );
+ cacheManager.cachePlayRecords(freshData);
+ return freshData;
+ } catch (err) {
+ console.error('获取播放记录失败:', err);
+ return {};
+ }
+ }
+ }
+
+ // localstorage 模式
+ try {
+ const raw = localStorage.getItem(PLAY_RECORDS_KEY);
+ if (!raw) return {};
+ return JSON.parse(raw) as Record;
+ } catch (err) {
+ console.error('读取播放记录失败:', err);
+ return {};
+ }
+}
+
+/**
+ * 保存播放记录。
+ * 数据库存储模式下使用乐观更新:先更新缓存(立即生效),再异步同步到数据库。
+ */
+export async function savePlayRecord(
+ source: string,
+ id: string,
+ record: PlayRecord
+): Promise {
+ const key = generateStorageKey(source, id);
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedRecords = cacheManager.getCachedPlayRecords() || {};
+ cachedRecords[key] = record;
+ cacheManager.cachePlayRecords(cachedRecords);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: cachedRecords,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch('/api/playrecords', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ key, record }),
+ });
+
+ if (!res.ok) {
+ throw new Error(`保存播放记录失败: ${res.status}`);
+ }
+ } catch (err) {
+ await handleDatabaseOperationFailure('playRecords', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localstorage 模式
+ if (typeof window === 'undefined') {
+ console.warn('无法在服务端保存播放记录到 localStorage');
+ return;
+ }
+
+ try {
+ const allRecords = await getAllPlayRecords();
+ allRecords[key] = record;
+ localStorage.setItem(PLAY_RECORDS_KEY, JSON.stringify(allRecords));
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: allRecords,
+ })
+ );
+ } catch (err) {
+ console.error('保存播放记录失败:', err);
+ throw err;
+ }
+}
+
+/**
+ * 删除播放记录。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function deletePlayRecord(
+ source: string,
+ id: string
+): Promise {
+ const key = generateStorageKey(source, id);
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedRecords = cacheManager.getCachedPlayRecords() || {};
+ delete cachedRecords[key];
+ cacheManager.cachePlayRecords(cachedRecords);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: cachedRecords,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(
+ `/api/playrecords?key=${encodeURIComponent(key)}`,
+ {
+ method: 'DELETE',
+ }
+ );
+ if (!res.ok) throw new Error(`删除播放记录失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('playRecords', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localstorage 模式
+ if (typeof window === 'undefined') {
+ console.warn('无法在服务端删除播放记录到 localStorage');
+ return;
+ }
+
+ try {
+ const allRecords = await getAllPlayRecords();
+ delete allRecords[key];
+ localStorage.setItem(PLAY_RECORDS_KEY, JSON.stringify(allRecords));
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: allRecords,
+ })
+ );
+ } catch (err) {
+ console.error('删除播放记录失败:', err);
+ throw err;
+ }
+}
+
+/* ---------------- 搜索历史相关 API ---------------- */
+
+/**
+ * 获取搜索历史。
+ * 数据库存储模式下使用混合缓存策略:优先返回缓存数据,后台异步同步最新数据。
+ */
+export async function getSearchHistory(): Promise {
+ // 服务器端渲染阶段直接返回空
+ if (typeof window === 'undefined') {
+ return [];
+ }
+
+ // 数据库存储模式:使用混合缓存策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 优先从缓存获取数据
+ const cachedData = cacheManager.getCachedSearchHistory();
+
+ if (cachedData) {
+ // 返回缓存数据,同时后台异步更新
+ fetchFromApi(`/api/searchhistory`)
+ .then((freshData) => {
+ // 只有数据真正不同时才更新缓存
+ if (JSON.stringify(cachedData) !== JSON.stringify(freshData)) {
+ cacheManager.cacheSearchHistory(freshData);
+ // 触发数据更新事件
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: freshData,
+ })
+ );
+ }
+ })
+ .catch((err) => {
+ console.warn('后台同步搜索历史失败:', err);
+ });
+
+ return cachedData;
+ } else {
+ // 缓存为空,直接从 API 获取并缓存
+ try {
+ const freshData = await fetchFromApi(`/api/searchhistory`);
+ cacheManager.cacheSearchHistory(freshData);
+ return freshData;
+ } catch (err) {
+ console.error('获取搜索历史失败:', err);
+ return [];
+ }
+ }
+ }
+
+ // localStorage 模式
+ try {
+ const raw = localStorage.getItem(SEARCH_HISTORY_KEY);
+ if (!raw) return [];
+ const arr = JSON.parse(raw) as string[];
+ // 仅返回字符串数组
+ return Array.isArray(arr) ? arr : [];
+ } catch (err) {
+ console.error('读取搜索历史失败:', err);
+ return [];
+ }
+}
+
+/**
+ * 将关键字添加到搜索历史。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function addSearchHistory(keyword: string): Promise {
+ const trimmed = keyword.trim();
+ if (!trimmed) return;
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedHistory = cacheManager.getCachedSearchHistory() || [];
+ const newHistory = [trimmed, ...cachedHistory.filter((k) => k !== trimmed)];
+ // 限制长度
+ if (newHistory.length > SEARCH_HISTORY_LIMIT) {
+ newHistory.length = SEARCH_HISTORY_LIMIT;
+ }
+ cacheManager.cacheSearchHistory(newHistory);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: newHistory,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch('/api/searchhistory', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ keyword: trimmed }),
+ });
+ if (!res.ok) throw new Error(`保存搜索历史失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('searchHistory', err);
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') return;
+
+ try {
+ const history = await getSearchHistory();
+ const newHistory = [trimmed, ...history.filter((k) => k !== trimmed)];
+ // 限制长度
+ if (newHistory.length > SEARCH_HISTORY_LIMIT) {
+ newHistory.length = SEARCH_HISTORY_LIMIT;
+ }
+ localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory));
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: newHistory,
+ })
+ );
+ } catch (err) {
+ console.error('保存搜索历史失败:', err);
+ }
+}
+
+/**
+ * 清空搜索历史。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function clearSearchHistory(): Promise {
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ cacheManager.cacheSearchHistory([]);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: [],
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(`/api/searchhistory`, {
+ method: 'DELETE',
+ });
+ if (!res.ok) throw new Error(`清空搜索历史失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('searchHistory', err);
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') return;
+ localStorage.removeItem(SEARCH_HISTORY_KEY);
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: [],
+ })
+ );
+}
+
+/**
+ * 删除单条搜索历史。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function deleteSearchHistory(keyword: string): Promise {
+ const trimmed = keyword.trim();
+ if (!trimmed) return;
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedHistory = cacheManager.getCachedSearchHistory() || [];
+ const newHistory = cachedHistory.filter((k) => k !== trimmed);
+ cacheManager.cacheSearchHistory(newHistory);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: newHistory,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(
+ `/api/searchhistory?keyword=${encodeURIComponent(trimmed)}`,
+ {
+ method: 'DELETE',
+ }
+ );
+ if (!res.ok) throw new Error(`删除搜索历史失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('searchHistory', err);
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') return;
+
+ try {
+ const history = await getSearchHistory();
+ const newHistory = history.filter((k) => k !== trimmed);
+ localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory));
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: newHistory,
+ })
+ );
+ } catch (err) {
+ console.error('删除搜索历史失败:', err);
+ }
+}
+
+// ---------------- 收藏相关 API ----------------
+
+/**
+ * 获取全部收藏。
+ * 数据库存储模式下使用混合缓存策略:优先返回缓存数据,后台异步同步最新数据。
+ */
+export async function getAllFavorites(): Promise> {
+ // 服务器端渲染阶段直接返回空
+ if (typeof window === 'undefined') {
+ return {};
+ }
+
+ // 数据库存储模式:使用混合缓存策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 优先从缓存获取数据
+ const cachedData = cacheManager.getCachedFavorites();
+
+ if (cachedData) {
+ // 返回缓存数据,同时后台异步更新
+ fetchFromApi>(`/api/favorites`)
+ .then((freshData) => {
+ // 只有数据真正不同时才更新缓存
+ if (JSON.stringify(cachedData) !== JSON.stringify(freshData)) {
+ cacheManager.cacheFavorites(freshData);
+ // 触发数据更新事件
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: freshData,
+ })
+ );
+ }
+ })
+ .catch((err) => {
+ console.warn('后台同步收藏失败:', err);
+ });
+
+ return cachedData;
+ } else {
+ // 缓存为空,直接从 API 获取并缓存
+ try {
+ const freshData = await fetchFromApi>(
+ `/api/favorites`
+ );
+ cacheManager.cacheFavorites(freshData);
+ return freshData;
+ } catch (err) {
+ console.error('获取收藏失败:', err);
+ return {};
+ }
+ }
+ }
+
+ // localStorage 模式
+ try {
+ const raw = localStorage.getItem(FAVORITES_KEY);
+ if (!raw) return {};
+ return JSON.parse(raw) as Record;
+ } catch (err) {
+ console.error('读取收藏失败:', err);
+ return {};
+ }
+}
+
+/**
+ * 保存收藏。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function saveFavorite(
+ source: string,
+ id: string,
+ favorite: Favorite
+): Promise {
+ const key = generateStorageKey(source, id);
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedFavorites = cacheManager.getCachedFavorites() || {};
+ cachedFavorites[key] = favorite;
+ cacheManager.cacheFavorites(cachedFavorites);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: cachedFavorites,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch('/api/favorites', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ key, favorite }),
+ });
+ if (!res.ok) throw new Error(`保存收藏失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('favorites', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') {
+ console.warn('无法在服务端保存收藏到 localStorage');
+ return;
+ }
+
+ try {
+ const allFavorites = await getAllFavorites();
+ allFavorites[key] = favorite;
+ localStorage.setItem(FAVORITES_KEY, JSON.stringify(allFavorites));
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: allFavorites,
+ })
+ );
+ } catch (err) {
+ console.error('保存收藏失败:', err);
+ throw err;
+ }
+}
+
+/**
+ * 删除收藏。
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function deleteFavorite(
+ source: string,
+ id: string
+): Promise {
+ const key = generateStorageKey(source, id);
+
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ const cachedFavorites = cacheManager.getCachedFavorites() || {};
+ delete cachedFavorites[key];
+ cacheManager.cacheFavorites(cachedFavorites);
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: cachedFavorites,
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(`/api/favorites?key=${encodeURIComponent(key)}`, {
+ method: 'DELETE',
+ });
+ if (!res.ok) throw new Error(`删除收藏失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('favorites', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') {
+ console.warn('无法在服务端删除收藏到 localStorage');
+ return;
+ }
+
+ try {
+ const allFavorites = await getAllFavorites();
+ delete allFavorites[key];
+ localStorage.setItem(FAVORITES_KEY, JSON.stringify(allFavorites));
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: allFavorites,
+ })
+ );
+ } catch (err) {
+ console.error('删除收藏失败:', err);
+ throw err;
+ }
+}
+
+/**
+ * 判断是否已收藏。
+ * 数据库存储模式下使用混合缓存策略:优先返回缓存数据,后台异步同步最新数据。
+ */
+export async function isFavorited(
+ source: string,
+ id: string
+): Promise {
+ const key = generateStorageKey(source, id);
+
+ // 数据库存储模式:使用混合缓存策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ const cachedFavorites = cacheManager.getCachedFavorites();
+
+ if (cachedFavorites) {
+ // 返回缓存数据,同时后台异步更新
+ fetchFromApi>(`/api/favorites`)
+ .then((freshData) => {
+ // 只有数据真正不同时才更新缓存
+ if (JSON.stringify(cachedFavorites) !== JSON.stringify(freshData)) {
+ cacheManager.cacheFavorites(freshData);
+ // 触发数据更新事件
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: freshData,
+ })
+ );
+ }
+ })
+ .catch((err) => {
+ console.warn('后台同步收藏失败:', err);
+ });
+
+ return !!cachedFavorites[key];
+ } else {
+ // 缓存为空,直接从 API 获取并缓存
+ try {
+ const freshData = await fetchFromApi>(
+ `/api/favorites`
+ );
+ cacheManager.cacheFavorites(freshData);
+ return !!freshData[key];
+ } catch (err) {
+ console.error('检查收藏状态失败:', err);
+ return false;
+ }
+ }
+ }
+
+ // localStorage 模式
+ const allFavorites = await getAllFavorites();
+ return !!allFavorites[key];
+}
+
+/**
+ * 清空全部播放记录
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function clearAllPlayRecords(): Promise {
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ cacheManager.cachePlayRecords({});
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: {},
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(`/api/playrecords`, {
+ method: 'DELETE',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ if (!res.ok) throw new Error(`清空播放记录失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('playRecords', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') return;
+ localStorage.removeItem(PLAY_RECORDS_KEY);
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: {},
+ })
+ );
+}
+
+/**
+ * 清空全部收藏
+ * 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
+ */
+export async function clearAllFavorites(): Promise {
+ // 数据库存储模式:乐观更新策略(包括 redis、d1、upstash)
+ if (STORAGE_TYPE !== 'localstorage') {
+ // 立即更新缓存
+ cacheManager.cacheFavorites({});
+
+ // 触发立即更新事件
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: {},
+ })
+ );
+
+ // 异步同步到数据库
+ try {
+ const res = await fetch(`/api/favorites`, {
+ method: 'DELETE',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ if (!res.ok) throw new Error(`清空收藏失败: ${res.status}`);
+ } catch (err) {
+ await handleDatabaseOperationFailure('favorites', err);
+ throw err;
+ }
+ return;
+ }
+
+ // localStorage 模式
+ if (typeof window === 'undefined') return;
+ localStorage.removeItem(FAVORITES_KEY);
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: {},
+ })
+ );
+}
+
+// ---------------- 混合缓存辅助函数 ----------------
+
+/**
+ * 清除当前用户的所有缓存数据
+ * 用于用户登出时清理缓存
+ */
+export function clearUserCache(): void {
+ if (STORAGE_TYPE !== 'localstorage') {
+ cacheManager.clearUserCache();
+ }
+}
+
+/**
+ * 手动刷新所有缓存数据
+ * 强制从服务器重新获取数据并更新缓存
+ */
+export async function refreshAllCache(): Promise {
+ if (STORAGE_TYPE === 'localstorage') return;
+
+ try {
+ // 并行刷新所有数据
+ const [playRecords, favorites, searchHistory] = await Promise.allSettled([
+ fetchFromApi>(`/api/playrecords`),
+ fetchFromApi>(`/api/favorites`),
+ fetchFromApi(`/api/searchhistory`),
+ ]);
+
+ if (playRecords.status === 'fulfilled') {
+ cacheManager.cachePlayRecords(playRecords.value);
+ window.dispatchEvent(
+ new CustomEvent('playRecordsUpdated', {
+ detail: playRecords.value,
+ })
+ );
+ }
+
+ if (favorites.status === 'fulfilled') {
+ cacheManager.cacheFavorites(favorites.value);
+ window.dispatchEvent(
+ new CustomEvent('favoritesUpdated', {
+ detail: favorites.value,
+ })
+ );
+ }
+
+ if (searchHistory.status === 'fulfilled') {
+ cacheManager.cacheSearchHistory(searchHistory.value);
+ window.dispatchEvent(
+ new CustomEvent('searchHistoryUpdated', {
+ detail: searchHistory.value,
+ })
+ );
+ }
+ } catch (err) {
+ console.error('刷新缓存失败:', err);
+ }
+}
+
+/**
+ * 获取缓存状态信息
+ * 用于调试和监控缓存健康状态
+ */
+export function getCacheStatus(): {
+ hasPlayRecords: boolean;
+ hasFavorites: boolean;
+ hasSearchHistory: boolean;
+ username: string | null;
+} {
+ if (STORAGE_TYPE === 'localstorage') {
+ return {
+ hasPlayRecords: false,
+ hasFavorites: false,
+ hasSearchHistory: false,
+ username: null,
+ };
+ }
+
+ const authInfo = getAuthInfoFromBrowserCookie();
+ return {
+ hasPlayRecords: !!cacheManager.getCachedPlayRecords(),
+ hasFavorites: !!cacheManager.getCachedFavorites(),
+ hasSearchHistory: !!cacheManager.getCachedSearchHistory(),
+ username: authInfo?.username || null,
+ };
+}
+
+// ---------------- React Hook 辅助类型 ----------------
+
+export type CacheUpdateEvent =
+ | 'playRecordsUpdated'
+ | 'favoritesUpdated'
+ | 'searchHistoryUpdated';
+
+/**
+ * 用于 React 组件监听数据更新的事件监听器
+ * 使用方法:
+ *
+ * useEffect(() => {
+ * const unsubscribe = subscribeToDataUpdates('playRecordsUpdated', (data) => {
+ * setPlayRecords(data);
+ * });
+ * return unsubscribe;
+ * }, []);
+ */
+export function subscribeToDataUpdates(
+ eventType: CacheUpdateEvent,
+ callback: (data: T) => void
+): () => void {
+ if (typeof window === 'undefined') {
+ return () => {};
+ }
+
+ const handleUpdate = (event: CustomEvent) => {
+ callback(event.detail);
+ };
+
+ window.addEventListener(eventType, handleUpdate as EventListener);
+
+ return () => {
+ window.removeEventListener(eventType, handleUpdate as EventListener);
+ };
+}
+
+/**
+ * 预加载所有用户数据到缓存
+ * 适合在应用启动时调用,提升后续访问速度
+ */
+export async function preloadUserData(): Promise {
+ if (STORAGE_TYPE === 'localstorage') return;
+
+ // 检查是否已有有效缓存,避免重复请求
+ const status = getCacheStatus();
+ if (status.hasPlayRecords && status.hasFavorites && status.hasSearchHistory) {
+ return;
+ }
+
+ // 后台静默预加载,不阻塞界面
+ refreshAllCache().catch((err) => {
+ console.warn('预加载用户数据失败:', err);
+ });
+}
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..b075b70
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,187 @@
+/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
+
+import { AdminConfig } from './admin.types';
+import { D1Storage } from './d1.db';
+import { RedisStorage } from './redis.db';
+import { Favorite, IStorage, PlayRecord } from './types';
+import { UpstashRedisStorage } from './upstash.db';
+
+// storage type 常量: 'localstorage' | 'redis' | 'd1' | 'upstash',默认 'localstorage'
+const STORAGE_TYPE =
+ (process.env.NEXT_PUBLIC_STORAGE_TYPE as
+ | 'localstorage'
+ | 'redis'
+ | 'd1'
+ | 'upstash'
+ | undefined) || 'localstorage';
+
+// 创建存储实例
+function createStorage(): IStorage {
+ switch (STORAGE_TYPE) {
+ case 'redis':
+ return new RedisStorage();
+ case 'upstash':
+ return new UpstashRedisStorage();
+ case 'd1':
+ return new D1Storage();
+ case 'localstorage':
+ default:
+ // 默认返回内存实现,保证本地开发可用
+ return null as unknown as IStorage;
+ }
+}
+
+// 单例存储实例
+let storageInstance: IStorage | null = null;
+
+export function getStorage(): IStorage {
+ if (!storageInstance) {
+ storageInstance = createStorage();
+ }
+ return storageInstance;
+}
+
+// 工具函数:生成存储key
+export function generateStorageKey(source: string, id: string): string {
+ return `${source}+${id}`;
+}
+
+// 导出便捷方法
+export class DbManager {
+ private storage: IStorage;
+
+ constructor() {
+ this.storage = getStorage();
+ }
+
+ // 播放记录相关方法
+ async getPlayRecord(
+ userName: string,
+ source: string,
+ id: string
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ return this.storage.getPlayRecord(userName, key);
+ }
+
+ async savePlayRecord(
+ userName: string,
+ source: string,
+ id: string,
+ record: PlayRecord
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ await this.storage.setPlayRecord(userName, key, record);
+ }
+
+ async getAllPlayRecords(userName: string): Promise<{
+ [key: string]: PlayRecord;
+ }> {
+ return this.storage.getAllPlayRecords(userName);
+ }
+
+ async deletePlayRecord(
+ userName: string,
+ source: string,
+ id: string
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ await this.storage.deletePlayRecord(userName, key);
+ }
+
+ // 收藏相关方法
+ async getFavorite(
+ userName: string,
+ source: string,
+ id: string
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ return this.storage.getFavorite(userName, key);
+ }
+
+ async saveFavorite(
+ userName: string,
+ source: string,
+ id: string,
+ favorite: Favorite
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ await this.storage.setFavorite(userName, key, favorite);
+ }
+
+ async getAllFavorites(
+ userName: string
+ ): Promise<{ [key: string]: Favorite }> {
+ return this.storage.getAllFavorites(userName);
+ }
+
+ async deleteFavorite(
+ userName: string,
+ source: string,
+ id: string
+ ): Promise {
+ const key = generateStorageKey(source, id);
+ await this.storage.deleteFavorite(userName, key);
+ }
+
+ async isFavorited(
+ userName: string,
+ source: string,
+ id: string
+ ): Promise {
+ const favorite = await this.getFavorite(userName, source, id);
+ return favorite !== null;
+ }
+
+ // ---------- 用户相关 ----------
+ async registerUser(userName: string, password: string): Promise {
+ await this.storage.registerUser(userName, password);
+ }
+
+ async verifyUser(userName: string, password: string): Promise {
+ return this.storage.verifyUser(userName, password);
+ }
+
+ // 检查用户是否已存在
+ async checkUserExist(userName: string): Promise {
+ return this.storage.checkUserExist(userName);
+ }
+
+ // ---------- 搜索历史 ----------
+ async getSearchHistory(userName: string): Promise {
+ return this.storage.getSearchHistory(userName);
+ }
+
+ async addSearchHistory(userName: string, keyword: string): Promise {
+ await this.storage.addSearchHistory(userName, keyword);
+ }
+
+ async deleteSearchHistory(userName: string, keyword?: string): Promise {
+ await this.storage.deleteSearchHistory(userName, keyword);
+ }
+
+ // 获取全部用户名
+ async getAllUsers(): Promise {
+ if (typeof (this.storage as any).getAllUsers === 'function') {
+ return (this.storage as any).getAllUsers();
+ }
+ return [];
+ }
+
+ // ---------- 管理员配置 ----------
+ async getAdminConfig(): Promise {
+ if (typeof (this.storage as any).getAdminConfig === 'function') {
+ return (this.storage as any).getAdminConfig();
+ }
+ return null;
+ }
+
+ async saveAdminConfig(config: AdminConfig): Promise {
+ if (typeof (this.storage as any).setAdminConfig === 'function') {
+ await (this.storage as any).setAdminConfig(config);
+ }
+ }
+}
+
+// 导出默认实例
+export const db = new DbManager();
diff --git a/src/lib/douban.client.ts b/src/lib/douban.client.ts
new file mode 100644
index 0000000..8c78194
--- /dev/null
+++ b/src/lib/douban.client.ts
@@ -0,0 +1,148 @@
+import { DoubanItem, DoubanResult } from './types';
+import { getDoubanProxyUrl } from './utils';
+
+interface DoubanCategoriesParams {
+ kind: 'tv' | 'movie';
+ category: string;
+ type: string;
+ pageLimit?: number;
+ pageStart?: number;
+}
+
+interface DoubanCategoryApiResponse {
+ total: number;
+ items: Array<{
+ id: string;
+ title: string;
+ card_subtitle: string;
+ pic: {
+ large: string;
+ normal: string;
+ };
+ rating: {
+ value: number;
+ };
+ }>;
+}
+
+/**
+ * 带超时的 fetch 请求
+ */
+async function fetchWithTimeout(
+ url: string,
+ options: RequestInit = {}
+): Promise {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
+
+ // 检查是否使用代理
+ const proxyUrl = getDoubanProxyUrl();
+ const finalUrl = proxyUrl ? `${proxyUrl}${encodeURIComponent(url)}` : url;
+
+ const fetchOptions: RequestInit = {
+ ...options,
+ signal: controller.signal,
+ headers: {
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
+ Referer: 'https://movie.douban.com/',
+ Accept: 'application/json, text/plain, */*',
+ ...options.headers,
+ },
+ };
+
+ try {
+ const response = await fetch(finalUrl, fetchOptions);
+ clearTimeout(timeoutId);
+ return response;
+ } catch (error) {
+ clearTimeout(timeoutId);
+ throw error;
+ }
+}
+
+/**
+ * 检查是否应该使用客户端获取豆瓣数据
+ */
+export function shouldUseDoubanClient(): boolean {
+ return getDoubanProxyUrl() !== null;
+}
+
+/**
+ * 浏览器端豆瓣分类数据获取函数
+ */
+export async function fetchDoubanCategories(
+ params: DoubanCategoriesParams
+): Promise {
+ const { kind, category, type, pageLimit = 20, pageStart = 0 } = params;
+
+ // 验证参数
+ if (!['tv', 'movie'].includes(kind)) {
+ throw new Error('kind 参数必须是 tv 或 movie');
+ }
+
+ if (!category || !type) {
+ throw new Error('category 和 type 参数不能为空');
+ }
+
+ if (pageLimit < 1 || pageLimit > 100) {
+ throw new Error('pageLimit 必须在 1-100 之间');
+ }
+
+ if (pageStart < 0) {
+ throw new Error('pageStart 不能小于 0');
+ }
+
+ const target = `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`;
+
+ try {
+ const response = await fetchWithTimeout(target);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const doubanData: DoubanCategoryApiResponse = await response.json();
+
+ // 转换数据格式
+ const list: DoubanItem[] = doubanData.items.map((item) => ({
+ id: item.id,
+ title: item.title,
+ poster: item.pic?.normal || item.pic?.large || '',
+ rate: item.rating?.value ? item.rating.value.toFixed(1) : '',
+ year: item.card_subtitle?.match(/(\d{4})/)?.[1] || '',
+ }));
+
+ return {
+ code: 200,
+ message: '获取成功',
+ list: list,
+ };
+ } catch (error) {
+ throw new Error(`获取豆瓣分类数据失败: ${(error as Error).message}`);
+ }
+}
+
+/**
+ * 统一的豆瓣分类数据获取函数,根据代理设置选择使用服务端 API 或客户端代理获取
+ */
+export async function getDoubanCategories(
+ params: DoubanCategoriesParams
+): Promise {
+ if (shouldUseDoubanClient()) {
+ // 使用客户端代理获取(当设置了代理 URL 时)
+ return fetchDoubanCategories(params);
+ } else {
+ // 使用服务端 API(当没有设置代理 URL 时)
+ const { kind, category, type, pageLimit = 20, pageStart = 0 } = params;
+ const response = await fetch(
+ `/api/douban/categories?kind=${kind}&category=${category}&type=${type}&limit=${pageLimit}&start=${pageStart}`
+ );
+
+ if (!response.ok) {
+ throw new Error('获取豆瓣分类数据失败');
+ }
+
+ return response.json();
+ }
+}
diff --git a/src/lib/downstream.ts b/src/lib/downstream.ts
new file mode 100644
index 0000000..6b84b43
--- /dev/null
+++ b/src/lib/downstream.ts
@@ -0,0 +1,344 @@
+import { API_CONFIG, ApiSite, getConfig } from '@/lib/config';
+import { SearchResult } from '@/lib/types';
+import { cleanHtmlTags } from '@/lib/utils';
+
+interface ApiSearchItem {
+ vod_id: string;
+ vod_name: string;
+ vod_pic: string;
+ vod_remarks?: string;
+ vod_play_url?: string;
+ vod_class?: string;
+ vod_year?: string;
+ vod_content?: string;
+ vod_douban_id?: number;
+ type_name?: string;
+}
+
+export async function searchFromApi(
+ apiSite: ApiSite,
+ query: string
+): Promise {
+ try {
+ const apiBaseUrl = apiSite.api;
+ const apiUrl =
+ apiBaseUrl + API_CONFIG.search.path + encodeURIComponent(query);
+ const apiName = apiSite.name;
+
+ // 添加超时处理
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 8000);
+
+ const response = await fetch(apiUrl, {
+ headers: API_CONFIG.search.headers,
+ signal: controller.signal,
+ });
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ return [];
+ }
+
+ const data = await response.json();
+ if (
+ !data ||
+ !data.list ||
+ !Array.isArray(data.list) ||
+ data.list.length === 0
+ ) {
+ return [];
+ }
+ // 处理第一页结果
+ const results = data.list.map((item: ApiSearchItem) => {
+ let episodes: string[] = [];
+
+ // 使用正则表达式从 vod_play_url 提取 m3u8 链接
+ if (item.vod_play_url) {
+ const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g;
+ // 先用 $$$ 分割
+ const vod_play_url_array = item.vod_play_url.split('$$$');
+ // 对每个分片做匹配,取匹配到最多的作为结果
+ vod_play_url_array.forEach((url: string) => {
+ const matches = url.match(m3u8Regex) || [];
+ if (matches.length > episodes.length) {
+ episodes = matches;
+ }
+ });
+ }
+
+ episodes = Array.from(new Set(episodes)).map((link: string) => {
+ link = link.substring(1); // 去掉开头的 $
+ const parenIndex = link.indexOf('(');
+ return parenIndex > 0 ? link.substring(0, parenIndex) : link;
+ });
+
+ return {
+ id: item.vod_id.toString(),
+ title: item.vod_name.trim().replace(/\s+/g, ' '),
+ poster: item.vod_pic,
+ episodes,
+ source: apiSite.key,
+ source_name: apiName,
+ class: item.vod_class,
+ year: item.vod_year
+ ? item.vod_year.match(/\d{4}/)?.[0] || ''
+ : 'unknown',
+ desc: cleanHtmlTags(item.vod_content || ''),
+ type_name: item.type_name,
+ douban_id: item.vod_douban_id,
+ };
+ });
+
+ const config = await getConfig();
+ const MAX_SEARCH_PAGES: number = config.SiteConfig.SearchDownstreamMaxPage;
+
+ // 获取总页数
+ const pageCount = data.pagecount || 1;
+ // 确定需要获取的额外页数
+ const pagesToFetch = Math.min(pageCount - 1, MAX_SEARCH_PAGES - 1);
+
+ // 如果有额外页数,获取更多页的结果
+ if (pagesToFetch > 0) {
+ const additionalPagePromises = [];
+
+ for (let page = 2; page <= pagesToFetch + 1; page++) {
+ const pageUrl =
+ apiBaseUrl +
+ API_CONFIG.search.pagePath
+ .replace('{query}', encodeURIComponent(query))
+ .replace('{page}', page.toString());
+
+ const pagePromise = (async () => {
+ try {
+ const pageController = new AbortController();
+ const pageTimeoutId = setTimeout(
+ () => pageController.abort(),
+ 8000
+ );
+
+ const pageResponse = await fetch(pageUrl, {
+ headers: API_CONFIG.search.headers,
+ signal: pageController.signal,
+ });
+
+ clearTimeout(pageTimeoutId);
+
+ if (!pageResponse.ok) return [];
+
+ const pageData = await pageResponse.json();
+
+ if (!pageData || !pageData.list || !Array.isArray(pageData.list))
+ return [];
+
+ return pageData.list.map((item: ApiSearchItem) => {
+ let episodes: string[] = [];
+
+ // 使用正则表达式从 vod_play_url 提取 m3u8 链接
+ if (item.vod_play_url) {
+ const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g;
+ episodes = item.vod_play_url.match(m3u8Regex) || [];
+ }
+
+ episodes = Array.from(new Set(episodes)).map((link: string) => {
+ link = link.substring(1); // 去掉开头的 $
+ const parenIndex = link.indexOf('(');
+ return parenIndex > 0 ? link.substring(0, parenIndex) : link;
+ });
+
+ return {
+ id: item.vod_id.toString(),
+ title: item.vod_name.trim().replace(/\s+/g, ' '),
+ poster: item.vod_pic,
+ episodes,
+ source: apiSite.key,
+ source_name: apiName,
+ class: item.vod_class,
+ year: item.vod_year
+ ? item.vod_year.match(/\d{4}/)?.[0] || ''
+ : 'unknown',
+ desc: cleanHtmlTags(item.vod_content || ''),
+ type_name: item.type_name,
+ douban_id: item.vod_douban_id,
+ };
+ });
+ } catch (error) {
+ return [];
+ }
+ })();
+
+ additionalPagePromises.push(pagePromise);
+ }
+
+ // 等待所有额外页的结果
+ const additionalResults = await Promise.all(additionalPagePromises);
+
+ // 合并所有页的结果
+ additionalResults.forEach((pageResults) => {
+ if (pageResults.length > 0) {
+ results.push(...pageResults);
+ }
+ });
+ }
+
+ return results;
+ } catch (error) {
+ return [];
+ }
+}
+
+// 匹配 m3u8 链接的正则
+const M3U8_PATTERN = /(https?:\/\/[^"'\s]+?\.m3u8)/g;
+
+export async function getDetailFromApi(
+ apiSite: ApiSite,
+ id: string
+): Promise {
+ if (apiSite.detail) {
+ return handleSpecialSourceDetail(id, apiSite);
+ }
+
+ const detailUrl = `${apiSite.api}${API_CONFIG.detail.path}${id}`;
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
+
+ const response = await fetch(detailUrl, {
+ headers: API_CONFIG.detail.headers,
+ signal: controller.signal,
+ });
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`详情请求失败: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ if (
+ !data ||
+ !data.list ||
+ !Array.isArray(data.list) ||
+ data.list.length === 0
+ ) {
+ throw new Error('获取到的详情内容无效');
+ }
+
+ const videoDetail = data.list[0];
+ let episodes: string[] = [];
+
+ // 处理播放源拆分
+ if (videoDetail.vod_play_url) {
+ const playSources = videoDetail.vod_play_url.split('$$$');
+ if (playSources.length > 0) {
+ const mainSource = playSources[0];
+ const episodeList = mainSource.split('#');
+ episodes = episodeList
+ .map((ep: string) => {
+ const parts = ep.split('$');
+ return parts.length > 1 ? parts[1] : '';
+ })
+ .filter(
+ (url: string) =>
+ url && (url.startsWith('http://') || url.startsWith('https://'))
+ );
+ }
+ }
+
+ // 如果播放源为空,则尝试从内容中解析 m3u8
+ if (episodes.length === 0 && videoDetail.vod_content) {
+ const matches = videoDetail.vod_content.match(M3U8_PATTERN) || [];
+ episodes = matches.map((link: string) => link.replace(/^\$/, ''));
+ }
+
+ return {
+ id: id.toString(),
+ title: videoDetail.vod_name,
+ poster: videoDetail.vod_pic,
+ episodes,
+ source: apiSite.key,
+ source_name: apiSite.name,
+ class: videoDetail.vod_class,
+ year: videoDetail.vod_year
+ ? videoDetail.vod_year.match(/\d{4}/)?.[0] || ''
+ : 'unknown',
+ desc: cleanHtmlTags(videoDetail.vod_content),
+ type_name: videoDetail.type_name,
+ douban_id: videoDetail.vod_douban_id,
+ };
+}
+
+async function handleSpecialSourceDetail(
+ id: string,
+ apiSite: ApiSite
+): Promise {
+ const detailUrl = `${apiSite.detail}/index.php/vod/detail/id/${id}.html`;
+
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
+
+ const response = await fetch(detailUrl, {
+ headers: API_CONFIG.detail.headers,
+ signal: controller.signal,
+ });
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`详情页请求失败: ${response.status}`);
+ }
+
+ const html = await response.text();
+ let matches: string[] = [];
+
+ if (apiSite.key === 'ffzy') {
+ const ffzyPattern =
+ /\$(https?:\/\/[^"'\s]+?\/\d{8}\/\d+_[a-f0-9]+\/index\.m3u8)/g;
+ matches = html.match(ffzyPattern) || [];
+ }
+
+ if (matches.length === 0) {
+ const generalPattern = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g;
+ matches = html.match(generalPattern) || [];
+ }
+
+ // 去重并清理链接前缀
+ matches = Array.from(new Set(matches)).map((link: string) => {
+ link = link.substring(1); // 去掉开头的 $
+ const parenIndex = link.indexOf('(');
+ return parenIndex > 0 ? link.substring(0, parenIndex) : link;
+ });
+
+ // 提取标题
+ const titleMatch = html.match(/]*>([^<]+)<\/h1>/);
+ const titleText = titleMatch ? titleMatch[1].trim() : '';
+
+ // 提取描述
+ const descMatch = html.match(
+ /]*class=["']sketch["'][^>]*>([\s\S]*?)<\/div>/
+ );
+ const descText = descMatch ? cleanHtmlTags(descMatch[1]) : '';
+
+ // 提取封面
+ const coverMatch = html.match(/(https?:\/\/[^"'\s]+?\.jpg)/g);
+ const coverUrl = coverMatch ? coverMatch[0].trim() : '';
+
+ // 提取年份
+ const yearMatch = html.match(/>(\d{4}));
+ const yearText = yearMatch ? yearMatch[1] : 'unknown';
+
+ return {
+ id,
+ title: titleText,
+ poster: coverUrl,
+ episodes: matches,
+ source: apiSite.key,
+ source_name: apiSite.name,
+ class: '',
+ year: yearText,
+ desc: descText,
+ type_name: '',
+ douban_id: 0,
+ };
+}
diff --git a/src/lib/fetchVideoDetail.ts b/src/lib/fetchVideoDetail.ts
new file mode 100644
index 0000000..ca61bd9
--- /dev/null
+++ b/src/lib/fetchVideoDetail.ts
@@ -0,0 +1,51 @@
+import { getAvailableApiSites } from '@/lib/config';
+import { SearchResult } from '@/lib/types';
+
+import { getDetailFromApi, searchFromApi } from './downstream';
+
+interface FetchVideoDetailOptions {
+ source: string;
+ id: string;
+ fallbackTitle?: string;
+}
+
+/**
+ * 根据 source 与 id 获取视频详情。
+ * 1. 若传入 fallbackTitle,则先调用 /api/search 搜索精确匹配。
+ * 2. 若搜索未命中或未提供 fallbackTitle,则直接调用 /api/detail。
+ */
+export async function fetchVideoDetail({
+ source,
+ id,
+ fallbackTitle = '',
+}: FetchVideoDetailOptions): Promise
{
+ // 优先通过搜索接口查找精确匹配
+ const apiSites = await getAvailableApiSites();
+ const apiSite = apiSites.find((site) => site.key === source);
+ if (!apiSite) {
+ throw new Error('无效的API来源');
+ }
+ if (fallbackTitle) {
+ try {
+ const searchData = await searchFromApi(apiSite, fallbackTitle.trim());
+ const exactMatch = searchData.find(
+ (item: SearchResult) =>
+ item.source.toString() === source.toString() &&
+ item.id.toString() === id.toString()
+ );
+ if (exactMatch) {
+ return exactMatch;
+ }
+ } catch (error) {
+ // do nothing
+ }
+ }
+
+ // 调用 /api/detail 接口
+ const detail = await getDetailFromApi(apiSite, id);
+ if (!detail) {
+ throw new Error('获取视频详情失败');
+ }
+
+ return detail;
+}
diff --git a/src/lib/redis.db.ts b/src/lib/redis.db.ts
new file mode 100644
index 0000000..1b9d024
--- /dev/null
+++ b/src/lib/redis.db.ts
@@ -0,0 +1,355 @@
+/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
+
+import { createClient, RedisClientType } from 'redis';
+
+import { AdminConfig } from './admin.types';
+import { Favorite, IStorage, PlayRecord } from './types';
+
+// 搜索历史最大条数
+const SEARCH_HISTORY_LIMIT = 20;
+
+// 数据类型转换辅助函数
+function ensureString(value: any): string {
+ return String(value);
+}
+
+function ensureStringArray(value: any[]): string[] {
+ return value.map((item) => String(item));
+}
+
+// 添加Redis操作重试包装器
+async function withRetry(
+ operation: () => Promise,
+ maxRetries = 3
+): Promise {
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await operation();
+ } catch (err: any) {
+ const isLastAttempt = i === maxRetries - 1;
+ const isConnectionError =
+ err.message?.includes('Connection') ||
+ err.message?.includes('ECONNREFUSED') ||
+ err.message?.includes('ENOTFOUND') ||
+ err.code === 'ECONNRESET' ||
+ err.code === 'EPIPE';
+
+ if (isConnectionError && !isLastAttempt) {
+ console.log(
+ `Redis operation failed, retrying... (${i + 1}/${maxRetries})`
+ );
+ console.error('Error:', err.message);
+
+ // 等待一段时间后重试
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
+
+ // 尝试重新连接
+ try {
+ const client = getRedisClient();
+ if (!client.isOpen) {
+ await client.connect();
+ }
+ } catch (reconnectErr) {
+ console.error('Failed to reconnect:', reconnectErr);
+ }
+
+ continue;
+ }
+
+ throw err;
+ }
+ }
+
+ throw new Error('Max retries exceeded');
+}
+
+export class RedisStorage implements IStorage {
+ private client: RedisClientType;
+
+ constructor() {
+ this.client = getRedisClient();
+ }
+
+ // ---------- 播放记录 ----------
+ private prKey(user: string, key: string) {
+ return `u:${user}:pr:${key}`; // u:username:pr:source+id
+ }
+
+ async getPlayRecord(
+ userName: string,
+ key: string
+ ): Promise {
+ const val = await withRetry(() =>
+ this.client.get(this.prKey(userName, key))
+ );
+ return val ? (JSON.parse(val) as PlayRecord) : null;
+ }
+
+ async setPlayRecord(
+ userName: string,
+ key: string,
+ record: PlayRecord
+ ): Promise {
+ await withRetry(() =>
+ this.client.set(this.prKey(userName, key), JSON.stringify(record))
+ );
+ }
+
+ async getAllPlayRecords(
+ userName: string
+ ): Promise> {
+ const pattern = `u:${userName}:pr:*`;
+ const keys: string[] = await withRetry(() => this.client.keys(pattern));
+ if (keys.length === 0) return {};
+ const values = await withRetry(() => this.client.mGet(keys));
+ const result: Record = {};
+ keys.forEach((fullKey: string, idx: number) => {
+ const raw = values[idx];
+ if (raw) {
+ const rec = JSON.parse(raw) as PlayRecord;
+ // 截取 source+id 部分
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:pr:`, ''));
+ result[keyPart] = rec;
+ }
+ });
+ return result;
+ }
+
+ async deletePlayRecord(userName: string, key: string): Promise {
+ await withRetry(() => this.client.del(this.prKey(userName, key)));
+ }
+
+ // ---------- 收藏 ----------
+ private favKey(user: string, key: string) {
+ return `u:${user}:fav:${key}`;
+ }
+
+ async getFavorite(userName: string, key: string): Promise {
+ const val = await withRetry(() =>
+ this.client.get(this.favKey(userName, key))
+ );
+ return val ? (JSON.parse(val) as Favorite) : null;
+ }
+
+ async setFavorite(
+ userName: string,
+ key: string,
+ favorite: Favorite
+ ): Promise {
+ await withRetry(() =>
+ this.client.set(this.favKey(userName, key), JSON.stringify(favorite))
+ );
+ }
+
+ async getAllFavorites(userName: string): Promise> {
+ const pattern = `u:${userName}:fav:*`;
+ const keys: string[] = await withRetry(() => this.client.keys(pattern));
+ if (keys.length === 0) return {};
+ const values = await withRetry(() => this.client.mGet(keys));
+ const result: Record = {};
+ keys.forEach((fullKey: string, idx: number) => {
+ const raw = values[idx];
+ if (raw) {
+ const fav = JSON.parse(raw) as Favorite;
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:fav:`, ''));
+ result[keyPart] = fav;
+ }
+ });
+ return result;
+ }
+
+ async deleteFavorite(userName: string, key: string): Promise {
+ await withRetry(() => this.client.del(this.favKey(userName, key)));
+ }
+
+ // ---------- 用户注册 / 登录 ----------
+ private userPwdKey(user: string) {
+ return `u:${user}:pwd`;
+ }
+
+ async registerUser(userName: string, password: string): Promise {
+ // 简单存储明文密码,生产环境应加密
+ await withRetry(() => this.client.set(this.userPwdKey(userName), password));
+ }
+
+ async verifyUser(userName: string, password: string): Promise {
+ const stored = await withRetry(() =>
+ this.client.get(this.userPwdKey(userName))
+ );
+ if (stored === null) return false;
+ // 确保比较时都是字符串类型
+ return ensureString(stored) === password;
+ }
+
+ // 检查用户是否存在
+ async checkUserExist(userName: string): Promise {
+ // 使用 EXISTS 判断 key 是否存在
+ const exists = await withRetry(() =>
+ this.client.exists(this.userPwdKey(userName))
+ );
+ return exists === 1;
+ }
+
+ // 修改用户密码
+ async changePassword(userName: string, newPassword: string): Promise {
+ // 简单存储明文密码,生产环境应加密
+ await withRetry(() =>
+ this.client.set(this.userPwdKey(userName), newPassword)
+ );
+ }
+
+ // 删除用户及其所有数据
+ async deleteUser(userName: string): Promise {
+ // 删除用户密码
+ await withRetry(() => this.client.del(this.userPwdKey(userName)));
+
+ // 删除搜索历史
+ await withRetry(() => this.client.del(this.shKey(userName)));
+
+ // 删除播放记录
+ const playRecordPattern = `u:${userName}:pr:*`;
+ const playRecordKeys = await withRetry(() =>
+ this.client.keys(playRecordPattern)
+ );
+ if (playRecordKeys.length > 0) {
+ await withRetry(() => this.client.del(playRecordKeys));
+ }
+
+ // 删除收藏夹
+ const favoritePattern = `u:${userName}:fav:*`;
+ const favoriteKeys = await withRetry(() =>
+ this.client.keys(favoritePattern)
+ );
+ if (favoriteKeys.length > 0) {
+ await withRetry(() => this.client.del(favoriteKeys));
+ }
+ }
+
+ // ---------- 搜索历史 ----------
+ private shKey(user: string) {
+ return `u:${user}:sh`; // u:username:sh
+ }
+
+ async getSearchHistory(userName: string): Promise {
+ const result = await withRetry(() =>
+ this.client.lRange(this.shKey(userName), 0, -1)
+ );
+ // 确保返回的都是字符串类型
+ return ensureStringArray(result as any[]);
+ }
+
+ async addSearchHistory(userName: string, keyword: string): Promise {
+ const key = this.shKey(userName);
+ // 先去重
+ await withRetry(() => this.client.lRem(key, 0, ensureString(keyword)));
+ // 插入到最前
+ await withRetry(() => this.client.lPush(key, ensureString(keyword)));
+ // 限制最大长度
+ await withRetry(() => this.client.lTrim(key, 0, SEARCH_HISTORY_LIMIT - 1));
+ }
+
+ async deleteSearchHistory(userName: string, keyword?: string): Promise {
+ const key = this.shKey(userName);
+ if (keyword) {
+ await withRetry(() => this.client.lRem(key, 0, ensureString(keyword)));
+ } else {
+ await withRetry(() => this.client.del(key));
+ }
+ }
+
+ // ---------- 获取全部用户 ----------
+ async getAllUsers(): Promise {
+ const keys = await withRetry(() => this.client.keys('u:*:pwd'));
+ return keys
+ .map((k) => {
+ const match = k.match(/^u:(.+?):pwd$/);
+ return match ? ensureString(match[1]) : undefined;
+ })
+ .filter((u): u is string => typeof u === 'string');
+ }
+
+ // ---------- 管理员配置 ----------
+ private adminConfigKey() {
+ return 'admin:config';
+ }
+
+ async getAdminConfig(): Promise {
+ const val = await withRetry(() => this.client.get(this.adminConfigKey()));
+ return val ? (JSON.parse(val) as AdminConfig) : null;
+ }
+
+ async setAdminConfig(config: AdminConfig): Promise {
+ await withRetry(() =>
+ this.client.set(this.adminConfigKey(), JSON.stringify(config))
+ );
+ }
+}
+
+// 单例 Redis 客户端
+function getRedisClient(): RedisClientType {
+ const globalKey = Symbol.for('__MOONTV_REDIS_CLIENT__');
+ let client: RedisClientType | undefined = (global as any)[globalKey];
+
+ if (!client) {
+ const url = process.env.REDIS_URL;
+ if (!url) {
+ throw new Error('REDIS_URL env variable not set');
+ }
+
+ // 创建客户端,配置重连策略
+ client = createClient({
+ url,
+ socket: {
+ // 重连策略:指数退避,最大30秒
+ reconnectStrategy: (retries: number) => {
+ console.log(`Redis reconnection attempt ${retries + 1}`);
+ if (retries > 10) {
+ console.error('Redis max reconnection attempts exceeded');
+ return false; // 停止重连
+ }
+ return Math.min(1000 * Math.pow(2, retries), 30000); // 指数退避,最大30秒
+ },
+ connectTimeout: 10000, // 10秒连接超时
+ // 设置no delay,减少延迟
+ noDelay: true,
+ },
+ // 添加其他配置
+ pingInterval: 30000, // 30秒ping一次,保持连接活跃
+ });
+
+ // 添加错误事件监听
+ client.on('error', (err) => {
+ console.error('Redis client error:', err);
+ });
+
+ client.on('connect', () => {
+ console.log('Redis connected');
+ });
+
+ client.on('reconnecting', () => {
+ console.log('Redis reconnecting...');
+ });
+
+ client.on('ready', () => {
+ console.log('Redis ready');
+ });
+
+ // 初始连接,带重试机制
+ const connectWithRetry = async () => {
+ try {
+ await client!.connect();
+ console.log('Redis connected successfully');
+ } catch (err) {
+ console.error('Redis initial connection failed:', err);
+ console.log('Will retry in 5 seconds...');
+ setTimeout(connectWithRetry, 5000);
+ }
+ };
+
+ connectWithRetry();
+
+ (global as any)[globalKey] = client;
+ }
+
+ return client;
+}
diff --git a/src/lib/types.ts b/src/lib/types.ts
new file mode 100644
index 0000000..a7e1f48
--- /dev/null
+++ b/src/lib/types.ts
@@ -0,0 +1,97 @@
+import { AdminConfig } from './admin.types';
+
+// 播放记录数据结构
+export interface PlayRecord {
+ title: string;
+ source_name: string;
+ cover: string;
+ year: string;
+ index: number; // 第几集
+ total_episodes: number; // 总集数
+ play_time: number; // 播放进度(秒)
+ total_time: number; // 总进度(秒)
+ save_time: number; // 记录保存时间(时间戳)
+ search_title: string; // 搜索时使用的标题
+}
+
+// 收藏数据结构
+export interface Favorite {
+ source_name: string;
+ total_episodes: number; // 总集数
+ title: string;
+ year: string;
+ cover: string;
+ save_time: number; // 记录保存时间(时间戳)
+ search_title: string; // 搜索时使用的标题
+}
+
+// 存储接口
+export interface IStorage {
+ // 播放记录相关
+ getPlayRecord(userName: string, key: string): Promise;
+ setPlayRecord(
+ userName: string,
+ key: string,
+ record: PlayRecord
+ ): Promise;
+ getAllPlayRecords(userName: string): Promise<{ [key: string]: PlayRecord }>;
+ deletePlayRecord(userName: string, key: string): Promise;
+
+ // 收藏相关
+ getFavorite(userName: string, key: string): Promise;
+ setFavorite(userName: string, key: string, favorite: Favorite): Promise;
+ getAllFavorites(userName: string): Promise<{ [key: string]: Favorite }>;
+ deleteFavorite(userName: string, key: string): Promise;
+
+ // 用户相关
+ registerUser(userName: string, password: string): Promise;
+ verifyUser(userName: string, password: string): Promise;
+ // 检查用户是否存在(无需密码)
+ checkUserExist(userName: string): Promise;
+ // 修改用户密码
+ changePassword(userName: string, newPassword: string): Promise;
+ // 删除用户(包括密码、搜索历史、播放记录、收藏夹)
+ deleteUser(userName: string): Promise;
+
+ // 搜索历史相关
+ getSearchHistory(userName: string): Promise;
+ addSearchHistory(userName: string, keyword: string): Promise;
+ deleteSearchHistory(userName: string, keyword?: string): Promise;
+
+ // 用户列表
+ getAllUsers(): Promise;
+
+ // 管理员配置相关
+ getAdminConfig(): Promise;
+ setAdminConfig(config: AdminConfig): Promise;
+}
+
+// 搜索结果数据结构
+export interface SearchResult {
+ id: string;
+ title: string;
+ poster: string;
+ episodes: string[];
+ source: string;
+ source_name: string;
+ class?: string;
+ year: string;
+ desc?: string;
+ type_name?: string;
+ douban_id?: number;
+}
+
+// 豆瓣数据结构
+export interface DoubanItem {
+ id: string;
+ title: string;
+ poster: string;
+ rate: string;
+ year: string;
+}
+
+export interface DoubanResult {
+ code: number;
+ message: string;
+ list: DoubanItem[];
+}
diff --git a/src/lib/upstash.db.ts b/src/lib/upstash.db.ts
new file mode 100644
index 0000000..3832bd3
--- /dev/null
+++ b/src/lib/upstash.db.ts
@@ -0,0 +1,305 @@
+/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
+
+import { Redis } from '@upstash/redis';
+
+import { AdminConfig } from './admin.types';
+import { Favorite, IStorage, PlayRecord } from './types';
+
+// 搜索历史最大条数
+const SEARCH_HISTORY_LIMIT = 20;
+
+// 数据类型转换辅助函数
+function ensureString(value: any): string {
+ return String(value);
+}
+
+function ensureStringArray(value: any[]): string[] {
+ return value.map((item) => String(item));
+}
+
+// 添加Upstash Redis操作重试包装器
+async function withRetry(
+ operation: () => Promise,
+ maxRetries = 3
+): Promise {
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await operation();
+ } catch (err: any) {
+ const isLastAttempt = i === maxRetries - 1;
+ const isConnectionError =
+ err.message?.includes('Connection') ||
+ err.message?.includes('ECONNREFUSED') ||
+ err.message?.includes('ENOTFOUND') ||
+ err.code === 'ECONNRESET' ||
+ err.code === 'EPIPE' ||
+ err.name === 'UpstashError';
+
+ if (isConnectionError && !isLastAttempt) {
+ console.log(
+ `Upstash Redis operation failed, retrying... (${i + 1}/${maxRetries})`
+ );
+ console.error('Error:', err.message);
+
+ // 等待一段时间后重试
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
+ continue;
+ }
+
+ throw err;
+ }
+ }
+
+ throw new Error('Max retries exceeded');
+}
+
+export class UpstashRedisStorage implements IStorage {
+ private client: Redis;
+
+ constructor() {
+ this.client = getUpstashRedisClient();
+ }
+
+ // ---------- 播放记录 ----------
+ private prKey(user: string, key: string) {
+ return `u:${user}:pr:${key}`; // u:username:pr:source+id
+ }
+
+ async getPlayRecord(
+ userName: string,
+ key: string
+ ): Promise {
+ const val = await withRetry(() =>
+ this.client.get(this.prKey(userName, key))
+ );
+ return val ? (val as PlayRecord) : null;
+ }
+
+ async setPlayRecord(
+ userName: string,
+ key: string,
+ record: PlayRecord
+ ): Promise {
+ await withRetry(() => this.client.set(this.prKey(userName, key), record));
+ }
+
+ async getAllPlayRecords(
+ userName: string
+ ): Promise> {
+ const pattern = `u:${userName}:pr:*`;
+ const keys: string[] = await withRetry(() => this.client.keys(pattern));
+ if (keys.length === 0) return {};
+
+ const result: Record = {};
+ for (const fullKey of keys) {
+ const value = await withRetry(() => this.client.get(fullKey));
+ if (value) {
+ // 截取 source+id 部分
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:pr:`, ''));
+ result[keyPart] = value as PlayRecord;
+ }
+ }
+ return result;
+ }
+
+ async deletePlayRecord(userName: string, key: string): Promise {
+ await withRetry(() => this.client.del(this.prKey(userName, key)));
+ }
+
+ // ---------- 收藏 ----------
+ private favKey(user: string, key: string) {
+ return `u:${user}:fav:${key}`;
+ }
+
+ async getFavorite(userName: string, key: string): Promise {
+ const val = await withRetry(() =>
+ this.client.get(this.favKey(userName, key))
+ );
+ return val ? (val as Favorite) : null;
+ }
+
+ async setFavorite(
+ userName: string,
+ key: string,
+ favorite: Favorite
+ ): Promise {
+ await withRetry(() =>
+ this.client.set(this.favKey(userName, key), favorite)
+ );
+ }
+
+ async getAllFavorites(userName: string): Promise> {
+ const pattern = `u:${userName}:fav:*`;
+ const keys: string[] = await withRetry(() => this.client.keys(pattern));
+ if (keys.length === 0) return {};
+
+ const result: Record = {};
+ for (const fullKey of keys) {
+ const value = await withRetry(() => this.client.get(fullKey));
+ if (value) {
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:fav:`, ''));
+ result[keyPart] = value as Favorite;
+ }
+ }
+ return result;
+ }
+
+ async deleteFavorite(userName: string, key: string): Promise {
+ await withRetry(() => this.client.del(this.favKey(userName, key)));
+ }
+
+ // ---------- 用户注册 / 登录 ----------
+ private userPwdKey(user: string) {
+ return `u:${user}:pwd`;
+ }
+
+ async registerUser(userName: string, password: string): Promise {
+ // 简单存储明文密码,生产环境应加密
+ await withRetry(() => this.client.set(this.userPwdKey(userName), password));
+ }
+
+ async verifyUser(userName: string, password: string): Promise {
+ const stored = await withRetry(() =>
+ this.client.get(this.userPwdKey(userName))
+ );
+ if (stored === null) return false;
+ // 确保比较时都是字符串类型
+ return ensureString(stored) === password;
+ }
+
+ // 检查用户是否存在
+ async checkUserExist(userName: string): Promise {
+ // 使用 EXISTS 判断 key 是否存在
+ const exists = await withRetry(() =>
+ this.client.exists(this.userPwdKey(userName))
+ );
+ return exists === 1;
+ }
+
+ // 修改用户密码
+ async changePassword(userName: string, newPassword: string): Promise {
+ // 简单存储明文密码,生产环境应加密
+ await withRetry(() =>
+ this.client.set(this.userPwdKey(userName), newPassword)
+ );
+ }
+
+ // 删除用户及其所有数据
+ async deleteUser(userName: string): Promise {
+ // 删除用户密码
+ await withRetry(() => this.client.del(this.userPwdKey(userName)));
+
+ // 删除搜索历史
+ await withRetry(() => this.client.del(this.shKey(userName)));
+
+ // 删除播放记录
+ const playRecordPattern = `u:${userName}:pr:*`;
+ const playRecordKeys = await withRetry(() =>
+ this.client.keys(playRecordPattern)
+ );
+ if (playRecordKeys.length > 0) {
+ await withRetry(() => this.client.del(...playRecordKeys));
+ }
+
+ // 删除收藏夹
+ const favoritePattern = `u:${userName}:fav:*`;
+ const favoriteKeys = await withRetry(() =>
+ this.client.keys(favoritePattern)
+ );
+ if (favoriteKeys.length > 0) {
+ await withRetry(() => this.client.del(...favoriteKeys));
+ }
+ }
+
+ // ---------- 搜索历史 ----------
+ private shKey(user: string) {
+ return `u:${user}:sh`; // u:username:sh
+ }
+
+ async getSearchHistory(userName: string): Promise {
+ const result = await withRetry(() =>
+ this.client.lrange(this.shKey(userName), 0, -1)
+ );
+ // 确保返回的都是字符串类型
+ return ensureStringArray(result as any[]);
+ }
+
+ async addSearchHistory(userName: string, keyword: string): Promise {
+ const key = this.shKey(userName);
+ // 先去重
+ await withRetry(() => this.client.lrem(key, 0, ensureString(keyword)));
+ // 插入到最前
+ await withRetry(() => this.client.lpush(key, ensureString(keyword)));
+ // 限制最大长度
+ await withRetry(() => this.client.ltrim(key, 0, SEARCH_HISTORY_LIMIT - 1));
+ }
+
+ async deleteSearchHistory(userName: string, keyword?: string): Promise {
+ const key = this.shKey(userName);
+ if (keyword) {
+ await withRetry(() => this.client.lrem(key, 0, ensureString(keyword)));
+ } else {
+ await withRetry(() => this.client.del(key));
+ }
+ }
+
+ // ---------- 获取全部用户 ----------
+ async getAllUsers(): Promise {
+ const keys = await withRetry(() => this.client.keys('u:*:pwd'));
+ return keys
+ .map((k) => {
+ const match = k.match(/^u:(.+?):pwd$/);
+ return match ? ensureString(match[1]) : undefined;
+ })
+ .filter((u): u is string => typeof u === 'string');
+ }
+
+ // ---------- 管理员配置 ----------
+ private adminConfigKey() {
+ return 'admin:config';
+ }
+
+ async getAdminConfig(): Promise {
+ const val = await withRetry(() => this.client.get(this.adminConfigKey()));
+ return val ? (val as AdminConfig) : null;
+ }
+
+ async setAdminConfig(config: AdminConfig): Promise {
+ await withRetry(() => this.client.set(this.adminConfigKey(), config));
+ }
+}
+
+// 单例 Upstash Redis 客户端
+function getUpstashRedisClient(): Redis {
+ const globalKey = Symbol.for('__MOONTV_UPSTASH_REDIS_CLIENT__');
+ let client: Redis | undefined = (global as any)[globalKey];
+
+ if (!client) {
+ const upstashUrl = process.env.UPSTASH_URL;
+ const upstashToken = process.env.UPSTASH_TOKEN;
+
+ if (!upstashUrl || !upstashToken) {
+ throw new Error(
+ 'UPSTASH_URL and UPSTASH_TOKEN env variables must be set'
+ );
+ }
+
+ // 创建 Upstash Redis 客户端
+ client = new Redis({
+ url: upstashUrl,
+ token: upstashToken,
+ // 可选配置
+ retry: {
+ retries: 3,
+ backoff: (retryCount: number) =>
+ Math.min(1000 * Math.pow(2, retryCount), 30000),
+ },
+ });
+
+ console.log('Upstash Redis client created successfully');
+
+ (global as any)[globalKey] = client;
+ }
+
+ return client;
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..cc5152c
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,247 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+
+import Hls from 'hls.js';
+
+/**
+ * 获取图片代理 URL 设置
+ */
+export function getImageProxyUrl(): string | null {
+ if (typeof window === 'undefined') return null;
+
+ // 本地未开启图片代理,则不使用代理
+ const enableImageProxy = localStorage.getItem('enableImageProxy');
+ if (enableImageProxy !== null) {
+ if (!JSON.parse(enableImageProxy) as boolean) {
+ return null;
+ }
+ }
+
+ const localImageProxy = localStorage.getItem('imageProxyUrl');
+ if (localImageProxy != null) {
+ return localImageProxy.trim() ? localImageProxy.trim() : null;
+ }
+
+ // 如果未设置,则使用全局对象
+ const serverImageProxy = (window as any).RUNTIME_CONFIG?.IMAGE_PROXY;
+ return serverImageProxy && serverImageProxy.trim()
+ ? serverImageProxy.trim()
+ : null;
+}
+
+/**
+ * 处理图片 URL,如果设置了图片代理则使用代理
+ */
+export function processImageUrl(originalUrl: string): string {
+ if (!originalUrl) return originalUrl;
+
+ const proxyUrl = getImageProxyUrl();
+ if (!proxyUrl) return originalUrl;
+
+ return `${proxyUrl}${encodeURIComponent(originalUrl)}`;
+}
+
+/**
+ * 获取豆瓣代理 URL 设置
+ */
+export function getDoubanProxyUrl(): string | null {
+ if (typeof window === 'undefined') return null;
+
+ // 本地未开启豆瓣代理,则不使用代理
+ const enableDoubanProxy = localStorage.getItem('enableDoubanProxy');
+ if (enableDoubanProxy !== null) {
+ if (!JSON.parse(enableDoubanProxy) as boolean) {
+ return null;
+ }
+ }
+
+ const localDoubanProxy = localStorage.getItem('doubanProxyUrl');
+ if (localDoubanProxy != null) {
+ return localDoubanProxy.trim() ? localDoubanProxy.trim() : null;
+ }
+
+ // 如果未设置,则使用全局对象
+ const serverDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY;
+ return serverDoubanProxy && serverDoubanProxy.trim()
+ ? serverDoubanProxy.trim()
+ : null;
+}
+
+/**
+ * 处理豆瓣 URL,如果设置了豆瓣代理则使用代理
+ */
+export function processDoubanUrl(originalUrl: string): string {
+ if (!originalUrl) return originalUrl;
+
+ const proxyUrl = getDoubanProxyUrl();
+ if (!proxyUrl) return originalUrl;
+
+ return `${proxyUrl}${encodeURIComponent(originalUrl)}`;
+}
+
+export function cleanHtmlTags(text: string): string {
+ if (!text) return '';
+ return text
+ .replace(/<[^>]+>/g, '\n') // 将 HTML 标签替换为换行
+ .replace(/\n+/g, '\n') // 将多个连续换行合并为一个
+ .replace(/[ \t]+/g, ' ') // 将多个连续空格和制表符合并为一个空格,但保留换行符
+ .replace(/^\n+|\n+$/g, '') // 去掉首尾换行
+ .replace(/ /g, ' ') // 将 替换为空格
+ .trim(); // 去掉首尾空格
+}
+
+/**
+ * 从m3u8地址获取视频质量等级和网络信息
+ * @param m3u8Url m3u8播放列表的URL
+ * @returns Promise<{quality: string, loadSpeed: string, pingTime: number}> 视频质量等级和网络信息
+ */
+export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{
+ quality: string; // 如720p、1080p等
+ loadSpeed: string; // 自动转换为KB/s或MB/s
+ pingTime: number; // 网络延迟(毫秒)
+}> {
+ try {
+ // 直接使用m3u8 URL作为视频源,避免CORS问题
+ return new Promise((resolve, reject) => {
+ const video = document.createElement('video');
+ video.muted = true;
+ video.preload = 'metadata';
+
+ // 测量网络延迟(ping时间) - 使用m3u8 URL而不是ts文件
+ const pingStart = performance.now();
+ let pingTime = 0;
+
+ // 测量ping时间(使用m3u8 URL)
+ fetch(m3u8Url, { method: 'HEAD', mode: 'no-cors' })
+ .then(() => {
+ pingTime = performance.now() - pingStart;
+ })
+ .catch(() => {
+ pingTime = performance.now() - pingStart; // 记录到失败为止的时间
+ });
+
+ // 固定使用hls.js加载
+ const hls = new Hls();
+
+ // 设置超时处理
+ const timeout = setTimeout(() => {
+ hls.destroy();
+ video.remove();
+ reject(new Error('Timeout loading video metadata'));
+ }, 4000);
+
+ video.onerror = () => {
+ clearTimeout(timeout);
+ hls.destroy();
+ video.remove();
+ reject(new Error('Failed to load video metadata'));
+ };
+
+ let actualLoadSpeed = '未知';
+ let hasSpeedCalculated = false;
+ let hasMetadataLoaded = false;
+
+ let fragmentStartTime = 0;
+
+ // 检查是否可以返回结果
+ const checkAndResolve = () => {
+ if (
+ hasMetadataLoaded &&
+ (hasSpeedCalculated || actualLoadSpeed !== '未知')
+ ) {
+ clearTimeout(timeout);
+ const width = video.videoWidth;
+ if (width && width > 0) {
+ hls.destroy();
+ video.remove();
+
+ // 根据视频宽度判断视频质量等级,使用经典分辨率的宽度作为分割点
+ const quality =
+ width >= 3840
+ ? '4K' // 4K: 3840x2160
+ : width >= 2560
+ ? '2K' // 2K: 2560x1440
+ : width >= 1920
+ ? '1080p' // 1080p: 1920x1080
+ : width >= 1280
+ ? '720p' // 720p: 1280x720
+ : width >= 854
+ ? '480p'
+ : 'SD'; // 480p: 854x480
+
+ resolve({
+ quality,
+ loadSpeed: actualLoadSpeed,
+ pingTime: Math.round(pingTime),
+ });
+ } else {
+ // webkit 无法获取尺寸,直接返回
+ resolve({
+ quality: '未知',
+ loadSpeed: actualLoadSpeed,
+ pingTime: Math.round(pingTime),
+ });
+ }
+ }
+ };
+
+ // 监听片段加载开始
+ hls.on(Hls.Events.FRAG_LOADING, () => {
+ fragmentStartTime = performance.now();
+ });
+
+ // 监听片段加载完成,只需首个分片即可计算速度
+ hls.on(Hls.Events.FRAG_LOADED, (event: any, data: any) => {
+ if (
+ fragmentStartTime > 0 &&
+ data &&
+ data.payload &&
+ !hasSpeedCalculated
+ ) {
+ const loadTime = performance.now() - fragmentStartTime;
+ const size = data.payload.byteLength || 0;
+
+ if (loadTime > 0 && size > 0) {
+ const speedKBps = size / 1024 / (loadTime / 1000);
+
+ // 立即计算速度,无需等待更多分片
+ const avgSpeedKBps = speedKBps;
+
+ if (avgSpeedKBps >= 1024) {
+ actualLoadSpeed = `${(avgSpeedKBps / 1024).toFixed(1)} MB/s`;
+ } else {
+ actualLoadSpeed = `${avgSpeedKBps.toFixed(1)} KB/s`;
+ }
+ hasSpeedCalculated = true;
+ checkAndResolve(); // 尝试返回结果
+ }
+ }
+ });
+
+ hls.loadSource(m3u8Url);
+ hls.attachMedia(video);
+
+ // 监听hls.js错误
+ hls.on(Hls.Events.ERROR, (event: any, data: any) => {
+ console.error('HLS错误:', data);
+ if (data.fatal) {
+ clearTimeout(timeout);
+ hls.destroy();
+ video.remove();
+ reject(new Error(`HLS播放失败: ${data.type}`));
+ }
+ });
+
+ // 监听视频元数据加载完成
+ video.onloadedmetadata = () => {
+ hasMetadataLoaded = true;
+ checkAndResolve(); // 尝试返回结果
+ };
+ });
+ } catch (error) {
+ throw new Error(
+ `Error getting video resolution: ${
+ error instanceof Error ? error.message : String(error)
+ }`
+ );
+ }
+}
diff --git a/src/lib/version.ts b/src/lib/version.ts
new file mode 100644
index 0000000..687eba4
--- /dev/null
+++ b/src/lib/version.ts
@@ -0,0 +1,97 @@
+/* eslint-disable no-console */
+
+'use client';
+
+const CURRENT_VERSION = '20250928125318';
+
+// 版本检查结果枚举
+export enum UpdateStatus {
+ HAS_UPDATE = 'has_update', // 有新版本
+ NO_UPDATE = 'no_update', // 无新版本
+ FETCH_FAILED = 'fetch_failed', // 获取失败
+}
+
+// 远程版本检查URL配置
+const VERSION_CHECK_URLS = [
+ 'https://ghfast.top/raw.githubusercontent.com/senshinya/MoonTV/main/VERSION.txt',
+ 'https://raw.githubusercontent.com/senshinya/MoonTV/main/VERSION.txt',
+];
+
+/**
+ * 检查是否有新版本可用
+ * @returns Promise