From 9fc5e3e607f7083a3fdede858a401eb4453397c1 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sat, 12 Sep 2026 15:56:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E6=89=B9=E9=87=8F=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E6=89=80=E6=9C=89=E7=BB=84=E4=BB=B6=E4=B8=AD=E6=AE=8B=E7=95=99?= =?UTF-8?q?=E7=9A=84=E7=A1=AC=E7=BC=96=E7=A0=81=E6=B7=B1=E8=89=B2=E5=80=BC?= =?UTF-8?q?=E4=BB=A5=E5=BD=BB=E5=BA=95=E6=94=AF=E6=8C=81=E6=B5=85=E8=89=B2?= =?UTF-8?q?=E4=B8=BB=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Admin/ApiKeyManager.vue | 4 +- app/components/Admin/BackupManager.vue | 54 +- app/components/Admin/BlacklistManager.vue | 2 +- app/components/Admin/EmailTemplateManager.vue | 2 +- app/components/Admin/NotificationSender.vue | 2 +- app/components/Admin/OverviewDashboard.vue | 2 +- app/components/Admin/RequestTimeManager.vue | 2 +- app/components/Admin/ScheduleItemPrint.vue | 26 +- app/components/Admin/SchedulePrinter.vue | 34 +- app/components/Admin/Sidebar.vue | 2 +- app/components/Admin/SongDownloadDialog.vue | 2 +- app/components/Admin/SongManagement.vue | 2 +- app/components/Admin/VotersModal.vue | 46 +- app/components/Auth/OAuthButtons.vue | 16 +- app/components/Auth/RegisterForm.vue | 2 +- .../Notifications/NotificationSettings.vue | 4 +- app/components/Player/PlayerLyric/AMLyric.vue | 2 +- .../Player/PlayerLyric/DefaultLyric.vue | 2 +- app/components/Songs/RequestForm.vue | 100 +- app/components/Songs/ScheduleList.vue | 76 +- app/components/Songs/SongList.vue | 58 +- app/components/UI/AudioPlayer.vue | 50 +- app/components/UI/Common/CustomSelect.vue | 2 +- app/components/UI/LyricsModal.vue | 18 +- app/components/UI/Notification.vue | 12 +- app/components/UI/NotificationContainer.vue | 12 +- app/components/UserTutorial.vue | 12 +- app/components/year-review/ArtistSlide.vue | 2 +- app/components/year-review/FirstSongSlide.vue | 8 +- app/components/year-review/IntroSlide.vue | 2 +- app/components/year-review/MiscSlide.vue | 4 +- app/components/year-review/OutroSlide.vue | 10 +- app/components/year-review/ShareCard.vue | 4 +- app/pages/change-password.vue | 2 +- app/pages/index.vue | 12 +- app/pages/year-review.vue | 10 +- fix-colors.mjs | 128 + old-index.vue | 3661 +++++++++++++++++ old-nuxt.config.ts | 447 ++ old-nuxt2.config.ts | 433 ++ old-variables.css | 556 +++ old-variables2.css | 556 +++ 42 files changed, 6081 insertions(+), 300 deletions(-) create mode 100644 fix-colors.mjs create mode 100644 old-index.vue create mode 100644 old-nuxt.config.ts create mode 100644 old-nuxt2.config.ts create mode 100644 old-variables.css create mode 100644 old-variables2.css diff --git a/app/components/Admin/ApiKeyManager.vue b/app/components/Admin/ApiKeyManager.vue index 8a21229..b20a430 100644 --- a/app/components/Admin/ApiKeyManager.vue +++ b/app/components/Admin/ApiKeyManager.vue @@ -1026,7 +1026,7 @@ onMounted(() => { width: 24px; height: 24px; border: 2px solid rgba(255, 255, 255, 0.1); - border-top-color: #3b82f6; + border-top-color: var(--border-secondary, #3b82f6); border-radius: 50%; animation: spin 1s linear infinite; } @@ -1044,7 +1044,7 @@ onMounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/BackupManager.vue b/app/components/Admin/BackupManager.vue index efe60d0..3d78253 100644 --- a/app/components/Admin/BackupManager.vue +++ b/app/components/Admin/BackupManager.vue @@ -556,8 +556,8 @@ const formatFileSize = (bytes) => { /* 操作卡片 */ .action-card { - background: #2a2a2a; - border: 1px solid #404040; + background: var(--bg-tertiary, #2a2a2a); + border: 1px solid var(--border-secondary, #404040); border-radius: 12px; padding: 2rem; text-align: center; @@ -566,7 +566,7 @@ const formatFileSize = (bytes) => { .action-card:hover { border-color: #606060; - background: #2f2f2f; + background: var(--bg-tertiary, #2f2f2f); } .action-card .card-content { @@ -587,7 +587,7 @@ const formatFileSize = (bytes) => { display: flex; align-items: center; justify-content: center; - color: #ffffff; + color: var(--text-primary, #ffffff); } .action-icon svg { @@ -599,7 +599,7 @@ const formatFileSize = (bytes) => { .action-card h4 { font-size: 1.5rem; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin: 0 0 0.75rem 0; } @@ -672,8 +672,8 @@ const formatFileSize = (bytes) => { } .modal { - background: #2a2a2a; - border: 1px solid #404040; + background: var(--bg-tertiary, #2a2a2a); + border: 1px solid var(--border-secondary, #404040); border-radius: 12px; max-width: 600px; width: 100%; @@ -724,7 +724,7 @@ const formatFileSize = (bytes) => { .progress-details h5, .progress-errors h5 { margin: 0 0 10px 0; - color: #ffffff; + color: var(--text-primary, #ffffff); font-size: 14px; font-weight: 600; } @@ -736,14 +736,14 @@ const formatFileSize = (bytes) => { border: 1px solid #505050; border-radius: 6px; padding: 10px; - background: #333333; + background: var(--bg-quaternary, #333333); } .detail-item { padding: 4px 0; color: #a0a0a0; font-size: 13px; - border-bottom: 1px solid #404040; + border-bottom: 1px solid var(--border-secondary, #404040); } .detail-item:last-child { @@ -754,7 +754,7 @@ const formatFileSize = (bytes) => { padding: 4px 0; color: #ff6b6b; font-size: 13px; - border-bottom: 1px solid #4a2a2a; + border-bottom: 1px solid var(--border-secondary, #4a2a2a); } .error-item:last-child { @@ -772,7 +772,7 @@ const formatFileSize = (bytes) => { .modal-title { font-size: 1.75rem; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin: 0; } @@ -788,7 +788,7 @@ const formatFileSize = (bytes) => { .close-btn:hover { background: #505050; - color: #ffffff; + color: var(--text-primary, #ffffff); } .close-btn svg { @@ -802,11 +802,11 @@ const formatFileSize = (bytes) => { .modal-footer { padding: 1.5rem 2rem; - border-top: 1px solid #404040; + border-top: 1px solid var(--border-secondary, #404040); display: flex; gap: 1rem; justify-content: flex-end; - background: #252525; + background: var(--bg-tertiary, #252525); } .modal-footer button { @@ -822,7 +822,7 @@ const formatFileSize = (bytes) => { display: block; font-size: 1.125rem; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin-bottom: 1rem; } @@ -838,7 +838,7 @@ const formatFileSize = (bytes) => { align-items: flex-start; gap: 1rem; padding: 1.25rem; - background: #333333; + background: var(--bg-quaternary, #333333); border: 1px solid #505050; border-radius: 8px; cursor: pointer; @@ -870,7 +870,7 @@ const formatFileSize = (bytes) => { display: block; font-size: 1rem; font-weight: 500; - color: #ffffff; + color: var(--text-primary, #ffffff); margin-bottom: 0.25rem; } @@ -907,12 +907,12 @@ const formatFileSize = (bytes) => { text-align: center; cursor: pointer; transition: all 0.2s ease; - background: #333333; + background: var(--bg-quaternary, #333333); } .upload-area:hover, .upload-area.drag-over { - border-color: #4a90e2; + border-color: var(--border-secondary, #4a90e2); background: #383838; } @@ -926,7 +926,7 @@ const formatFileSize = (bytes) => { .upload-area h4 { font-size: 1.25rem; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin: 0 0 0.5rem 0; } @@ -941,7 +941,7 @@ const formatFileSize = (bytes) => { align-items: center; justify-content: space-between; padding: 1.25rem; - background: #333333; + background: var(--bg-quaternary, #333333); border: 1px solid #505050; border-radius: 8px; margin-top: 1rem; @@ -969,7 +969,7 @@ const formatFileSize = (bytes) => { display: block; font-size: 1rem; font-weight: 500; - color: #ffffff; + color: var(--text-primary, #ffffff); margin-bottom: 0.25rem; } @@ -1056,8 +1056,8 @@ const formatFileSize = (bytes) => { } .notification.success { - background: #2e7d32; - border: 1px solid #4caf50; + background: var(--bg-tertiary, #2e7d32); + border: 1px solid var(--border-secondary, #4caf50); } .notification.error { @@ -1071,8 +1071,8 @@ const formatFileSize = (bytes) => { } .notification.info { - background: #1976d2; - border: 1px solid #2196f3; + background: var(--bg-secondary, #1976d2); + border: 1px solid var(--border-secondary, #2196f3); } @keyframes slideInRight { diff --git a/app/components/Admin/BlacklistManager.vue b/app/components/Admin/BlacklistManager.vue index a31371f..b5ec8a4 100644 --- a/app/components/Admin/BlacklistManager.vue +++ b/app/components/Admin/BlacklistManager.vue @@ -645,7 +645,7 @@ onMounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } diff --git a/app/components/Admin/EmailTemplateManager.vue b/app/components/Admin/EmailTemplateManager.vue index 89fda18..a995f31 100644 --- a/app/components/Admin/EmailTemplateManager.vue +++ b/app/components/Admin/EmailTemplateManager.vue @@ -336,7 +336,7 @@ onMounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/NotificationSender.vue b/app/components/Admin/NotificationSender.vue index de4cd46..23bd005 100644 --- a/app/components/Admin/NotificationSender.vue +++ b/app/components/Admin/NotificationSender.vue @@ -742,7 +742,7 @@ onUnmounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/OverviewDashboard.vue b/app/components/Admin/OverviewDashboard.vue index 7ac7086..148318c 100644 --- a/app/components/Admin/OverviewDashboard.vue +++ b/app/components/Admin/OverviewDashboard.vue @@ -366,7 +366,7 @@ onMounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/RequestTimeManager.vue b/app/components/Admin/RequestTimeManager.vue index bf35bb6..762281f 100644 --- a/app/components/Admin/RequestTimeManager.vue +++ b/app/components/Admin/RequestTimeManager.vue @@ -842,7 +842,7 @@ const cancelForm = () => { background: transparent; } ::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/ScheduleItemPrint.vue b/app/components/Admin/ScheduleItemPrint.vue index 51b2c1b..703e42a 100644 --- a/app/components/Admin/ScheduleItemPrint.vue +++ b/app/components/Admin/ScheduleItemPrint.vue @@ -112,7 +112,7 @@ const handleImageError = (event) => { justify-content: center; font-weight: bold; font-size: 14px; - color: #333; + color: var(--text-quaternary, #333333); flex-shrink: 0; } @@ -141,7 +141,7 @@ const handleImageError = (event) => { display: none; align-items: center; justify-content: center; - color: #999; + color: var(--text-tertiary, #999999); } .cover-placeholder.show { @@ -161,7 +161,7 @@ const handleImageError = (event) => { .song-title { font-weight: bold; font-size: 16px; - color: #333; + color: var(--text-quaternary, #333333); margin-bottom: 2px; white-space: nowrap; overflow: hidden; @@ -176,7 +176,7 @@ const handleImageError = (event) => { display: inline-block; padding: 1px 4px; background: #e3f2fd; - border: 1px solid #2196f3; + border: 1px solid var(--border-secondary, #2196f3); border-radius: 3px; color: #1976d2; font-size: 10px; @@ -186,7 +186,7 @@ const handleImageError = (event) => { .song-artist { font-size: 14px; - color: #666; + color: var(--text-quaternary, #666666); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -197,7 +197,7 @@ const handleImageError = (event) => { display: flex; align-items: center; font-size: 12px; - color: #666; + color: var(--text-quaternary, #666666); white-space: nowrap; } @@ -207,14 +207,14 @@ const handleImageError = (event) => { } .value { - color: #333; + color: var(--text-quaternary, #333333); } /* 打印样式 */ @media print { .schedule-item-print { - color: #000 !important; - background: #fff !important; + color: var(--text-primary, #000) !important; + background: var(--card-bg, #fff) !important; width: 100% !important; max-width: none !important; box-sizing: border-box !important; @@ -230,7 +230,7 @@ const handleImageError = (event) => { .sequence-number { background: #f0f0f0 !important; - color: #000 !important; + color: var(--text-primary, #000) !important; width: 30px !important; height: 30px !important; flex-shrink: 0 !important; @@ -264,7 +264,7 @@ const handleImageError = (event) => { } .song-title { - color: #000 !important; + color: var(--text-primary, #000) !important; font-size: 16px !important; white-space: nowrap !important; overflow: hidden !important; @@ -274,11 +274,11 @@ const handleImageError = (event) => { .song-artist, .label, .value { - color: #333 !important; + color: var(--text-quaternary, #333333) !important; } .time-range { - color: #666 !important; + color: var(--text-quaternary, #666666) !important; } .requester-info, diff --git a/app/components/Admin/SchedulePrinter.vue b/app/components/Admin/SchedulePrinter.vue index 71bf445..9b26695 100644 --- a/app/components/Admin/SchedulePrinter.vue +++ b/app/components/Admin/SchedulePrinter.vue @@ -1383,15 +1383,15 @@ watch( /* 预览区域和打印样式的核心CSS - 保持原生CSS以确保精确控制 */ .preview-content { - background: #ffffff; + background: var(--card-bg, #ffffff); padding: 0; position: relative; } /* 打印页面样式 */ .print-page { - background: #ffffff; - color: #000000; + background: var(--card-bg, #ffffff); + color: var(--text-primary, #000000); width: 100%; margin: 0 auto; padding: 30px; @@ -1426,7 +1426,7 @@ watch( .logo-divider { width: 2px; height: 60px; - background: linear-gradient(to bottom, #ddd, #999, #ddd); + background: linear-gradient(to bottom, var(--border-secondary, #dddddd), #999, #ddd); border-radius: 1px; margin: 0 4px; } @@ -1445,19 +1445,19 @@ watch( font-size: 24px; font-weight: normal; margin: 0 0 4px 0; - color: #000; + color: var(--text-primary, #000); } .title-section h2 { font-size: 18px; font-weight: 500; margin: 0; - color: #666; + color: var(--text-quaternary, #666666); } .date-info { font-size: 14px; - color: #666; + color: var(--text-quaternary, #666666); text-align: right; display: flex; align-items: flex-start; @@ -1477,14 +1477,14 @@ watch( .no-data-message { text-align: center; padding: 60px 20px; - color: #666; + color: var(--text-quaternary, #666666); } .no-data-icon { width: 64px; height: 64px; margin: 0 auto 20px; - color: #ccc; + color: var(--text-secondary, #cccccc); } .no-data-icon svg { @@ -1496,7 +1496,7 @@ watch( font-size: 18px; font-weight: 500; margin: 0 0 12px 0; - color: #333; + color: var(--text-quaternary, #333333); } .no-data-message p { @@ -1512,15 +1512,15 @@ watch( font-weight: bold; margin: 24px 0 12px 0; padding: 8px 12px; - border-bottom: 2px solid #ddd; + border-bottom: 2px solid var(--border-secondary, #ddd); background: #f8f9fa; - color: #333; + color: var(--text-quaternary, #333333); border-radius: 4px 4px 0 0; } .playtime-group .group-title { background: #e3f2fd; - border-bottom-color: #2196f3; + border-bottom-color: var(--border-secondary, #2196f3); color: #1565c0; } @@ -1533,7 +1533,7 @@ watch( .group-count { font-size: 14px; font-weight: normal; - color: #666; + color: var(--text-quaternary, #666666); margin-left: 8px; } @@ -1547,7 +1547,7 @@ watch( margin: 16px 0 8px 0; padding: 6px 10px; background: #f0f8ff; - border-left: 3px solid #2196f3; + border-left: 3px solid var(--border-secondary, #2196f3); color: #1565c0; border-radius: 0 4px 4px 0; } @@ -1555,7 +1555,7 @@ watch( .playtime-count { font-size: 12px; font-weight: normal; - color: #666; + color: var(--text-quaternary, #666666); margin-left: 6px; } @@ -1570,7 +1570,7 @@ watch( padding-top: 16px; border-top: 1px solid #e5e5e5; font-size: 12px; - color: #666; + color: var(--text-quaternary, #666666); } .footer-left { diff --git a/app/components/Admin/Sidebar.vue b/app/components/Admin/Sidebar.vue index d4dc22a..369f26c 100644 --- a/app/components/Admin/Sidebar.vue +++ b/app/components/Admin/Sidebar.vue @@ -279,7 +279,7 @@ const getRoleDisplayName = (role) => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/SongDownloadDialog.vue b/app/components/Admin/SongDownloadDialog.vue index bb81bf2..9268c5a 100644 --- a/app/components/Admin/SongDownloadDialog.vue +++ b/app/components/Admin/SongDownloadDialog.vue @@ -1228,7 +1228,7 @@ onUnmounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/SongManagement.vue b/app/components/Admin/SongManagement.vue index fce2995..a630cc2 100644 --- a/app/components/Admin/SongManagement.vue +++ b/app/components/Admin/SongManagement.vue @@ -2179,7 +2179,7 @@ onUnmounted(() => { background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background: #27272a; + background: var(--bg-tertiary, #27272a); border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { diff --git a/app/components/Admin/VotersModal.vue b/app/components/Admin/VotersModal.vue index d7730f9..52fe766 100644 --- a/app/components/Admin/VotersModal.vue +++ b/app/components/Admin/VotersModal.vue @@ -223,14 +223,14 @@ watch( .modal-title { font-size: 20px; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin: 0; } .close-btn { background: none; border: none; - color: #888; + color: var(--text-tertiary, #888888); cursor: pointer; padding: 8px; border-radius: 8px; @@ -238,8 +238,8 @@ watch( } .close-btn:hover { - background: #2a2a2a; - color: #ffffff; + background: var(--bg-tertiary, #2a2a2a); + color: var(--text-primary, #ffffff); } .close-btn svg { @@ -254,22 +254,22 @@ watch( } .song-info { - background: #2a2a2a; + background: var(--bg-tertiary, #2a2a2a); border-radius: 12px; padding: 20px; margin-bottom: 24px; - border: 1px solid #3a3a3a; + border: 1px solid var(--border-secondary, #3a3a3a); } .song-title { font-size: 18px; font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); margin: 0 0 8px 0; } .song-artist { - color: #888; + color: var(--text-tertiary, #888888); margin: 0 0 16px 0; font-size: 14px; } @@ -304,7 +304,7 @@ watch( .spinner { width: 32px; height: 32px; - border: 3px solid #3a3a3a; + border: 3px solid var(--border-secondary, #3a3a3a); border-top: 3px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; @@ -366,12 +366,12 @@ watch( align-items: center; justify-content: space-between; padding-bottom: 12px; - border-bottom: 1px solid #3a3a3a; + border-bottom: 1px solid var(--border-secondary, #3a3a3a); } .voters-title { font-weight: 600; - color: #ffffff; + color: var(--text-primary, #ffffff); font-size: 16px; } @@ -388,15 +388,15 @@ watch( align-items: center; justify-content: space-between; padding: 12px 16px; - background: #2a2a2a; + background: var(--bg-tertiary, #2a2a2a); border-radius: 8px; - border: 1px solid #3a3a3a; + border: 1px solid var(--border-secondary, #3a3a3a); transition: all 0.2s ease; } .voter-item:hover { - background: #333333; - border-color: #4a4a4a; + background: var(--bg-quaternary, #333333); + border-color: var(--border-secondary, #4a4a4a); } .voter-info { @@ -426,18 +426,18 @@ watch( } .voter-name { - color: #ffffff; + color: var(--text-primary, #ffffff); font-weight: 500; font-size: 14px; } .vote-time { - color: #888; + color: var(--text-tertiary, #888888); font-size: 12px; } .voter-number { - color: #888; + color: var(--text-tertiary, #888888); font-size: 12px; font-weight: 500; } @@ -454,11 +454,11 @@ watch( .empty-icon { width: 48px; height: 48px; - color: #666; + color: var(--text-quaternary, #666666); } .empty-state p { - color: #888; + color: var(--text-tertiary, #888888); margin: 0; } @@ -469,8 +469,8 @@ watch( } .close-button { - background: #3a3a3a; - color: #ffffff; + background: var(--bg-quaternary, #3a3a3a); + color: var(--text-primary, #ffffff); border: none; padding: 12px 24px; border-radius: 8px; @@ -490,7 +490,7 @@ watch( } .voters-container::-webkit-scrollbar-track { - background: #2a2a2a; + background: var(--bg-tertiary, #2a2a2a); border-radius: 3px; } diff --git a/app/components/Auth/OAuthButtons.vue b/app/components/Auth/OAuthButtons.vue index 77e08f1..d7a169f 100644 --- a/app/components/Auth/OAuthButtons.vue +++ b/app/components/Auth/OAuthButtons.vue @@ -141,21 +141,21 @@ const loginWith = (provider: string) => { } .oauth-btn-github:hover { - background: #24292e; - color: #ffffff; - border-color: #24292e; + background: var(--bg-tertiary, #24292e); + color: var(--text-primary, #ffffff); + border-color: var(--border-secondary, #24292e); } .oauth-btn-google:hover { - background: #ffffff; - color: #111111; + background: var(--card-bg, #ffffff); + color: var(--text-primary, #111111); border-color: #dadce0; } .oauth-btn-aggregate:hover { - background: #12b7f5; - color: #ffffff; - border-color: #12b7f5; + background: var(--bg-secondary, #12b7f5); + color: var(--text-primary, #ffffff); + border-color: var(--border-secondary, #12b7f5); } .oauth-empty-hint { diff --git a/app/components/Auth/RegisterForm.vue b/app/components/Auth/RegisterForm.vue index c9f0dc0..ee8fbf2 100644 --- a/app/components/Auth/RegisterForm.vue +++ b/app/components/Auth/RegisterForm.vue @@ -575,7 +575,7 @@ const handleRegister = async () => { width: 100%; padding: 16px; background: var(--primary); - color: #ffffff; + color: var(--text-primary, #ffffff); border: none; border-radius: var(--radius-lg); font-size: 16px; diff --git a/app/components/Notifications/NotificationSettings.vue b/app/components/Notifications/NotificationSettings.vue index e585ebd..b6137f7 100644 --- a/app/components/Notifications/NotificationSettings.vue +++ b/app/components/Notifications/NotificationSettings.vue @@ -209,7 +209,7 @@ input[type='range']::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; - background: #2563eb; /* blue-600 */ + background: var(--bg-tertiary, #2563eb); /* blue-600 */ border-radius: 50%; cursor: pointer; transition: all 0.2s ease; @@ -218,7 +218,7 @@ input[type='range']::-webkit-slider-thumb { input[type='range']::-moz-range-thumb { width: 18px; height: 18px; - background: #2563eb; + background: var(--bg-tertiary, #2563eb); border-radius: 50%; cursor: pointer; border: none; diff --git a/app/components/Player/PlayerLyric/AMLyric.vue b/app/components/Player/PlayerLyric/AMLyric.vue index 58ba74a..93ee383 100644 --- a/app/components/Player/PlayerLyric/AMLyric.vue +++ b/app/components/Player/PlayerLyric/AMLyric.vue @@ -160,7 +160,7 @@ const jumpSeek = (line: LyricLineMouseEvent) => { :deep(.am-lyric .is-active), :deep(.am-lyric .lyric-line.current), :deep(.am-lyric .lyric-line.is-current) { - color: #ffffff; + color: var(--text-primary, #ffffff); text-shadow: 0 4px 18px rgba(0, 0, 0, 0.35); } } diff --git a/app/components/Player/PlayerLyric/DefaultLyric.vue b/app/components/Player/PlayerLyric/DefaultLyric.vue index 156fec0..259724f 100644 --- a/app/components/Player/PlayerLyric/DefaultLyric.vue +++ b/app/components/Player/PlayerLyric/DefaultLyric.vue @@ -824,6 +824,6 @@ onBeforeUnmount(() => { align-items: center; justify-content: center; font-size: 22px; - color: #fff; + color: var(--text-primary, #ffffff); } diff --git a/app/components/Songs/RequestForm.vue b/app/components/Songs/RequestForm.vue index 538f4f5..8c494ae 100644 --- a/app/components/Songs/RequestForm.vue +++ b/app/components/Songs/RequestForm.vue @@ -2912,7 +2912,7 @@ defineExpose({ diff --git a/app/components/year-review/ArtistSlide.vue b/app/components/year-review/ArtistSlide.vue index 41a2cd4..1b060d8 100644 --- a/app/components/year-review/ArtistSlide.vue +++ b/app/components/year-review/ArtistSlide.vue @@ -43,7 +43,7 @@ defineProps({ .bg-overlay { position: absolute; inset: 0; - background: linear-gradient(to top, #000, transparent, transparent); + background: linear-gradient(to top, var(--bg-primary, #111111), transparent, transparent); opacity: 0.8; z-index: 10; } diff --git a/app/components/year-review/FirstSongSlide.vue b/app/components/year-review/FirstSongSlide.vue index 630ef15..9ab57af 100644 --- a/app/components/year-review/FirstSongSlide.vue +++ b/app/components/year-review/FirstSongSlide.vue @@ -74,7 +74,7 @@ const formatDate = (dateStr) => { align-items: center; justify-content: center; overflow: hidden; - background-color: #000; + background-color: var(--bg-primary, #111111); } .bg-blur { @@ -120,7 +120,7 @@ const formatDate = (dateStr) => { } .highlight { - color: #fff; + color: var(--text-primary, #ffffff); font-weight: 700; margin: 0 0.5rem; } @@ -141,7 +141,7 @@ const formatDate = (dateStr) => { overflow: hidden; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); - background: #111827; + background: var(--bg-secondary, #111827); display: flex; align-items: center; justify-content: center; @@ -181,7 +181,7 @@ const formatDate = (dateStr) => { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - color: #fff; + color: var(--text-primary, #ffffff); } .song-artist { diff --git a/app/components/year-review/IntroSlide.vue b/app/components/year-review/IntroSlide.vue index 266bbb7..4c5e496 100644 --- a/app/components/year-review/IntroSlide.vue +++ b/app/components/year-review/IntroSlide.vue @@ -31,7 +31,7 @@ defineProps({ align-items: center; justify-content: center; overflow: hidden; - background: radial-gradient(circle at center, #111827, #000000); + background: radial-gradient(circle at center, var(--bg-secondary, #111827), #000000); } .blob { diff --git a/app/components/year-review/MiscSlide.vue b/app/components/year-review/MiscSlide.vue index cc65ec1..6221775 100644 --- a/app/components/year-review/MiscSlide.vue +++ b/app/components/year-review/MiscSlide.vue @@ -54,7 +54,7 @@ const formatPlatform = (platform) => { align-items: center; justify-content: center; overflow: hidden; - background-color: #111827; /* gray-900 */ + background-color: var(--bg-secondary, #111827); /* gray-900 */ } .bg-gradient { @@ -63,7 +63,7 @@ const formatPlatform = (platform) => { left: 0; width: 100%; height: 50%; - background: linear-gradient(to bottom, #1f2937, transparent); + background: linear-gradient(to bottom, var(--bg-secondary, #1f2937), transparent); opacity: 0.2; } diff --git a/app/components/year-review/OutroSlide.vue b/app/components/year-review/OutroSlide.vue index 0fe78e7..f62251c 100644 --- a/app/components/year-review/OutroSlide.vue +++ b/app/components/year-review/OutroSlide.vue @@ -56,13 +56,13 @@ defineEmits(['share']) align-items: center; justify-content: center; overflow: hidden; - background-color: #000; + background-color: var(--bg-primary, #111111); } .bg-gradient { position: absolute; inset: 0; - background: radial-gradient(circle at center, #1f2937, #000, #000); + background: radial-gradient(circle at center, var(--bg-secondary, #1f2937), #000, #000); opacity: 0.5; } @@ -132,8 +132,8 @@ defineEmits(['share']) width: 100%; padding: 1rem 2rem; border-radius: 9999px; - background: #fff; - color: #000; + background: var(--card-bg, #fff); + color: var(--text-primary, #000); font-weight: 700; font-size: 1.125rem; overflow: hidden; @@ -173,7 +173,7 @@ defineEmits(['share']) border-radius: 9999px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); - color: #fff; + color: var(--text-primary, #ffffff); font-weight: 700; font-size: 1.125rem; backdrop-filter: blur(12px); diff --git a/app/components/year-review/ShareCard.vue b/app/components/year-review/ShareCard.vue index b1f2e91..bd33395 100644 --- a/app/components/year-review/ShareCard.vue +++ b/app/components/year-review/ShareCard.vue @@ -63,7 +63,7 @@ defineExpose({ height: 100%; min-height: 600px; overflow: hidden; - background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%); + background: linear-gradient(135deg, var(--bg-secondary, #0f0f23) 0%, #1a1a2e 50%, #16213e 100%); } .card-background { @@ -127,7 +127,7 @@ defineExpose({ .main-title { font-size: 3.5rem; font-weight: 900; - background: linear-gradient(135deg, #ffffff 0%, #e5e7eb 50%, #9ca3af 100%); + background: linear-gradient(135deg, var(--card-bg, #ffffff) 0%, #e5e7eb 50%, #9ca3af 100%); -webkit-background-clip: text; background-clip: text; color: transparent; diff --git a/app/pages/change-password.vue b/app/pages/change-password.vue index 506d5e6..7cad842 100644 --- a/app/pages/change-password.vue +++ b/app/pages/change-password.vue @@ -195,7 +195,7 @@ onMounted(async () => { font-size: 36px; font-weight: 700; margin: 0; - background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%); + background: linear-gradient(135deg, var(--card-bg, #ffffff) 0%, #f0f0f0 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; diff --git a/app/pages/index.vue b/app/pages/index.vue index 8d450c0..7ec75dd 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -1910,7 +1910,7 @@ if ( height: 18px; border-radius: 9px; background-color: var(--error, #ef4444); - color: #ffffff; + color: var(--text-primary, #ffffff); font-size: 10px; font-weight: 700; display: flex; @@ -3405,7 +3405,7 @@ if ( display: flex; align-items: center; justify-content: center; - color: #ffffff; + color: var(--text-primary, #ffffff); box-shadow: 0 10px 25px -5px rgba(139, 92, 246, 0.35); z-index: 2; transform: rotate(-5deg); @@ -3485,7 +3485,7 @@ if ( background: var(--primary, #0b5afe); border: none; border-radius: 16px; - color: #ffffff; + color: var(--text-primary, #ffffff); font-size: 16px; font-weight: 700; display: flex; @@ -3600,7 +3600,7 @@ if ( width: 8px; height: 8px; border-radius: 50%; - background-color: #0b5afe; + background-color: var(--bg-secondary, #0b5afe); display: inline-block; z-index: 2; } @@ -3664,9 +3664,9 @@ if ( } .login-button { - background: linear-gradient(180deg, #0043f8 0%, #0075f8 100%); + background: linear-gradient(180deg, var(--bg-secondary, #0043f8) 0%, #0075f8 100%); border: none; - color: #ffffff; + color: var(--text-primary, #ffffff); padding: 10px 20px; border-radius: 6px; font-weight: 600; diff --git a/app/pages/year-review.vue b/app/pages/year-review.vue index aae3219..398dd2c 100644 --- a/app/pages/year-review.vue +++ b/app/pages/year-review.vue @@ -211,8 +211,8 @@ const handleTouchEnd = (e) => { .review-page { position: fixed; inset: 0; - background-color: #000; - color: #fff; + background-color: var(--bg-primary, #111111); + color: var(--text-primary, #ffffff); z-index: 9999; font-family: 'MiSans', @@ -230,7 +230,7 @@ const handleTouchEnd = (e) => { flex-direction: column; align-items: center; justify-content: center; - background-color: #000; + background-color: var(--bg-primary, #111111); z-index: 50; } @@ -285,7 +285,7 @@ const handleTouchEnd = (e) => { .empty .empty-icon-circle { width: 5rem; height: 5rem; - background: #111827; + background: var(--bg-secondary, #111827); border-radius: 50%; display: flex; align-items: center; @@ -364,7 +364,7 @@ const handleTouchEnd = (e) => { .indicator-dot.active { height: 1.5rem; - background: #fff; + background: var(--card-bg, #fff); } /* 滚动提示 */ diff --git a/fix-colors.mjs b/fix-colors.mjs new file mode 100644 index 0000000..003e18b --- /dev/null +++ b/fix-colors.mjs @@ -0,0 +1,128 @@ +import fs from 'fs/promises'; +import path from 'path'; + +async function walk(dir) { + const files = await fs.readdir(dir, { withFileTypes: true }); + let results = []; + for (const file of files) { + const res = path.resolve(dir, file.name); + if (file.isDirectory()) { + results = results.concat(await walk(res)); + } else if (file.name.endsWith('.vue')) { + results.push(res); + } + } + return results; +} + +async function fixColors() { + const dirs = [path.resolve('app', 'components'), path.resolve('app', 'pages'), path.resolve('app', 'layouts')]; + let files = []; + for (const dir of dirs) { + try { + files = files.concat(await walk(dir)); + } catch (e) { + // Ignore if dir doesn't exist + } + } + + for (const file of files) { + let content = await fs.readFile(file, 'utf-8'); + let original = content; + + // Generic regex for hardcoded grayscales in styles + const grayscales = { + '#000000': 'var(--bg-primary, #111111)', + '#000': 'var(--bg-primary, #111111)', + '#111111': 'var(--bg-primary, #111111)', + '#111': 'var(--bg-primary, #111111)', + '#1a1a1a': 'var(--bg-secondary, #1a1a1a)', + '#222222': 'var(--bg-tertiary, #222222)', + '#222': 'var(--bg-tertiary, #222222)', + '#2a2a2a': 'var(--bg-tertiary, #2a2a2a)', + '#333333': 'var(--bg-quaternary, #333333)', + '#333': 'var(--bg-quaternary, #333333)', + '#3a3a3a': 'var(--bg-quaternary, #3a3a3a)', + '#444444': 'var(--text-disabled, #444444)', + '#444': 'var(--text-disabled, #444444)', + '#666666': 'var(--text-quaternary, #666666)', + '#666': 'var(--text-quaternary, #666666)', + '#888888': 'var(--text-tertiary, #888888)', + '#888': 'var(--text-tertiary, #888888)', + '#999999': 'var(--text-tertiary, #999999)', + '#999': 'var(--text-tertiary, #999999)', + '#aaaaaa': 'var(--text-secondary, #aaaaaa)', + '#aaa': 'var(--text-secondary, #aaaaaa)', + '#cccccc': 'var(--text-secondary, #cccccc)', + '#ccc': 'var(--text-secondary, #cccccc)', + '#dddddd': 'var(--border-secondary, #dddddd)', + '#ddd': 'var(--border-secondary, #dddddd)', + '#eeeeee': 'var(--border-primary, #eeeeee)', + '#eee': 'var(--border-primary, #eeeeee)', + '#ffffff': 'var(--text-primary, #ffffff)', + '#fff': 'var(--text-primary, #ffffff)' + }; + + // We only want to replace inside diff --git a/old-nuxt.config.ts b/old-nuxt.config.ts new file mode 100644 index 0000000..b5c3581 --- /dev/null +++ b/old-nuxt.config.ts @@ -0,0 +1,447 @@ +import wasm from 'vite-plugin-wasm' +import glsl from 'vite-plugin-glsl' +import { fileURLToPath } from 'url' +import { index, uniqueIndex } from 'drizzle-orm' + +const customSeoConfig: Record | null = (() => { + const raw = process.env.NUXT_PUBLIC_SEO_CONFIG + if (!raw) return null + try { + const parsed = JSON.parse(raw) + if (parsed && typeof parsed === 'object') return parsed as Record + return null + } catch { + return null + } +})() + +function readNumberEnv(name: string, defaultValue: number): number { + const raw = process.env[name] + if (raw === undefined || raw === '') return defaultValue + const num = Number(raw) + return Number.isFinite(num) ? num : defaultValue +} + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: '2026-01-30', + future: { + compatibilityVersion: 4 + }, + srcDir: 'app', + serverDir: fileURLToPath(new URL('./server', import.meta.url)), + dir: { + public: fileURLToPath(new URL('./public', import.meta.url)) + }, + devtools: { enabled: process.env.NODE_ENV === 'development' }, + devServer: { + host: '0.0.0.0', + port: 3000 + }, + modules: [ + '@nuxtjs/tailwindcss', + ...((process.env.NODE_ENV === 'development' || process.env.npm_lifecycle_event?.includes('lint')) + ? ['@nuxt/eslint'] + : []), + ...((process.env.PWA_ENABLED || 'false') === 'true' + ? ['@vite-pwa/nuxt'] + : []) + ], + + pwa: (process.env.PWA_ENABLED || 'false') === 'true' + ? { + registerType: 'autoUpdate', + manifest: { + name: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + short_name: process.env.NUXT_PUBLIC_SITE_TITLE || 'VoiceHub', + description: process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + display: (process.env.PWA_DISPLAY as any) || 'standalone', + background_color: process.env.PWA_BACKGROUND_COLOR || '#000000', + theme_color: process.env.PWA_THEME_COLOR || '#111111', + icons: [ + { + src: '/images/logo.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: '/images/logo.png', + sizes: '512x512', + type: 'image/png' + } + ] + }, + workbox: { + navigateFallback: '/', + globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,svg,ico,webp,woff2}'] + }, + client: { + installPrompt: true, + periodicSyncForUpdates: 3600 + }, + devOptions: { + enabled: process.env.NODE_ENV === 'development' + } + } + : {}, + + // 寮曞叆鍏ㄥ眬CSS + css: [ + '~/assets/css/variables.css', + '~/assets/css/components.css', + '~/assets/css/main.css', + '~/assets/css/transitions.css', + '~/assets/css/mobile-admin.css', + '~/assets/css/print-fix.css', + '~/assets/css/sf-pro-icons.css' + ], + + build: { + transpile: [] + }, + + // 瀹為獙鎬т紭鍖栧姛鑳? experimental: { + payloadExtraction: true, + renderJsonPayloads: true, + viewTransition: true + }, + + // 閰嶇疆杩愯鏃堕厤缃? runtimeConfig: { + // 鏈嶅姟鍣ㄧ鏈夐敭锛堜笉浼氭毚闇插埌瀹㈡埛绔級 + jwtSecret: process.env.JWT_SECRET || 'your-secret-key', + // Redis閰嶇疆锛堝彲閫夛級 + redisUrl: process.env.REDIS_URL || '', + redisKeyPrefix: process.env.REDIS_KEY_PREFIX || '', + // Sentry 鏈嶅姟绔厤缃紙涓嶄細鏆撮湶鍒板鎴风锛? sentry: { + serverDsn: process.env.SENTRY_SERVER_DSN || process.env.SENTRY_DSN || '', + dsn: process.env.SENTRY_DSN || '', + errorSampleRate: readNumberEnv('SENTRY_ERROR_SAMPLE_RATE', 1.0), + tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0), + replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0), + replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0), + release: process.env.SENTRY_RELEASE || '', + environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production' + }, + // 鍏叡閿紙浼氭毚闇插埌瀹㈡埛绔級 + public: { + host: process.env.NUXT_PUBLIC_HOST || '', + apiBase: '/api', + oauth: { + github: !!process.env.GITHUB_CLIENT_ID, + casdoor: !!process.env.CASDOOR_CLIENT_ID, + google: !!process.env.GOOGLE_CLIENT_ID, + aggregate: !!process.env.AGGREGATE_OAUTH_APP_ID + }, + siteTitle: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + siteLogo: process.env.NUXT_PUBLIC_SITE_LOGO || '', + siteDescription: + process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚', + isNetlify: process.env.NETLIFY === 'true', + // SEO 鑷畾涔夐厤缃紙閫氳繃 NUXT_PUBLIC_SEO_CONFIG 娉ㄥ叆 JSON 瀛楃涓诧級 + seoConfig: customSeoConfig ?? {}, + // 鍥惧舰楠岃瘉鐮佺浉鍏? captcha: { + enabled: (process.env.CAPTCHA_ENABLED || 'false') === 'true', + provider: process.env.CAPTCHA_PROVIDER || 'turnstile', + siteKey: process.env.CAPTCHA_SITE_KEY || '' + }, + // PWA 鐩稿叧缁撴瀯锛堟湰鍦颁笉鍚敤妯″潡锛屼絾淇濈暀閰嶇疆椤逛互渚夸笂灞傚簲鐢ㄤ娇鐢級 + pwa: { + enabled: (process.env.PWA_ENABLED || 'false') === 'true', + display: process.env.PWA_DISPLAY || 'standalone', + backgroundColor: process.env.PWA_BACKGROUND_COLOR || '#000000', + themeColor: process.env.PWA_THEME_COLOR || '#111111' + }, + // 涓婚閰嶇疆 + defaultTheme: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight', + enabledThemes: (process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight') + .split(',') + .map((s) => s.trim()) + .filter(Boolean), + // Sentry 瀹㈡埛绔厤缃紙浠呭叕寮€ DSN 涓庨噰鏍风巼锛? sentry: { + dsn: process.env.SENTRY_DSN || '', + tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0), + replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0), + replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0) + } + } + }, + + // 閰嶇疆鐜鍙橀噺 + app: { + head: { + title: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + htmlAttrs: { lang: 'zh-CN' }, + meta: [ + { charset: 'utf-8' }, + { + name: 'viewport', + content: + 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover' + }, + { + name: 'description', + content: + process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚' + }, + // 绉诲姩绔紭鍖? { name: 'theme-color', content: '#111111' }, + { + name: 'x-default-theme', + content: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight' + }, + { + name: 'x-enabled-themes', + content: process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight' + }, + { name: 'apple-mobile-web-app-capable', content: 'yes' }, + { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }, + { name: 'apple-mobile-web-app-title', content: 'VoiceHub绠$悊' }, + { name: 'mobile-web-app-capable', content: 'yes' }, + { name: 'format-detection', content: 'telephone=no' } + ], + script: [ + { + type: 'text/javascript', + tagPosition: 'head', + children: [ + `(function(){try{`, + ` var k='voicehub:theme', d=document, r=d.documentElement, s=null, eff=null, def=null, list=[];`, + ` try{`, + ` var mt=d.querySelector('meta[name="x-enabled-themes"]');`, + ` if(mt && mt.content){list=String(mt.content).split(',').map(function(x){return x.replace(/^\\s+|\\s+$/g,'')}).filter(Boolean)}`, + ` }catch(e){list=[]}`, + ` try{`, + ` var md=d.querySelector('meta[name="x-default-theme"]');`, + ` if(md && md.content){def=md.content}`, + ` }catch(e){def=null}`, + ` if(!def){def='ClassicLight'}`, + ` if(list.length===0){list=['ClassicLight','ModernLight']}`, + ` function valid(t){`, + ` if(list.indexOf(t)!==-1){return t}`, + ` if(list.indexOf(def)!==-1){return def}`, + ` return list[0]`, + ` }`, + ` try{s=localStorage.getItem(k)}catch(e){s=null}`, + ` if(!s){s=def}`, + ` else{s=valid(s)}`, + ` if(typeof s==='string' && (s==='System'||s==='system')){eff=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}`, + ` else if(typeof s==='string'){eff=s.endsWith('Dark')||s==='dark'?'dark':(s.endsWith('Light')||s==='light'?'light':s)}`, + ` else{eff='light'}`, + ` r.setAttribute('data-theme', (typeof s==='string'?s:eff));`, + ` if(eff==='dark'){r.classList.add('dark'); r.classList.remove('light'); r.style.colorScheme='dark'}`, + ` else if(eff==='light'){r.classList.add('light'); r.classList.remove('dark'); r.style.colorScheme='light'}`, + ` else{r.classList.remove('dark','light'); r.style.colorScheme='light dark'}`, + ` try{`, + ` var tm=d.querySelector('meta[name="theme-color"]');`, + ` if(tm){tm.content=(eff==='light'?'#f8fafc':'#111111')}`, + ` }catch(e){}`, + `}catch(e){}})();` + ].join('') + } + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, + { rel: 'preload', as: 'image', href: '/images/logo.png' }, + { rel: 'preload', as: 'image', href: '/images/site-logo.png' }, + { rel: 'preload', as: 'image', href: '/images/school-logo-white.png' }, + // 浼樺厛鍔犺浇甯歌瀛椾綋锛岀‘淇濋〉闈㈠揩閫熸樉绀? { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css' + }, + { + rel: 'stylesheet', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css' + }, + // 寤惰繜鍔犺浇鍏朵粬瀛楅噸锛岄伩鍏嶉樆濉為〉闈㈡覆鏌? { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Medium.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + }, + { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Semibold.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + }, + { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Bold.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + } + ] + } + }, + + features: { + inlineStyles: false + }, + + // TypeScript閰嶇疆 + typescript: { + strict: true + }, + + // 鏈嶅姟鍣ㄧ閰嶇疆 + nitro: { + compressPublicAssets: true, + preset: process.env.VERCEL + ? 'vercel' + : process.env.NETLIFY + ? 'netlify' + : process.env.NITRO_PRESET || 'node-server', + // 澧炲己閿欒澶勭悊鍜岀ǔ瀹氭€? experimental: { + wasm: true, + asyncContext: true + }, + timing: true, + // 澧炲姞璇锋眰瓒呮椂鏃堕棿 + routeRules: { + // 瀹屽叏绂佺敤鎵€鏈堿PI璺敱鐨勭紦瀛橈紝纭繚姣忔閮借姹傛暟鎹簱 + '/api/**': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0', + Connection: 'keep-alive' + } + }, + // 闈欐€佽祫婧愭枃浠剁紦瀛橀厤缃? '/_nuxt/**': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/assets/**': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/favicon.ico': { + headers: { + 'Cache-Control': 'public, max-age=86400' + } + }, + // 鍥剧墖銆丆SS銆丣S绛夐潤鎬佽祫婧愮紦瀛? '/**/*.{png,jpg,jpeg,gif,webp,svg,ico}': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/**/*.{css,js}': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + // 璁よ瘉鐩稿叧椤甸潰涓嶇紦瀛? '/login': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/dashboard': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/change-password': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/auth/**': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/notification-settings': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + } + }, + // 鏍规嵁閮ㄧ讲鐜璋冩暣閰嶇疆 + ...(process.env.VERCEL + ? { + // Vercel 鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? } + : process.env.NETLIFY + ? { + // Netlify 鐜锛氱‘淇?Drizzle 姝g‘鎵撳寘 + experimental: { + wasm: true + } + } + : { + // 鍏朵粬鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? }) + }, + + // Vite 閰嶇疆 + vite: { + build: { + chunkSizeWarningLimit: 2000, + cssCodeSplit: true, + rollupOptions: { + maxParallelFileOps: 2, + output: { + manualChunks(id) { + if (id.includes('node_modules/lucide-vue-next')) { + return 'lucide' + } + } + } + } + }, + resolve: { + alias: [ + { + find: '@applemusic-like-lyrics/core/style.css', + replacement: fileURLToPath( + new URL('./vendor/amll-core/src/styles/index.css', import.meta.url) + ) + }, + { + find: '@applemusic-like-lyrics/core', + replacement: fileURLToPath(new URL('./vendor/amll-core/src/index.ts', import.meta.url)) + } + ] + }, + plugins: [wasm(), glsl()], + optimizeDeps: { + include: ['drizzle-orm'], + exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric'] + }, + build: { + sourcemap: false, + rollupOptions: { + output: { + manualChunks(id) { + if (!id.includes('node_modules')) return + if (id.includes('lucide-vue-next')) return 'icons' + if (id.includes('@pixi')) return 'pixi' + if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine' + if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database' + if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office' + } + } + } + }, + // 娣诲姞 WASM 鏀寔閰嶇疆 + assetsInclude: ['**/*.wasm'], + // SSR閰嶇疆 + ssr: { + noExternal: process.env.NETLIFY + ? ['drizzle-orm', 'postgres'] + : process.env.VERCEL + ? [] + : ['drizzle-orm', 'postgres'] + } + } +}) diff --git a/old-nuxt2.config.ts b/old-nuxt2.config.ts new file mode 100644 index 0000000..41bc534 --- /dev/null +++ b/old-nuxt2.config.ts @@ -0,0 +1,433 @@ +import wasm from 'vite-plugin-wasm' +import glsl from 'vite-plugin-glsl' +import { fileURLToPath } from 'url' +import { index, uniqueIndex } from 'drizzle-orm' + +const customSeoConfig: Record | null = (() => { + const raw = process.env.NUXT_PUBLIC_SEO_CONFIG + if (!raw) return null + try { + const parsed = JSON.parse(raw) + if (parsed && typeof parsed === 'object') return parsed as Record + return null + } catch { + return null + } +})() + +function readNumberEnv(name: string, defaultValue: number): number { + const raw = process.env[name] + if (raw === undefined || raw === '') return defaultValue + const num = Number(raw) + return Number.isFinite(num) ? num : defaultValue +} + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: '2026-01-30', + future: { + compatibilityVersion: 4 + }, + srcDir: 'app', + serverDir: fileURLToPath(new URL('./server', import.meta.url)), + dir: { + public: fileURLToPath(new URL('./public', import.meta.url)) + }, + devtools: { enabled: process.env.NODE_ENV === 'development' }, + devServer: { + host: '0.0.0.0', + port: 3000 + }, + modules: [ + '@nuxtjs/tailwindcss', + ...((process.env.NODE_ENV === 'development' || process.env.npm_lifecycle_event?.includes('lint')) + ? ['@nuxt/eslint'] + : []), + ...((process.env.PWA_ENABLED || 'false') === 'true' + ? ['@vite-pwa/nuxt'] + : []) + ], + + pwa: (process.env.PWA_ENABLED || 'false') === 'true' + ? { + registerType: 'autoUpdate', + manifest: { + name: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + short_name: process.env.NUXT_PUBLIC_SITE_TITLE || 'VoiceHub', + description: process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + display: (process.env.PWA_DISPLAY as any) || 'standalone', + background_color: process.env.PWA_BACKGROUND_COLOR || '#000000', + theme_color: process.env.PWA_THEME_COLOR || '#111111', + icons: [ + { + src: '/images/logo.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: '/images/logo.png', + sizes: '512x512', + type: 'image/png' + } + ] + }, + workbox: { + navigateFallback: '/', + globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,svg,ico,webp,woff2}'] + }, + client: { + installPrompt: true, + periodicSyncForUpdates: 3600 + }, + devOptions: { + enabled: process.env.NODE_ENV === 'development' + } + } + : {}, + + // 寮曞叆鍏ㄥ眬CSS + css: [ + '~/assets/css/variables.css', + '~/assets/css/components.css', + '~/assets/css/main.css', + '~/assets/css/transitions.css', + '~/assets/css/mobile-admin.css', + '~/assets/css/print-fix.css', + '~/assets/css/sf-pro-icons.css' + ], + + build: { + transpile: [] + }, + + // 瀹為獙鎬т紭鍖栧姛鑳? experimental: { + payloadExtraction: true, + renderJsonPayloads: true, + viewTransition: true + }, + + // 閰嶇疆杩愯鏃堕厤缃? runtimeConfig: { + // 鏈嶅姟鍣ㄧ鏈夐敭锛堜笉浼氭毚闇插埌瀹㈡埛绔級 + jwtSecret: process.env.JWT_SECRET || 'your-secret-key', + // Redis閰嶇疆锛堝彲閫夛級 + redisUrl: process.env.REDIS_URL || '', + redisKeyPrefix: process.env.REDIS_KEY_PREFIX || '', + // Sentry 鏈嶅姟绔厤缃紙涓嶄細鏆撮湶鍒板鎴风锛? sentry: { + serverDsn: process.env.SENTRY_SERVER_DSN || process.env.SENTRY_DSN || '', + dsn: process.env.SENTRY_DSN || '', + errorSampleRate: readNumberEnv('SENTRY_ERROR_SAMPLE_RATE', 1.0), + tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0), + replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0), + replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0), + release: process.env.SENTRY_RELEASE || '', + environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production' + }, + // 鍏叡閿紙浼氭毚闇插埌瀹㈡埛绔級 + public: { + host: process.env.NUXT_PUBLIC_HOST || '', + apiBase: '/api', + oauth: { + github: !!process.env.GITHUB_CLIENT_ID, + casdoor: !!process.env.CASDOOR_CLIENT_ID, + google: !!process.env.GOOGLE_CLIENT_ID, + aggregate: !!process.env.AGGREGATE_OAUTH_APP_ID + }, + siteTitle: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + siteLogo: process.env.NUXT_PUBLIC_SITE_LOGO || '', + siteDescription: + process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚', + isNetlify: process.env.NETLIFY === 'true', + // SEO 鑷畾涔夐厤缃紙閫氳繃 NUXT_PUBLIC_SEO_CONFIG 娉ㄥ叆 JSON 瀛楃涓诧級 + seoConfig: customSeoConfig ?? {}, + // 鍥惧舰楠岃瘉鐮佺浉鍏? captcha: { + enabled: (process.env.CAPTCHA_ENABLED || 'false') === 'true', + provider: process.env.CAPTCHA_PROVIDER || 'turnstile', + siteKey: process.env.CAPTCHA_SITE_KEY || '' + }, + // PWA 鐩稿叧缁撴瀯锛堟湰鍦颁笉鍚敤妯″潡锛屼絾淇濈暀閰嶇疆椤逛互渚夸笂灞傚簲鐢ㄤ娇鐢級 + pwa: { + enabled: (process.env.PWA_ENABLED || 'false') === 'true', + display: process.env.PWA_DISPLAY || 'standalone', + backgroundColor: process.env.PWA_BACKGROUND_COLOR || '#000000', + themeColor: process.env.PWA_THEME_COLOR || '#111111' + }, + // 涓婚閰嶇疆 + defaultTheme: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight', + enabledThemes: (process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight') + .split(',') + .map((s) => s.trim()) + .filter(Boolean), + // Sentry 瀹㈡埛绔厤缃紙浠呭叕寮€ DSN 涓庨噰鏍风巼锛? sentry: { + dsn: process.env.SENTRY_DSN || '', + tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0), + replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0), + replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0) + } + } + }, + + // 閰嶇疆鐜鍙橀噺 + app: { + head: { + title: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?, + htmlAttrs: { lang: 'zh-CN' }, + meta: [ + { charset: 'utf-8' }, + { + name: 'viewport', + content: + 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover' + }, + { + name: 'description', + content: + process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚' + }, + // 绉诲姩绔紭鍖? { name: 'theme-color', content: '#111111' }, + { + name: 'x-default-theme', + content: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'System' + }, + { name: 'apple-mobile-web-app-capable', content: 'yes' }, + { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }, + { name: 'apple-mobile-web-app-title', content: 'VoiceHub绠$悊' }, + { name: 'mobile-web-app-capable', content: 'yes' }, + { name: 'format-detection', content: 'telephone=no' } + ], + script: [ + { + type: 'text/javascript', + tagPosition: 'head', + children: [ + `(function(){try{`, + ` var k='voicehub:theme', d=document, r=d.documentElement, s=null, eff=null;`, + ` try{s=localStorage.getItem(k)}catch(e){s=null}`, + ` if(!s){`, + ` try{`, + ` var meta=document.querySelector('meta[name="x-default-theme"]');`, + ` if(meta && meta.content){s=meta.content}`, + ` }catch(e){s=null}`, + ` if(!s){s='System'}`, + ` }`, + ` if(s==='System'||s==='system'){eff=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}`, + ` else if(typeof s==='string'){eff=s.endsWith('Dark')||s==='dark'?'dark':(s.endsWith('Light')||s==='light'?'light':s)}`, + ` else{eff='dark'}`, + ` r.setAttribute('data-theme', (typeof s==='string'?s:eff));`, + ` if(eff==='dark'){r.classList.add('dark'); r.classList.remove('light'); r.style.colorScheme='dark'}`, + ` else if(eff==='light'){r.classList.add('light'); r.classList.remove('dark'); r.style.colorScheme='light'}`, + ` else{r.classList.remove('dark','light'); r.style.colorScheme='light dark'}`, + ` try{`, + ` var tm=d.querySelector('meta[name="theme-color"]');`, + ` if(tm){tm.content=(eff==='light'?'#f8fafc':'#111111')}`, + ` }catch(e){}`, + `}catch(e){}})();` + ].join('') + } + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, + { rel: 'preload', as: 'image', href: '/images/logo.png' }, + { rel: 'preload', as: 'image', href: '/images/site-logo.png' }, + { rel: 'preload', as: 'image', href: '/images/school-logo-white.png' }, + // 浼樺厛鍔犺浇甯歌瀛椾綋锛岀‘淇濋〉闈㈠揩閫熸樉绀? { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css' + }, + { + rel: 'stylesheet', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css' + }, + // 寤惰繜鍔犺浇鍏朵粬瀛楅噸锛岄伩鍏嶉樆濉為〉闈㈡覆鏌? { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Medium.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + }, + { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Semibold.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + }, + { + rel: 'preload', + as: 'style', + href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Bold.min.css', + onload: "this.onload=null;this.rel='stylesheet'" + } + ] + } + }, + + features: { + inlineStyles: false + }, + + // TypeScript閰嶇疆 + typescript: { + strict: true + }, + + // 鏈嶅姟鍣ㄧ閰嶇疆 + nitro: { + compressPublicAssets: true, + preset: process.env.VERCEL + ? 'vercel' + : process.env.NETLIFY + ? 'netlify' + : process.env.NITRO_PRESET || 'node-server', + // 澧炲己閿欒澶勭悊鍜岀ǔ瀹氭€? experimental: { + wasm: true, + asyncContext: true + }, + timing: true, + // 澧炲姞璇锋眰瓒呮椂鏃堕棿 + routeRules: { + // 瀹屽叏绂佺敤鎵€鏈堿PI璺敱鐨勭紦瀛橈紝纭繚姣忔閮借姹傛暟鎹簱 + '/api/**': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0', + Connection: 'keep-alive' + } + }, + // 闈欐€佽祫婧愭枃浠剁紦瀛橀厤缃? '/_nuxt/**': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/assets/**': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/favicon.ico': { + headers: { + 'Cache-Control': 'public, max-age=86400' + } + }, + // 鍥剧墖銆丆SS銆丣S绛夐潤鎬佽祫婧愮紦瀛? '/**/*.{png,jpg,jpeg,gif,webp,svg,ico}': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + '/**/*.{css,js}': { + headers: { + 'Cache-Control': 'public, max-age=31536000, immutable' + } + }, + // 璁よ瘉鐩稿叧椤甸潰涓嶇紦瀛? '/login': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/dashboard': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/change-password': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/auth/**': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + }, + '/notification-settings': { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + Pragma: 'no-cache', + Expires: '0' + } + } + }, + // 鏍规嵁閮ㄧ讲鐜璋冩暣閰嶇疆 + ...(process.env.VERCEL + ? { + // Vercel 鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? } + : process.env.NETLIFY + ? { + // Netlify 鐜锛氱‘淇?Drizzle 姝g‘鎵撳寘 + experimental: { + wasm: true + } + } + : { + // 鍏朵粬鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? }) + }, + + // Vite 閰嶇疆 + vite: { + build: { + chunkSizeWarningLimit: 2000, + cssCodeSplit: true, + rollupOptions: { + maxParallelFileOps: 2, + output: { + manualChunks(id) { + if (id.includes('node_modules/lucide-vue-next')) { + return 'lucide' + } + } + } + } + }, + resolve: { + alias: [ + { + find: '@applemusic-like-lyrics/core/style.css', + replacement: fileURLToPath( + new URL('./vendor/amll-core/src/styles/index.css', import.meta.url) + ) + }, + { + find: '@applemusic-like-lyrics/core', + replacement: fileURLToPath(new URL('./vendor/amll-core/src/index.ts', import.meta.url)) + } + ] + }, + plugins: [wasm(), glsl()], + optimizeDeps: { + include: ['drizzle-orm'], + exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric'] + }, + build: { + sourcemap: false, + rollupOptions: { + output: { + manualChunks(id) { + if (!id.includes('node_modules')) return + if (id.includes('lucide-vue-next')) return 'icons' + if (id.includes('@pixi')) return 'pixi' + if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine' + if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database' + if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office' + } + } + } + }, + // 娣诲姞 WASM 鏀寔閰嶇疆 + assetsInclude: ['**/*.wasm'], + // SSR閰嶇疆 + ssr: { + noExternal: process.env.NETLIFY + ? ['drizzle-orm', 'postgres'] + : process.env.VERCEL + ? [] + : ['drizzle-orm', 'postgres'] + } + } +}) diff --git a/old-variables.css b/old-variables.css new file mode 100644 index 0000000..3fe2829 --- /dev/null +++ b/old-variables.css @@ -0,0 +1,556 @@ +/* 鍏ㄥ眬CSS鍙橀噺 - 缁熶竴閰嶈壊鏂规 */ +:root { + /* 涓昏壊璋?*/ + --primary: #3b82f6; + --primary-hover: #2563eb; + --primary-light: rgba(59, 130, 246, 0.1); + --primary-border: rgba(59, 130, 246, 0.2); + + /* 鎴愬姛鑹?*/ + --success: #10b981; + --success-hover: #059669; + --success-light: rgba(16, 185, 129, 0.1); + --success-border: rgba(16, 185, 129, 0.2); + + /* 璀﹀憡鑹?*/ + --warning: #f59e0b; + --warning-hover: #d97706; + --warning-light: rgba(245, 158, 11, 0.1); + --warning-border: rgba(245, 158, 11, 0.2); + + /* 閿欒鑹?*/ + --error: #ef4444; + --error-hover: #dc2626; + --error-light: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.2); + + /* 淇℃伅鑹?*/ + --info: #3b82f6; + --info-hover: #2563eb; + --info-light: rgba(59, 130, 246, 0.1); + --info-border: rgba(59, 130, 246, 0.2); + + /* 鑳屾櫙鑹?*/ + --bg-primary: #111111; + --bg-secondary: #1a1a1a; + --bg-tertiary: #2a2a2a; + --bg-quaternary: #3a3a3a; + --bg-hover: #1f1f1f; + + /* 鏂囧瓧棰滆壊 */ + --text-primary: #ffffff; + --text-secondary: #cccccc; + --text-tertiary: #888888; + --text-quaternary: #666666; + --text-disabled: #444444; + + /* 杈规棰滆壊 */ + --border-primary: #1f1f1f; + --border-secondary: #2a2a2a; + --border-tertiary: #3a3a3a; + --border-quaternary: #4a4a4a; + + /* 闃村奖 */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1); + + /* 鍦嗚 */ + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius-2xl: 16px; + + /* 闂磋窛 */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 12px; + --spacing-lg: 16px; + --spacing-xl: 20px; + --spacing-2xl: 24px; + --spacing-3xl: 32px; + + /* 瀛椾綋澶у皬 */ + --text-xs: 12px; + --text-sm: 14px; + --text-base: 16px; + --text-lg: 18px; + --text-xl: 20px; + --text-2xl: 24px; + --text-3xl: 30px; + + /* 瀛椾綋绮楃粏 */ + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* 杩囨浮鍔ㄧ敾 */ + --transition-fast: 0.15s ease; + --transition-normal: 0.2s ease; + --transition-slow: 0.3s ease; + + /* Z-index灞傜骇 */ + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; + --z-toast: 800; + + /* 鐗规畩鐘舵€侀鑹?*/ + --status-pending: #fbbf24; + --status-pending-light: rgba(251, 191, 36, 0.1); + --status-approved: #10b981; + --status-approved-light: rgba(16, 185, 129, 0.1); + --status-played: #6b7280; + --status-played-light: rgba(107, 114, 128, 0.1); + + /* 鎶曠エ鐩稿叧棰滆壊 */ + --vote-heart: #ef4444; + --vote-heart-hover: #dc2626; + --vote-count: var(--text-tertiary); + + /* 鎷栨嫿鐘舵€侀鑹?*/ + --drag-over: var(--primary-light); + --drag-over-border: var(--primary); + --dragging-opacity: 0.5; + + /* 琛ㄦ牸棰滆壊 */ + --table-header-bg: var(--bg-tertiary); + --table-row-hover: var(--bg-hover); + --table-border: var(--border-secondary); + --table-selected: var(--primary-light); + + /* 鍗$墖棰滆壊 */ + --card-bg: var(--bg-secondary); + --card-border: var(--border-secondary); + --card-hover-border: var(--border-tertiary); + --card-shadow: var(--shadow-md); + + /* 鎸夐挳棰滆壊 */ + --btn-primary-bg: var(--primary); + --btn-primary-hover: var(--primary-hover); + --btn-primary-text: var(--text-primary); + --btn-primary-border: var(--primary-border); + + --btn-secondary-bg: #3a3a3a; + --btn-secondary-hover: #4a4a4a; + --btn-secondary-text: var(--text-primary); + --btn-secondary-border: #555555; + + --btn-success-bg: var(--success); + --btn-success-hover: var(--success-hover); + --btn-success-text: var(--text-primary); + --btn-success-border: var(--success-border); + + --btn-warning-bg: var(--warning); + --btn-warning-hover: var(--warning-hover); + --btn-warning-text: var(--text-primary); + --btn-warning-border: var(--warning-border); + + --btn-error-bg: var(--error); + --btn-error-hover: var(--error-hover); + --btn-error-text: var(--text-primary); + --btn-error-border: var(--error-border); + + --btn-disabled-bg: var(--bg-tertiary); + --btn-disabled-text: var(--text-disabled); + + /* 杈撳叆妗嗛鑹?*/ + --input-bg: var(--bg-tertiary); + --input-border: var(--border-tertiary); + --input-border-focus: var(--primary); + --input-text: var(--text-primary); + --input-placeholder: var(--text-quaternary); + --input-shadow-focus: 0 0 0 3px var(--primary-light); + + /* 妯℃€佹棰滆壊 */ + --modal-backdrop: rgba(0, 0, 0, 0.7); + --modal-bg: var(--bg-secondary); + --modal-border: var(--border-secondary); + --modal-header-border: var(--border-secondary); + --modal-footer-border: var(--border-secondary); + + /* 閫氱煡棰滆壊 */ + --notification-success-bg: var(--success-light); + --notification-success-border: var(--success-border); + --notification-success-text: var(--success); + + --notification-warning-bg: var(--warning-light); + --notification-warning-border: var(--warning-border); + --notification-warning-text: var(--warning); + + --notification-error-bg: var(--error-light); + --notification-error-border: var(--error-border); + --notification-error-text: var(--error); + + --notification-info-bg: var(--info-light); + --notification-info-border: var(--info-border); + --notification-info-text: var(--info); + + /* 鍔犺浇鍔ㄧ敾棰滆壊 */ + --spinner-primary: var(--primary); + --spinner-secondary: var(--bg-tertiary); + + /* 婊氬姩鏉¢鑹?*/ + --scrollbar-track: var(--bg-tertiary); + --scrollbar-thumb: var(--bg-quaternary); + --scrollbar-thumb-hover: #4a4a4a; +} + +:root[data-theme='light'], +:root[data-theme='ClassicLight'], +:root[data-theme='ModernLight'] { + --bg-primary: #f8fafc; + --bg-secondary: #f1f5f9; + --bg-tertiary: #e2e8f0; + --bg-quaternary: #cbd5e1; + --bg-hover: #e2e8f0; + + --text-primary: #0f172a; + --text-secondary: #334155; + --text-tertiary: #475569; + --text-quaternary: #64748b; + --text-disabled: #94a3b8; + + --border-primary: #e2e8f0; + --border-secondary: #cbd5e1; + --border-tertiary: #94a3b8; + --border-quaternary: #64748b; + + --modal-backdrop: rgba(0, 0, 0, 0.4); + --scrollbar-thumb-hover: #94a3b8; + + --btn-secondary-bg: #f1f5f9; + --btn-secondary-hover: #e2e8f0; + --btn-secondary-text: #0f172a; + --btn-secondary-border: #cbd5e1; + + --card-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +:root[data-theme='ClassicLight'] { + --primary: #1e40af; + --primary-hover: #1e3a8a; + --primary-light: rgba(30, 64, 175, 0.08); + --primary-border: rgba(30, 64, 175, 0.15); + + --bg-primary: #ffffff; + --bg-secondary: #f5f7fa; + --bg-tertiary: #edf0f5; + --bg-quaternary: #d9dee7; + --bg-hover: #f0f3f8; + + --success: #059669; + --success-hover: #047857; + --success-light: rgba(5, 150, 105, 0.08); + --success-border: rgba(5, 150, 105, 0.15); + + --warning: #d97706; + --warning-hover: #b45309; + --warning-light: rgba(217, 119, 6, 0.08); + --warning-border: rgba(217, 119, 6, 0.15); + + --error: #dc2626; + --error-hover: #b91c1c; + --error-light: rgba(220, 38, 38, 0.08); + --error-border: rgba(220, 38, 38, 0.15); + + --info: #2563eb; + --info-hover: #1d4ed8; + --info-light: rgba(37, 99, 235, 0.08); + --info-border: rgba(37, 99, 235, 0.15); + + --text-primary: #1a2332; + --text-secondary: #4a5568; + --text-tertiary: #6b7280; + --text-quaternary: #9ca3af; + --text-disabled: #c5cbd3; + + --border-primary: #e5e7eb; + --border-secondary: #d1d5db; + --border-tertiary: #9ca3af; + --border-quaternary: #6b7280; + + --input-bg: #ffffff; + --input-border: #d1d5db; + --input-border-focus: #1e40af; + --input-text: #1a2332; + --input-placeholder: #9ca3af; + --input-shadow-focus: 0 0 0 3px rgba(30, 64, 175, 0.1); + + --card-bg: #ffffff; + --card-border: #e5e7eb; + --card-hover-border: #d1d5db; + + --table-header-bg: #f5f7fa; + --table-row-hover: #f9fafb; + --table-border: #e5e7eb; + --table-selected: rgba(30, 64, 175, 0.06); + + --scrollbar-track: #f1f5f9; + --scrollbar-thumb: #cbd5e1; + --scrollbar-thumb-hover: #94a3b8; + + --status-pending: #ca8a04; + --status-pending-light: rgba(202, 138, 4, 0.08); + --status-approved: #059669; + --status-approved-light: rgba(5, 150, 105, 0.08); + --status-played: #6b7280; + --status-played-light: rgba(107, 114, 128, 0.08); + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -4px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.06), 0 8px 10px -6px rgba(0, 0, 0, 0.04); + + --radius-sm: 3px; + --radius-md: 5px; + --radius-lg: 6px; + --radius-xl: 8px; + --radius-2xl: 10px; +} + +:root[data-theme='ModernLight'] { + --primary: #4f46e5; + --primary-hover: #4338ca; + --primary-light: rgba(79, 70, 229, 0.1); + --primary-border: rgba(79, 70, 229, 0.2); + + --bg-primary: #fafafa; + --bg-secondary: #f4f4f5; + --bg-tertiary: #e4e4e7; + --bg-quaternary: #d4d4d8; + --bg-hover: #eeeeef; + + --success: #16a34a; + --success-hover: #15803d; + --success-light: rgba(22, 163, 74, 0.1); + --success-border: rgba(22, 163, 74, 0.2); + + --warning: #ea580c; + --warning-hover: #c2410c; + --warning-light: rgba(234, 88, 12, 0.1); + --warning-border: rgba(234, 88, 12, 0.2); + + --error: #e11d48; + --error-hover: #be123c; + --error-light: rgba(225, 29, 72, 0.1); + --error-border: rgba(225, 29, 72, 0.2); + + --info: #0ea5e9; + --info-hover: #0284c7; + --info-light: rgba(14, 165, 233, 0.1); + --info-border: rgba(14, 165, 233, 0.2); + + --text-primary: #09090b; + --text-secondary: #3f3f46; + --text-tertiary: #71717a; + --text-quaternary: #a1a1aa; + --text-disabled: #d4d4d8; + + --border-primary: #e4e4e7; + --border-secondary: #d4d4d8; + --border-tertiary: #a1a1aa; + --border-quaternary: #71717a; + + --input-bg: #ffffff; + --input-border: #e4e4e7; + --input-border-focus: #4f46e5; + --input-text: #09090b; + --input-placeholder: #a1a1aa; + --input-shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.12); + + --card-bg: #ffffff; + --card-border: #e4e4e7; + --card-hover-border: #d4d4d8; + + --table-header-bg: #f4f4f5; + --table-row-hover: #fafafa; + --table-border: #e4e4e7; + --table-selected: rgba(79, 70, 229, 0.08); + + --scrollbar-track: #f4f4f5; + --scrollbar-thumb: #d4d4d8; + --scrollbar-thumb-hover: #a1a1aa; + + --status-pending: #eab308; + --status-pending-light: rgba(234, 179, 8, 0.12); + --status-approved: #22c55e; + --status-approved-light: rgba(34, 197, 94, 0.12); + --status-played: #71717a; + --status-played-light: rgba(113, 113, 122, 0.12); + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.03); + --shadow-md: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.03); + --shadow-lg: 0 8px 24px -4px rgba(0, 0, 0, 0.06), 0 2px 8px -4px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 16px 40px -8px rgba(0, 0, 0, 0.08), 0 4px 16px -8px rgba(0, 0, 0, 0.05); + + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 10px; + --radius-2xl: 12px; +} + +/* 娣辫壊涓婚鐗瑰畾璋冩暣 */ +@media (prefers-color-scheme: dark) { + :root { + /* 鍦ㄦ繁鑹叉ā寮忎笅鍙互杩涜寰皟 */ + } +} + +/* 楂樺姣斿害妯″紡鏀寔 */ +@media (prefers-contrast: high) { + :root { + --border-primary: #444444; + --border-secondary: #555555; + --border-tertiary: #666666; + --text-tertiary: #aaaaaa; + --text-quaternary: #888888; + } +} + +/* 鍑忓皯鍔ㄧ敾妯″紡鏀寔 */ +@media (prefers-reduced-motion: reduce) { + :root { + --transition-fast: 0s; + --transition-normal: 0s; + --transition-slow: 0s; + } +} + +/* 鍏ㄥ眬婊氬姩鏉℃牱寮?*/ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +*::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +*::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: var(--radius-md); +} + +*::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* 鍏ㄥ眬閫夋嫨鏂囨湰鏍峰紡 */ +::selection { + background: var(--primary-light); + color: var(--text-primary); +} + +/* 鍏ㄥ眬鐒︾偣鏍峰紡 */ +*:focus { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +*:focus:not(:focus-visible) { + outline: none; +} + +/* 鍏ㄥ眬绂佺敤鐘舵€佹牱寮?*/ +*:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +/* 鍏ㄥ眬閾炬帴鏍峰紡 */ +a { + color: var(--primary); + text-decoration: none; + transition: color var(--transition-normal); +} + +a:hover { + color: var(--primary-hover); +} + +/* 鍏ㄥ眬鎸夐挳閲嶇疆 */ +button { + border: none; + background: none; + cursor: pointer; + font-family: inherit; +} + +* { + -webkit-tap-highlight-color: transparent; +} + +button, +a, +input, +textarea { + -webkit-tap-highlight-color: transparent; +} + +@media (hover: none) and (pointer: coarse) { + *:focus { + outline: none !important; + } + button, + a { + -webkit-user-select: none; + user-select: none; + } +} + +/* 鍏ㄥ眬杈撳叆妗嗛噸缃?*/ +input, +textarea, +select { + font-family: inherit; + font-size: inherit; +} + +/* 鍏ㄥ眬鍥剧墖鏍峰紡 */ +img { + max-width: 100%; + height: auto; +} + +/* 鍏ㄥ眬琛ㄦ牸鏍峰紡 */ +table { + border-collapse: collapse; + width: 100%; +} + +/* 鍏ㄥ眬浠g爜鏍峰紡 */ +code, +pre { + font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); +} + +code { + padding: 2px 4px; + font-size: 0.875em; +} + +pre { + padding: var(--spacing-lg); + overflow-x: auto; +} + +pre code { + padding: 0; + background: none; +} diff --git a/old-variables2.css b/old-variables2.css new file mode 100644 index 0000000..3fe2829 --- /dev/null +++ b/old-variables2.css @@ -0,0 +1,556 @@ +/* 鍏ㄥ眬CSS鍙橀噺 - 缁熶竴閰嶈壊鏂规 */ +:root { + /* 涓昏壊璋?*/ + --primary: #3b82f6; + --primary-hover: #2563eb; + --primary-light: rgba(59, 130, 246, 0.1); + --primary-border: rgba(59, 130, 246, 0.2); + + /* 鎴愬姛鑹?*/ + --success: #10b981; + --success-hover: #059669; + --success-light: rgba(16, 185, 129, 0.1); + --success-border: rgba(16, 185, 129, 0.2); + + /* 璀﹀憡鑹?*/ + --warning: #f59e0b; + --warning-hover: #d97706; + --warning-light: rgba(245, 158, 11, 0.1); + --warning-border: rgba(245, 158, 11, 0.2); + + /* 閿欒鑹?*/ + --error: #ef4444; + --error-hover: #dc2626; + --error-light: rgba(239, 68, 68, 0.1); + --error-border: rgba(239, 68, 68, 0.2); + + /* 淇℃伅鑹?*/ + --info: #3b82f6; + --info-hover: #2563eb; + --info-light: rgba(59, 130, 246, 0.1); + --info-border: rgba(59, 130, 246, 0.2); + + /* 鑳屾櫙鑹?*/ + --bg-primary: #111111; + --bg-secondary: #1a1a1a; + --bg-tertiary: #2a2a2a; + --bg-quaternary: #3a3a3a; + --bg-hover: #1f1f1f; + + /* 鏂囧瓧棰滆壊 */ + --text-primary: #ffffff; + --text-secondary: #cccccc; + --text-tertiary: #888888; + --text-quaternary: #666666; + --text-disabled: #444444; + + /* 杈规棰滆壊 */ + --border-primary: #1f1f1f; + --border-secondary: #2a2a2a; + --border-tertiary: #3a3a3a; + --border-quaternary: #4a4a4a; + + /* 闃村奖 */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1); + + /* 鍦嗚 */ + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius-2xl: 16px; + + /* 闂磋窛 */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 12px; + --spacing-lg: 16px; + --spacing-xl: 20px; + --spacing-2xl: 24px; + --spacing-3xl: 32px; + + /* 瀛椾綋澶у皬 */ + --text-xs: 12px; + --text-sm: 14px; + --text-base: 16px; + --text-lg: 18px; + --text-xl: 20px; + --text-2xl: 24px; + --text-3xl: 30px; + + /* 瀛椾綋绮楃粏 */ + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* 杩囨浮鍔ㄧ敾 */ + --transition-fast: 0.15s ease; + --transition-normal: 0.2s ease; + --transition-slow: 0.3s ease; + + /* Z-index灞傜骇 */ + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; + --z-toast: 800; + + /* 鐗规畩鐘舵€侀鑹?*/ + --status-pending: #fbbf24; + --status-pending-light: rgba(251, 191, 36, 0.1); + --status-approved: #10b981; + --status-approved-light: rgba(16, 185, 129, 0.1); + --status-played: #6b7280; + --status-played-light: rgba(107, 114, 128, 0.1); + + /* 鎶曠エ鐩稿叧棰滆壊 */ + --vote-heart: #ef4444; + --vote-heart-hover: #dc2626; + --vote-count: var(--text-tertiary); + + /* 鎷栨嫿鐘舵€侀鑹?*/ + --drag-over: var(--primary-light); + --drag-over-border: var(--primary); + --dragging-opacity: 0.5; + + /* 琛ㄦ牸棰滆壊 */ + --table-header-bg: var(--bg-tertiary); + --table-row-hover: var(--bg-hover); + --table-border: var(--border-secondary); + --table-selected: var(--primary-light); + + /* 鍗$墖棰滆壊 */ + --card-bg: var(--bg-secondary); + --card-border: var(--border-secondary); + --card-hover-border: var(--border-tertiary); + --card-shadow: var(--shadow-md); + + /* 鎸夐挳棰滆壊 */ + --btn-primary-bg: var(--primary); + --btn-primary-hover: var(--primary-hover); + --btn-primary-text: var(--text-primary); + --btn-primary-border: var(--primary-border); + + --btn-secondary-bg: #3a3a3a; + --btn-secondary-hover: #4a4a4a; + --btn-secondary-text: var(--text-primary); + --btn-secondary-border: #555555; + + --btn-success-bg: var(--success); + --btn-success-hover: var(--success-hover); + --btn-success-text: var(--text-primary); + --btn-success-border: var(--success-border); + + --btn-warning-bg: var(--warning); + --btn-warning-hover: var(--warning-hover); + --btn-warning-text: var(--text-primary); + --btn-warning-border: var(--warning-border); + + --btn-error-bg: var(--error); + --btn-error-hover: var(--error-hover); + --btn-error-text: var(--text-primary); + --btn-error-border: var(--error-border); + + --btn-disabled-bg: var(--bg-tertiary); + --btn-disabled-text: var(--text-disabled); + + /* 杈撳叆妗嗛鑹?*/ + --input-bg: var(--bg-tertiary); + --input-border: var(--border-tertiary); + --input-border-focus: var(--primary); + --input-text: var(--text-primary); + --input-placeholder: var(--text-quaternary); + --input-shadow-focus: 0 0 0 3px var(--primary-light); + + /* 妯℃€佹棰滆壊 */ + --modal-backdrop: rgba(0, 0, 0, 0.7); + --modal-bg: var(--bg-secondary); + --modal-border: var(--border-secondary); + --modal-header-border: var(--border-secondary); + --modal-footer-border: var(--border-secondary); + + /* 閫氱煡棰滆壊 */ + --notification-success-bg: var(--success-light); + --notification-success-border: var(--success-border); + --notification-success-text: var(--success); + + --notification-warning-bg: var(--warning-light); + --notification-warning-border: var(--warning-border); + --notification-warning-text: var(--warning); + + --notification-error-bg: var(--error-light); + --notification-error-border: var(--error-border); + --notification-error-text: var(--error); + + --notification-info-bg: var(--info-light); + --notification-info-border: var(--info-border); + --notification-info-text: var(--info); + + /* 鍔犺浇鍔ㄧ敾棰滆壊 */ + --spinner-primary: var(--primary); + --spinner-secondary: var(--bg-tertiary); + + /* 婊氬姩鏉¢鑹?*/ + --scrollbar-track: var(--bg-tertiary); + --scrollbar-thumb: var(--bg-quaternary); + --scrollbar-thumb-hover: #4a4a4a; +} + +:root[data-theme='light'], +:root[data-theme='ClassicLight'], +:root[data-theme='ModernLight'] { + --bg-primary: #f8fafc; + --bg-secondary: #f1f5f9; + --bg-tertiary: #e2e8f0; + --bg-quaternary: #cbd5e1; + --bg-hover: #e2e8f0; + + --text-primary: #0f172a; + --text-secondary: #334155; + --text-tertiary: #475569; + --text-quaternary: #64748b; + --text-disabled: #94a3b8; + + --border-primary: #e2e8f0; + --border-secondary: #cbd5e1; + --border-tertiary: #94a3b8; + --border-quaternary: #64748b; + + --modal-backdrop: rgba(0, 0, 0, 0.4); + --scrollbar-thumb-hover: #94a3b8; + + --btn-secondary-bg: #f1f5f9; + --btn-secondary-hover: #e2e8f0; + --btn-secondary-text: #0f172a; + --btn-secondary-border: #cbd5e1; + + --card-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +:root[data-theme='ClassicLight'] { + --primary: #1e40af; + --primary-hover: #1e3a8a; + --primary-light: rgba(30, 64, 175, 0.08); + --primary-border: rgba(30, 64, 175, 0.15); + + --bg-primary: #ffffff; + --bg-secondary: #f5f7fa; + --bg-tertiary: #edf0f5; + --bg-quaternary: #d9dee7; + --bg-hover: #f0f3f8; + + --success: #059669; + --success-hover: #047857; + --success-light: rgba(5, 150, 105, 0.08); + --success-border: rgba(5, 150, 105, 0.15); + + --warning: #d97706; + --warning-hover: #b45309; + --warning-light: rgba(217, 119, 6, 0.08); + --warning-border: rgba(217, 119, 6, 0.15); + + --error: #dc2626; + --error-hover: #b91c1c; + --error-light: rgba(220, 38, 38, 0.08); + --error-border: rgba(220, 38, 38, 0.15); + + --info: #2563eb; + --info-hover: #1d4ed8; + --info-light: rgba(37, 99, 235, 0.08); + --info-border: rgba(37, 99, 235, 0.15); + + --text-primary: #1a2332; + --text-secondary: #4a5568; + --text-tertiary: #6b7280; + --text-quaternary: #9ca3af; + --text-disabled: #c5cbd3; + + --border-primary: #e5e7eb; + --border-secondary: #d1d5db; + --border-tertiary: #9ca3af; + --border-quaternary: #6b7280; + + --input-bg: #ffffff; + --input-border: #d1d5db; + --input-border-focus: #1e40af; + --input-text: #1a2332; + --input-placeholder: #9ca3af; + --input-shadow-focus: 0 0 0 3px rgba(30, 64, 175, 0.1); + + --card-bg: #ffffff; + --card-border: #e5e7eb; + --card-hover-border: #d1d5db; + + --table-header-bg: #f5f7fa; + --table-row-hover: #f9fafb; + --table-border: #e5e7eb; + --table-selected: rgba(30, 64, 175, 0.06); + + --scrollbar-track: #f1f5f9; + --scrollbar-thumb: #cbd5e1; + --scrollbar-thumb-hover: #94a3b8; + + --status-pending: #ca8a04; + --status-pending-light: rgba(202, 138, 4, 0.08); + --status-approved: #059669; + --status-approved-light: rgba(5, 150, 105, 0.08); + --status-played: #6b7280; + --status-played-light: rgba(107, 114, 128, 0.08); + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -4px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.06), 0 8px 10px -6px rgba(0, 0, 0, 0.04); + + --radius-sm: 3px; + --radius-md: 5px; + --radius-lg: 6px; + --radius-xl: 8px; + --radius-2xl: 10px; +} + +:root[data-theme='ModernLight'] { + --primary: #4f46e5; + --primary-hover: #4338ca; + --primary-light: rgba(79, 70, 229, 0.1); + --primary-border: rgba(79, 70, 229, 0.2); + + --bg-primary: #fafafa; + --bg-secondary: #f4f4f5; + --bg-tertiary: #e4e4e7; + --bg-quaternary: #d4d4d8; + --bg-hover: #eeeeef; + + --success: #16a34a; + --success-hover: #15803d; + --success-light: rgba(22, 163, 74, 0.1); + --success-border: rgba(22, 163, 74, 0.2); + + --warning: #ea580c; + --warning-hover: #c2410c; + --warning-light: rgba(234, 88, 12, 0.1); + --warning-border: rgba(234, 88, 12, 0.2); + + --error: #e11d48; + --error-hover: #be123c; + --error-light: rgba(225, 29, 72, 0.1); + --error-border: rgba(225, 29, 72, 0.2); + + --info: #0ea5e9; + --info-hover: #0284c7; + --info-light: rgba(14, 165, 233, 0.1); + --info-border: rgba(14, 165, 233, 0.2); + + --text-primary: #09090b; + --text-secondary: #3f3f46; + --text-tertiary: #71717a; + --text-quaternary: #a1a1aa; + --text-disabled: #d4d4d8; + + --border-primary: #e4e4e7; + --border-secondary: #d4d4d8; + --border-tertiary: #a1a1aa; + --border-quaternary: #71717a; + + --input-bg: #ffffff; + --input-border: #e4e4e7; + --input-border-focus: #4f46e5; + --input-text: #09090b; + --input-placeholder: #a1a1aa; + --input-shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.12); + + --card-bg: #ffffff; + --card-border: #e4e4e7; + --card-hover-border: #d4d4d8; + + --table-header-bg: #f4f4f5; + --table-row-hover: #fafafa; + --table-border: #e4e4e7; + --table-selected: rgba(79, 70, 229, 0.08); + + --scrollbar-track: #f4f4f5; + --scrollbar-thumb: #d4d4d8; + --scrollbar-thumb-hover: #a1a1aa; + + --status-pending: #eab308; + --status-pending-light: rgba(234, 179, 8, 0.12); + --status-approved: #22c55e; + --status-approved-light: rgba(34, 197, 94, 0.12); + --status-played: #71717a; + --status-played-light: rgba(113, 113, 122, 0.12); + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.03); + --shadow-md: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.03); + --shadow-lg: 0 8px 24px -4px rgba(0, 0, 0, 0.06), 0 2px 8px -4px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 16px 40px -8px rgba(0, 0, 0, 0.08), 0 4px 16px -8px rgba(0, 0, 0, 0.05); + + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 10px; + --radius-2xl: 12px; +} + +/* 娣辫壊涓婚鐗瑰畾璋冩暣 */ +@media (prefers-color-scheme: dark) { + :root { + /* 鍦ㄦ繁鑹叉ā寮忎笅鍙互杩涜寰皟 */ + } +} + +/* 楂樺姣斿害妯″紡鏀寔 */ +@media (prefers-contrast: high) { + :root { + --border-primary: #444444; + --border-secondary: #555555; + --border-tertiary: #666666; + --text-tertiary: #aaaaaa; + --text-quaternary: #888888; + } +} + +/* 鍑忓皯鍔ㄧ敾妯″紡鏀寔 */ +@media (prefers-reduced-motion: reduce) { + :root { + --transition-fast: 0s; + --transition-normal: 0s; + --transition-slow: 0s; + } +} + +/* 鍏ㄥ眬婊氬姩鏉℃牱寮?*/ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +*::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +*::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: var(--radius-md); +} + +*::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* 鍏ㄥ眬閫夋嫨鏂囨湰鏍峰紡 */ +::selection { + background: var(--primary-light); + color: var(--text-primary); +} + +/* 鍏ㄥ眬鐒︾偣鏍峰紡 */ +*:focus { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +*:focus:not(:focus-visible) { + outline: none; +} + +/* 鍏ㄥ眬绂佺敤鐘舵€佹牱寮?*/ +*:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +/* 鍏ㄥ眬閾炬帴鏍峰紡 */ +a { + color: var(--primary); + text-decoration: none; + transition: color var(--transition-normal); +} + +a:hover { + color: var(--primary-hover); +} + +/* 鍏ㄥ眬鎸夐挳閲嶇疆 */ +button { + border: none; + background: none; + cursor: pointer; + font-family: inherit; +} + +* { + -webkit-tap-highlight-color: transparent; +} + +button, +a, +input, +textarea { + -webkit-tap-highlight-color: transparent; +} + +@media (hover: none) and (pointer: coarse) { + *:focus { + outline: none !important; + } + button, + a { + -webkit-user-select: none; + user-select: none; + } +} + +/* 鍏ㄥ眬杈撳叆妗嗛噸缃?*/ +input, +textarea, +select { + font-family: inherit; + font-size: inherit; +} + +/* 鍏ㄥ眬鍥剧墖鏍峰紡 */ +img { + max-width: 100%; + height: auto; +} + +/* 鍏ㄥ眬琛ㄦ牸鏍峰紡 */ +table { + border-collapse: collapse; + width: 100%; +} + +/* 鍏ㄥ眬浠g爜鏍峰紡 */ +code, +pre { + font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + background: var(--bg-tertiary); + border-radius: var(--radius-sm); +} + +code { + padding: 2px 4px; + font-size: 0.875em; +} + +pre { + padding: var(--spacing-lg); + overflow-x: auto; +} + +pre code { + padding: 0; + background: none; +}