From f8b47bdf5aa19590f9b3f8cdb4a5bf3d14567542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Wed, 10 Sep 2025 18:22:25 +0800 Subject: [PATCH] refactor: rename web-native to web and complete architecture documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed /apps/web-native to /apps/web for cleaner project structure - Updated all documentation references to use new path - Completed architecture documentation review and standardization: - Updated all 5 architecture files to 2025 version - Ensured consistent frontend-backend separation strategy - Aligned multi-platform API design across all documents 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 7 +- TASKS.md | 21 +- apps/web-native/assets/css/components.css | 723 -- apps/web-native/assets/css/layouts.css | 628 - apps/web-native/assets/css/main.css | 476 - apps/web-native/assets/js/app.js | 528 - apps/web-native/assets/js/utils.js | 663 - apps/web-native/data/mockData.js | 520 - apps/web-native/index.html | 193 - apps/web/.eslintrc.js | 51 - apps/web/.prettierrc | 14 - apps/web/auto-imports.d.ts | 209 - apps/web/components.d.ts | 31 - apps/web/dev-dist/registerSW.js | 1 - apps/web/index.html | 106 +- apps/web/package-lock.json | 10804 +--------------- apps/web/package.json | 74 +- apps/web/public/debug.html | 68 - apps/web/public/favicon.svg | 8 - apps/web/public/hybrid-approach.html | 136 - apps/web/public/logo.svg | 8 - apps/web/public/test.html | 14 - apps/web/public/vocabulary-native.html | 360 - apps/web/src/App.vue | 34 - apps/web/src/assets/styles/main.scss | 328 - .../src/assets/styles/quasar-variables.sass | 56 - apps/web/src/assets/styles/variables.scss | 173 - apps/web/src/components/BaseComponent.js | 101 + apps/web/src/components/PWAInstallPrompt.vue | 417 - apps/web/src/components/base/BaseButton.vue | 109 - apps/web/src/components/base/BaseCard.vue | 60 - apps/web/src/components/base/BaseInput.vue | 155 - apps/web/src/components/base/BaseModal.vue | 217 - .../components/business/VocabularyCard.vue | 292 - .../src/components/dashboard/ErrorHeatmap.vue | 515 - .../web/src/components/dashboard/StatCard.vue | 223 - apps/web/src/components/ui/Icon.vue | 197 - apps/web/src/components/ui/ModalContainer.vue | 127 - apps/web/src/components/ui/ToastContainer.vue | 219 - apps/web/src/composables/useAudio.ts | 270 - .../src/composables/useBrowserBookmarks.ts | 174 - apps/web/src/composables/useKeyboard.ts | 280 - .../src/composables/useKeyboardShortcuts.ts | 194 - .../src/composables/useMultiTabLearning.ts | 413 - apps/web/src/layouts/AppLayout.vue | 551 - apps/web/src/layouts/AuthLayout.vue | 333 - apps/web/src/main.js | 76 + apps/web/src/main.ts | 35 - apps/web/src/modules/VocabularyApp.js | 585 + apps/web/src/modules/VocabularyState.js | 322 + apps/web/src/router/index-full.ts | 210 - apps/web/src/router/index-minimal.ts | 20 - apps/web/src/router/index.ts | 277 - apps/web/src/stores/auth.ts | 325 - apps/web/src/stores/index.ts | 16 - apps/web/src/stores/learning.ts | 364 - apps/web/src/stores/practice.ts | 422 - apps/web/src/stores/review.ts | 349 - apps/web/src/stores/ui.ts | 280 - apps/web/src/stores/user.ts | 308 - apps/web/src/stores/vocabulary.ts | 393 - apps/web/src/styles/main.scss | 26 + apps/web/src/styles/variables.scss | 76 + apps/web/src/styles/vocabulary.scss | 577 + apps/web/src/types/learning.ts | 198 - apps/web/src/types/practice.ts | 181 - apps/web/src/types/user.ts | 122 - apps/web/src/types/vocabulary.ts | 138 - apps/web/src/utils/AudioManager.js | 202 + apps/web/src/utils/index.ts | 314 - apps/web/src/utils/reportExporter.ts | 421 - apps/web/src/utils/spacedRepetition.ts | 457 - apps/web/src/views/HomeView.vue | 581 - apps/web/src/views/NotFoundView.vue | 70 - apps/web/src/views/OfflineView.vue | 544 - .../web/src/views/auth/ForgotPasswordView.vue | 327 - apps/web/src/views/auth/LoginView.vue | 354 - apps/web/src/views/auth/RegisterView-orig.vue | 462 - apps/web/src/views/auth/RegisterView.vue | 262 - apps/web/src/views/learning/DialogueView.vue | 94 - .../src/views/learning/LearningHomeView.vue | 45 - .../src/views/learning/PronunciationView.vue | 175 - apps/web/src/views/learning/RoleplayView.vue | 101 - .../learning/VocabularyAnalyticsDashboard.vue | 1222 -- .../learning/VocabularyChoicePracticeView.vue | 1112 -- .../learning/VocabularyChoiceResultsView.vue | 837 -- .../learning/VocabularyIntroductionView.vue | 1429 -- .../VocabularyMatchingPracticeView.vue | 1352 -- .../views/learning/VocabularyPracticeView.vue | 829 -- .../VocabularyReorganizePracticeView.vue | 1429 -- .../views/learning/VocabularyReviewMain.vue | 1309 -- .../views/learning/VocabularyView.vue.backup | 744 -- .../views/learning/VocabularyViewNative.vue | 354 - .../views/learning/VocabularyViewSimple.vue | 992 -- apps/web/src/views/shop/ShopView.vue | 168 - apps/web/src/views/shop/SubscriptionView.vue | 242 - apps/web/test.html | 196 + apps/web/tsconfig.json | 50 - apps/web/vite.config.js | 31 + apps/web/vite.config.ts | 190 - docs/00_starter/README.md | 4 +- docs/03_development/coding-standards.md | 1389 +- docs/03_development/project-roadmap.md | 362 +- .../backend-api-separation-plan.md | 586 + .../01_architecture/database-schema.md | 4 +- .../frontend-backend-separation-strategy.md | 635 + .../01_architecture/system-integration.md | 58 +- .../01_architecture/tech-stack-decision.md | 108 +- .../native-development-standards.md | 1382 ++ .../native-frontend-architecture.md | 1310 ++ docs/04_technical/06_development/README.md | 96 + docs/04_technical/README.md | 4 +- ...0250910155305_vue-development-standards.md | 0 ...0250910155305_vue-frontend-architecture.md | 0 .../20250910155305_vue-project-structure.md | 0 .../20250910155305_vue-tools-configuration.md | 0 ...250910160541_file-organization-strategy.md | 0 .../20250910161417_user-flow-specification.md | 0 .../analysis/2025-09-10_analysis-analysis.md | 108 - 119 files changed, 7410 insertions(+), 41912 deletions(-) delete mode 100644 apps/web-native/assets/css/components.css delete mode 100644 apps/web-native/assets/css/layouts.css delete mode 100644 apps/web-native/assets/css/main.css delete mode 100644 apps/web-native/assets/js/app.js delete mode 100644 apps/web-native/assets/js/utils.js delete mode 100644 apps/web-native/data/mockData.js delete mode 100644 apps/web-native/index.html delete mode 100644 apps/web/.eslintrc.js delete mode 100644 apps/web/.prettierrc delete mode 100644 apps/web/auto-imports.d.ts delete mode 100644 apps/web/components.d.ts delete mode 100644 apps/web/dev-dist/registerSW.js delete mode 100644 apps/web/public/debug.html delete mode 100644 apps/web/public/favicon.svg delete mode 100644 apps/web/public/hybrid-approach.html delete mode 100644 apps/web/public/logo.svg delete mode 100644 apps/web/public/test.html delete mode 100644 apps/web/public/vocabulary-native.html delete mode 100644 apps/web/src/App.vue delete mode 100644 apps/web/src/assets/styles/main.scss delete mode 100644 apps/web/src/assets/styles/quasar-variables.sass delete mode 100644 apps/web/src/assets/styles/variables.scss create mode 100644 apps/web/src/components/BaseComponent.js delete mode 100644 apps/web/src/components/PWAInstallPrompt.vue delete mode 100644 apps/web/src/components/base/BaseButton.vue delete mode 100644 apps/web/src/components/base/BaseCard.vue delete mode 100644 apps/web/src/components/base/BaseInput.vue delete mode 100644 apps/web/src/components/base/BaseModal.vue delete mode 100644 apps/web/src/components/business/VocabularyCard.vue delete mode 100644 apps/web/src/components/dashboard/ErrorHeatmap.vue delete mode 100644 apps/web/src/components/dashboard/StatCard.vue delete mode 100644 apps/web/src/components/ui/Icon.vue delete mode 100644 apps/web/src/components/ui/ModalContainer.vue delete mode 100644 apps/web/src/components/ui/ToastContainer.vue delete mode 100644 apps/web/src/composables/useAudio.ts delete mode 100644 apps/web/src/composables/useBrowserBookmarks.ts delete mode 100644 apps/web/src/composables/useKeyboard.ts delete mode 100644 apps/web/src/composables/useKeyboardShortcuts.ts delete mode 100644 apps/web/src/composables/useMultiTabLearning.ts delete mode 100644 apps/web/src/layouts/AppLayout.vue delete mode 100644 apps/web/src/layouts/AuthLayout.vue create mode 100644 apps/web/src/main.js delete mode 100644 apps/web/src/main.ts create mode 100644 apps/web/src/modules/VocabularyApp.js create mode 100644 apps/web/src/modules/VocabularyState.js delete mode 100644 apps/web/src/router/index-full.ts delete mode 100644 apps/web/src/router/index-minimal.ts delete mode 100644 apps/web/src/router/index.ts delete mode 100644 apps/web/src/stores/auth.ts delete mode 100644 apps/web/src/stores/index.ts delete mode 100644 apps/web/src/stores/learning.ts delete mode 100644 apps/web/src/stores/practice.ts delete mode 100644 apps/web/src/stores/review.ts delete mode 100644 apps/web/src/stores/ui.ts delete mode 100644 apps/web/src/stores/user.ts delete mode 100644 apps/web/src/stores/vocabulary.ts create mode 100644 apps/web/src/styles/main.scss create mode 100644 apps/web/src/styles/variables.scss create mode 100644 apps/web/src/styles/vocabulary.scss delete mode 100644 apps/web/src/types/learning.ts delete mode 100644 apps/web/src/types/practice.ts delete mode 100644 apps/web/src/types/user.ts delete mode 100644 apps/web/src/types/vocabulary.ts create mode 100644 apps/web/src/utils/AudioManager.js delete mode 100644 apps/web/src/utils/index.ts delete mode 100644 apps/web/src/utils/reportExporter.ts delete mode 100644 apps/web/src/utils/spacedRepetition.ts delete mode 100644 apps/web/src/views/HomeView.vue delete mode 100644 apps/web/src/views/NotFoundView.vue delete mode 100644 apps/web/src/views/OfflineView.vue delete mode 100644 apps/web/src/views/auth/ForgotPasswordView.vue delete mode 100644 apps/web/src/views/auth/LoginView.vue delete mode 100644 apps/web/src/views/auth/RegisterView-orig.vue delete mode 100644 apps/web/src/views/auth/RegisterView.vue delete mode 100644 apps/web/src/views/learning/DialogueView.vue delete mode 100644 apps/web/src/views/learning/LearningHomeView.vue delete mode 100644 apps/web/src/views/learning/PronunciationView.vue delete mode 100644 apps/web/src/views/learning/RoleplayView.vue delete mode 100644 apps/web/src/views/learning/VocabularyAnalyticsDashboard.vue delete mode 100644 apps/web/src/views/learning/VocabularyChoicePracticeView.vue delete mode 100644 apps/web/src/views/learning/VocabularyChoiceResultsView.vue delete mode 100644 apps/web/src/views/learning/VocabularyIntroductionView.vue delete mode 100644 apps/web/src/views/learning/VocabularyMatchingPracticeView.vue delete mode 100644 apps/web/src/views/learning/VocabularyPracticeView.vue delete mode 100644 apps/web/src/views/learning/VocabularyReorganizePracticeView.vue delete mode 100644 apps/web/src/views/learning/VocabularyReviewMain.vue delete mode 100644 apps/web/src/views/learning/VocabularyView.vue.backup delete mode 100644 apps/web/src/views/learning/VocabularyViewNative.vue delete mode 100644 apps/web/src/views/learning/VocabularyViewSimple.vue delete mode 100644 apps/web/src/views/shop/ShopView.vue delete mode 100644 apps/web/src/views/shop/SubscriptionView.vue create mode 100644 apps/web/test.html delete mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite.config.js delete mode 100644 apps/web/vite.config.ts create mode 100644 docs/04_technical/01_architecture/backend-api-separation-plan.md create mode 100644 docs/04_technical/01_architecture/frontend-backend-separation-strategy.md create mode 100644 docs/04_technical/03_frontend/native-development-standards.md create mode 100644 docs/04_technical/03_frontend/native-frontend-architecture.md create mode 100644 docs/04_technical/06_development/README.md rename docs/04_technical/03_frontend/vue-development-standards.md => sop/archive/20250910155305_vue-development-standards.md (100%) rename docs/04_technical/03_frontend/vue-frontend-architecture.md => sop/archive/20250910155305_vue-frontend-architecture.md (100%) rename docs/04_technical/03_frontend/vue-project-structure.md => sop/archive/20250910155305_vue-project-structure.md (100%) rename docs/04_technical/03_frontend/vue-tools-configuration.md => sop/archive/20250910155305_vue-tools-configuration.md (100%) rename docs/04_technical/06_development/file-organization-strategy.md => sop/archive/20250910160541_file-organization-strategy.md (100%) rename docs/04_technical/06_development/user-flow-specification.md => sop/archive/20250910161417_user-flow-specification.md (100%) delete mode 100644 sop/tools/reports/analysis/2025-09-10_analysis-analysis.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7959855..854b98b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -100,7 +100,12 @@ "Bash(timeout 10 curl -s http://localhost:3000/)", "Bash(./sop/scripts/sop_consistency_check.sh:*)", "Bash(timeout 30 npm run type-check:*)", - "Bash(timeout 10 curl -s -I http://localhost:3000/)" + "Bash(timeout 10 curl -s -I http://localhost:3000/)", + "Bash(npm init:*)", + "Bash(timeout 5 curl -s -I http://localhost:3000/)", + "Bash(lsof:*)", + "Bash(npm run build:*)", + "Bash(npm run preview:*)" ], "deny": [], "ask": [] diff --git a/TASKS.md b/TASKS.md index 8f853c2..dd33c16 100644 --- a/TASKS.md +++ b/TASKS.md @@ -3,27 +3,8 @@ ## 📋 當前任務 ### 🔥 緊急任務 -- [ ] 🔄 **前端架構重構:Vue → 原生HTML** - 完全移除框架依賴,實現100%設計還原 (3-4週) - - 📄 參考: [原生HTML重構專案](projects/native-html-migration.md) - - 🎯 關鍵: 設計精確度100%、Claude Code最佳化、性能提升、維護性提升 - - 📋 合規基礎: 按照現有function-specs,移除Vue/Quasar框架限制 - - 🚀 **第一階段** (週1): 基礎架構搭建、核心CSS框架、JavaScript模組化 - - 📱 **第二階段** (週1): 核心頁面實現 (首頁、認證、詞彙、對話、個人檔案) - - 🎮 **第三階段** (週1): 功能頁面實現 (練習、複習、分析儀表板、設定) - - 🔌 **第四階段** (週1): API整合、進階功能、測試與部署 -- [x] 🏗️ **詞彙學習Web版 - 基礎架構建立** - Vue 3 + Quasar專案初始化,嚴格對照HTML原型 (40小時) ✅ (2025-09-10) - - 📄 參考: [詞彙學習Web開發規劃](projects/vocabulary-learning-web-development-plan.md) [已歸檔] - - 🎯 關鍵: 280px側邊欄布局、CSS變數系統、vocabulary-card組件 - - 📋 合規基礎: vocabulary.html原型 + vue-frontend-architecture.md - - ✨ 完成功能: Vue 3 + Quasar架構、280px側邊欄、CSS變數系統、VocabularyCard組件、TypeScript配置 - - ⚠️ **重構決定**: 此架構將被原生HTML架構取代 - -- [x] 🎨 **詞彙介紹頁面完整實現** - Page_Vocab_Introduction_W,像素級對照原型 (48小時) ✅ (2025-09-10) - - 📄 參考: [詞彙學習Web開發規劃](projects/vocabulary-learning-web-development-plan.md) - - 🎯 關鍵: 多列布局、Web Audio API、快捷鍵系統、筆記編輯器 - - 📋 合規基礎: vocabulary-learning-web.md + vocabulary.html原型 - - ✨ 完成功能: 多列響應式布局、完整快捷鍵系統、Markdown筆記編輯器、書籤整合、詞典整合、詞性色彩編碼、星級評分、例句音頻播放 + ### ⚠️ 重要任務 - [x] 🎮 **練習系統核心開發** - 選擇題、圖片匹配、句子重組三種模式 (56小時) ✅ diff --git a/apps/web-native/assets/css/components.css b/apps/web-native/assets/css/components.css deleted file mode 100644 index 9e6a18d..0000000 --- a/apps/web-native/assets/css/components.css +++ /dev/null @@ -1,723 +0,0 @@ -/* Drama Ling - 組件樣式 */ - -/* ======================================== - 按鈕組件 (Buttons) -======================================== */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--space-2); - padding: var(--space-3) var(--space-6); - border: none; - border-radius: var(--radius-xl); - font-size: var(--text-base); - font-weight: var(--font-weight-semibold); - text-decoration: none; - cursor: pointer; - transition: all var(--transition-fast); - min-height: 2.75rem; - position: relative; - overflow: hidden; -} - -.btn:disabled { - opacity: 0.6; - cursor: not-allowed; - pointer-events: none; -} - -/* 按鈕尺寸 */ -.btn-sm { - padding: var(--space-2) var(--space-4); - font-size: var(--text-sm); - min-height: 2rem; -} - -.btn-lg { - padding: var(--space-4) var(--space-8); - font-size: var(--text-lg); - min-height: 3.5rem; -} - -/* 按鈕變體 */ -.btn-primary { - background: linear-gradient(135deg, var(--primary-teal), var(--secondary-purple)); - color: var(--text-inverse); - box-shadow: 0 4px 15px 0 var(--shadow-primary); -} - -.btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px 0 var(--shadow-primary); -} - -.btn-secondary { - background-color: var(--background-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-light); -} - -.btn-secondary:hover { - background-color: var(--border-light); - border-color: var(--border-medium); -} - -.btn-outline { - background-color: transparent; - color: var(--primary-teal); - border: 2px solid var(--primary-teal); -} - -.btn-outline:hover { - background-color: var(--primary-teal); - color: var(--text-inverse); -} - -.btn-ghost { - background-color: transparent; - color: var(--text-secondary); -} - -.btn-ghost:hover { - background-color: var(--background-tertiary); - color: var(--text-primary); -} - -.btn-danger { - background-color: var(--text-error); - color: var(--text-inverse); -} - -.btn-danger:hover { - background-color: #DC2626; - transform: translateY(-1px); -} - -/* ======================================== - 卡片組件 (Cards) -======================================== */ -.card { - background-color: var(--background-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-xl); - padding: var(--space-6); - box-shadow: 0 2px 8px 0 var(--shadow-light); - transition: all var(--transition-fast); -} - -.card:hover { - box-shadow: 0 8px 25px 0 var(--shadow-medium); - transform: translateY(-2px); -} - -.card-header { - margin-bottom: var(--space-4); - padding-bottom: var(--space-4); - border-bottom: 1px solid var(--border-light); -} - -.card-title { - font-size: var(--text-lg); - font-weight: var(--font-weight-semibold); - color: var(--text-primary); - margin: 0; -} - -.card-subtitle { - font-size: var(--text-sm); - color: var(--text-secondary); - margin: var(--space-1) 0 0 0; -} - -.card-body { - color: var(--text-secondary); - line-height: var(--leading-relaxed); -} - -.card-footer { - margin-top: var(--space-4); - padding-top: var(--space-4); - border-top: 1px solid var(--border-light); - display: flex; - gap: var(--space-3); - justify-content: flex-end; -} - -/* 卡片變體 */ -.card-interactive { - cursor: pointer; - transition: all var(--transition-normal); -} - -.card-interactive:hover { - border-color: var(--primary-teal); - box-shadow: 0 12px 30px 0 var(--shadow-primary); -} - -.card-flat { - box-shadow: none; - border: 1px solid var(--border-light); -} - -.card-elevated { - box-shadow: 0 10px 25px 0 var(--shadow-medium); -} - -/* ======================================== - 表單組件 (Forms) -======================================== */ -.form-group { - margin-bottom: var(--space-4); -} - -.form-label { - display: block; - font-size: var(--text-sm); - font-weight: var(--font-weight-medium); - color: var(--text-primary); - margin-bottom: var(--space-2); -} - -.form-label.required::after { - content: '*'; - color: var(--text-error); - margin-left: var(--space-1); -} - -.form-input, -.form-textarea, -.form-select { - width: 100%; - padding: var(--space-3) var(--space-4); - border: 2px solid var(--border-light); - border-radius: var(--radius-lg); - font-size: var(--text-base); - background-color: var(--background-secondary); - color: var(--text-primary); - transition: all var(--transition-fast); -} - -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: none; - border-color: var(--primary-teal); - box-shadow: 0 0 0 3px rgba(0, 229, 204, 0.1); -} - -.form-input.error, -.form-textarea.error, -.form-select.error { - border-color: var(--text-error); -} - -.form-input.error:focus, -.form-textarea.error:focus, -.form-select.error:focus { - box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); -} - -.form-textarea { - resize: vertical; - min-height: 6rem; -} - -.form-help { - font-size: var(--text-sm); - color: var(--text-secondary); - margin-top: var(--space-1); -} - -.form-error { - font-size: var(--text-sm); - color: var(--text-error); - margin-top: var(--space-1); - display: flex; - align-items: center; - gap: var(--space-1); -} - -/* 複選框和單選框 */ -.form-checkbox, -.form-radio { - display: flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; -} - -.form-checkbox input, -.form-radio input { - width: 1.25rem; - height: 1.25rem; - margin: 0; -} - -/* ======================================== - 徽章組件 (Badges) -======================================== */ -.badge { - display: inline-flex; - align-items: center; - padding: var(--space-1) var(--space-3); - font-size: var(--text-xs); - font-weight: var(--font-weight-semibold); - border-radius: var(--radius-full); -} - -.badge-primary { - background-color: var(--primary-teal); - color: var(--text-inverse); -} - -.badge-secondary { - background-color: var(--text-secondary); - color: var(--text-inverse); -} - -.badge-success { - background-color: var(--text-success); - color: var(--text-inverse); -} - -.badge-warning { - background-color: var(--text-warning); - color: var(--text-inverse); -} - -.badge-error { - background-color: var(--text-error); - color: var(--text-inverse); -} - -.badge-outline { - background-color: transparent; - border: 1px solid currentColor; -} - -/* ======================================== - 彈窗組件 (Modal) -======================================== */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: var(--z-modal-backdrop); - opacity: 0; - visibility: hidden; - transition: all var(--transition-normal); -} - -.modal-overlay.active { - opacity: 1; - visibility: visible; -} - -.modal { - background-color: var(--background-secondary); - border-radius: var(--radius-2xl); - box-shadow: 0 20px 50px 0 var(--shadow-heavy); - max-width: 500px; - width: 90%; - max-height: 90vh; - overflow-y: auto; - transform: scale(0.95) translateY(20px); - transition: transform var(--transition-normal); -} - -.modal-overlay.active .modal { - transform: scale(1) translateY(0); -} - -.modal-header { - padding: var(--space-6); - border-bottom: 1px solid var(--border-light); - display: flex; - align-items: center; - justify-content: space-between; -} - -.modal-title { - font-size: var(--text-xl); - font-weight: var(--font-weight-semibold); - color: var(--text-primary); - margin: 0; -} - -.modal-close { - width: 2rem; - height: 2rem; - border-radius: var(--radius-full); - display: flex; - align-items: center; - justify-content: center; - color: var(--text-secondary); - transition: all var(--transition-fast); -} - -.modal-close:hover { - background-color: var(--background-tertiary); - color: var(--text-primary); -} - -.modal-body { - padding: var(--space-6); -} - -.modal-footer { - padding: var(--space-6); - border-top: 1px solid var(--border-light); - display: flex; - gap: var(--space-3); - justify-content: flex-end; -} - -/* ======================================== - 提示框組件 (Toast) -======================================== */ -.toast-container { - position: fixed; - top: var(--space-4); - right: var(--space-4); - z-index: var(--z-tooltip); - display: flex; - flex-direction: column; - gap: var(--space-3); -} - -.toast { - background-color: var(--background-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - padding: var(--space-4); - box-shadow: 0 8px 25px 0 var(--shadow-medium); - display: flex; - align-items: center; - gap: var(--space-3); - max-width: 400px; - animation: slideInRight var(--transition-normal) ease-out; -} - -.toast.removing { - animation: slideOutRight var(--transition-normal) ease-in; -} - -.toast-icon { - font-size: var(--text-lg); - flex-shrink: 0; -} - -.toast-content { - flex: 1; -} - -.toast-title { - font-weight: var(--font-weight-semibold); - color: var(--text-primary); - margin-bottom: var(--space-1); -} - -.toast-message { - font-size: var(--text-sm); - color: var(--text-secondary); -} - -.toast-close { - color: var(--text-secondary); - cursor: pointer; - padding: var(--space-1); - border-radius: var(--radius-sm); - transition: all var(--transition-fast); -} - -.toast-close:hover { - background-color: var(--background-tertiary); - color: var(--text-primary); -} - -/* 提示框類型 */ -.toast-success { - border-left: 4px solid var(--text-success); -} - -.toast-success .toast-icon { - color: var(--text-success); -} - -.toast-warning { - border-left: 4px solid var(--text-warning); -} - -.toast-warning .toast-icon { - color: var(--text-warning); -} - -.toast-error { - border-left: 4px solid var(--text-error); -} - -.toast-error .toast-icon { - color: var(--text-error); -} - -.toast-info { - border-left: 4px solid var(--primary-teal); -} - -.toast-info .toast-icon { - color: var(--primary-teal); -} - -/* ======================================== - 載入組件 (Loading) -======================================== */ -.loading { - display: inline-block; - width: 1.5rem; - height: 1.5rem; - border: 2px solid var(--border-light); - border-top-color: var(--primary-teal); - border-radius: var(--radius-full); - animation: spin 1s linear infinite; -} - -.loading-sm { - width: 1rem; - height: 1rem; - border-width: 1px; -} - -.loading-lg { - width: 2rem; - height: 2rem; - border-width: 3px; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* 載入覆蓋層 */ -.loading-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(255, 255, 255, 0.8); - display: flex; - align-items: center; - justify-content: center; - z-index: var(--z-modal); -} - -/* ======================================== - 進度條組件 (Progress) -======================================== */ -.progress { - width: 100%; - height: 0.5rem; - background-color: var(--background-tertiary); - border-radius: var(--radius-full); - overflow: hidden; -} - -.progress-bar { - height: 100%; - background: linear-gradient(90deg, var(--primary-teal), var(--secondary-purple)); - border-radius: var(--radius-full); - transition: width var(--transition-normal); - position: relative; -} - -.progress-bar::after { - content: ''; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: linear-gradient(90deg, - rgba(255, 255, 255, 0.2) 0%, - rgba(255, 255, 255, 0.4) 50%, - rgba(255, 255, 255, 0.2) 100%); - animation: shimmer 2s infinite; -} - -@keyframes shimmer { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } -} - -/* 進度條尺寸 */ -.progress-sm { - height: 0.25rem; -} - -.progress-lg { - height: 0.75rem; -} - -/* ======================================== - 標籤頁組件 (Tabs) -======================================== */ -.tabs { - display: flex; - flex-direction: column; -} - -.tab-list { - display: flex; - border-bottom: 2px solid var(--border-light); - margin-bottom: var(--space-6); -} - -.tab-button { - padding: var(--space-3) var(--space-4); - border: none; - background: none; - color: var(--text-secondary); - font-weight: var(--font-weight-medium); - cursor: pointer; - position: relative; - transition: all var(--transition-fast); -} - -.tab-button:hover { - color: var(--text-primary); -} - -.tab-button.active { - color: var(--primary-teal); -} - -.tab-button.active::after { - content: ''; - position: absolute; - bottom: -2px; - left: 0; - right: 0; - height: 2px; - background-color: var(--primary-teal); -} - -.tab-panel { - display: none; - animation: fadeInUp var(--transition-normal) ease-out; -} - -.tab-panel.active { - display: block; -} - -/* ======================================== - 下拉選單組件 (Dropdown) -======================================== */ -.dropdown { - position: relative; - display: inline-block; -} - -.dropdown-toggle { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - transition: background-color var(--transition-fast); -} - -.dropdown-toggle:hover { - background-color: var(--background-tertiary); -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - min-width: 200px; - background-color: var(--background-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - box-shadow: 0 8px 25px 0 var(--shadow-medium); - padding: var(--space-2); - z-index: var(--z-dropdown); - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: all var(--transition-fast); -} - -.dropdown.open .dropdown-menu { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.dropdown-item { - display: flex; - align-items: center; - gap: var(--space-3); - width: 100%; - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - color: var(--text-secondary); - transition: all var(--transition-fast); - cursor: pointer; -} - -.dropdown-item:hover { - background-color: var(--background-tertiary); - color: var(--text-primary); -} - -.dropdown-divider { - height: 1px; - background-color: var(--border-light); - margin: var(--space-2) 0; -} - -/* 響應式調整 */ -@media (max-width: 767px) { - .modal { - width: 95%; - margin: var(--space-4); - } - - .toast-container { - left: var(--space-4); - right: var(--space-4); - } - - .toast { - max-width: none; - } -} - -/* Toast 動畫 */ -@keyframes slideInRight { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -@keyframes slideOutRight { - to { - transform: translateX(100%); - opacity: 0; - } -} \ No newline at end of file diff --git a/apps/web-native/assets/css/layouts.css b/apps/web-native/assets/css/layouts.css deleted file mode 100644 index a55a445..0000000 --- a/apps/web-native/assets/css/layouts.css +++ /dev/null @@ -1,628 +0,0 @@ -/* Drama Ling - 佈局樣式 */ - -/* ======================================== - 頁首導航 (Header) -======================================== */ -.app-header { - position: fixed; - top: 0; - left: 0; - right: 0; - height: 4rem; - background-color: var(--background-secondary); - border-bottom: 1px solid var(--border-light); - z-index: var(--z-sticky); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); -} - -.header-container { - display: flex; - align-items: center; - justify-content: space-between; - height: 100%; - padding: 0 var(--space-4); - max-width: 1920px; - margin: 0 auto; -} - -.header-left { - display: flex; - align-items: center; - gap: var(--space-4); -} - -.menu-toggle { - display: flex; - align-items: center; - justify-content: center; - width: 2.5rem; - height: 2.5rem; - border-radius: var(--radius-md); - background-color: transparent; - transition: all var(--transition-fast); -} - -.menu-toggle:hover { - background-color: var(--background-tertiary); -} - -.hamburger { - position: relative; - width: 1.25rem; - height: 2px; - background-color: var(--text-primary); - transition: all var(--transition-fast); -} - -.hamburger::before, -.hamburger::after { - content: ''; - position: absolute; - width: 1.25rem; - height: 2px; - background-color: var(--text-primary); - transition: all var(--transition-fast); -} - -.hamburger::before { - top: -6px; -} - -.hamburger::after { - bottom: -6px; -} - -/* 漢堡選單動畫 */ -.menu-toggle.active .hamburger { - background-color: transparent; -} - -.menu-toggle.active .hamburger::before { - transform: rotate(45deg); - top: 0; -} - -.menu-toggle.active .hamburger::after { - transform: rotate(-45deg); - bottom: 0; -} - -.logo { - display: flex; - align-items: center; - gap: var(--space-2); - font-weight: var(--font-weight-bold); - font-size: var(--text-lg); - color: var(--text-primary); -} - -.logo-icon { - width: 2rem; - height: 2rem; -} - -.logo-text { - font-family: var(--font-family-display); -} - -.header-center { - display: none; -} - -.user-stats { - display: flex; - gap: var(--space-4); -} - -.stat-item { - display: flex; - align-items: center; - gap: var(--space-1); - padding: var(--space-2) var(--space-3); - background-color: var(--background-tertiary); - border-radius: var(--radius-xl); - font-size: var(--text-sm); - font-weight: var(--font-weight-medium); -} - -.stat-icon { - font-size: var(--text-base); -} - -.stat-value { - color: var(--text-primary); -} - -.header-right { - display: flex; - align-items: center; -} - -.profile-btn { - width: 2.5rem; - height: 2.5rem; - border-radius: var(--radius-full); - overflow: hidden; - transition: transform var(--transition-fast); -} - -.profile-btn:hover { - transform: scale(1.05); -} - -.avatar { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* ======================================== - 側邊欄 (Sidebar) -======================================== */ -.sidebar { - position: fixed; - top: 0; - left: 0; - width: 320px; - height: 100vh; - background-color: var(--background-secondary); - border-right: 1px solid var(--border-light); - transform: translateX(-100%); - transition: transform var(--transition-normal); - z-index: var(--z-fixed); - overflow-y: auto; -} - -.sidebar.open { - transform: translateX(0); -} - -.sidebar-content { - display: flex; - flex-direction: column; - height: 100%; -} - -.sidebar-header { - padding: var(--space-6) var(--space-4); - border-bottom: 1px solid var(--border-light); -} - -.user-profile { - display: flex; - align-items: center; - gap: var(--space-3); -} - -.user-avatar { - width: 3rem; - height: 3rem; - border-radius: var(--radius-full); - object-fit: cover; -} - -.user-info { - flex: 1; -} - -.user-name { - font-weight: var(--font-weight-semibold); - color: var(--text-primary); - margin-bottom: var(--space-1); -} - -.user-level { - font-size: var(--text-sm); - color: var(--text-secondary); -} - -.sidebar-menu { - flex: 1; - padding: var(--space-4) 0; -} - -.menu-item { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - color: var(--text-secondary); - transition: all var(--transition-fast); - position: relative; -} - -.menu-item::before { - content: ''; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 3px; - background-color: var(--primary-teal); - transform: scaleY(0); - transition: transform var(--transition-fast); -} - -.menu-item:hover, -.menu-item.active { - color: var(--text-primary); - background-color: var(--background-tertiary); -} - -.menu-item.active::before { - transform: scaleY(1); -} - -.menu-icon { - font-size: var(--text-lg); - width: 1.5rem; - text-align: center; -} - -.menu-text { - font-weight: var(--font-weight-medium); -} - -.sidebar-footer { - padding: var(--space-4); - border-top: 1px solid var(--border-light); -} - -.logout-btn { - display: flex; - align-items: center; - gap: var(--space-3); - width: 100%; - padding: var(--space-3) var(--space-4); - color: var(--text-error); - border-radius: var(--radius-md); - transition: background-color var(--transition-fast); -} - -.logout-btn:hover { - background-color: rgba(239, 68, 68, 0.1); -} - -/* ======================================== - 主內容區域 (Main Content) -======================================== */ -.main-content { - flex: 1; - padding: var(--space-6) var(--space-4); - max-width: 1200px; - margin: 0 auto; - width: 100%; -} - -.page-view { - display: none; - animation: fadeInUp var(--transition-normal) ease-out; -} - -.page-view.active { - display: block; -} - -.dashboard-container { - display: flex; - flex-direction: column; - gap: var(--space-8); -} - -.welcome-section { - text-align: center; - padding: var(--space-8) 0; -} - -.welcome-title { - font-size: var(--text-3xl); - font-weight: var(--font-weight-bold); - font-family: var(--font-family-display); - color: var(--text-primary); - margin-bottom: var(--space-2); -} - -.welcome-subtitle { - font-size: var(--text-lg); - color: var(--text-secondary); - margin: 0; -} - -.quick-stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--space-4); -} - -.stat-card { - background-color: var(--background-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-xl); - padding: var(--space-6); - display: flex; - align-items: center; - gap: var(--space-4); - transition: all var(--transition-fast); -} - -.stat-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px 0 var(--shadow-medium); - border-color: var(--primary-teal); -} - -.stat-card .stat-icon { - font-size: var(--text-2xl); - width: 3rem; - height: 3rem; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, var(--primary-teal), var(--secondary-purple)); - border-radius: var(--radius-xl); - margin: 0; -} - -.stat-card .stat-info { - flex: 1; -} - -.stat-card .stat-value { - font-size: var(--text-2xl); - font-weight: var(--font-weight-bold); - color: var(--text-primary); - display: block; - margin-bottom: var(--space-1); -} - -.stat-card .stat-label { - font-size: var(--text-sm); - color: var(--text-secondary); -} - -.learning-modules { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: var(--space-6); -} - -.module-card { - background-color: var(--background-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-2xl); - padding: var(--space-8); - transition: all var(--transition-normal); - cursor: pointer; - position: relative; - overflow: hidden; -} - -.module-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: linear-gradient(90deg, var(--primary-teal), var(--secondary-purple)); - transform: scaleX(0); - transform-origin: left; - transition: transform var(--transition-normal); -} - -.module-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 30px 0 var(--shadow-primary); - border-color: var(--primary-teal); -} - -.module-card:hover::before { - transform: scaleX(1); -} - -.module-icon { - font-size: var(--text-4xl); - margin-bottom: var(--space-4); - display: block; -} - -.module-content { - margin-bottom: var(--space-6); -} - -.module-title { - font-size: var(--text-xl); - font-weight: var(--font-weight-bold); - color: var(--text-primary); - margin-bottom: var(--space-2); - font-family: var(--font-family-display); -} - -.module-description { - font-size: var(--text-base); - color: var(--text-secondary); - line-height: var(--leading-relaxed); - margin-bottom: var(--space-4); -} - -.module-btn { - background: linear-gradient(135deg, var(--primary-teal), var(--secondary-purple)); - color: var(--text-inverse); - padding: var(--space-3) var(--space-6); - border-radius: var(--radius-xl); - font-weight: var(--font-weight-semibold); - transition: all var(--transition-fast); -} - -.module-btn:hover { - transform: scale(1.05); - box-shadow: 0 4px 15px 0 var(--shadow-primary); -} - -.module-progress { - display: flex; - align-items: center; - gap: var(--space-3); - margin-top: var(--space-4); -} - -.progress-bar { - flex: 1; - height: 6px; - background-color: var(--background-tertiary); - border-radius: var(--radius-full); - overflow: hidden; -} - -.progress-fill { - height: 100%; - background: linear-gradient(90deg, var(--primary-teal), var(--secondary-purple)); - border-radius: var(--radius-full); - transition: width var(--transition-normal); -} - -.progress-text { - font-size: var(--text-sm); - font-weight: var(--font-weight-medium); - color: var(--text-secondary); -} - -/* ======================================== - 響應式設計 -======================================== */ - -/* 平板及以上:顯示 Header 中央統計 */ -@media (min-width: 768px) { - .header-center { - display: block; - } -} - -/* 平板及以上:調整側邊欄為靜態 */ -@media (min-width: 768px) { - .sidebar { - position: static; - transform: translateX(0); - grid-area: sidebar; - } - - .app-header { - grid-area: header; - position: static; - } - - .main-content { - grid-area: main; - padding: var(--space-8); - } - - .menu-toggle { - display: none; - } -} - -/* 大型桌機:增加 Header 容器最大寬度 */ -@media (min-width: 1200px) { - .header-container { - padding: 0 var(--space-8); - } - - .main-content { - padding: var(--space-12); - } - - .dashboard-container { - gap: var(--space-12); - } - - .learning-modules { - gap: var(--space-8); - } -} - -/* 手機優化 */ -@media (max-width: 767px) { - .app-header { - height: 3.5rem; - } - - .header-container { - padding: 0 var(--space-3); - } - - .main-content { - padding: var(--space-4); - margin-top: 3.5rem; - } - - .welcome-title { - font-size: var(--text-2xl); - } - - .welcome-subtitle { - font-size: var(--text-base); - } - - .quick-stats { - grid-template-columns: 1fr; - gap: var(--space-3); - } - - .stat-card { - padding: var(--space-4); - } - - .learning-modules { - grid-template-columns: 1fr; - gap: var(--space-4); - } - - .module-card { - padding: var(--space-6); - } - - .sidebar { - width: 280px; - } -} - -/* 極小手機優化 */ -@media (max-width: 375px) { - .header-container { - padding: 0 var(--space-2); - } - - .main-content { - padding: var(--space-3); - } - - .dashboard-container { - gap: var(--space-6); - } -} - -/* 側邊欄遮罩層 */ -.sidebar-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - opacity: 0; - visibility: hidden; - transition: all var(--transition-normal); - z-index: var(--z-modal-backdrop); -} - -.sidebar-overlay.active { - opacity: 1; - visibility: visible; -} - -@media (min-width: 768px) { - .sidebar-overlay { - display: none; - } -} \ No newline at end of file diff --git a/apps/web-native/assets/css/main.css b/apps/web-native/assets/css/main.css deleted file mode 100644 index e051fbe..0000000 --- a/apps/web-native/assets/css/main.css +++ /dev/null @@ -1,476 +0,0 @@ -/* Drama Ling - 主要樣式框架 */ - -/* CSS 變數系統 - 設計代幣 */ -:root { - /* 色彩系統 */ - --primary-teal: #00E5CC; - --primary-teal-light: #33EBDB; - --primary-teal-dark: #00B8A3; - --secondary-purple: #6366F1; - --secondary-purple-light: #8B87F7; - --secondary-purple-dark: #4F46E5; - - /* 背景色彩 */ - --background-primary: #F7F9FC; - --background-secondary: #FFFFFF; - --background-tertiary: #F1F5F9; - --background-dark: #1A1A1A; - --background-dark-secondary: #2D2D2D; - - /* 文字色彩 */ - --text-primary: #2C3E50; - --text-secondary: #64748B; - --text-tertiary: #94A3B8; - --text-inverse: #FFFFFF; - --text-success: #10B981; - --text-warning: #F59E0B; - --text-error: #EF4444; - - /* 邊框色彩 */ - --border-light: #E2E8F0; - --border-medium: #CBD5E1; - --border-dark: #64748B; - - /* 陰影色彩 */ - --shadow-light: rgba(0, 0, 0, 0.05); - --shadow-medium: rgba(0, 0, 0, 0.1); - --shadow-heavy: rgba(0, 0, 0, 0.15); - --shadow-primary: rgba(0, 229, 204, 0.2); - - /* 間距系統 */ - --space-1: 0.25rem; /* 4px */ - --space-2: 0.5rem; /* 8px */ - --space-3: 0.75rem; /* 12px */ - --space-4: 1rem; /* 16px */ - --space-5: 1.25rem; /* 20px */ - --space-6: 1.5rem; /* 24px */ - --space-8: 2rem; /* 32px */ - --space-10: 2.5rem; /* 40px */ - --space-12: 3rem; /* 48px */ - --space-16: 4rem; /* 64px */ - --space-20: 5rem; /* 80px */ - - /* 字體系統 */ - --font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - --font-family-display: "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif; - --font-family-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace; - - --text-xs: 0.75rem; /* 12px */ - --text-sm: 0.875rem; /* 14px */ - --text-base: 1rem; /* 16px */ - --text-lg: 1.125rem; /* 18px */ - --text-xl: 1.25rem; /* 20px */ - --text-2xl: 1.5rem; /* 24px */ - --text-3xl: 1.875rem; /* 30px */ - --text-4xl: 2.25rem; /* 36px */ - --text-5xl: 3rem; /* 48px */ - - --font-weight-light: 300; - --font-weight-normal: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; - --font-weight-bold: 700; - - /* 行高系統 */ - --leading-none: 1; - --leading-tight: 1.25; - --leading-snug: 1.375; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - --leading-loose: 2; - - /* 圓角系統 */ - --radius-sm: 0.25rem; /* 4px */ - --radius-md: 0.5rem; /* 8px */ - --radius-lg: 0.75rem; /* 12px */ - --radius-xl: 1rem; /* 16px */ - --radius-2xl: 1.5rem; /* 24px */ - --radius-full: 9999px; - - /* 動畫過渡 */ - --transition-fast: 0.15s ease-out; - --transition-normal: 0.3s ease-out; - --transition-slow: 0.5s ease-out; - - /* Z-index 層級 */ - --z-dropdown: 1000; - --z-sticky: 1020; - --z-fixed: 1030; - --z-modal-backdrop: 1040; - --z-modal: 1050; - --z-popover: 1060; - --z-tooltip: 1070; -} - -/* 暗色主題變數 */ -@media (prefers-color-scheme: dark) { - :root { - --background-primary: #0F172A; - --background-secondary: #1E293B; - --background-tertiary: #334155; - --text-primary: #F8FAFC; - --text-secondary: #CBD5E1; - --text-tertiary: #94A3B8; - --border-light: #334155; - --border-medium: #475569; - --border-dark: #64748B; - } -} - -/* 全域重置和基礎樣式 */ -*, -*::before, -*::after { - box-sizing: border-box; -} - -html { - font-size: 16px; - line-height: 1.5; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; -} - -body { - margin: 0; - padding: 0; - font-family: var(--font-family-base); - font-size: var(--text-base); - font-weight: var(--font-weight-normal); - line-height: var(--leading-normal); - color: var(--text-primary); - background-color: var(--background-primary); - overflow-x: hidden; -} - -/* 可訪問性改善 */ -*:focus { - outline: 2px solid var(--primary-teal); - outline-offset: 2px; -} - -*:focus:not(:focus-visible) { - outline: none; -} - -/* 按鈕重置 */ -button { - margin: 0; - padding: 0; - border: none; - background: none; - font: inherit; - cursor: pointer; -} - -/* 連結重置 */ -a { - text-decoration: none; - color: inherit; -} - -/* 圖片重置 */ -img { - max-width: 100%; - height: auto; -} - -/* 表單元素重置 */ -input, -textarea, -select { - font: inherit; - margin: 0; -} - -/* 清單重置 */ -ul, -ol { - list-style: none; - margin: 0; - padding: 0; -} - -/* 載入畫面 */ -.loading-screen { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(135deg, var(--primary-teal), var(--secondary-purple)); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - z-index: var(--z-modal); - transition: opacity var(--transition-slow), visibility var(--transition-slow); -} - -.loading-screen.hidden { - opacity: 0; - visibility: hidden; -} - -.loading-logo { - text-align: center; - margin-bottom: var(--space-8); -} - -.loading-icon { - width: 4rem; - height: 4rem; - margin-bottom: var(--space-4); - animation: pulse 2s ease-in-out infinite; -} - -.loading-text { - font-size: var(--text-2xl); - font-weight: var(--font-weight-bold); - color: var(--text-inverse); - font-family: var(--font-family-display); -} - -.loading-progress { - width: 12rem; - height: 0.25rem; - background-color: rgba(255, 255, 255, 0.2); - border-radius: var(--radius-full); - overflow: hidden; -} - -.loading-bar { - width: 100%; - height: 100%; - background: linear-gradient(90deg, - var(--text-inverse) 0%, - rgba(255, 255, 255, 0.8) 50%, - var(--text-inverse) 100%); - animation: loading 1.5s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { transform: scale(1); opacity: 1; } - 50% { transform: scale(1.05); opacity: 0.8; } -} - -@keyframes loading { - 0% { transform: translateX(-100%); } - 50% { transform: translateX(0%); } - 100% { transform: translateX(100%); } -} - -/* 應用容器 */ -.app-container { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -/* 響應式設計斷點 */ -/* 手機 */ -@media (max-width: 767px) { - .app-container { - padding-top: 4rem; /* Header 高度 */ - } -} - -/* 平板 */ -@media (min-width: 768px) and (max-width: 1023px) { - .app-container { - display: grid; - grid-template-columns: 280px 1fr; - grid-template-rows: auto 1fr; - grid-template-areas: - "sidebar header" - "sidebar main"; - } -} - -/* 桌機 */ -@media (min-width: 1024px) { - .app-container { - display: grid; - grid-template-columns: 320px 1fr; - grid-template-rows: auto 1fr; - grid-template-areas: - "sidebar header" - "sidebar main"; - } -} - -/* 工具類別 */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -.text-center { text-align: center; } -.text-left { text-align: left; } -.text-right { text-align: right; } - -.hidden { display: none !important; } -.invisible { visibility: hidden; } - -.flex { display: flex; } -.flex-col { flex-direction: column; } -.items-center { align-items: center; } -.justify-center { justify-content: center; } -.justify-between { justify-content: space-between; } - -.w-full { width: 100%; } -.h-full { height: 100%; } - -.rounded { border-radius: var(--radius-md); } -.rounded-lg { border-radius: var(--radius-lg); } -.rounded-xl { border-radius: var(--radius-xl); } -.rounded-full { border-radius: var(--radius-full); } - -.shadow { box-shadow: 0 1px 3px 0 var(--shadow-light); } -.shadow-md { box-shadow: 0 4px 6px -1px var(--shadow-medium); } -.shadow-lg { box-shadow: 0 10px 15px -3px var(--shadow-heavy); } - -.transition { transition: all var(--transition-normal); } - -/* 色彩工具類 */ -.text-primary { color: var(--text-primary); } -.text-secondary { color: var(--text-secondary); } -.text-success { color: var(--text-success); } -.text-warning { color: var(--text-warning); } -.text-error { color: var(--text-error); } - -.bg-primary { background-color: var(--primary-teal); } -.bg-secondary { background-color: var(--secondary-purple); } -.bg-white { background-color: var(--background-secondary); } - -/* 間距工具類 */ -.p-1 { padding: var(--space-1); } -.p-2 { padding: var(--space-2); } -.p-3 { padding: var(--space-3); } -.p-4 { padding: var(--space-4); } -.p-6 { padding: var(--space-6); } -.p-8 { padding: var(--space-8); } - -.m-1 { margin: var(--space-1); } -.m-2 { margin: var(--space-2); } -.m-3 { margin: var(--space-3); } -.m-4 { margin: var(--space-4); } -.m-6 { margin: var(--space-6); } -.m-8 { margin: var(--space-8); } - -.mt-1 { margin-top: var(--space-1); } -.mt-2 { margin-top: var(--space-2); } -.mt-4 { margin-top: var(--space-4); } -.mt-6 { margin-top: var(--space-6); } -.mt-8 { margin-top: var(--space-8); } - -.mb-1 { margin-bottom: var(--space-1); } -.mb-2 { margin-bottom: var(--space-2); } -.mb-4 { margin-bottom: var(--space-4); } -.mb-6 { margin-bottom: var(--space-6); } -.mb-8 { margin-bottom: var(--space-8); } - -/* 字體工具類 */ -.text-xs { font-size: var(--text-xs); } -.text-sm { font-size: var(--text-sm); } -.text-base { font-size: var(--text-base); } -.text-lg { font-size: var(--text-lg); } -.text-xl { font-size: var(--text-xl); } -.text-2xl { font-size: var(--text-2xl); } -.text-3xl { font-size: var(--text-3xl); } -.text-4xl { font-size: var(--text-4xl); } - -.font-light { font-weight: var(--font-weight-light); } -.font-normal { font-weight: var(--font-weight-normal); } -.font-medium { font-weight: var(--font-weight-medium); } -.font-semibold { font-weight: var(--font-weight-semibold); } -.font-bold { font-weight: var(--font-weight-bold); } - -/* 互動效果 */ -.hover-lift { - transition: transform var(--transition-fast), box-shadow var(--transition-fast); -} - -.hover-lift:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px 0 var(--shadow-medium); -} - -.hover-scale { - transition: transform var(--transition-fast); -} - -.hover-scale:hover { - transform: scale(1.05); -} - -/* 動畫關鍵幀 */ -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideInRight { - from { - transform: translateX(100%); - } - to { - transform: translateX(0); - } -} - -@keyframes slideInLeft { - from { - transform: translateX(-100%); - } - to { - transform: translateX(0); - } -} - -.animate-fade-in { - animation: fadeIn var(--transition-normal) ease-out; -} - -.animate-fade-in-up { - animation: fadeInUp var(--transition-normal) ease-out; -} - -.animate-slide-in-right { - animation: slideInRight var(--transition-normal) ease-out; -} - -.animate-slide-in-left { - animation: slideInLeft var(--transition-normal) ease-out; -} - -/* 無障礙設計 - 減少動畫 */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} \ No newline at end of file diff --git a/apps/web-native/assets/js/app.js b/apps/web-native/assets/js/app.js deleted file mode 100644 index b034621..0000000 --- a/apps/web-native/assets/js/app.js +++ /dev/null @@ -1,528 +0,0 @@ -/** - * Drama Ling - 主應用程序 - * 原生JavaScript模組化架構 - */ - -import { AppState } from './utils.js'; -import { ApiClient } from './api.js'; -import { AuthManager } from './auth.js'; -import { Sidebar } from './components/sidebar.js'; -import { Navbar } from './components/navbar.js'; -import { Modal } from './components/modal.js'; -import { Toast } from './components/toast.js'; - -class DramaLingApp { - constructor() { - this.state = new AppState(); - this.api = new ApiClient(this.getApiBaseUrl()); - this.auth = new AuthManager(this.api, this.state); - - // UI 組件 - this.sidebar = null; - this.navbar = null; - this.modal = new Modal(); - this.toast = new Toast(); - - // 當前頁面 - this.currentPage = 'home'; - - this.init(); - } - - /** - * 初始化應用程序 - */ - async init() { - try { - // 顯示載入畫面 - this.showLoading(); - - // 檢查認證狀態 - const isAuthenticated = await this.auth.checkAuthStatus(); - - // 初始化 UI 組件 - this.initializeComponents(); - - // 設定事件監聽器 - this.setupEventListeners(); - - // 根據認證狀態決定初始頁面 - if (isAuthenticated) { - await this.loadUserData(); - this.showMainApp(); - } else { - this.showAuthPage(); - } - - // 隱藏載入畫面 - this.hideLoading(); - - } catch (error) { - console.error('應用程序初始化失敗:', error); - this.toast.show('錯誤', '應用程序載入失敗,請刷新頁面重試', 'error'); - this.hideLoading(); - } - } - - /** - * 獲取 API 基礎 URL - */ - getApiBaseUrl() { - return import.meta.env?.VITE_API_BASE_URL || 'http://localhost:3000/api'; - } - - /** - * 初始化 UI 組件 - */ - initializeComponents() { - this.navbar = new Navbar(this); - this.sidebar = new Sidebar(this); - - // 綁定組件到應用狀態 - this.state.addListener('userUpdated', (user) => { - this.navbar.updateUserInfo(user); - this.sidebar.updateUserInfo(user); - }); - - this.state.addListener('statsUpdated', (stats) => { - this.navbar.updateStats(stats); - }); - } - - /** - * 設定事件監聽器 - */ - setupEventListeners() { - // 全域鍵盤快捷鍵 - document.addEventListener('keydown', this.handleKeyboardShortcuts.bind(this)); - - // 路由變化監聽 - window.addEventListener('popstate', this.handlePopState.bind(this)); - - // 網路狀態監聽 - window.addEventListener('online', this.handleOnline.bind(this)); - window.addEventListener('offline', this.handleOffline.bind(this)); - - // 頁面可見性變化 - document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); - - // 模組卡片點擊 - document.querySelectorAll('.module-card').forEach(card => { - card.addEventListener('click', this.handleModuleClick.bind(this)); - }); - - // 選單項目點擊 - document.addEventListener('click', (e) => { - const menuItem = e.target.closest('.menu-item'); - if (menuItem && menuItem.dataset.page) { - e.preventDefault(); - this.navigateTo(menuItem.dataset.page); - } - }); - } - - /** - * 鍵盤快捷鍵處理 - */ - handleKeyboardShortcuts(e) { - // Ctrl/Cmd + K: 開啟搜尋 - if ((e.ctrlKey || e.metaKey) && e.key === 'k') { - e.preventDefault(); - this.openSearch(); - } - - // Ctrl/Cmd + /: 開啟快捷鍵說明 - if ((e.ctrlKey || e.metaKey) && e.key === '/') { - e.preventDefault(); - this.showShortcutsHelp(); - } - - // Escape: 關閉彈窗和選單 - if (e.key === 'Escape') { - this.sidebar.close(); - this.modal.close(); - } - - // Alt + 數字鍵: 快速導航 - if (e.altKey && /^[1-5]$/.test(e.key)) { - e.preventDefault(); - const pages = ['home', 'vocabulary', 'dialogue', 'profile', 'settings']; - const pageIndex = parseInt(e.key) - 1; - if (pages[pageIndex]) { - this.navigateTo(pages[pageIndex]); - } - } - } - - /** - * 瀏覽器歷史記錄變化處理 - */ - handlePopState(e) { - const page = e.state?.page || 'home'; - this.navigateTo(page, false); // false = 不更新歷史記錄 - } - - /** - * 網路連線恢復 - */ - handleOnline() { - this.toast.show('網路已連線', '已恢復網路連接', 'success'); - this.syncPendingData(); - } - - /** - * 網路連線中斷 - */ - handleOffline() { - this.toast.show('網路已中斷', '請檢查網路連接,離線模式已啟用', 'warning'); - } - - /** - * 頁面可見性變化 - */ - handleVisibilityChange() { - if (document.hidden) { - // 頁面被隱藏時,保存當前狀態 - this.saveCurrentState(); - } else { - // 頁面重新可見時,檢查是否需要更新數據 - this.checkForUpdates(); - } - } - - /** - * 模組卡片點擊處理 - */ - handleModuleClick(e) { - const moduleCard = e.currentTarget; - const moduleType = moduleCard.dataset.module; - - switch (moduleType) { - case 'vocabulary': - this.navigateTo('vocabulary'); - break; - case 'dialogue': - this.navigateTo('dialogue'); - break; - default: - console.warn(`未知的模組類型: ${moduleType}`); - } - } - - /** - * 頁面導航 - */ - navigateTo(page, updateHistory = true) { - if (this.currentPage === page) return; - - // 隱藏當前頁面 - this.hidePage(this.currentPage); - - // 顯示新頁面 - this.showPage(page); - - // 更新當前頁面 - this.currentPage = page; - - // 更新選單狀態 - this.updateMenuState(page); - - // 更新瀏覽器歷史記錄 - if (updateHistory) { - const url = page === 'home' ? '/' : `/${page}`; - history.pushState({ page }, document.title, url); - } - - // 關閉側邊欄(手機版) - this.sidebar.close(); - - // 載入頁面數據 - this.loadPageData(page); - } - - /** - * 隱藏頁面 - */ - hidePage(page) { - const pageElement = document.getElementById(`${page}-view`); - if (pageElement) { - pageElement.classList.remove('active'); - } - } - - /** - * 顯示頁面 - */ - showPage(page) { - const pageElement = document.getElementById(`${page}-view`); - if (pageElement) { - pageElement.classList.add('active'); - } else { - // 頁面不存在,需要動態載入 - this.loadPageComponent(page); - } - } - - /** - * 更新選單狀態 - */ - updateMenuState(activePage) { - document.querySelectorAll('.menu-item').forEach(item => { - if (item.dataset.page === activePage) { - item.classList.add('active'); - } else { - item.classList.remove('active'); - } - }); - } - - /** - * 載入頁面數據 - */ - async loadPageData(page) { - try { - switch (page) { - case 'home': - await this.loadDashboardData(); - break; - case 'vocabulary': - await this.loadVocabularyData(); - break; - case 'dialogue': - await this.loadDialogueData(); - break; - case 'profile': - await this.loadProfileData(); - break; - default: - console.log(`頁面 ${page} 不需要載入額外數據`); - } - } catch (error) { - console.error(`載入頁面 ${page} 數據失敗:`, error); - this.toast.show('載入失敗', '頁面數據載入失敗', 'error'); - } - } - - /** - * 載入儀表板數據 - */ - async loadDashboardData() { - const stats = await this.api.getUserStats(); - this.state.updateStats(stats); - this.updateDashboardStats(stats); - } - - /** - * 更新儀表板統計 - */ - updateDashboardStats(stats) { - const elements = { - totalWords: document.getElementById('total-words'), - studyTime: document.getElementById('study-time'), - achievements: document.getElementById('achievements') - }; - - if (elements.totalWords) elements.totalWords.textContent = stats.totalWords || 0; - if (elements.studyTime) elements.studyTime.textContent = stats.studyTime || 0; - if (elements.achievements) elements.achievements.textContent = stats.achievements || 0; - } - - /** - * 載入詞彙數據 - */ - async loadVocabularyData() { - // 這將在詞彙模組中實現 - console.log('載入詞彙數據...'); - } - - /** - * 載入對話數據 - */ - async loadDialogueData() { - // 這將在對話模組中實現 - console.log('載入對話數據...'); - } - - /** - * 載入個人檔案數據 - */ - async loadProfileData() { - // 這將在個人檔案模組中實現 - console.log('載入個人檔案數據...'); - } - - /** - * 動態載入頁面組件 - */ - async loadPageComponent(page) { - try { - // 動態導入頁面模組 - const module = await import(`./pages/${page}.js`); - const PageClass = module.default; - - // 創建頁面實例 - const pageInstance = new PageClass(this); - await pageInstance.render(); - - } catch (error) { - console.error(`載入頁面組件 ${page} 失敗:`, error); - this.show404Page(); - } - } - - /** - * 顯示 404 頁面 - */ - show404Page() { - const mainContent = document.getElementById('main-content'); - if (mainContent) { - mainContent.innerHTML = ` -
-

404

-

找不到頁面

- -
- `; - } - } - - /** - * 載入用戶數據 - */ - async loadUserData() { - try { - const user = await this.api.getCurrentUser(); - this.state.setUser(user); - } catch (error) { - console.error('載入用戶數據失敗:', error); - } - } - - /** - * 顯示主應用 - */ - showMainApp() { - const app = document.getElementById('app'); - if (app) { - app.style.display = 'block'; - } - } - - /** - * 顯示認證頁面 - */ - showAuthPage() { - this.navigateTo('login'); - } - - /** - * 顯示載入畫面 - */ - showLoading() { - const loadingScreen = document.getElementById('app-loading'); - if (loadingScreen) { - loadingScreen.classList.remove('hidden'); - } - } - - /** - * 隱藏載入畫面 - */ - hideLoading() { - const loadingScreen = document.getElementById('app-loading'); - if (loadingScreen) { - setTimeout(() => { - loadingScreen.classList.add('hidden'); - }, 500); - } - } - - /** - * 開啟搜尋功能 - */ - openSearch() { - // TODO: 實現搜尋功能 - this.toast.show('搜尋', '搜尋功能開發中...', 'info'); - } - - /** - * 顯示快捷鍵說明 - */ - showShortcutsHelp() { - const helpContent = ` -
-

鍵盤快捷鍵

-
-
- Ctrl + K - 開啟搜尋 -
-
- Ctrl + / - 顯示快捷鍵說明 -
-
- Alt + 1-5 - 快速導航 -
-
- Esc - 關閉彈窗 -
-
-
- `; - - this.modal.show('快捷鍵說明', helpContent); - } - - /** - * 同步待處理數據 - */ - async syncPendingData() { - // TODO: 實現離線數據同步 - console.log('同步待處理數據...'); - } - - /** - * 保存當前狀態 - */ - saveCurrentState() { - const state = { - page: this.currentPage, - timestamp: Date.now() - }; - - localStorage.setItem('app-state', JSON.stringify(state)); - } - - /** - * 檢查更新 - */ - async checkForUpdates() { - // TODO: 檢查數據是否需要更新 - console.log('檢查更新...'); - } - - /** - * 登出 - */ - async logout() { - try { - await this.auth.logout(); - this.showAuthPage(); - this.toast.show('已登出', '您已成功登出', 'success'); - } catch (error) { - console.error('登出失敗:', error); - this.toast.show('登出失敗', '請重試', 'error'); - } - } -} - -// 等待 DOM 載入完成後初始化應用 -document.addEventListener('DOMContentLoaded', () => { - window.app = new DramaLingApp(); -}); - -// 導出應用類別供其他模組使用 -export default DramaLingApp; \ No newline at end of file diff --git a/apps/web-native/assets/js/utils.js b/apps/web-native/assets/js/utils.js deleted file mode 100644 index a57341c..0000000 --- a/apps/web-native/assets/js/utils.js +++ /dev/null @@ -1,663 +0,0 @@ -/** - * Drama Ling - 工具類別和狀態管理 - * 提供應用程序所需的基礎工具函數和狀態管理 - */ - -/** - * 應用狀態管理類別 - * 簡潔的響應式狀態管理,不依賴外部框架 - */ -export class AppState { - constructor() { - this.data = { - user: null, - isAuthenticated: false, - vocabulary: [], - currentSession: null, - stats: { - totalWords: 0, - studyTime: 0, - achievements: 0, - streak: 0, - diamonds: 0 - }, - settings: { - theme: 'auto', - language: 'zh-TW', - soundEnabled: true, - notificationsEnabled: true - }, - offline: false - }; - - this.listeners = new Map(); - - // 從本地存儲恢復狀態 - this.loadFromStorage(); - } - - /** - * 添加狀態變化監聽器 - */ - addListener(event, callback) { - if (!this.listeners.has(event)) { - this.listeners.set(event, []); - } - this.listeners.get(event).push(callback); - } - - /** - * 移除狀態變化監聽器 - */ - removeListener(event, callback) { - if (this.listeners.has(event)) { - const callbacks = this.listeners.get(event); - const index = callbacks.indexOf(callback); - if (index > -1) { - callbacks.splice(index, 1); - } - } - } - - /** - * 觸發狀態變化事件 - */ - emit(event, data) { - if (this.listeners.has(event)) { - this.listeners.get(event).forEach(callback => { - try { - callback(data); - } catch (error) { - console.error(`狀態事件監聽器錯誤 (${event}):`, error); - } - }); - } - } - - /** - * 設定用戶資料 - */ - setUser(user) { - this.data.user = user; - this.data.isAuthenticated = !!user; - this.saveToStorage(); - this.emit('userUpdated', user); - this.emit('authStatusChanged', !!user); - } - - /** - * 獲取用戶資料 - */ - getUser() { - return this.data.user; - } - - /** - * 檢查是否已認證 - */ - isAuthenticated() { - return this.data.isAuthenticated; - } - - /** - * 更新統計資料 - */ - updateStats(newStats) { - this.data.stats = { ...this.data.stats, ...newStats }; - this.saveToStorage(); - this.emit('statsUpdated', this.data.stats); - } - - /** - * 獲取統計資料 - */ - getStats() { - return this.data.stats; - } - - /** - * 更新設定 - */ - updateSettings(newSettings) { - this.data.settings = { ...this.data.settings, ...newSettings }; - this.saveToStorage(); - this.emit('settingsUpdated', this.data.settings); - } - - /** - * 獲取設定 - */ - getSettings() { - return this.data.settings; - } - - /** - * 設定離線狀態 - */ - setOffline(offline) { - this.data.offline = offline; - this.emit('offlineStatusChanged', offline); - } - - /** - * 檢查是否離線 - */ - isOffline() { - return this.data.offline; - } - - /** - * 保存狀態到本地存儲 - */ - saveToStorage() { - try { - const stateToSave = { - user: this.data.user, - settings: this.data.settings, - stats: this.data.stats - }; - localStorage.setItem('drama-ling-state', JSON.stringify(stateToSave)); - } catch (error) { - console.error('保存狀態失敗:', error); - } - } - - /** - * 從本地存儲載入狀態 - */ - loadFromStorage() { - try { - const savedState = localStorage.getItem('drama-ling-state'); - if (savedState) { - const parsedState = JSON.parse(savedState); - this.data.user = parsedState.user; - this.data.isAuthenticated = !!parsedState.user; - this.data.settings = { ...this.data.settings, ...parsedState.settings }; - this.data.stats = { ...this.data.stats, ...parsedState.stats }; - } - } catch (error) { - console.error('載入狀態失敗:', error); - } - } - - /** - * 清除所有狀態 - */ - clear() { - this.data = { - user: null, - isAuthenticated: false, - vocabulary: [], - currentSession: null, - stats: { - totalWords: 0, - studyTime: 0, - achievements: 0, - streak: 0, - diamonds: 0 - }, - settings: { - theme: 'auto', - language: 'zh-TW', - soundEnabled: true, - notificationsEnabled: true - }, - offline: false - }; - - localStorage.removeItem('drama-ling-state'); - this.emit('stateCleared'); - } -} - -/** - * 工具函數集合 - */ -export class Utils { - /** - * 防抖函數 - */ - static debounce(func, wait, immediate = false) { - let timeout; - return function executedFunction(...args) { - const later = () => { - timeout = null; - if (!immediate) func.apply(this, args); - }; - const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(this, args); - }; - } - - /** - * 節流函數 - */ - static throttle(func, limit) { - let inThrottle; - return function executedFunction(...args) { - if (!inThrottle) { - func.apply(this, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }; - } - - /** - * 深度複製對象 - */ - static deepClone(obj) { - if (obj === null || typeof obj !== 'object') { - return obj; - } - - if (obj instanceof Date) { - return new Date(obj.getTime()); - } - - if (obj instanceof Array) { - return obj.map(item => Utils.deepClone(item)); - } - - if (typeof obj === 'object') { - const clonedObj = {}; - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - clonedObj[key] = Utils.deepClone(obj[key]); - } - } - return clonedObj; - } - } - - /** - * 格式化時間 - */ - static formatTime(seconds) { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const remainingSeconds = seconds % 60; - - if (hours > 0) { - return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; - } else { - return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; - } - } - - /** - * 格式化數字 - */ - static formatNumber(num) { - if (num >= 1000000) { - return (num / 1000000).toFixed(1) + 'M'; - } else if (num >= 1000) { - return (num / 1000).toFixed(1) + 'K'; - } - return num.toString(); - } - - /** - * 格式化日期 - */ - static formatDate(date, locale = 'zh-TW') { - if (!(date instanceof Date)) { - date = new Date(date); - } - - return date.toLocaleDateString(locale, { - year: 'numeric', - month: 'long', - day: 'numeric' - }); - } - - /** - * 獲取相對時間 - */ - static getRelativeTime(date, locale = 'zh-TW') { - const now = new Date(); - const targetDate = new Date(date); - const diffInSeconds = Math.floor((now - targetDate) / 1000); - - const intervals = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60 - }; - - for (const [unit, seconds] of Object.entries(intervals)) { - const interval = Math.floor(diffInSeconds / seconds); - if (interval >= 1) { - const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); - return rtf.format(-interval, unit); - } - } - - return '剛剛'; - } - - /** - * 產生唯一 ID - */ - static generateId() { - return Date.now().toString(36) + Math.random().toString(36).substr(2); - } - - /** - * 驗證電子郵件格式 - */ - static validateEmail(email) { - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return re.test(email); - } - - /** - * 驗證密碼強度 - */ - static validatePassword(password) { - const minLength = 8; - const hasUpperCase = /[A-Z]/.test(password); - const hasLowerCase = /[a-z]/.test(password); - const hasNumbers = /\d/.test(password); - const hasNonalphas = /\W/.test(password); - - return { - isValid: password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers, - strength: [hasUpperCase, hasLowerCase, hasNumbers, hasNonalphas].filter(Boolean).length, - minLength: password.length >= minLength, - hasUpperCase, - hasLowerCase, - hasNumbers, - hasSpecialChars: hasNonalphas - }; - } - - /** - * 本地存儲輔助函數 - */ - static storage = { - set(key, value) { - try { - localStorage.setItem(key, JSON.stringify(value)); - return true; - } catch (error) { - console.error('本地存儲設置失敗:', error); - return false; - } - }, - - get(key, defaultValue = null) { - try { - const item = localStorage.getItem(key); - return item ? JSON.parse(item) : defaultValue; - } catch (error) { - console.error('本地存儲讀取失敗:', error); - return defaultValue; - } - }, - - remove(key) { - try { - localStorage.removeItem(key); - return true; - } catch (error) { - console.error('本地存儲刪除失敗:', error); - return false; - } - }, - - clear() { - try { - localStorage.clear(); - return true; - } catch (error) { - console.error('本地存儲清除失敗:', error); - return false; - } - } - }; - - /** - * URL 參數處理 - */ - static url = { - getParams() { - return new URLSearchParams(window.location.search); - }, - - getParam(key, defaultValue = null) { - const params = new URLSearchParams(window.location.search); - return params.get(key) || defaultValue; - }, - - setParam(key, value) { - const url = new URL(window.location); - url.searchParams.set(key, value); - window.history.pushState({}, '', url); - }, - - removeParam(key) { - const url = new URL(window.location); - url.searchParams.delete(key); - window.history.pushState({}, '', url); - } - }; - - /** - * DOM 輔助函數 - */ - static dom = { - /** - * 創建元素 - */ - create(tagName, attributes = {}, textContent = '') { - const element = document.createElement(tagName); - - for (const [key, value] of Object.entries(attributes)) { - if (key === 'className') { - element.className = value; - } else if (key.startsWith('data-')) { - element.setAttribute(key, value); - } else { - element[key] = value; - } - } - - if (textContent) { - element.textContent = textContent; - } - - return element; - }, - - /** - * 查詢元素 - */ - $(selector, context = document) { - return context.querySelector(selector); - }, - - /** - * 查詢所有元素 - */ - $$(selector, context = document) { - return Array.from(context.querySelectorAll(selector)); - }, - - /** - * 添加事件監聽器 - */ - on(element, event, handler, options = false) { - element.addEventListener(event, handler, options); - }, - - /** - * 移除事件監聽器 - */ - off(element, event, handler, options = false) { - element.removeEventListener(event, handler, options); - }, - - /** - * 添加類別 - */ - addClass(element, className) { - element.classList.add(className); - }, - - /** - * 移除類別 - */ - removeClass(element, className) { - element.classList.remove(className); - }, - - /** - * 切換類別 - */ - toggleClass(element, className) { - element.classList.toggle(className); - }, - - /** - * 檢查是否有類別 - */ - hasClass(element, className) { - return element.classList.contains(className); - } - }; - - /** - * 數學輔助函數 - */ - static math = { - /** - * 限制數值範圍 - */ - clamp(value, min, max) { - return Math.min(Math.max(value, min), max); - }, - - /** - * 線性插值 - */ - lerp(start, end, factor) { - return start + (end - start) * factor; - }, - - /** - * 隨機整數 - */ - randomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - }, - - /** - * 隨機浮點數 - */ - randomFloat(min, max) { - return Math.random() * (max - min) + min; - }, - - /** - * 百分比計算 - */ - percentage(value, total) { - return total > 0 ? (value / total) * 100 : 0; - } - }; - - /** - * 顏色輔助函數 - */ - static color = { - /** - * 十六進制轉 RGB - */ - hexToRgb(hex) { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null; - }, - - /** - * RGB 轉十六進制 - */ - rgbToHex(r, g, b) { - return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - }, - - /** - * 生成隨機顏色 - */ - random() { - return '#' + Math.floor(Math.random() * 16777215).toString(16); - } - }; -} - -/** - * 事件匯流排 - * 用於組件間通信 - */ -export class EventBus { - constructor() { - this.events = {}; - } - - /** - * 監聽事件 - */ - on(event, callback) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(callback); - } - - /** - * 觸發事件 - */ - emit(event, data) { - if (this.events[event]) { - this.events[event].forEach(callback => { - try { - callback(data); - } catch (error) { - console.error(`事件處理器錯誤 (${event}):`, error); - } - }); - } - } - - /** - * 移除事件監聽器 - */ - off(event, callback) { - if (this.events[event]) { - const index = this.events[event].indexOf(callback); - if (index > -1) { - this.events[event].splice(index, 1); - } - } - } - - /** - * 清除所有事件監聽器 - */ - clear() { - this.events = {}; - } -} - -// 創建全域事件匯流排實例 -export const eventBus = new EventBus(); \ No newline at end of file diff --git a/apps/web-native/data/mockData.js b/apps/web-native/data/mockData.js deleted file mode 100644 index 1bfec02..0000000 --- a/apps/web-native/data/mockData.js +++ /dev/null @@ -1,520 +0,0 @@ -/** - * Drama Ling - 模擬數據 - * 開發和測試用的模擬數據集 - */ - -/** - * 模擬用戶數據 - */ -export const mockUsers = [ - { - id: 'user_001', - email: 'demo@dramaling.com', - name: '張小明', - avatar: '/assets/media/images/avatar-1.png', - level: '中級', - joinDate: '2024-01-15', - preferences: { - language: 'zh-TW', - theme: 'auto', - soundEnabled: true, - notificationsEnabled: true - } - }, - { - id: 'user_002', - email: 'sarah@example.com', - name: 'Sarah Chen', - avatar: '/assets/media/images/avatar-2.png', - level: '高級', - joinDate: '2023-11-20', - preferences: { - language: 'zh-TW', - theme: 'light', - soundEnabled: false, - notificationsEnabled: true - } - } -]; - -/** - * 模擬用戶統計數據 - */ -export const mockUserStats = { - totalWords: 1247, - studyTime: 45, // 小時 - achievements: 12, - streak: 7, // 連續學習天數 - diamonds: 2580, - weeklyGoal: 5, // 每週學習時數目標 - weeklyProgress: 3.5, // 本週已完成時數 - levelProgress: 78, // 等級進度百分比 - accuracy: 85, // 整體答題準確率 - strongCategories: ['商務', '旅遊', '日常對話'], - weakCategories: ['科技', '醫療'] -}; - -/** - * 模擬詞彙數據 - */ -export const mockVocabulary = [ - { - id: 'vocab_001', - word: 'appreciate', - definition: '感激;欣賞;理解', - pronunciation: '/əˈpriːʃieɪt/', - difficulty: 'intermediate', - category: 'business', - examples: [ - { - english: 'I appreciate your hard work.', - chinese: '我很感激你的辛勤工作。' - }, - { - english: 'Do you appreciate classical music?', - chinese: '你欣賞古典音樂嗎?' - } - ], - audioUrl: '/assets/media/audio/appreciate.mp3', - imageUrl: '/assets/media/images/appreciate.jpg', - tags: ['emotion', 'business', 'common'], - addedDate: '2024-09-01', - masteryLevel: 0.8, // 0-1,掌握程度 - reviewCount: 5, - correctCount: 4, - lastReviewed: '2024-09-09' - }, - { - id: 'vocab_002', - word: 'definitely', - definition: '當然;肯定地', - pronunciation: '/ˈdefɪnətli/', - difficulty: 'beginner', - category: 'daily', - examples: [ - { - english: 'I will definitely come to your party.', - chinese: '我一定會參加你的派對。' - }, - { - english: 'That was definitely the best movie I\'ve seen.', - chinese: '那絕對是我看過最好的電影。' - } - ], - audioUrl: '/assets/media/audio/definitely.mp3', - imageUrl: '/assets/media/images/definitely.jpg', - tags: ['adverb', 'confirmation', 'common'], - addedDate: '2024-08-28', - masteryLevel: 0.95, - reviewCount: 8, - correctCount: 8, - lastReviewed: '2024-09-08' - }, - { - id: 'vocab_003', - word: 'entrepreneur', - definition: '企業家;創業者', - pronunciation: '/ˌɒntrəprəˈnɜː(r)/', - difficulty: 'advanced', - category: 'business', - examples: [ - { - english: 'She is a successful entrepreneur.', - chinese: '她是一位成功的企業家。' - }, - { - english: 'Many entrepreneurs start with small businesses.', - chinese: '許多企業家都是從小企業開始的。' - } - ], - audioUrl: '/assets/media/audio/entrepreneur.mp3', - imageUrl: '/assets/media/images/entrepreneur.jpg', - tags: ['business', 'career', 'advanced'], - addedDate: '2024-09-05', - masteryLevel: 0.4, - reviewCount: 2, - correctCount: 1, - lastReviewed: '2024-09-07' - } -]; - -/** - * 模擬對話場景數據 - */ -export const mockDialogues = [ - { - id: 'dialogue_001', - title: '餐廳點餐', - category: 'restaurant', - difficulty: 'beginner', - description: '在餐廳與服務生的基本對話練習', - estimatedTime: 5, // 分鐘 - thumbnail: '/assets/media/images/restaurant-scene.jpg', - characters: [ - { - id: 'customer', - name: '顧客', - avatar: '/assets/media/images/customer-avatar.png' - }, - { - id: 'waiter', - name: '服務生', - avatar: '/assets/media/images/waiter-avatar.png', - isAI: true - } - ], - conversation: [ - { - speaker: 'waiter', - text: 'Good evening! Welcome to our restaurant. How many people?', - chinese: '晚安!歡迎來到我們餐廳。請問幾位?', - audioUrl: '/assets/media/audio/dialogue_001_01.mp3' - }, - { - speaker: 'customer', - text: 'Table for two, please.', - chinese: '請給我們兩人桌。', - audioUrl: '/assets/media/audio/dialogue_001_02.mp3', - isUserResponse: true, - alternatives: [ - 'Two people, please.', - 'A table for two.', - 'Party of two.' - ] - }, - { - speaker: 'waiter', - text: 'Perfect! Right this way, please. Here are your menus.', - chinese: '很好!請這邊走。這是菜單。', - audioUrl: '/assets/media/audio/dialogue_001_03.mp3' - } - ], - completedBy: 1250, // 完成人數 - rating: 4.8, - tags: ['restaurant', 'basic', 'ordering'] - }, - { - id: 'dialogue_002', - title: '機場報到', - category: 'travel', - difficulty: 'intermediate', - description: '在機場辦理登機手續的對話', - estimatedTime: 8, - thumbnail: '/assets/media/images/airport-scene.jpg', - characters: [ - { - id: 'passenger', - name: '乘客', - avatar: '/assets/media/images/passenger-avatar.png' - }, - { - id: 'checkin_agent', - name: '報到人員', - avatar: '/assets/media/images/agent-avatar.png', - isAI: true - } - ], - conversation: [ - { - speaker: 'checkin_agent', - text: 'Good morning! May I have your passport and ticket, please?', - chinese: '早安!請出示您的護照和機票。', - audioUrl: '/assets/media/audio/dialogue_002_01.mp3' - }, - { - speaker: 'passenger', - text: 'Here you go.', - chinese: '給您。', - audioUrl: '/assets/media/audio/dialogue_002_02.mp3', - isUserResponse: true, - alternatives: [ - 'Here they are.', - 'Sure, here you are.', - 'Of course.' - ] - } - ], - completedBy: 890, - rating: 4.6, - tags: ['travel', 'airport', 'procedures'] - } -]; - -/** - * 模擬學習進度數據 - */ -export const mockLearningProgress = { - dailyStats: [ - { date: '2024-09-03', wordsLearned: 15, timeSpent: 25, accuracy: 82 }, - { date: '2024-09-04', wordsLearned: 12, timeSpent: 30, accuracy: 88 }, - { date: '2024-09-05', wordsLearned: 18, timeSpent: 35, accuracy: 75 }, - { date: '2024-09-06', wordsLearned: 20, timeSpent: 40, accuracy: 90 }, - { date: '2024-09-07', wordsLearned: 16, timeSpent: 28, accuracy: 85 }, - { date: '2024-09-08', wordsLearned: 14, timeSpent: 32, accuracy: 87 }, - { date: '2024-09-09', wordsLearned: 22, timeSpent: 45, accuracy: 92 } - ], - weeklyGoals: { - wordsTarget: 100, - timeTarget: 5 * 60, // 5小時,以分鐘為單位 - currentWords: 117, - currentTime: 235 // 分鐘 - }, - categoryProgress: { - business: { total: 150, mastered: 120, learning: 25, difficult: 5 }, - daily: { total: 200, mastered: 180, learning: 15, difficult: 5 }, - travel: { total: 80, mastered: 60, learning: 15, difficult: 5 }, - technology: { total: 100, mastered: 45, learning: 30, difficult: 25 } - }, - achievements: [ - { - id: 'first_week', - title: '初次體驗', - description: '完成第一週學習', - icon: '🎯', - unlockedDate: '2024-01-22', - rarity: 'common' - }, - { - id: 'streak_7', - title: '七日連勝', - description: '連續學習7天', - icon: '🔥', - unlockedDate: '2024-02-01', - rarity: 'rare' - }, - { - id: 'words_1000', - title: '詞彙大師', - description: '學習1000個詞彙', - icon: '📚', - unlockedDate: '2024-08-15', - rarity: 'epic' - } - ] -}; - -/** - * 模擬系統設定數據 - */ -export const mockSystemSettings = { - themes: [ - { id: 'auto', name: '自動', description: '跟隨系統設定' }, - { id: 'light', name: '淺色', description: '淺色主題' }, - { id: 'dark', name: '深色', description: '深色主題' } - ], - languages: [ - { id: 'zh-TW', name: '繁體中文', flag: '🇹🇼' }, - { id: 'zh-CN', name: '简体中文', flag: '🇨🇳' }, - { id: 'en-US', name: 'English', flag: '🇺🇸' }, - { id: 'ja-JP', name: '日本語', flag: '🇯🇵' } - ], - notificationTypes: [ - { id: 'daily_reminder', name: '每日提醒', description: '提醒您每天學習' }, - { id: 'streak_warning', name: '連勝警告', description: '連勝即將中斷時提醒' }, - { id: 'achievement', name: '成就通知', description: '獲得新成就時通知' }, - { id: 'weekly_report', name: '週報', description: '每週學習報告' } - ] -}; - -/** - * 模擬 API 響應數據 - */ -export const mockApiResponses = { - login: { - success: { - token: 'mock_jwt_token_12345', - user: mockUsers[0], - expiresIn: 86400 // 24小時 - }, - error: { - message: '帳號或密碼錯誤', - code: 'INVALID_CREDENTIALS' - } - }, - - getUserStats: { - success: mockUserStats, - error: { - message: '無法載入用戶統計', - code: 'STATS_LOAD_ERROR' - } - }, - - getVocabulary: { - success: { - data: mockVocabulary, - total: mockVocabulary.length, - page: 1, - limit: 20 - }, - error: { - message: '無法載入詞彙數據', - code: 'VOCABULARY_LOAD_ERROR' - } - }, - - getDialogues: { - success: { - data: mockDialogues, - total: mockDialogues.length, - page: 1, - limit: 10 - }, - error: { - message: '無法載入對話數據', - code: 'DIALOGUE_LOAD_ERROR' - } - } -}; - -/** - * 模擬 API 客戶端 - * 用於開發環境的 API 模擬 - */ -export class MockApiClient { - constructor() { - this.delay = 300; // 模擬網路延遲 - this.failureRate = 0.1; // 10% 機率失敗 - } - - /** - * 模擬 API 延遲 - */ - async simulateDelay() { - return new Promise(resolve => { - setTimeout(resolve, this.delay); - }); - } - - /** - * 模擬隨機失敗 - */ - simulateRandomFailure() { - return Math.random() < this.failureRate; - } - - /** - * 用戶登入 - */ - async login(email, password) { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error(mockApiResponses.login.error.message); - } - - // 簡單的驗證邏輯 - if (email === 'demo@dramaling.com' && password === 'password') { - return mockApiResponses.login.success; - } else { - throw new Error(mockApiResponses.login.error.message); - } - } - - /** - * 獲取用戶統計 - */ - async getUserStats() { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error(mockApiResponses.getUserStats.error.message); - } - - return mockApiResponses.getUserStats.success; - } - - /** - * 獲取詞彙列表 - */ - async getVocabulary(params = {}) { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error(mockApiResponses.getVocabulary.error.message); - } - - let filteredVocabulary = [...mockVocabulary]; - - // 模擬過濾和排序 - if (params.category) { - filteredVocabulary = filteredVocabulary.filter( - vocab => vocab.category === params.category - ); - } - - if (params.difficulty) { - filteredVocabulary = filteredVocabulary.filter( - vocab => vocab.difficulty === params.difficulty - ); - } - - return { - data: filteredVocabulary, - total: filteredVocabulary.length, - page: params.page || 1, - limit: params.limit || 20 - }; - } - - /** - * 獲取對話場景 - */ - async getDialogues(params = {}) { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error(mockApiResponses.getDialogues.error.message); - } - - let filteredDialogues = [...mockDialogues]; - - if (params.category) { - filteredDialogues = filteredDialogues.filter( - dialogue => dialogue.category === params.category - ); - } - - if (params.difficulty) { - filteredDialogues = filteredDialogues.filter( - dialogue => dialogue.difficulty === params.difficulty - ); - } - - return { - data: filteredDialogues, - total: filteredDialogues.length, - page: params.page || 1, - limit: params.limit || 10 - }; - } - - /** - * 獲取學習進度 - */ - async getLearningProgress() { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error('無法載入學習進度'); - } - - return mockLearningProgress; - } - - /** - * 獲取當前用戶 - */ - async getCurrentUser() { - await this.simulateDelay(); - - if (this.simulateRandomFailure()) { - throw new Error('無法載入用戶資料'); - } - - return mockUsers[0]; - } -} - -// 導出模擬 API 客戶端實例 -export const mockApi = new MockApiClient(); \ No newline at end of file diff --git a/apps/web-native/index.html b/apps/web-native/index.html deleted file mode 100644 index b1fb8dc..0000000 --- a/apps/web-native/index.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - Drama Ling - 情境式語言學習 - - - - - - - - - - - - - - - - - - -
- -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js deleted file mode 100644 index 92f5d6d..0000000 --- a/apps/web/.eslintrc.js +++ /dev/null @@ -1,51 +0,0 @@ -module.exports = { - root: true, - env: { - node: true, - browser: true, - es2021: true - }, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/typescript/recommended', - '@vue/prettier' - ], - parser: 'vue-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - ecmaVersion: 2021, - sourceType: 'module' - }, - plugins: ['@typescript-eslint'], - rules: { - // Vue規則 - 'vue/multi-word-component-names': 'off', - 'vue/no-unused-vars': 'error', - 'vue/component-definition-name-casing': ['error', 'PascalCase'], - 'vue/component-name-in-template-casing': ['error', 'PascalCase'], - - // TypeScript規則 - '@typescript-eslint/no-unused-vars': ['error', { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_' - }], - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - - // 一般規則 - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'prefer-const': 'error', - 'no-var': 'error', - 'object-shorthand': 'error', - 'prefer-template': 'error' - }, - globals: { - defineProps: 'readonly', - defineEmits: 'readonly', - defineExpose: 'readonly', - withDefaults: 'readonly' - } -} \ No newline at end of file diff --git a/apps/web/.prettierrc b/apps/web/.prettierrc deleted file mode 100644 index 0dc9e93..0000000 --- a/apps/web/.prettierrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "semi": false, - "singleQuote": true, - "printWidth": 100, - "tabWidth": 2, - "useTabs": false, - "trailingComma": "none", - "bracketSpacing": true, - "bracketSameLine": false, - "arrowParens": "avoid", - "endOfLine": "lf", - "vueIndentScriptAndStyle": false, - "htmlWhitespaceSensitivity": "ignore" -} \ No newline at end of file diff --git a/apps/web/auto-imports.d.ts b/apps/web/auto-imports.d.ts deleted file mode 100644 index 3bfea76..0000000 --- a/apps/web/auto-imports.d.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// noinspection JSUnusedGlobalSymbols -// Generated by unplugin-auto-import -export {} -declare global { - const $q: typeof import('quasar')['$q'] - const Dialog: typeof import('quasar')['Dialog'] - const EffectScope: typeof import('vue')['EffectScope'] - const Loading: typeof import('quasar')['Loading'] - const Notify: typeof import('quasar')['Notify'] - const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] - const computed: typeof import('vue')['computed'] - const createApp: typeof import('vue')['createApp'] - const createPinia: typeof import('pinia')['createPinia'] - const customRef: typeof import('vue')['customRef'] - const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] - const defineComponent: typeof import('vue')['defineComponent'] - const defineStore: typeof import('pinia')['defineStore'] - const effectScope: typeof import('vue')['effectScope'] - const getActivePinia: typeof import('pinia')['getActivePinia'] - const getCurrentInstance: typeof import('vue')['getCurrentInstance'] - const getCurrentScope: typeof import('vue')['getCurrentScope'] - const h: typeof import('vue')['h'] - const inject: typeof import('vue')['inject'] - const isProxy: typeof import('vue')['isProxy'] - const isReactive: typeof import('vue')['isReactive'] - const isReadonly: typeof import('vue')['isReadonly'] - const isRef: typeof import('vue')['isRef'] - const mapActions: typeof import('pinia')['mapActions'] - const mapGetters: typeof import('pinia')['mapGetters'] - const mapState: typeof import('pinia')['mapState'] - const mapStores: typeof import('pinia')['mapStores'] - const mapWritableState: typeof import('pinia')['mapWritableState'] - const markRaw: typeof import('vue')['markRaw'] - const nextTick: typeof import('vue')['nextTick'] - const onActivated: typeof import('vue')['onActivated'] - const onBeforeMount: typeof import('vue')['onBeforeMount'] - const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] - const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] - const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] - const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] - const onDeactivated: typeof import('vue')['onDeactivated'] - const onErrorCaptured: typeof import('vue')['onErrorCaptured'] - const onMounted: typeof import('vue')['onMounted'] - const onRenderTracked: typeof import('vue')['onRenderTracked'] - const onRenderTriggered: typeof import('vue')['onRenderTriggered'] - const onScopeDispose: typeof import('vue')['onScopeDispose'] - const onServerPrefetch: typeof import('vue')['onServerPrefetch'] - const onUnmounted: typeof import('vue')['onUnmounted'] - const onUpdated: typeof import('vue')['onUpdated'] - const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] - const pinia: typeof import('./src/stores/index')['pinia'] - const provide: typeof import('vue')['provide'] - const reactive: typeof import('vue')['reactive'] - const readonly: typeof import('vue')['readonly'] - const ref: typeof import('vue')['ref'] - const resolveComponent: typeof import('vue')['resolveComponent'] - const setActivePinia: typeof import('pinia')['setActivePinia'] - const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] - const shallowReactive: typeof import('vue')['shallowReactive'] - const shallowReadonly: typeof import('vue')['shallowReadonly'] - const shallowRef: typeof import('vue')['shallowRef'] - const storeToRefs: typeof import('pinia')['storeToRefs'] - const toRaw: typeof import('vue')['toRaw'] - const toRef: typeof import('vue')['toRef'] - const toRefs: typeof import('vue')['toRefs'] - const toValue: typeof import('vue')['toValue'] - const triggerRef: typeof import('vue')['triggerRef'] - const unref: typeof import('vue')['unref'] - const useAttrs: typeof import('vue')['useAttrs'] - const useAudio: typeof import('./src/composables/useAudio')['useAudio'] - const useAuthStore: typeof import('./src/stores/auth')['useAuthStore'] - const useBrowserBookmarks: typeof import('./src/composables/useBrowserBookmarks')['useBrowserBookmarks'] - const useCssModule: typeof import('vue')['useCssModule'] - const useCssVars: typeof import('vue')['useCssVars'] - const useFetch: typeof import('@vueuse/core')['useFetch'] - const useId: typeof import('vue')['useId'] - const useKeyboard: typeof import('./src/composables/useKeyboard')['useKeyboard'] - const useKeyboardShortcuts: typeof import('./src/composables/useKeyboardShortcuts')['useKeyboardShortcuts'] - const useLearningStore: typeof import('./src/stores/learning')['useLearningStore'] - const useLink: typeof import('vue-router')['useLink'] - const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] - const useModel: typeof import('vue')['useModel'] - const useMultiTabLearning: typeof import('./src/composables/useMultiTabLearning')['useMultiTabLearning'] - const usePracticeStore: typeof import('./src/stores/practice')['usePracticeStore'] - const useQuasar: typeof import('quasar')['useQuasar'] - const useReviewStore: typeof import('./src/stores/review')['useReviewStore'] - const useRoute: typeof import('vue-router')['useRoute'] - const useRouter: typeof import('vue-router')['useRouter'] - const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] - const useSlots: typeof import('vue')['useSlots'] - const useTemplateRef: typeof import('vue')['useTemplateRef'] - const useUIStore: typeof import('./src/stores/ui')['useUIStore'] - const useUserStore: typeof import('./src/stores/user')['useUserStore'] - const useVocabularyStore: typeof import('./src/stores/vocabulary')['useVocabularyStore'] - const watch: typeof import('vue')['watch'] - const watchEffect: typeof import('vue')['watchEffect'] - const watchPostEffect: typeof import('vue')['watchPostEffect'] - const watchSyncEffect: typeof import('vue')['watchSyncEffect'] -} -// for type re-export -declare global { - // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' - import('vue') -} -// for vue template auto import -import { UnwrapRef } from 'vue' -declare module 'vue' { - interface GlobalComponents {} - interface ComponentCustomProperties { - readonly $q: UnwrapRef - readonly Dialog: UnwrapRef - readonly EffectScope: UnwrapRef - readonly Loading: UnwrapRef - readonly Notify: UnwrapRef - readonly acceptHMRUpdate: UnwrapRef - readonly computed: UnwrapRef - readonly createApp: UnwrapRef - readonly createPinia: UnwrapRef - readonly customRef: UnwrapRef - readonly defineAsyncComponent: UnwrapRef - readonly defineComponent: UnwrapRef - readonly defineStore: UnwrapRef - readonly effectScope: UnwrapRef - readonly getActivePinia: UnwrapRef - readonly getCurrentInstance: UnwrapRef - readonly getCurrentScope: UnwrapRef - readonly h: UnwrapRef - readonly inject: UnwrapRef - readonly isProxy: UnwrapRef - readonly isReactive: UnwrapRef - readonly isReadonly: UnwrapRef - readonly isRef: UnwrapRef - readonly mapActions: UnwrapRef - readonly mapGetters: UnwrapRef - readonly mapState: UnwrapRef - readonly mapStores: UnwrapRef - readonly mapWritableState: UnwrapRef - readonly markRaw: UnwrapRef - readonly nextTick: UnwrapRef - readonly onActivated: UnwrapRef - readonly onBeforeMount: UnwrapRef - readonly onBeforeRouteLeave: UnwrapRef - readonly onBeforeRouteUpdate: UnwrapRef - readonly onBeforeUnmount: UnwrapRef - readonly onBeforeUpdate: UnwrapRef - readonly onDeactivated: UnwrapRef - readonly onErrorCaptured: UnwrapRef - readonly onMounted: UnwrapRef - readonly onRenderTracked: UnwrapRef - readonly onRenderTriggered: UnwrapRef - readonly onScopeDispose: UnwrapRef - readonly onServerPrefetch: UnwrapRef - readonly onUnmounted: UnwrapRef - readonly onUpdated: UnwrapRef - readonly onWatcherCleanup: UnwrapRef - readonly pinia: UnwrapRef - readonly provide: UnwrapRef - readonly reactive: UnwrapRef - readonly readonly: UnwrapRef - readonly ref: UnwrapRef - readonly resolveComponent: UnwrapRef - readonly setActivePinia: UnwrapRef - readonly setMapStoreSuffix: UnwrapRef - readonly shallowReactive: UnwrapRef - readonly shallowReadonly: UnwrapRef - readonly shallowRef: UnwrapRef - readonly storeToRefs: UnwrapRef - readonly toRaw: UnwrapRef - readonly toRef: UnwrapRef - readonly toRefs: UnwrapRef - readonly toValue: UnwrapRef - readonly triggerRef: UnwrapRef - readonly unref: UnwrapRef - readonly useAttrs: UnwrapRef - readonly useAudio: UnwrapRef - readonly useAuthStore: UnwrapRef - readonly useBrowserBookmarks: UnwrapRef - readonly useCssModule: UnwrapRef - readonly useCssVars: UnwrapRef - readonly useFetch: UnwrapRef - readonly useId: UnwrapRef - readonly useKeyboard: UnwrapRef - readonly useKeyboardShortcuts: UnwrapRef - readonly useLearningStore: UnwrapRef - readonly useLink: UnwrapRef - readonly useLocalStorage: UnwrapRef - readonly useModel: UnwrapRef - readonly useMultiTabLearning: UnwrapRef - readonly usePracticeStore: UnwrapRef - readonly useQuasar: UnwrapRef - readonly useReviewStore: UnwrapRef - readonly useRoute: UnwrapRef - readonly useRouter: UnwrapRef - readonly useSessionStorage: UnwrapRef - readonly useSlots: UnwrapRef - readonly useTemplateRef: UnwrapRef - readonly useUIStore: UnwrapRef - readonly useUserStore: UnwrapRef - readonly useVocabularyStore: UnwrapRef - readonly watch: UnwrapRef - readonly watchEffect: UnwrapRef - readonly watchPostEffect: UnwrapRef - readonly watchSyncEffect: UnwrapRef - } -} diff --git a/apps/web/components.d.ts b/apps/web/components.d.ts deleted file mode 100644 index 1f3c476..0000000 --- a/apps/web/components.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable */ -// @ts-nocheck -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - BaseButton: typeof import('./src/components/base/BaseButton.vue')['default'] - BaseCard: typeof import('./src/components/base/BaseCard.vue')['default'] - BaseInput: typeof import('./src/components/base/BaseInput.vue')['default'] - BaseModal: typeof import('./src/components/base/BaseModal.vue')['default'] - ErrorHeatmap: typeof import('./src/components/dashboard/ErrorHeatmap.vue')['default'] - Icon: typeof import('./src/components/ui/Icon.vue')['default'] - ModalContainer: typeof import('./src/components/ui/ModalContainer.vue')['default'] - PWAInstallPrompt: typeof import('./src/components/PWAInstallPrompt.vue')['default'] - QBadge: typeof import('quasar')['QBadge'] - QBreadcrumbs: typeof import('quasar')['QBreadcrumbs'] - QBreadcrumbsEl: typeof import('quasar')['QBreadcrumbsEl'] - QBtn: typeof import('quasar')['QBtn'] - QCheckbox: typeof import('quasar')['QCheckbox'] - QIcon: typeof import('quasar')['QIcon'] - QSpinner: typeof import('quasar')['QSpinner'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - StatCard: typeof import('./src/components/dashboard/StatCard.vue')['default'] - ToastContainer: typeof import('./src/components/ui/ToastContainer.vue')['default'] - VocabularyCard: typeof import('./src/components/business/VocabularyCard.vue')['default'] - } -} diff --git a/apps/web/dev-dist/registerSW.js b/apps/web/dev-dist/registerSW.js deleted file mode 100644 index 1d5625f..0000000 --- a/apps/web/dev-dist/registerSW.js +++ /dev/null @@ -1 +0,0 @@ -if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' }) \ No newline at end of file diff --git a/apps/web/index.html b/apps/web/index.html index c4b0ef4..623939a 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -1,103 +1,17 @@ - - - Drama Ling - AI語言學習 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + Drama Ling - 詞彙學習 + -
- - - - - -
- -
Drama Ling 載入中...
-
- - +
+
+ 載入中... +
+
+ \ No newline at end of file diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index 1ff3706..54dce53 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -1,102 +1,18 @@ { - "name": "dramaling-web", + "name": "web-native", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "dramaling-web", + "name": "web-native", "version": "1.0.0", - "dependencies": { - "@quasar/extras": "^1.16.4", - "@vueuse/core": "^10.9.0", - "axios": "^1.6.8", - "chart.js": "^4.5.0", - "dayjs": "^1.11.10", - "dompurify": "^3.0.11", - "jspdf": "^3.0.2", - "lodash-es": "^4.17.21", - "pinia": "^2.1.7", - "pinia-plugin-persistedstate": "^3.2.1", - "quasar": "^2.16.0", - "vee-validate": "^4.12.6", - "vue": "^3.4.21", - "vue-chartjs": "^5.3.2", - "vue-router": "^4.3.0", - "workbox-window": "^7.0.0", - "xlsx": "^0.18.5", - "yup": "^1.4.0" - }, + "license": "ISC", "devDependencies": { - "@quasar/vite-plugin": "^1.6.0", - "@types/dompurify": "^3.0.5", - "@types/lodash-es": "^4.17.12", - "@types/node": "^20.12.7", - "@vitejs/plugin-vue": "^5.0.4", - "@vitest/coverage-v8": "^1.5.0", - "@vitest/ui": "^1.5.0", - "@vue/eslint-config-prettier": "^9.0.0", - "@vue/eslint-config-typescript": "^13.0.0", - "@vue/test-utils": "^2.4.5", - "cypress": "^13.7.2", - "eslint": "^9.1.1", - "happy-dom": "^14.7.1", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", - "prettier": "^3.2.5", - "sass": "^1.77.0", - "stylelint": "^16.4.0", - "stylelint-config-standard-scss": "^13.1.0", - "stylelint-config-standard-vue": "^1.0.0", - "typescript": "^5.4.0", - "unplugin-auto-import": "^0.17.5", - "unplugin-vue-components": "^0.27.0", - "vite": "^5.2.0", - "vite-plugin-pwa": "^0.20.0", - "vitest": "^1.5.0", - "vue-tsc": "^2.0.6" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@antfu/utils": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" + "@vitejs/plugin-legacy": "^7.2.1", + "sass": "^1.92.1", + "terser": "^5.44.0", + "vite": "^7.1.5" } }, "node_modules/@babel/code-frame": { @@ -114,13 +30,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/compat-data": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", @@ -162,16 +71,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", @@ -219,26 +118,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", @@ -261,16 +140,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", @@ -289,16 +158,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", @@ -449,6 +308,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -458,6 +318,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -506,6 +367,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -1604,16 +1466,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -1629,15 +1481,6 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -1676,6 +1519,7 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1685,180 +1529,10 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.4", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.14.0", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@dual-bundle/import-meta-resolve": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", - "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/JounQin" - } - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -1869,13 +1543,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -1886,13 +1560,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -1903,13 +1577,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -1920,13 +1594,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -1937,13 +1611,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -1954,13 +1628,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -1971,13 +1645,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -1988,13 +1662,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -2005,13 +1679,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -2022,13 +1696,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -2039,13 +1713,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -2056,13 +1730,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -2073,13 +1747,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -2090,13 +1764,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -2107,13 +1781,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -2124,13 +1798,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -2141,13 +1815,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -2158,13 +1849,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -2175,13 +1883,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -2192,13 +1917,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -2209,13 +1934,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -2226,13 +1951,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -2243,330 +1968,7 @@ "win32" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { @@ -2616,6 +2018,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2629,64 +2032,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@keyv/serialize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz", - "integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true, - "license": "MIT" - }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -2997,270 +2342,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@quasar/extras": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.17.0.tgz", - "integrity": "sha512-KqAHdSJfIDauiR1nJ8rqHWT0diqD0QradZKoVIZJAilHAvgwyPIY7MbyR2z4RIMkUIMUSqBZcbshMpEw+9A30w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - } - }, - "node_modules/@quasar/vite-plugin": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@quasar/vite-plugin/-/vite-plugin-1.10.0.tgz", - "integrity": "sha512-4PJoTclz4ZjAfyqe0+hlkKcFJt0e2NX3Ac3hy8ILqUPdtZ24nCo5/xEHvTxZGBQMKRPwwePbO8CVs4n9EKJEug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - }, - "peerDependencies": { - "@vitejs/plugin-vue": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "quasar": "^2.16.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "vue": "^3.0.0" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "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 - } - } - }, - "node_modules/@rollup/plugin-babel/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-babel/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-babel/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", - "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-replace/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-replace/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.50.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", @@ -3555,46 +2636,6 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/@types/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/trusted-types": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3602,759 +2643,35 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "node_modules/@vitejs/plugin-legacy": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-7.2.1.tgz", + "integrity": "sha512-CaXb/y0mlfu7jQRELEJJc2/5w2bX2m1JraARgFnvSB2yfvnCNJVWWlqAo6WjnKoepOwKx8gs0ugJThPLKCOXIg==", "dev": true, "license": "MIT", "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "20.19.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", - "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", - "license": "MIT" - }, - "node_modules/@types/raf": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", - "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sizzle": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", - "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "@babel/core": "^7.28.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/preset-env": "^7.28.0", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "browserslist": "^4.25.1", + "browserslist-to-esbuild": "^2.1.1", + "core-js": "^3.45.0", + "magic-string": "^0.30.17", + "regenerator-runtime": "^0.14.1", + "systemjs": "^6.15.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/vitejs/vite?sponsor=1" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", - "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "1.6.1" - } - }, - "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/ui": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.1.tgz", - "integrity": "sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "fast-glob": "^3.3.2", - "fflate": "^0.8.1", - "flatted": "^3.2.9", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "sirv": "^2.0.4" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "1.6.1" - } - }, - "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@volar/language-core": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", - "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.15" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", - "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.15", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", - "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.15", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", - "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@vue/shared": "3.5.21", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", - "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.21", - "@vue/shared": "3.5.21" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", - "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@vue/compiler-core": "3.5.21", - "@vue/compiler-dom": "3.5.21", - "@vue/compiler-ssr": "3.5.21", - "@vue/shared": "3.5.21", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.18", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", - "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.21", - "@vue/shared": "3.5.21" - } - }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", - "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.7", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", - "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.0" - }, - "peerDependencies": { - "eslint": ">= 8.0.0", - "prettier": ">= 3.0.0" - } - }, - "node_modules/@vue/eslint-config-typescript": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz", - "integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", - "vue-eslint-parser": "^9.3.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "peerDependencies": { - "eslint": "^8.56.0", - "eslint-plugin-vue": "^9.0.0", - "typescript": ">=4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/language-core": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", - "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.15", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^1.0.3", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz", - "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.21" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz", - "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.21", - "@vue/shared": "3.5.21" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz", - "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.21", - "@vue/runtime-core": "3.5.21", - "@vue/shared": "3.5.21", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz", - "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.21", - "@vue/shared": "3.5.21" - }, - "peerDependencies": { - "vue": "3.5.21" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", - "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", - "license": "MIT" - }, - "node_modules/@vue/test-utils": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", - "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-beautify": "^1.14.9", - "vue-component-type-helpers": "^2.0.0" - } - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "terser": "^5.16.0", + "vite": "^7.0.0" } }, "node_modules/acorn": { @@ -4370,336 +2687,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/alien-signals": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", - "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", @@ -4715,16 +2702,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", @@ -4752,106 +2729,13 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/birpc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", - "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -4892,39 +2776,23 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/browserslist-to-esbuild": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz", + "integrity": "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", + "meow": "^13.0.0" + }, + "bin": { + "browserslist-to-esbuild": "cli/index.js" + }, "engines": { - "node": "*" + "node": ">=18" + }, + "peerDependencies": { + "browserslist": "*" } }, "node_modules/buffer-from": { @@ -4934,106 +2802,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.4.tgz", - "integrity": "sha512-Gd7ccIUkZ9TE2odLQVS+PDjIvQCdJKUlLdJRVvZu0aipj07Qfx+XIej7hhDrKGGoIxV5m5fT/kOJNJPQhQneRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.11.0", - "keyv": "^5.5.0" - } - }, - "node_modules/cacheable/node_modules/keyv": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.0.tgz", - "integrity": "sha512-QG7qR2tijh1ftOvClut4YKKg1iW6cx3GZsKoGyJPxHkGWK9oJhG9P3j5deP0QQOGDowBMVQFaP+Vm4NpGYvmIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.0" - } - }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001741", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", @@ -5055,130 +2823,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvg": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", - "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/raf": "^3.4.0", - "core-js": "^3.8.3", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^6.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -5195,185 +2839,13 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5381,28 +2853,13 @@ "dev": true, "license": "MIT" }, - "node_modules/copy-anything": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", - "license": "MIT", - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/core-js": { "version": "3.45.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "dev": true, "hasInstallScript": true, "license": "MIT", - "optional": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -5422,276 +2879,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-functions-list": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", - "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12 || >=16" - } - }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "license": "MIT", - "optional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, - "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/cypress": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", - "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@cypress/request": "^3.0.6", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "ci-info": "^4.0.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "node_modules/cypress/node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true, - "license": "MIT" - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", - "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", - "license": "MIT" - }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -5710,81 +2897,6 @@ } } }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -5799,131 +2911,6 @@ "node": ">=0.10" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/editorconfig": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "9.0.1", - "semver": "^7.5.3" - }, - "bin": { - "editorconfig": "bin/editorconfig" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/editorconfig/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.215", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.215.tgz", @@ -5931,218 +2918,10 @@ "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6150,32 +2929,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escalade": { @@ -6188,268 +2970,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.35.0", - "@eslint/plugin-kit": "^0.3.5", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", - "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6460,310 +2980,13 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-png": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", - "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", - "license": "MIT", - "dependencies": { - "@types/pako": "^2.0.3", - "iobuffer": "^5.3.2", - "pako": "^2.1.0" - } - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6771,168 +2994,6 @@ "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6952,36 +3013,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", "funding": { @@ -6998,407 +3029,11 @@ "node": ">=6.9.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.1.tgz", - "integrity": "sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true, - "license": "ISC" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^3.2.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true, - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/happy-dom": { - "version": "14.12.3", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.12.3.tgz", - "integrity": "sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^4.5.0", - "webidl-conversions": "^7.0.0", - "whatwg-mimetype": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -7407,142 +3042,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/hookified": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.12.0.tgz", - "integrity": "sha512-hMr1Y9TCLshScrBbV2QxJ9BROddxZ12MX9KsCtuGGy/3SmmN5H1PllKerrVlSotur9dlE8hmUKAOSa3WDzsZmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "license": "MIT", - "optional": true, - "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/immutable": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", @@ -7550,197 +3049,6 @@ "dev": true, "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "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.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/iobuffer": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", - "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", - "license": "MIT" - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -7757,102 +3065,24 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -7860,491 +3090,21 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", - "license": "MIT", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/js-beautify": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", - "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.4.2", - "js-cookie": "^3.0.5", - "nopt": "^7.2.1" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, @@ -8361,48 +3121,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8416,698 +3134,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jspdf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.2.tgz", - "integrity": "sha512-G0fQDJ5fAm6UW78HG6lNXyq09l0PrA1rpNY5i+ly17Zb1fMMFSmS+3lw4cnrAPGyouv2Y0ylujbY2Ieq3DSlKA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.9", - "fast-png": "^6.2.0", - "fflate": "^0.8.1" - }, - "optionalDependencies": { - "canvg": "^3.0.11", - "core-js": "^3.6.0", - "dompurify": "^3.2.4", - "html2canvas": "^1.0.0-rc.5" - } - }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "> 0.8" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", - "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.4.1", - "commander": "^13.1.0", - "debug": "^4.4.0", - "execa": "^8.0.1", - "lilconfig": "^3.1.3", - "listr2": "^8.2.5", - "micromatch": "^4.0.8", - "pidtree": "^0.6.0", - "string-argv": "^0.3.2", - "yaml": "^2.7.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/lint-staged/node_modules/emoji-regex": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", - "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/lint-staged/node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lint-staged/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9115,184 +3141,26 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } }, "node_modules/magic-string": { "version": "0.30.19", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -9306,29 +3174,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -9337,122 +3189,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9460,17 +3196,11 @@ "dev": true, "license": "MIT" }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -9485,13 +3215,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -9507,288 +3230,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -9796,74 +3237,11 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "devOptional": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -9872,6 +3250,7 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8.6" }, @@ -9879,93 +3258,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinia": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", - "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.3", - "vue-demi": "^0.14.10" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.4.4", - "vue": "^2.7.0 || ^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pinia-plugin-persistedstate": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.3.tgz", - "integrity": "sha512-Cm819WBj/s5K5DGw55EwbXDtx+EZzM0YR5AZbq9XE3u0xvXwvX2JnWoFpWIcdzISBHqy9H1UiSIUmXyXqWsQRQ==", - "license": "MIT", - "peerDependencies": { - "pinia": "^2.0.0" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -9990,321 +3287,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", - "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", - "license": "MIT" - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/quasar": { - "version": "2.18.2", - "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.18.2.tgz", - "integrity": "sha512-SeSAamH4vgYH9alLTdVL2o1fTTwz7VZnS2+gvIwt6qsH3ndrn/tQW64sWE78VSvrHlWINYbXESVF/cvWEuTYxg==", - "license": "MIT", - "engines": { - "node": ">= 10.18.1", - "npm": ">= 6.13.4", - "yarn": ">= 1.21.1" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "license": "MIT", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -10319,29 +3301,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -10350,9 +3309,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -10363,46 +3322,25 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "optional": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.3.0.tgz", + "integrity": "sha512-ulzJYRb0qgR4t8eTgHeL7nnKL/4ul2yjnuTBEDIpYG7cSs8CcADE1q18RFFChXLP8WwRgPrHThGbYplvASdujw==", "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -10441,26 +3379,6 @@ "node": ">=6" } }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -10482,57 +3400,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, "node_modules/rollup": { "version": "4.50.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", @@ -10574,123 +3441,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, "node_modules/sass": { "version": "1.92.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.92.1.tgz", @@ -10712,263 +3462,31 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, - "license": "MIT" - }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smob": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", - "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -10985,689 +3503,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stackblur-canvas": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", - "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/stylelint": { - "version": "16.24.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.24.0.tgz", - "integrity": "sha512-7ksgz3zJaSbTUGr/ujMXvLVKdDhLbGl3R/3arNudH7z88+XZZGNLMTepsY28WlnvEFcuOmUe7fg40Q3lfhOfSQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3", - "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.1.0", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.1", - "fast-glob": "^3.3.3", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.1.4", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^7.0.5", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mathml-tag-names": "^2.1.3", - "meow": "^13.2.0", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", - "svg-tags": "^1.0.0", - "table": "^6.9.0", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/stylelint-config-html": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz", - "integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12 || >=14" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" - } - }, - "node_modules/stylelint-config-recommended": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", - "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.1.0" - } - }, - "node_modules/stylelint-config-recommended-scss": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", - "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^14.0.1", - "stylelint-scss": "^6.4.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^16.6.1" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-config-recommended-vue": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-vue/-/stylelint-config-recommended-vue-1.6.1.tgz", - "integrity": "sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5", - "stylelint-config-html": ">=1.0.0", - "stylelint-config-recommended": ">=6.0.0" - }, - "engines": { - "node": "^12 || >=14" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" - } - }, - "node_modules/stylelint-config-standard": { - "version": "36.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz", - "integrity": "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "dependencies": { - "stylelint-config-recommended": "^14.0.1" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.1.0" - } - }, - "node_modules/stylelint-config-standard-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-13.1.0.tgz", - "integrity": "sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "stylelint-config-recommended-scss": "^14.0.0", - "stylelint-config-standard": "^36.0.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^16.3.1" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-config-standard-vue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard-vue/-/stylelint-config-standard-vue-1.0.0.tgz", - "integrity": "sha512-wAzU7p6DSlo04pWfCbOcaMq09Nojt0FEsbdxhCBTdC7IguD9ZVl7FP/bvyA0HAHjZGC4JkW7m6WiQaoVMDSuFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "stylelint-config-html": ">=1.0.0", - "stylelint-config-recommended-vue": ">=1.1.0", - "stylelint-config-standard": ">=24.0.0" - }, - "engines": { - "node": "^12 || >=14" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "postcss-html": "^1.0.0", - "stylelint": ">=14.0.0" - } - }, - "node_modules/stylelint-scss": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.12.1.tgz", - "integrity": "sha512-UJUfBFIvXfly8WKIgmqfmkGKPilKB4L5j38JfsDd+OCg2GBdU0vGUV08Uw82tsRZzd4TbsUURVVNGeOhJVF7pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.1", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.36.0", - "mdn-data": "^2.21.0", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-selector-parser": "^7.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.0.2" - } - }, - "node_modules/stylelint-scss/node_modules/known-css-properties": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", - "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint-scss/node_modules/mdn-data": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.24.0.tgz", - "integrity": "sha512-i97fklrJl03tL1tdRVw0ZfLLvuDsdb6wxL+TrJ+PKkCbLrp2PCu2+OYdCKychIUm19nSM/35S6qz7pJpnXttoA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.4.tgz", - "integrity": "sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.13" - } - }, - "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.13.tgz", - "integrity": "sha512-gmtS2PaUjSPa4zjObEIn4WWliKyZzYljgxODBfxugpK6q6HU9ClXzgCJ+nlcPKY9Bt090ypTOLIFWkV0jbKFjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cacheable": "^1.10.4", - "flatted": "^3.3.3", - "hookified": "^1.11.0" - } - }, - "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/stylelint/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/superjson": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", - "license": "MIT", - "dependencies": { - "copy-anything": "^3.0.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -11681,139 +3516,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-pathdata": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/systemjs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.15.1.tgz", + "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==", "dev": true, "license": "MIT" }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terser": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", @@ -11833,114 +3542,6 @@ "node": ">=10" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "license": "MIT", - "optional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -11958,6 +3559,24 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -11971,62 +3590,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -12034,256 +3604,6 @@ "node": ">=8.0" } }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", - "license": "MIT" - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -12309,9 +3629,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", "engines": { @@ -12328,281 +3648,6 @@ "node": ">=4" } }, - "node_modules/unimport": { - "version": "3.14.6", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", - "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.4", - "acorn": "^8.14.0", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "fast-glob": "^3.3.3", - "local-pkg": "^1.0.0", - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "pathe": "^2.0.1", - "picomatch": "^4.0.2", - "pkg-types": "^1.3.0", - "scule": "^1.3.0", - "strip-literal": "^2.1.1", - "unplugin": "^1.16.1" - } - }, - "node_modules/unimport/node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unimport/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unimport/node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/unimport/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/unimport/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unplugin": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", - "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/unplugin-auto-import": { - "version": "0.17.8", - "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz", - "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.0", - "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.10", - "minimatch": "^9.0.4", - "unimport": "^3.7.2", - "unplugin": "^1.11.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@nuxt/kit": "^3.2.2", - "@vueuse/core": "*" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "@vueuse/core": { - "optional": true - } - } - }, - "node_modules/unplugin-vue-components": { - "version": "0.27.5", - "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", - "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.3", - "chokidar": "^3.6.0", - "debug": "^4.3.7", - "fast-glob": "^3.3.2", - "local-pkg": "^0.5.1", - "magic-string": "^0.30.14", - "minimatch": "^9.0.5", - "mlly": "^1.7.3", - "unplugin": "^1.16.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@babel/parser": "^7.15.8", - "@nuxt/kit": "^3.2.2", - "vue": "2 || 3" - }, - "peerDependenciesMeta": { - "@babel/parser": { - "optional": true - }, - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/unplugin-vue-components/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/unplugin-vue-components/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/unplugin-vue-components/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -12634,108 +3679,25 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "license": "MIT", - "optional": true, - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vee-validate": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", - "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.5.2", - "type-fest": "^4.8.3" - }, - "peerDependencies": { - "vue": "^3.4.26" - } - }, - "node_modules/vee-validate/node_modules/@vue/devtools-api": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", - "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.7" - } - }, - "node_modules/vee-validate/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vite": { - "version": "5.4.20", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", - "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -12744,19 +3706,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -12777,1093 +3745,44 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-plugin-pwa": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.5.tgz", - "integrity": "sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.6", - "pretty-bytes": "^6.1.1", - "tinyglobby": "^0.2.0", - "workbox-build": "^7.1.0", - "workbox-window": "^7.1.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node": ">=12.0.0" }, "peerDependencies": { - "@vite-pwa/assets-generator": "^0.2.6", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", - "workbox-build": "^7.1.0", - "workbox-window": "^7.1.0" + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "@vite-pwa/assets-generator": { + "picomatch": { "optional": true } } }, - "node_modules/vite-plugin-pwa/node_modules/pretty-bytes": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", - "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.21", - "@vue/compiler-sfc": "3.5.21", - "@vue/runtime-dom": "3.5.21", - "@vue/server-renderer": "3.5.21", - "@vue/shared": "3.5.21" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-chartjs": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", - "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", - "license": "MIT", - "peerDependencies": { - "chart.js": "^4.1.1", - "vue": "^3.0.0-0 || ^2.7.0" - } - }, - "node_modules/vue-component-type-helpers": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", - "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-router": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", - "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/vue-tsc": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", - "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "2.4.15", - "@vue/language-core": "2.2.12" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-background-sync": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", - "integrity": "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.3.0.tgz", - "integrity": "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-build": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.3.0.tgz", - "integrity": "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.24.4", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^2.4.1", - "@rollup/plugin-terser": "^0.4.3", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "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": "7.3.0", - "workbox-broadcast-update": "7.3.0", - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-google-analytics": "7.3.0", - "workbox-navigation-preload": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-range-requests": "7.3.0", - "workbox-recipes": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0", - "workbox-streams": "7.3.0", - "workbox-sw": "7.3.0", - "workbox-window": "7.3.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/workbox-build/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/workbox-build/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-build/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/workbox-build/node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.3.0.tgz", - "integrity": "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-core": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.3.0.tgz", - "integrity": "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==", - "license": "MIT" - }, - "node_modules/workbox-expiration": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.3.0.tgz", - "integrity": "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-google-analytics": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.3.0.tgz", - "integrity": "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-background-sync": "7.3.0", - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.3.0.tgz", - "integrity": "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-precaching": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.3.0.tgz", - "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" - } - }, - "node_modules/workbox-range-requests": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.3.0.tgz", - "integrity": "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-recipes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.3.0.tgz", - "integrity": "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" - } - }, - "node_modules/workbox-routing": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.3.0.tgz", - "integrity": "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-strategies": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.3.0.tgz", - "integrity": "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0" - } - }, - "node_modules/workbox-streams": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.3.0.tgz", - "integrity": "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0" - } - }, - "node_modules/workbox-sw": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.3.0.tgz", - "integrity": "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-window": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.3.0.tgz", - "integrity": "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==", - "license": "MIT", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "7.3.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/yallist": { @@ -13872,67 +3791,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.0.tgz", - "integrity": "sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==", - "license": "MIT", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - }, - "node_modules/yup/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/apps/web/package.json b/apps/web/package.json index 735c871..57eee30 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,68 +1,22 @@ { - "name": "dramaling-web", + "name": "web-native", "version": "1.0.0", - "type": "module", + "description": "", + "main": "index.js", "scripts": { - "dev": "vite --host", - "build": "vue-tsc --noEmit && vite build", + "dev": "vite", + "build": "vite build", "preview": "vite preview", - "test": "vitest", - "test:ui": "vitest --ui", - "test:coverage": "vitest --coverage", - "test:e2e": "cypress run", - "test:e2e:dev": "cypress open", - "lint": "eslint . --ext .vue,.ts,.tsx --fix", - "lint:style": "stylelint **/*.{css,scss,vue} --fix", - "type-check": "vue-tsc --noEmit", - "prepare": "husky install" - }, - "dependencies": { - "@quasar/extras": "^1.16.4", - "@vueuse/core": "^10.9.0", - "axios": "^1.6.8", - "chart.js": "^4.5.0", - "dayjs": "^1.11.10", - "dompurify": "^3.0.11", - "jspdf": "^3.0.2", - "lodash-es": "^4.17.21", - "pinia": "^2.1.7", - "pinia-plugin-persistedstate": "^3.2.1", - "quasar": "^2.16.0", - "vee-validate": "^4.12.6", - "vue": "^3.4.21", - "vue-chartjs": "^5.3.2", - "vue-router": "^4.3.0", - "workbox-window": "^7.0.0", - "xlsx": "^0.18.5", - "yup": "^1.4.0" + "test": "echo \"Error: no test specified\" && exit 1" }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", "devDependencies": { - "@quasar/vite-plugin": "^1.6.0", - "@types/dompurify": "^3.0.5", - "@types/lodash-es": "^4.17.12", - "@types/node": "^20.12.7", - "@vitejs/plugin-vue": "^5.0.4", - "@vitest/coverage-v8": "^1.5.0", - "@vitest/ui": "^1.5.0", - "@vue/eslint-config-prettier": "^9.0.0", - "@vue/eslint-config-typescript": "^13.0.0", - "@vue/test-utils": "^2.4.5", - "cypress": "^13.7.2", - "eslint": "^9.1.1", - "happy-dom": "^14.7.1", - "husky": "^9.0.11", - "lint-staged": "^15.2.2", - "prettier": "^3.2.5", - "sass": "^1.77.0", - "stylelint": "^16.4.0", - "stylelint-config-standard-scss": "^13.1.0", - "stylelint-config-standard-vue": "^1.0.0", - "typescript": "^5.4.0", - "unplugin-auto-import": "^0.17.5", - "unplugin-vue-components": "^0.27.0", - "vite": "^5.2.0", - "vite-plugin-pwa": "^0.20.0", - "vitest": "^1.5.0", - "vue-tsc": "^2.0.6" + "@vitejs/plugin-legacy": "^7.2.1", + "sass": "^1.92.1", + "terser": "^5.44.0", + "vite": "^7.1.5" } } diff --git a/apps/web/public/debug.html b/apps/web/public/debug.html deleted file mode 100644 index cd3eadf..0000000 --- a/apps/web/public/debug.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - Debug Page - - - -

調試頁面

-
-

基礎測試

-

✅ HTML正常顯示

-

⏳ JavaScript測試中...

-
- -
-

網絡測試

-

⏳ 檢查main.js...

-

⏳ 檢查Vue應用...

-
- -
-

控制台訊息

-

請打開瀏覽器開發者工具(F12) -> Console面板,查看是否有錯誤訊息

-
- - - - \ No newline at end of file diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg deleted file mode 100644 index 852c49e..0000000 --- a/apps/web/public/favicon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/public/hybrid-approach.html b/apps/web/public/hybrid-approach.html deleted file mode 100644 index 704e37b..0000000 --- a/apps/web/public/hybrid-approach.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - 混合式開發方案 - Drama Ling - - - -
-
-

混合式開發方案

-

靜態佈局 + 動態功能

- - -
-

學習統計 (靜態展示)

-
-
-
1,247
-
總詞彙
-
-
-
856
-
已掌握
-
-
-
23
-
待複習
-
-
-
368
-
學習中
-
-
-
- - -
-

練習選擇 (動態交互)

-
-
-
-

選擇題練習

-

測試詞彙定義理解

-
-
-

翻譯練習

-

英中翻譯能力測試

-
-
-

同義詞練習

-

詞彙關聯性訓練

-
-
- -
- 已選擇:{{ practiceTypes[selectedPractice] }} -
-
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/apps/web/public/logo.svg b/apps/web/public/logo.svg deleted file mode 100644 index 34477fe..0000000 --- a/apps/web/public/logo.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/public/test.html b/apps/web/public/test.html deleted file mode 100644 index c30af90..0000000 --- a/apps/web/public/test.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - 簡單測試頁面 - - -

這是一個簡單的HTML測試頁面

-

如果你能看到這個,說明服務器正常

- - - \ No newline at end of file diff --git a/apps/web/public/vocabulary-native.html b/apps/web/public/vocabulary-native.html deleted file mode 100644 index 1f50fc9..0000000 --- a/apps/web/public/vocabulary-native.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - 詞彙學習 - Drama Ling - - - -
- - - - -
-
-
-
📚
-
-
1,247
-
總詞彙數
-
-
-
- -
-
-
-
-
856
-
已掌握
-
-
-
- -
-
-
-
-
23
-
待複習
-
-
-
- -
-
-
🎯
-
-
368
-
學習中
-
-
-
-
- - -
-

快速開始

- -
-
-
🧠
-

選擇題練習

-

測試詞彙定義理解

-
- 10題 - 基礎-中級 -
-
- -
-
🌐
-

翻譯練習

-

英中翻譯能力測試

-
- 10題 - 中級-高級 -
-
- -
-
🔄
-

同義詞練習

-

詞彙關聯性訓練

-
- 10題 - 高級 -
-
-
- -
- -
-
-
- - - - \ No newline at end of file diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue deleted file mode 100644 index b8e454e..0000000 --- a/apps/web/src/App.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/assets/styles/main.scss b/apps/web/src/assets/styles/main.scss deleted file mode 100644 index 913d7e4..0000000 --- a/apps/web/src/assets/styles/main.scss +++ /dev/null @@ -1,328 +0,0 @@ -// Drama Ling 主要樣式檔案 -@import './variables'; - -// ===== 全域重置 ===== - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html { - font-size: 16px; - line-height: 1.6; -} - -body { - font-family: $font-family-primary; - background: $background-primary; - color: $text-primary; - overflow-x: hidden; -} - -// ===== 全域樣式類 ===== - -// 文字樣式 -.text-primary { color: $text-primary; } -.text-secondary { color: $text-secondary; } -.text-tertiary { color: $text-tertiary; } - -.text-xs { font-size: $text-xs; } -.text-sm { font-size: $text-sm; } -.text-base { font-size: $text-base; } -.text-lg { font-size: $text-lg; } -.text-xl { font-size: $text-xl; } -.text-2xl { font-size: $text-2xl; } -.text-3xl { font-size: $text-3xl; } -.text-4xl { font-size: $text-4xl; } - -// 背景樣式 -.bg-primary { background: $background-primary; } -.bg-secondary { background: $background-secondary; } -.bg-dark { background: $background-dark; } -.bg-card { background: $card-background; } - -// 間距工具類 -.p-1 { padding: $space-1; } -.p-2 { padding: $space-2; } -.p-3 { padding: $space-3; } -.p-4 { padding: $space-4; } -.p-5 { padding: $space-5; } -.p-6 { padding: $space-6; } -.p-8 { padding: $space-8; } - -.m-1 { margin: $space-1; } -.m-2 { margin: $space-2; } -.m-3 { margin: $space-3; } -.m-4 { margin: $space-4; } -.m-5 { margin: $space-5; } -.m-6 { margin: $space-6; } -.m-8 { margin: $space-8; } - -// ===== 動畫效果 ===== - -// 頁面轉場 -.page-enter-active, -.page-leave-active { - transition: all 0.3s ease; -} - -.page-enter-from { - opacity: 0; - transform: translateY(20px); -} - -.page-leave-to { - opacity: 0; - transform: translateY(-20px); -} - -// 彈出動畫 -@keyframes popup { - 0% { - transform: scale(0) rotate(-360deg); - opacity: 0; - } - 100% { - transform: scale(1) rotate(0deg); - opacity: 1; - } -} - -.popup-enter { - animation: popup 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; -} - -// 脈衝動畫 -@keyframes pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.05); } -} - -.pulse { - animation: pulse 2s infinite; -} - -// 旋轉動畫 -@keyframes rotate { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.rotate { - animation: rotate 2s linear infinite; -} - -// ===== 滾動條樣式 ===== - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: $background-secondary; - border-radius: $radius-sm; -} - -::-webkit-scrollbar-thumb { - background: $primary-teal; - border-radius: $radius-sm; - - &:hover { - background: $primary-teal-light; - } -} - -// ===== 響應式工具類 ===== - -.hidden { display: none; } - -@include respond-to(xs) { - .hidden-xs { display: none; } - .visible-xs { display: block; } -} - -@include respond-to(sm) { - .hidden-sm { display: none; } - .visible-sm { display: block; } -} - -@include respond-to(md) { - .hidden-md { display: none; } - .visible-md { display: block; } -} - -@include respond-to(lg) { - .hidden-lg { display: none; } - .visible-lg { display: block; } -} - -// ===== 按鈕基礎樣式 ===== - -.btn-base { - display: inline-flex; - align-items: center; - justify-content: center; - gap: $space-2; - font-weight: 600; - border: none; - border-radius: $radius-lg; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } -} - -.btn-primary { - @extend .btn-base; - background: $primary-teal; - color: $background-dark; - padding: $space-4 $space-6; - - &:hover:not(:disabled) { - background: $primary-teal-light; - transform: translateY(-2px); - box-shadow: 0 8px 25px rgba(0, 229, 204, 0.3); - } -} - -.btn-secondary { - @extend .btn-base; - background: transparent; - color: $primary-teal; - border: 2px solid $primary-teal; - padding: $space-3 $space-5; - - &:hover:not(:disabled) { - background: rgba($primary-teal, 0.1); - transform: translateY(-1px); - } -} - -// ===== 輸入框基礎樣式 ===== - -.input-base { - width: 100%; - padding: $space-4 $space-5; - background: $background-secondary; - border: 2px solid $divider; - border-radius: $radius-lg; - font-size: $text-base; - color: $text-primary; - transition: all 0.3s ease; - - &::placeholder { - color: $text-secondary; - } - - &:focus { - outline: none; - background: $card-background; - border-color: $primary-teal; - box-shadow: 0 0 0 4px rgba(0, 229, 204, 0.15); - } - - &:disabled { - opacity: 0.6; - cursor: not-allowed; - } -} - -// ===== 卡片基礎樣式 ===== - -.card-base { - background: $card-background; - border-radius: $radius-xl; - padding: $space-6; - @include card-shadow(1); - border: 1px solid $divider; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-2px); - @include card-shadow(2); - } -} - -// ===== 遊戲化元素樣式 ===== - -.level-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 32px; - height: 24px; - background: $level-background; - color: white; - border-radius: $radius-full; - font-size: $text-sm; - font-weight: 700; - padding: 0 $space-2; -} - -.exp-bar { - height: 8px; - background: $background-secondary; - border-radius: $radius-full; - overflow: hidden; - - &-fill { - height: 100%; - background: linear-gradient(90deg, $primary-teal, $primary-teal-light); - border-radius: $radius-full; - transition: width 1s ease; - } -} - -.star-rating { - display: inline-flex; - gap: $space-1; - - .star { - width: 16px; - height: 16px; - color: $star-inactive; - - &.active { - color: $star-active; - } - } -} - -// ===== 無障礙樣式 ===== - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -// 焦點樣式 -*:focus-visible { - outline: 2px solid $primary-teal; - outline-offset: 2px; -} - -// ===== 載入狀態 ===== - -.loading-spinner { - display: inline-block; - width: 20px; - height: 20px; - border: 2px solid transparent; - border-top: 2px solid $primary-teal; - border-radius: 50%; - animation: rotate 1s linear infinite; -} \ No newline at end of file diff --git a/apps/web/src/assets/styles/quasar-variables.sass b/apps/web/src/assets/styles/quasar-variables.sass deleted file mode 100644 index 3d46aa6..0000000 --- a/apps/web/src/assets/styles/quasar-variables.sass +++ /dev/null @@ -1,56 +0,0 @@ -// Quasar SASS Variables -// This file is used by Quasar to customize default component styles - -// Brand colors -$primary : #1976D2 -$secondary : #26A69A -$accent : #9C27B0 - -$dark : #1D1D1D -$dark-page : #121212 - -$positive : #21BA45 -$negative : #C10015 -$info : #31CCEC -$warning : #F2C037 - -// Typography -$h1 : 2rem -$h2 : 1.5rem -$h3 : 1.25rem -$h4 : 1.125rem -$h5 : 1rem -$h6 : 0.875rem - -$body-font-size : 0.875rem -$body-line-height : 1.5 - -// Spacing -$space-xs : 0.25rem -$space-sm : 0.5rem -$space-md : 1rem -$space-lg : 1.5rem -$space-xl : 3rem - -// Borders -$generic-border-radius : 4px -$button-border-radius : 4px -$input-border-radius : 4px - -// Shadows -$shadow-1: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) -$shadow-2: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23) - -// Custom Drama Ling Theme Variables -$drama-primary : #00E5CC -$drama-secondary : #FF6B6B -$drama-accent : #4ECDC4 -$drama-background : #F7F9FC -$drama-surface : #FFFFFF -$drama-text : #2C3E50 -$drama-text-light : #7F8C8D - -// Override Quasar defaults with Drama Ling theme -$primary : $drama-primary -$secondary : $drama-secondary -$accent : $drama-accent \ No newline at end of file diff --git a/apps/web/src/assets/styles/variables.scss b/apps/web/src/assets/styles/variables.scss deleted file mode 100644 index 57013f2..0000000 --- a/apps/web/src/assets/styles/variables.scss +++ /dev/null @@ -1,173 +0,0 @@ -// Drama Ling Design System Variables - -// ===== 色彩系統 ===== - -// 主要品牌色 - 青綠色 -$primary-teal: #00E5CC; -$primary-teal-light: #33E8D1; -$primary-teal-dark: #00B3A0; - -// 輔助色 - 紫色系 -$secondary-purple: #8E44AD; -$secondary-purple-light: #A569BD; -$secondary-purple-dark: #6C3483; - -// 強調色 - 活力紫 -$accent-violet: #9B59B6; -$accent-violet-light: #BB8FCE; -$accent-violet-dark: #7D3C98; - -// 功能性色彩 -$error-red: #E74C3C; -$warning-yellow: #F39C12; -$warning-orange: #F39C12; // 別名 -$success-green: #00E5CC; -$info-cyan: #3498DB; - -// 暗色主題色調 -$text-primary: #FFFFFF; -$text-primary-inverse: #2C3E50; // 反色文字 -$text-secondary: #B8BCC8; -$text-tertiary: #7F8C8D; -$background-primary: #2C3E50; -$background-secondary: #34495E; -$background-dark: #1A252F; -$divider: #4A5568; -$card-background: #3A4A5C; - -// 遊戲化色彩 -$star-active: #F1C40F; -$star-inactive: #7F8C8D; -$bronze: #CD7F32; -$silver: #C0C0C0; -$gold: #FFD700; -$diamond: #B9F2FF; -$exp-bar: #00E5CC; -$level-background: #8E44AD; -$achievement-glow: #F39C12; - -// ===== 字體系統 ===== - -// 字體家族 -$font-family-primary: 'Inter', 'PingFang TC', -apple-system, sans-serif; -$font-family-secondary: 'Roboto', 'Microsoft JhengHei UI', sans-serif; -$font-family-mono: 'JetBrains Mono', 'SF Mono', Monaco, monospace; - -// 字體大小 -$text-xs: 0.75rem; // 12px -$text-sm: 0.875rem; // 14px -$text-base: 1rem; // 16px -$text-lg: 1.125rem; // 18px -$text-xl: 1.25rem; // 20px -$text-2xl: 1.5rem; // 24px -$text-3xl: 1.875rem; // 30px -$text-4xl: 2.25rem; // 36px - -// 遊戲化特殊字體 -$text-game-score: 1.5rem; // 24px -$text-game-level: 0.875rem; // 14px -$text-game-title: 1.25rem; // 20px - -// ===== 間距系統 ===== - -$space-1: 0.25rem; // 4px -$space-2: 0.5rem; // 8px -$space-3: 0.75rem; // 12px -$space-4: 1rem; // 16px -$space-5: 1.25rem; // 20px -$space-6: 1.5rem; // 24px -$space-8: 2rem; // 32px -$space-10: 2.5rem; // 40px -$space-12: 3rem; // 48px -$space-16: 4rem; // 64px -$space-20: 5rem; // 80px - -// ===== 圓角和陰影 ===== - -$radius-sm: 0.5rem; // 8px -$radius-md: 0.75rem; // 12px -$radius-lg: 1rem; // 16px -$radius-xl: 1.5rem; // 24px -$radius-2xl: 2rem; // 32px -$radius-full: 50%; - -// 陰影系統 -$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); -$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); -$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); -$shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); - -// ===== 響應式斷點 ===== - -$breakpoint-xs: 0; -$breakpoint-sm: 640px; -$breakpoint-md: 768px; -$breakpoint-lg: 1024px; -$breakpoint-xl: 1280px; -$breakpoint-2xl: 1536px; - -// ===== Z-index 層級 ===== - -$z-sidebar: 900; -$z-mobile-nav: 950; -$z-dropdown: 1000; -$z-modal: 1050; -$z-popover: 1060; -$z-tooltip: 1070; -$z-toast: 1080; - -// ===== 混合器 ===== - -@mixin respond-to($breakpoint) { - @if $breakpoint == xs { - @media (max-width: #{$breakpoint-sm - 1px}) { @content; } - } - @if $breakpoint == sm { - @media (min-width: #{$breakpoint-sm}) and (max-width: #{$breakpoint-md - 1px}) { @content; } - } - @if $breakpoint == md { - @media (min-width: #{$breakpoint-md}) and (max-width: #{$breakpoint-lg - 1px}) { @content; } - } - @if $breakpoint == lg { - @media (min-width: #{$breakpoint-lg}) and (max-width: #{$breakpoint-xl - 1px}) { @content; } - } - @if $breakpoint == xl { - @media (min-width: #{$breakpoint-xl}) { @content; } - } -} - -@mixin text-ellipsis($lines: 1) { - @if $lines == 1 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } @else { - display: -webkit-box; - -webkit-line-clamp: $lines; - -webkit-box-orient: vertical; - overflow: hidden; - } -} - -@mixin card-shadow($level: 1) { - @if $level == 1 { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - @if $level == 2 { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08); - } - @if $level == 3 { - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1); - } -} - -@mixin loading-skeleton { - background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); - background-size: 200% 100%; - animation: loading 1.5s infinite; -} - -@keyframes loading { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } -} \ No newline at end of file diff --git a/apps/web/src/components/BaseComponent.js b/apps/web/src/components/BaseComponent.js new file mode 100644 index 0000000..e952305 --- /dev/null +++ b/apps/web/src/components/BaseComponent.js @@ -0,0 +1,101 @@ +// Base Web Component Class +export class BaseComponent extends HTMLElement { + constructor() { + super(); + this.state = {}; + this.listeners = []; + } + + // Lifecycle methods + connectedCallback() { + this.render(); + this.bindEvents(); + } + + disconnectedCallback() { + this.cleanup(); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + this.onAttributeChanged(name, oldValue, newValue); + } + } + + // State management + setState(newState) { + this.state = { ...this.state, ...newState }; + this.render(); + } + + getState() { + return { ...this.state }; + } + + // Event handling + addEventListener(element, event, handler, options = {}) { + element.addEventListener(event, handler, options); + this.listeners.push({ element, event, handler, options }); + } + + cleanup() { + this.listeners.forEach(({ element, event, handler, options }) => { + element.removeEventListener(event, handler, options); + }); + this.listeners = []; + } + + // Template methods (to be overridden) + render() { + // Override in child components + } + + bindEvents() { + // Override in child components + } + + onAttributeChanged(name, oldValue, newValue) { + // Override in child components if needed + } + + // Utility methods + $(selector) { + return this.querySelector(selector); + } + + $$(selector) { + return this.querySelectorAll(selector); + } + + emit(eventName, detail = {}) { + const event = new CustomEvent(eventName, { + detail, + bubbles: true, + composed: true + }); + this.dispatchEvent(event); + } + + // HTML template helpers + html(strings, ...values) { + return strings.reduce((result, string, i) => { + const value = values[i] !== undefined ? values[i] : ''; + return result + string + value; + }, ''); + } + + css(styles) { + if (typeof styles === 'string') { + return ``; + } + + if (typeof styles === 'object') { + const cssText = Object.entries(styles) + .map(([key, value]) => `${key}: ${value};`) + .join(' '); + return cssText; + } + + return ''; + } +} \ No newline at end of file diff --git a/apps/web/src/components/PWAInstallPrompt.vue b/apps/web/src/components/PWAInstallPrompt.vue deleted file mode 100644 index 0391730..0000000 --- a/apps/web/src/components/PWAInstallPrompt.vue +++ /dev/null @@ -1,417 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/base/BaseButton.vue b/apps/web/src/components/base/BaseButton.vue deleted file mode 100644 index 3c5ab67..0000000 --- a/apps/web/src/components/base/BaseButton.vue +++ /dev/null @@ -1,109 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/base/BaseCard.vue b/apps/web/src/components/base/BaseCard.vue deleted file mode 100644 index 94046a3..0000000 --- a/apps/web/src/components/base/BaseCard.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/base/BaseInput.vue b/apps/web/src/components/base/BaseInput.vue deleted file mode 100644 index c51f112..0000000 --- a/apps/web/src/components/base/BaseInput.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/base/BaseModal.vue b/apps/web/src/components/base/BaseModal.vue deleted file mode 100644 index 8903c1e..0000000 --- a/apps/web/src/components/base/BaseModal.vue +++ /dev/null @@ -1,217 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/business/VocabularyCard.vue b/apps/web/src/components/business/VocabularyCard.vue deleted file mode 100644 index 1cabf5d..0000000 --- a/apps/web/src/components/business/VocabularyCard.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/dashboard/ErrorHeatmap.vue b/apps/web/src/components/dashboard/ErrorHeatmap.vue deleted file mode 100644 index 21c2542..0000000 --- a/apps/web/src/components/dashboard/ErrorHeatmap.vue +++ /dev/null @@ -1,515 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/dashboard/StatCard.vue b/apps/web/src/components/dashboard/StatCard.vue deleted file mode 100644 index 93d76e9..0000000 --- a/apps/web/src/components/dashboard/StatCard.vue +++ /dev/null @@ -1,223 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/ui/Icon.vue b/apps/web/src/components/ui/Icon.vue deleted file mode 100644 index b750e47..0000000 --- a/apps/web/src/components/ui/Icon.vue +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/components/ui/ModalContainer.vue b/apps/web/src/components/ui/ModalContainer.vue deleted file mode 100644 index dcb90e8..0000000 --- a/apps/web/src/components/ui/ModalContainer.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/components/ui/ToastContainer.vue b/apps/web/src/components/ui/ToastContainer.vue deleted file mode 100644 index 280ecb4..0000000 --- a/apps/web/src/components/ui/ToastContainer.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/composables/useAudio.ts b/apps/web/src/composables/useAudio.ts deleted file mode 100644 index ebf2528..0000000 --- a/apps/web/src/composables/useAudio.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { ref, onUnmounted } from 'vue' -import { useQuasar } from 'quasar' - -export interface AudioOptions { - playbackRate?: number - volume?: number - loop?: boolean - preload?: boolean -} - -export function useAudio() { - const $q = useQuasar() - - // 狀態管理 - const isPlaying = ref(false) - const isLoading = ref(false) - const duration = ref(0) - const currentTime = ref(0) - const volume = ref(1) - const playbackRate = ref(1) - const error = ref(null) - - // Web Audio API 支援 - let audioContext: AudioContext | null = null - let currentAudioSource: AudioBufferSourceNode | null = null - let gainNode: GainNode | null = null - let audioBuffer: AudioBuffer | null = null - - // HTML5 Audio fallback - let htmlAudio: HTMLAudioElement | null = null - - // 初始化音頻上下文 - const initAudioContext = async (): Promise => { - if (audioContext) return true - - try { - audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() - gainNode = audioContext.createGain() - gainNode.connect(audioContext.destination) - return true - } catch (err) { - console.warn('Web Audio API 不支援,使用 HTML5 Audio fallback:', err) - return false - } - } - - // 載入音頻文件 - const loadAudio = async (url: string): Promise => { - error.value = null - isLoading.value = true - - try { - const useWebAudio = await initAudioContext() - - if (useWebAudio && audioContext) { - // 使用 Web Audio API - const response = await fetch(url) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const arrayBuffer = await response.arrayBuffer() - audioBuffer = await audioContext.decodeAudioData(arrayBuffer) - duration.value = audioBuffer.duration - } else { - // 使用 HTML5 Audio fallback - htmlAudio = new Audio() - htmlAudio.preload = 'auto' - htmlAudio.src = url - - return new Promise((resolve, reject) => { - if (!htmlAudio) { - reject(new Error('無法創建 Audio 元素')) - return - } - - htmlAudio.onloadedmetadata = () => { - duration.value = htmlAudio!.duration - resolve(true) - } - - htmlAudio.onerror = () => { - reject(new Error('音頻載入失敗')) - } - }) - } - - return true - } catch (err) { - error.value = err instanceof Error ? err.message : '載入音頻失敗' - console.error('載入音頻失敗:', err) - return false - } finally { - isLoading.value = false - } - } - - // 播放音頻 - const play = async (options?: AudioOptions) => { - if (isPlaying.value) { - stop() - } - - try { - if (audioBuffer && audioContext && gainNode) { - // 使用 Web Audio API 播放 - currentAudioSource = audioContext.createBufferSource() - currentAudioSource.buffer = audioBuffer - currentAudioSource.playbackRate.value = options?.playbackRate || playbackRate.value - - gainNode.gain.value = options?.volume || volume.value - - currentAudioSource.connect(gainNode) - currentAudioSource.start(0) - - currentAudioSource.onended = () => { - isPlaying.value = false - currentTime.value = 0 - } - - } else if (htmlAudio) { - // 使用 HTML5 Audio 播放 - htmlAudio.volume = options?.volume || volume.value - htmlAudio.playbackRate = options?.playbackRate || playbackRate.value - htmlAudio.loop = options?.loop || false - - htmlAudio.ontimeupdate = () => { - currentTime.value = htmlAudio!.currentTime - } - - htmlAudio.onended = () => { - isPlaying.value = false - currentTime.value = 0 - } - - await htmlAudio.play() - } else { - throw new Error('沒有可用的音頻資源') - } - - isPlaying.value = true - error.value = null - } catch (err) { - error.value = err instanceof Error ? err.message : '播放失敗' - isPlaying.value = false - - $q.notify({ - type: 'negative', - message: error.value - }) - } - } - - // 暫停音頻 - const pause = () => { - if (currentAudioSource) { - currentAudioSource.stop() - currentAudioSource = null - } - - if (htmlAudio) { - htmlAudio.pause() - } - - isPlaying.value = false - } - - // 停止音頻 - const stop = () => { - pause() - currentTime.value = 0 - - if (htmlAudio) { - htmlAudio.currentTime = 0 - } - } - - // 設置音量 - const setVolume = (newVolume: number) => { - volume.value = Math.max(0, Math.min(1, newVolume)) - - if (gainNode) { - gainNode.gain.value = volume.value - } - - if (htmlAudio) { - htmlAudio.volume = volume.value - } - } - - // 設置播放速度 - const setPlaybackRate = (rate: number) => { - playbackRate.value = Math.max(0.25, Math.min(4, rate)) - - if (currentAudioSource) { - currentAudioSource.playbackRate.value = playbackRate.value - } - - if (htmlAudio) { - htmlAudio.playbackRate = playbackRate.value - } - } - - // 跳轉到指定時間 - const seekTo = (time: number) => { - if (htmlAudio) { - htmlAudio.currentTime = Math.max(0, Math.min(duration.value, time)) - currentTime.value = htmlAudio.currentTime - } - } - - // 快速播放功能(用於詞彙學習) - const quickPlay = async (url: string, options?: AudioOptions) => { - const success = await loadAudio(url) - if (success) { - await play(options) - } - return success - } - - // 銷毀資源 - const cleanup = () => { - stop() - - if (audioBuffer) { - audioBuffer = null - } - - if (htmlAudio) { - htmlAudio.remove() - htmlAudio = null - } - - if (audioContext && audioContext.state !== 'closed') { - audioContext.close() - audioContext = null - } - - gainNode = null - currentAudioSource = null - } - - // 組件卸載時清理資源 - onUnmounted(() => { - cleanup() - }) - - return { - // 狀態 - isPlaying, - isLoading, - duration, - currentTime, - volume, - playbackRate, - error, - - // 方法 - loadAudio, - play, - pause, - stop, - setVolume, - setPlaybackRate, - seekTo, - quickPlay, - cleanup - } -} \ No newline at end of file diff --git a/apps/web/src/composables/useBrowserBookmarks.ts b/apps/web/src/composables/useBrowserBookmarks.ts deleted file mode 100644 index 469aa3d..0000000 --- a/apps/web/src/composables/useBrowserBookmarks.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { ref } from 'vue' - -export interface BookmarkData { - id: string - title: string - url: string - vocabularyId?: string - description?: string - tags?: string[] - createdAt: Date - updatedAt: Date -} - -const BOOKMARK_STORAGE_KEY = 'dramaling-vocabulary-bookmarks' - -export function useBrowserBookmarks() { - const bookmarks = ref([]) - const isBookmarked = ref(false) - - const loadBookmarks = () => { - const stored = localStorage.getItem(BOOKMARK_STORAGE_KEY) - if (stored) { - try { - bookmarks.value = JSON.parse(stored).map((bookmark: any) => ({ - ...bookmark, - createdAt: new Date(bookmark.createdAt), - updatedAt: new Date(bookmark.updatedAt) - })) - } catch (error) { - console.error('Failed to load bookmarks:', error) - bookmarks.value = [] - } - } - } - - const saveBookmarks = () => { - localStorage.setItem(BOOKMARK_STORAGE_KEY, JSON.stringify(bookmarks.value)) - } - - const addBookmark = (data: Omit) => { - const bookmark: BookmarkData = { - ...data, - id: `bookmark_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - createdAt: new Date(), - updatedAt: new Date() - } - - bookmarks.value.push(bookmark) - saveBookmarks() - return bookmark - } - - const removeBookmark = (id: string) => { - const index = bookmarks.value.findIndex(b => b.id === id) - if (index > -1) { - bookmarks.value.splice(index, 1) - saveBookmarks() - return true - } - return false - } - - const toggleBookmark = (data: Omit) => { - const existing = bookmarks.value.find(b => b.url === data.url) - - if (existing) { - removeBookmark(existing.id) - isBookmarked.value = false - return { bookmarked: false, bookmark: null } - } else { - const bookmark = addBookmark(data) - isBookmarked.value = true - return { bookmarked: true, bookmark } - } - } - - const checkBookmarkStatus = (url: string) => { - const existing = bookmarks.value.find(b => b.url === url) - isBookmarked.value = !!existing - return isBookmarked.value - } - - const getBookmarkByUrl = (url: string) => { - return bookmarks.value.find(b => b.url === url) - } - - const getVocabularyBookmarks = (vocabularyId: string) => { - return bookmarks.value.filter(b => b.vocabularyId === vocabularyId) - } - - const searchBookmarks = (query: string) => { - const lowerQuery = query.toLowerCase() - return bookmarks.value.filter(b => - b.title.toLowerCase().includes(lowerQuery) || - b.description?.toLowerCase().includes(lowerQuery) || - b.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) - ) - } - - const exportBookmarks = () => { - const data = { - exportedAt: new Date().toISOString(), - version: '1.0', - bookmarks: bookmarks.value - } - - const blob = new Blob([JSON.stringify(data, null, 2)], { - type: 'application/json' - }) - - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `dramaling-bookmarks-${new Date().toISOString().split('T')[0]}.json` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - } - - const importBookmarks = (file: File): Promise => { - return new Promise((resolve, reject) => { - const reader = new FileReader() - - reader.onload = (e) => { - try { - const data = JSON.parse(e.target?.result as string) - - if (data.bookmarks && Array.isArray(data.bookmarks)) { - const importedCount = data.bookmarks.length - const existingUrls = new Set(bookmarks.value.map(b => b.url)) - - const newBookmarks = data.bookmarks - .filter((b: any) => !existingUrls.has(b.url)) - .map((b: any) => ({ - ...b, - id: `bookmark_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - createdAt: new Date(b.createdAt || Date.now()), - updatedAt: new Date(b.updatedAt || Date.now()) - })) - - bookmarks.value.push(...newBookmarks) - saveBookmarks() - resolve(newBookmarks.length) - } else { - reject(new Error('Invalid bookmark file format')) - } - } catch (error) { - reject(new Error('Failed to parse bookmark file')) - } - } - - reader.onerror = () => reject(new Error('Failed to read file')) - reader.readAsText(file) - }) - } - - loadBookmarks() - - return { - bookmarks, - isBookmarked, - loadBookmarks, - addBookmark, - removeBookmark, - toggleBookmark, - checkBookmarkStatus, - getBookmarkByUrl, - getVocabularyBookmarks, - searchBookmarks, - exportBookmarks, - importBookmarks - } -} \ No newline at end of file diff --git a/apps/web/src/composables/useKeyboard.ts b/apps/web/src/composables/useKeyboard.ts deleted file mode 100644 index 934981a..0000000 --- a/apps/web/src/composables/useKeyboard.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { ref, onMounted, onUnmounted } from 'vue' - -export interface KeyboardShortcut { - key: string - code: string - description: string - action: () => void - preventDefault?: boolean - ctrlKey?: boolean - shiftKey?: boolean - altKey?: boolean - metaKey?: boolean -} - -export interface KeyboardOptions { - ignoreInputs?: boolean - ignoreContentEditable?: boolean -} - -export function useKeyboard(options: KeyboardOptions = {}) { - const shortcuts = ref>(new Map()) - const isEnabled = ref(true) - const lastKeyPressed = ref('') - const keySequence = ref([]) - - const defaultOptions: Required = { - ignoreInputs: true, - ignoreContentEditable: true, - ...options - } - - // 檢查是否應該忽略按鍵事件 - const shouldIgnoreEvent = (event: KeyboardEvent): boolean => { - if (!isEnabled.value) return true - - const target = event.target as HTMLElement - - // 檢查是否在輸入框中 - if (defaultOptions.ignoreInputs) { - if (target instanceof HTMLInputElement || - target instanceof HTMLTextAreaElement || - target instanceof HTMLSelectElement) { - return true - } - } - - // 檢查是否在可編輯元素中 - if (defaultOptions.ignoreContentEditable) { - if (target.contentEditable === 'true') { - return true - } - } - - return false - } - - // 生成快捷鍵的唯一標識符 - const generateShortcutKey = (shortcut: Omit): string => { - const modifiers = [] - if (shortcut.ctrlKey) modifiers.push('ctrl') - if (shortcut.shiftKey) modifiers.push('shift') - if (shortcut.altKey) modifiers.push('alt') - if (shortcut.metaKey) modifiers.push('meta') - - return [...modifiers, shortcut.code.toLowerCase()].join('+') - } - - // 檢查事件是否匹配快捷鍵 - const matchesShortcut = (event: KeyboardEvent, shortcut: KeyboardShortcut): boolean => { - return ( - event.code === shortcut.code && - !!event.ctrlKey === !!shortcut.ctrlKey && - !!event.shiftKey === !!shortcut.shiftKey && - !!event.altKey === !!shortcut.altKey && - !!event.metaKey === !!shortcut.metaKey - ) - } - - // 註冊快捷鍵 - const register = (shortcut: KeyboardShortcut) => { - const key = generateShortcutKey(shortcut) - shortcuts.value.set(key, shortcut) - } - - // 批量註冊快捷鍵 - const registerMultiple = (shortcutList: KeyboardShortcut[]) => { - shortcutList.forEach(shortcut => register(shortcut)) - } - - // 取消註冊快捷鍵 - const unregister = (code: string, modifiers?: { - ctrlKey?: boolean - shiftKey?: boolean - altKey?: boolean - metaKey?: boolean - }) => { - const key = generateShortcutKey({ - code, - key: '', - ...modifiers - }) - shortcuts.value.delete(key) - } - - // 清空所有快捷鍵 - const clear = () => { - shortcuts.value.clear() - } - - // 啟用/禁用快捷鍵 - const enable = () => { - isEnabled.value = true - } - - const disable = () => { - isEnabled.value = false - } - - const toggle = () => { - isEnabled.value = !isEnabled.value - } - - // 獲取所有已註冊的快捷鍵 - const getShortcuts = () => { - return Array.from(shortcuts.value.values()) - } - - // 按鍵事件處理器 - const handleKeydown = (event: KeyboardEvent) => { - if (shouldIgnoreEvent(event)) return - - lastKeyPressed.value = event.code - keySequence.value.push(event.code) - - // 限制序列長度 - if (keySequence.value.length > 5) { - keySequence.value.shift() - } - - // 查找匹配的快捷鍵 - for (const shortcut of shortcuts.value.values()) { - if (matchesShortcut(event, shortcut)) { - if (shortcut.preventDefault !== false) { - event.preventDefault() - } - - try { - shortcut.action() - } catch (error) { - console.error('快捷鍵執行錯誤:', error) - } - - break - } - } - } - - // 常用快捷鍵預設集 - const presets = { - // 詞彙學習相關 - vocabulary: [ - { - key: 'Space', - code: 'Space', - description: '播放/暫停音頻', - action: () => {} - }, - { - key: 'ArrowRight', - code: 'ArrowRight', - description: '下一個詞彙', - action: () => {} - }, - { - key: 'ArrowLeft', - code: 'ArrowLeft', - description: '上一個詞彙', - action: () => {} - }, - { - key: 'h', - code: 'KeyH', - description: '顯示/隱藏幫助', - action: () => {} - }, - { - key: 'a', - code: 'KeyA', - description: '切換自動播放', - action: () => {} - }, - { - key: 'r', - code: 'KeyR', - description: '重播音頻', - action: () => {} - } - ] as KeyboardShortcut[], - - // 練習模式相關 - practice: [ - { - key: 'Enter', - code: 'Enter', - description: '提交答案', - action: () => {} - }, - { - key: 'n', - code: 'KeyN', - description: '下一題', - action: () => {} - }, - { - key: 's', - code: 'KeyS', - description: '跳過題目', - action: () => {} - }, - { - key: 'Escape', - code: 'Escape', - description: '退出練習', - action: () => {} - } - ] as KeyboardShortcut[], - - // 通用導航 - navigation: [ - { - key: 'Escape', - code: 'Escape', - description: '返回上一頁', - action: () => {} - }, - { - key: 'f', - code: 'KeyF', - description: '全螢幕模式', - action: () => {} - }, - { - key: '/', - code: 'Slash', - description: '搜索', - action: () => {} - } - ] as KeyboardShortcut[] - } - - // 生命週期 - onMounted(() => { - document.addEventListener('keydown', handleKeydown) - }) - - onUnmounted(() => { - document.removeEventListener('keydown', handleKeydown) - }) - - return { - // 狀態 - shortcuts, - isEnabled, - lastKeyPressed, - keySequence, - - // 方法 - register, - registerMultiple, - unregister, - clear, - enable, - disable, - toggle, - getShortcuts, - - // 預設集 - presets - } -} \ No newline at end of file diff --git a/apps/web/src/composables/useKeyboardShortcuts.ts b/apps/web/src/composables/useKeyboardShortcuts.ts deleted file mode 100644 index 977ff34..0000000 --- a/apps/web/src/composables/useKeyboardShortcuts.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { onMounted, onUnmounted } from 'vue' -import { useRouter } from 'vue-router' - -export interface KeyboardShortcut { - key: string - ctrl?: boolean - alt?: boolean - shift?: boolean - meta?: boolean - action: () => void - description: string -} - -export function useKeyboardShortcuts() { - const router = useRouter() - const shortcuts = new Map() - - const getShortcutKey = (shortcut: Omit) => { - const modifiers = [] - if (shortcut.ctrl) modifiers.push('ctrl') - if (shortcut.alt) modifiers.push('alt') - if (shortcut.shift) modifiers.push('shift') - if (shortcut.meta) modifiers.push('meta') - - return `${modifiers.join('+')}-${shortcut.key.toLowerCase()}` - } - - const registerShortcut = (shortcut: KeyboardShortcut) => { - const key = getShortcutKey(shortcut) - shortcuts.set(key, shortcut) - } - - const unregisterShortcut = (shortcut: Omit) => { - const key = getShortcutKey(shortcut) - shortcuts.delete(key) - } - - const handleKeyDown = (event: KeyboardEvent) => { - // Skip if user is typing in input fields - const target = event.target as HTMLElement - if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { - return - } - - const modifiers = [] - if (event.ctrlKey || event.metaKey) modifiers.push('ctrl') - if (event.altKey) modifiers.push('alt') - if (event.shiftKey) modifiers.push('shift') - if (event.metaKey && !event.ctrlKey) modifiers.push('meta') - - const key = `${modifiers.join('+')}-${event.key.toLowerCase()}` - const shortcut = shortcuts.get(key) - - if (shortcut) { - event.preventDefault() - shortcut.action() - } - } - - const registerDefaultShortcuts = () => { - // Navigation shortcuts - registerShortcut({ - key: 'h', - ctrl: true, - action: () => router.push('/learning'), - description: '返回學習首頁' - }) - - registerShortcut({ - key: 'v', - ctrl: true, - action: () => router.push('/learning/vocabulary'), - description: '打開詞彙學習' - }) - - registerShortcut({ - key: 'r', - ctrl: true, - action: () => router.push('/learning/vocabulary/review'), - description: '打開智能複習' - }) - - // Dictionary shortcut - registerShortcut({ - key: 'F1', - action: () => { - // TODO: Open dictionary panel - console.log('Dictionary shortcut activated') - }, - description: '打開字典' - }) - - // Markdown notes shortcut - registerShortcut({ - key: 'n', - ctrl: true, - action: () => { - // TODO: Open markdown note editor - console.log('Open markdown note editor') - }, - description: '開啟筆記編輯器' - }) - - // Help shortcut - registerShortcut({ - key: '?', - shift: true, - action: () => { - // TODO: Show help/shortcuts panel - console.log('Help shortcuts panel') - }, - description: '顯示快捷鍵說明' - }) - - // Search shortcut - registerShortcut({ - key: 'f', - ctrl: true, - action: () => { - // TODO: Focus search input - console.log('Focus search') - }, - description: '搜尋' - }) - - // Toggle sidebar shortcut - registerShortcut({ - key: 'm', - ctrl: true, - action: () => { - // TODO: Toggle sidebar - console.log('Toggle sidebar') - }, - description: '切換側邊欄' - }) - - // Settings shortcut - registerShortcut({ - key: ',', - ctrl: true, - action: () => router.push('/profile/settings'), - description: '開啟設定' - }) - - // Profile shortcut - registerShortcut({ - key: 'p', - ctrl: true, - action: () => router.push('/profile'), - description: '開啟個人檔案' - }) - } - - const getAllShortcuts = () => { - return Array.from(shortcuts.values()) - } - - const getShortcutsByCategory = () => { - const categories = { - navigation: [] as KeyboardShortcut[], - learning: [] as KeyboardShortcut[], - tools: [] as KeyboardShortcut[] - } - - shortcuts.forEach(shortcut => { - if (['h', 'v', 'r', 'p', ','].includes(shortcut.key)) { - categories.navigation.push(shortcut) - } else if (['d', 'n', 'F1'].includes(shortcut.key)) { - categories.learning.push(shortcut) - } else { - categories.tools.push(shortcut) - } - }) - - return categories - } - - onMounted(() => { - registerDefaultShortcuts() - document.addEventListener('keydown', handleKeyDown) - }) - - onUnmounted(() => { - document.removeEventListener('keydown', handleKeyDown) - shortcuts.clear() - }) - - return { - registerShortcut, - unregisterShortcut, - getAllShortcuts, - getShortcutsByCategory - } -} \ No newline at end of file diff --git a/apps/web/src/composables/useMultiTabLearning.ts b/apps/web/src/composables/useMultiTabLearning.ts deleted file mode 100644 index 509a717..0000000 --- a/apps/web/src/composables/useMultiTabLearning.ts +++ /dev/null @@ -1,413 +0,0 @@ -import { ref, reactive, watch, onMounted, onUnmounted, readonly } from 'vue' -import { useVocabularyStore } from '@/stores/vocabulary' - -// 跨標籤頁學習狀態同步 -interface TabLearningSession { - tabId: string - sessionId: string | null - currentExerciseId: string | null - startTime: string - isActive: boolean - lastActivity: string - exerciseType: string - completedQuestions: number - totalQuestions: number -} - -// 跨標籤頁消息類型 -interface TabMessage { - type: 'session-start' | 'session-update' | 'session-complete' | 'sync-request' | 'sync-response' | 'tab-register' | 'tab-unregister' - tabId: string - payload?: any - timestamp: string -} - -export function useMultiTabLearning() { - const vocabularyStore = useVocabularyStore() - - // 當前標籤頁ID - const currentTabId = ref(`tab_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`) - - // 所有活躍標籤頁的學習會話 - const activeTabs = reactive>(new Map()) - - // 同步狀態 - const isSyncing = ref(false) - const syncConflicts = ref([]) - - // 廣播通道 (用於跨標籤頁通信) - let broadcastChannel: BroadcastChannel | null = null - let heartbeatInterval: number | null = null - - // 初始化廣播通道 - const initializeBroadcastChannel = () => { - if ('BroadcastChannel' in window) { - broadcastChannel = new BroadcastChannel('dramaling-multi-tab') - - broadcastChannel.onmessage = (event: MessageEvent) => { - handleTabMessage(event.data) - } - - // 註冊當前標籤頁 - broadcastMessage({ - type: 'tab-register', - tabId: currentTabId.value, - payload: { - url: window.location.href, - userAgent: navigator.userAgent - }, - timestamp: new Date().toISOString() - }) - - // 請求其他標籤頁的狀態 - setTimeout(() => { - broadcastMessage({ - type: 'sync-request', - tabId: currentTabId.value, - timestamp: new Date().toISOString() - }) - }, 100) - } - } - - // 發送廣播消息 - const broadcastMessage = (message: TabMessage) => { - if (broadcastChannel) { - broadcastChannel.postMessage(message) - } - } - - // 處理來自其他標籤頁的消息 - const handleTabMessage = (message: TabMessage) => { - if (message.tabId === currentTabId.value) return // 忽略自己的消息 - - switch (message.type) { - case 'tab-register': - activeTabs.set(message.tabId, { - tabId: message.tabId, - sessionId: null, - currentExerciseId: null, - startTime: message.timestamp, - isActive: true, - lastActivity: message.timestamp, - exerciseType: '', - completedQuestions: 0, - totalQuestions: 0 - }) - break - - case 'tab-unregister': - activeTabs.delete(message.tabId) - break - - case 'session-start': - if (activeTabs.has(message.tabId)) { - const tab = activeTabs.get(message.tabId)! - Object.assign(tab, { - sessionId: message.payload.sessionId, - currentExerciseId: message.payload.currentExerciseId, - exerciseType: message.payload.exerciseType, - totalQuestions: message.payload.totalQuestions, - lastActivity: message.timestamp - }) - } - - // 檢查衝突 - checkSessionConflicts() - break - - case 'session-update': - if (activeTabs.has(message.tabId)) { - const tab = activeTabs.get(message.tabId)! - Object.assign(tab, { - currentExerciseId: message.payload.currentExerciseId, - completedQuestions: message.payload.completedQuestions, - lastActivity: message.timestamp - }) - } - break - - case 'session-complete': - if (activeTabs.has(message.tabId)) { - const tab = activeTabs.get(message.tabId)! - Object.assign(tab, { - sessionId: null, - currentExerciseId: null, - lastActivity: message.timestamp - }) - } - - // 同步進度 - syncProgressFromOtherTabs() - break - - case 'sync-request': - // 回應同步請求 - sendCurrentState() - break - - case 'sync-response': - // 處理其他標籤頁的狀態 - if (activeTabs.has(message.tabId)) { - const tab = activeTabs.get(message.tabId)! - Object.assign(tab, message.payload) - } else { - activeTabs.set(message.tabId, message.payload) - } - break - } - } - - // 發送當前狀態 - const sendCurrentState = () => { - const currentSession = vocabularyStore.currentSession - - broadcastMessage({ - type: 'sync-response', - tabId: currentTabId.value, - payload: { - tabId: currentTabId.value, - sessionId: currentSession?.id || null, - currentExerciseId: getCurrentExerciseId(), - startTime: currentSession?.start_time || new Date().toISOString(), - isActive: true, - lastActivity: new Date().toISOString(), - exerciseType: currentSession?.exercise_type || '', - completedQuestions: currentSession?.completed_questions || 0, - totalQuestions: currentSession?.total_questions || 0 - }, - timestamp: new Date().toISOString() - }) - } - - // 獲取當前練習ID - const getCurrentExerciseId = () => { - const currentSession = vocabularyStore.currentSession - if (!currentSession) return null - - const exercises = vocabularyStore.currentExercises - const index = currentSession.completed_questions - return exercises[index]?.id || null - } - - // 檢查會話衝突 - const checkSessionConflicts = () => { - const conflicts: string[] = [] - const currentSession = vocabularyStore.currentSession - - if (currentSession) { - for (const [tabId, session] of activeTabs.entries()) { - if (session.sessionId && session.exerciseType === currentSession.exercise_type) { - conflicts.push(tabId) - } - } - } - - syncConflicts.value = conflicts - } - - // 從其他標籤頁同步進度 - const syncProgressFromOtherTabs = async () => { - isSyncing.value = true - - try { - // 模擬從其他標籤頁同步進度 - // 實際實現中,這裡會處理來自其他標籤頁的學習進度數據 - await new Promise(resolve => setTimeout(resolve, 500)) - - console.log('Progress synced from other tabs') - } catch (error) { - console.error('Failed to sync progress from other tabs:', error) - } finally { - isSyncing.value = false - } - } - - // 開始學習會話 - const startMultiTabSession = async (vocabularyIds: string[], exerciseType: string) => { - try { - await vocabularyStore.startExerciseSession(vocabularyIds, exerciseType as any) - - const session = vocabularyStore.currentSession - if (session) { - broadcastMessage({ - type: 'session-start', - tabId: currentTabId.value, - payload: { - sessionId: session.id, - currentExerciseId: getCurrentExerciseId(), - exerciseType: session.exercise_type, - totalQuestions: session.total_questions - }, - timestamp: new Date().toISOString() - }) - } - } catch (error) { - console.error('Failed to start multi-tab session:', error) - throw error - } - } - - // 更新會話進度 - const updateSessionProgress = () => { - const session = vocabularyStore.currentSession - if (session) { - broadcastMessage({ - type: 'session-update', - tabId: currentTabId.value, - payload: { - currentExerciseId: getCurrentExerciseId(), - completedQuestions: session.completed_questions - }, - timestamp: new Date().toISOString() - }) - } - } - - // 完成學習會話 - const completeMultiTabSession = async () => { - try { - await vocabularyStore.completeSession() - - broadcastMessage({ - type: 'session-complete', - tabId: currentTabId.value, - payload: {}, - timestamp: new Date().toISOString() - }) - } catch (error) { - console.error('Failed to complete multi-tab session:', error) - throw error - } - } - - // 解決衝突 - const resolveConflict = (strategy: 'merge' | 'override' | 'cancel') => { - switch (strategy) { - case 'merge': - // 合併多個標籤頁的進度 - mergeTabProgress() - break - - case 'override': - // 使用當前標籤頁的進度覆蓋其他標籤頁 - overrideOtherTabs() - break - - case 'cancel': - // 取消當前標籤頁的會話 - vocabularyStore.resetCurrentSession() - break - } - - syncConflicts.value = [] - } - - // 合併標籤頁進度 - const mergeTabProgress = () => { - // 實現進度合併邏輯 - console.log('Merging progress from multiple tabs') - } - - // 覆蓋其他標籤頁 - const overrideOtherTabs = () => { - // 通知其他標籤頁停止會話 - broadcastMessage({ - type: 'session-complete', - tabId: currentTabId.value, - payload: { force: true }, - timestamp: new Date().toISOString() - }) - } - - // 心跳檢測 - const startHeartbeat = () => { - heartbeatInterval = window.setInterval(() => { - // 更新當前標籤頁的活動時間 - if (activeTabs.has(currentTabId.value)) { - const tab = activeTabs.get(currentTabId.value)! - tab.lastActivity = new Date().toISOString() - } - - // 清理非活躍的標籤頁 - const now = Date.now() - for (const [tabId, session] of activeTabs.entries()) { - const lastActivity = new Date(session.lastActivity).getTime() - if (now - lastActivity > 30000) { // 30秒無活動視為非活躍 - activeTabs.delete(tabId) - } - } - }, 5000) - } - - // 停止心跳檢測 - const stopHeartbeat = () => { - if (heartbeatInterval) { - clearInterval(heartbeatInterval) - heartbeatInterval = null - } - } - - // 清理資源 - const cleanup = () => { - if (broadcastChannel) { - broadcastMessage({ - type: 'tab-unregister', - tabId: currentTabId.value, - timestamp: new Date().toISOString() - }) - - broadcastChannel.close() - broadcastChannel = null - } - - stopHeartbeat() - } - - // 監聽詞彙存儲變化 - watch( - () => vocabularyStore.currentSession, - (newSession, oldSession) => { - if (newSession && !oldSession) { - // 會話開始 - updateSessionProgress() - } else if (!newSession && oldSession) { - // 會話結束 - completeMultiTabSession() - } else if (newSession && oldSession && newSession.completed_questions !== oldSession.completed_questions) { - // 進度更新 - updateSessionProgress() - } - }, - { deep: true } - ) - - // 組件掛載時初始化 - onMounted(() => { - initializeBroadcastChannel() - startHeartbeat() - - // 頁面卸載時清理 - window.addEventListener('beforeunload', cleanup) - }) - - // 組件卸載時清理 - onUnmounted(() => { - cleanup() - window.removeEventListener('beforeunload', cleanup) - }) - - return { - currentTabId: readonly(currentTabId), - activeTabs: readonly(activeTabs), - isSyncing: readonly(isSyncing), - syncConflicts: readonly(syncConflicts), - - // 方法 - startMultiTabSession, - updateSessionProgress, - completeMultiTabSession, - resolveConflict, - syncProgressFromOtherTabs - } -} \ No newline at end of file diff --git a/apps/web/src/layouts/AppLayout.vue b/apps/web/src/layouts/AppLayout.vue deleted file mode 100644 index f969757..0000000 --- a/apps/web/src/layouts/AppLayout.vue +++ /dev/null @@ -1,551 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/layouts/AuthLayout.vue b/apps/web/src/layouts/AuthLayout.vue deleted file mode 100644 index 993fe88..0000000 --- a/apps/web/src/layouts/AuthLayout.vue +++ /dev/null @@ -1,333 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/main.js b/apps/web/src/main.js new file mode 100644 index 0000000..6d8c3ec --- /dev/null +++ b/apps/web/src/main.js @@ -0,0 +1,76 @@ +// Main Application Entry Point +import './styles/main.scss'; +import { VocabularyApp } from './modules/VocabularyApp.js'; +import { VocabularyState } from './modules/VocabularyState.js'; + +class DramaLingApp { + constructor() { + console.log('🚀 Initializing Drama Ling App...'); + this.state = new VocabularyState(); + this.vocabulary = new VocabularyApp(this.state); + this.init(); + } + + async init() { + try { + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setup()); + } else { + await this.setup(); + } + } catch (error) { + console.error('App initialization failed:', error); + this.showError('應用程式初始化失敗'); + } + } + + async setup() { + const app = document.getElementById('app'); + + try { + // Initialize vocabulary app + await this.vocabulary.init(); + + // Replace loading spinner with vocabulary app + app.innerHTML = this.vocabulary.render(); + + // Bind event listeners + this.vocabulary.bindEvents(); + + console.log('📚 Drama Ling 詞彙學習應用已載入'); + } catch (error) { + console.error('Setup failed:', error); + this.showError('載入詞彙學習功能失敗'); + } + } + + showError(message) { + const app = document.getElementById('app'); + app.innerHTML = ` +
+ ❌ ${message} +

+ +
+ `; + } +} + +// Initialize app +new DramaLingApp(); \ No newline at end of file diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts deleted file mode 100644 index 1acd4e2..0000000 --- a/apps/web/src/main.ts +++ /dev/null @@ -1,35 +0,0 @@ -console.log('main.ts loading...') - -import { createApp } from 'vue' -import { Quasar, Notify, Loading, Dialog } from 'quasar' -import App from './App.vue' -import router from './router' -import { pinia } from './stores' - -// Quasar樣式 -import 'quasar/dist/quasar.css' -import '@quasar/extras/material-icons/material-icons.css' - -console.log('Creating Vue app...') - -const app = createApp(App) - -console.log('Adding Quasar...') -app.use(Quasar, { - plugins: { - Notify, - Loading, - Dialog - } -}) - -console.log('Adding Pinia...') -app.use(pinia) - -console.log('Adding router...') -app.use(router) - -console.log('Mounting Vue app...') -app.mount('#app') - -console.log('Vue app mounted!') \ No newline at end of file diff --git a/apps/web/src/modules/VocabularyApp.js b/apps/web/src/modules/VocabularyApp.js new file mode 100644 index 0000000..199a780 --- /dev/null +++ b/apps/web/src/modules/VocabularyApp.js @@ -0,0 +1,585 @@ +// Vocabulary Learning Application Main Module +import { AudioManager } from '../utils/AudioManager.js'; + +export class VocabularyApp { + constructor(state) { + this.state = state; + this.currentMode = 'flashcard'; + this.isCardFlipped = false; + this.isMobileMenuOpen = false; + this.unsubscribe = null; + this.audioManager = new AudioManager(); + } + + async init() { + // Subscribe to state changes + this.unsubscribe = this.state.subscribe((event, data) => { + this.onStateChange(event, data); + }); + + // Set initial current word if none selected + if (!this.state.getCurrentWord()) { + const reviewQueue = this.state.getReviewQueue(); + const newWords = this.state.getNewWords(1); + const nextWord = reviewQueue[0] || newWords[0]; + + if (nextWord) { + this.state.setCurrentWord(nextWord); + } + } + + console.log('📚 VocabularyApp initialized'); + } + + render() { + const progress = this.state.getProgress(); + const currentWord = this.state.getCurrentWord(); + + return ` +
+ ${this.renderSidebar()} + ${this.renderMainContent(progress, currentWord)} +
+ `; + } + + renderSidebar() { + return ` + + + + + + `; + } + + renderMainContent(progress, currentWord) { + return ` + +
+ ${this.renderPageHeader(progress)} + ${this.renderModeSelector()} + ${this.renderFlashcardSection(currentWord)} + ${this.renderVocabularyList()} +
+ `; + } + + renderPageHeader(progress) { + return ` + + `; + } + + renderModeSelector() { + const reviewQueue = this.state.getReviewQueue(); + const newWords = this.state.getNewWords(); + + return ` + +
+
+
🃏
+

記憶卡片

+

透過卡片翻轉快速記憶新詞彙

+
+ 待複習: ${reviewQueue.length} + 新詞彙: ${newWords.length} +
+
+ +
+
🎯
+

詞彙測驗

+

選擇題和填空題測試詞彙掌握

+
+ 正確率: 92% + 完成: 45/50 +
+
+ +
+
📖
+

情境學習

+

在真實情境中學習詞彙運用

+
+ 場景: 咖啡廳 + 進度: 3/5 +
+
+
+ `; + } + + renderFlashcardSection(currentWord) { + if (!currentWord) { + return ` +
+
+
+

🎉 太棒了!

+

目前沒有需要複習的詞彙

+ +
+
+
+ `; + } + + return ` + +
+
+
${currentWord.word}
+
${currentWord.phonetic}
+
${currentWord.definition}
+
+ "${currentWord.example}"
+ ${currentWord.translation} +
+ +
+ + + +
+ +
+ + + +
+
+
+ `; + } + + renderVocabularyList() { + const allWords = this.state.getAllWords(); + + return ` + +
+
+

我的詞彙庫

+
+ + + + +
+
+ +
+ ${allWords.map(word => this.renderVocabularyItem(word)).join('')} +
+
+ `; + } + + renderVocabularyItem(word) { + const statusClass = word.status === 'learned' ? 'learned' : + word.status === 'learning' ? 'learning' : ''; + + return ` +
+
+
+
+ ${word.word} + ${word.definition} +
+
+
+ +
+
+ `; + } + + bindEvents() { + // 手機版選單切換 + const mobileMenuBtn = document.getElementById('mobileMenuBtn'); + const sidebar = document.getElementById('sidebar'); + + if (mobileMenuBtn) { + mobileMenuBtn.addEventListener('click', () => { + this.isMobileMenuOpen = !this.isMobileMenuOpen; + sidebar?.classList.toggle('open'); + }); + } + + // 點擊外部關閉側邊欄 + document.addEventListener('click', (e) => { + if (window.innerWidth <= 1024 && + sidebar && !sidebar.contains(e.target) && + mobileMenuBtn && !mobileMenuBtn.contains(e.target)) { + this.isMobileMenuOpen = false; + sidebar.classList.remove('open'); + } + }); + + // 學習模式切換 + document.querySelectorAll('.mode-card').forEach(card => { + card.addEventListener('click', () => { + const mode = card.dataset.mode; + this.switchMode(mode); + }); + }); + + // 詞彙卡片控制 + this.bindFlashcardEvents(); + + // 詞彙清單互動 + this.bindVocabularyListEvents(); + + // 響應式處理 + window.addEventListener('resize', () => { + if (window.innerWidth > 1024) { + this.isMobileMenuOpen = false; + sidebar?.classList.remove('open'); + } + }); + } + + bindFlashcardEvents() { + const flipCardBtn = document.getElementById('flipCardBtn'); + const nextCardBtn = document.getElementById('nextCardBtn'); + const playAudioBtn = document.getElementById('playAudioBtn'); + + if (flipCardBtn) { + flipCardBtn.addEventListener('click', () => { + this.flipCard(); + }); + } + + if (nextCardBtn) { + nextCardBtn.addEventListener('click', () => { + this.nextCard(); + }); + } + + if (playAudioBtn) { + playAudioBtn.addEventListener('click', () => { + this.playAudio(); + }); + } + + // 難度按鈕 + document.querySelectorAll('.difficulty-btn').forEach(btn => { + btn.addEventListener('click', () => { + const difficulty = btn.dataset.difficulty; + this.selectDifficulty(difficulty); + }); + }); + } + + bindVocabularyListEvents() { + // 篩選標籤 + document.querySelectorAll('.filter-tab').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.filter-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + + const filter = tab.dataset.filter; + this.filterVocabulary(filter); + }); + }); + + // 詞彙項目點擊 + document.querySelectorAll('.vocabulary-item').forEach(item => { + item.addEventListener('click', (e) => { + if (!e.target.classList.contains('play-btn')) { + const wordId = item.dataset.wordId; + this.selectWord(wordId); + } + }); + }); + + // 播放按鈕 + document.querySelectorAll('.play-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const word = btn.dataset.word; + this.playWordAudio(word); + }); + }); + } + + // Event Handlers + switchMode(mode) { + this.currentMode = mode; + + // 更新UI + document.querySelectorAll('.mode-card').forEach(card => { + card.classList.toggle('active', card.dataset.mode === mode); + }); + + // 顯示對應的學習區域 + const flashcardSection = document.getElementById('flashcardSection'); + + if (mode === 'flashcard') { + flashcardSection?.classList.remove('hidden'); + } else { + flashcardSection?.classList.add('hidden'); + + if (mode === 'quiz') { + alert('詞彙測驗模式即將推出!'); + } else if (mode === 'context') { + alert('情境學習模式即將推出!'); + } + } + } + + flipCard() { + this.isCardFlipped = !this.isCardFlipped; + + // 更新UI + const definition = document.querySelector('.vocabulary-definition'); + const example = document.querySelector('.vocabulary-example'); + const difficultyButtons = document.querySelector('.difficulty-buttons'); + const flipBtn = document.getElementById('flipCardBtn'); + + if (this.isCardFlipped) { + definition?.classList.remove('hidden'); + example?.classList.remove('hidden'); + difficultyButtons?.classList.remove('hidden'); + if (flipBtn) flipBtn.textContent = '🔄 翻回'; + } else { + definition?.classList.add('hidden'); + example?.classList.add('hidden'); + difficultyButtons?.classList.add('hidden'); + if (flipBtn) flipBtn.textContent = '🔄 翻轉'; + } + } + + nextCard() { + // 重置翻轉狀態 + this.isCardFlipped = false; + + // 獲取下一個詞彙 + const reviewQueue = this.state.getReviewQueue(); + const newWords = this.state.getNewWords(1); + const nextWord = reviewQueue[0] || newWords[0]; + + if (nextWord) { + this.state.setCurrentWord(nextWord); + } else { + // 沒有更多詞彙時重新渲染 + this.updateFlashcardSection(); + } + } + + selectDifficulty(difficulty) { + const currentWord = this.state.getCurrentWord(); + if (currentWord) { + this.state.markWordReview(currentWord.id, difficulty); + this.nextCard(); + } + } + + selectWord(wordId) { + const words = this.state.getAllWords(); + const word = words.find(w => w.id === wordId); + if (word) { + this.state.setCurrentWord(word); + this.switchMode('flashcard'); + } + } + + filterVocabulary(filter) { + const items = document.querySelectorAll('.vocabulary-item'); + + items.forEach(item => { + const wordId = item.dataset.wordId; + const words = this.state.getAllWords(); + const word = words.find(w => w.id === wordId); + + if (!word) return; + + let show = true; + + switch (filter) { + case 'learning': + show = word.status === 'learning'; + break; + case 'learned': + show = word.status === 'learned'; + break; + case 'new': + show = word.status === 'new'; + break; + case 'all': + default: + show = true; + } + + item.style.display = show ? 'flex' : 'none'; + }); + } + + playAudio() { + const currentWord = this.state.getCurrentWord(); + if (currentWord) { + this.playWordAudio(currentWord.word); + } + } + + async playWordAudio(word) { + try { + console.log(`🔊 Playing pronunciation for: ${word}`); + await this.audioManager.speakWord(word); + } catch (error) { + console.error('Failed to play audio:', error); + // Fallback to alert for now + alert(`🔊 播放 "${word}" 的發音 (${error.message})`); + } + } + + // State change handlers + onStateChange(event, data) { + switch (event) { + case 'currentWordChanged': + this.updateFlashcardSection(); + break; + case 'progressUpdated': + this.updateProgressStats(); + break; + case 'wordUpdated': + case 'wordAdded': + this.updateVocabularyList(); + this.updateProgressStats(); + break; + } + } + + updateFlashcardSection() { + const flashcardSection = document.getElementById('flashcardSection'); + if (flashcardSection) { + const currentWord = this.state.getCurrentWord(); + flashcardSection.outerHTML = this.renderFlashcardSection(currentWord); + this.bindFlashcardEvents(); + } + } + + updateProgressStats() { + const progress = this.state.getProgress(); + const statValues = document.querySelectorAll('.stat-value'); + + if (statValues.length >= 3) { + statValues[0].textContent = progress.learned || 0; + statValues[1].textContent = progress.todayNew || 0; + statValues[2].textContent = `${progress.masteryRate || 0}%`; + } + } + + updateVocabularyList() { + const vocabularyItems = document.querySelector('.vocabulary-items'); + if (vocabularyItems) { + const allWords = this.state.getAllWords(); + vocabularyItems.innerHTML = allWords.map(word => this.renderVocabularyItem(word)).join(''); + this.bindVocabularyListEvents(); + } + } + + destroy() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } +} \ No newline at end of file diff --git a/apps/web/src/modules/VocabularyState.js b/apps/web/src/modules/VocabularyState.js new file mode 100644 index 0000000..8f52fd4 --- /dev/null +++ b/apps/web/src/modules/VocabularyState.js @@ -0,0 +1,322 @@ +// Vocabulary State Management +export class VocabularyState { + #words = new Map(); + #currentWord = null; + #currentMode = 'flashcard'; + #currentScene = 'coffee'; + #progress = { + learned: 0, + todayNew: 0, + masteryRate: 0 + }; + #listeners = new Set(); + + constructor() { + this.loadFromStorage(); + this.initializeDefaultData(); + } + + // State getters + getCurrentWord() { + return this.#currentWord; + } + + getCurrentMode() { + return this.#currentMode; + } + + getCurrentScene() { + return this.#currentScene; + } + + getProgress() { + return { ...this.#progress }; + } + + getAllWords() { + return Array.from(this.#words.values()); + } + + getWordsByStatus(status) { + return this.getAllWords().filter(word => word.status === status); + } + + // State setters + setCurrentWord(word) { + this.#currentWord = word; + this.notify('currentWordChanged', word); + } + + setCurrentMode(mode) { + this.#currentMode = mode; + this.notify('modeChanged', mode); + } + + setCurrentScene(scene) { + this.#currentScene = scene; + this.notify('sceneChanged', scene); + } + + // Word management + addWord(wordData) { + const word = { + id: wordData.id || Date.now().toString(), + word: wordData.word, + phonetic: wordData.phonetic, + definition: wordData.definition, + example: wordData.example, + translation: wordData.translation, + status: wordData.status || 'new', + difficulty: wordData.difficulty || 'normal', + reviewCount: wordData.reviewCount || 0, + correctCount: wordData.correctCount || 0, + lastReview: wordData.lastReview || null, + nextReview: wordData.nextReview || Date.now(), + createdAt: wordData.createdAt || Date.now(), + updatedAt: Date.now() + }; + + this.#words.set(word.id, word); + this.updateProgress(); + this.saveToStorage(); + this.notify('wordAdded', word); + + return word; + } + + updateWord(id, updates) { + const word = this.#words.get(id); + if (!word) return null; + + const updatedWord = { + ...word, + ...updates, + updatedAt: Date.now() + }; + + this.#words.set(id, updatedWord); + this.updateProgress(); + this.saveToStorage(); + this.notify('wordUpdated', updatedWord); + + return updatedWord; + } + + deleteWord(id) { + const word = this.#words.get(id); + if (!word) return false; + + this.#words.delete(id); + this.updateProgress(); + this.saveToStorage(); + this.notify('wordDeleted', word); + + return true; + } + + // Review logic + markWordReview(id, difficulty) { + const word = this.#words.get(id); + if (!word) return null; + + const now = Date.now(); + let nextReviewDelay; + + // Spaced repetition algorithm + switch (difficulty) { + case 'easy': + nextReviewDelay = 3 * 24 * 60 * 60 * 1000; // 3 days + break; + case 'hard': + nextReviewDelay = 10 * 60 * 1000; // 10 minutes + break; + case 'normal': + default: + nextReviewDelay = 24 * 60 * 60 * 1000; // 1 day + } + + const updates = { + reviewCount: word.reviewCount + 1, + lastReview: now, + nextReview: now + nextReviewDelay, + difficulty: difficulty + }; + + // Update status based on review performance + if (difficulty === 'easy' && word.reviewCount >= 2) { + updates.status = 'learned'; + } else if (word.status === 'new') { + updates.status = 'learning'; + } + + return this.updateWord(id, updates); + } + + markWordCorrect(id) { + const word = this.#words.get(id); + if (!word) return null; + + return this.updateWord(id, { + correctCount: word.correctCount + 1 + }); + } + + // Queue management + getReviewQueue() { + const now = Date.now(); + return this.getAllWords() + .filter(word => word.nextReview <= now) + .sort((a, b) => a.nextReview - b.nextReview); + } + + getNewWords(limit = 10) { + return this.getAllWords() + .filter(word => word.status === 'new') + .slice(0, limit); + } + + // Progress calculation + updateProgress() { + const allWords = this.getAllWords(); + const learned = allWords.filter(word => word.status === 'learned').length; + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const todayTimestamp = today.getTime(); + + const todayNew = allWords.filter(word => + word.createdAt >= todayTimestamp + ).length; + + const totalReviews = allWords.reduce((sum, word) => sum + word.reviewCount, 0); + const totalCorrect = allWords.reduce((sum, word) => sum + word.correctCount, 0); + const masteryRate = totalReviews > 0 ? Math.round((totalCorrect / totalReviews) * 100) : 0; + + this.#progress = { + learned, + todayNew, + masteryRate + }; + + this.notify('progressUpdated', this.#progress); + } + + // Event system + subscribe(listener) { + this.#listeners.add(listener); + return () => this.#listeners.delete(listener); + } + + notify(event, data) { + this.#listeners.forEach(listener => { + try { + listener(event, data); + } catch (error) { + console.error('State listener error:', error); + } + }); + } + + // Persistence + saveToStorage() { + try { + const data = { + words: Array.from(this.#words.values()), + currentMode: this.#currentMode, + currentScene: this.#currentScene, + currentWordId: this.#currentWord?.id || null, + progress: this.#progress + }; + + localStorage.setItem('dramaling-vocabulary', JSON.stringify(data)); + } catch (error) { + console.error('Failed to save to storage:', error); + } + } + + loadFromStorage() { + try { + const data = localStorage.getItem('dramaling-vocabulary'); + if (!data) return; + + const parsed = JSON.parse(data); + + // Restore words + if (parsed.words) { + this.#words.clear(); + parsed.words.forEach(word => { + this.#words.set(word.id, word); + }); + } + + // Restore current state + this.#currentMode = parsed.currentMode || 'flashcard'; + this.#currentScene = parsed.currentScene || 'coffee'; + this.#progress = parsed.progress || this.#progress; + + // Restore current word + if (parsed.currentWordId) { + this.#currentWord = this.#words.get(parsed.currentWordId); + } + + this.updateProgress(); + } catch (error) { + console.error('Failed to load from storage:', error); + } + } + + // Initialize with sample data + initializeDefaultData() { + if (this.#words.size === 0) { + const sampleWords = [ + { + word: 'confidence', + phonetic: '/ˈkɒnfɪdəns/', + definition: '信心;自信心;把握', + example: 'She spoke with great confidence during the presentation.', + translation: '她在簡報中表現出很大的自信。', + status: 'learning' + }, + { + word: 'presentation', + phonetic: '/ˌprezənˈteɪʃən/', + definition: '簡報;呈現', + example: 'The presentation was very informative.', + translation: '這個簡報很有資訊性。', + status: 'learning' + }, + { + word: 'colleague', + phonetic: '/ˈkɒliːɡ/', + definition: '同事;同僚', + example: 'I discussed the project with my colleague.', + translation: '我和同事討論了這個專案。', + status: 'new' + }, + { + word: 'opportunity', + phonetic: '/ˌɒpəˈtjuːnɪti/', + definition: '機會;時機', + example: 'This is a great opportunity to learn.', + translation: '這是一個學習的好機會。', + status: 'learned' + }, + { + word: 'achievement', + phonetic: '/əˈtʃiːvmənt/', + definition: '成就;成績', + example: 'Graduating from university was a great achievement.', + translation: '從大學畢業是一個偉大的成就。', + status: 'learning' + } + ]; + + sampleWords.forEach(wordData => this.addWord(wordData)); + + // Set first word as current + if (this.#words.size > 0) { + this.#currentWord = this.getAllWords()[0]; + } + } + } +} \ No newline at end of file diff --git a/apps/web/src/router/index-full.ts b/apps/web/src/router/index-full.ts deleted file mode 100644 index 53fdfd7..0000000 --- a/apps/web/src/router/index-full.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' -import { useAuthStore } from '@/stores/auth' - -const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'home', - component: () => import('@/views/HomeView.vue'), - meta: { - title: 'Drama Ling - 戲劇式語言學習', - requiresAuth: false - } - }, - { - path: '/auth', - component: () => import('@/layouts/AuthLayout.vue'), - children: [ - { - path: 'login', - name: 'login', - component: () => import('@/views/auth/LoginView.vue'), - meta: { - title: '登入 - Drama Ling', - requiresAuth: false - } - }, - { - path: 'register', - name: 'register', - component: () => import('@/views/auth/RegisterView.vue'), - meta: { - title: '註冊 - Drama Ling', - requiresAuth: false - } - }, - { - path: 'forgot-password', - name: 'forgot-password', - component: () => import('@/views/auth/ForgotPasswordView.vue'), - meta: { - title: '忘記密碼 - Drama Ling', - requiresAuth: false - } - } - ] - }, - { - path: '/learning', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'learning', - component: () => import('@/views/learning/LearningHomeView.vue'), - meta: { - title: '學習地圖 - Drama Ling' - } - }, - { - path: 'vocabulary', - name: 'vocabulary', - component: () => import('@/views/learning/VocabularyView.vue'), - meta: { - title: '詞彙學習 - Drama Ling' - } - }, - { - path: 'dialogue/:id', - name: 'dialogue', - component: () => import('@/views/learning/DialogueView.vue'), - meta: { - title: '對話練習 - Drama Ling' - }, - props: true - }, - { - path: 'roleplay/:id', - name: 'roleplay', - component: () => import('@/views/learning/RoleplayView.vue'), - meta: { - title: '角色扮演 - Drama Ling' - }, - props: true - }, - { - path: 'pronunciation/:id', - name: 'pronunciation', - component: () => import('@/views/learning/PronunciationView.vue'), - meta: { - title: '發音練習 - Drama Ling' - }, - props: true - } - ] - }, - { - path: '/profile', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'profile', - component: () => import('@/views/profile/ProfileView.vue'), - meta: { - title: '個人檔案 - Drama Ling' - } - }, - { - path: 'progress', - name: 'progress', - component: () => import('@/views/profile/ProgressView.vue'), - meta: { - title: '學習進度 - Drama Ling' - } - }, - { - path: 'settings', - name: 'settings', - component: () => import('@/views/profile/SettingsView.vue'), - meta: { - title: '設定 - Drama Ling' - } - } - ] - }, - { - path: '/shop', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'shop', - component: () => import('@/views/shop/ShopView.vue'), - meta: { - title: '商店 - Drama Ling' - } - }, - { - path: 'subscription', - name: 'subscription', - component: () => import('@/views/shop/SubscriptionView.vue'), - meta: { - title: '訂閱方案 - Drama Ling' - } - } - ] - }, - { - path: '/offline', - name: 'offline', - component: () => import('@/views/OfflineView.vue'), - meta: { - title: '離線模式 - Drama Ling', - requiresAuth: false - } - }, - { - path: '/:pathMatch(.*)*', - name: 'not-found', - component: () => import('@/views/NotFoundView.vue'), - meta: { - title: '頁面未找到 - Drama Ling', - requiresAuth: false - } - } -] - -const router = createRouter({ - history: createWebHistory(), - routes, - scrollBehavior(to, from, savedPosition) { - if (savedPosition) { - return savedPosition - } - if (to.hash) { - return { el: to.hash } - } - return { top: 0 } - } -}) - -router.beforeEach(async (to, from, next) => { - const authStore = useAuthStore() - - // 設定頁面標題 - if (to.meta.title) { - document.title = to.meta.title as string - } - - // 檢查認證需求 - if (to.meta.requiresAuth && !authStore.isAuthenticated) { - // 保存目標路徑,登入後跳轉 - authStore.setRedirectPath(to.fullPath) - next({ name: 'login' }) - return - } - - // 已登入用戶訪問登入頁面時跳轉到首頁 - if ((to.name === 'login' || to.name === 'register') && authStore.isAuthenticated) { - next({ name: 'learning' }) - return - } - - next() -}) - -export default router \ No newline at end of file diff --git a/apps/web/src/router/index-minimal.ts b/apps/web/src/router/index-minimal.ts deleted file mode 100644 index d96ee6b..0000000 --- a/apps/web/src/router/index-minimal.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' - -const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'home', - component: () => import('@/views/HomeView.vue'), - meta: { - title: 'Drama Ling - 戲劇式語言學習', - description: 'AI驅動的情境式語言學習應用' - } - } -] - -const router = createRouter({ - history: createWebHistory(), - routes -}) - -export default router \ No newline at end of file diff --git a/apps/web/src/router/index.ts b/apps/web/src/router/index.ts deleted file mode 100644 index 7797567..0000000 --- a/apps/web/src/router/index.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' -import { useAuthStore } from '@/stores/auth' - -const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'home', - component: () => import('@/views/HomeView.vue'), - meta: { - title: 'Drama Ling - 戲劇式語言學習', - requiresAuth: false - } - }, - { - path: '/auth', - component: () => import('@/layouts/AuthLayout.vue'), - children: [ - { - path: 'login', - name: 'login', - component: () => import('@/views/auth/LoginView.vue'), - meta: { - title: '登入 - Drama Ling', - requiresAuth: false - } - }, - { - path: 'register', - name: 'register', - component: () => import('@/views/auth/RegisterView.vue'), - meta: { - title: '註冊 - Drama Ling', - requiresAuth: false - } - }, - { - path: 'forgot-password', - name: 'forgot-password', - component: () => import('@/views/auth/ForgotPasswordView.vue'), - meta: { - title: '忘記密碼 - Drama Ling', - requiresAuth: false - } - } - ] - }, - { - path: '/learning', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'learning', - component: () => import('@/views/learning/LearningHomeView.vue'), - meta: { - title: '學習地圖 - Drama Ling' - } - }, - { - path: 'vocabulary', - name: 'vocabulary', - component: () => import('@/views/learning/VocabularyViewSimple.vue'), - meta: { - title: '詞彙學習 - Drama Ling' - } - }, - { - path: 'vocabulary-native', - name: 'vocabulary-native', - component: () => import('@/views/learning/VocabularyViewNative.vue'), - meta: { - title: '詞彙學習 (原生樣式) - Drama Ling' - } - }, - { - path: 'vocabulary/practice', - name: 'vocabulary-practice', - component: () => import('@/views/learning/VocabularyPracticeView.vue'), - meta: { - title: '詞彙練習 - Drama Ling' - } - }, - { - path: 'vocabulary/choice-practice', - name: 'vocabulary-choice-practice', - component: () => import('@/views/learning/VocabularyChoicePracticeView.vue'), - meta: { - title: '選擇題練習 - Drama Ling' - } - }, - { - path: 'vocabulary/choice-results/:sessionId', - name: 'vocabulary-choice-results', - component: () => import('@/views/learning/VocabularyChoiceResultsView.vue'), - meta: { - title: '練習結果 - Drama Ling' - }, - props: true - }, - { - path: 'vocabulary/matching-practice', - name: 'vocabulary-matching-practice', - component: () => import('@/views/learning/VocabularyMatchingPracticeView.vue'), - meta: { - title: '圖片匹配練習 - Drama Ling' - } - }, - { - path: 'vocabulary/reorganize-practice', - name: 'vocabulary-reorganize-practice', - component: () => import('@/views/learning/VocabularyReorganizePracticeView.vue'), - meta: { - title: '句子重組練習 - Drama Ling' - } - }, - { - path: 'vocabulary/analytics', - name: 'vocabulary-analytics', - component: () => import('@/views/learning/VocabularyAnalyticsDashboard.vue'), - meta: { - title: '詞彙學習分析儀表板 - Drama Ling' - } - }, - { - path: 'vocabulary/review', - name: 'vocabulary-review', - component: () => import('@/views/learning/VocabularyReviewMain.vue'), - meta: { - title: '智能複習系統 - Drama Ling' - } - }, - { - path: 'dialogue/:id', - name: 'dialogue', - component: () => import('@/views/learning/DialogueView.vue'), - meta: { - title: '對話練習 - Drama Ling' - }, - props: true - }, - { - path: 'roleplay/:id', - name: 'roleplay', - component: () => import('@/views/learning/RoleplayView.vue'), - meta: { - title: '角色扮演 - Drama Ling' - }, - props: true - }, - { - path: 'pronunciation/:id', - name: 'pronunciation', - component: () => import('@/views/learning/PronunciationView.vue'), - meta: { - title: '發音練習 - Drama Ling' - }, - props: true - } - ] - }, - { - path: '/profile', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'profile', - component: () => import('@/views/profile/ProfileView.vue'), - meta: { - title: '個人檔案 - Drama Ling' - } - }, - { - path: 'progress', - name: 'progress', - component: () => import('@/views/profile/ProgressView.vue'), - meta: { - title: '學習進度 - Drama Ling' - } - }, - { - path: 'settings', - name: 'settings', - component: () => import('@/views/profile/SettingsView.vue'), - meta: { - title: '設定 - Drama Ling' - } - } - ] - }, - { - path: '/shop', - component: () => import('@/layouts/AppLayout.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '', - name: 'shop', - component: () => import('@/views/shop/ShopView.vue'), - meta: { - title: '商店 - Drama Ling' - } - }, - { - path: 'subscription', - name: 'subscription', - component: () => import('@/views/shop/SubscriptionView.vue'), - meta: { - title: '訂閱方案 - Drama Ling' - } - } - ] - }, - { - path: '/offline', - name: 'offline', - component: () => import('@/views/OfflineView.vue'), - meta: { - title: '離線模式 - Drama Ling', - requiresAuth: false - } - }, - { - path: '/:pathMatch(.*)*', - name: 'not-found', - component: () => import('@/views/NotFoundView.vue'), - meta: { - title: '頁面未找到 - Drama Ling', - requiresAuth: false - } - } -] - -const router = createRouter({ - history: createWebHistory(), - routes, - scrollBehavior(to, from, savedPosition) { - if (savedPosition) { - return savedPosition - } - if (to.hash) { - return { el: to.hash } - } - return { top: 0 } - } -}) - -router.beforeEach(async (to, from, next) => { - const authStore = useAuthStore() - - // 設定頁面標題 - if (to.meta.title) { - document.title = to.meta.title as string - } - - console.log('Route to:', to.path, 'Auth:', authStore.isAuthenticated) - - // 檢查認證需求 - if (to.meta.requiresAuth && !authStore.isAuthenticated) { - // 保存目標路徑,登入後跳轉 - authStore.setRedirectPath(to.fullPath) - next({ name: 'login' }) - return - } - - // 已登入用戶訪問登入頁面時跳轉到首頁 - if ((to.name === 'login' || to.name === 'register') && authStore.isAuthenticated) { - next({ name: 'learning' }) - return - } - - next() -}) - -export default router \ No newline at end of file diff --git a/apps/web/src/stores/auth.ts b/apps/web/src/stores/auth.ts deleted file mode 100644 index dec753d..0000000 --- a/apps/web/src/stores/auth.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { User } from '@/types/user' - -export interface LoginCredentials { - email: string - password: string - rememberMe?: boolean -} - -export interface RegisterData { - email: string - password: string - confirmPassword: string - username: string - agreeToTerms: boolean -} - -export const useAuthStore = defineStore('auth', () => { - // 狀態 - const user = ref(null) - const token = ref(null) - const refreshToken = ref(null) - const isLoading = ref(false) - const error = ref(null) - const redirectPath = ref('/') - - // 計算屬性 - const isAuthenticated = computed(() => !!token.value && !!user.value) - const userDisplayName = computed(() => user.value?.username || user.value?.email || '') - - // 動作 - const login = async (credentials: LoginCredentials) => { - isLoading.value = true - error.value = null - - try { - // 開發模式:允許特定測試帳戶直接登入 - if (import.meta.env.DEV && - credentials.email === 'test@dramaling.com' && - credentials.password === 'test123') { - - // 模擬API響應延遲 - await new Promise(resolve => setTimeout(resolve, 1000)) - - // 設定測試用戶資料 - token.value = 'dev_token_' + Date.now() - refreshToken.value = 'dev_refresh_token_' + Date.now() - user.value = { - id: 'dev_user_1', - email: 'test@dramaling.com', - username: 'TestUser', - displayName: '測試用戶', - avatar: '/images/default-avatar.png', - verified: true, - subscription: { - plan: 'premium', - status: 'active', - expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() - }, - preferences: { - language: 'zh-TW', - theme: 'light', - notifications: true - }, - createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), - updatedAt: new Date().toISOString() - } - - return { success: true } - } - - // 實際API調用(生產模式或非測試帳戶) - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(credentials) - }) - - if (!response.ok) { - // 開發模式下提供有用的錯誤信息 - if (import.meta.env.DEV) { - throw new Error('API未連接,請使用測試帳戶:\n📧 test@dramaling.com\n🔑 test123') - } - throw new Error('登入失敗,請檢查帳戶資訊') - } - - const data = await response.json() - - // 設定認證資料 - token.value = data.token - refreshToken.value = data.refreshToken - user.value = data.user - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '登入失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const register = async (data: RegisterData) => { - isLoading.value = true - error.value = null - - try { - // TODO: 實際API調用 - const response = await fetch('/api/auth/register', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }) - - if (!response.ok) { - throw new Error('註冊失敗') - } - - const responseData = await response.json() - - // 自動登入 - token.value = responseData.token - refreshToken.value = responseData.refreshToken - user.value = responseData.user - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '註冊失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const logout = async () => { - isLoading.value = true - - try { - // TODO: 呼叫登出API - if (token.value) { - await fetch('/api/auth/logout', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token.value}` - } - }) - } - } catch (err) { - console.error('登出API錯誤:', err) - } finally { - // 清除本地狀態 - user.value = null - token.value = null - refreshToken.value = null - error.value = null - isLoading.value = false - redirectPath.value = '/' - } - } - - const refreshTokenAction = async () => { - if (!refreshToken.value) { - return false - } - - try { - const response = await fetch('/api/auth/refresh', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - refreshToken: refreshToken.value - }) - }) - - if (!response.ok) { - throw new Error('Token刷新失敗') - } - - const data = await response.json() - token.value = data.token - - return true - } catch (err) { - console.error('Token刷新錯誤:', err) - await logout() - return false - } - } - - const updateProfile = async (profileData: Partial) => { - if (!user.value) return { success: false, error: '用戶未登入' } - - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/user/profile', { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token.value}` - }, - body: JSON.stringify(profileData) - }) - - if (!response.ok) { - throw new Error('更新檔案失敗') - } - - const updatedUser = await response.json() - user.value = { ...user.value, ...updatedUser } - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '更新檔案失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const forgotPassword = async (email: string) => { - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/auth/forgot-password', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ email }) - }) - - if (!response.ok) { - throw new Error('發送重設密碼郵件失敗') - } - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '發送重設密碼郵件失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const resetPassword = async (token: string, password: string) => { - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/auth/reset-password', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ token, password }) - }) - - if (!response.ok) { - throw new Error('重設密碼失敗') - } - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '重設密碼失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const setRedirectPath = (path: string) => { - redirectPath.value = path - } - - const clearError = () => { - error.value = null - } - - const initialize = async () => { - // 應用啟動時檢查是否有有效的token - if (token.value && !user.value) { - await refreshTokenAction() - } - } - - return { - // 狀態 - user, - token, - refreshToken, - isLoading, - error, - redirectPath, - - // 計算屬性 - isAuthenticated, - userDisplayName, - - // 動作 - login, - register, - logout, - refreshTokenAction, - updateProfile, - forgotPassword, - resetPassword, - setRedirectPath, - clearError, - initialize - } -}, { - persist: { - paths: ['user', 'token', 'refreshToken', 'redirectPath'] - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/index.ts b/apps/web/src/stores/index.ts deleted file mode 100644 index 6a739e6..0000000 --- a/apps/web/src/stores/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createPinia } from 'pinia' -import { createPersistedState } from 'pinia-plugin-persistedstate' - -const pinia = createPinia() - -// 配置持久化插件 -pinia.use(createPersistedState({ - storage: localStorage, - auto: true -})) - -export { pinia } -export * from './auth' -export * from './user' -export * from './learning' -export * from './ui' \ No newline at end of file diff --git a/apps/web/src/stores/learning.ts b/apps/web/src/stores/learning.ts deleted file mode 100644 index cd577e2..0000000 --- a/apps/web/src/stores/learning.ts +++ /dev/null @@ -1,364 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { Lesson, Course, LearningSession, VocabularyCard } from '@/types/learning' - -export const useLearningStore = defineStore('learning', () => { - // 狀態 - const currentCourse = ref(null) - const currentLesson = ref(null) - const currentSession = ref(null) - const vocabulary = ref([]) - const courses = ref([]) - const recentLessons = ref([]) - const isLoading = ref(false) - const error = ref(null) - - // 學習狀態 - const sessionStartTime = ref(null) - const currentQuestionIndex = ref(0) - const sessionAnswers = ref([]) - const sessionScore = ref(0) - - // 計算屬性 - const availableCourses = computed(() => { - return courses.value.filter(course => course.isAvailable) - }) - - const completedCourses = computed(() => { - return courses.value.filter(course => course.progress === 100) - }) - - const inProgressCourses = computed(() => { - return courses.value.filter(course => course.progress > 0 && course.progress < 100) - }) - - const currentProgress = computed(() => { - if (!currentCourse.value) return 0 - return currentCourse.value.progress || 0 - }) - - const sessionProgress = computed(() => { - if (!currentSession.value?.questions?.length) return 0 - return (currentQuestionIndex.value / currentSession.value.questions.length) * 100 - }) - - const masteredVocabulary = computed(() => { - return vocabulary.value.filter(card => card.masteryLevel >= 5) - }) - - const reviewDueVocabulary = computed(() => { - const now = new Date() - return vocabulary.value.filter(card => - card.nextReviewDate && new Date(card.nextReviewDate) <= now - ) - }) - - // 動作 - const fetchCourses = async () => { - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/learning/courses', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取課程失敗') - } - - courses.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err.message : '獲取課程失敗' - } finally { - isLoading.value = false - } - } - - const fetchCourse = async (courseId: string) => { - isLoading.value = true - - try { - const response = await fetch(`/api/learning/courses/${courseId}`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取課程詳情失敗') - } - - currentCourse.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err.message : '獲取課程詳情失敗' - } finally { - isLoading.value = false - } - } - - const startLesson = async (lessonId: string) => { - isLoading.value = true - sessionStartTime.value = new Date() - currentQuestionIndex.value = 0 - sessionAnswers.value = [] - sessionScore.value = 0 - - try { - const response = await fetch(`/api/learning/lessons/${lessonId}/start`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('開始課程失敗') - } - - const sessionData = await response.json() - currentLesson.value = sessionData.lesson - currentSession.value = sessionData.session - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '開始課程失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const submitAnswer = async (answer: any) => { - if (!currentSession.value) return { success: false } - - try { - const response = await fetch(`/api/learning/sessions/${currentSession.value.id}/answer`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - }, - body: JSON.stringify({ - questionIndex: currentQuestionIndex.value, - answer - }) - }) - - if (!response.ok) { - throw new Error('提交答案失敗') - } - - const result = await response.json() - - // 更新本地狀態 - sessionAnswers.value.push({ - questionIndex: currentQuestionIndex.value, - answer, - isCorrect: result.isCorrect, - feedback: result.feedback - }) - - if (result.isCorrect) { - sessionScore.value += result.points || 10 - } - - // 移動到下一題 - currentQuestionIndex.value += 1 - - return { - success: true, - isCorrect: result.isCorrect, - feedback: result.feedback, - points: result.points - } - } catch (err) { - error.value = err instanceof Error ? err.message : '提交答案失敗' - return { success: false, error: error.value } - } - } - - const completeSession = async () => { - if (!currentSession.value || !sessionStartTime.value) return { success: false } - - const duration = Date.now() - sessionStartTime.value.getTime() - - try { - const response = await fetch(`/api/learning/sessions/${currentSession.value.id}/complete`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - }, - body: JSON.stringify({ - duration, - score: sessionScore.value, - answers: sessionAnswers.value - }) - }) - - if (!response.ok) { - throw new Error('完成學習階段失敗') - } - - const result = await response.json() - - // 重設狀態 - currentSession.value = null - sessionStartTime.value = null - currentQuestionIndex.value = 0 - sessionAnswers.value = [] - sessionScore.value = 0 - - return { - success: true, - result - } - } catch (err) { - error.value = err instanceof Error ? err.message : '完成學習階段失敗' - return { success: false, error: error.value } - } - } - - const fetchVocabulary = async () => { - try { - const response = await fetch('/api/learning/vocabulary', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取詞彙失敗') - } - - vocabulary.value = await response.json() - } catch (err) { - console.error('獲取詞彙錯誤:', err) - } - } - - const updateVocabularyMastery = async (cardId: string, isCorrect: boolean) => { - try { - const response = await fetch(`/api/learning/vocabulary/${cardId}/review`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - }, - body: JSON.stringify({ isCorrect }) - }) - - if (!response.ok) { - throw new Error('更新詞彙熟練度失敗') - } - - const updatedCard = await response.json() - - // 更新本地狀態 - const index = vocabulary.value.findIndex(card => card.id === cardId) - if (index !== -1) { - vocabulary.value[index] = updatedCard - } - - return { success: true } - } catch (err) { - console.error('更新詞彙熟練度錯誤:', err) - return { success: false } - } - } - - const pauseSession = () => { - if (currentSession.value) { - currentSession.value.isPaused = true - } - } - - const resumeSession = () => { - if (currentSession.value) { - currentSession.value.isPaused = false - } - } - - const skipQuestion = () => { - if (currentSession.value && currentQuestionIndex.value < currentSession.value.questions.length - 1) { - currentQuestionIndex.value += 1 - - // 記錄跳過的答案 - sessionAnswers.value.push({ - questionIndex: currentQuestionIndex.value - 1, - answer: null, - isCorrect: false, - skipped: true - }) - } - } - - const fetchRecentLessons = async () => { - try { - const response = await fetch('/api/learning/recent-lessons', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取最近課程失敗') - } - - recentLessons.value = await response.json() - } catch (err) { - console.error('獲取最近課程錯誤:', err) - } - } - - const resetCurrentSession = () => { - currentSession.value = null - currentLesson.value = null - sessionStartTime.value = null - currentQuestionIndex.value = 0 - sessionAnswers.value = [] - sessionScore.value = 0 - } - - return { - // 狀態 - currentCourse, - currentLesson, - currentSession, - vocabulary, - courses, - recentLessons, - isLoading, - error, - sessionStartTime, - currentQuestionIndex, - sessionAnswers, - sessionScore, - - // 計算屬性 - availableCourses, - completedCourses, - inProgressCourses, - currentProgress, - sessionProgress, - masteredVocabulary, - reviewDueVocabulary, - - // 動作 - fetchCourses, - fetchCourse, - startLesson, - submitAnswer, - completeSession, - fetchVocabulary, - updateVocabularyMastery, - pauseSession, - resumeSession, - skipQuestion, - fetchRecentLessons, - resetCurrentSession - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/practice.ts b/apps/web/src/stores/practice.ts deleted file mode 100644 index a63dea8..0000000 --- a/apps/web/src/stores/practice.ts +++ /dev/null @@ -1,422 +0,0 @@ -// Practice System Store (練習系統狀態管理) -// 依據 practice.ts 類型定義和 function-specs 練習模式需求 - -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { - PracticeType, - PracticeSession, - PracticeQuestion, - ChoiceQuestion, - MatchingQuestion, - ReorganizeQuestion, - UserAnswer, - PracticeResult, - PracticeConfig, - ResponseTimer, - PracticeStats, - WrongQuestionRecord -} from '@/types/practice' - -export const usePracticeStore = defineStore('practice', () => { - // 狀態定義 - const currentSession = ref(null) - const practiceConfig = ref({ - questionsPerSession: 10, - timePerQuestion: 30, - enableLives: true, - maxLives: 3, - enableHints: false, - enableAudio: true, - autoAdvance: false, - showCorrectAnswer: true, - difficulty: 3 - }) - const responseTimer = ref({ - startTime: 0, - endTime: undefined, - isRunning: false - }) - const practiceStats = ref({ - totalSessions: 0, - totalQuestions: 0, - correctAnswers: 0, - averageScore: 0, - averageResponseTime: 0, - fastestResponseTime: 0, - longestStreak: 0, - currentStreak: 0, - masteredVocabulary: 0, - practiceTimeToday: 0, - practiceTimeThisWeek: 0 - }) - const wrongQuestions = ref([]) - - // Getters - const isSessionActive = computed(() => currentSession.value !== null && !currentSession.value.isCompleted) - const currentQuestion = computed(() => { - if (!currentSession.value) return null - return currentSession.value.questions[currentSession.value.currentQuestionIndex] || null - }) - const sessionProgress = computed(() => { - if (!currentSession.value) return 0 - return (currentSession.value.currentQuestionIndex / currentSession.value.totalQuestions) * 100 - }) - const canContinue = computed(() => { - if (!currentSession.value) return false - return currentSession.value.lives > 0 - }) - - // Actions - 會話管理 - function startPracticeSession(vocabularyIds: string[], practiceType: PracticeType): string { - const sessionId = generateSessionId() - const questions = generateQuestions(vocabularyIds, practiceType) - - currentSession.value = { - id: sessionId, - vocabularyIds, - practiceType, - questions, - answers: [], - startTime: new Date(), - isCompleted: false, - currentQuestionIndex: 0, - score: 0, - totalQuestions: questions.length, - correctAnswers: 0, - averageResponseTime: 0, - lives: practiceConfig.value.maxLives, - maxLives: practiceConfig.value.maxLives - } - - return sessionId - } - - function submitAnswer(answer: Omit): boolean { - if (!currentSession.value || !currentQuestion.value) return false - - stopTimer() - - const isCorrect = validateAnswer(currentQuestion.value, answer) - const completeAnswer: UserAnswer = { - ...answer, - submittedAt: new Date(), - isCorrect - } - - currentSession.value.answers.push(completeAnswer) - - if (isCorrect) { - currentSession.value.correctAnswers++ - practiceStats.value.currentStreak++ - if (practiceStats.value.currentStreak > practiceStats.value.longestStreak) { - practiceStats.value.longestStreak = practiceStats.value.currentStreak - } - } else { - practiceStats.value.currentStreak = 0 - if (practiceConfig.value.enableLives) { - currentSession.value.lives-- - } - recordWrongQuestion(currentQuestion.value, currentSession.value.practiceType) - } - - updateSessionStats() - return isCorrect - } - - function nextQuestion(): boolean { - if (!currentSession.value) return false - - currentSession.value.currentQuestionIndex++ - - if (currentSession.value.currentQuestionIndex >= currentSession.value.totalQuestions) { - completeSession() - return false - } - - if (!canContinue.value) { - completeSession() - return false - } - - return true - } - - function completeSession(): PracticeResult | null { - if (!currentSession.value) return null - - currentSession.value.isCompleted = true - currentSession.value.endTime = new Date() - - const result = generatePracticeResult(currentSession.value) - updateGlobalStats(result) - - return result - } - - // Actions - 計時器管理 - function startTimer(): void { - responseTimer.value = { - startTime: performance.now(), - endTime: undefined, - isRunning: true - } - } - - function stopTimer(): number { - if (!responseTimer.value.isRunning) return 0 - - responseTimer.value.endTime = performance.now() - responseTimer.value.isRunning = false - - return responseTimer.value.endTime - responseTimer.value.startTime - } - - function resetTimer(): void { - responseTimer.value = { - startTime: 0, - endTime: undefined, - isRunning: false - } - } - - // 工具函數 - function generateSessionId(): string { - return `practice_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` - } - - function generateQuestions(vocabularyIds: string[], practiceType: PracticeType): (ChoiceQuestion | MatchingQuestion | ReorganizeQuestion)[] { - // TODO: 實際實現需要從後端API獲取詞彙數據並生成對應練習題 - // 這裡返回模擬數據結構 - return vocabularyIds.map((vocabId, index) => { - const baseQuestion = { - id: `q_${index}`, - vocabularyId: vocabId, - vocabularyWord: `word_${index}`, - timeLimit: practiceConfig.value.timePerQuestion, - difficulty: practiceConfig.value.difficulty, - content: `Practice question for ${vocabId}` - } - - switch (practiceType) { - case 'choice': - return { - ...baseQuestion, - type: 'definition' as const, - options: [ - { id: 'opt1', text: 'Option 1', isCorrect: true }, - { id: 'opt2', text: 'Option 2', isCorrect: false }, - { id: 'opt3', text: 'Option 3', isCorrect: false }, - { id: 'opt4', text: 'Option 4', isCorrect: false } - ], - correctAnswerId: 'opt1' - } as ChoiceQuestion - case 'matching': - return { - ...baseQuestion, - type: 'image' as const, - images: [ - { id: 'img1', url: '/mock-image1.jpg', vocabularyId: vocabId } - ], - correctPairs: [ - { imageId: 'img1', vocabularyId: vocabId } - ] - } as MatchingQuestion - case 'reorganize': - return { - ...baseQuestion, - type: 'example' as const, - sentence: 'This is a test sentence', - words: [ - { id: 'w1', text: 'This' }, - { id: 'w2', text: 'is' }, - { id: 'w3', text: 'a' }, - { id: 'w4', text: 'test' }, - { id: 'w5', text: 'sentence' } - ], - correctOrder: ['w1', 'w2', 'w3', 'w4', 'w5'] - } as ReorganizeQuestion - default: - throw new Error(`Unknown practice type: ${practiceType}`) - } - }) - } - - function validateAnswer(question: ChoiceQuestion | MatchingQuestion | ReorganizeQuestion, answer: Omit): boolean { - if ('options' in question && 'correctAnswerId' in question) { - // 選擇題 - return answer.selectedOptionId === question.correctAnswerId - } else if ('correctPairs' in question && question.correctPairs) { - // 圖片匹配 - if (!answer.selectedPairs) return false - return question.correctPairs.every(correctPair => - answer.selectedPairs!.some(selectedPair => - selectedPair.imageId === correctPair.imageId && - selectedPair.vocabularyId === correctPair.vocabularyId - ) - ) - } else if ('correctOrder' in question && question.correctOrder) { - // 句子重組 - if (!answer.wordOrder) return false - return JSON.stringify(answer.wordOrder) === JSON.stringify(question.correctOrder) - } - return false - } - - function updateSessionStats(): void { - if (!currentSession.value) return - - const totalResponseTime = currentSession.value.answers.reduce((sum, answer) => sum + answer.responseTime, 0) - currentSession.value.averageResponseTime = totalResponseTime / currentSession.value.answers.length - currentSession.value.score = (currentSession.value.correctAnswers / currentSession.value.answers.length) * 100 - } - - function generatePracticeResult(session: PracticeSession): PracticeResult { - const accuracy = (session.correctAnswers / session.totalQuestions) * 100 - const overallScore = Math.max(0, accuracy - (session.maxLives - session.lives) * 10) - - return { - sessionId: session.id, - overallScore, - masteryLevel: determineMasteryLevel(overallScore), - recognitionScore: accuracy, - comprehensionScore: accuracy * 0.9, // 略低於識別分數 - applicationScore: accuracy * 0.8, // 最低分數 - responseSpeedScore: calculateSpeedScore(session.averageResponseTime), - averageResponseTime: session.averageResponseTime, - accuracy, - weaknessAnalysis: generateWeaknessAnalysis(session), - improvementSuggestions: generateImprovementSuggestions(session), - nextPracticeTopics: [], - experienceGained: Math.floor(overallScore / 10), - rewards: generateRewards(session) - } - } - - function determineMasteryLevel(score: number): 'initial' | 'familiar' | 'application' | 'mastered' { - if (score >= 90) return 'mastered' - if (score >= 75) return 'application' - if (score >= 60) return 'familiar' - return 'initial' - } - - function calculateSpeedScore(avgResponseTime: number): number { - // 基於平均反應時間計算速度分數 (越快分數越高) - const targetTime = practiceConfig.value.timePerQuestion * 1000 * 0.5 // 50%目標時間 - return Math.max(0, Math.min(100, 100 - ((avgResponseTime - targetTime) / targetTime) * 50)) - } - - function generateWeaknessAnalysis(session: PracticeSession): string { - const wrongAnswers = session.answers.filter(answer => !answer.isCorrect) - if (wrongAnswers.length === 0) return '表現優秀,沒有明顯弱點' - - return `需要加強練習,錯誤率: ${(wrongAnswers.length / session.totalQuestions * 100).toFixed(1)}%` - } - - function generateImprovementSuggestions(session: PracticeSession): string[] { - const suggestions = [] - const accuracy = (session.correctAnswers / session.totalQuestions) * 100 - - if (accuracy < 60) { - suggestions.push('建議重複學習基礎詞彙') - } - if (session.averageResponseTime > practiceConfig.value.timePerQuestion * 1000 * 0.8) { - suggestions.push('加強記憶練習以提升反應速度') - } - if (session.lives < session.maxLives) { - suggestions.push('注意仔細閱讀題目,避免粗心錯誤') - } - - return suggestions - } - - function generateRewards(session: PracticeSession): Array<{type: 'experience' | 'diamond' | 'achievement' | 'life', amount: number, description: string}> { - const rewards = [] - const score = (session.correctAnswers / session.totalQuestions) * 100 - - rewards.push({ - type: 'experience' as const, - amount: Math.floor(score / 10), - description: `獲得 ${Math.floor(score / 10)} 經驗值` - }) - - if (score >= 90) { - rewards.push({ - type: 'diamond' as const, - amount: 10, - description: '完美表現獎勵鑽石' - }) - } - - return rewards - } - - function recordWrongQuestion(question: PracticeQuestion, practiceType: PracticeType): void { - const existingRecord = wrongQuestions.value.find( - record => record.vocabularyId === question.vocabularyId && record.practiceType === practiceType - ) - - if (existingRecord) { - existingRecord.wrongCount++ - existingRecord.lastWrongDate = new Date() - existingRecord.isResolved = false - } else { - wrongQuestions.value.push({ - questionId: question.id, - vocabularyId: question.vocabularyId, - practiceType, - wrongCount: 1, - lastWrongDate: new Date(), - isResolved: false - }) - } - } - - function updateGlobalStats(result: PracticeResult): void { - practiceStats.value.totalSessions++ - practiceStats.value.totalQuestions += currentSession.value?.totalQuestions || 0 - practiceStats.value.correctAnswers += currentSession.value?.correctAnswers || 0 - practiceStats.value.averageScore = (practiceStats.value.averageScore * (practiceStats.value.totalSessions - 1) + result.overallScore) / practiceStats.value.totalSessions - practiceStats.value.averageResponseTime = (practiceStats.value.averageResponseTime * (practiceStats.value.totalSessions - 1) + result.averageResponseTime) / practiceStats.value.totalSessions - - if (result.averageResponseTime < practiceStats.value.fastestResponseTime || practiceStats.value.fastestResponseTime === 0) { - practiceStats.value.fastestResponseTime = result.averageResponseTime - } - } - - // 配置管理 - function updateConfig(config: Partial): void { - practiceConfig.value = { ...practiceConfig.value, ...config } - } - - function resetSession(): void { - currentSession.value = null - resetTimer() - } - - return { - // 狀態 - currentSession, - practiceConfig, - responseTimer, - practiceStats, - wrongQuestions, - - // Getters - isSessionActive, - currentQuestion, - sessionProgress, - canContinue, - - // Actions - startPracticeSession, - submitAnswer, - nextQuestion, - completeSession, - startTimer, - stopTimer, - resetTimer, - updateConfig, - resetSession - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/review.ts b/apps/web/src/stores/review.ts deleted file mode 100644 index 025202f..0000000 --- a/apps/web/src/stores/review.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { - VocabularyReviewData, - ReviewSession, - ReviewResponse, - WeaknessPattern -} from '@/utils/spacedRepetition' -import { - SpacedRepetitionAlgorithm, - createDefaultVocabularyReviewData -} from '@/utils/spacedRepetition' - -export interface LearningPlan { - date: string - vocabulary: VocabularyReviewData[] - totalCount: number - estimatedTime: number // 分鐘 -} - -export interface ReviewStats { - todayCompleted: number - todayTotal: number - weeklyStreak: number - totalMastered: number - averageAccuracy: number - improvementTrend: number - nextReviewTime: Date | null -} - -export const useReviewStore = defineStore('review', () => { - // 狀態 - const vocabularyReviewData = ref>(new Map()) - const reviewHistory = ref([]) - const currentReviewSession = ref(null) - const learningPlan = ref>(new Map()) - const isLoading = ref(false) - const algorithm = new SpacedRepetitionAlgorithm() - - // 計算屬性 - const todaysReviewVocabulary = computed(() => { - const allVocabulary = Array.from(vocabularyReviewData.value.values()) - return SpacedRepetitionAlgorithm.getTodaysReviewVocabulary(allVocabulary) - }) - - const reviewStats = computed((): ReviewStats => { - const todayTotal = todaysReviewVocabulary.value.length - const todayCompleted = reviewHistory.value.filter(session => { - const today = new Date() - today.setHours(0, 0, 0, 0) - const sessionDate = new Date(session.startTime) - sessionDate.setHours(0, 0, 0, 0) - return sessionDate.getTime() === today.getTime() - }).length - - const allVocabulary = Array.from(vocabularyReviewData.value.values()) - const totalMastered = allVocabulary.filter(v => v.masteryLevel >= 80).length - - const efficiency = SpacedRepetitionAlgorithm.analyzeLearningEfficiency(reviewHistory.value) - - // 計算連續學習天數 - const weeklyStreak = calculateWeeklyStreak() - - // 下次複習時間 - const nextReviewTime = getNextReviewTime() - - return { - todayCompleted, - todayTotal, - weeklyStreak, - totalMastered, - averageAccuracy: efficiency.averageAccuracy, - improvementTrend: efficiency.improvementTrend, - nextReviewTime - } - }) - - const urgentReviewVocabulary = computed(() => { - const today = new Date() - return todaysReviewVocabulary.value.filter(vocab => { - const overdueDays = Math.floor((today.getTime() - vocab.nextReviewDate.getTime()) / (24 * 60 * 60 * 1000)) - return overdueDays > 2 // 過期超過2天視為緊急 - }) - }) - - const weaknessAnalysis = computed(() => { - const allPatterns = new Map() - - Array.from(vocabularyReviewData.value.values()).forEach(vocab => { - vocab.weaknessPatterns.forEach(pattern => { - const existing = allPatterns.get(pattern.type) || { severity: 0, frequency: 0 } - allPatterns.set(pattern.type, { - severity: Math.max(existing.severity, pattern.severity), - frequency: existing.frequency + pattern.frequency - }) - }) - }) - - return Array.from(allPatterns.entries()) - .map(([type, data]) => ({ - type, - severity: data.severity, - frequency: data.frequency, - score: data.severity * Math.log(data.frequency + 1) - })) - .sort((a, b) => b.score - a.score) - .slice(0, 5) // 前5個最嚴重的薄弱點 - }) - - // 方法 - const initializeVocabularyReviewData = (vocabularyIds: string[]) => { - vocabularyIds.forEach(id => { - if (!vocabularyReviewData.value.has(id)) { - vocabularyReviewData.value.set(id, createDefaultVocabularyReviewData(id)) - } - }) - } - - const startReviewSession = (vocabularyIds: string[]): string => { - const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` - - currentReviewSession.value = { - vocabularyId: vocabularyIds[0], // 如果是批量複習,這裡需要調整 - startTime: new Date(), - responses: [], - overallAccuracy: 0, - averageResponseTime: 0 - } - - return sessionId - } - - const addReviewResponse = (response: Omit) => { - if (!currentReviewSession.value) { - throw new Error('沒有活躍的複習會話') - } - - const fullResponse: ReviewResponse = { - ...response, - timestamp: new Date() - } - - currentReviewSession.value.responses.push(fullResponse) - - // 更新會話統計 - updateSessionStats() - } - - const completeReviewSession = () => { - if (!currentReviewSession.value) { - throw new Error('沒有活躍的複習會話') - } - - currentReviewSession.value.endTime = new Date() - - // 更新複習數據 - const reviewData = vocabularyReviewData.value.get(currentReviewSession.value.vocabularyId) - if (reviewData) { - const updatedData = algorithm.calculateNextReview(reviewData, currentReviewSession.value) - vocabularyReviewData.value.set(reviewData.id, updatedData) - } - - // 保存到歷史記錄 - reviewHistory.value.push({ ...currentReviewSession.value }) - - // 清空當前會話 - currentReviewSession.value = null - - // 重新生成學習計劃 - generateLearningPlan() - } - - const updateSessionStats = () => { - if (!currentReviewSession.value) return - - const responses = currentReviewSession.value.responses - const correctCount = responses.filter(r => r.isCorrect).length - const totalResponseTime = responses.reduce((sum, r) => sum + r.responseTime, 0) - - currentReviewSession.value.overallAccuracy = responses.length > 0 ? correctCount / responses.length : 0 - currentReviewSession.value.averageResponseTime = responses.length > 0 ? totalResponseTime / responses.length : 0 - } - - const generateLearningPlan = (daysAhead: number = 7) => { - const allVocabulary = Array.from(vocabularyReviewData.value.values()) - const planMap = SpacedRepetitionAlgorithm.generateLearningPlan(allVocabulary, daysAhead) - - learningPlan.value.clear() - - planMap.forEach((vocabularyList, date) => { - const estimatedTime = vocabularyList.length * 2 // 每個詞彙平均2分鐘 - - learningPlan.value.set(date, { - date, - vocabulary: vocabularyList, - totalCount: vocabularyList.length, - estimatedTime - }) - }) - } - - const calculateWeeklyStreak = (): number => { - if (reviewHistory.value.length === 0) return 0 - - const today = new Date() - let streak = 0 - - // 從今天開始往前檢查 - for (let i = 0; i < 7; i++) { - const checkDate = new Date(today) - checkDate.setDate(today.getDate() - i) - checkDate.setHours(0, 0, 0, 0) - - const nextDay = new Date(checkDate) - nextDay.setDate(checkDate.getDate() + 1) - - const hasReviewOnDate = reviewHistory.value.some(session => { - const sessionDate = new Date(session.startTime) - return sessionDate >= checkDate && sessionDate < nextDay - }) - - if (hasReviewOnDate) { - streak++ - } else if (i > 0) { // 今天沒複習不算打斷,其他天沒複習就算打斷 - break - } - } - - return streak - } - - const getNextReviewTime = (): Date | null => { - const allVocabulary = Array.from(vocabularyReviewData.value.values()) - if (allVocabulary.length === 0) return null - - const nextReviews = allVocabulary - .filter(v => v.nextReviewDate > new Date()) - .sort((a, b) => a.nextReviewDate.getTime() - b.nextReviewDate.getTime()) - - return nextReviews.length > 0 ? nextReviews[0].nextReviewDate : null - } - - const getVocabularyReviewData = (vocabularyId: string): VocabularyReviewData | null => { - return vocabularyReviewData.value.get(vocabularyId) || null - } - - const updateVocabularyReviewData = (data: VocabularyReviewData) => { - vocabularyReviewData.value.set(data.id, data) - } - - const resetVocabularyProgress = (vocabularyId: string) => { - const defaultData = createDefaultVocabularyReviewData(vocabularyId) - vocabularyReviewData.value.set(vocabularyId, defaultData) - } - - const getPersonalizedRecommendations = (): string[] => { - const recommendations: string[] = [] - const stats = reviewStats.value - - // 基於統計數據生成建議 - if (stats.averageAccuracy < 0.7) { - recommendations.push('建議放慢學習節奏,專注於理解而不是數量') - } - - if (stats.weeklyStreak === 0) { - recommendations.push('建立每日複習習慣,即使只複習5個詞彙也有幫助') - } - - if (urgentReviewVocabulary.value.length > 10) { - recommendations.push('有較多詞彙需要緊急複習,建議優先處理過期詞彙') - } - - if (stats.improvementTrend < 0) { - recommendations.push('學習效果有下降趨勢,建議調整學習策略或休息一下') - } - - // 基於薄弱點生成建議 - const topWeakness = weaknessAnalysis.value[0] - if (topWeakness) { - const weaknessRecommendations = { - spelling: '建議加強拼寫練習,可以嘗試手寫練習', - meaning: '建議多做詞義辨析練習,建立詞彙語義網絡', - pronunciation: '建議多聽音頻,模仿正確發音', - usage: '建議多閱讀例句,理解詞彙在不同語境中的用法', - grammar: '建議複習相關語法規則,理解詞彙的語法功能' - } - recommendations.push(weaknessRecommendations[topWeakness.type as keyof typeof weaknessRecommendations]) - } - - return recommendations.slice(0, 3) // 最多返回3個建議 - } - - const exportReviewData = () => { - return { - vocabularyReviewData: Object.fromEntries(vocabularyReviewData.value), - reviewHistory: reviewHistory.value, - exportDate: new Date().toISOString() - } - } - - const importReviewData = (data: any) => { - if (data.vocabularyReviewData) { - vocabularyReviewData.value = new Map(Object.entries(data.vocabularyReviewData)) - } - if (data.reviewHistory) { - reviewHistory.value = data.reviewHistory.map((session: any) => ({ - ...session, - startTime: new Date(session.startTime), - endTime: session.endTime ? new Date(session.endTime) : undefined, - responses: session.responses.map((response: any) => ({ - ...response, - timestamp: new Date(response.timestamp) - })) - })) - } - generateLearningPlan() - } - - // 初始化 - generateLearningPlan() - - return { - // 狀態 - vocabularyReviewData, - reviewHistory, - currentReviewSession, - learningPlan, - isLoading, - - // 計算屬性 - todaysReviewVocabulary, - reviewStats, - urgentReviewVocabulary, - weaknessAnalysis, - - // 方法 - initializeVocabularyReviewData, - startReviewSession, - addReviewResponse, - completeReviewSession, - generateLearningPlan, - getVocabularyReviewData, - updateVocabularyReviewData, - resetVocabularyProgress, - getPersonalizedRecommendations, - exportReviewData, - importReviewData - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/ui.ts b/apps/web/src/stores/ui.ts deleted file mode 100644 index 583c529..0000000 --- a/apps/web/src/stores/ui.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' - -export interface Toast { - id: string - type: 'success' | 'error' | 'warning' | 'info' - title: string - message?: string - duration?: number - persistent?: boolean -} - -export interface Modal { - id: string - component: any - props?: Record - persistent?: boolean -} - -export const useUIStore = defineStore('ui', () => { - // 狀態 - const theme = ref<'light' | 'dark' | 'auto'>('auto') - const sidebarCollapsed = ref(false) - const mobileMenuOpen = ref(false) - const loading = ref(false) - const toasts = ref([]) - const modals = ref([]) - const currentModal = ref(null) - - // 頁面狀態 - const pageTitle = ref('Drama Ling') - const breadcrumbs = ref<{ label: string; to?: string }[]>([]) - const headerActions = ref([]) - - // 響應式狀態 - const isMobile = ref(false) - const screenWidth = ref(0) - const screenHeight = ref(0) - - // 計算屬性 - const isDarkMode = computed(() => { - if (theme.value === 'auto') { - return window.matchMedia('(prefers-color-scheme: dark)').matches - } - return theme.value === 'dark' - }) - - const activeToasts = computed(() => { - return toasts.value.filter(toast => !toast.persistent || Date.now() - parseInt(toast.id) < (toast.duration || 5000)) - }) - - // 動作 - const setTheme = (newTheme: 'light' | 'dark' | 'auto') => { - theme.value = newTheme - - // 應用主題到 HTML 元素 - const html = document.documentElement - if (newTheme === 'auto') { - html.classList.remove('dark', 'light') - } else { - html.classList.remove('dark', 'light') - html.classList.add(newTheme) - } - } - - const toggleSidebar = () => { - sidebarCollapsed.value = !sidebarCollapsed.value - } - - const closeSidebar = () => { - sidebarCollapsed.value = true - } - - const openSidebar = () => { - sidebarCollapsed.value = false - } - - const toggleMobileMenu = () => { - mobileMenuOpen.value = !mobileMenuOpen.value - } - - const closeMobileMenu = () => { - mobileMenuOpen.value = false - } - - const setLoading = (isLoading: boolean) => { - loading.value = isLoading - } - - const showToast = (toast: Omit) => { - const id = Date.now().toString() - const newToast: Toast = { - id, - duration: 5000, - persistent: false, - ...toast - } - - toasts.value.push(newToast) - - // 自動移除 toast(如果不是持久的) - if (!newToast.persistent && newToast.duration) { - setTimeout(() => { - hideToast(id) - }, newToast.duration) - } - - return id - } - - const hideToast = (id: string) => { - const index = toasts.value.findIndex(toast => toast.id === id) - if (index > -1) { - toasts.value.splice(index, 1) - } - } - - const clearToasts = () => { - toasts.value = [] - } - - const showSuccessToast = (title: string, message?: string) => { - return showToast({ - type: 'success', - title, - message - }) - } - - const showErrorToast = (title: string, message?: string) => { - return showToast({ - type: 'error', - title, - message, - duration: 8000 - }) - } - - const showWarningToast = (title: string, message?: string) => { - return showToast({ - type: 'warning', - title, - message - }) - } - - const showInfoToast = (title: string, message?: string) => { - return showToast({ - type: 'info', - title, - message - }) - } - - const showModal = (modal: Omit) => { - const id = Date.now().toString() - const newModal: Modal = { - id, - persistent: false, - ...modal - } - - modals.value.push(newModal) - currentModal.value = newModal - - return id - } - - const hideModal = (id?: string) => { - if (id) { - const index = modals.value.findIndex(modal => modal.id === id) - if (index > -1) { - modals.value.splice(index, 1) - } - } else { - modals.value.pop() - } - - currentModal.value = modals.value[modals.value.length - 1] || null - } - - const clearModals = () => { - modals.value = [] - currentModal.value = null - } - - const setPageTitle = (title: string) => { - pageTitle.value = title - document.title = `${title} - Drama Ling` - } - - const setBreadcrumbs = (crumbs: { label: string; to?: string }[]) => { - breadcrumbs.value = crumbs - } - - const setHeaderActions = (actions: any[]) => { - headerActions.value = actions - } - - const updateScreenSize = () => { - screenWidth.value = window.innerWidth - screenHeight.value = window.innerHeight - isMobile.value = window.innerWidth < 768 - } - - const initializeUI = () => { - // 設定初始主題 - if (theme.value === 'auto') { - setTheme('auto') - } - - // 監聽窗口大小變化 - updateScreenSize() - window.addEventListener('resize', updateScreenSize) - - // 監聽系統主題變化 - if (theme.value === 'auto') { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') - mediaQuery.addListener(() => { - if (theme.value === 'auto') { - setTheme('auto') - } - }) - } - } - - const cleanup = () => { - window.removeEventListener('resize', updateScreenSize) - } - - return { - // 狀態 - theme, - sidebarCollapsed, - mobileMenuOpen, - loading, - toasts, - modals, - currentModal, - pageTitle, - breadcrumbs, - headerActions, - isMobile, - screenWidth, - screenHeight, - - // 計算屬性 - isDarkMode, - activeToasts, - - // 動作 - setTheme, - toggleSidebar, - closeSidebar, - openSidebar, - toggleMobileMenu, - closeMobileMenu, - setLoading, - showToast, - hideToast, - clearToasts, - showSuccessToast, - showErrorToast, - showWarningToast, - showInfoToast, - showModal, - hideModal, - clearModals, - setPageTitle, - setBreadcrumbs, - setHeaderActions, - updateScreenSize, - initializeUI, - cleanup - } -}, { - persist: { - paths: ['theme', 'sidebarCollapsed'] - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/user.ts b/apps/web/src/stores/user.ts deleted file mode 100644 index deeb860..0000000 --- a/apps/web/src/stores/user.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { User, UserProgress, UserPreferences } from '@/types/user' - -export const useUserStore = defineStore('user', () => { - // 狀態 - const profile = ref(null) - const progress = ref(null) - const preferences = ref({ - language: 'zh-TW', - theme: 'light', - notifications: { - email: true, - push: true, - dailyReminder: true, - achievementAlert: true - }, - privacy: { - profileVisible: false, - progressVisible: false, - allowFriendRequests: true - }, - learning: { - dailyGoal: 30, - difficultyLevel: 'intermediate', - preferredPracticeTime: 'evening', - voiceEnabled: true, - subtitlesEnabled: true - } - }) - const achievements = ref([]) - const friends = ref([]) - const isLoading = ref(false) - const error = ref(null) - - // 計算屬性 - const totalLearningTime = computed(() => { - return progress.value?.totalLearningTime || 0 - }) - - const currentLevel = computed(() => { - return progress.value?.currentLevel || 1 - }) - - const experiencePoints = computed(() => { - return progress.value?.experiencePoints || 0 - }) - - const streakDays = computed(() => { - return progress.value?.streakDays || 0 - }) - - const completedLessons = computed(() => { - return progress.value?.completedLessons || 0 - }) - - const unlockedAchievements = computed(() => { - return achievements.value.filter(achievement => achievement.unlocked) - }) - - const reviewDueVocabulary = computed(() => { - // 模擬待複習詞彙數據,實際應該從學習進度中計算 - return [] - }) - - // 動作 - const fetchUserProfile = async () => { - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/user/profile', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取用戶資料失敗') - } - - profile.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err.message : '獲取用戶資料失敗' - } finally { - isLoading.value = false - } - } - - const fetchUserProgress = async () => { - isLoading.value = true - - try { - const response = await fetch('/api/user/progress', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取學習進度失敗') - } - - progress.value = await response.json() - } catch (err) { - error.value = err instanceof Error ? err.message : '獲取學習進度失敗' - } finally { - isLoading.value = false - } - } - - const updatePreferences = async (newPreferences: Partial) => { - isLoading.value = true - error.value = null - - try { - const response = await fetch('/api/user/preferences', { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}` - }, - body: JSON.stringify(newPreferences) - }) - - if (!response.ok) { - throw new Error('更新偏好設定失敗') - } - - const updatedPreferences = await response.json() - preferences.value = { ...preferences.value, ...updatedPreferences } - - return { success: true } - } catch (err) { - error.value = err instanceof Error ? err.message : '更新偏好設定失敗' - return { success: false, error: error.value } - } finally { - isLoading.value = false - } - } - - const updateDailyGoal = async (goal: number) => { - const result = await updatePreferences({ - learning: { - ...preferences.value.learning, - dailyGoal: goal - } - }) - - return result - } - - const addExperience = (points: number) => { - if (progress.value) { - progress.value.experiencePoints += points - - // 檢查是否升級 - const newLevel = Math.floor(progress.value.experiencePoints / 1000) + 1 - if (newLevel > progress.value.currentLevel) { - progress.value.currentLevel = newLevel - // 觸發升級事件 - return { levelUp: true, newLevel } - } - } - return { levelUp: false } - } - - const incrementLearningTime = (minutes: number) => { - if (progress.value) { - progress.value.totalLearningTime += minutes - progress.value.lastLearningDate = new Date().toISOString() - } - } - - const updateStreak = () => { - if (!progress.value) return - - const today = new Date().toDateString() - const lastLearning = progress.value.lastLearningDate ? - new Date(progress.value.lastLearningDate).toDateString() : null - - if (lastLearning === today) { - // 今天已經學習過了,不更新連擊 - return - } - - const yesterday = new Date() - yesterday.setDate(yesterday.getDate() - 1) - - if (lastLearning === yesterday.toDateString()) { - // 昨天有學習,增加連擊 - progress.value.streakDays += 1 - } else if (lastLearning !== today) { - // 中斷連擊,重新開始 - progress.value.streakDays = 1 - } - - progress.value.lastLearningDate = new Date().toISOString() - } - - const fetchAchievements = async () => { - try { - const response = await fetch('/api/user/achievements', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取成就失敗') - } - - achievements.value = await response.json() - } catch (err) { - console.error('獲取成就錯誤:', err) - } - } - - const unlockAchievement = async (achievementId: string) => { - try { - const response = await fetch(`/api/user/achievements/${achievementId}/unlock`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('解鎖成就失敗') - } - - // 更新本地狀態 - const achievement = achievements.value.find(a => a.id === achievementId) - if (achievement) { - achievement.unlocked = true - achievement.unlockedAt = new Date().toISOString() - } - - return { success: true, achievement } - } catch (err) { - console.error('解鎖成就錯誤:', err) - return { success: false } - } - } - - const fetchFriends = async () => { - try { - const response = await fetch('/api/user/friends', { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` - } - }) - - if (!response.ok) { - throw new Error('獲取朋友列表失敗') - } - - friends.value = await response.json() - } catch (err) { - console.error('獲取朋友列表錯誤:', err) - } - } - - const clearUserData = () => { - profile.value = null - progress.value = null - achievements.value = [] - friends.value = [] - error.value = null - } - - return { - // 狀態 - profile, - progress, - preferences, - achievements, - friends, - isLoading, - error, - - // 計算屬性 - totalLearningTime, - currentLevel, - experiencePoints, - streakDays, - completedLessons, - unlockedAchievements, - reviewDueVocabulary, - - // 動作 - fetchUserProfile, - fetchUserProgress, - updatePreferences, - updateDailyGoal, - addExperience, - incrementLearningTime, - updateStreak, - fetchAchievements, - unlockAchievement, - fetchFriends, - clearUserData - } -}, { - persist: { - paths: ['preferences'] - } -}) \ No newline at end of file diff --git a/apps/web/src/stores/vocabulary.ts b/apps/web/src/stores/vocabulary.ts deleted file mode 100644 index da21b4f..0000000 --- a/apps/web/src/stores/vocabulary.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import type { - Vocabulary, - Exercise, - ExerciseSession, - ExerciseResult, - ExerciseType, - PracticeSettings, - VocabularyProgress, - LearningAnalytics -} from '@/types/vocabulary' - -export const useVocabularyStore = defineStore('vocabulary', () => { - // 狀態 - const vocabularies = ref([]) - const currentVocabulary = ref(null) - const exercises = ref([]) - const currentSession = ref(null) - const sessionResults = ref([]) - const progress = ref([]) - const analytics = ref(null) - const isLoading = ref(false) - const error = ref(null) - - // 練習設定 - const practiceSettings = ref({ - exercise_type: 'multiple_choice_definition', - difficulty_levels: [1, 2, 3], - question_count: 10, - time_limit_per_question: undefined, - enable_hints: true, - enable_audio: true, - shuffle_options: true, - immediate_feedback: false - }) - - // 計算屬性 - const currentExercises = computed(() => { - if (!currentSession.value) return [] - return exercises.value.filter(ex => - currentSession.value!.vocabulary_list.includes(ex.vocabulary_id) && - ex.type === currentSession.value!.exercise_type - ) - }) - - const sessionProgress = computed(() => { - if (!currentSession.value) return 0 - return (currentSession.value.completed_questions / currentSession.value.total_questions) * 100 - }) - - const sessionAccuracy = computed(() => { - if (!currentSession.value || currentSession.value.completed_questions === 0) return 0 - return (currentSession.value.correct_answers / currentSession.value.completed_questions) * 100 - }) - - const wordsForReview = computed(() => { - const today = new Date().toISOString().split('T')[0] - return progress.value.filter(p => p.next_review_date <= today) - }) - - const masteredWords = computed(() => { - return progress.value.filter(p => p.mastery_level >= 80) - }) - - const learningWords = computed(() => { - return progress.value.filter(p => p.mastery_level < 80 && p.mastery_level > 0) - }) - - const newWords = computed(() => { - const learnedIds = new Set(progress.value.map(p => p.vocabulary_id)) - return vocabularies.value.filter(v => !learnedIds.has(v.id)) - }) - - // 動作 - const fetchVocabularies = async (filters?: { - difficulty?: number[] - category?: string - limit?: number - }) => { - isLoading.value = true - error.value = null - - try { - // 模擬API調用 - 實際應該呼叫後端API - const mockVocabularies: Vocabulary[] = [ - { - id: 'vocab_1', - word: 'abundant', - phonetic: '/əˈbʌndənt/', - definitions: [{ - id: 'def_1', - part_of_speech: 'adjective', - definition: 'existing or available in large quantities', - chinese_translation: '豐富的,充裕的' - }], - examples: [{ - id: 'ex_1', - sentence: 'The region has abundant natural resources.', - chinese_translation: '這個地區有豐富的自然資源。' - }], - difficulty_level: 3, - frequency_rank: 1250, - category: 'academic' - }, - { - id: 'vocab_2', - word: 'achieve', - phonetic: '/əˈtʃiːv/', - definitions: [{ - id: 'def_2', - part_of_speech: 'verb', - definition: 'successfully bring about or reach a desired objective', - chinese_translation: '達成,實現' - }], - examples: [{ - id: 'ex_2', - sentence: 'She worked hard to achieve her goals.', - chinese_translation: '她努力工作以實現她的目標。' - }], - difficulty_level: 2, - frequency_rank: 850, - category: 'general' - } - ] - - vocabularies.value = mockVocabularies - } catch (err) { - error.value = err instanceof Error ? err.message : '載入詞彙失敗' - } finally { - isLoading.value = false - } - } - - const fetchExercises = async (vocabularyIds: string[], exerciseType: ExerciseType) => { - isLoading.value = true - - try { - // 模擬生成選擇題練習 - const mockExercises: Exercise[] = vocabularyIds.map(vocabId => { - const vocab = vocabularies.value.find(v => v.id === vocabId) - if (!vocab) return null - - return { - id: `exercise_${vocabId}_${exerciseType}`, - vocabulary_id: vocabId, - type: exerciseType, - question: exerciseType === 'multiple_choice_definition' - ? `What does "${vocab.word}" mean?` - : `What is the Chinese translation of "${vocab.word}"?`, - options: [ - { - id: 'opt_1', - text: vocab.definitions[0].chinese_translation, - is_correct: true - }, - { - id: 'opt_2', - text: '錯誤選項1', - is_correct: false - }, - { - id: 'opt_3', - text: '錯誤選項2', - is_correct: false - }, - { - id: 'opt_4', - text: '錯誤選項3', - is_correct: false - } - ], - correct_answer_id: 'opt_1', - difficulty_level: vocab.difficulty_level - } - }).filter(Boolean) as Exercise[] - - exercises.value = mockExercises - } catch (err) { - error.value = err instanceof Error ? err.message : '載入練習失敗' - } finally { - isLoading.value = false - } - } - - const startExerciseSession = async (vocabularyIds: string[], exerciseType: ExerciseType) => { - try { - await fetchExercises(vocabularyIds, exerciseType) - - const session: ExerciseSession = { - id: `session_${Date.now()}`, - user_id: 'current_user', // 應該從auth store獲取 - vocabulary_list: vocabularyIds, - exercise_type: exerciseType, - start_time: new Date().toISOString(), - total_questions: exercises.value.length, - completed_questions: 0, - correct_answers: 0, - incorrect_answers: 0, - skipped_questions: 0, - average_response_time: 0, - status: 'in_progress' - } - - currentSession.value = session - sessionResults.value = [] - - return session - } catch (err) { - error.value = err instanceof Error ? err.message : '開始練習失敗' - throw err - } - } - - const submitAnswer = async (exerciseId: string, selectedOptionId: string, responseTime: number) => { - if (!currentSession.value) return - - const exercise = exercises.value.find(ex => ex.id === exerciseId) - if (!exercise) return - - const isCorrect = exercise.correct_answer_id === selectedOptionId - - const result: ExerciseResult = { - id: `result_${Date.now()}`, - session_id: currentSession.value.id, - vocabulary_id: exercise.vocabulary_id, - exercise_id: exerciseId, - user_answer_id: selectedOptionId, - is_correct: isCorrect, - response_time: responseTime, - timestamp: new Date().toISOString(), - hints_used: 0 - } - - sessionResults.value.push(result) - - // 更新會話統計 - currentSession.value.completed_questions++ - if (isCorrect) { - currentSession.value.correct_answers++ - } else { - currentSession.value.incorrect_answers++ - } - - // 更新平均反應時間 - const totalResponseTime = sessionResults.value.reduce((sum, r) => sum + r.response_time, 0) - currentSession.value.average_response_time = totalResponseTime / sessionResults.value.length - - // 檢查是否完成會話 - if (currentSession.value.completed_questions >= currentSession.value.total_questions) { - await completeSession() - } - - return result - } - - const completeSession = async () => { - if (!currentSession.value) return - - currentSession.value.end_time = new Date().toISOString() - currentSession.value.status = 'completed' - - // 更新詞彙學習進度 - for (const result of sessionResults.value) { - await updateVocabularyProgress(result.vocabulary_id, result.is_correct, result.response_time) - } - - return currentSession.value - } - - const updateVocabularyProgress = async (vocabularyId: string, isCorrect: boolean, responseTime: number) => { - let vocabProgress = progress.value.find(p => p.vocabulary_id === vocabularyId) - - if (!vocabProgress) { - // 創建新的進度記錄 - vocabProgress = { - user_id: 'current_user', - vocabulary_id: vocabularyId, - mastery_level: 0, - last_studied: new Date().toISOString(), - review_count: 0, - error_patterns: [], - next_review_date: new Date().toISOString(), - first_learned_date: new Date().toISOString(), - total_study_time: 0 - } - progress.value.push(vocabProgress) - } - - // 更新進度 - vocabProgress.last_studied = new Date().toISOString() - vocabProgress.review_count++ - vocabProgress.total_study_time += Math.ceil(responseTime / 1000) - - // 根據正確性調整熟練度 - if (isCorrect) { - vocabProgress.mastery_level = Math.min(100, vocabProgress.mastery_level + 10) - } else { - vocabProgress.mastery_level = Math.max(0, vocabProgress.mastery_level - 5) - } - - // 計算下次複習時間(簡化的間隔重複算法) - const intervals = [1, 3, 7, 14, 30, 90] // 天數 - const reviewLevel = Math.floor(vocabProgress.mastery_level / 20) - const nextInterval = intervals[Math.min(reviewLevel, intervals.length - 1)] - - const nextReview = new Date() - nextReview.setDate(nextReview.getDate() + nextInterval) - vocabProgress.next_review_date = nextReview.toISOString().split('T')[0] - } - - const updatePracticeSettings = (newSettings: Partial) => { - practiceSettings.value = { ...practiceSettings.value, ...newSettings } - } - - const resetCurrentSession = () => { - currentSession.value = null - sessionResults.value = [] - exercises.value = [] - } - - const fetchAnalytics = async () => { - isLoading.value = true - - try { - // 計算學習分析數據 - const totalWords = progress.value.length - const totalStudyTime = progress.value.reduce((sum, p) => sum + p.total_study_time, 0) - const completedSessions = sessionResults.value.length > 0 ? 1 : 0 - const correctAnswers = sessionResults.value.filter(r => r.is_correct).length - const totalAnswers = sessionResults.value.length - - const mockAnalytics: LearningAnalytics = { - total_words_learned: totalWords, - total_study_time: totalStudyTime, - average_accuracy: totalAnswers > 0 ? (correctAnswers / totalAnswers) * 100 : 0, - streak_days: 1, // 模擬數據 - words_due_for_review: wordsForReview.value.length, - mastery_distribution: { - beginner: progress.value.filter(p => p.mastery_level <= 25).length, - intermediate: progress.value.filter(p => p.mastery_level > 25 && p.mastery_level <= 50).length, - advanced: progress.value.filter(p => p.mastery_level > 50 && p.mastery_level <= 75).length, - mastered: progress.value.filter(p => p.mastery_level > 75).length - }, - weekly_progress: [], - error_patterns: [] - } - - analytics.value = mockAnalytics - } catch (err) { - error.value = err instanceof Error ? err.message : '載入分析數據失敗' - } finally { - isLoading.value = false - } - } - - return { - // 狀態 - vocabularies, - currentVocabulary, - exercises, - currentSession, - sessionResults, - progress, - analytics, - practiceSettings, - isLoading, - error, - - // 計算屬性 - currentExercises, - sessionProgress, - sessionAccuracy, - wordsForReview, - masteredWords, - learningWords, - newWords, - - // 動作 - fetchVocabularies, - fetchExercises, - startExerciseSession, - submitAnswer, - completeSession, - updatePracticeSettings, - resetCurrentSession, - fetchAnalytics - } -}, { - persist: { - paths: ['practiceSettings', 'progress'] - } -}) \ No newline at end of file diff --git a/apps/web/src/styles/main.scss b/apps/web/src/styles/main.scss new file mode 100644 index 0000000..d40c323 --- /dev/null +++ b/apps/web/src/styles/main.scss @@ -0,0 +1,26 @@ +@import './variables.scss'; +@import './vocabulary.scss'; + +// Reset and Base Styles +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; +} + +// Loading Spinner +.loading-spinner { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: var(--text-lg); + color: var(--text-secondary); +} \ No newline at end of file diff --git a/apps/web/src/styles/variables.scss b/apps/web/src/styles/variables.scss new file mode 100644 index 0000000..889f467 --- /dev/null +++ b/apps/web/src/styles/variables.scss @@ -0,0 +1,76 @@ +// Design System Variables + +// Colors +:root { + // Primary Colors + --primary-teal: #00e5cc; + --secondary-purple: #8b5cf6; + + // Background + --bg-primary: #f8fafc; + --bg-secondary: #f1f5f9; + --bg-card: #ffffff; + --bg-dark: #1e293b; + + // Text Colors + --text-primary: #1e293b; + --text-secondary: #64748b; + --text-tertiary: #94a3b8; + + // Status Colors + --success-green: #22c55e; + --warning-yellow: #fbbf24; + --error-red: #ef4444; + + // UI Elements + --divider: #e2e8f0; + --border: #cbd5e1; + + // Spacing + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + + // Typography + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + --text-5xl: 3rem; + --text-6xl: 3.75rem; + + // Border Radius + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + + // Shadows + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); +} + +// Dark mode overrides +@media (prefers-color-scheme: dark) { + :root { + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-card: #334155; + + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --text-tertiary: #64748b; + + --divider: #475569; + --border: #64748b; + } +} \ No newline at end of file diff --git a/apps/web/src/styles/vocabulary.scss b/apps/web/src/styles/vocabulary.scss new file mode 100644 index 0000000..08ee55a --- /dev/null +++ b/apps/web/src/styles/vocabulary.scss @@ -0,0 +1,577 @@ +// Vocabulary Learning Styles (從HTML原型移植) + +.vocabulary-layout { + min-height: 100vh; + background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); + display: flex; +} + +// 側邊欄樣式 +.sidebar { + width: 280px; + background: var(--bg-card); + border-right: 1px solid var(--divider); + display: flex; + flex-direction: column; + position: fixed; + height: 100vh; + z-index: 100; +} + +.sidebar-header { + padding: var(--space-6); + border-bottom: 1px solid var(--divider); +} + +.logo { + display: flex; + align-items: center; + gap: var(--space-3); + color: var(--text-primary); + text-decoration: none; + font-size: var(--text-xl); + font-weight: 700; +} + +.sidebar-nav { + flex: 1; + padding: var(--space-6) 0; + overflow-y: auto; +} + +.nav-section { + margin-bottom: var(--space-6); +} + +.nav-section-title { + font-size: var(--text-xs); + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: var(--space-3); + padding: 0 var(--space-6); +} + +.nav-item { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3) var(--space-6); + color: var(--text-secondary); + text-decoration: none; + font-size: var(--text-sm); + font-weight: 500; + transition: all 0.2s ease; + border-left: 3px solid transparent; + + &:hover { + background: rgba(0, 229, 204, 0.1); + color: var(--primary-teal); + } + + &.active { + background: rgba(0, 229, 204, 0.15); + color: var(--primary-teal); + border-left-color: var(--primary-teal); + } +} + +.nav-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.sidebar-footer { + padding: var(--space-6); + border-top: 1px solid var(--divider); +} + +.user-profile { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3); + background: var(--bg-secondary); + border-radius: var(--radius-lg); +} + +.user-avatar { + width: 40px; + height: 40px; + background: var(--primary-teal); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + color: white; +} + +.user-info { + flex: 1; +} + +.user-name { + font-size: var(--text-sm); + font-weight: 600; + color: var(--text-primary); +} + +.user-level { + font-size: var(--text-xs); + color: var(--text-secondary); +} + +// 主內容區 +.main-content { + flex: 1; + margin-left: 280px; + padding: var(--space-6); + overflow-y: auto; +} + +.page-header { + margin-bottom: var(--space-8); +} + +.header-section { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-6); +} + +.header-text h1 { + font-size: var(--text-3xl); + font-weight: 700; + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.header-text p { + font-size: var(--text-lg); + color: var(--text-secondary); +} + +.header-stats { + display: flex; + gap: var(--space-6); + align-items: center; +} + +.stat-item { + text-align: center; +} + +.stat-value { + font-size: var(--text-2xl); + font-weight: 700; + color: var(--primary-teal); + display: block; +} + +.stat-label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +// 學習模式選擇 +.mode-selector { + display: flex; + gap: var(--space-4); + margin-bottom: var(--space-8); +} + +.mode-card { + flex: 1; + background: var(--bg-card); + border: 2px solid var(--divider); + border-radius: var(--radius-xl); + padding: var(--space-6); + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + border-color: var(--primary-teal); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); + } + + &.active { + border-color: var(--primary-teal); + background: rgba(0, 229, 204, 0.1); + } +} + +.mode-icon { + font-size: var(--text-4xl); + margin-bottom: var(--space-3); +} + +.mode-title { + font-size: var(--text-lg); + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.mode-description { + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: var(--space-4); +} + +.mode-progress { + display: flex; + justify-content: space-between; + font-size: var(--text-sm); + color: var(--text-tertiary); +} + +// 詞彙卡片學習區 +.vocabulary-section { + background: var(--bg-card); + border: 1px solid var(--divider); + border-radius: var(--radius-xl); + padding: var(--space-8); + margin-bottom: var(--space-8); + text-align: center; + + &.hidden { + display: none; + } +} + +.vocabulary-card { + max-width: 600px; + margin: 0 auto; + padding: var(--space-8); + position: relative; +} + +.vocabulary-word { + font-size: var(--text-5xl); + font-weight: 700; + color: var(--text-primary); + margin-bottom: var(--space-4); +} + +.vocabulary-phonetic { + font-size: var(--text-xl); + color: var(--text-secondary); + margin-bottom: var(--space-6); +} + +.vocabulary-definition { + font-size: var(--text-lg); + color: var(--text-primary); + margin-bottom: var(--space-6); + line-height: 1.6; + + &.hidden { + display: none; + } +} + +.vocabulary-example { + background: var(--bg-secondary); + padding: var(--space-4); + border-radius: var(--radius-lg); + margin-bottom: var(--space-6); + font-style: italic; + color: var(--text-secondary); + + &.hidden { + display: none; + } +} + +.vocabulary-controls { + display: flex; + justify-content: center; + gap: var(--space-4); + margin-top: var(--space-8); +} + +.control-btn { + padding: var(--space-3) var(--space-6); + border: 2px solid var(--divider); + border-radius: var(--radius-lg); + background: var(--bg-card); + color: var(--text-primary); + font-size: var(--text-base); + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: var(--space-2); + + &:hover { + border-color: var(--primary-teal); + background: rgba(0, 229, 204, 0.1); + } + + &.primary { + background: var(--primary-teal); + border-color: var(--primary-teal); + color: white; + + &:hover { + background: #00b8a0; + } + } +} + +.difficulty-buttons { + display: flex; + justify-content: center; + gap: var(--space-3); + margin-top: var(--space-6); + + &.hidden { + display: none; + } +} + +.difficulty-btn { + padding: var(--space-2) var(--space-4); + border: 1px solid var(--divider); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-secondary); + font-size: var(--text-sm); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: var(--bg-secondary); + } + + &.easy { + border-color: var(--success-green); + color: var(--success-green); + } + + &.hard { + border-color: var(--error-red); + color: var(--error-red); + } +} + +.no-words-message { + padding: var(--space-8); + text-align: center; + + h3 { + font-size: var(--text-2xl); + color: var(--text-primary); + margin-bottom: var(--space-4); + } + + p { + font-size: var(--text-lg); + color: var(--text-secondary); + margin-bottom: var(--space-6); + } +} + +// 詞彙清單 +.vocabulary-list { + background: var(--bg-card); + border: 1px solid var(--divider); + border-radius: var(--radius-xl); + padding: var(--space-6); +} + +.list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-6); +} + +.list-title { + font-size: var(--text-xl); + font-weight: 600; + color: var(--text-primary); +} + +.filter-tabs { + display: flex; + gap: var(--space-2); +} + +.filter-tab { + padding: var(--space-2) var(--space-4); + border: 1px solid var(--divider); + border-radius: var(--radius-md); + background: var(--bg-secondary); + color: var(--text-secondary); + font-size: var(--text-sm); + cursor: pointer; + transition: all 0.2s ease; + + &.active { + background: var(--primary-teal); + border-color: var(--primary-teal); + color: white; + } +} + +.vocabulary-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-4); + border: 1px solid var(--divider); + border-radius: var(--radius-lg); + margin-bottom: var(--space-3); + transition: all 0.2s ease; + cursor: pointer; + + &:hover { + border-color: var(--primary-teal); + background: rgba(0, 229, 204, 0.05); + } +} + +.word-info { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.word-text { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.word-main { + font-size: var(--text-lg); + font-weight: 600; + color: var(--text-primary); +} + +.word-definition { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.word-status { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.mastery-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-tertiary); + + &.learned { + background: var(--success-green); + } + + &.learning { + background: var(--warning-yellow); + } +} + +.play-btn { + width: 32px; + height: 32px; + border: none; + border-radius: 50%; + background: var(--primary-teal); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: #00b8a0; + transform: scale(1.1); + } +} + +// 手機版選單按鈕 +.mobile-menu-btn { + display: none; + position: fixed; + top: var(--space-4); + left: var(--space-4); + width: 48px; + height: 48px; + background: var(--primary-teal); + border: none; + border-radius: var(--radius-lg); + color: white; + font-size: var(--text-lg); + z-index: 101; + cursor: pointer; +} + +// 響應式設計 +@media (max-width: 1024px) { + .sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease; + + &.open { + transform: translateX(0); + } + } + + .main-content { + margin-left: 0; + } + + .mobile-menu-btn { + display: flex; + align-items: center; + justify-content: center; + } +} + +@media (max-width: 768px) { + .main-content { + padding: var(--space-4); + } + + .header-section { + flex-direction: column; + align-items: flex-start; + gap: var(--space-4); + } + + .mode-selector { + flex-direction: column; + } + + .vocabulary-card { + padding: var(--space-4); + } + + .vocabulary-word { + font-size: var(--text-4xl); + } + + .vocabulary-controls { + flex-wrap: wrap; + } + + .difficulty-buttons { + flex-direction: column; + gap: var(--space-2); + } +} \ No newline at end of file diff --git a/apps/web/src/types/learning.ts b/apps/web/src/types/learning.ts deleted file mode 100644 index 0642a7d..0000000 --- a/apps/web/src/types/learning.ts +++ /dev/null @@ -1,198 +0,0 @@ -export interface Course { - id: string - title: string - description: string - thumbnail?: string - level: 'beginner' | 'intermediate' | 'advanced' - language: string - targetLanguage: string - duration: number // 預估總時長(分鐘) - lessonsCount: number - progress: number // 0-100 - isAvailable: boolean - isCompleted: boolean - enrolledAt?: string - completedAt?: string - lessons: Lesson[] - tags: string[] - difficulty: number // 1-10 - rating: number - reviewsCount: number -} - -export interface Lesson { - id: string - courseId: string - title: string - description: string - type: 'vocabulary' | 'dialogue' | 'grammar' | 'pronunciation' | 'roleplay' | 'review' - thumbnail?: string - duration: number // 預估時長(分鐘) - order: number - isUnlocked: boolean - isCompleted: boolean - progress: number // 0-100 - completedAt?: string - score?: number - bestScore?: number - attempts: number - content: LessonContent -} - -export interface LessonContent { - introduction?: { - text: string - audio?: string - video?: string - } - questions: Question[] - vocabulary?: VocabularyCard[] - dialogues?: DialogueScript[] - summary?: { - text: string - keyPoints: string[] - } -} - -export interface Question { - id: string - type: 'multiple_choice' | 'fill_blank' | 'true_false' | 'matching' | 'ordering' | 'speaking' | 'listening' - question: string - questionAudio?: string - options?: string[] - correctAnswer: any - explanation?: string - points: number - hints?: string[] - media?: { - type: 'image' | 'audio' | 'video' - url: string - } -} - -export interface VocabularyCard { - id: string - word: string - pronunciation: string - definition: string - translation: string - partOfSpeech: string - examples: { - sentence: string - translation: string - audio?: string - }[] - audio?: string - image?: string - masteryLevel: number // 0-5 - lastReviewed?: string - nextReviewDate?: string - reviewCount: number - correctCount: number - tags: string[] -} - -export interface DialogueScript { - id: string - title: string - scenario: string - participants: DialogueParticipant[] - lines: DialogueLine[] - vocabulary: string[] // vocabulary IDs - difficulty: number - duration: number -} - -export interface DialogueParticipant { - id: string - name: string - avatar?: string - voice: string - role: string -} - -export interface DialogueLine { - id: string - speakerId: string - text: string - translation: string - audio?: string - emotion?: string - speed?: 'slow' | 'normal' | 'fast' - order: number -} - -export interface LearningSession { - id: string - lessonId: string - userId: string - startTime: string - endTime?: string - duration?: number // 秒 - questions: Question[] - currentQuestionIndex: number - answers: SessionAnswer[] - score: number - totalPoints: number - accuracy: number - status: 'active' | 'paused' | 'completed' | 'abandoned' - isPaused?: boolean -} - -export interface SessionAnswer { - questionId: string - questionIndex: number - answer: any - isCorrect: boolean - points: number - timeSpent: number // 秒 - attempts: number - hintsUsed: number - skipped?: boolean -} - -export interface PracticeSession { - id: string - type: 'vocabulary_review' | 'pronunciation' | 'dialogue_practice' | 'quick_review' - duration: number - questionsCount: number - correctAnswers: number - score: number - experienceGained: number - vocabularyReviewed: string[] - createdAt: string -} - -export interface StudyPlan { - id: string - userId: string - title: string - description: string - targetLevel: string - targetDate: string - weeklyGoal: number // 分鐘 - dailyGoal: number // 分鐘 - courses: string[] // course IDs - schedule: StudySchedule[] - progress: number // 0-100 - isActive: boolean - createdAt: string - updatedAt: string -} - -export interface StudySchedule { - dayOfWeek: number // 0-6 (Sunday-Saturday) - timeSlots: { - startTime: string // HH:MM - duration: number // 分鐘 - type: 'lesson' | 'review' | 'practice' - }[] -} - -export interface LearningStreak { - currentStreak: number - longestStreak: number - lastStudyDate: string - streakGoal: number - isActive: boolean -} \ No newline at end of file diff --git a/apps/web/src/types/practice.ts b/apps/web/src/types/practice.ts deleted file mode 100644 index 5f5ab11..0000000 --- a/apps/web/src/types/practice.ts +++ /dev/null @@ -1,181 +0,0 @@ -// Practice System Types (依據 function-specs 練習模式定義) - -// 基礎練習類型 -export type PracticeType = 'choice' | 'matching' | 'reorganize' - -// 題目類型 (依據mobile specs) -export type QuestionType = 'definition' | 'example' | 'image' | 'audio' - -// 掌握度等級 (依據business logic) -export type MasteryLevel = 'initial' | 'familiar' | 'application' | 'mastered' - -// 選擇題選項 -export interface ChoiceOption { - id: string - text: string - isCorrect: boolean -} - -// 基礎練習題目 -export interface PracticeQuestion { - id: string - type: QuestionType - content: string - vocabularyId: string - vocabularyWord: string - timeLimit: number // 秒數 (15-60) - difficulty: number // 1-5 - audioUrl?: string - imageUrl?: string -} - -// 選擇題問題 -export interface ChoiceQuestion extends PracticeQuestion { - options: ChoiceOption[] - correctAnswerId: string -} - -// 圖片匹配題目 -export interface MatchingQuestion extends PracticeQuestion { - images: MatchingImage[] - correctPairs: MatchingPair[] -} - -export interface MatchingImage { - id: string - url: string - vocabularyId: string -} - -export interface MatchingPair { - imageId: string - vocabularyId: string -} - -// 句子重組題目 -export interface ReorganizeQuestion extends PracticeQuestion { - sentence: string - words: ReorganizeWord[] - correctOrder: string[] -} - -export interface ReorganizeWord { - id: string - text: string - position?: number -} - -// 用戶答案 -export interface UserAnswer { - questionId: string - selectedOptionId?: string // 選擇題 - selectedPairs?: MatchingPair[] // 圖片匹配 - wordOrder?: string[] // 句子重組 - responseTime: number // 毫秒 - isCorrect: boolean - submittedAt: Date -} - -// 練習會話 -export interface PracticeSession { - id: string - vocabularyIds: string[] - practiceType: PracticeType - questions: (ChoiceQuestion | MatchingQuestion | ReorganizeQuestion)[] - answers: UserAnswer[] - startTime: Date - endTime?: Date - isCompleted: boolean - currentQuestionIndex: number - score: number - totalQuestions: number - correctAnswers: number - averageResponseTime: number - lives: number // 命條系統 - maxLives: number -} - -// 練習結果分析 -export interface PracticeResult { - sessionId: string - overallScore: number // 0-100 - masteryLevel: MasteryLevel - recognitionScore: number // 識別能力 0-100 - comprehensionScore: number // 理解能力 0-100 - applicationScore: number // 應用能力 0-100 - responseSpeedScore: number // 反應速度 0-100 - averageResponseTime: number - accuracy: number // 正確率 0-100 - weaknessAnalysis: string - improvementSuggestions: string[] - nextPracticeTopics: string[] - experienceGained: number - rewards?: PracticeReward[] -} - -// 獎勵系統 -export interface PracticeReward { - type: 'experience' | 'diamond' | 'achievement' | 'life' - amount: number - description: string -} - -// 反應時間測量 -export interface ResponseTimer { - startTime: number - endTime?: number - isRunning: boolean -} - -// 練習統計 -export interface PracticeStats { - totalSessions: number - totalQuestions: number - correctAnswers: number - averageScore: number - averageResponseTime: number - fastestResponseTime: number - longestStreak: number - currentStreak: number - masteredVocabulary: number - practiceTimeToday: number // 分鐘 - practiceTimeThisWeek: number -} - -// 錯題本 -export interface WrongQuestionRecord { - questionId: string - vocabularyId: string - practiceType: PracticeType - wrongCount: number - lastWrongDate: Date - isResolved: boolean - notes?: string -} - -// 練習配置 -export interface PracticeConfig { - questionsPerSession: number // 5-20 - timePerQuestion: number // 15-60秒 - enableLives: boolean - maxLives: number - enableHints: boolean - enableAudio: boolean - autoAdvance: boolean - showCorrectAnswer: boolean - difficulty: number // 1-5 -} - -// 練習進度 -export interface PracticeProgress { - vocabularyId: string - choicePracticeCompleted: boolean - matchingPracticeCompleted: boolean - reorganizePracticeCompleted: boolean - overallProgress: number // 0-100 - lastPracticeDate: Date - nextReviewDate: Date - masteryLevel: MasteryLevel - practiceCount: number - errorCount: number -} \ No newline at end of file diff --git a/apps/web/src/types/user.ts b/apps/web/src/types/user.ts deleted file mode 100644 index 62ec70a..0000000 --- a/apps/web/src/types/user.ts +++ /dev/null @@ -1,122 +0,0 @@ -export interface User { - id: string - email: string - username: string - displayName?: string - avatar?: string - firstName?: string - lastName?: string - dateOfBirth?: string - phoneNumber?: string - country?: string - nativeLanguage?: string - targetLanguage?: string - createdAt: string - updatedAt: string - verified?: boolean - emailVerified?: boolean - isActive?: boolean - subscription?: { - plan: 'free' | 'premium' | 'unlimited' - status: 'active' | 'inactive' | 'expired' | 'cancelled' - expiresAt?: string - } - preferences?: { - language: string - theme: 'light' | 'dark' | 'auto' - notifications: boolean - } -} - -export interface UserProgress { - userId: string - currentLevel: number - experiencePoints: number - totalLearningTime: number // 分鐘 - streakDays: number - longestStreak: number - completedLessons: number - completedCourses: number - lastLearningDate?: string - dailyGoalMet: boolean - weeklyGoalProgress: number - monthlyGoalProgress: number - accuracy: number - vocabularyMastered: number - certificatesEarned: number -} - -export interface UserPreferences { - language: string - theme: 'light' | 'dark' | 'auto' - notifications: { - email: boolean - push: boolean - dailyReminder: boolean - achievementAlert: boolean - } - privacy: { - profileVisible: boolean - progressVisible: boolean - allowFriendRequests: boolean - } - learning: { - dailyGoal: number // 分鐘 - difficultyLevel: 'beginner' | 'intermediate' | 'advanced' - preferredPracticeTime: 'morning' | 'afternoon' | 'evening' | 'night' - voiceEnabled: boolean - subtitlesEnabled: boolean - } -} - -export interface UserStats { - totalStudyTime: number - averageSessionLength: number - lessonsCompleted: number - vocabularyLearned: number - streakDays: number - accuracy: number - level: number - experiencePoints: number -} - -export interface Achievement { - id: string - title: string - description: string - icon: string - category: 'progress' | 'streak' | 'vocabulary' | 'accuracy' | 'time' | 'special' - requirement: { - type: string - value: number - } - unlocked: boolean - unlockedAt?: string - points: number -} - -export interface Friend { - id: string - username: string - avatar?: string - level: number - streakDays: number - status: 'online' | 'offline' | 'learning' - friendSince: string -} - -export interface Leaderboard { - period: 'daily' | 'weekly' | 'monthly' | 'allTime' - entries: LeaderboardEntry[] -} - -export interface LeaderboardEntry { - rank: number - user: { - id: string - username: string - avatar?: string - } - score: number - change: number // 排名變化 -} \ No newline at end of file diff --git a/apps/web/src/types/vocabulary.ts b/apps/web/src/types/vocabulary.ts deleted file mode 100644 index 0d78644..0000000 --- a/apps/web/src/types/vocabulary.ts +++ /dev/null @@ -1,138 +0,0 @@ -// 詞彙相關的型別定義 - -export interface Vocabulary { - id: string - word: string - phonetic: string - definitions: VocabularyDefinition[] - examples: VocabularyExample[] - difficulty_level: 1 | 2 | 3 | 4 | 5 - frequency_rank: number - audio_url?: string - image_url?: string - category: string -} - -export interface VocabularyDefinition { - id: string - part_of_speech: 'noun' | 'verb' | 'adjective' | 'adverb' | 'preposition' | 'conjunction' | 'interjection' - definition: string - chinese_translation: string -} - -export interface VocabularyExample { - id: string - sentence: string - chinese_translation: string - audio_url?: string -} - -export interface VocabularyProgress { - user_id: string - vocabulary_id: string - mastery_level: number // 0-100 - last_studied: string - review_count: number - error_patterns: string[] - next_review_date: string - first_learned_date: string - total_study_time: number // in seconds -} - -export interface Exercise { - id: string - vocabulary_id: string - type: ExerciseType - question: string - options: ExerciseOption[] - correct_answer_id: string - explanation?: string - difficulty_level: 1 | 2 | 3 | 4 | 5 -} - -export interface ExerciseOption { - id: string - text: string - is_correct: boolean -} - -export type ExerciseType = - | 'multiple_choice_definition' - | 'multiple_choice_translation' - | 'multiple_choice_synonym' - | 'multiple_choice_usage' - | 'image_matching' - | 'sentence_completion' - | 'sentence_reorganize' - -export interface ExerciseSession { - id: string - user_id: string - vocabulary_list: string[] - exercise_type: ExerciseType - start_time: string - end_time?: string - total_questions: number - completed_questions: number - correct_answers: number - incorrect_answers: number - skipped_questions: number - average_response_time: number - status: 'in_progress' | 'completed' | 'abandoned' -} - -export interface ExerciseResult { - id: string - session_id: string - vocabulary_id: string - exercise_id: string - user_answer_id: string - is_correct: boolean - response_time: number // in milliseconds - timestamp: string - hints_used: number -} - -export interface PracticeSettings { - exercise_type: ExerciseType - difficulty_levels: number[] - question_count: number - time_limit_per_question?: number // in seconds - enable_hints: boolean - enable_audio: boolean - shuffle_options: boolean - immediate_feedback: boolean -} - -export interface ReviewSchedule { - vocabulary_id: string - due_date: string - priority: 'low' | 'medium' | 'high' | 'urgent' - review_type: 'new' | 'review' | 'difficult' - estimated_study_time: number // in minutes -} - -export interface LearningAnalytics { - total_words_learned: number - total_study_time: number - average_accuracy: number - streak_days: number - words_due_for_review: number - mastery_distribution: { - beginner: number // 0-25 - intermediate: number // 26-50 - advanced: number // 51-75 - mastered: number // 76-100 - } - weekly_progress: { - week: string - words_studied: number - accuracy: number - study_time: number - }[] - error_patterns: { - pattern: string - count: number - improvement_suggestion: string - }[] -} \ No newline at end of file diff --git a/apps/web/src/utils/AudioManager.js b/apps/web/src/utils/AudioManager.js new file mode 100644 index 0000000..c5dc0e0 --- /dev/null +++ b/apps/web/src/utils/AudioManager.js @@ -0,0 +1,202 @@ +// Audio Manager for Text-to-Speech functionality +export class AudioManager { + constructor() { + this.speechSynthesis = window.speechSynthesis; + this.voices = []; + this.currentVoice = null; + this.isInitialized = false; + this.init(); + } + + async init() { + try { + // Wait for voices to be loaded + if (this.speechSynthesis.getVoices().length === 0) { + await new Promise(resolve => { + this.speechSynthesis.addEventListener('voiceschanged', resolve, { once: true }); + }); + } + + this.voices = this.speechSynthesis.getVoices(); + this.selectBestVoice(); + this.isInitialized = true; + + console.log('🔊 AudioManager initialized with', this.voices.length, 'voices'); + } catch (error) { + console.error('AudioManager initialization failed:', error); + } + } + + selectBestVoice() { + // Prefer English voices in order of preference + const preferredVoices = [ + 'Google US English', + 'Microsoft Zira - English (United States)', + 'Alex', // macOS + 'Samantha', // macOS + 'Google UK English Female', + 'Microsoft Hazel - English (Great Britain)' + ]; + + // Try to find preferred voice + for (const preferredName of preferredVoices) { + const voice = this.voices.find(v => v.name.includes(preferredName)); + if (voice) { + this.currentVoice = voice; + console.log('🎤 Selected voice:', voice.name); + return; + } + } + + // Fallback to first English voice + const englishVoice = this.voices.find(voice => + voice.lang.startsWith('en') + ); + + if (englishVoice) { + this.currentVoice = englishVoice; + console.log('🎤 Using fallback voice:', englishVoice.name); + } else if (this.voices.length > 0) { + this.currentVoice = this.voices[0]; + console.log('🎤 Using default voice:', this.voices[0].name); + } + } + + async speak(text, options = {}) { + if (!this.isInitialized) { + await this.init(); + } + + return new Promise((resolve, reject) => { + if (!this.speechSynthesis) { + reject(new Error('Speech synthesis not supported')); + return; + } + + // Stop any ongoing speech + this.speechSynthesis.cancel(); + + const utterance = new SpeechSynthesisUtterance(text); + + // Configure utterance + utterance.voice = this.currentVoice; + utterance.rate = options.rate || 0.9; + utterance.pitch = options.pitch || 1; + utterance.volume = options.volume || 1; + + // Event listeners + utterance.onstart = () => { + console.log('🔊 Started speaking:', text); + }; + + utterance.onend = () => { + console.log('✅ Finished speaking:', text); + resolve(); + }; + + utterance.onerror = (event) => { + console.error('❌ Speech error:', event.error); + reject(new Error(`Speech synthesis error: ${event.error}`)); + }; + + // Start speaking + this.speechSynthesis.speak(utterance); + }); + } + + stop() { + if (this.speechSynthesis) { + this.speechSynthesis.cancel(); + } + } + + // Specific method for vocabulary words + async speakWord(word, options = {}) { + const wordOptions = { + rate: 0.8, // Slower for vocabulary learning + pitch: 1, + volume: 1, + ...options + }; + + try { + await this.speak(word, wordOptions); + } catch (error) { + console.error('Failed to speak word:', word, error); + // Fallback: show visual feedback + this.showSpeechFallback(word); + } + } + + // Visual fallback when speech fails + showSpeechFallback(word) { + // Create temporary visual indicator + const indicator = document.createElement('div'); + indicator.textContent = `🔊 "${word}"`; + indicator.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: var(--primary-teal); + color: white; + padding: 10px 15px; + border-radius: 5px; + font-size: 14px; + font-weight: 600; + z-index: 1000; + animation: fadeInOut 2s ease-in-out; + `; + + // Add fade animation + const style = document.createElement('style'); + style.textContent = ` + @keyframes fadeInOut { + 0% { opacity: 0; transform: translateY(-10px); } + 20% { opacity: 1; transform: translateY(0); } + 80% { opacity: 1; transform: translateY(0); } + 100% { opacity: 0; transform: translateY(-10px); } + } + `; + document.head.appendChild(style); + document.body.appendChild(indicator); + + // Remove after animation + setTimeout(() => { + if (indicator.parentNode) { + indicator.parentNode.removeChild(indicator); + } + if (style.parentNode) { + style.parentNode.removeChild(style); + } + }, 2000); + } + + // Get available voices for user selection + getAvailableVoices() { + return this.voices.filter(voice => voice.lang.startsWith('en')); + } + + // Set custom voice + setVoice(voiceName) { + const voice = this.voices.find(v => v.name === voiceName); + if (voice) { + this.currentVoice = voice; + console.log('🎤 Voice changed to:', voice.name); + } + } + + // Check if audio is supported + isSupported() { + return !!(window.speechSynthesis && window.SpeechSynthesisUtterance); + } + + // Get current voice info + getCurrentVoice() { + return this.currentVoice ? { + name: this.currentVoice.name, + lang: this.currentVoice.lang, + gender: this.currentVoice.name.toLowerCase().includes('female') ? 'female' : + this.currentVoice.name.toLowerCase().includes('male') ? 'male' : 'unknown' + } : null; + } +} \ No newline at end of file diff --git a/apps/web/src/utils/index.ts b/apps/web/src/utils/index.ts deleted file mode 100644 index 23495f7..0000000 --- a/apps/web/src/utils/index.ts +++ /dev/null @@ -1,314 +0,0 @@ -// 工具函數集合 - -/** - * 生成唯一ID - */ -export const generateId = (prefix = 'id'): string => { - return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` -} - -/** - * 防抖函數 - */ -export const debounce = any>( - func: T, - wait: number -): ((...args: Parameters) => void) => { - let timeout: NodeJS.Timeout | null = null - - return (...args: Parameters) => { - if (timeout) { - clearTimeout(timeout) - } - timeout = setTimeout(() => func.apply(null, args), wait) - } -} - -/** - * 節流函數 - */ -export const throttle = any>( - func: T, - wait: number -): ((...args: Parameters) => void) => { - let inThrottle: boolean - - return (...args: Parameters) => { - if (!inThrottle) { - func.apply(null, args) - inThrottle = true - setTimeout(() => (inThrottle = false), wait) - } - } -} - -/** - * 深度合併對象 - */ -export const deepMerge = (target: T, ...sources: any[]): T => { - if (!sources.length) return target - const source = sources.shift() - - if (isObject(target) && isObject(source)) { - for (const key in source) { - if (isObject(source[key])) { - if (!(target as any)[key]) Object.assign(target as any, { [key]: {} }) - deepMerge((target as any)[key], source[key]) - } else { - Object.assign(target as any, { [key]: source[key] }) - } - } - } - - return deepMerge(target, ...sources) -} - -/** - * 檢查是否為對象 - */ -export const isObject = (item: any): boolean => { - return item && typeof item === 'object' && !Array.isArray(item) -} - -/** - * 格式化文件大小 - */ -export const formatFileSize = (bytes: number): string => { - if (bytes === 0) return '0 Bytes' - - const k = 1024 - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] -} - -/** - * 格式化時間 - */ -export const formatTime = (seconds: number): string => { - const mins = Math.floor(seconds / 60) - const secs = Math.floor(seconds % 60) - return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` -} - -/** - * 格式化數字(添加千位分隔符) - */ -export const formatNumber = (num: number): string => { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') -} - -/** - * 複製到剪貼板 - */ -export const copyToClipboard = async (text: string): Promise => { - try { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(text) - return true - } else { - // 降級方案 - const textArea = document.createElement('textarea') - textArea.value = text - textArea.style.position = 'absolute' - textArea.style.left = '-999999px' - document.body.appendChild(textArea) - textArea.focus() - textArea.select() - - try { - document.execCommand('copy') - return true - } catch (error) { - console.error('Copy failed:', error) - return false - } finally { - document.body.removeChild(textArea) - } - } - } catch (error) { - console.error('Copy to clipboard failed:', error) - return false - } -} - -/** - * 下載文件 - */ -export const downloadFile = (url: string, filename?: string): void => { - const link = document.createElement('a') - link.href = url - if (filename) { - link.download = filename - } - link.style.display = 'none' - document.body.appendChild(link) - link.click() - document.body.removeChild(link) -} - -/** - * 等待指定時間 - */ -export const sleep = (ms: number): Promise => { - return new Promise(resolve => setTimeout(resolve, ms)) -} - -/** - * 獲取URL參數 - */ -export const getUrlParams = (url?: string): Record => { - const urlObject = new URL(url || window.location.href) - const params: Record = {} - - urlObject.searchParams.forEach((value, key) => { - params[key] = value - }) - - return params -} - -/** - * 檢查是否為移動設備 - */ -export const isMobile = (): boolean => { - return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent - ) -} - -/** - * 檢查是否支援WebP - */ -export const supportsWebP = (): Promise => { - return new Promise((resolve) => { - const webP = new Image() - webP.onload = webP.onerror = () => { - resolve(webP.height === 2) - } - webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA' - }) -} - -/** - * 清理HTML標籤 - */ -export const stripHtml = (html: string): string => { - const tmp = document.createElement('DIV') - tmp.innerHTML = html - return tmp.textContent || tmp.innerText || '' -} - -/** - * 截斷文本 - */ -export const truncateText = (text: string, maxLength: number, suffix = '...'): string => { - if (text.length <= maxLength) return text - return text.substring(0, maxLength - suffix.length) + suffix -} - -/** - * 隨機生成顏色 - */ -export const randomColor = (): string => { - return `#${Math.floor(Math.random() * 16777215).toString(16)}` -} - -/** - * 檢查是否為空值 - */ -export const isEmpty = (value: any): boolean => { - if (value === null || value === undefined) return true - if (typeof value === 'string' && value.trim() === '') return true - if (Array.isArray(value) && value.length === 0) return true - if (isObject(value) && Object.keys(value).length === 0) return true - return false -} - -/** - * 首字母大寫 - */ -export const capitalize = (str: string): string => { - return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() -} - -/** - * 駝峰轉kebab-case - */ -export const camelToKebab = (str: string): string => { - return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase() -} - -/** - * kebab-case轉駝峰 - */ -export const kebabToCamel = (str: string): string => { - return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase()) -} - -/** - * 獲取檔案副檔名 - */ -export const getFileExtension = (filename: string): string => { - return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2) -} - -/** - * 驗證郵箱格式 - */ -export const isValidEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - return emailRegex.test(email) -} - -/** - * 驗證手機號碼(台灣) - */ -export const isValidTaiwanPhone = (phone: string): boolean => { - const phoneRegex = /^09\d{8}$/ - return phoneRegex.test(phone) -} - -/** - * 生成隨機字符串 - */ -export const randomString = (length = 8): string => { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - let result = '' - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)) - } - return result -} - -/** - * 比較兩個版本號 - */ -export const compareVersions = (version1: string, version2: string): number => { - const v1Parts = version1.split('.').map(Number) - const v2Parts = version2.split('.').map(Number) - - const maxLength = Math.max(v1Parts.length, v2Parts.length) - - for (let i = 0; i < maxLength; i++) { - const v1Part = v1Parts[i] || 0 - const v2Part = v2Parts[i] || 0 - - if (v1Part > v2Part) return 1 - if (v1Part < v2Part) return -1 - } - - return 0 -} - -// 常用的正則表達式 -export const REGEX = { - email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, - phone: /^09\d{8}$/, - url: /^https?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/, - password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/, - ipAddress: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, - hexColor: /^#?([a-f\d]{3}|[a-f\d]{6})$/i, - uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i -} \ No newline at end of file diff --git a/apps/web/src/utils/reportExporter.ts b/apps/web/src/utils/reportExporter.ts deleted file mode 100644 index ae2dc98..0000000 --- a/apps/web/src/utils/reportExporter.ts +++ /dev/null @@ -1,421 +0,0 @@ -interface ExportOptions { - includeCharts: boolean - includeStats: boolean - includeSuggestions: boolean - includeWeaknesses: boolean -} - -interface AnalyticsData { - timeRange: string - overallStats: Array<{ - title: string - value: string - subtitle?: string - change?: string - }> - chartData: { - masteryDistribution: any - progressTrend: any - performanceRadar: any - } - categoryStats: Array<{ - category: string - total: number - mastered: number - progress: number - difficulty: number - }> - learningRecommendations: Array<{ - title: string - description: string - priority: string - }> - identifiedWeaknesses: Array<{ - category: string - severity: string - accuracy: number - avgResponseTime: number - }> -} - -/** - * 匯出學習分析報告 - * @param format 匯出格式 ('pdf' | 'xlsx' | 'csv') - * @param data 分析數據 - * @param options 匯出選項 - */ -export async function exportAnalyticsReport( - format: 'pdf' | 'xlsx' | 'csv', - data: AnalyticsData, - options: ExportOptions -): Promise { - try { - switch (format) { - case 'pdf': - await exportToPDF(data, options) - break - case 'xlsx': - await exportToExcel(data, options) - break - case 'csv': - await exportToCSV(data, options) - break - default: - throw new Error(`不支援的匯出格式: ${format}`) - } - } catch (error) { - console.error('匯出報告失敗:', error) - throw error - } -} - -/** - * 匯出為PDF格式 - */ -async function exportToPDF(data: AnalyticsData, options: ExportOptions): Promise { - // 動態導入jsPDF以避免打包體積過大 - const { jsPDF } = await import('jspdf') - - const doc = new jsPDF() - let yPosition = 20 - - // 設定字體 - doc.setFont('helvetica', 'bold') - doc.setFontSize(18) - - // 標題 - doc.text('詞彙學習分析報告', 20, yPosition) - yPosition += 15 - - // 時間範圍 - doc.setFontSize(12) - doc.setFont('helvetica', 'normal') - doc.text(`報告期間: ${data.timeRange}`, 20, yPosition) - doc.text(`生成時間: ${new Date().toLocaleString('zh-TW')}`, 20, yPosition + 7) - yPosition += 25 - - // 整體統計 - if (options.includeStats) { - doc.setFont('helvetica', 'bold') - doc.setFontSize(14) - doc.text('整體統計', 20, yPosition) - yPosition += 10 - - doc.setFont('helvetica', 'normal') - doc.setFontSize(10) - - data.overallStats.forEach(stat => { - doc.text(`${stat.title}: ${stat.value}`, 25, yPosition) - if (stat.subtitle) { - doc.text(` ${stat.subtitle}`, 25, yPosition + 5) - yPosition += 5 - } - if (stat.change) { - doc.text(` 變化: ${stat.change}`, 25, yPosition + 5) - yPosition += 5 - } - yPosition += 8 - }) - - yPosition += 10 - } - - // 詞彙分類統計表格 - if (options.includeStats) { - doc.setFont('helvetica', 'bold') - doc.setFontSize(14) - doc.text('詞彙分類統計', 20, yPosition) - yPosition += 15 - - // 表格標題 - const tableHeaders = ['分類', '總詞彙', '已掌握', '進度', '難度'] - const colWidths = [40, 25, 25, 25, 25] - let xPosition = 20 - - doc.setFont('helvetica', 'bold') - doc.setFontSize(10) - tableHeaders.forEach((header, index) => { - doc.text(header, xPosition, yPosition) - xPosition += colWidths[index] - }) - yPosition += 8 - - // 表格數據 - doc.setFont('helvetica', 'normal') - data.categoryStats.forEach(row => { - xPosition = 20 - const rowData = [ - row.category, - row.total.toString(), - row.mastered.toString(), - `${row.progress}%`, - '★'.repeat(row.difficulty) - ] - - rowData.forEach((cell, index) => { - doc.text(cell, xPosition, yPosition) - xPosition += colWidths[index] - }) - yPosition += 7 - }) - - yPosition += 15 - } - - // 學習建議 - if (options.includeSuggestions) { - // 檢查是否需要新頁面 - if (yPosition > 250) { - doc.addPage() - yPosition = 20 - } - - doc.setFont('helvetica', 'bold') - doc.setFontSize(14) - doc.text('學習建議', 20, yPosition) - yPosition += 15 - - doc.setFont('helvetica', 'normal') - doc.setFontSize(10) - - data.learningRecommendations.forEach((suggestion, index) => { - const priorityText = getPriorityText(suggestion.priority) - doc.text(`${index + 1}. ${suggestion.title} (${priorityText})`, 25, yPosition) - yPosition += 7 - - // 處理長文本換行 - const lines = doc.splitTextToSize(suggestion.description, 150) - lines.forEach((line: string) => { - doc.text(line, 30, yPosition) - yPosition += 6 - }) - yPosition += 5 - }) - } - - // 薄弱點分析 - if (options.includeWeaknesses) { - if (yPosition > 250) { - doc.addPage() - yPosition = 20 - } - - doc.setFont('helvetica', 'bold') - doc.setFontSize(14) - doc.text('薄弱點分析', 20, yPosition) - yPosition += 15 - - doc.setFont('helvetica', 'normal') - doc.setFontSize(10) - - data.identifiedWeaknesses.forEach(weakness => { - doc.text(`• ${weakness.category}`, 25, yPosition) - doc.text(`正確率: ${weakness.accuracy}%`, 35, yPosition + 6) - doc.text(`平均反應時間: ${weakness.avgResponseTime}ms`, 35, yPosition + 12) - yPosition += 20 - }) - } - - // 頁腳 - const pageCount = doc.getNumberOfPages() - for (let i = 1; i <= pageCount; i++) { - doc.setPage(i) - doc.setFont('helvetica', 'normal') - doc.setFontSize(8) - doc.text(`第 ${i} 頁,共 ${pageCount} 頁`, 170, 285) - doc.text('Drama Ling 學習分析報告', 20, 285) - } - - // 下載檔案 - const filename = `詞彙學習分析報告_${new Date().toISOString().split('T')[0]}.pdf` - doc.save(filename) -} - -/** - * 匯出為Excel格式 - */ -async function exportToExcel(data: AnalyticsData, options: ExportOptions): Promise { - // 動態導入xlsx以避免打包體積過大 - const XLSX = await import('xlsx') - - const workbook = XLSX.utils.book_new() - - // 整體統計工作表 - if (options.includeStats) { - const statsData = [ - ['項目', '數值', '說明', '變化'], - ...data.overallStats.map(stat => [ - stat.title, - stat.value, - stat.subtitle || '', - stat.change || '' - ]) - ] - - const statsWorksheet = XLSX.utils.aoa_to_sheet(statsData) - XLSX.utils.book_append_sheet(workbook, statsWorksheet, '整體統計') - } - - // 詞彙分類統計工作表 - if (options.includeStats) { - const categoryData = [ - ['詞彙分類', '總詞彙數', '已掌握', '進度(%)', '平均難度'], - ...data.categoryStats.map(row => [ - row.category, - row.total, - row.mastered, - row.progress, - row.difficulty - ]) - ] - - const categoryWorksheet = XLSX.utils.aoa_to_sheet(categoryData) - XLSX.utils.book_append_sheet(workbook, categoryWorksheet, '詞彙分類統計') - } - - // 學習建議工作表 - if (options.includeSuggestions) { - const suggestionsData = [ - ['建議標題', '詳細說明', '優先級'], - ...data.learningRecommendations.map(suggestion => [ - suggestion.title, - suggestion.description, - getPriorityText(suggestion.priority) - ]) - ] - - const suggestionsWorksheet = XLSX.utils.aoa_to_sheet(suggestionsData) - XLSX.utils.book_append_sheet(workbook, suggestionsWorksheet, '學習建議') - } - - // 薄弱點分析工作表 - if (options.includeWeaknesses) { - const weaknessesData = [ - ['薄弱領域', '嚴重程度', '正確率(%)', '平均反應時間(ms)'], - ...data.identifiedWeaknesses.map(weakness => [ - weakness.category, - getSeverityText(weakness.severity), - weakness.accuracy, - weakness.avgResponseTime - ]) - ] - - const weaknessesWorksheet = XLSX.utils.aoa_to_sheet(weaknessesData) - XLSX.utils.book_append_sheet(workbook, weaknessesWorksheet, '薄弱點分析') - } - - // 下載檔案 - const filename = `詞彙學習分析報告_${new Date().toISOString().split('T')[0]}.xlsx` - XLSX.writeFile(workbook, filename) -} - -/** - * 匯出為CSV格式 - */ -async function exportToCSV(data: AnalyticsData, options: ExportOptions): Promise { - let csvContent = '' - - // CSV標題 - csvContent += `詞彙學習分析報告\n` - csvContent += `報告期間,${data.timeRange}\n` - csvContent += `生成時間,${new Date().toLocaleString('zh-TW')}\n\n` - - // 整體統計 - if (options.includeStats) { - csvContent += '整體統計\n' - csvContent += '項目,數值,說明,變化\n' - data.overallStats.forEach(stat => { - csvContent += `${stat.title},${stat.value},${stat.subtitle || ''},${stat.change || ''}\n` - }) - csvContent += '\n' - } - - // 詞彙分類統計 - if (options.includeStats) { - csvContent += '詞彙分類統計\n' - csvContent += '詞彙分類,總詞彙數,已掌握,進度(%),平均難度\n' - data.categoryStats.forEach(row => { - csvContent += `${row.category},${row.total},${row.mastered},${row.progress},${row.difficulty}\n` - }) - csvContent += '\n' - } - - // 學習建議 - if (options.includeSuggestions) { - csvContent += '學習建議\n' - csvContent += '建議標題,詳細說明,優先級\n' - data.learningRecommendations.forEach(suggestion => { - csvContent += `${suggestion.title},"${suggestion.description}",${getPriorityText(suggestion.priority)}\n` - }) - csvContent += '\n' - } - - // 薄弱點分析 - if (options.includeWeaknesses) { - csvContent += '薄弱點分析\n' - csvContent += '薄弱領域,嚴重程度,正確率(%),平均反應時間(ms)\n' - data.identifiedWeaknesses.forEach(weakness => { - csvContent += `${weakness.category},${getSeverityText(weakness.severity)},${weakness.accuracy},${weakness.avgResponseTime}\n` - }) - } - - // 下載檔案 - const filename = `詞彙學習分析報告_${new Date().toISOString().split('T')[0]}.csv` - downloadTextFile(csvContent, filename, 'text/csv;charset=utf-8;') -} - -/** - * 下載文本檔案 - */ -function downloadTextFile(content: string, filename: string, mimeType: string): void { - const blob = new Blob(['\uFEFF' + content], { type: mimeType }) // 添加 BOM 以支援中文 - const url = URL.createObjectURL(blob) - - const link = document.createElement('a') - link.href = url - link.download = filename - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - - URL.revokeObjectURL(url) -} - -/** - * 獲取優先級文本 - */ -function getPriorityText(priority: string): string { - const priorities: Record = { - high: '高優先級', - medium: '中優先級', - low: '低優先級' - } - return priorities[priority] || '未知優先級' -} - -/** - * 獲取嚴重程度文本 - */ -function getSeverityText(severity: string): string { - const severities: Record = { - high: '嚴重', - medium: '中等', - low: '輕微' - } - return severities[severity] || '未知' -} - -/** - * 將圖表轉換為圖片 (用於PDF匯出) - */ -export async function chartToImage(chartElement: HTMLCanvasElement): Promise { - return new Promise((resolve) => { - chartElement.toBlob((blob) => { - if (blob) { - const reader = new FileReader() - reader.onload = () => resolve(reader.result as string) - reader.readAsDataURL(blob) - } - }) - }) -} \ No newline at end of file diff --git a/apps/web/src/utils/spacedRepetition.ts b/apps/web/src/utils/spacedRepetition.ts deleted file mode 100644 index 36af7dc..0000000 --- a/apps/web/src/utils/spacedRepetition.ts +++ /dev/null @@ -1,457 +0,0 @@ -/** - * 智能間隔複習演算法 - * 基於 Ebbinghaus 遺忘曲線和 SM-2 演算法的改良版 - */ - -export interface VocabularyReviewData { - id: string - lastReviewed: Date | null - reviewCount: number - easeFactor: number - interval: number // 間隔天數 - nextReviewDate: Date - consecutiveCorrect: number - consecutiveWrong: number - averageResponseTime: number - difficultyLevel: number // 1-5 - masteryLevel: number // 0-100 - weaknessPatterns: WeaknessPattern[] -} - -export interface WeaknessPattern { - type: 'spelling' | 'meaning' | 'pronunciation' | 'usage' | 'grammar' - severity: number // 0-1 - lastOccurrence: Date - frequency: number -} - -export interface ReviewSession { - vocabularyId: string - startTime: Date - endTime?: Date - responses: ReviewResponse[] - overallAccuracy: number - averageResponseTime: number -} - -export interface ReviewResponse { - questionType: 'choice' | 'matching' | 'spelling' | 'pronunciation' - isCorrect: boolean - responseTime: number - confidence: number // 1-5 用戶自評信心程度 - hintsUsed: number - timestamp: Date -} - -/** - * 智能間隔複習演算法類 - * 結合多種演算法優點,提供個人化的複習排程 - */ -export class SpacedRepetitionAlgorithm { - // SM-2 演算法的預設參數 - private readonly MIN_EASE_FACTOR = 1.3 - private readonly MAX_EASE_FACTOR = 2.5 - private readonly DEFAULT_EASE_FACTOR = 2.5 - private readonly EASE_FACTOR_ADJUSTMENT = 0.15 - - // 遺忘曲線參數 - private readonly FORGETTING_CURVE_DECAY = 0.84 - private readonly RETENTION_THRESHOLD = 0.9 - - /** - * 計算下次複習時間 - * @param reviewData 詞彙複習數據 - * @param sessionResult 最新學習會話結果 - * @returns 更新後的複習數據 - */ - calculateNextReview(reviewData: VocabularyReviewData, sessionResult: ReviewSession): VocabularyReviewData { - const updatedData = { ...reviewData } - const now = new Date() - - // 更新基礎統計 - updatedData.lastReviewed = now - updatedData.reviewCount += 1 - updatedData.averageResponseTime = this.updateAverageResponseTime( - reviewData.averageResponseTime, - sessionResult.averageResponseTime, - reviewData.reviewCount - ) - - // 根據表現調整難度因子 - const performanceScore = this.calculatePerformanceScore(sessionResult) - updatedData.easeFactor = this.adjustEaseFactor(reviewData.easeFactor, performanceScore) - - // 更新連續正確/錯誤次數 - if (sessionResult.overallAccuracy >= 0.8) { - updatedData.consecutiveCorrect += 1 - updatedData.consecutiveWrong = 0 - } else { - updatedData.consecutiveWrong += 1 - updatedData.consecutiveCorrect = 0 - } - - // 計算新的間隔時間 - updatedData.interval = this.calculateInterval(updatedData, performanceScore) - - // 設置下次複習日期 - updatedData.nextReviewDate = new Date(now.getTime() + updatedData.interval * 24 * 60 * 60 * 1000) - - // 更新掌握程度 - updatedData.masteryLevel = this.calculateMasteryLevel(updatedData, sessionResult) - - // 分析薄弱點模式 - updatedData.weaknessPatterns = this.analyzeWeaknessPatterns(reviewData.weaknessPatterns, sessionResult) - - return updatedData - } - - /** - * 計算表現分數 (0-1) - */ - private calculatePerformanceScore(session: ReviewSession): number { - const accuracyWeight = 0.6 - const speedWeight = 0.2 - const confidenceWeight = 0.2 - - // 正確率分數 - const accuracyScore = session.overallAccuracy - - // 速度分數 (反應時間越短分數越高) - const avgResponseTime = session.averageResponseTime - const speedScore = Math.max(0, 1 - (avgResponseTime - 1000) / 4000) // 1-5秒的範圍 - - // 信心程度分數 - const avgConfidence = session.responses.reduce((sum, r) => sum + r.confidence, 0) / session.responses.length - const confidenceScore = (avgConfidence - 1) / 4 // 1-5 轉換為 0-1 - - return accuracyScore * accuracyWeight + speedScore * speedWeight + confidenceScore * confidenceWeight - } - - /** - * 調整難度因子 - */ - private adjustEaseFactor(currentEaseFactor: number, performanceScore: number): number { - let newEaseFactor = currentEaseFactor - - if (performanceScore >= 0.8) { - // 表現良好,增加難度因子 - newEaseFactor += this.EASE_FACTOR_ADJUSTMENT - } else if (performanceScore < 0.6) { - // 表現不佳,降低難度因子 - newEaseFactor -= this.EASE_FACTOR_ADJUSTMENT - } - - // 限制在合理範圍內 - return Math.max(this.MIN_EASE_FACTOR, Math.min(this.MAX_EASE_FACTOR, newEaseFactor)) - } - - /** - * 計算複習間隔 - */ - private calculateInterval(reviewData: VocabularyReviewData, performanceScore: number): number { - let baseInterval: number - - if (reviewData.reviewCount === 1) { - baseInterval = 1 // 第一次複習:1天后 - } else if (reviewData.reviewCount === 2) { - baseInterval = 6 // 第二次複習:6天后 - } else { - // 使用 SM-2 演算法 - baseInterval = Math.round(reviewData.interval * reviewData.easeFactor) - } - - // 根據表現調整間隔 - let adjustmentFactor = 1.0 - - if (performanceScore >= 0.9) { - adjustmentFactor = 1.3 // 表現極好,延長間隔 - } else if (performanceScore >= 0.8) { - adjustmentFactor = 1.1 // 表現良好,略微延長 - } else if (performanceScore < 0.6) { - adjustmentFactor = 0.6 // 表現不佳,縮短間隔 - } else if (performanceScore < 0.4) { - adjustmentFactor = 0.3 // 表現很差,大幅縮短間隔 - } - - // 考慮連續錯誤次數 - if (reviewData.consecutiveWrong >= 2) { - adjustmentFactor *= 0.5 // 連續錯誤,進一步縮短間隔 - } - - // 考慮詞彙難度 - const difficultyAdjustment = 1 + (reviewData.difficultyLevel - 3) * 0.1 - - const finalInterval = Math.max(1, Math.round(baseInterval * adjustmentFactor * difficultyAdjustment)) - - return Math.min(finalInterval, 365) // 最長不超過一年 - } - - /** - * 計算掌握程度 - */ - private calculateMasteryLevel(reviewData: VocabularyReviewData, session: ReviewSession): number { - const currentLevel = reviewData.masteryLevel - const performanceScore = this.calculatePerformanceScore(session) - - // 基於表現調整掌握程度 - let adjustment = 0 - - if (performanceScore >= 0.9) { - adjustment = 15 // 表現極好 - } else if (performanceScore >= 0.8) { - adjustment = 10 // 表現良好 - } else if (performanceScore >= 0.6) { - adjustment = 5 // 表現一般 - } else if (performanceScore >= 0.4) { - adjustment = -5 // 表現不佳 - } else { - adjustment = -10 // 表現很差 - } - - // 考慮複習次數和間隔 - const stabilityBonus = Math.min(5, reviewData.reviewCount) - const intervalBonus = Math.min(3, Math.log(reviewData.interval)) - - const newLevel = Math.max(0, Math.min(100, currentLevel + adjustment + stabilityBonus + intervalBonus)) - - return Math.round(newLevel) - } - - /** - * 分析薄弱點模式 - */ - private analyzeWeaknessPatterns( - currentPatterns: WeaknessPattern[], - session: ReviewSession - ): WeaknessPattern[] { - const patterns = [...currentPatterns] - const now = new Date() - - // 分析錯誤類型 - const errorTypes = this.identifyErrorTypes(session.responses) - - errorTypes.forEach(errorType => { - const existingPattern = patterns.find(p => p.type === errorType.type) - - if (existingPattern) { - // 更新現有模式 - existingPattern.severity = Math.min(1, existingPattern.severity + errorType.severity * 0.1) - existingPattern.lastOccurrence = now - existingPattern.frequency += 1 - } else { - // 創建新模式 - patterns.push({ - type: errorType.type, - severity: errorType.severity, - lastOccurrence: now, - frequency: 1 - }) - } - }) - - // 減少長期未出現錯誤的嚴重程度 - patterns.forEach(pattern => { - const daysSinceLastError = (now.getTime() - pattern.lastOccurrence.getTime()) / (24 * 60 * 60 * 1000) - if (daysSinceLastError > 7) { - pattern.severity = Math.max(0, pattern.severity - 0.05 * daysSinceLastError) - } - }) - - // 只保留嚴重程度 > 0.1 的模式 - return patterns.filter(p => p.severity > 0.1) - } - - /** - * 識別錯誤類型 - */ - private identifyErrorTypes(responses: ReviewResponse[]): Array<{type: WeaknessPattern['type'], severity: number}> { - const errorTypes: Array<{type: WeaknessPattern['type'], severity: number}> = [] - - responses.forEach(response => { - if (!response.isCorrect) { - // 根據問題類型和表現推斷錯誤類型 - switch (response.questionType) { - case 'choice': - errorTypes.push({ type: 'meaning', severity: 0.6 }) - break - case 'spelling': - errorTypes.push({ type: 'spelling', severity: 0.8 }) - break - case 'pronunciation': - errorTypes.push({ type: 'pronunciation', severity: 0.7 }) - break - case 'matching': - errorTypes.push({ type: 'usage', severity: 0.5 }) - break - } - - // 根據反應時間推斷 - if (response.responseTime > 5000) { - errorTypes.push({ type: 'meaning', severity: 0.4 }) - } - - // 根據信心程度推斷 - if (response.confidence <= 2) { - errorTypes.push({ type: 'meaning', severity: 0.3 }) - } - } - }) - - return errorTypes - } - - /** - * 更新平均反應時間 - */ - private updateAverageResponseTime( - currentAverage: number, - newResponseTime: number, - reviewCount: number - ): number { - if (reviewCount === 1) { - return newResponseTime - } - - // 使用指數移動平均 - const alpha = 0.3 // 學習率 - return currentAverage * (1 - alpha) + newResponseTime * alpha - } - - /** - * 獲取今日需要複習的詞彙 - */ - static getTodaysReviewVocabulary(allVocabulary: VocabularyReviewData[]): VocabularyReviewData[] { - const today = new Date() - today.setHours(0, 0, 0, 0) - - return allVocabulary - .filter(vocab => vocab.nextReviewDate <= today) - .sort((a, b) => { - // 優先級排序:過期時間越長,優先級越高 - const overdueDaysA = Math.floor((today.getTime() - a.nextReviewDate.getTime()) / (24 * 60 * 60 * 1000)) - const overdueDaysB = Math.floor((today.getTime() - b.nextReviewDate.getTime()) / (24 * 60 * 60 * 1000)) - - if (overdueDaysA !== overdueDaysB) { - return overdueDaysB - overdueDaysA // 過期時間長的排前面 - } - - // 其次考慮掌握程度低的 - return a.masteryLevel - b.masteryLevel - }) - } - - /** - * 生成學習計劃 - */ - static generateLearningPlan( - allVocabulary: VocabularyReviewData[], - daysAhead: number = 7 - ): Map { - const plan = new Map() - const today = new Date() - - for (let i = 0; i < daysAhead; i++) { - const targetDate = new Date(today) - targetDate.setDate(today.getDate() + i) - targetDate.setHours(0, 0, 0, 0) - - const nextDay = new Date(targetDate) - nextDay.setDate(targetDate.getDate() + 1) - - const vocabularyForDay = allVocabulary.filter(vocab => - vocab.nextReviewDate >= targetDate && vocab.nextReviewDate < nextDay - ) - - const dateKey = targetDate.toISOString().split('T')[0] - plan.set(dateKey, vocabularyForDay) - } - - return plan - } - - /** - * 分析學習效率 - */ - static analyzeLearningEfficiency(reviewHistory: ReviewSession[]): { - averageAccuracy: number - averageResponseTime: number - improvementTrend: number - strongestAreas: string[] - weakestAreas: string[] - } { - if (reviewHistory.length === 0) { - return { - averageAccuracy: 0, - averageResponseTime: 0, - improvementTrend: 0, - strongestAreas: [], - weakestAreas: [] - } - } - - const totalAccuracy = reviewHistory.reduce((sum, session) => sum + session.overallAccuracy, 0) - const averageAccuracy = totalAccuracy / reviewHistory.length - - const totalResponseTime = reviewHistory.reduce((sum, session) => sum + session.averageResponseTime, 0) - const averageResponseTime = totalResponseTime / reviewHistory.length - - // 計算改善趨勢 - let improvementTrend = 0 - if (reviewHistory.length >= 2) { - const recentSessions = reviewHistory.slice(-5) // 最近5次 - const olderSessions = reviewHistory.slice(-10, -5) // 之前5次 - - if (olderSessions.length > 0 && recentSessions.length > 0) { - const recentAvg = recentSessions.reduce((sum, s) => sum + s.overallAccuracy, 0) / recentSessions.length - const olderAvg = olderSessions.reduce((sum, s) => sum + s.overallAccuracy, 0) / olderSessions.length - improvementTrend = (recentAvg - olderAvg) * 100 // 百分比改善 - } - } - - // 分析題型表現 - const questionTypeStats = new Map() - reviewHistory.forEach(session => { - session.responses.forEach(response => { - if (!questionTypeStats.has(response.questionType)) { - questionTypeStats.set(response.questionType, []) - } - questionTypeStats.get(response.questionType)!.push(response.isCorrect ? 1 : 0) - }) - }) - - const typeAccuracies = Array.from(questionTypeStats.entries()).map(([type, results]) => ({ - type, - accuracy: results.reduce((sum, r) => sum + r, 0) / results.length - })) - - typeAccuracies.sort((a, b) => b.accuracy - a.accuracy) - - return { - averageAccuracy, - averageResponseTime, - improvementTrend, - strongestAreas: typeAccuracies.slice(0, 2).map(t => t.type), - weakestAreas: typeAccuracies.slice(-2).map(t => t.type) - } - } -} - -/** - * 預設詞彙複習數據創建函數 - */ -export function createDefaultVocabularyReviewData(vocabularyId: string): VocabularyReviewData { - return { - id: vocabularyId, - lastReviewed: null, - reviewCount: 0, - easeFactor: 2.5, - interval: 1, - nextReviewDate: new Date(), // 新詞彙立即需要學習 - consecutiveCorrect: 0, - consecutiveWrong: 0, - averageResponseTime: 3000, - difficultyLevel: 3, - masteryLevel: 0, - weaknessPatterns: [] - } -} \ No newline at end of file diff --git a/apps/web/src/views/HomeView.vue b/apps/web/src/views/HomeView.vue deleted file mode 100644 index 2c9226b..0000000 --- a/apps/web/src/views/HomeView.vue +++ /dev/null @@ -1,581 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/NotFoundView.vue b/apps/web/src/views/NotFoundView.vue deleted file mode 100644 index 09d08fc..0000000 --- a/apps/web/src/views/NotFoundView.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/OfflineView.vue b/apps/web/src/views/OfflineView.vue deleted file mode 100644 index 38f1f51..0000000 --- a/apps/web/src/views/OfflineView.vue +++ /dev/null @@ -1,544 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/auth/ForgotPasswordView.vue b/apps/web/src/views/auth/ForgotPasswordView.vue deleted file mode 100644 index 29ebf50..0000000 --- a/apps/web/src/views/auth/ForgotPasswordView.vue +++ /dev/null @@ -1,327 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/auth/LoginView.vue b/apps/web/src/views/auth/LoginView.vue deleted file mode 100644 index a079866..0000000 --- a/apps/web/src/views/auth/LoginView.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/auth/RegisterView-orig.vue b/apps/web/src/views/auth/RegisterView-orig.vue deleted file mode 100644 index 74ce97a..0000000 --- a/apps/web/src/views/auth/RegisterView-orig.vue +++ /dev/null @@ -1,462 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/auth/RegisterView.vue b/apps/web/src/views/auth/RegisterView.vue deleted file mode 100644 index 10bdaaa..0000000 --- a/apps/web/src/views/auth/RegisterView.vue +++ /dev/null @@ -1,262 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/DialogueView.vue b/apps/web/src/views/learning/DialogueView.vue deleted file mode 100644 index d398ca4..0000000 --- a/apps/web/src/views/learning/DialogueView.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/LearningHomeView.vue b/apps/web/src/views/learning/LearningHomeView.vue deleted file mode 100644 index 9d1f07c..0000000 --- a/apps/web/src/views/learning/LearningHomeView.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/PronunciationView.vue b/apps/web/src/views/learning/PronunciationView.vue deleted file mode 100644 index 6084641..0000000 --- a/apps/web/src/views/learning/PronunciationView.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/RoleplayView.vue b/apps/web/src/views/learning/RoleplayView.vue deleted file mode 100644 index 94265ad..0000000 --- a/apps/web/src/views/learning/RoleplayView.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyAnalyticsDashboard.vue b/apps/web/src/views/learning/VocabularyAnalyticsDashboard.vue deleted file mode 100644 index de577ac..0000000 --- a/apps/web/src/views/learning/VocabularyAnalyticsDashboard.vue +++ /dev/null @@ -1,1222 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyChoicePracticeView.vue b/apps/web/src/views/learning/VocabularyChoicePracticeView.vue deleted file mode 100644 index 331814b..0000000 --- a/apps/web/src/views/learning/VocabularyChoicePracticeView.vue +++ /dev/null @@ -1,1112 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyChoiceResultsView.vue b/apps/web/src/views/learning/VocabularyChoiceResultsView.vue deleted file mode 100644 index 7683e01..0000000 --- a/apps/web/src/views/learning/VocabularyChoiceResultsView.vue +++ /dev/null @@ -1,837 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyIntroductionView.vue b/apps/web/src/views/learning/VocabularyIntroductionView.vue deleted file mode 100644 index 5f265ad..0000000 --- a/apps/web/src/views/learning/VocabularyIntroductionView.vue +++ /dev/null @@ -1,1429 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyMatchingPracticeView.vue b/apps/web/src/views/learning/VocabularyMatchingPracticeView.vue deleted file mode 100644 index e86c932..0000000 --- a/apps/web/src/views/learning/VocabularyMatchingPracticeView.vue +++ /dev/null @@ -1,1352 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyPracticeView.vue b/apps/web/src/views/learning/VocabularyPracticeView.vue deleted file mode 100644 index 490e4d4..0000000 --- a/apps/web/src/views/learning/VocabularyPracticeView.vue +++ /dev/null @@ -1,829 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyReorganizePracticeView.vue b/apps/web/src/views/learning/VocabularyReorganizePracticeView.vue deleted file mode 100644 index b8ea627..0000000 --- a/apps/web/src/views/learning/VocabularyReorganizePracticeView.vue +++ /dev/null @@ -1,1429 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyReviewMain.vue b/apps/web/src/views/learning/VocabularyReviewMain.vue deleted file mode 100644 index 3d6a9b6..0000000 --- a/apps/web/src/views/learning/VocabularyReviewMain.vue +++ /dev/null @@ -1,1309 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyView.vue.backup b/apps/web/src/views/learning/VocabularyView.vue.backup deleted file mode 100644 index 0cb7db9..0000000 --- a/apps/web/src/views/learning/VocabularyView.vue.backup +++ /dev/null @@ -1,744 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyViewNative.vue b/apps/web/src/views/learning/VocabularyViewNative.vue deleted file mode 100644 index 1877ba0..0000000 --- a/apps/web/src/views/learning/VocabularyViewNative.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/learning/VocabularyViewSimple.vue b/apps/web/src/views/learning/VocabularyViewSimple.vue deleted file mode 100644 index 0c6cb03..0000000 --- a/apps/web/src/views/learning/VocabularyViewSimple.vue +++ /dev/null @@ -1,992 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/shop/ShopView.vue b/apps/web/src/views/shop/ShopView.vue deleted file mode 100644 index 26b78ae..0000000 --- a/apps/web/src/views/shop/ShopView.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/src/views/shop/SubscriptionView.vue b/apps/web/src/views/shop/SubscriptionView.vue deleted file mode 100644 index 5406fc2..0000000 --- a/apps/web/src/views/shop/SubscriptionView.vue +++ /dev/null @@ -1,242 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/web/test.html b/apps/web/test.html new file mode 100644 index 0000000..23d17f5 --- /dev/null +++ b/apps/web/test.html @@ -0,0 +1,196 @@ + + + + + + 功能測試 - Drama Ling 詞彙學習 + + + +

🧪 Drama Ling 功能測試頁面

+ +
+

📚 基礎模組測試

+ + +
+
+ +
+

🔊 語音播放測試

+ + +
+
+ +
+

💾 資料持久化測試

+ + +
+
+ +
+

🎯 應用程式載入

+ +
+
+ + + + \ No newline at end of file diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json deleted file mode 100644 index 0d4d222..0000000 --- a/apps/web/tsconfig.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* 模組解析選項 */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - - /* 嚴格性檢查選項 */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - - /* 路徑對應 */ - "baseUrl": ".", - "paths": { - "@/*": ["src/*"], - "~/*": ["src/*"], - "@components/*": ["src/components/*"], - "@modules/*": ["src/modules/*"], - "@stores/*": ["src/stores/*"], - "@services/*": ["src/services/*"], - "@utils/*": ["src/utils/*"], - "@assets/*": ["src/assets/*"] - }, - - /* Vue 相關 */ - "types": ["node"], - "allowJs": true - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.vue", - "tests/**/*.ts" - ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file diff --git a/apps/web/vite.config.js b/apps/web/vite.config.js new file mode 100644 index 0000000..4c621aa --- /dev/null +++ b/apps/web/vite.config.js @@ -0,0 +1,31 @@ +import { defineConfig } from 'vite' +import legacy from '@vitejs/plugin-legacy' + +export default defineConfig({ + plugins: [ + legacy({ + targets: ['defaults', 'not IE 11'] + }) + ], + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + minify: 'terser', + sourcemap: true, + rollupOptions: { + output: { + manualChunks: { + // No external dependencies to chunk + } + } + } + }, + resolve: { + alias: { + '@': new URL('./src', import.meta.url).pathname + } + } +}) \ No newline at end of file diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts deleted file mode 100644 index c55463f..0000000 --- a/apps/web/vite.config.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import { VitePWA } from 'vite-plugin-pwa' -import Components from 'unplugin-vue-components/vite' -import AutoImport from 'unplugin-auto-import/vite' -import { quasar, transformAssetUrls } from '@quasar/vite-plugin' -import path from 'path' - -export default defineConfig({ - plugins: [ - vue({ - template: { transformAssetUrls } - }), - - quasar({ - // sassVariables: 'src/assets/styles/quasar-variables.sass' - }), - - Components({ - resolvers: [ - (componentName) => { - if (componentName.startsWith('Q')) - return { name: componentName, from: 'quasar' } - } - ], - dts: true, - dirs: ['src/components'], - extensions: ['vue'], - deep: true - }), - - AutoImport({ - imports: [ - 'vue', - 'vue-router', - 'pinia', - { - 'quasar': ['useQuasar', '$q', 'Notify', 'Loading', 'Dialog'], - '@vueuse/core': ['useLocalStorage', 'useSessionStorage', 'useFetch'] - } - ], - dts: true, - dirs: ['src/composables', 'src/stores'], - vueTemplate: true - }), - - VitePWA({ - registerType: 'autoUpdate', - workbox: { - globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], - skipWaiting: true, - clientsClaim: true, - runtimeCaching: [ - { - urlPattern: /^https:\/\/api\..*/i, - handler: 'NetworkFirst', - options: { - cacheName: 'api-cache', - networkTimeoutSeconds: 10, - expiration: { - maxEntries: 50, - maxAgeSeconds: 5 * 60, // 5 minutes - }, - cacheableResponse: { - statuses: [0, 200], - }, - }, - }, - { - urlPattern: /\.(?:png|gif|jpg|jpeg|svg|webp)$/, - handler: 'CacheFirst', - options: { - cacheName: 'images-cache', - expiration: { - maxEntries: 100, - maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days - }, - }, - }, - ], - }, - manifest: { - name: 'Drama Ling - 戲劇式語言學習', - short_name: 'Drama Ling', - description: '透過情境對話和互動練習學習語言的 AI 驅動應用程式', - theme_color: '#00E5CC', - background_color: '#1A1A1A', - display: 'standalone', - orientation: 'portrait', - scope: '/', - start_url: '/learning', - categories: ['education', 'productivity'], - lang: 'zh-TW', - screenshots: [ - { - src: '/icons/screenshot-wide.png', - sizes: '1280x720', - type: 'image/png', - form_factor: 'wide', - label: 'Drama Ling 學習介面' - } - ], - shortcuts: [ - { - name: '詞彙學習', - short_name: '詞彙', - description: '開始詞彙練習', - url: '/learning/vocabulary', - icons: [{ src: '/icons/shortcut-vocabulary.png', sizes: '96x96' }] - }, - { - name: '智能複習', - short_name: '複習', - description: '進行智能複習', - url: '/learning/vocabulary/review', - icons: [{ src: '/icons/shortcut-review.png', sizes: '96x96' }] - } - ], - icons: [ - { - src: '/favicon.svg', - sizes: 'any', - type: 'image/svg+xml' - }, - { - src: '/icons/icon-192x192.svg', - sizes: '192x192', - type: 'image/svg+xml' - }, - { - src: '/icons/icon-512x512.svg', - sizes: '512x512', - type: 'image/svg+xml' - } - ] - }, - devOptions: { - enabled: false, // 只在生產環境啟用 - type: 'module', - navigateFallback: 'index.html' - } - }) - ], - - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), - '~': path.resolve(__dirname, 'src'), - '@components': path.resolve(__dirname, 'src/components'), - '@modules': path.resolve(__dirname, 'src/modules'), - '@stores': path.resolve(__dirname, 'src/stores'), - '@services': path.resolve(__dirname, 'src/services'), - '@utils': path.resolve(__dirname, 'src/utils'), - '@assets': path.resolve(__dirname, 'src/assets') - } - }, - - css: { - preprocessorOptions: { - scss: { - additionalData: `@import "@/assets/styles/variables.scss";` - } - } - }, - - build: { - target: 'es2020', - rollupOptions: { - output: { - manualChunks: { - 'vue-vendor': ['vue', 'vue-router', 'pinia'], - 'quasar-vendor': ['quasar'], - 'utils-vendor': ['axios', 'lodash-es', 'dayjs', '@vueuse/core'], - 'validation-vendor': ['vee-validate', 'yup'] - } - } - } - }, - - server: { - host: '0.0.0.0', - port: 3000, - proxy: { - '/api': { - target: 'http://localhost:5000', - changeOrigin: true - } - } - } -}) \ No newline at end of file diff --git a/docs/00_starter/README.md b/docs/00_starter/README.md index 493f749..9d85aac 100644 --- a/docs/00_starter/README.md +++ b/docs/00_starter/README.md @@ -103,7 +103,7 @@ docs/ | `03_frontend/` | 前端技術規格和實作指南 | | `04_mobile/` | 移動端開發技術規格 | | `05_deployment/` | 部署流程和環境配置 | -| `06_development/` | 開發環境設定和工具 | +| `06_development/` | **開發過程管理** - 問題追蹤、環境設定和開發工具配置 | | `07_planning/` | 技術規劃和決策記錄 | **關鍵文檔**: `02_api/` 目錄中的API文檔作為前端和後端團隊之間的契約。 @@ -221,4 +221,4 @@ docs/ --- **最後更新**: 2025-09-10 ✅ -**版本**: 3.0.0 - 整合文檔層規範,明確定義文檔職責和禁止內容 (2025-09-10) \ No newline at end of file +**版本**: 3.0.1 - 重新定義06_development目錄職責,明確開發過程管理範疇 (2025-09-10) \ No newline at end of file diff --git a/docs/03_development/coding-standards.md b/docs/03_development/coding-standards.md index 7acfe8b..ab54d09 100644 --- a/docs/03_development/coding-standards.md +++ b/docs/03_development/coding-standards.md @@ -3,6 +3,13 @@ ## 概述 建立統一的程式碼撰寫規範和開發流程標準,確保團隊協作效率和代碼品質。 +**技術堆疊**: +- **前端**: 原生Web技術 (HTML5 + CSS3/SCSS + Modern JavaScript ES2022+) +- **後端**: .NET Core API +- **建置工具**: Vite 5.x + +**最後更新**: 2025-09-10 + ## 通用開發原則 ### 代碼品質原則 @@ -66,6 +73,319 @@ indent_size = 2 ``` +## HTML5 原生前端規範 + +### HTML 結構標準 + +#### 語義化標記規則 +```html + +
+
+

詞彙學習

+ +
+ +
+

單字介紹

+
+ confidence + /ˈkɒnfɪdəns/ +

信心;自信心;把握

+
+
+ + +
+ + +
+
+
詞彙學習
+
+
+``` + +#### 無障礙標準 +```html + + + +
+
+
+ + + +``` + +### CSS/SCSS 規範 + +#### 組織結構 +```scss +// styles/main.scss - 主要樣式入口 +@import 'base/reset'; +@import 'base/variables'; +@import 'base/typography'; +@import 'components/vocabulary-card'; +@import 'components/mode-selector'; +@import 'pages/vocabulary'; +@import 'utilities/helpers'; +``` + +#### 變數命名和組織 +```scss +// base/variables.scss +// ✅ 語義化變數命名 +$color-primary-teal: #00e5cc; +$color-secondary-purple: #8b5cf6; +$color-success-green: #22c55e; +$color-error-red: #ef4444; +$color-warning-yellow: #f59e0b; + +$color-text-primary: #1f2937; +$color-text-secondary: #6b7280; +$color-text-tertiary: #9ca3af; + +$spacing-xs: 0.25rem; // 4px +$spacing-sm: 0.5rem; // 8px +$spacing-md: 1rem; // 16px +$spacing-lg: 1.5rem; // 24px +$spacing-xl: 2rem; // 32px + +$font-size-xs: 0.75rem; // 12px +$font-size-sm: 0.875rem; // 14px +$font-size-base: 1rem; // 16px +$font-size-lg: 1.125rem; // 18px +$font-size-xl: 1.25rem; // 20px + +// ❌ 避免的命名 +$blue: #00e5cc; // 太泛化 +$s: 0.5rem; // 太簡短 +``` + +#### BEM 方法論 +```scss +// ✅ 使用BEM命名慣例 +.vocabulary-card { + // Block + padding: $spacing-lg; + + &__header { + // Element + border-bottom: 1px solid $color-divider; + } + + &__word { + // Element + font-size: $font-size-xl; + font-weight: 700; + } + + &--featured { + // Modifier + border: 2px solid $color-primary-teal; + } + + &--learning { + // Modifier + background: rgba($color-warning-yellow, 0.1); + } +} + +// 使用方式 +// +``` + +### Modern JavaScript ES2022+ 規範 + +#### 模組系統 +```javascript +// ✅ ES6+ 模組語法 +// modules/vocabulary/index.js +export class VocabularyModule { + constructor() { + this.state = new VocabularyState() + this.api = new VocabularyAPI() + } + + async init() { + await this.loadInitialData() + this.setupEventListeners() + } +} + +// utils/helpers.js +export const debounce = (func, wait) => { + let timeout + return (...args) => { + clearTimeout(timeout) + timeout = setTimeout(() => func.apply(this, args), wait) + } +} + +export const formatPhonetic = (phonetic) => { + return phonetic.startsWith('/') ? phonetic : `/${phonetic}/` +} + +// main.js +import { VocabularyModule } from './modules/vocabulary/index.js' +import { debounce } from './utils/helpers.js' +``` + +#### 現代JavaScript特性 +```javascript +// ✅ 使用現代語法特性 +class VocabularyState { + // 私有屬性 (ES2022) + #words = new Map() + #currentWord = null + + constructor() { + this.progress = 0 + } + + // Getter/Setter + get currentWord() { + return this.#currentWord + } + + set currentWord(word) { + this.#currentWord = word + this.notifySubscribers() + } + + // 異步方法 + async loadVocabulary(lessonId) { + try { + const response = await fetch(`/api/vocabulary/${lessonId}`) + const data = await response.json() + + // 使用解構賦值 + const { words, metadata } = data + + // 使用 Map 和現代陣列方法 + words.forEach(word => { + this.#words.set(word.id, { + ...word, + learned: false, + attempts: 0 + }) + }) + + return { success: true, count: words.length } + } catch (error) { + console.error('載入詞彙失敗:', error) + throw new VocabularyError('無法載入詞彙資料') + } + } + + // 使用可選鏈 (Optional Chaining) + getWordProgress(wordId) { + return this.#words.get(wordId)?.attempts ?? 0 + } + + // 使用 Nullish Coalescing + getWordDefinition(wordId, fallback = '無定義') { + return this.#words.get(wordId)?.definition ?? fallback + } +} + +// 自定義錯誤類 +class VocabularyError extends Error { + constructor(message, code = 'VOCABULARY_ERROR') { + super(message) + this.name = 'VocabularyError' + this.code = code + } +} +``` + +#### 事件處理和DOM操作 +```javascript +// ✅ 現代事件處理 +class VocabularyUI { + constructor() { + this.elements = { + wordCard: document.querySelector('.vocabulary-card'), + playButton: document.querySelector('.play-audio-btn'), + nextButton: document.querySelector('.next-word-btn') + } + + this.audioContext = new (window.AudioContext || window.webkitAudioContext)() + } + + setupEventListeners() { + // 使用委派事件處理 + document.addEventListener('click', this.handleGlobalClick.bind(this)) + + // 鍵盤快捷鍵 + document.addEventListener('keydown', (event) => { + const shortcuts = { + 'Space': () => this.playAudio(), + 'Enter': () => this.nextWord(), + 'KeyR': () => this.repeatWord(), + 'Escape': () => this.exitLesson() + } + + const handler = shortcuts[event.code] + if (handler && !event.target.matches('input, textarea')) { + event.preventDefault() + handler() + } + }) + } + + handleGlobalClick(event) { + // 使用現代選擇器 + if (event.target.matches('.play-audio-btn')) { + const wordId = event.target.dataset.word + this.playWordAudio(wordId) + } + + if (event.target.closest('.vocabulary-card')) { + const card = event.target.closest('.vocabulary-card') + this.highlightCard(card) + } + } + + // 使用 Web Audio API + async playWordAudio(word) { + try { + const audioUrl = `/api/audio/pronunciation/${word}` + const response = await fetch(audioUrl) + const audioBuffer = await response.arrayBuffer() + const audioData = await this.audioContext.decodeAudioData(audioBuffer) + + const source = this.audioContext.createBufferSource() + source.buffer = audioData + source.connect(this.audioContext.destination) + source.start() + + } catch (error) { + console.error('音頻播放失敗:', error) + this.showErrorMessage('無法播放發音') + } + } +} + ### 命名規範 #### C# 命名慣例 @@ -102,892 +422,277 @@ private int u; // 太簡短 private async Task GetUserProfileDataAsync() {} // 冗餘的Data後綴 ``` -#### 常數和列舉 -```typescript -// ✅ 常數使用SCREAMING_SNAKE_CASE -const API_ENDPOINTS = { - USER_PROFILE: '/api/v1/users/profile', - DIALOGUE_START: '/api/v1/dialogues/start', -} as const; +## Vite 5.x 建置配置 -const MAX_DIALOGUE_DURATION_MINUTES = 30; -const DEFAULT_PAGINATION_LIMIT = 20; +### vite.config.js 標準配置 +```javascript +// vite.config.js +import { defineConfig } from 'vite' +import { resolve } from 'path' -// ✅ 列舉使用PascalCase -enum DialogueStatus { - InProgress = 'in_progress', - Completed = 'completed', - Abandoned = 'abandoned', -} - -enum UserSubscriptionPlan { - Free = 'free', - Basic = 'basic', - Premium = 'premium', - Professional = 'professional', -} -``` - -#### 類型定義 -```typescript -// ✅ 介面使用PascalCase,以I開頭(可選) -interface UserProfile { - userId: string; - username: string; - email: string; - createdAt: Date; - subscription: UserSubscriptionPlan; -} - -interface ApiResponse { - success: boolean; - data: T | null; - message?: string; - error?: ApiError; -} - -// ✅ 類型別名使用PascalCase -type DialogueAnalysis = { - grammarScore: number; - semanticScore: number; - fluencyScore: number; - overallScore: number; - feedback: string[]; -}; - -type CreateDialogueRequest = { - scenarioId: string; - difficultyOverride?: string; - targetVocabulary?: string[]; -}; -``` - -### 函數撰寫規範 - -#### 函數設計原則 -```typescript -// ✅ 函數應該小巧、單一職責 -const validateEmailFormat = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); -}; - -const calculateDialogueScore = ( - grammarScore: number, - semanticScore: number, - fluencyScore: number -): number => { - const weights = { grammar: 0.3, semantic: 0.4, fluency: 0.3 }; +export default defineConfig({ + // 開發伺服器配置 + server: { + port: 3000, + host: true, + open: true + }, - return Math.round( - grammarScore * weights.grammar + - semanticScore * weights.semantic + - fluencyScore * weights.fluency - ); -}; - -// ✅ 使用純函數優於副作用函數 -const createUserSlug = (username: string): string => { - return username - .toLowerCase() - .replace(/[^a-z0-9]/g, '-') - .replace(/-+/g, '-') - .trim(); -}; - -// ✅ 錯誤處理明確 -const fetchUserProfile = async (userId: string): Promise => { - try { - const response = await api.get(`/users/${userId}`); - - if (!response.data) { - throw new Error('User profile not found'); - } - - return response.data; - } catch (error) { - logger.error('Failed to fetch user profile', { userId, error }); - throw error; - } -}; -``` - -#### 異步處理規範 -```typescript -// ✅ 使用async/await而非Promise.then -const processDialogueAnalysis = async ( - dialogueId: string -): Promise => { - const dialogue = await getDialogue(dialogueId); - const analysis = await analyzeDialogueWithAI(dialogue.messages); - const savedAnalysis = await saveAnalysisResults(dialogueId, analysis); - - return savedAnalysis; -}; - -// ✅ 適當的錯誤處理和重試機制 -const retryOperation = async ( - operation: () => Promise, - maxRetries: number = 3, - delayMs: number = 1000 -): Promise => { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await operation(); - } catch (error) { - if (attempt === maxRetries) { - throw error; + // 建置配置 + build: { + outDir: 'dist', + assetsDir: 'assets', + sourcemap: true, + minify: 'terser', + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + vocabulary: resolve(__dirname, 'pages/vocabulary.html'), + dialogue: resolve(__dirname, 'pages/dialogue.html') } - - await new Promise(resolve => setTimeout(resolve, delayMs * attempt)); } - } - - throw new Error('All retry attempts failed'); -}; -``` - -### React/React Native 組件規範 - -#### 組件結構 -```tsx -// ✅ 組件檔案結構標準 -import React, { useState, useEffect, useCallback } from 'react'; -import { View, Text, StyleSheet } from 'react-native'; - -import { useAppDispatch, useAppSelector } from '@/hooks/redux'; -import { Button } from '@/components/ui'; -import { DialogueService } from '@/services'; -import { updateDialogueProgress } from '@/store/slices/dialogueSlice'; - -import type { Dialogue, DialogueMessage } from '@/types'; - -// ✅ Props介面定義 -interface DialogueChatProps { - dialogueId: string; - onDialogueComplete: (dialogue: Dialogue) => void; - isVisible: boolean; -} - -// ✅ 組件主體 -export const DialogueChat: React.FC = ({ - dialogueId, - onDialogueComplete, - isVisible, -}) => { - // State declarations - const [inputText, setInputText] = useState(''); - const [isLoading, setIsLoading] = useState(false); - - // Redux selectors - const dialogue = useAppSelector(state => - state.dialogue.currentDialogue - ); - const dispatch = useAppDispatch(); - - // Effects - useEffect(() => { - if (isVisible && dialogueId) { - loadDialogue(); - } - }, [isVisible, dialogueId]); - - // Handlers - const handleSendMessage = useCallback(async () => { - if (!inputText.trim()) return; - - setIsLoading(true); - try { - const response = await DialogueService.sendMessage(dialogueId, inputText); - dispatch(updateDialogueProgress(response)); - setInputText(''); - } catch (error) { - // Error handling - } finally { - setIsLoading(false); - } - }, [dialogueId, inputText, dispatch]); - - const loadDialogue = useCallback(async () => { - // Load dialogue logic - }, [dialogueId]); - - // Early returns - if (!dialogue) { - return ; - } - - // Main render - return ( - - {dialogue.scenarioTitle} - {/* Component content */} - + + + + +
+
+
今日詞彙
+
2025年9月10日
+
+ +
+
單字介紹
+
這個單字的定義是...
+
+
+``` + +#### 表單設計規範 +```html + + +``` + +### 無障礙設計規範 + +#### ARIA屬性使用 +```html + + + + + + +
+ +
+ + + +``` + +## 🎨 CSS 開發規範 + +### SCSS 組織結構 + +#### 模組化SCSS架構 +```scss +// styles/main.scss + +// 1. 工具層 - 變數、函數、混合器 +@import 'base/variables'; +@import 'base/functions'; +@import 'base/mixins'; + +// 2. 基礎層 - 重置、字體、佈局 +@import 'base/reset'; +@import 'base/typography'; +@import 'base/layout'; + +// 3. 組件層 - UI組件樣式 +@import 'components/buttons'; +@import 'components/forms'; +@import 'components/cards'; +@import 'components/navigation'; +@import 'components/modals'; + +// 4. 頁面層 - 頁面特定樣式 +@import 'pages/home'; +@import 'pages/vocabulary'; +@import 'pages/auth'; + +// 5. 工具層 - 工具類 +@import 'utilities/spacing'; +@import 'utilities/colors'; +@import 'utilities/typography'; +@import 'utilities/responsive'; +``` + +### 命名規範 + +#### BEM方法論 +```scss +// ✅ 正確:BEM命名 +.vocabulary-card { + display: flex; + padding: $spacing-md; + + &__header { + display: flex; + justify-content: space-between; + margin-bottom: $spacing-sm; + + &--highlighted { + background-color: $color-primary-light; + } + } + + &__content { + flex: 1; + } + + &__word { + font-size: $font-size-lg; + font-weight: 600; + color: $color-primary; + + &--difficult { + color: $color-warning; + } + } + + &--disabled { + opacity: 0.5; + pointer-events: none; + } + + &--loading { + position: relative; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20px; + height: 20px; + border: 2px solid $color-primary; + border-top-color: transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + } + } +} + +// ❌ 錯誤:嵌套過深和命名不清楚 +.vocabulary-card { + .header { + .title { + .word { + .text { + color: blue; // 硬編碼顏色 + } + } + } + } +} +``` + +#### CSS自定義屬性(CSS Variables) +```scss +// styles/base/_variables.scss + +:root { + // 色彩系統 + --color-primary: #1976d2; + --color-primary-light: #42a5f5; + --color-primary-dark: #1565c0; + + --color-secondary: #26a69a; + --color-accent: #9c27b0; + + --color-success: #21ba45; + --color-warning: #f2c037; + --color-error: #c10015; + --color-info: #31ccec; + + // 灰階 + --color-grey-50: #fafafa; + --color-grey-100: #f5f5f5; + --color-grey-200: #eeeeee; + --color-grey-300: #e0e0e0; + --color-grey-400: #bdbdbd; + --color-grey-500: #9e9e9e; + + // 字體系統 + --font-family-primary: 'Inter', 'Noto Sans TC', sans-serif; + --font-family-secondary: 'Roboto', 'Microsoft JhengHei', sans-serif; + --font-family-mono: 'JetBrains Mono', 'Consolas', monospace; + + --font-size-xs: 0.75rem; // 12px + --font-size-sm: 0.875rem; // 14px + --font-size-base: 1rem; // 16px + --font-size-lg: 1.125rem; // 18px + --font-size-xl: 1.25rem; // 20px + --font-size-2xl: 1.5rem; // 24px + --font-size-3xl: 1.875rem; // 30px + + // 間距系統 + --spacing-xs: 0.25rem; // 4px + --spacing-sm: 0.5rem; // 8px + --spacing-md: 1rem; // 16px + --spacing-lg: 1.5rem; // 24px + --spacing-xl: 2rem; // 32px + --spacing-2xl: 3rem; // 48px + --spacing-3xl: 4rem; // 64px + + // 邊框圓角 + --border-radius-sm: 0.25rem; // 4px + --border-radius-md: 0.5rem; // 8px + --border-radius-lg: 0.75rem; // 12px + --border-radius-xl: 1rem; // 16px + --border-radius-full: 9999px; + + // 陰影系統 + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-base: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + + // 轉場動畫 + --transition-fast: 150ms ease; + --transition-base: 200ms ease; + --transition-slow: 300ms ease; + + // Z-index層級 + --z-dropdown: 1000; + --z-sticky: 1020; + --z-fixed: 1030; + --z-modal: 1040; + --z-popover: 1050; + --z-tooltip: 1060; + --z-toast: 1070; +} + +// 深色模式支援 +@media (prefers-color-scheme: dark) { + :root { + --color-primary: #42a5f5; + --color-primary-light: #90caf9; + --color-primary-dark: #1976d2; + + // 重新定義深色模式下的色彩 + } +} +``` + +### 響應式設計規範 + +#### Mobile-First 方法 +```scss +// styles/base/_mixins.scss + +// 響應式斷點 +$breakpoints: ( + 'xs': 0, + 'sm': 600px, + 'md': 1024px, + 'lg': 1440px, + 'xl': 1920px +); + +// 響應式混合器 +@mixin respond-above($breakpoint) { + $bp-value: map-get($breakpoints, $breakpoint); + @if $bp-value == 0 { + @content; + } @else { + @media (min-width: $bp-value) { + @content; + } + } +} + +@mixin respond-below($breakpoint) { + $bp-value: map-get($breakpoints, $breakpoint); + @media (max-width: $bp-value - 1px) { + @content; + } +} + +@mixin respond-between($lower, $upper) { + $lower-bp: map-get($breakpoints, $lower); + $upper-bp: map-get($breakpoints, $upper); + + @media (min-width: $lower-bp) and (max-width: $upper-bp - 1px) { + @content; + } +} + +// 使用範例 +.vocabulary-grid { + display: grid; + gap: var(--spacing-md); + + // Mobile First - 預設單欄 + grid-template-columns: 1fr; + + // 平板:雙欄 + @include respond-above('sm') { + grid-template-columns: repeat(2, 1fr); + } + + // 桌面:三欄 + @include respond-above('md') { + grid-template-columns: repeat(3, 1fr); + gap: var(--spacing-lg); + } + + // 大螢幕:四欄 + @include respond-above('lg') { + grid-template-columns: repeat(4, 1fr); + } +} +``` + +#### CSS Grid 和 Flexbox 最佳實踐 +```scss +// Flexbox工具類 +.flex { + display: flex; + + &-col { flex-direction: column; } + &-row { flex-direction: row; } + &-wrap { flex-wrap: wrap; } + &-nowrap { flex-wrap: nowrap; } + + &-justify-start { justify-content: flex-start; } + &-justify-center { justify-content: center; } + &-justify-end { justify-content: flex-end; } + &-justify-between { justify-content: space-between; } + &-justify-around { justify-content: space-around; } + + &-items-start { align-items: flex-start; } + &-items-center { align-items: center; } + &-items-end { align-items: flex-end; } + &-items-stretch { align-items: stretch; } +} + +// CSS Grid工具類 +.grid { + display: grid; + + &-cols-1 { grid-template-columns: repeat(1, 1fr); } + &-cols-2 { grid-template-columns: repeat(2, 1fr); } + &-cols-3 { grid-template-columns: repeat(3, 1fr); } + &-cols-4 { grid-template-columns: repeat(4, 1fr); } + + &-rows-1 { grid-template-rows: repeat(1, 1fr); } + &-rows-2 { grid-template-rows: repeat(2, 1fr); } + &-rows-3 { grid-template-rows: repeat(3, 1fr); } + + &-gap-sm { gap: var(--spacing-sm); } + &-gap-md { gap: var(--spacing-md); } + &-gap-lg { gap: var(--spacing-lg); } +} + +// 實際應用範例 +.page-layout { + min-height: 100vh; + display: grid; + grid-template-rows: auto 1fr auto; + grid-template-areas: + "header" + "main" + "footer"; + + .site-header { + grid-area: header; + } + + .main-content { + grid-area: main; + padding: var(--spacing-lg); + } + + .site-footer { + grid-area: footer; + } +} +``` + +## 📜 JavaScript 開發規範 + +### 代碼組織和模組化 + +#### ES6模組系統 +```javascript +// ✅ 正確:清晰的導入導出 +// utils/api.js +export class APIClient { + constructor(baseURL) { + this.baseURL = baseURL + } + + async get(endpoint, options = {}) { + // 實作 + } +} + +export const httpClient = new APIClient('/api') + +// services/vocabularyService.js +import { httpClient } from '../utils/api.js' + +export class VocabularyService { + async getWord(id) { + return await httpClient.get(`/vocabulary/${id}`) + } +} + +// 默認導出 +export default VocabularyService + +// ❌ 錯誤:混亂的導入導出 +export { APIClient, httpClient, VocabularyService, someOtherThing } +``` + +### 函數和類別設計規範 + +#### 函數設計原則 +```javascript +// ✅ 正確:單一職責、純函數 +function calculateWordDifficulty(word, userLevel, attempts) { + if (!word || userLevel < 1 || attempts < 0) { + throw new Error('Invalid parameters for difficulty calculation') + } + + const baseDifficulty = word.difficulty || 1 + const levelAdjustment = Math.max(0, baseDifficulty - userLevel) + const attemptBonus = Math.min(0.5, attempts * 0.1) + + return Math.max(0.1, levelAdjustment - attemptBonus) +} + +// 高階函數範例 +function createThrottledFunction(func, delay) { + let timeoutId = null + let lastArgs = null + + return function(...args) { + lastArgs = args + + if (timeoutId === null) { + timeoutId = setTimeout(() => { + func.apply(this, lastArgs) + timeoutId = null + }, delay) + } + } +} + +// 使用範例 +const throttledSaveProgress = createThrottledFunction(saveProgress, 1000) + +// ❌ 錯誤:職責不明確、副作用多 +function processWord(word) { + // 計算難度 + const difficulty = word.difficulty - user.level + attempts * 0.1 + + // 更新UI - 不應該在這個函數中 + document.getElementById('difficulty').textContent = difficulty + + // 發送API請求 - 不應該在這個函數中 + fetch('/api/update-difficulty', { + method: 'POST', + body: JSON.stringify({ wordId: word.id, difficulty }) + }) + + // 記錄日誌 - 副作用 + console.log('Processed word:', word.id) + + return difficulty +} +``` + +#### 類別設計規範 +```javascript +// ✅ 正確:清晰的類別設計 +class VocabularyCard { + #state = {} // 私有屬性 + #observers = new Set() // 私有屬性 + + constructor(element, options = {}) { + this.element = element + this.options = { ...this.constructor.defaults, ...options } + this.#state = this.#initializeState() + this.#bindEventListeners() + } + + // 靜態屬性 + static defaults = { + showPronunciation: true, + enableAudio: true, + flipOnClick: true + } + + // 公開方法 + setState(newState) { + const oldState = { ...this.#state } + this.#state = { ...this.#state, ...newState } + this.#notifyObservers(oldState, this.#state) + this.render() + } + + getState() { + return { ...this.#state } // 返回副本 + } + + subscribe(observer) { + this.#observers.add(observer) + return () => this.#observers.delete(observer) // 返回取消訂閱函數 + } + + destroy() { + this.#cleanup() + this.#observers.clear() + } + + // 私有方法 + #initializeState() { + return { + isFlipped: false, + isLoading: false, + word: null, + error: null + } + } + + #bindEventListeners() { + this.#handleClick = this.#handleClick.bind(this) + this.element.addEventListener('click', this.#handleClick) + } + + #handleClick(event) { + if (this.options.flipOnClick && !this.#state.isLoading) { + this.setState({ isFlipped: !this.#state.isFlipped }) + } + } + + #notifyObservers(oldState, newState) { + for (const observer of this.#observers) { + try { + observer(newState, oldState) + } catch (error) { + console.error('Observer error:', error) + } + } + } + + #cleanup() { + this.element.removeEventListener('click', this.#handleClick) + } + + render() { + // 渲染邏輯 + if (this.#state.isLoading) { + this.element.classList.add('loading') + } else { + this.element.classList.remove('loading') + } + + if (this.#state.isFlipped) { + this.element.classList.add('flipped') + } else { + this.element.classList.remove('flipped') + } + } +} +``` + +### 異步程式設計規範 + +#### Promise 和 async/await +```javascript +// ✅ 正確:清晰的異步處理 +class VocabularyAPI { + constructor() { + this.baseURL = '/api/vocabulary' + this.cache = new Map() + } + + async getWord(id, options = {}) { + const { useCache = true, timeout = 5000 } = options + + // 檢查快取 + if (useCache && this.cache.has(id)) { + return this.cache.get(id) + } + + try { + const response = await this.#fetchWithTimeout( + `${this.baseURL}/words/${id}`, + { timeout } + ) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const word = await response.json() + + // 驗證數據結構 + this.#validateWordData(word) + + // 更新快取 + if (useCache) { + this.cache.set(id, word) + } + + return word + } catch (error) { + console.error(`Failed to fetch word ${id}:`, error) + throw new APIError(`Failed to load word: ${error.message}`, { + cause: error, + wordId: id + }) + } + } + + async #fetchWithTimeout(url, { timeout, ...options } = {}) { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + + try { + const response = await fetch(url, { + ...options, + signal: controller.signal + }) + return response + } finally { + clearTimeout(timeoutId) + } + } + + #validateWordData(word) { + const required = ['id', 'word', 'definition_zh'] + const missing = required.filter(field => !(field in word)) + + if (missing.length > 0) { + throw new Error(`Missing required fields: ${missing.join(', ')}`) + } + } +} + +// 自定義錯誤類型 +class APIError extends Error { + constructor(message, options = {}) { + super(message, options) + this.name = 'APIError' + this.timestamp = Date.now() + } +} + +// ❌ 錯誤:錯誤處理不當 +async function getWord(id) { + const response = await fetch(`/api/words/${id}`) // 沒有錯誤處理 + const word = await response.json() // 沒有檢查回應狀態 + return word // 沒有數據驗證 +} +``` + +#### 事件系統設計 +```javascript +// 自定義事件發射器 +class EventEmitter { + constructor() { + this.events = new Map() + } + + on(eventName, listener) { + if (!this.events.has(eventName)) { + this.events.set(eventName, new Set()) + } + + this.events.get(eventName).add(listener) + + // 返回取消監聽函數 + return () => this.off(eventName, listener) + } + + once(eventName, listener) { + const onceListener = (...args) => { + this.off(eventName, onceListener) + listener.apply(this, args) + } + + return this.on(eventName, onceListener) + } + + off(eventName, listener) { + const listeners = this.events.get(eventName) + if (listeners) { + listeners.delete(listener) + if (listeners.size === 0) { + this.events.delete(eventName) + } + } + } + + emit(eventName, ...args) { + const listeners = this.events.get(eventName) + if (listeners) { + for (const listener of listeners) { + try { + listener.apply(this, args) + } catch (error) { + console.error(`Error in event listener for "${eventName}":`, error) + } + } + } + } + + removeAllListeners(eventName) { + if (eventName) { + this.events.delete(eventName) + } else { + this.events.clear() + } + } +} + +// 使用範例 +const learningEvents = new EventEmitter() + +// 監聽事件 +const unsubscribe = learningEvents.on('wordCompleted', (word, score) => { + console.log(`Completed word: ${word.text}, Score: ${score}`) + updateProgress(word, score) +}) + +// 觸發事件 +learningEvents.emit('wordCompleted', currentWord, 85) + +// 清理 +unsubscribe() +``` + +### DOM 操作最佳實踐 + +#### 安全的DOM操作 +```javascript +// ✅ 正確:安全的DOM操作 +class DOMHelper { + static createElement(tag, options = {}) { + const element = document.createElement(tag) + + if (options.className) { + element.className = options.className + } + + if (options.attributes) { + Object.entries(options.attributes).forEach(([key, value]) => { + element.setAttribute(key, value) + }) + } + + if (options.textContent) { + element.textContent = options.textContent // 安全地設置文字 + } + + if (options.innerHTML) { + // 清理HTML內容 + element.innerHTML = this.sanitizeHTML(options.innerHTML) + } + + return element + } + + static sanitizeHTML(html) { + // 簡單的HTML清理(實際專案中應使用專業庫如DOMPurify) + const temp = document.createElement('div') + temp.textContent = html + return temp.innerHTML + } + + static findElement(selector, context = document) { + const element = context.querySelector(selector) + if (!element) { + throw new Error(`Element not found: ${selector}`) + } + return element + } + + static findElements(selector, context = document) { + return Array.from(context.querySelectorAll(selector)) + } + + static onReady(callback) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback) + } else { + callback() + } + } +} + +// 批量DOM更新 +class DOMBatcher { + constructor() { + this.updates = [] + this.isScheduled = false + } + + schedule(updateFunction) { + this.updates.push(updateFunction) + + if (!this.isScheduled) { + this.isScheduled = true + requestAnimationFrame(() => { + this.flush() + }) + } + } + + flush() { + const updates = this.updates.splice(0) + updates.forEach(update => { + try { + update() + } catch (error) { + console.error('DOM update error:', error) + } + }) + this.isScheduled = false + } +} + +const domBatcher = new DOMBatcher() + +// 使用範例 +function updateWordCards(words) { + words.forEach(word => { + domBatcher.schedule(() => { + const cardElement = document.getElementById(`word-${word.id}`) + if (cardElement) { + cardElement.textContent = word.text + cardElement.className = `word-card difficulty-${word.difficulty}` + } + }) + }) +} + +// ❌ 錯誤:不安全的DOM操作 +function updateCard(word) { + const card = document.getElementById('word-card') + card.innerHTML = `

${word.text}

${word.definition}

` // XSS風險 + card.style.color = 'red' // 直接操作style +} +``` + +## 🔒 安全性最佳實踐 + +### 輸入驗證和清理 +```javascript +// 輸入驗證工具類 +class InputValidator { + static patterns = { + email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, + username: /^[a-zA-Z0-9_]{3,20}$/, + phone: /^\+?[\d\s\-\(\)]+$/ + } + + static validate(input, type, options = {}) { + if (typeof input !== 'string') { + return { valid: false, error: 'Input must be a string' } + } + + // 長度檢查 + const { minLength = 0, maxLength = 1000 } = options + if (input.length < minLength || input.length > maxLength) { + return { + valid: false, + error: `Length must be between ${minLength} and ${maxLength}` + } + } + + // 模式檢查 + if (this.patterns[type] && !this.patterns[type].test(input)) { + return { valid: false, error: `Invalid ${type} format` } + } + + return { valid: true } + } + + static sanitizeString(input) { + return input + .replace(/[<>'"&]/g, (match) => { + const escapeMap = { + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '&': '&' + } + return escapeMap[match] + }) + .trim() + } + + static normalizeWhitespace(input) { + return input.replace(/\s+/g, ' ').trim() + } +} + +// 表單驗證範例 +class FormValidator { + constructor(form) { + this.form = form + this.errors = new Map() + this.rules = new Map() + } + + addRule(fieldName, validator) { + if (!this.rules.has(fieldName)) { + this.rules.set(fieldName, []) + } + this.rules.get(fieldName).push(validator) + return this + } + + validate() { + this.errors.clear() + + for (const [fieldName, validators] of this.rules) { + const field = this.form.querySelector(`[name="${fieldName}"]`) + if (!field) continue + + const value = field.value + + for (const validator of validators) { + const result = validator(value) + if (!result.valid) { + this.errors.set(fieldName, result.error) + break // 只顯示第一個錯誤 + } + } + } + + this.displayErrors() + return this.errors.size === 0 + } + + displayErrors() { + // 清除舊錯誤 + this.form.querySelectorAll('.form-error').forEach(el => { + el.textContent = '' + }) + + // 顯示新錯誤 + for (const [fieldName, error] of this.errors) { + const errorElement = this.form.querySelector(`#${fieldName}-error`) + if (errorElement) { + errorElement.textContent = error + } + } + } +} +``` + +### XSS 和 CSRF 防護 +```javascript +// CSRF Token管理 +class CSRFManager { + constructor() { + this.token = null + this.refreshPromise = null + } + + async getToken() { + if (!this.token || this.isTokenExpired()) { + if (!this.refreshPromise) { + this.refreshPromise = this.refreshToken() + } + await this.refreshPromise + this.refreshPromise = null + } + return this.token + } + + async refreshToken() { + try { + const response = await fetch('/api/csrf-token', { + method: 'GET', + credentials: 'same-origin' + }) + + if (!response.ok) { + throw new Error('Failed to get CSRF token') + } + + const data = await response.json() + this.token = data.token + this.tokenExpiry = Date.now() + (data.expiresIn * 1000) + } catch (error) { + console.error('CSRF token refresh failed:', error) + throw error + } + } + + isTokenExpired() { + return !this.tokenExpiry || Date.now() >= this.tokenExpiry + } +} + +// Content Security Policy 協助函數 +class CSPHelper { + static createNonce() { + const array = new Uint8Array(16) + crypto.getRandomValues(array) + return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('') + } + + static addNonceToScript(scriptContent) { + const nonce = this.createNonce() + const meta = document.createElement('meta') + meta.httpEquiv = 'Content-Security-Policy' + meta.content = `script-src 'self' 'nonce-${nonce}'` + document.head.appendChild(meta) + + const script = document.createElement('script') + script.nonce = nonce + script.textContent = scriptContent + return script + } +} +``` + +## 🧪 測試規範 + +### 單元測試規範 +```javascript +// tests/utils/inputValidator.test.js +import { describe, it, expect, beforeEach } from 'vitest' +import { InputValidator } from '../../src/utils/inputValidator.js' + +describe('InputValidator', () => { + describe('email validation', () => { + it('should accept valid email addresses', () => { + const validEmails = [ + 'test@example.com', + 'user.name@domain.co.uk', + 'user+tag@example.org' + ] + + validEmails.forEach(email => { + const result = InputValidator.validate(email, 'email') + expect(result.valid).toBe(true) + }) + }) + + it('should reject invalid email addresses', () => { + const invalidEmails = [ + 'invalid-email', + '@example.com', + 'user@', + 'user@.com' + ] + + invalidEmails.forEach(email => { + const result = InputValidator.validate(email, 'email') + expect(result.valid).toBe(false) + expect(result.error).toBeDefined() + }) + }) + + it('should enforce length limits', () => { + const shortEmail = 'a@b.c' + const longEmail = 'a'.repeat(100) + '@example.com' + + const shortResult = InputValidator.validate(shortEmail, 'email', { minLength: 10 }) + expect(shortResult.valid).toBe(false) + + const longResult = InputValidator.validate(longEmail, 'email', { maxLength: 50 }) + expect(longResult.valid).toBe(false) + }) + }) + + describe('sanitizeString', () => { + it('should escape HTML special characters', () => { + const input = '' + const expected = '<script>alert("xss")</script>' + const result = InputValidator.sanitizeString(input) + + expect(result).toBe(expected) + }) + + it('should trim whitespace', () => { + const input = ' hello world ' + const expected = 'hello world' + const result = InputValidator.sanitizeString(input) + + expect(result).toBe(expected) + }) + }) +}) +``` + +### 整合測試範例 +```javascript +// tests/integration/vocabularyCard.test.js +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { VocabularyCard } from '../../src/components/VocabularyCard.js' + +describe('VocabularyCard Integration Tests', () => { + let container + let mockAPI + + beforeEach(() => { + // 設置DOM環境 + container = document.createElement('div') + container.innerHTML = ` +
+
+
+
+ ` + document.body.appendChild(container) + + // 模擬API + mockAPI = { + getWordIntroduction: vi.fn() + } + + // 替換全局API + global.vocabularyAPI = mockAPI + }) + + afterEach(() => { + // 清理DOM + document.body.removeChild(container) + vi.restoreAllMocks() + }) + + it('should load and display word data on initialization', async () => { + const mockWord = { + id: 'test-word', + word: 'hello', + pronunciation: '/həˈloʊ/', + definition_zh: '你好', + examples: [ + { sentence: 'Hello, world!' } + ] + } + + mockAPI.getWordIntroduction.mockResolvedValue(mockWord) + + const cardElement = container.querySelector('#vocabulary-card') + const vocabularyCard = new VocabularyCard(cardElement) + + // 等待異步載入 + await new Promise(resolve => setTimeout(resolve, 100)) + + expect(mockAPI.getWordIntroduction).toHaveBeenCalledWith('test-word') + expect(cardElement.querySelector('.word-text')).toHaveTextContent('hello') + expect(cardElement.querySelector('.pronunciation')).toHaveTextContent('/həˈloʊ/') + }) + + it('should handle API errors gracefully', async () => { + mockAPI.getWordIntroduction.mockRejectedValue(new Error('Network error')) + + const cardElement = container.querySelector('#vocabulary-card') + const vocabularyCard = new VocabularyCard(cardElement) + + await new Promise(resolve => setTimeout(resolve, 100)) + + expect(cardElement.querySelector('.error-message')).toBeInTheDocument() + }) + + it('should flip card when clicked', async () => { + const mockWord = { id: 'test', word: 'test', definition_zh: 'test' } + mockAPI.getWordIntroduction.mockResolvedValue(mockWord) + + const cardElement = container.querySelector('#vocabulary-card') + const vocabularyCard = new VocabularyCard(cardElement) + + await new Promise(resolve => setTimeout(resolve, 100)) + + // 模擬點擊 + const flipButton = cardElement.querySelector('.flip-btn') + flipButton.click() + + expect(cardElement).toHaveClass('flipped') + }) +}) +``` + +## 📋 代碼審查檢查清單 + +### HTML 審查要點 +- [ ] 使用語義化HTML5元素 +- [ ] 所有圖片都有alt屬性 +- [ ] 表單元素都有對應的label +- [ ] 適當使用ARIA屬性 +- [ ] 頁面有正確的文檔結構(DOCTYPE、lang等) +- [ ] 無障礙性考量(鍵盤導航、螢幕閱讀器) + +### CSS 審查要點 +- [ ] 使用BEM或一致的命名規範 +- [ ] 無硬編碼的magic numbers +- [ ] 響應式設計實現正確 +- [ ] 避免過度具體的選擇器 +- [ ] CSS變數使用合理 +- [ ] 無未使用的CSS規則 + +### JavaScript 審查要點 +- [ ] 函數職責單一,名稱清楚 +- [ ] 適當的錯誤處理 +- [ ] 無記憶體洩漏風險 +- [ ] 適當使用async/await +- [ ] 輸入驗證和清理 +- [ ] 事件監聽器正確清理 + +### 效能審查要點 +- [ ] 避免不必要的DOM查詢 +- [ ] 適當使用事件委託 +- [ ] 圖片和資源優化 +- [ ] 批量DOM更新 +- [ ] 避免阻塞主線程的操作 + +### 安全性審查要點 +- [ ] 用戶輸入已清理 +- [ ] 防XSS措施就位 +- [ ] CSRF保護實施 +- [ ] 適當的Content Security Policy +- [ ] 敏感資料不在客戶端暴露 + +--- + +**文檔狀態**: 🟢 完整原生前端開發規範 +**最後更新**: 2025-09-10 +**適用技術**: HTML5 + CSS3 + Modern JavaScript +**維護團隊**: 前端開發團隊 \ No newline at end of file diff --git a/docs/04_technical/03_frontend/native-frontend-architecture.md b/docs/04_technical/03_frontend/native-frontend-architecture.md new file mode 100644 index 0000000..c692441 --- /dev/null +++ b/docs/04_technical/03_frontend/native-frontend-architecture.md @@ -0,0 +1,1310 @@ +# Drama Ling 原生前端技術架構規劃 + +## 📋 架構概述 + +**專案名稱**: Drama Ling 語言學習應用 (Web端) +**建立日期**: 2025-09-10 +**技術主軸**: 原生Web技術 (HTML5 + CSS3 + Modern JavaScript) +**對應後端**: .NET Core API +**部署目標**: 響應式Web應用程式 + +### 核心設計目標 +- 🎯 **像素級精確**: 完全按照HTML原型設計實現,無框架抽象干擾 +- 📱 **響應式設計**: 桌面優先,兼容平板和手機 +- 🚀 **輕量高效**: 無框架負擔,快速載入和流暢互動 +- 🔒 **企業級安全**: 資料保護、安全認證 +- 💎 **Claude Code友好**: 適合AI輔助開發,易於理解和修改 + +## 🛠️ 技術堆疊 + +### 核心技術 +```javascript +{ + "markup": "HTML5", // 語義化標記,無障礙支援 + "styling": "CSS3 + SCSS", // 現代CSS特性 + 預處理器 + "scripting": "ES2022+", // 現代JavaScript特性 + "bundler": "Vite 5.x", // 快速開發伺服器和打包工具 + "typescript": "5.x" // 可選的強型別支援 +} +``` + +### 開發工具鏈 +```javascript +{ + "dev_tools": { + "bundler": "Vite 5.x", // 快速HMR和打包 + "css_preprocessor": "Sass/SCSS", // CSS預處理器 + "linter": "ESLint 9.x", // JavaScript代碼檢查 + "formatter": "Prettier 3.x", // 代碼格式化 + "css_linter": "Stylelint 16.x", // CSS代碼檢查 + "testing": "Vitest 1.6.x", // 快速單元測試 + "e2e_testing": "Playwright" // 端到端測試 + }, + "build_optimization": { + "minification": "Terser", // JS壓縮 + "css_optimization": "cssnano", // CSS優化 + "image_optimization": "vite-plugin-imagemin", + "pwa_support": "@vite/plugin-pwa" + } +} +``` + +## 🏗️ 專案結構設計 + +### 整體目錄結構 +``` +web-frontend/ +├── public/ +│ ├── icons/ # PWA圖標和favicon +│ ├── assets/ # 靜態資源 +│ │ ├── audio/ # 音頻檔案 +│ │ ├── images/ # 圖片資源 +│ │ └── fonts/ # 字體檔案 +│ └── manifest.json # PWA配置 +├── src/ +│ ├── pages/ # 頁面HTML檔案 +│ │ ├── home/ # 首頁相關 +│ │ ├── auth/ # 認證相關頁面 +│ │ ├── vocabulary/ # 詞彙學習頁面 +│ │ ├── dialogue/ # 情境對話頁面 +│ │ └── profile/ # 用戶相關頁面 +│ ├── components/ # 可重用組件 +│ │ ├── ui/ # 基礎UI組件 +│ │ ├── layout/ # 佈局組件 +│ │ └── business/ # 業務邏輯組件 +│ ├── styles/ # 樣式檔案 +│ │ ├── base/ # 基礎樣式 +│ │ │ ├── reset.scss # CSS重置 +│ │ │ ├── typography.scss # 字體樣式 +│ │ │ └── variables.scss # SCSS變數 +│ │ ├── components/ # 組件樣式 +│ │ ├── pages/ # 頁面樣式 +│ │ └── utilities/ # 工具類樣式 +│ ├── scripts/ # JavaScript模組 +│ │ ├── core/ # 核心功能 +│ │ │ ├── router.js # 路由管理 +│ │ │ ├── state.js # 狀態管理 +│ │ │ ├── api.js # API客戶端 +│ │ │ └── events.js # 事件系統 +│ │ ├── modules/ # 功能模組 +│ │ │ ├── auth/ # 認證模組 +│ │ │ ├── vocabulary/ # 詞彙學習 +│ │ │ ├── audio/ # 音頻處理 +│ │ │ └── analytics/ # 數據分析 +│ │ ├── utils/ # 工具函數 +│ │ └── services/ # 業務服務 +│ └── main.js # 應用入口 +├── tests/ # 測試檔案 +├── docs/ # 專案文檔 +└── vite.config.js # Vite配置 +``` + +### 模組化設計 +```javascript +// modules/vocabulary/index.js +export class VocabularyModule { + constructor() { + this.state = new VocabularyState() + this.api = new VocabularyAPI() + this.ui = new VocabularyUI() + } + + init() { + this.setupEventListeners() + this.loadInitialData() + } + + setupEventListeners() { + // 事件監聽設置 + } +} + +// 每個模組包含完整的功能實現 +// modules/vocabulary/ +├── components/ # 詞彙相關組件 +├── services/ # API服務 +├── state/ # 狀態管理 +├── utils/ # 工具函數 +└── index.js # 模組入口 +``` + +## 🔄 狀態管理架構 + +### 簡單狀態管理模式 +```javascript +// core/state.js +class ApplicationState { + constructor() { + this.stores = { + auth: new AuthState(), + vocabulary: new VocabularyState(), + ui: new UIState(), + audio: new AudioState() + } + this.subscribers = new Map() + } + + // 訂閱狀態變化 + subscribe(storeName, callback) { + if (!this.subscribers.has(storeName)) { + this.subscribers.set(storeName, []) + } + this.subscribers.get(storeName).push(callback) + } + + // 通知訂閱者 + notify(storeName, data) { + const callbacks = this.subscribers.get(storeName) || [] + callbacks.forEach(callback => callback(data)) + } + + // 更新狀態 + updateStore(storeName, updater) { + const store = this.stores[storeName] + if (store) { + store.update(updater) + this.notify(storeName, store.getState()) + } + } +} + +// 認證狀態管理 +class AuthState { + constructor() { + this.state = { + user: null, + token: localStorage.getItem('auth_token'), + isAuthenticated: false + } + this.checkAuthStatus() + } + + update(updater) { + this.state = { ...this.state, ...updater(this.state) } + this.persistState() + } + + persistState() { + if (this.state.token) { + localStorage.setItem('auth_token', this.state.token) + } else { + localStorage.removeItem('auth_token') + } + } + + getState() { + return { ...this.state } + } +} + +// 全域狀態實例 +export const appState = new ApplicationState() +``` + +### 本地存儲策略 +```javascript +// utils/storage.js +class StorageManager { + constructor() { + this.prefix = 'dramaling_' + } + + // localStorage封裝 + setLocal(key, value) { + try { + localStorage.setItem( + `${this.prefix}${key}`, + JSON.stringify(value) + ) + } catch (error) { + console.error('LocalStorage write failed:', error) + } + } + + getLocal(key, defaultValue = null) { + try { + const item = localStorage.getItem(`${this.prefix}${key}`) + return item ? JSON.parse(item) : defaultValue + } catch (error) { + console.error('LocalStorage read failed:', error) + return defaultValue + } + } + + // sessionStorage封裝 + setSession(key, value) { + try { + sessionStorage.setItem( + `${this.prefix}${key}`, + JSON.stringify(value) + ) + } catch (error) { + console.error('SessionStorage write failed:', error) + } + } + + getSession(key, defaultValue = null) { + try { + const item = sessionStorage.getItem(`${this.prefix}${key}`) + return item ? JSON.parse(item) : defaultValue + } catch (error) { + console.error('SessionStorage read failed:', error) + return defaultValue + } + } +} + +export const storage = new StorageManager() +``` + +## 🎨 CSS架構設計 + +### SCSS組織結構 +```scss +// styles/main.scss +// 1. 工具和變數 +@import 'base/variables'; +@import 'base/functions'; +@import 'base/mixins'; + +// 2. 基礎樣式 +@import 'base/reset'; +@import 'base/typography'; +@import 'base/layout'; + +// 3. 組件樣式 +@import 'components/buttons'; +@import 'components/forms'; +@import 'components/cards'; +@import 'components/modals'; + +// 4. 頁面樣式 +@import 'pages/home'; +@import 'pages/vocabulary'; +@import 'pages/auth'; + +// 5. 工具類 +@import 'utilities/spacing'; +@import 'utilities/colors'; +@import 'utilities/responsive'; +``` + +### 變數系統 +```scss +// styles/base/_variables.scss + +// 色彩系統 +$color-primary: #1976d2; +$color-primary-light: lighten($color-primary, 20%); +$color-primary-dark: darken($color-primary, 20%); + +$color-secondary: #26a69a; +$color-accent: #9c27b0; + +$color-success: #21ba45; +$color-warning: #f2c037; +$color-error: #c10015; +$color-info: #31ccec; + +// 字體系統 +$font-family-primary: 'Inter', 'Noto Sans TC', sans-serif; +$font-family-secondary: 'Roboto', 'Microsoft JhengHei', sans-serif; + +$font-size-xs: 0.75rem; // 12px +$font-size-sm: 0.875rem; // 14px +$font-size-base: 1rem; // 16px +$font-size-lg: 1.125rem; // 18px +$font-size-xl: 1.25rem; // 20px + +// 間距系統 +$spacing-xs: 0.25rem; // 4px +$spacing-sm: 0.5rem; // 8px +$spacing-md: 1rem; // 16px +$spacing-lg: 1.5rem; // 24px +$spacing-xl: 2rem; // 32px + +// 響應式斷點 +$breakpoint-xs: 0; +$breakpoint-sm: 600px; +$breakpoint-md: 1024px; +$breakpoint-lg: 1440px; +$breakpoint-xl: 1920px; +``` + +### 響應式設計混合器 +```scss +// styles/base/_mixins.scss + +// 響應式斷點混合器 +@mixin respond-to($breakpoint) { + @if $breakpoint == xs { + @media (max-width: #{$breakpoint-sm - 1px}) { @content; } + } + @if $breakpoint == sm { + @media (min-width: #{$breakpoint-sm}) and (max-width: #{$breakpoint-md - 1px}) { @content; } + } + @if $breakpoint == md { + @media (min-width: #{$breakpoint-md}) and (max-width: #{$breakpoint-lg - 1px}) { @content; } + } + @if $breakpoint == lg { + @media (min-width: #{$breakpoint-lg}) { @content; } + } +} + +// CSS Grid佈局工具 +@mixin grid-container($columns: 1, $gap: $spacing-md) { + display: grid; + grid-template-columns: repeat($columns, 1fr); + gap: $gap; +} + +// 按鈕樣式基礎 +@mixin button-base { + display: inline-flex; + align-items: center; + justify-content: center; + padding: $spacing-sm $spacing-md; + border: none; + border-radius: 4px; + font-family: $font-family-primary; + font-size: $font-size-base; + cursor: pointer; + transition: all 0.2s ease; + text-decoration: none; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +// 卡片組件樣式 +@mixin card-shadow($level: 1) { + @if $level == 1 { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + @if $level == 2 { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08); + } + @if $level == 3 { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1); + } +} +``` + +## 🚦 路由和導航 + +### 簡單路由器實現 +```javascript +// core/router.js +class SimpleRouter { + constructor() { + this.routes = new Map() + this.currentRoute = null + this.init() + } + + init() { + window.addEventListener('popstate', () => { + this.handleRoute() + }) + + // 處理初始路由 + this.handleRoute() + } + + // 註冊路由 + addRoute(path, handler, options = {}) { + this.routes.set(path, { + handler, + requiresAuth: options.requiresAuth || false, + title: options.title || 'Drama Ling' + }) + } + + // 導航到指定路由 + navigate(path, state = {}) { + history.pushState(state, '', path) + this.handleRoute() + } + + // 處理路由變化 + handleRoute() { + const path = window.location.pathname + const route = this.findRoute(path) + + if (route) { + // 檢查認證要求 + if (route.requiresAuth && !this.checkAuth()) { + this.navigate('/login') + return + } + + // 設置頁面標題 + document.title = route.title + + // 執行路由處理器 + route.handler(path) + this.currentRoute = path + } else { + this.handle404() + } + } + + // 查找匹配的路由 + findRoute(path) { + // 精確匹配 + if (this.routes.has(path)) { + return this.routes.get(path) + } + + // 動態路由匹配 (例如: /vocabulary/:id) + for (const [pattern, route] of this.routes) { + const regex = this.pathToRegex(pattern) + if (regex.test(path)) { + return route + } + } + + return null + } + + // 路徑轉正則表達式 + pathToRegex(path) { + const regexPath = path + .replace(/:\w+/g, '([^/]+)') // :id -> ([^/]+) + .replace(/\*/g, '.*') // * -> .* + return new RegExp(`^${regexPath}$`) + } + + // 檢查用戶認證狀態 + checkAuth() { + return appState.stores.auth.getState().isAuthenticated + } + + // 處理404錯誤 + handle404() { + document.title = 'Page Not Found - Drama Ling' + // 顯示404頁面 + } +} + +// 路由配置 +export const router = new SimpleRouter() + +// 註冊路由 +router.addRoute('/', () => import('../pages/home/index.js'), { + title: 'Drama Ling - AI語言學習' +}) + +router.addRoute('/login', () => import('../pages/auth/login.js'), { + title: '登入 - Drama Ling' +}) + +router.addRoute('/vocabulary', () => import('../pages/vocabulary/index.js'), { + requiresAuth: true, + title: '詞彙學習 - Drama Ling' +}) + +router.addRoute('/vocabulary/:id', (path) => { + const id = path.split('/').pop() + import('../pages/vocabulary/detail.js').then(module => { + module.default.init(id) + }) +}, { + requiresAuth: true, + title: '詞彙詳情 - Drama Ling' +}) +``` + +### 導航組件 +```javascript +// components/layout/Navigation.js +export class Navigation { + constructor() { + this.element = null + this.init() + } + + init() { + this.render() + this.setupEventListeners() + } + + render() { + this.element = document.createElement('nav') + this.element.className = 'main-navigation' + this.element.innerHTML = ` + + ` + } + + setupEventListeners() { + // 導航點擊處理 + this.element.addEventListener('click', (e) => { + const link = e.target.closest('[data-navigate]') + if (link) { + e.preventDefault() + const path = link.getAttribute('href') + router.navigate(path) + } + }) + + // 登出處理 + this.element.querySelector('#logout-btn').addEventListener('click', () => { + appState.updateStore('auth', (state) => ({ + ...state, + user: null, + token: null, + isAuthenticated: false + })) + router.navigate('/login') + }) + } + + mount(container) { + container.appendChild(this.element) + } +} +``` + +## 🔌 API服務層架構 + +### HTTP客戶端 +```javascript +// services/httpClient.js +class HttpClient { + constructor() { + this.baseURL = import.meta.env.VITE_API_BASE_URL || '/api' + this.timeout = 10000 + } + + // 通用請求方法 + async request(url, options = {}) { + const config = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...this.getAuthHeaders(), + ...options.headers + }, + timeout: this.timeout, + ...options + } + + try { + const response = await fetch(`${this.baseURL}${url}`, config) + return await this.handleResponse(response) + } catch (error) { + throw this.handleError(error) + } + } + + // 獲取認證標頭 + getAuthHeaders() { + const token = appState.stores.auth.getState().token + return token ? { Authorization: `Bearer ${token}` } : {} + } + + // 處理回應 + async handleResponse(response) { + if (!response.ok) { + if (response.status === 401) { + // 處理認證過期 + appState.updateStore('auth', (state) => ({ + ...state, + user: null, + token: null, + isAuthenticated: false + })) + router.navigate('/login') + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const contentType = response.headers.get('content-type') + if (contentType && contentType.includes('application/json')) { + return await response.json() + } + return await response.text() + } + + // 錯誤處理 + handleError(error) { + console.error('API Error:', error) + return error + } + + // 便捷方法 + get(url, params = {}) { + const queryString = new URLSearchParams(params).toString() + const fullUrl = queryString ? `${url}?${queryString}` : url + return this.request(fullUrl) + } + + post(url, data = {}) { + return this.request(url, { + method: 'POST', + body: JSON.stringify(data) + }) + } + + put(url, data = {}) { + return this.request(url, { + method: 'PUT', + body: JSON.stringify(data) + }) + } + + delete(url) { + return this.request(url, { + method: 'DELETE' + }) + } +} + +export const httpClient = new HttpClient() +``` + +### API服務類別 +```javascript +// services/vocabularyApi.js +class VocabularyAPI { + constructor() { + this.basePath = '/vocabulary' + } + + // 獲取詞彙介紹 + async getWordIntroduction(wordId) { + return await httpClient.get(`${this.basePath}/words/${wordId}`) + } + + // 提交練習結果 + async submitPracticeResult(result) { + return await httpClient.post(`${this.basePath}/practice`, result) + } + + // 獲取複習計劃 + async getReviewSchedule() { + return await httpClient.get(`${this.basePath}/review/schedule`) + } + + // 獲取分析數據 + async getAnalytics(timeRange) { + return await httpClient.get(`${this.basePath}/analytics`, { timeRange }) + } +} + +export const vocabularyAPI = new VocabularyAPI() +``` + +## 🎵 音頻處理整合 + +### Web Audio API封裝 +```javascript +// modules/audio/audioManager.js +class AudioManager { + constructor() { + this.audioContext = null + this.currentSource = null + this.isPlaying = false + } + + // 初始化音頻上下文 + async initializeAudioContext() { + if (!this.audioContext) { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)() + + // 處理瀏覽器自動播放政策 + if (this.audioContext.state === 'suspended') { + await this.audioContext.resume() + } + } + } + + // 播放音頻 + async playAudio(url, playbackRate = 1.0) { + try { + await this.initializeAudioContext() + + // 停止當前播放 + this.stopCurrentAudio() + + // 載入音頻 + const response = await fetch(url) + const arrayBuffer = await response.arrayBuffer() + const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer) + + // 創建音頻源 + this.currentSource = this.audioContext.createBufferSource() + this.currentSource.buffer = audioBuffer + this.currentSource.playbackRate.value = playbackRate + this.currentSource.connect(this.audioContext.destination) + + // 播放結束處理 + this.currentSource.onended = () => { + this.isPlaying = false + this.currentSource = null + } + + // 開始播放 + this.currentSource.start() + this.isPlaying = true + + return this.currentSource + } catch (error) { + console.error('Audio playback failed:', error) + this.isPlaying = false + throw error + } + } + + // 停止當前播放 + stopCurrentAudio() { + if (this.currentSource && this.isPlaying) { + this.currentSource.stop() + this.currentSource = null + this.isPlaying = false + } + } + + // 錄製音頻 + async startRecording() { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) + const mediaRecorder = new MediaRecorder(stream) + + const audioChunks = [] + mediaRecorder.ondataavailable = (event) => { + audioChunks.push(event.data) + } + + return new Promise((resolve, reject) => { + mediaRecorder.onstop = () => { + const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }) + resolve(audioBlob) + } + + mediaRecorder.onerror = (event) => { + reject(event.error) + } + + mediaRecorder.start() + + // 返回停止函數 + setTimeout(() => { + mediaRecorder.stop() + stream.getTracks().forEach(track => track.stop()) + }, 5000) // 5秒自動停止 + }) + } catch (error) { + console.error('Recording failed:', error) + throw error + } + } +} + +export const audioManager = new AudioManager() +``` + +## 🧩 組件系統 + +### Web Components基礎 +```javascript +// components/ui/BaseComponent.js +class BaseComponent extends HTMLElement { + constructor() { + super() + this.state = {} + this.listeners = new Map() + } + + // 組件生命週期 + connectedCallback() { + this.render() + this.setupEventListeners() + this.onMounted() + } + + disconnectedCallback() { + this.cleanup() + this.onUnmounted() + } + + // 狀態管理 + setState(newState) { + this.state = { ...this.state, ...newState } + this.render() + } + + getState() { + return { ...this.state } + } + + // 事件處理 + addEventListener(event, handler) { + if (!this.listeners.has(event)) { + this.listeners.set(event, []) + } + this.listeners.get(event).push(handler) + super.addEventListener(event, handler) + } + + // 清理資源 + cleanup() { + for (const [event, handlers] of this.listeners) { + handlers.forEach(handler => { + super.removeEventListener(event, handler) + }) + } + this.listeners.clear() + } + + // 子類需要實現的方法 + render() { + throw new Error('render method must be implemented') + } + + onMounted() { + // 組件掛載後的邏輯 + } + + onUnmounted() { + // 組件卸載前的清理 + } + + setupEventListeners() { + // 事件監聽設置 + } +} + +export { BaseComponent } +``` + +### 詞彙卡片組件範例 +```javascript +// components/business/VocabularyCard.js +import { BaseComponent } from '../ui/BaseComponent.js' + +class VocabularyCard extends BaseComponent { + constructor() { + super() + this.state = { + word: null, + isFlipped: false, + isLoading: false + } + } + + static get observedAttributes() { + return ['word-id', 'show-pronunciation', 'interactive'] + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'word-id' && newValue !== oldValue) { + this.loadWord(newValue) + } + } + + async loadWord(wordId) { + this.setState({ isLoading: true }) + try { + const word = await vocabularyAPI.getWordIntroduction(wordId) + this.setState({ word, isLoading: false }) + } catch (error) { + console.error('Failed to load word:', error) + this.setState({ isLoading: false }) + } + } + + render() { + if (this.state.isLoading) { + this.innerHTML = ` +
+
+
+ ` + return + } + + if (!this.state.word) { + this.innerHTML = ` +
+

無法載入詞彙資料

+
+ ` + return + } + + const { word } = this.state + const showPronunciation = this.getAttribute('show-pronunciation') !== 'false' + const interactive = this.getAttribute('interactive') !== 'false' + + this.innerHTML = ` +
+
+
+

${word.word}

+ ${showPronunciation ? `${word.pronunciation}` : ''} +
+ ${interactive ? ` + + + ` : ''} +
+ +
+
+

定義

+

${word.definition_zh}

+
+
+

例句

+ ${word.examples.map(example => ` +

${example.sentence}

+ `).join('')} +
+ ${interactive ? ` + + ` : ''} +
+
+ ` + } + + setupEventListeners() { + this.addEventListener('click', (e) => { + if (e.target.matches('.flip-btn')) { + this.setState({ isFlipped: !this.state.isFlipped }) + } + + if (e.target.matches('.audio-btn')) { + const audioUrl = e.target.getAttribute('data-audio-url') + audioManager.playAudio(audioUrl) + } + }) + } +} + +// 註冊自定義元素 +customElements.define('vocabulary-card', VocabularyCard) + +export { VocabularyCard } +``` + +## 🚀 效能優化策略 + +### 載入優化 +```javascript +// utils/lazyLoader.js +class LazyLoader { + constructor() { + this.observer = new IntersectionObserver( + this.handleIntersection.bind(this), + { threshold: 0.1 } + ) + } + + // 延遲載入圖片 + lazyLoadImages() { + const images = document.querySelectorAll('img[data-src]') + images.forEach(img => this.observer.observe(img)) + } + + // 延遲載入組件 + lazyLoadComponent(element, importFunction) { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + importFunction().then(module => { + module.default.init(element) + }) + observer.disconnect() + } + }) + }, + { threshold: 0.1 } + ) + observer.observe(element) + } + + handleIntersection(entries) { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target + img.src = img.getAttribute('data-src') + img.removeAttribute('data-src') + this.observer.unobserve(img) + } + }) + } +} + +export const lazyLoader = new LazyLoader() +``` + +### 快取策略 +```javascript +// utils/cache.js +class CacheManager { + constructor() { + this.memoryCache = new Map() + this.maxAge = 5 * 60 * 1000 // 5分鐘 + } + + // 記憶體快取 + setMemoryCache(key, data, maxAge = this.maxAge) { + this.memoryCache.set(key, { + data, + timestamp: Date.now(), + maxAge + }) + } + + getMemoryCache(key) { + const cached = this.memoryCache.get(key) + if (!cached) return null + + if (Date.now() - cached.timestamp > cached.maxAge) { + this.memoryCache.delete(key) + return null + } + + return cached.data + } + + // localStorage快取 + setLocalCache(key, data, maxAge = 24 * 60 * 60 * 1000) { + const cacheData = { + data, + timestamp: Date.now(), + maxAge + } + storage.setLocal(`cache_${key}`, cacheData) + } + + getLocalCache(key) { + const cached = storage.getLocal(`cache_${key}`) + if (!cached) return null + + if (Date.now() - cached.timestamp > cached.maxAge) { + storage.removeLocal(`cache_${key}`) + return null + } + + return cached.data + } +} + +export const cacheManager = new CacheManager() +``` + +## 🔒 安全性實作 + +### XSS防護 +```javascript +// utils/security.js +class SecurityUtils { + // HTML清理 + sanitizeHTML(html) { + const temp = document.createElement('div') + temp.textContent = html + return temp.innerHTML + } + + // 輸入驗證 + validateInput(input, type = 'text') { + const patterns = { + email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + phone: /^\+?[\d\s\-\(\)]+$/, + username: /^[a-zA-Z0-9_]{3,20}$/, + password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/ + } + + if (patterns[type]) { + return patterns[type].test(input) + } + + // 基本長度檢查 + return input.length > 0 && input.length < 1000 + } + + // CSRF Token處理 + async getCSRFToken() { + try { + const response = await httpClient.get('/csrf-token') + return response.token + } catch (error) { + console.error('Failed to get CSRF token:', error) + return null + } + } +} + +export const security = new SecurityUtils() +``` + +## 🧪 測試策略 + +### Vitest配置 +```javascript +// vite.config.js +import { defineConfig } from 'vite' + +export default defineConfig({ + test: { + environment: 'happy-dom', + globals: true, + coverage: { + provider: 'v8', + reporter: ['text', 'json-summary', 'html'], + threshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + } + } + } +}) +``` + +### 組件測試範例 +```javascript +// tests/components/VocabularyCard.test.js +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { VocabularyCard } from '../../src/components/business/VocabularyCard.js' + +describe('VocabularyCard', () => { + let container + let vocabularyCard + + beforeEach(() => { + // 設置DOM容器 + container = document.createElement('div') + document.body.appendChild(container) + + // 創建組件實例 + vocabularyCard = document.createElement('vocabulary-card') + container.appendChild(vocabularyCard) + }) + + afterEach(() => { + // 清理DOM + document.body.removeChild(container) + }) + + it('should render word information correctly', () => { + vocabularyCard.setAttribute('word-id', 'test-word') + + // 模擬API回應 + vi.mock('../../src/services/vocabularyApi.js', () => ({ + vocabularyAPI: { + getWordIntroduction: vi.fn().mockResolvedValue({ + word: 'hello', + pronunciation: '/həˈloʊ/', + definition_zh: '你好', + examples: [ + { sentence: 'Hello, world!' } + ] + }) + } + })) + + // 等待組件渲染 + return new Promise(resolve => { + setTimeout(() => { + expect(vocabularyCard.querySelector('.word-text')).toHaveTextContent('hello') + expect(vocabularyCard.querySelector('.pronunciation')).toHaveTextContent('/həˈloʊ/') + resolve() + }, 100) + }) + }) +}) +``` + +## 📦 建構和部署 + +### Vite建構配置 +```javascript +// vite.config.js +import { defineConfig } from 'vite' +import { VitePWA } from 'vite-plugin-pwa' + +export default defineConfig({ + plugins: [ + VitePWA({ + registerType: 'autoUpdate', + manifest: { + name: 'Drama Ling', + short_name: 'DramaLing', + description: 'AI-powered language learning app', + theme_color: '#1976d2', + background_color: '#ffffff', + display: 'standalone', + icons: [ + { + src: '/icons/icon-192x192.png', + sizes: '192x192', + type: 'image/png' + } + ] + } + }) + ], + + build: { + rollupOptions: { + output: { + manualChunks: { + vendor: ['vite'], + utils: ['./src/utils/index.js'] + } + } + }, + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true + } + } + }, + + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "./src/styles/base/variables.scss";` + } + } + } +}) +``` + +--- + +**文檔狀態**: 🟢 已完成原生前端技術架構規劃 +**最後更新**: 2025-09-10 +**負責團隊**: 前端開發團隊 +**下次檢查**: 開發開始前進行技術實施確認 \ No newline at end of file diff --git a/docs/04_technical/06_development/README.md b/docs/04_technical/06_development/README.md new file mode 100644 index 0000000..d4c9239 --- /dev/null +++ b/docs/04_technical/06_development/README.md @@ -0,0 +1,96 @@ +# 🔧 Development Tools & Processes + +## 📁 目錄用途 + +`06_development` 資料夾專門存放**開發過程管理**相關的工具和文檔,與技術規格不同,這裡著重於: + +- **開發過程中的問題追蹤和管理** +- **開發環境的設定和配置** +- **開發工具和流程的標準化** + +## 📂 目錄結構 + +``` +06_development/ +├── README.md # 本目錄說明文檔 +├── issues-tracking.md # 問題追蹤系統 +└── environment/ # 開發環境設定 + ├── README.md # 環境設定總覽 + └── xcode_setup_guide.md # Xcode 設定指南 +``` + +## 📋 文檔說明 + +### 🎯 issues-tracking.md +**用途**: 專案開發過程中的問題追蹤和管理系統 + +**包含內容**: +- 問題分類和優先級定義 +- 問題追蹤工作流程 +- 開放問題的記錄和狀態追蹤 +- 問題解決方案和決議記錄 + +**使用時機**: +- 發現規格衝突或不確定性 +- 開發過程中遇到技術問題 +- 需要團隊討論的技術決策 +- 跨模組整合出現問題 + +### 🔧 environment/ +**用途**: 開發環境設定和配置指南 + +**包含內容**: +- 開發環境統一標準 +- 特定平台設定指南(如 Xcode) +- 開發工具配置說明 +- 環境問題排除方法 + +**使用時機**: +- 新成員加入專案 +- 開發環境出現問題 +- 需要統一開發工具設定 +- 平台特定配置需求 + +## 🚀 文檔創建原則 + +### ✅ 應該創建的文檔 +- **問題追蹤記錄**: 開發過程中遇到的具體問題 +- **環境設定指南**: 開發環境配置和問題解決 +- **工具配置文檔**: 開發工具的標準化設定 +- **流程改進文檔**: 開發流程的優化和標準化 + +### ❌ 不應該放在此處的內容 +- **技術規格文檔** → 應放在 `04_technical/` 的其他子目錄 +- **用戶流程規格** → 應放在 `01_requirement/` 或 `02_design/` +- **API文檔** → 應放在 `04_technical/02_api/` +- **架構設計** → 應放在 `04_technical/01_architecture/` + +## 🔄 與其他目錄的關係 + +| 目錄關係 | 說明 | +|---------|------| +| **vs `/sop/`** | SOP定義「如何工作」的規範,06_development管理「開發中遇到的具體問題」 | +| **vs `03_development/`** | 03_development包含開發指南和標準,06_development處理執行過程中的問題 | +| **vs `04_technical/其他`** | 其他技術目錄定義規格,06_development管理實施過程中的障礙 | + +## 📊 工作流程 + +### 問題發現 → 記錄追蹤 +1. 在開發過程中發現問題 +2. 在 `issues-tracking.md` 中記錄 +3. 分類、設定優先級和負責人 +4. 追蹤解決進度 +5. 記錄解決方案和結果 + +### 環境問題 → 文檔化解決 +1. 遇到開發環境問題 +2. 創建或更新環境設定文檔 +3. 提供詳細的解決步驟 +4. 確保其他開發者可以參考 + +--- + +**用途總結**: 此目錄是開發過程中的「實戰記錄」,專注於解決開發執行階段遇到的實際問題和環境配置需求。 + +**最後更新**: 2025-09-10 +**維護者**: Drama Ling 開發團隊 \ No newline at end of file diff --git a/docs/04_technical/README.md b/docs/04_technical/README.md index 195fd0c..ad6c52d 100644 --- a/docs/04_technical/README.md +++ b/docs/04_technical/README.md @@ -71,7 +71,7 @@ git clone https://github.com/your-org/dramaling-app.git cd dramaling-app # 啟動開發服務器 (使用Vite僅作為開發服務器) -cd apps/web-native +cd apps/web npm install npm run dev @@ -81,7 +81,7 @@ open http://localhost:3000 ### **專案結構預覽** ``` -apps/web-native/ +apps/web/ ├── index.html # 應用入口 ├── pages/ # 頁面文件 │ ├── vocabulary/ diff --git a/docs/04_technical/03_frontend/vue-development-standards.md b/sop/archive/20250910155305_vue-development-standards.md similarity index 100% rename from docs/04_technical/03_frontend/vue-development-standards.md rename to sop/archive/20250910155305_vue-development-standards.md diff --git a/docs/04_technical/03_frontend/vue-frontend-architecture.md b/sop/archive/20250910155305_vue-frontend-architecture.md similarity index 100% rename from docs/04_technical/03_frontend/vue-frontend-architecture.md rename to sop/archive/20250910155305_vue-frontend-architecture.md diff --git a/docs/04_technical/03_frontend/vue-project-structure.md b/sop/archive/20250910155305_vue-project-structure.md similarity index 100% rename from docs/04_technical/03_frontend/vue-project-structure.md rename to sop/archive/20250910155305_vue-project-structure.md diff --git a/docs/04_technical/03_frontend/vue-tools-configuration.md b/sop/archive/20250910155305_vue-tools-configuration.md similarity index 100% rename from docs/04_technical/03_frontend/vue-tools-configuration.md rename to sop/archive/20250910155305_vue-tools-configuration.md diff --git a/docs/04_technical/06_development/file-organization-strategy.md b/sop/archive/20250910160541_file-organization-strategy.md similarity index 100% rename from docs/04_technical/06_development/file-organization-strategy.md rename to sop/archive/20250910160541_file-organization-strategy.md diff --git a/docs/04_technical/06_development/user-flow-specification.md b/sop/archive/20250910161417_user-flow-specification.md similarity index 100% rename from docs/04_technical/06_development/user-flow-specification.md rename to sop/archive/20250910161417_user-flow-specification.md diff --git a/sop/tools/reports/analysis/2025-09-10_analysis-analysis.md b/sop/tools/reports/analysis/2025-09-10_analysis-analysis.md deleted file mode 100644 index 463a389..0000000 --- a/sop/tools/reports/analysis/2025-09-10_analysis-analysis.md +++ /dev/null @@ -1,108 +0,0 @@ -# 前端架構重構 Vue→原生HTML 決策分析報告 - -## 📊 檢查結果總覽 - -### 基本資訊 -- **分析日期**: 2025-09-10 -- **分析範圍**: Drama Ling Web前端架構完整重構評估 -- **觸發原因**: Vue框架限制設計還原精確度、Claude Code開發效率需求 -- **相關任務**: TASKS.md - 🔄 前端架構重構:Vue → 原生HTML - -### 數據統計 -- **現有Vue頁面**: 15個主要頁面組件 -- **需重構頁面**: 15個原生HTML頁面 -- **保留功能**: 100% (功能規格不變) -- **預期性能提升**: 60% (載入速度) -- **代碼理解度提升**: 15% (Claude Code相容性) - -## 🔍 詳細分析 - -### 1. 問題分類 - -#### 🔴 嚴重問題 -- **設計還原精確度不足**: Vue+Quasar框架樣式覆蓋導致設計與實現差異達15% -- **Claude Code理解困難**: 框架抽象層讓AI難以精確修改,開發效率降低20% -- **性能負擔**: 框架overhead導致首次載入時間超過2秒,影響用戶體驗 - -#### 🟡 重要問題 -- **調試複雜度**: Vue DevTools依賴增加調試步驟,問題定位時間延長 -- **團隊學習成本**: 框架概念學習曲線影響新成員上手速度 -- **打包體積過大**: ~800KB bundle size影響載入性能 - -#### 🟢 輕微問題 -- **開發工具配置**: Vue生態系統配置複雜性 -- **類型系統複雜**: Vue+TypeScript配置增加專案複雜度 -- **第三方依賴風險**: 框架升級可能帶來破壞性變更 - -### 2. 根因分析 - -#### 問題成因 -1. **框架抽象過度**: Vue組件系統為簡單頁面創造了不必要的複雜性 -2. **設計系統不匹配**: Quasar預設樣式與Drama Ling設計語言存在衝突 -3. **AI開發不友好**: 框架的黑盒邏輯降低了Claude Code的理解和修改精確度 - -#### 風險評估 -- **技術風險**: 中等 - 原生HTML技術成熟,風險可控 -- **時程風險**: 高等 - 3-4週重構週期可能影響其他功能開發 -- **維護風險**: 低等 - 原生HTML更易維護和理解 - -## 💡 解決方案 - -### 短期方案 (立即執行) -- [x] 建立重構專案文檔和技術架構規劃 -- [ ] 歸檔現有Vue版本代碼作為備份 -- [ ] 建立原生HTML專案目錄結構 - -### 中期方案 (1-2週內) -- [ ] 實施階段性重構 (基礎架構 → 核心頁面 → 功能頁面) -- [ ] 建立設計系統和CSS架構 -- [ ] 實現JavaScript模組化組件系統 - -### 長期方案 (1個月內) -- [ ] 完整API整合和功能測試 -- [ ] 性能優化和跨瀏覽器兼容性 -- [ ] 建立完善的文檔和最佳實踐指南 - -## 🎯 實施計劃 - -### 執行優先級 -1. 🔥 **緊急**: 基礎架構建立、核心頁面重構、設計系統建立 -2. ⚠️ **重要**: 功能頁面實現、JavaScript組件系統、API整合 -3. 📝 **一般**: 性能優化、跨瀏覽器測試、文檔完善 - -### 成功指標 -- **載入速度**: 從2s降至0.8s以下 -- **Bundle大小**: 從800KB降至150KB以下 -- **設計還原度**: 從85%提升至100% -- **Claude Code相容性**: 從80%提升至95% - -### 預期效益 -- **效率提升**: Claude Code開發效率提升20%,調試時間縮短50% -- **品質改善**: 設計還原100%準確,用戶體驗顯著提升 -- **維護便利**: 代碼直觀易懂,新成員上手時間縮短40% - -## 📋 後續追蹤 - -### 檢查清單 -- [ ] 週1:基礎架構建立完成度檢查 -- [ ] 週2:核心頁面實現進度檢查 -- [ ] 週3:功能頁面完整性檢查 -- [ ] 週4:整合測試和性能驗證 - -### 下次檢查 -- **檢查日期**: 2025-09-17 (週檢查點) -- **檢查範圍**: 第一階段基礎架構建立進度和品質 -- **負責人**: Claude Code + 專案負責人 - -### 相關文檔 -- [原生HTML重構專案](../../../projects/native-html-migration.md) -- [技術架構文檔](../../../docs/04_technical/README.md) -- [Web端功能規格](../../../docs/02_design/function-specs/web/README.md) -- [TASKS.md任務追蹤](../../../TASKS.md) - ---- - -**報告產生**: Drama Ling 分析報告工具 (./dl report analysis) -**報告人**: Claude Code -**審核人**: 專案負責人 -**檔案位置**: `sop/tools/reports/analysis/2025-09-10_analysis-analysis.md` \ No newline at end of file