diff --git a/src/App.tsx b/src/App.tsx index f544cdf..5166c70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,7 +30,7 @@ import AccountManagementPage from './pages/AccountManagementPage' import BackupPage from './pages/BackupPage' import { useAppStore } from './stores/appStore' -import { useThemeStore, type ThemeMode } from './stores/themeStore' +import { themes, useThemeStore, type ThemeId, type ThemeMode } from './stores/themeStore' import * as configService from './services/config' import * as cloudControl from './services/cloudControl' import { Download, X, Shield } from 'lucide-react' @@ -74,7 +74,7 @@ function App() { setLocked } = useAppStore() - const { themeMode, setThemeMode } = useThemeStore() + const { currentTheme, themeMode, setTheme, setThemeMode } = useThemeStore() const isAgreementWindow = location.pathname === '/agreement-window' const isOnboardingWindow = location.pathname === '/onboarding-window' const isVideoPlayerWindow = location.pathname === '/video-player-window' @@ -149,11 +149,12 @@ function App() { } }, [isOnboardingWindow, isNotificationWindow, isAnnualReportWindow, isDualReportWindow]) - // 应用主题模式 (light / dark / system) + // 应用主题 (accent color + light/dark mode) useEffect(() => { const mq = window.matchMedia('(prefers-color-scheme: dark)') const applyMode = (mode: ThemeMode, systemDark?: boolean) => { const effectiveMode = mode === 'system' ? (systemDark ?? mq.matches ? 'dark' : 'light') : mode + document.documentElement.setAttribute('data-theme', currentTheme) document.documentElement.setAttribute('data-mode', effectiveMode) } @@ -167,13 +168,19 @@ function App() { } mq.addEventListener('change', handler) return () => mq.removeEventListener('change', handler) - }, [themeMode, isOnboardingWindow, isNotificationWindow, isAnnualReportWindow, isDualReportWindow]) + }, [currentTheme, themeMode, isOnboardingWindow, isNotificationWindow, isAnnualReportWindow, isDualReportWindow]) // 读取已保存的主题设置 useEffect(() => { const loadTheme = async () => { try { - const savedThemeMode = await configService.getTheme() + const [savedThemeId, savedThemeMode] = await Promise.all([ + configService.getThemeId(), + configService.getTheme() + ]) + if (savedThemeId && themes.some((theme) => theme.id === savedThemeId)) { + setTheme(savedThemeId as ThemeId) + } if (savedThemeMode === 'light' || savedThemeMode === 'dark' || savedThemeMode === 'system') { setThemeMode(savedThemeMode) } @@ -184,20 +191,23 @@ function App() { } } loadTheme() - }, [setThemeMode]) + }, [setTheme, setThemeMode]) // 保存主题设置 useEffect(() => { if (!themeHydrated) return const saveTheme = async () => { try { - await configService.setTheme(themeMode) + await Promise.all([ + configService.setThemeId(currentTheme), + configService.setTheme(themeMode) + ]) } catch (e) { console.error('保存主题配置失败:', e) } } saveTheme() - }, [themeMode, themeHydrated]) + }, [currentTheme, themeMode, themeHydrated]) // 检查是否已同意协议 useEffect(() => { diff --git a/src/components/Sidebar.scss b/src/components/Sidebar.scss index d082bd4..0c1d31e 100644 --- a/src/components/Sidebar.scss +++ b/src/components/Sidebar.scss @@ -1,13 +1,14 @@ -// ChatGPT-style sidebar +// Redesigned sidebar — premium feel with left accent bar, refined spacing .sidebar { width: var(--sidebar-width, 260px); background: var(--bg-sidebar, var(--bg-secondary)); display: flex; flex-direction: column; - padding: 8px 0; - transition: width 0.2s ease; + padding: 0; + transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1); flex-shrink: 0; overflow: hidden; + border-right: 1px solid var(--border-color); &.collapsed { width: 68px; @@ -23,21 +24,37 @@ .user-meta { display: none; } + + .user-menu-caret { + display: none; + } } - .nav-menu, - .sidebar-footer { + .nav-menu { padding: 0 8px; } + .sidebar-footer { + padding: 0 8px; + padding-top: 8px; + } + .nav-label { display: none; } + .nav-badge:not(.icon-badge) { + display: none; + } + .nav-item { justify-content: center; padding: 10px; gap: 0; + + &::before { + display: none; + } } } } @@ -47,18 +64,19 @@ flex: 1; display: flex; flex-direction: column; - gap: 2px; - padding: 0 12px; + gap: 1px; + padding: 12px 10px; overflow-y: auto; overflow-x: hidden; } .nav-item { + position: relative; display: flex; align-items: center; gap: 12px; - padding: 10px 12px; - border-radius: 8px; + padding: 9px 14px; + border-radius: 10px; color: var(--text-secondary); text-decoration: none; transition: background 0.15s ease, color 0.15s ease; @@ -68,6 +86,21 @@ cursor: pointer; font-family: inherit; font-size: 14px; + margin: 1px 0; + + // Left accent bar for active state + &::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%) scaleY(0); + width: 3px; + height: 16px; + border-radius: 0 2px 2px 0; + background: var(--primary); + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } &:hover { background: var(--bg-hover); @@ -78,6 +111,14 @@ background: var(--bg-hover); color: var(--text-primary); font-weight: 600; + + &::before { + transform: translateY(-50%) scaleY(1); + } + + .nav-icon { + color: var(--primary); + } } } @@ -88,6 +129,7 @@ width: 20px; height: 20px; flex-shrink: 0; + transition: color 0.15s ease; } .nav-icon-with-badge { @@ -129,19 +171,19 @@ // ---- Footer ---- .sidebar-footer { - padding: 0 12px; + padding: 4px 10px; border-top: 1px solid var(--border-color); padding-top: 8px; margin-top: 4px; display: flex; flex-direction: column; - gap: 2px; + gap: 1px; } // ---- User card ---- .sidebar-user-card-wrap { position: relative; - margin: 0 12px 8px; + margin: 0 10px 10px; --sidebar-user-menu-width: 172px; } @@ -153,7 +195,7 @@ width: max(100%, var(--sidebar-user-menu-width)); z-index: 12; border: 1px solid var(--border-color); - border-radius: 10px; + border-radius: 12px; background: var(--bg-secondary-solid, var(--bg-secondary)); display: flex; flex-direction: column; @@ -203,7 +245,7 @@ .sidebar-user-card { width: 100%; - padding: 10px; + padding: 10px 12px; border-radius: 10px; background: transparent; display: flex; @@ -223,8 +265,8 @@ } .user-avatar { - width: 32px; - height: 32px; + width: 34px; + height: 34px; border-radius: 50%; overflow: hidden; background: var(--primary); @@ -232,6 +274,7 @@ align-items: center; justify-content: center; flex-shrink: 0; + box-shadow: 0 0 0 2px var(--bg-sidebar, var(--bg-secondary)); img { width: 100%; diff --git a/src/pages/AnnualReportPage.scss b/src/pages/AnnualReportPage.scss index 396441c..bdee64f 100644 --- a/src/pages/AnnualReportPage.scss +++ b/src/pages/AnnualReportPage.scss @@ -7,6 +7,7 @@ min-height: 100%; text-align: center; padding: 40px 24px; + animation: reportFadeIn 0.35s ease-out; } .annual-report-page.report-route-transitioning > :not(.report-launch-overlay) { @@ -20,40 +21,43 @@ } .page-title { - font-size: 32px; + font-size: 28px; font-weight: 700; color: var(--text-primary); - margin: 0 0 12px; + margin: 0 0 10px; + letter-spacing: -0.5px; } .page-desc { font-size: 15px; color: var(--text-secondary); - margin: 0 0 48px; + margin: 0 0 40px; + line-height: 1.7; } .page-desc.load-summary { - margin: 0 0 28px; + margin: 0 0 24px; } .page-desc.load-summary.complete { color: var(--text-secondary); } +// ---- Load telemetry ---- .load-telemetry { - width: min(760px, 100%); - padding: 12px 14px; - margin: 0 0 28px; - border-radius: 12px; - border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent); - background: color-mix(in srgb, var(--card-bg) 92%, transparent); + width: min(620px, 100%); + padding: 12px 16px; + margin: 0 0 24px; + border-radius: 10px; + border: 1px solid var(--border-color); + background: var(--card-bg); text-align: left; font-size: 13px; color: var(--text-secondary); line-height: 1.5; p { - margin: 4px 0; + margin: 3px 0; } .label { @@ -62,31 +66,32 @@ } .load-telemetry.loading { - border-color: color-mix(in srgb, var(--primary) 30%, var(--border-color)); + border-color: color-mix(in srgb, var(--primary) 25%, var(--border-color)); } .load-telemetry.complete { - border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color)); + border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color)); } .load-telemetry.compact { margin: 12px 0 0; - width: min(560px, 100%); + width: min(500px, 100%); } +// ---- Report sections ---- .report-sections { display: flex; flex-direction: column; - gap: 32px; - width: min(760px, 100%); + gap: 20px; + width: min(620px, 100%); } .report-section { width: 100%; background: var(--card-bg); border: 1px solid var(--border-color); - border-radius: 20px; - padding: 28px; + border-radius: 16px; + padding: 24px; text-align: left; } @@ -95,57 +100,57 @@ align-items: flex-start; justify-content: space-between; gap: 16px; - margin-bottom: 20px; + margin-bottom: 16px; } .section-title { margin: 0; - font-size: 20px; + font-size: 17px; font-weight: 700; color: var(--text-primary); } .section-desc { - margin: 8px 0 0; - font-size: 14px; + margin: 6px 0 0; + font-size: 13px; color: var(--text-tertiary); } .section-badge { display: inline-flex; align-items: center; - gap: 6px; - padding: 6px 10px; + gap: 5px; + padding: 4px 10px; border-radius: 999px; - background: color-mix(in srgb, var(--primary) 12%, transparent); + background: var(--primary-light); color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent); font-size: 12px; font-weight: 600; white-space: nowrap; } .section-hint { - margin: 12px 0 0; + margin: 10px 0 0; font-size: 12px; color: var(--text-tertiary); } +// ---- Year cards ---- .year-grid-with-status { display: flex; align-items: flex-start; justify-content: space-between; - gap: 16px; - margin-bottom: 24px; + gap: 12px; + margin-bottom: 20px; } .year-grid { display: flex; flex-wrap: wrap; - gap: 16px; + gap: 10px; justify-content: center; max-width: 600px; - margin-bottom: 48px; + margin-bottom: 40px; } .report-section .year-grid { @@ -169,7 +174,7 @@ } .year-load-status.complete { - color: color-mix(in srgb, var(--primary) 80%, var(--text-secondary)); + color: var(--primary); } .dot-ellipsis { @@ -187,32 +192,33 @@ } .year-card { - width: 120px; - height: 100px; + width: 88px; + height: 64px; display: flex; flex-direction: column; align-items: center; justify-content: center; - background: var(--card-bg); - border: 2px solid var(--border-color); - border-radius: 16px; + background: transparent; + border: 1px solid var(--border-color); + border-radius: 10px; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s ease; + gap: 2px; &:hover { - border-color: var(--primary); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + border-color: var(--text-tertiary); + background: var(--bg-hover); } &.disabled { pointer-events: none; - opacity: 0.72; + opacity: 0.6; } &.selected { border-color: var(--primary); background: var(--primary-light); + box-shadow: 0 0 0 1px var(--primary); .year-number { color: var(--primary); @@ -220,45 +226,41 @@ } .year-number { - font-size: 32px; + font-size: 22px; font-weight: 700; color: var(--text-primary); line-height: 1; } .year-label { - font-size: 14px; + font-size: 11px; color: var(--text-tertiary); - margin-top: 4px; } } +// ---- Generate button ---- .generate-btn { display: flex; align-items: center; - gap: 10px; - padding: 16px 40px; - background: linear-gradient(135deg, var(--primary) 0%, color-mix(in srgb, var(--primary) 80%, #000) 100%); + justify-content: center; + gap: 8px; + width: 100%; + padding: 12px 24px; + background: var(--primary); border: none; - border-radius: 50px; - color: #fff; - font-size: 16px; + border-radius: 10px; + color: var(--on-primary); + font-size: 15px; font-weight: 600; cursor: pointer; - transition: all 0.2s; - box-shadow: 0 4px 16px color-mix(in srgb, var(--primary) 30%, transparent); + transition: opacity 0.15s ease; &:hover:not(:disabled) { - transform: translateY(-2px); - box-shadow: 0 8px 24px color-mix(in srgb, var(--primary) 40%, transparent); - } - - &:active:not(:disabled) { - transform: translateY(0); + opacity: 0.9; } &:disabled { - opacity: 0.6; + opacity: 0.5; cursor: not-allowed; } @@ -267,13 +269,18 @@ } &.secondary { - background: var(--card-bg); + background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-color); - box-shadow: none; + + &:hover:not(:disabled) { + background: var(--bg-hover); + opacity: 1; + } } } +// ---- Launch overlay ---- .report-launch-overlay { position: fixed; inset: 0; @@ -281,9 +288,9 @@ display: flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--bg-primary) 78%, transparent); + background: color-mix(in srgb, var(--bg-primary) 80%, transparent); backdrop-filter: blur(8px); - animation: report-launch-overlay-in 420ms ease-out both; + animation: report-launch-overlay-in 350ms ease-out both; } .launch-core { @@ -293,13 +300,13 @@ gap: 10px; text-align: center; color: var(--text-primary); - animation: report-launch-core-in 420ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation: report-launch-core-in 350ms cubic-bezier(0.2, 0.8, 0.2, 1) both; } .launch-title { margin: 4px 0 0; - font-size: 18px; - font-weight: 650; + font-size: 17px; + font-weight: 600; } .launch-subtitle { @@ -312,6 +319,7 @@ animation: spin 1s linear infinite; } +// ---- Animations ---- @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } @@ -329,27 +337,34 @@ } to { opacity: 0; - filter: blur(8px); + filter: blur(6px); transform: scale(0.985); } } @keyframes report-launch-overlay-in { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { opacity: 0; } + to { opacity: 1; } } @keyframes report-launch-core-in { from { opacity: 0; - transform: translateY(18px) scale(0.96); + transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } + +@keyframes reportFadeIn { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/pages/ChatAnalyticsHubPage.scss b/src/pages/ChatAnalyticsHubPage.scss index f6621a7..e84a14d 100644 --- a/src/pages/ChatAnalyticsHubPage.scss +++ b/src/pages/ChatAnalyticsHubPage.scss @@ -7,68 +7,79 @@ } .analytics-hub-inner { - width: min(720px, 100%); - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - animation: analyticsHubFadeIn 0.4s ease-out; + width: min(560px, 100%); + animation: analyticsHubFadeIn 0.35s ease-out; +} + +// ---- Hero ---- +.analytics-hub-hero { + margin-bottom: 40px; } .analytics-hub-title { font-size: 28px; font-weight: 700; color: var(--text-primary); - margin: 0 0 12px; + margin: 0 0 10px; letter-spacing: -0.5px; } .analytics-hub-desc { - max-width: 520px; - margin: 0 0 36px; + margin: 0; color: var(--text-secondary); font-size: 15px; line-height: 1.7; } -.analytics-hub-grid { - width: 100%; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; -} - -.analytics-hub-card { +// ---- Perspectives list ---- +.analytics-hub-perspectives { display: flex; flex-direction: column; - align-items: flex-start; - text-align: left; - gap: 16px; - padding: 24px; - border: 1px solid var(--border-color); + gap: 4px; +} + +.analytics-hub-perspectives-label { + font-size: 12px; + font-weight: 600; + color: var(--text-tertiary); + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 0 4px; + margin-bottom: 8px; +} + +.analytics-hub-row { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 16px; + border: none; border-radius: 12px; - background: var(--card-bg); + background: transparent; color: var(--text-primary); cursor: pointer; - transition: background 0.15s ease, border-color 0.15s ease; + text-align: left; + transition: background 0.15s ease; + width: 100%; &:hover { background: var(--bg-hover); - border-color: var(--text-tertiary); - .analytics-hub-card-arrow { + .analytics-hub-row-arrow { transform: translateX(3px); + color: var(--text-secondary); } } } -.analytics-hub-card-icon { - width: 44px; - height: 44px; - border-radius: 10px; +.analytics-hub-row-icon { + width: 40px; + height: 40px; + border-radius: 50%; display: flex; align-items: center; justify-content: center; + flex-shrink: 0; background: var(--primary-light); color: var(--primary); @@ -78,53 +89,39 @@ } } -[data-mode="dark"] .analytics-hub-card-icon--group { +[data-mode="dark"] .analytics-hub-row-icon--group { background: rgba(96, 165, 250, 0.12); color: #60a5fa; } -.analytics-hub-card-body { - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; +.analytics-hub-row-body { + flex: 1; + min-width: 0; } -.analytics-hub-card-header { - display: flex; - align-items: center; - justify-content: space-between; - - h2 { - margin: 0; - font-size: 18px; - font-weight: 600; - line-height: 1.2; - } +.analytics-hub-row-title { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 2px; } -.analytics-hub-card-arrow { - color: var(--text-tertiary); - transition: transform 0.15s ease; -} - -.analytics-hub-card-body p { - margin: 0; - color: var(--text-secondary); +.analytics-hub-row-desc { font-size: 13px; - line-height: 1.6; + color: var(--text-tertiary); + line-height: 1.5; } -@media (max-width: 640px) { - .analytics-hub-grid { - grid-template-columns: 1fr; - } +.analytics-hub-row-arrow { + color: var(--text-tertiary); + flex-shrink: 0; + transition: transform 0.15s ease, color 0.15s ease; } @keyframes analyticsHubFadeIn { from { opacity: 0; - transform: translateY(10px); + transform: translateY(8px); } to { opacity: 1; diff --git a/src/pages/ChatAnalyticsHubPage.tsx b/src/pages/ChatAnalyticsHubPage.tsx index 3e21c73..4bc514e 100644 --- a/src/pages/ChatAnalyticsHubPage.tsx +++ b/src/pages/ChatAnalyticsHubPage.tsx @@ -8,44 +8,44 @@ function ChatAnalyticsHubPage() { return (
- 选择你要进入的分析视角。私聊分析适合查看好友聊天统计,群聊分析则用于查看群成员活跃度。 -
++ 选择你要进入的分析视角,深入了解关系网络、活跃时段与消息趋势。 +
+