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/)**
-[](https://github.com/KuekHaoYang/KVideo)
[](https://nextjs.org/)
[](https://react.dev/)
[](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 (
-