diff --git a/README.md b/README.md index 54ac3fe..648f2e3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ **在线体验:[https://kvideo.pages.dev/](https://kvideo.pages.dev/)** -[![Version](https://img.shields.io/badge/Version-4.7.0-blue?style=for-the-badge)](https://github.com/KuekHaoYang/KVideo) [![Next.js](https://img.shields.io/badge/Next.js-16.1-black?style=for-the-badge&logo=next.js)](https://nextjs.org/) [![React](https://img.shields.io/badge/React-19.2-blue?style=for-the-badge&logo=react)](https://react.dev/) [![Tailwind CSS](https://img.shields.io/badge/Tailwind-4.0-38B2AC?style=for-the-badge&logo=tailwind-css)](https://tailwindcss.com/) @@ -25,12 +24,11 @@ 项目的视觉设计基于 **"Liquid Glass"** 设计系统,这是一套融合了以下特性的现代化 UI 设计语言: -- **玻璃拟态效果**:通过 `backdrop-filter: blur(25px) saturate(180%)` 实现的磨砂半透明效果,让 UI 元素如同真实的玻璃材质,动态反射和折射背景内容 -- **通用柔和度**:严格使用 `rounded-2xl`(1.5rem)和 `rounded-full`(9999px)两种圆角半径,创造和谐统一的视觉体验 -- **光影交互**:悬停和聚焦状态下的内发光效果(lensing),模拟光线被"捕获"的物理现象,配合动态阴影响应用户交互 -- **流畅动画**:基于物理的 `cubic-bezier(0.2, 0.8, 0.2, 1)` 曲线,实现自然的加速和减速过渡 -- **深度层级**:清晰的 z-axis 层次结构,主交互层始终在视觉最前方,背景元素通过更多模糊和更低饱和度自然后退 -- **固定渐变背景**:`background-attachment: fixed` 的渐变背景,让玻璃效果在滚动时保持一致的视觉效果 +- **玻璃拟态效果**:通过 `backdrop-filter` 实现的磨砂半透明效果,让 UI 元素如同真实的玻璃材质 +- **通用柔和度**:统一使用 `rounded-2xl` 和 `rounded-full` 两种圆角半径,创造和谐的视觉体验 +- **光影交互**:悬停和聚焦状态下的内发光效果,模拟光线被"捕获"的物理现象 +- **流畅动画**:基于物理的 `cubic-bezier` 曲线,实现自然的加速和减速过渡 +- **深度层级**:清晰的 z-axis 层次结构,增强空间感和交互反馈 ## 核心功能 diff --git a/app/page.tsx b/app/page.tsx index a4fe980..7102e61 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -40,8 +40,8 @@ function HomePage() { {/* Glass Navbar */} - {/* Search Form */} -
diff --git a/app/styles/base.css b/app/styles/base.css index 1828392..027aa69 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -1,4 +1,4 @@ -/* Base Styles - Liquid Glass Design System v5 */ +/* Base Styles */ *, *::before, *::after { @@ -46,7 +46,7 @@ select:focus-visible { /* Scrollbar Styles */ ::-webkit-scrollbar { - width: 8px; + width: 10px; } ::-webkit-scrollbar-track { @@ -54,19 +54,21 @@ select:focus-visible { } ::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--text-color) 15%, transparent); + background: rgba(128, 128, 128, 0.5); + background: color-mix(in srgb, var(--glass-bg) 80%, transparent); border-radius: var(--radius-full); border: 2px solid transparent; background-clip: padding-box; } ::-webkit-scrollbar-thumb:hover { - background: color-mix(in srgb, var(--accent-color) 50%, transparent); + background: rgba(0, 122, 255, 0.6); + background: color-mix(in srgb, var(--accent-color) 60%, transparent); background-clip: padding-box; } .search-history-dropdown::-webkit-scrollbar { - width: 5px; + width: 6px; } .search-history-dropdown::-webkit-scrollbar-track { @@ -74,7 +76,8 @@ select:focus-visible { } .search-history-dropdown::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--text-color) 15%, transparent); + background: rgba(128, 128, 128, 0.5); + background: color-mix(in srgb, var(--glass-bg) 80%, transparent); border-radius: var(--radius-full); } @@ -111,8 +114,13 @@ nav:where(.sticky, .fixed), transform: translateZ(0); } +/* [class*="grid"]>* { + content-visibility: auto; + contain-intrinsic-size: auto 400px; +} */ + [class*="grid"]>a { - animation: fadeInUp 0.25s cubic-bezier(0.22, 0.68, 0, 1); + animation: fadeInUp 0.2s ease-out; } @supports (content-visibility: auto) { @@ -129,10 +137,12 @@ body.scrolling * { pointer-events: none !important; } +.group:hover img, .glass-card, nav { transform: translateZ(0); backface-visibility: hidden; + perspective: 1000px; } [class*="badge"], @@ -150,7 +160,7 @@ nav { body.tv-mode [data-focusable]:focus { outline: 3px solid var(--accent-color); outline-offset: 4px; - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent-color) 20%, transparent); + box-shadow: 0 0 0 6px rgba(0, 122, 255, 0.2); transform: scale(1.03); transition: transform 0.15s ease-out, box-shadow 0.15s ease-out; } @@ -174,4 +184,4 @@ body.tv-mode [class*="gap-3"] { body.tv-mode [class*="gap-4"] { gap: 1.5rem; -} +} \ No newline at end of file diff --git a/app/styles/effects.css b/app/styles/effects.css index 3c779f2..3a49490 100644 --- a/app/styles/effects.css +++ b/app/styles/effects.css @@ -1,38 +1,71 @@ -/* Effects - Liquid Glass Design System v5 */ @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } @keyframes spin-slow { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } } @keyframes spin-reverse { - from { transform: rotate(360deg); } - to { transform: rotate(0deg); } + from { + transform: rotate(360deg); + } + + to { + transform: rotate(0deg); + } } @keyframes bounce-subtle { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-4px); } + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-5px); + } } @keyframes shimmer { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } } @keyframes float { - 0%, 100% { + + 0%, + 100% { transform: translateY(0) translateX(0); opacity: 0.3; } + 50% { transform: translateY(-20px) translateX(10px); opacity: 0.8; @@ -40,47 +73,13 @@ } @keyframes gradient-x { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } -} -@keyframes glow-pulse { - 0%, 100% { box-shadow: 0 0 8px color-mix(in srgb, var(--accent-color) 20%, transparent); } - 50% { box-shadow: 0 0 20px color-mix(in srgb, var(--accent-color) 40%, transparent); } -} + 0%, + 100% { + background-position: 0% 50%; + } -/* Lensing inner-glow effect for interactive elements */ -.glass-glow:hover { - box-shadow: - var(--shadow-lg), - inset 0 1px 0 rgba(255, 255, 255, 0.1); -} - -.glass-glow:focus-visible { - box-shadow: - 0 0 0 3px color-mix(in srgb, var(--accent-color) 25%, transparent), - inset 0 1px 0 rgba(255, 255, 255, 0.1); -} - -/* Hover lift effect */ -.hover-lift { - transition: transform var(--transition-fluid), box-shadow var(--transition-fluid); -} - -.hover-lift:hover { - transform: translateY(-3px); - box-shadow: var(--shadow-lg); -} - -/* Subtle scale on hover */ -.hover-scale { - transition: transform var(--transition-snappy); -} - -.hover-scale:hover { - transform: scale(1.03); -} - -.hover-scale:active { - transform: scale(0.97); -} + 50% { + background-position: 100% 50%; + } +} \ No newline at end of file diff --git a/app/styles/glass.css b/app/styles/glass.css index cf9e918..828dece 100644 --- a/app/styles/glass.css +++ b/app/styles/glass.css @@ -1,115 +1,66 @@ -/* Glass Components - Liquid Glass Design System v5 */ +/* Glass Components */ .glass-card { - background: var(--glass-bg); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); + background: var(--bg-color); + opacity: 1; border-radius: var(--radius-2xl); - box-shadow: var(--shadow-md); + box-shadow: var(--shadow-sm); border: 1px solid var(--glass-border); - transition: all var(--transition-fluid); - position: relative; + transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; + transform: translateZ(0); + will-change: transform; } .glass-card:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-lg); - z-index: 10; + transform: translateY(-2px); + box-shadow: var(--shadow-md); } .glass-input { - width: 100%; background: var(--glass-bg); - backdrop-filter: blur(12px) saturate(160%); - -webkit-backdrop-filter: blur(12px) saturate(160%); border: 1px solid var(--glass-border); border-radius: var(--radius-2xl); color: var(--text-color); - transition: all var(--transition-fluid); + transition: border-color 0.2s ease, box-shadow 0.2s ease; } .glass-input:focus { outline: none; border-color: var(--accent-color); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 20%, transparent), - var(--shadow-md); + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.3); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 30%, transparent); } .glass-button { - display: inline-flex; - align-items: center; - justify-content: center; background: var(--accent-color); color: white; border: none; border-radius: var(--radius-2xl); - padding: 0.75rem 1.5rem; + padding: 0.75rem 1.25rem; font-weight: 600; cursor: pointer; - transition: all var(--transition-snappy); - box-shadow: 0 2px 8px color-mix(in srgb, var(--accent-color) 35%, transparent); + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); } .glass-button:hover { - transform: translateY(-1px); - background: var(--accent-hover, var(--accent-color)); - box-shadow: 0 4px 16px color-mix(in srgb, var(--accent-color) 40%, transparent); + transform: translateY(-2px); + filter: brightness(1.1); + box-shadow: var(--shadow-md); } .glass-button:active { - transform: translateY(0) scale(0.97); -} - -.glass-button:disabled { - background-color: var(--text-color-secondary); - opacity: 0.4; - cursor: not-allowed; - transform: none; - box-shadow: none; + transform: translateY(0) scale(0.98); + filter: brightness(0.95); } .glass-badge { display: inline-flex; align-items: center; justify-content: center; - padding: 0.2rem 0.625rem; + padding: 0.25rem 0.75rem; border-radius: var(--radius-full); - font-size: 0.75rem; + font-size: 0.8rem; font-weight: 600; background-color: var(--accent-color); color: white; - letter-spacing: 0.01em; -} - -.glass-badge-secondary { - background: var(--glass-bg); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: 1px solid var(--glass-border); - color: var(--text-color); -} - -/* Glass backdrop for modals/drawers */ -.glass-backdrop { - background-color: rgba(0, 0, 0, 0.4); - backdrop-filter: blur(12px) saturate(120%); - -webkit-backdrop-filter: blur(12px) saturate(120%); -} - -/* Glass popover/dropdown */ -.glass-popover { - background: var(--glass-bg); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); - border-radius: var(--radius-2xl); - box-shadow: var(--shadow-lg); - border: 1px solid var(--glass-border); -} - -/* Glass surface - subtle inner highlight */ -.glass-surface { - background: var(--glass-bg); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); - border: 1px solid var(--glass-border); - border-radius: var(--radius-2xl); } diff --git a/app/styles/transitions.css b/app/styles/transitions.css index 6147ba7..9c156c5 100644 --- a/app/styles/transitions.css +++ b/app/styles/transitions.css @@ -1,89 +1,91 @@ -/* Transitions - Liquid Glass Design System v5 */ @keyframes fade-in { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } } @keyframes slide-up { - from { opacity: 0; transform: translateY(16px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(20px); + } + + to { + opacity: 1; + transform: translateY(0); + } } @keyframes scale-in { - 0% { opacity: 0; transform: scale(0.96) translateY(8px); } - 100% { opacity: 1; transform: scale(1) translateY(0); } + 0% { + opacity: 0; + transform: scale(0.9) translateY(10px); + } + + 100% { + opacity: 1; + transform: scale(1) translateY(0); + } } @keyframes scale-out { - 0% { opacity: 1; transform: scale(1) translateY(0); } - 100% { opacity: 0; transform: scale(0.96) translateY(-8px); } + 0% { + opacity: 1; + transform: scale(1) translateY(0); + } + + 100% { + opacity: 0; + transform: scale(0.9) translateY(-10px); + } } @keyframes slideIn { - from { opacity: 0; transform: translateY(-8px) scale(0.97); } - to { opacity: 1; transform: translateY(0) scale(1); } + from { + opacity: 0; + transform: translateY(-10px) scale(0.95); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + } } @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + + to { + opacity: 1; + } } @keyframes slideInRight { - from { transform: translateX(100%); } - to { transform: translateX(0); } -} + from { + transform: translateX(100%); + } -@keyframes slideInLeft { - from { transform: translateX(-100%); } - to { transform: translateX(0); } + to { + transform: translateX(0); + } } @keyframes fadeInUp { - from { opacity: 0; transform: translateY(6px); } - to { opacity: 1; transform: translateY(0); } -} + from { + opacity: 0; + transform: translateY(8px); + } -@keyframes toast-in { - from { opacity: 0; transform: translateY(16px) scale(0.97); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -@keyframes sidebar-enter { - from { opacity: 0; transform: translateX(100%); } - to { opacity: 1; transform: translateX(0); } -} - -@keyframes sidebar-enter-left { - from { opacity: 0; transform: translateX(-100%); } - to { opacity: 1; transform: translateX(0); } -} - -/* Utility animation classes */ -.animate-fade-in { - animation: fade-in 0.35s cubic-bezier(0.22, 0.68, 0, 1); -} - -.animate-slide-up { - animation: slide-up 0.3s cubic-bezier(0.22, 0.68, 0, 1); -} - -.animate-scale-in { - animation: scale-in 0.3s cubic-bezier(0.22, 0.68, 0, 1); -} - -.animate-toast-in { - animation: toast-in 0.4s cubic-bezier(0.22, 0.68, 0, 1); -} - -/* Staggered children animation */ -.stagger-children > * { - animation: fadeInUp 0.3s cubic-bezier(0.22, 0.68, 0, 1) backwards; -} - -.stagger-children > *:nth-child(1) { animation-delay: 0ms; } -.stagger-children > *:nth-child(2) { animation-delay: 40ms; } -.stagger-children > *:nth-child(3) { animation-delay: 80ms; } -.stagger-children > *:nth-child(4) { animation-delay: 120ms; } -.stagger-children > *:nth-child(5) { animation-delay: 160ms; } -.stagger-children > *:nth-child(6) { animation-delay: 200ms; } + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/app/styles/variables.css b/app/styles/variables.css index 60ac745..57ea282 100644 --- a/app/styles/variables.css +++ b/app/styles/variables.css @@ -1,55 +1,46 @@ -/* CSS Custom Properties - Liquid Glass Design System v5 */ +/* CSS Custom Properties */ :root { - --font-family-system: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif; + --font-family-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif; /* Light Mode Palette */ - --bg-color-light: #f0f2f5; - --bg-image-light: linear-gradient(135deg, #fdfbfb 0%, #e8ecf1 50%, #f0f2f5 100%); - --text-color-light: #1a1a2e; - --text-color-secondary-light: #64748b; - --accent-color-light: #2563eb; - --accent-hover-light: #1d4ed8; - --glass-bg-light: rgba(255, 255, 255, 0.72); - --glass-border-light: rgba(255, 255, 255, 0.45); - --shadow-color-light: rgba(0, 0, 0, 0.08); + --bg-color-light: #f2f4f7; + --bg-image-light: none; + --text-color-light: #1d1d1f; + --text-color-secondary-light: #6e6e73; + --accent-color-light: #0056b3; + --glass-bg-light: rgba(255, 255, 255, 0.95); + --glass-border-light: rgba(0, 0, 0, 0.05); + --shadow-color-light: rgba(0, 0, 0, 0.05); /* Dark Mode Palette */ - --bg-color-dark: #0a0a0f; - --bg-image-dark: linear-gradient(135deg, #0a0a0f 0%, #141420 50%, #0d0d14 100%); - --text-color-dark: #f0f0f5; - --text-color-secondary-dark: #7c8298; - --accent-color-dark: #3b82f6; - --accent-hover-dark: #60a5fa; - --glass-bg-dark: rgba(22, 22, 35, 0.78); - --glass-border-dark: rgba(255, 255, 255, 0.06); - --shadow-color-dark: rgba(0, 0, 0, 0.45); + --bg-color-dark: #121212; + --bg-image-dark: linear-gradient(120deg, #1a1a1a 0%, #121212 100%); + --text-color-dark: #f5f5f7; + --text-color-secondary-dark: #8e8e93; + --accent-color-dark: #1A6DBF; + --glass-bg-dark: rgba(30, 30, 30, 0.9); + --glass-border-dark: rgba(255, 255, 255, 0.1); + --shadow-color-dark: rgba(0, 0, 0, 0.3); /* Universal Variables */ - --radius-xs: 0.5rem; - --radius-sm: 0.75rem; - --radius-md: 1rem; - --radius-lg: 1.25rem; --radius-2xl: 1.5rem; --radius-full: 9999px; - --shadow-sm: 0 1px 3px var(--shadow-color), 0 1px 2px var(--shadow-color); - --shadow-md: 0 4px 16px var(--shadow-color), 0 2px 4px var(--shadow-color); - --shadow-lg: 0 8px 32px var(--shadow-color), 0 4px 8px var(--shadow-color); - --transition-fluid: 0.35s cubic-bezier(0.22, 0.68, 0, 1); - --transition-spring: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); - --transition-snappy: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); + --shadow-sm: 0 1px 2px var(--shadow-color); + --shadow-md: 0 4px 6px var(--shadow-color); + --transition-fluid: 0.3s ease; - /* Active Theme Variables (default: light) */ + /* Active Theme Variables */ --bg-color: var(--bg-color-light); --bg-image: var(--bg-image-light); --text-color: var(--text-color-light); --text-color-secondary: var(--text-color-secondary-light); --accent-color: var(--accent-color-light); - --accent-hover: var(--accent-hover-light); --glass-bg: var(--glass-bg-light); --glass-border: var(--glass-border-light); --shadow-color: var(--shadow-color-light); - --background: #f0f2f5; - --foreground: #1a1a2e; + + --background: #f2f4f7; + --foreground: #1d1d1f; } body { @@ -58,17 +49,15 @@ body { --text-color: var(--text-color-light); --text-color-secondary: var(--text-color-secondary-light); --accent-color: var(--accent-color-light); - --accent-hover: var(--accent-hover-light); --glass-bg: var(--glass-bg-light); --glass-border: var(--glass-border-light); --shadow-color: var(--shadow-color-light); background-color: var(--bg-color); background-image: var(--bg-image); - background-attachment: fixed; color: var(--text-color); font-family: var(--font-family-system); - transition: background 0.4s ease; + -webkit-overflow-scrolling: touch; overscroll-behavior-y: none; } @@ -80,10 +69,10 @@ body.dark, --text-color: var(--text-color-dark); --text-color-secondary: var(--text-color-secondary-dark); --accent-color: var(--accent-color-dark); - --accent-hover: var(--accent-hover-dark); --glass-bg: var(--glass-bg-dark); --glass-border: var(--glass-border-dark); --shadow-color: var(--shadow-color-dark); - --background: #0a0a0f; - --foreground: #f0f0f5; -} + + --background: #121212; + --foreground: #f5f5f7; +} \ No newline at end of file diff --git a/components/favorites/FavoritesSidebar.tsx b/components/favorites/FavoritesSidebar.tsx index bd71c6f..9a5b711 100644 --- a/components/favorites/FavoritesSidebar.tsx +++ b/components/favorites/FavoritesSidebar.tsx @@ -1,3 +1,8 @@ +/** + * FavoritesSidebar - Main favorites sidebar component + * Positioned on the left side of the screen (opposite to WatchHistorySidebar) + */ + 'use client'; import { useState, useEffect, useRef } from 'react'; @@ -22,10 +27,12 @@ export function FavoritesSidebar({ isPremium = false }: { isPremium?: boolean }) const sidebarRef = useRef(null); const cleanupFocusTrapRef = useRef<(() => void) | null>(null); + // Setup focus trap when sidebar opens useEffect(() => { if (isOpen && sidebarRef.current) { cleanupFocusTrapRef.current = trapFocus(sidebarRef.current); } + return () => { if (cleanupFocusTrapRef.current) { cleanupFocusTrapRef.current(); @@ -34,14 +41,24 @@ export function FavoritesSidebar({ isPremium = false }: { isPremium?: boolean }) }; }, [isOpen]); + // Handle escape key to close sidebar useEffect(() => { const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape' && isOpen) setIsOpen(false); + if (e.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleEscape); + } + + return () => { + document.removeEventListener('keydown', handleEscape); }; - if (isOpen) document.addEventListener('keydown', handleEscape); - return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); + // Handle delete confirmation const handleDeleteItem = (videoId: string | number, source: string) => { setDeleteConfirm({ isOpen: true, videoId: String(videoId), source }); }; @@ -51,8 +68,15 @@ export function FavoritesSidebar({ isPremium = false }: { isPremium?: boolean }) }; const confirmDelete = () => { - if (deleteConfirm.isClearAll) clearFavorites(); - else if (deleteConfirm.videoId && deleteConfirm.source) removeFavorite(deleteConfirm.videoId, deleteConfirm.source); + if (deleteConfirm.isClearAll) { + clearFavorites(); + } else if (deleteConfirm.videoId && deleteConfirm.source) { + removeFavorite(deleteConfirm.videoId, deleteConfirm.source); + } + setDeleteConfirm({ isOpen: false }); + }; + + const cancelDelete = () => { setDeleteConfirm({ isOpen: false }); }; @@ -61,16 +85,16 @@ export function FavoritesSidebar({ isPremium = false }: { isPremium?: boolean }) {/* Toggle Button - Left side */} {/* Backdrop */} {isOpen && (
setIsOpen(false)} /> )} @@ -85,21 +109,36 @@ export function FavoritesSidebar({ isPremium = false }: { isPremium?: boolean }) transform: isOpen ? 'translate3d(0, 0, 0)' : 'translate3d(-100%, 0, 0)', willChange: isOpen ? 'transform' : 'auto' }} - className="fixed top-0 left-0 bottom-0 w-[85%] sm:w-[90%] max-w-[400px] z-[2000] bg-[var(--glass-bg)] backdrop-blur-[20px] saturate-[160%] [-webkit-backdrop-filter:blur(20px)_saturate(160%)] border-r border-[var(--glass-border)] rounded-r-[var(--radius-2xl)] p-5 sm:p-6 flex flex-col shadow-[var(--shadow-lg)] transition-transform duration-300 [transition-timing-function:cubic-bezier(0.22,0.68,0,1)]" + className={`fixed top-0 left-0 bottom-0 w-[85%] sm:w-[90%] max-w-[420px] z-[2000] bg-[var(--glass-bg)] backdrop-blur-[8px] saturate-[120%] border-r border-[var(--glass-border)] rounded-tr-[var(--radius-2xl)] rounded-br-[var(--radius-2xl)] p-6 flex flex-col shadow-[0_8px_32px_rgba(0,0,0,0.2)] transition-transform duration-250 ease-out`} > setIsOpen(false)} /> - - 0} onClearAll={handleClearAll} /> + + + + 0} + onClearAll={handleClearAll} + /> + {/* Watch History Sidebar - Right side */} + {/* Confirm Dialog */} setDeleteConfirm({ isOpen: false })} + onCancel={cancelDelete} confirmText="确定" cancelText="取消" variant="danger" diff --git a/components/history/WatchHistorySidebar.tsx b/components/history/WatchHistorySidebar.tsx index b8ee9ad..4d6bf6e 100644 --- a/components/history/WatchHistorySidebar.tsx +++ b/components/history/WatchHistorySidebar.tsx @@ -1,3 +1,8 @@ +/** + * Watch History Sidebar Component + * 观看历史侧边栏组件 - Main layout and state management + */ + 'use client'; import { useState, useEffect, useRef } from 'react'; @@ -20,10 +25,12 @@ export function WatchHistorySidebar({ isPremium = false }: { isPremium?: boolean const sidebarRef = useRef(null); const cleanupFocusTrapRef = useRef<(() => void) | null>(null); + // Setup focus trap when sidebar opens useEffect(() => { if (isOpen && sidebarRef.current) { cleanupFocusTrapRef.current = trapFocus(sidebarRef.current); } + return () => { if (cleanupFocusTrapRef.current) { cleanupFocusTrapRef.current(); @@ -32,14 +39,24 @@ export function WatchHistorySidebar({ isPremium = false }: { isPremium?: boolean }; }, [isOpen]); + // Handle escape key to close sidebar useEffect(() => { const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape' && isOpen) setIsOpen(false); + if (e.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleEscape); + } + + return () => { + document.removeEventListener('keydown', handleEscape); }; - if (isOpen) document.addEventListener('keydown', handleEscape); - return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); + // Handle delete confirmation const handleDeleteItem = (showIdentifier: string) => { setDeleteConfirm({ isOpen: true, showIdentifier }); }; @@ -49,8 +66,15 @@ export function WatchHistorySidebar({ isPremium = false }: { isPremium?: boolean }; const confirmDelete = () => { - if (deleteConfirm.isClearAll) clearHistory(); - else if (deleteConfirm.showIdentifier) removeFromHistory(deleteConfirm.showIdentifier); + if (deleteConfirm.isClearAll) { + clearHistory(); + } else if (deleteConfirm.showIdentifier) { + removeFromHistory(deleteConfirm.showIdentifier); + } + setDeleteConfirm({ isOpen: false }); + }; + + const cancelDelete = () => { setDeleteConfirm({ isOpen: false }); }; @@ -59,16 +83,16 @@ export function WatchHistorySidebar({ isPremium = false }: { isPremium?: boolean {/* Toggle Button */} {/* Backdrop */} {isOpen && (
setIsOpen(false)} /> )} @@ -83,19 +107,33 @@ export function WatchHistorySidebar({ isPremium = false }: { isPremium?: boolean transform: isOpen ? 'translate3d(0, 0, 0)' : 'translate3d(100%, 0, 0)', willChange: isOpen ? 'transform' : 'auto' }} - className="fixed top-0 right-0 bottom-0 w-[85%] sm:w-[90%] max-w-[400px] z-[2000] bg-[var(--glass-bg)] backdrop-blur-[20px] saturate-[160%] [-webkit-backdrop-filter:blur(20px)_saturate(160%)] border-l border-[var(--glass-border)] rounded-l-[var(--radius-2xl)] p-5 sm:p-6 flex flex-col shadow-[var(--shadow-lg)] transition-transform duration-300 [transition-timing-function:cubic-bezier(0.22,0.68,0,1)]" + className={`fixed top-0 right-0 bottom-0 w-[85%] sm:w-[90%] max-w-[420px] z-[2000] bg-[var(--glass-bg)] backdrop-blur-[8px] saturate-[120%] border-l border-[var(--glass-border)] rounded-tl-[var(--radius-2xl)] rounded-bl-[var(--radius-2xl)] p-6 flex flex-col shadow-[0_8px_32px_rgba(0,0,0,0.2)] transition-transform duration-250 ease-out`} > setIsOpen(false)} /> - - 0} onClearAll={handleClearAll} /> + + + + 0} + onClearAll={handleClearAll} + /> + {/* Confirm Dialog */} setDeleteConfirm({ isOpen: false })} + onCancel={cancelDelete} confirmText="删除" cancelText="取消" variant="danger" diff --git a/components/home/PopularFeatures.tsx b/components/home/PopularFeatures.tsx index d9384cf..597110d 100644 --- a/components/home/PopularFeatures.tsx +++ b/components/home/PopularFeatures.tsx @@ -93,25 +93,29 @@ export function PopularFeatures({ onSearch }: PopularFeaturesProps) { return (
- {/* Content Type Toggle */} + {/* Content Type Toggle (Capsule Liquid Glass - Fixed & Centered) */} {!effectiveRecommendSelected && ( -
-
+
+
+ {/* Sliding Indicator */}
+ diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index 24d2262..6a303d2 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -24,88 +24,95 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) { const handleLogout = () => { clearSession(); + // Navigate to root to clear search query params window.location.href = '/'; }; - const iconBtnClass = ` - w-9 h-9 sm:w-10 sm:h-10 - flex items-center justify-center - rounded-[var(--radius-full)] - bg-[var(--glass-bg)] - backdrop-blur-[8px] - [-webkit-backdrop-filter:blur(8px)] - border border-[var(--glass-border)] - text-[var(--text-color-secondary)] - hover:text-[var(--text-color)] - hover:bg-[color-mix(in_srgb,var(--accent-color)_8%,var(--glass-bg))] - hover:border-[color-mix(in_srgb,var(--accent-color)_15%,var(--glass-border))] - transition-all duration-200 - cursor-pointer - `; - return ( -