Merge pull request #19 from Moe-Sakura/dev

Dev
This commit is contained in:
Asuna
2025-11-27 05:21:08 +08:00
committed by GitHub
20 changed files with 1540 additions and 926 deletions

127
THEME_SYSTEM.md Normal file
View File

@@ -0,0 +1,127 @@
# 主题系统说明
## 概述
本项目采用简化的主题系统,**自动跟随操作系统主题设置**。
## 自动跟随系统
### 工作原理
应用会自动检测并应用操作系统的主题偏好:
- 🌞 系统为浅色模式 → 应用显示浅色主题
- 🌙 系统为深色模式 → 应用显示深色主题
- 🔄 系统主题变化 → 应用自动切换
### 实现原理
使用 Tailwind CSS 的 `dark:` 变体来定义暗色模式样式:
```css
/* 浅色模式样式 */
.bg-white { background: white; }
/* 深色模式样式 */
.dark .bg-white { background: rgb(15, 23, 42); }
```
`<html>` 元素添加 `dark` class 时,所有使用 `dark:` 前缀的样式会自动生效。
### 系统监听
使用 `prefers-color-scheme` 媒体查询监听系统主题变化:
```javascript
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', (e) => {
applyTheme(e.matches ? 'dark' : 'light')
})
```
## 自定义样式
### 使用方法
用户可以在"设置"中添加自定义 CSS 代码来个性化界面:
```css
/* 自定义按钮样式 */
.my-custom-button {
background: linear-gradient(to right, #ec4899, #8b5cf6);
color: white;
padding: 12px 24px;
border-radius: 12px;
}
/* 深色模式下的样式 */
.dark .my-custom-button {
background: linear-gradient(to right, #a855f7, #8b5cf6);
}
```
### 存储和应用
- 自定义 CSS 保存在 `localStorage`key: `searchgal_custom_css`
- 页面加载时自动应用到 `<style id="custom-user-styles">` 标签中
- 支持完整的 CSS 语法和 Tailwind 暗色模式变体
## 技术实现
### 核心文件
- `src/utils/theme.ts` - 主题管理工具
- `src/styles/base.css` - 基础样式和暗色模式定义
- `src/components/SettingsModal.vue` - 自定义CSS编辑器
### API
```typescript
// 获取系统主题
getSystemTheme(): 'light' | 'dark'
// 应用主题
applyTheme(theme: 'light' | 'dark'): void
// 监听系统主题变化
watchSystemTheme(callback): () => void
// 加载自定义CSS
loadCustomCSS(): string
// 应用自定义CSS
applyCustomCSS(css: string): void
// 保存自定义CSS
saveCustomCSS(css: string): void
```
## 背景图片
- 浅色模式:显示粉色纹理背景
- 深色模式:显示深色纹理背景,透明度允许背景图片显示
- 随机图片切换每10秒自动切换一张随机动漫图片
## 如何更改系统主题
### macOS
1. 打开"系统设置"
2. 点击"外观"
3. 选择"浅色"、"深色"或"自动"
### Windows 11
1. 打开"设置"
2. 点击"个性化" → "颜色"
3. 选择"浅色"、"深色"或"自定义"
### Linux (GNOME)
1. 打开"设置"
2. 点击"外观"
3. 选择"浅色"或"深色"
## 最佳实践
1. 使用 Tailwind 的 `dark:` 变体定义暗色样式
2. 避免硬编码颜色,优先使用语义化的 Tailwind 类名
3. 自定义CSS应该同时定义浅色和深色两种样式
4. 测试两种模式下的显示效果,确保可读性

View File

@@ -62,22 +62,11 @@
<link rel="dns-prefetch" href="https://status.searchgal.homes" />
<style>
/* ============================================
CSS 变量定义(由 JavaScript 动态修改)
============================================ */
:root {
/* 主题配色 - 默认值(玫瑰粉),会被 themeColors.ts 动态覆盖 */
--theme-primary: #ec4899;
--theme-primary-dark: #db2777;
--theme-accent: #8b5cf6;
--theme-accent-dark: #7c3aed;
}
/* ============================================
第三方库样式覆盖
============================================ */
/* Pace.js 加载进度条 */
/* Pace.js 加载进度条 - 艳粉主色调 */
.pace {
pointer-events: none;
user-select: none;
@@ -86,64 +75,82 @@
display: none;
}
.pace .pace-progress {
background: linear-gradient(90deg, var(--theme-primary), var(--theme-accent));
background: linear-gradient(90deg, #ff1493, #ff69b4, #d946ef);
position: fixed;
z-index: 9999;
top: 0;
right: 100%;
width: 100%;
height: 3px;
box-shadow: 0 0 10px color-mix(in srgb, var(--theme-primary) 50%, transparent);
height: 4px;
box-shadow: 0 0 15px rgba(255, 20, 147, 0.6), 0 0 30px rgba(217, 70, 239, 0.4);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
/* Fancybox 图片查看器 */
/* Fancybox 图片查看器 - 艳粉主色调 */
.fancybox-custom .fancybox__backdrop {
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10px);
}
.fancybox-custom .fancybox__container {
--fancybox-accent-color: var(--theme-primary);
--fancybox-accent-color: #ff1493;
}
/* ============================================
全局伪元素样式(无法用 Tailwind 替代)
============================================ */
/* 自定义滚动条 */
/* 自定义滚动条 - 艳粉主题 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
background: linear-gradient(to bottom, rgba(255, 179, 217, 0.1), rgba(255, 228, 242, 0.15));
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(255, 20, 147, 0.05);
}
.dark ::-webkit-scrollbar-track {
background: rgba(30, 41, 59, 0.3);
background: linear-gradient(to bottom, rgba(30, 41, 59, 0.4), rgba(15, 23, 42, 0.5));
box-shadow: inset 0 0 5px rgba(255, 105, 180, 0.1);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--theme-primary), var(--theme-accent));
background: linear-gradient(180deg, #ff1493 0%, #ff69b4 50%, #d946ef 100%);
border-radius: 10px;
transition: background 0.3s ease;
border: 2px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 8px rgba(255, 20, 147, 0.3);
transition: all 0.3s ease;
}
.dark ::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--theme-accent), var(--theme-accent-dark));
background: linear-gradient(180deg, #ff69b4 0%, #ffb3d9 50%, #e879f9 100%);
border: 2px solid rgba(255, 105, 180, 0.2);
box-shadow: 0 2px 10px rgba(255, 105, 180, 0.4);
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, var(--theme-primary-dark), var(--theme-accent-dark));
background: linear-gradient(180deg, #ff69b4 0%, #ff1493 50%, #d946ef 100%);
border-color: rgba(255, 255, 255, 0.5);
box-shadow: 0 4px 12px rgba(255, 20, 147, 0.5);
transform: scale(1.05);
}
.dark ::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, var(--theme-accent-dark), var(--theme-accent));
background: linear-gradient(180deg, #ffb3d9 0%, #ff69b4 50%, #f0abfc 100%);
border-color: rgba(255, 105, 180, 0.4);
box-shadow: 0 4px 15px rgba(255, 105, 180, 0.6);
}
/* 文本选中高亮 */
/* 文本选中高亮 - 艳粉主色调 */
::selection {
background: color-mix(in srgb, var(--theme-primary) 30%, transparent);
color: inherit;
background: linear-gradient(135deg, rgba(255, 20, 147, 0.35), rgba(255, 105, 180, 0.3));
color: #1d1b1e;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
}
.dark ::selection {
background: color-mix(in srgb, var(--theme-accent) 40%, transparent);
color: inherit;
background: linear-gradient(135deg, rgba(255, 105, 180, 0.4), rgba(232, 121, 249, 0.35));
color: #ffffff;
text-shadow: 0 0 10px rgba(255, 20, 147, 0.6);
}
/* ============================================
@@ -210,17 +217,25 @@
100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
/* 背景图层伪元素(默认纹理和遮罩层) */
/* 背景图层伪元素 - 仅在无背景图时显示淡粉色纹理 */
#background-layer::before {
content: "";
position: absolute;
inset: 0;
background: #f0bbbb url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23000000' fill-opacity='0.05' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
background:
radial-gradient(circle at 20% 50%, rgba(255, 20, 147, 0.08), transparent 50%),
radial-gradient(circle at 80% 80%, rgba(217, 70, 239, 0.08), transparent 50%),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23ff1493' fill-opacity='0.02' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
z-index: -1;
opacity: 0.4;
transition: opacity 0.8s ease-in-out;
}
.dark #background-layer::before {
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23ffffff' fill-opacity='0.03' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
background:
radial-gradient(circle at 30% 20%, rgba(255, 105, 180, 0.06), transparent 40%),
radial-gradient(circle at 70% 70%, rgba(232, 121, 249, 0.05), transparent 40%),
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23ff69b4' fill-opacity='0.015' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
opacity: 0.3;
}
#background-layer.has-image::before {
opacity: 0;

View File

@@ -27,6 +27,7 @@
<SettingsModal
:is-open="isSettingsOpen"
:custom-api="searchStore.customApi"
:custom-c-s-s="customCSS"
@close="closeSettings"
@save="saveSettings"
/>
@@ -38,8 +39,13 @@
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { imageDB } from '@/utils/imageDB'
import { useSearchStore } from '@/stores/search'
import { initTheme } from '@/utils/themeColors'
import type { ThemePresetKey } from '@/types/theme'
import {
getSystemTheme,
applyTheme,
watchSystemTheme,
loadCustomCSS,
applyCustomCSS,
} from '@/utils/theme'
import StatsCorner from '@/components/StatsCorner.vue'
import TopToolbar from '@/components/TopToolbar.vue'
import SearchHeader from '@/components/SearchHeader.vue'
@@ -57,9 +63,11 @@ const imageBlobUrls = ref<Map<string, string>>(new Map()) // URL -> Blob URL 映
const shuffledQueue = ref<string[]>([])
let fetchInterval: number | null = null
let displayInterval: number | null = null
let systemThemeCleanup: (() => void) | null = null
// 设置模态框
const isSettingsOpen = ref(false)
const customCSS = ref('')
const MAX_CACHE_SIZE = 10000 // 最大缓存 10000 张图片
const CLEANUP_BATCH_SIZE = 2000 // 每次清理 2000 张
@@ -360,8 +368,18 @@ function stopAllIntervals() {
}
onMounted(async () => {
// 初始化主题
initTheme()
// 初始化主题 - 跟随系统
const systemTheme = getSystemTheme()
applyTheme(systemTheme)
// 加载并应用自定义CSS
customCSS.value = loadCustomCSS()
applyCustomCSS(customCSS.value)
// 监听系统主题变化
systemThemeCleanup = watchSystemTheme((theme) => {
applyTheme(theme)
})
// 恢复保存的搜索状态
searchStore.restoreState()
@@ -385,6 +403,12 @@ onMounted(async () => {
onUnmounted(() => {
stopAllIntervals()
// 清理系统主题监听
if (systemThemeCleanup) {
systemThemeCleanup()
systemThemeCleanup = null
}
// 清理所有 Blob URL
imageBlobUrls.value.forEach(blobUrl => {
URL.revokeObjectURL(blobUrl)
@@ -404,10 +428,12 @@ function closeSettings() {
isSettingsOpen.value = false
}
function saveSettings(customApi: string, theme: ThemePresetKey) {
function saveSettings(customApi: string, newCustomCSS: string) {
// 保存自定义 API 到 store
searchStore.setCustomApi(customApi)
// 主题已经在 SettingsModal 中保存到 localStorage
// 保存并应用自定义CSS
customCSS.value = newCustomCSS
applyCustomCSS(newCustomCSS)
}
</script>

View File

@@ -18,6 +18,7 @@ export interface SearchResult {
export interface PlatformResult {
name: string
color: 'lime' | 'white' | 'gold' | 'red'
url?: string
items: SearchResult[]
error: string
}
@@ -142,15 +143,31 @@ export async function searchGameStream(
callbacks.onProgress?.(data.progress.completed, data.progress.total)
// 转换为我们的格式,保留 tags 标签信息
const items = data.result.items.map((item: any) => ({
platform: data.result.name,
title: item.name,
url: item.url,
tags: data.result.tags || [], // 保留平台标签NoReq, Login, BTmag 等)
}))
// 提取平台URL优先使用API返回的url/website否则从第一个结果的URL中提取域名
let platformUrl = data.result.url || data.result.website || ''
if (!platformUrl && items.length > 0 && items[0].url) {
try {
const firstUrl = new URL(items[0].url)
platformUrl = `${firstUrl.protocol}//${firstUrl.host}`
} catch (e) {
// URL解析失败保持为空
console.warn(`无法从 ${items[0].url} 提取平台URL`)
}
}
const platformResult: PlatformResult = {
name: data.result.name,
color: data.result.color || 'white',
items: data.result.items.map((item: any) => ({
platform: data.result.name,
title: item.name,
url: item.url,
tags: data.result.tags || [], // 保留平台标签NoReq, Login, BTmag 等)
})),
url: platformUrl,
items: items,
error: data.result.error || '',
}

View File

@@ -24,15 +24,15 @@
>
<div
v-if="searchStore.isCommentsModalOpen"
class="bg-white/60 dark:bg-slate-800/60 backdrop-blur-2xl backdrop-saturate-150 rounded-2xl sm:rounded-3xl shadow-2xl w-full max-w-4xl max-h-[90vh] mx-4 flex flex-col overflow-hidden border border-white/40 dark:border-slate-700/40"
class="glassmorphism-modal rounded-2xl sm:rounded-3xl shadow-2xl w-full max-w-4xl max-h-[90vh] mx-4 flex flex-col overflow-hidden"
@click.stop
>
<!-- 标题栏 -->
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-theme-primary/20 dark:border-slate-700">
<i class="fas fa-comments text-theme-primary dark:text-theme-accent text-xl sm:text-2xl" />
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-[#ff1493]/20 dark:border-slate-700">
<i class="fas fa-comments text-[#ff1493] dark:text-[#ff69b4] text-xl sm:text-2xl" />
<h2 class="text-lg sm:text-xl font-bold text-gray-800 dark:text-slate-100 flex-1">评论区</h2>
<button
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-theme-primary/10 dark:hover:bg-slate-700 text-gray-500 dark:text-slate-400 hover:text-theme-primary dark:hover:text-theme-accent transition-all duration-200"
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-[#ff1493]/10 dark:hover:bg-slate-700 text-gray-500 dark:text-slate-400 hover:text-[#ff1493] dark:hover:text-[#ff69b4] transition-all duration-200"
@click="closeModal"
>
<i class="fas fa-times text-xl" />
@@ -91,6 +91,7 @@ function initArtalk() {
pageTitle: 'Galgame 聚合搜索',
server: 'https://artalk.saop.cc',
site: 'Galgame 聚合搜索',
darkMode: "auto",
} as any)
} catch (error) {
// 静默处理错误

View File

@@ -173,15 +173,21 @@ onUnmounted(() => {
width: 44px;
height: 44px;
border-radius: 18px;
border: none;
border: 1.5px solid rgba(255, 20, 147, 0.3);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4), 0 3px 10px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
/* 液态玻璃效果 + 艳粉阴影 */
backdrop-filter: blur(15px) saturate(180%);
-webkit-backdrop-filter: blur(15px) saturate(180%);
box-shadow:
0 6px 20px rgba(255, 20, 147, 0.3),
0 3px 10px rgba(255, 105, 180, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
@@ -191,7 +197,10 @@ onUnmounted(() => {
height: 52px;
border-radius: 22px;
font-size: 22px;
box-shadow: 0 8px 24px rgba(236, 72, 153, 0.4), 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow:
0 8px 24px rgba(255, 20, 147, 0.35),
0 4px 12px rgba(255, 105, 180, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
}
}
@@ -205,38 +214,59 @@ onUnmounted(() => {
}
.fab-button:hover {
box-shadow: 0 12px 36px rgba(236, 72, 153, 0.5), 0 6px 20px rgba(0, 0, 0, 0.2);
box-shadow:
0 12px 36px rgba(255, 20, 147, 0.45),
0 6px 20px rgba(255, 105, 180, 0.35),
0 0 40px rgba(255, 20, 147, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.7) inset;
transform: translateY(-4px) scale(1.08) rotate(5deg);
border-color: rgba(255, 20, 147, 0.5);
}
.fab-button:active {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 6px 20px rgba(236, 72, 153, 0.4);
box-shadow:
0 6px 20px rgba(255, 20, 147, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.4) inset;
}
/* 各按钮特定颜色 - 艳粉主题 */
.scroll-top-btn {
background: linear-gradient(135deg, rgb(99, 102, 241), rgb(79, 70, 229));
background: linear-gradient(135deg, rgb(236, 72, 153), rgb(219, 39, 119));
color: white;
}
.comments-btn {
background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark));
background: linear-gradient(135deg, #ff1493, #c71585);
color: white;
}
.comments-btn.comments-open {
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
background: linear-gradient(135deg, rgb(255, 105, 180), rgb(199, 21, 133));
color: white;
border-color: rgba(255, 105, 180, 0.5);
}
.vndb-btn {
background: linear-gradient(135deg, var(--theme-accent), var(--theme-accent-dark));
background: linear-gradient(135deg, #d946ef, #c026d3);
color: white;
}
.vndb-btn.vndb-open {
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
background: linear-gradient(135deg, rgb(232, 121, 249), rgb(217, 70, 239));
color: white;
border-color: rgba(232, 121, 249, 0.5);
}
.nav-btn {
background: linear-gradient(135deg, rgb(255, 20, 147), rgb(217, 70, 239));
color: white;
}
.nav-btn.nav-open {
background: linear-gradient(135deg, rgb(255, 105, 180), rgb(232, 121, 249));
color: white;
border-color: rgba(255, 105, 180, 0.5);
}
.fab-button i {
@@ -247,17 +277,7 @@ onUnmounted(() => {
transform: scale(1.1);
}
.nav-btn {
background: linear-gradient(135deg, rgb(16, 185, 129), rgb(5, 150, 105));
color: white;
}
.nav-btn.nav-open {
background: linear-gradient(135deg, rgb(156, 163, 175), rgb(107, 114, 128));
color: white;
}
/* 自定义滚动条 */
/* 自定义滚动条 - 艳粉主题 */
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
@@ -267,14 +287,15 @@ onUnmounted(() => {
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5);
background: linear-gradient(180deg, rgba(255, 20, 147, 0.5), rgba(217, 70, 239, 0.5));
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(156, 163, 175, 0.7);
background: linear-gradient(180deg, rgba(255, 20, 147, 0.7), rgba(217, 70, 239, 0.7));
}
/* 站点导航项图标颜色 */
.item-lime i {
color: rgb(132, 204, 22);
}

View File

@@ -1,13 +1,17 @@
<template>
<div class="container mx-auto w-full px-4 sm:px-6 lg:px-8 py-4 sm:py-6">
<!-- 主内容区域 - 垂直居中布局 -->
<div class="flex flex-col items-center justify-center min-h-[60vh]">
<!-- Title with gamepad icon and status -->
<div class="flex flex-col items-center justify-center min-h-[65vh] sm:min-h-[70vh]">
<!-- Title with gamepad icon and status - 艳粉主题 -->
<div
class="header-title flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 mb-8 sm:mb-12 animate-fade-in-down"
class="header-title flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 mb-6 sm:mb-8 animate-fade-in-down"
>
<h1
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-center text-white drop-shadow-[0_4px_8px_rgba(0,0,0,0.3)]"
class="text-3xl sm:text-4xl lg:text-5xl font-bold text-center
text-white
drop-shadow-[0_2px_8px_rgba(255,20,147,0.6)]
dark:drop-shadow-[0_2px_12px_rgba(255,105,180,0.8)]"
style="text-shadow: 0 0 30px rgba(255, 20, 147, 0.4), 0 0 60px rgba(255, 105, 180, 0.2);"
>
<span class="whitespace-nowrap">Galgame 聚合搜索</span>
</h1>
@@ -15,17 +19,24 @@
href="https://status.searchgal.homes"
target="_blank"
rel="noopener noreferrer"
class="status-link px-3 sm:px-4 py-1.5 sm:py-2 rounded-full bg-white/70 dark:bg-slate-800/70 backdrop-blur-md flex items-center gap-1.5 sm:gap-2 text-green-600 dark:text-green-400 text-sm sm:text-base font-semibold hover:scale-105 transition-transform shadow-lg"
:class="[
'status-link px-3 sm:px-4 py-1.5 sm:py-2 rounded-full backdrop-blur-md border border-white/30 dark:border-white/20 flex items-center gap-1.5 sm:gap-2 text-white text-sm sm:text-base font-bold hover:scale-105 transition-all duration-300 shadow-md',
statusOnline === null
? 'bg-gray-500 hover:bg-gray-600 shadow-gray-500/20 hover:shadow-gray-500/30 dark:bg-gray-600 dark:hover:bg-gray-700'
: statusOnline
? 'bg-green-500 hover:bg-green-600 shadow-green-500/20 hover:shadow-green-500/30 dark:bg-green-600 dark:hover:bg-green-700'
: 'bg-red-500 hover:bg-red-600 shadow-red-500/20 hover:shadow-red-500/30 dark:bg-red-600 dark:hover:bg-red-700'
]"
>
<i class="fas fa-check-circle" />
<span>状态</span>
<i :class="statusOnline === null ? 'fas fa-spinner fa-spin' : statusOnline ? 'fas fa-check-circle' : 'fas fa-exclamation-circle'" />
<span>{{ statusOnline === null ? '检测中' : statusOnline ? '正常' : '异常' }}</span>
</a>
</div>
<!-- 搜索历史 -->
<SearchHistory
ref="searchHistoryRef"
class="w-full max-w-2xl px-2 sm:px-0 mb-4"
class="w-full max-w-2xl px-2 sm:px-0 mb-3 sm:mb-4"
@select="handleHistorySelect"
/>
@@ -35,67 +46,103 @@
@submit.prevent="handleSearch"
>
<div class="flex flex-col gap-4">
<!-- Search Input -->
<!-- Search Input with Button Inside - 使用 Tailwind -->
<div class="relative">
<i
class="fas fa-search absolute left-3 sm:left-4 top-1/2 -translate-y-1/2 text-gray-400 text-lg sm:text-xl pointer-events-none z-10"
/>
<!-- 搜索图标 -->
<i class="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-theme-primary/60 dark:text-theme-accent/70 text-xl pointer-events-none z-10" />
<!-- 输入框 -->
<input
v-model="searchQuery"
type="search"
placeholder="游戏或补丁关键字词*"
required
class="w-full pl-10 sm:pl-12 pr-4 py-3 sm:py-4 text-sm sm:text-base rounded-2xl bg-white/50 dark:bg-slate-800/50 text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-400 backdrop-blur-2xl backdrop-saturate-150 shadow-lg focus:shadow-2xl focus:scale-[1.01] transition-all outline-none border border-white/40 dark:border-slate-700/40 focus:border-theme-primary/60 dark:focus:border-theme-accent/60"
class="w-full pl-12 pr-32 py-4 text-base rounded-2xl
bg-white/70 dark:bg-slate-800/70
text-gray-900 dark:text-slate-100
placeholder:text-gray-400 dark:placeholder:text-slate-400
backdrop-blur-xl backdrop-saturate-150
border border-white/40 dark:border-slate-700/30
shadow-lg shadow-theme-primary/10 dark:shadow-theme-accent/15
hover:shadow-xl hover:shadow-theme-primary/15 dark:hover:shadow-theme-accent/20
focus:shadow-2xl focus:shadow-theme-primary/20 dark:focus:shadow-theme-accent/25
focus:scale-[1.01]
transition-all duration-300 outline-none font-medium"
@keydown.enter.prevent="handleSearch"
/>
</div>
<!-- Search Button and Mode Selector -->
<div class="flex flex-col gap-3">
<!-- 搜索按钮 - 内嵌在输入框右侧 -->
<button
type="submit"
:disabled="searchStore.searchDisabled"
class="search-button w-full py-3 sm:py-4 rounded-2xl bg-gradient-to-r from-theme-primary/70 to-theme-primary-dark/70 dark:from-theme-accent/70 dark:to-theme-accent-dark/70 backdrop-blur-2xl backdrop-saturate-150 text-white font-semibold text-base sm:text-lg shadow-lg hover:shadow-2xl hover:scale-[1.02] active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 border border-white/30"
class="absolute right-2 top-1/2 -translate-y-1/2
px-6 py-2.5 rounded-xl
bg-gradient-pink text-white font-bold text-sm
backdrop-blur-md
border border-white/30 dark:border-white/20
shadow-md shadow-theme-primary/20
hover:shadow-lg hover:shadow-theme-primary/30 hover:scale-105
active:scale-95
disabled:opacity-50 disabled:cursor-not-allowed
transition-all duration-200
flex items-center gap-2 z-10"
@click.prevent="handleSearch"
>
<i class="fas fa-search" />
<span v-if="!searchStore.isSearching">开始搜索</span>
<span v-else>进度: {{ searchStore.searchProgress.current }} /
{{ searchStore.searchProgress.total }}</span>
<i class="fas fa-search text-sm" />
<span v-if="!searchStore.isSearching" class="hidden sm:inline">搜索</span>
<span v-else class="hidden sm:inline">{{ searchStore.searchProgress.current }}/{{ searchStore.searchProgress.total }}</span>
</button>
</div>
<!-- Search Mode Selector - 胶囊开关 -->
<div class="flex justify-center">
<div class="mode-switch-container relative bg-white/50 dark:bg-slate-700/50 backdrop-blur-2xl backdrop-saturate-150 rounded-full p-1 shadow-lg border border-white/40 dark:border-slate-600/40">
<!-- 滑动背景 -->
<div
class="mode-slider absolute top-1 bottom-1 rounded-full bg-gradient-to-r from-theme-primary/90 to-theme-primary-dark/90 dark:from-theme-accent/90 dark:to-theme-accent-dark/90 backdrop-blur-md shadow-md transition-all duration-300 ease-out"
:style="{
left: searchMode === 'game' ? '4px' : 'calc(50%)',
width: 'calc(50% - 4px)'
}"
/>
<!-- 游戏按钮 -->
<button
type="button"
class="mode-option relative z-10 px-6 py-2 rounded-full font-medium transition-all duration-300 flex items-center gap-2"
:class="searchMode === 'game' ? 'text-white' : 'text-gray-700 dark:text-slate-300'"
@click="searchMode = 'game'"
>
<i class="fas fa-gamepad" />
<span>游戏</span>
</button>
<!-- 补丁按钮 -->
<button
type="button"
class="mode-option relative z-10 px-6 py-2 rounded-full font-medium transition-all duration-300 flex items-center gap-2"
:class="searchMode === 'patch' ? 'text-white' : 'text-gray-700 dark:text-slate-300'"
@click="searchMode = 'patch'"
>
<i class="fas fa-tools" />
<span>补丁</span>
</button>
</div>
<!-- Search Mode Selector - 使用 Tailwind 胶囊开关 -->
<div class="flex justify-center">
<div class="relative flex p-1.5 rounded-full
bg-white/60 dark:bg-slate-700/60
backdrop-blur-xl backdrop-saturate-150
border border-white/40 dark:border-slate-600/30
shadow-lg shadow-theme-primary/10 dark:shadow-theme-accent/15">
<!-- 滑动背景指示器 -->
<div
class="absolute top-1.5 bottom-1.5 rounded-full
bg-gradient-pink
shadow-md shadow-theme-primary/30
transition-all duration-300 ease-out"
:style="{
left: searchMode === 'game' ? '6px' : 'calc(50% + 2px)',
width: 'calc(50% - 8px)'
}"
/>
<!-- 游戏按钮 -->
<button
type="button"
class="relative z-10 px-6 py-2 rounded-full font-semibold
transition-all duration-300
flex items-center gap-2 text-sm whitespace-nowrap"
:class="searchMode === 'game'
? 'text-white drop-shadow-md'
: 'text-gray-700 dark:text-slate-300 hover:text-theme-primary dark:hover:text-theme-accent'"
@click="searchMode = 'game'"
>
<i class="fas fa-gamepad" />
<span>游戏</span>
</button>
<!-- 补丁按钮 -->
<button
type="button"
class="relative z-10 px-6 py-2 rounded-full font-semibold
transition-all duration-300
flex items-center gap-2 text-sm whitespace-nowrap"
:class="searchMode === 'patch'
? 'text-white drop-shadow-md'
: 'text-gray-700 dark:text-slate-300 hover:text-theme-primary dark:hover:text-theme-accent'"
@click="searchMode = 'patch'"
>
<i class="fas fa-tools" />
<span>补丁</span>
</button>
</div>
</div>
</div>
@@ -125,15 +172,23 @@
</Transition>
</div>
<!-- Usage Notice - 独立于居中区域 -->
<!-- Usage Notice - 独立于居中区域 - 艳粉主题 -->
<div class="w-full max-w-4xl mx-auto mt-8 sm:mt-12 px-2 sm:px-0 animate-fade-in animation-delay-1000">
<div
class="usage-notice bg-white/50 dark:bg-slate-800/50 backdrop-blur-2xl backdrop-saturate-150 rounded-2xl sm:rounded-3xl shadow-xl p-4 sm:p-6 lg:p-8 border border-white/30 dark:border-slate-700/30"
class="usage-notice
bg-white/75 dark:bg-slate-800/75
backdrop-blur-xl backdrop-saturate-150
rounded-2xl sm:rounded-3xl
shadow-xl shadow-theme-primary/10 dark:shadow-theme-accent/20
p-4 sm:p-6 lg:p-8
border border-white/50 dark:border-slate-700/30"
>
<h2
class="text-xl sm:text-2xl font-bold text-gray-800 dark:text-slate-100 mb-4 sm:mb-6 flex items-center gap-2"
class="text-xl sm:text-2xl font-bold
text-theme-primary dark:text-theme-accent
mb-4 sm:mb-6 flex items-center gap-2"
>
<i class="fas fa-info-circle text-theme-primary dark:text-theme-accent" />
<i class="fas fa-info-circle" />
使用须知
</h2>
<ul class="space-y-3 sm:space-y-4 text-sm sm:text-base text-gray-700 dark:text-slate-300 leading-relaxed">
@@ -142,57 +197,59 @@
<a
href="https://saop.cc/"
target="_blank"
class="text-indigo-600 hover:underline font-semibold"
class="text-theme-primary dark:text-theme-accent hover:underline font-bold
hover:text-theme-primary-dark dark:hover:text-theme-accent-light
transition-colors duration-200"
>@Asuna</a>
大佬提供的服务器和技术支持没有大佬的魔法咱可跑不起来
</li>
<li>
本程序纯属
<strong>爱发电</strong>仅供绅士们交流学习使用务必请大家
<strong>支持正版 Galgame</strong>入正不亏哦
<strong class="text-theme-primary dark:text-theme-accent font-bold">爱发电</strong>仅供绅士们交流学习使用务必请大家
<strong class="text-theme-primary dark:text-theme-accent font-bold">支持正版 Galgame</strong>入正不亏哦
</li>
<li>
本站只做互联网内容的
<strong>聚合搬运工</strong>搜索结果均来自第三方站点下载前请各位自行判断
<strong>资源安全性</strong>以免翻车
<strong class="text-theme-primary dark:text-theme-accent font-bold">聚合搬运工</strong>搜索结果均来自第三方站点下载前请各位自行判断
<strong class="text-theme-primary dark:text-theme-accent font-bold">资源安全性</strong>以免翻车
</li>
<li>
搜索结果会显示不同的<strong>标签</strong>帮助你快速了解资源特性
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-100 text-green-700 text-xs font-medium ml-1">
搜索结果会显示不同的<strong class="text-theme-primary dark:text-theme-accent font-bold">标签</strong>帮助你快速了解资源特性
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 text-xs font-medium ml-1">
<i class="fas fa-check-circle text-[10px]" />直接下载
</span>
表示无需登录
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-blue-100 text-blue-700 text-xs font-medium">
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-xs font-medium">
<i class="fas fa-user text-[10px]" />需登录
</span>
表示需要账号
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700 text-xs font-medium">
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300 text-xs font-medium">
<i class="fas fa-rocket text-[10px]" />不限速
</span>
表示高速网盘
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-violet-100 text-violet-700 text-xs font-medium">
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 text-xs font-medium">
<i class="fas fa-magnet text-[10px]" />BT/磁力
</span>
表示种子下载等
</li>
<li>
搜索时请注意关键词长度<strong>关键词太短</strong>
可能搜不全部分站点只显示首批结果<strong>太长</strong>
搜索时请注意关键词长度<strong class="text-theme-primary dark:text-theme-accent font-bold">关键词太短</strong>
可能搜不全部分站点只显示首批结果<strong class="text-theme-primary dark:text-theme-accent font-bold">太长</strong>
则可能无法精准匹配建议尝试
<strong>适当的关键词</strong>效果更佳~
<strong class="text-theme-primary dark:text-theme-accent font-bold">适当的关键词</strong>效果更佳~
</li>
<li>
本程序每次查询完毕即断开连接<strong>严禁任何形式的爆破或恶意爬取</strong>做个文明的绅士
本程序每次查询完毕即断开连接<strong class="text-theme-primary dark:text-theme-accent font-bold">严禁任何形式的爆破或恶意爬取</strong>做个文明的绅士
</li>
<li>
万一某个站点搜索挂了先看看自己的魔法是否到位也可能是站点维护了或者咱这边的
<strong>爬虫失效</strong>
<strong class="text-theme-primary dark:text-theme-accent font-bold">爬虫失效</strong>
</li>
<li>
为了支持各 Galgame 站点能长久运营还请各位把浏览器的
<strong>广告屏蔽插件</strong>
<strong class="text-theme-primary dark:text-theme-accent font-bold">广告屏蔽插件</strong>
关掉或将这些站点加入白名单大家建站不易小小的支持也是大大的动力
</li>
<li>
@@ -200,17 +257,21 @@
<a
href="https://vndb.org/"
target="_blank"
class="text-indigo-600 hover:underline font-semibold"
class="text-theme-primary dark:text-theme-accent hover:underline font-bold
hover:text-theme-primary-dark dark:hover:text-theme-accent-light
transition-colors duration-200"
>VNDB</a>
提供由AI大模型翻译翻译结果不保证准确性仅作为检索游戏时的参考
</li>
<li> 郑重呼吁请务必支持 Galgame 正版让爱与梦想延续</li>
<li> 郑重呼吁请务必<strong class="text-theme-primary dark:text-theme-accent font-bold">支持 Galgame 正版</strong>让爱与梦想延续</li>
<li>
如果您觉得咱这小工具好用请移步
<a
href="https://github.com/Moe-Sakura/SearchGal"
href="https://github.com/Moe-Sakura"
target="_blank"
class="text-indigo-600 hover:underline font-semibold"
class="text-theme-primary dark:text-theme-accent hover:underline font-bold
hover:text-theme-primary-dark dark:hover:text-theme-accent-light
transition-colors duration-200"
>GitHub</a>
给本项目点个免费的
<strong>Star</strong> 秋梨膏你的支持就是咱最大的动力比心~
@@ -234,11 +295,41 @@ const searchQuery = ref('')
const customApi = ref('')
const searchMode = ref<'game' | 'patch'>('game')
const searchHistoryRef = ref<InstanceType<typeof SearchHistory> | null>(null)
const statusOnline = ref<boolean | null>(null) // null=检测中, true=在线, false=离线
let cleanupURLListener: (() => void) | null = null
let isUpdatingFromURL = false
let statusCheckInterval: number | null = null
// 检查状态页面是否在线
async function checkStatus() {
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5秒超时
const response = await fetch('https://status.searchgal.homes', {
method: 'HEAD',
mode: 'no-cors', // 避免CORS问题
signal: controller.signal,
})
clearTimeout(timeoutId)
// no-cors模式下只要请求不报错就认为是在线
statusOnline.value = true
} catch (error) {
statusOnline.value = false
}
}
// 从 URL 或 store 恢复搜索参数
onMounted(() => {
// 立即检查状态
checkStatus()
// 每30秒检查一次状态
statusCheckInterval = window.setInterval(() => {
checkStatus()
}, 30000)
// 优先从 URL 读取参数
const urlParams = getSearchParamsFromURL()
@@ -278,6 +369,9 @@ onUnmounted(() => {
if (cleanupURLListener) {
cleanupURLListener()
}
if (statusCheckInterval) {
clearInterval(statusCheckInterval)
}
})
// 同步到 store 和 URL

View File

@@ -9,20 +9,54 @@
:class="getCardClass(platformData.color)"
>
<div class="p-3 sm:p-4 md:p-6">
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 sm:gap-4 mb-3 sm:mb-4">
<h3 class="text-lg sm:text-xl font-bold flex items-center gap-2 flex-wrap" :class="getTextColor(platformData.color)">
<!-- 站点标题行网站名称 + 推荐标签 + 资源标签 + 结果数 -->
<div class="flex flex-wrap items-center gap-2 sm:gap-3 mb-3 sm:mb-4">
<!-- 网站名称可点击 -->
<a
v-if="platformData.url"
:href="platformData.url"
target="_blank"
rel="noopener noreferrer"
class="text-lg sm:text-xl font-bold flex items-center gap-2 hover:opacity-80 hover:underline transition-all cursor-pointer"
:class="getTextColor(platformData.color)"
:title="`访问 ${platformData.name}`"
>
<i :class="getPlatformIcon(platformData.color)" />
{{ platformData.name }}
<i class="fas fa-external-link-alt text-xs opacity-60" />
</a>
<div
v-else
class="text-lg sm:text-xl font-bold flex items-center gap-2"
:class="getTextColor(platformData.color)"
>
<i :class="getPlatformIcon(platformData.color)" />
{{ platformData.name }}
</div>
<!-- 推荐/付费标签 -->
<span
v-if="getRecommendText(platformData.color)"
class="px-2 sm:px-3 py-0.5 sm:py-1 rounded-full text-xs sm:text-sm font-medium flex items-center gap-1"
:class="getChipClass(platformData.color)"
>
<i :class="platformData.color === 'red' ? 'fas fa-times-circle' : 'fas fa-star'" />
{{ getRecommendText(platformData.color) }}
</span>
<!-- 站点的所有标签去重 -->
<template v-for="tag in getUniqueTags(platformData)" :key="tag">
<span
v-if="getRecommendText(platformData.color)"
class="px-2 sm:px-3 py-0.5 sm:py-1 rounded-full text-xs sm:text-sm font-medium flex items-center gap-1"
:class="getChipClass(platformData.color)"
:class="getTagClass(tag)"
class="px-2 py-0.5 rounded-full text-xs font-medium flex items-center gap-1"
>
<i :class="platformData.color === 'red' ? 'fas fa-times-circle' : 'fas fa-star'" />
{{ getRecommendText(platformData.color) }}
<i :class="getTagIcon(tag)" class="text-[10px]" />
<span>{{ getTagLabel(tag) }}</span>
</span>
</h3>
<span class="px-2 sm:px-3 py-0.5 sm:py-1 rounded-full bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-200 text-xs sm:text-sm font-medium flex items-center gap-1 shrink-0">
</template>
<!-- 结果数量 -->
<span class="ml-auto px-2 sm:px-3 py-0.5 sm:py-1 rounded-full bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-200 text-xs sm:text-sm font-medium flex items-center gap-1 shrink-0">
<i class="fas fa-hashtag text-xs" />
{{ platformData.items.length }}
</span>
@@ -41,6 +75,7 @@
:key="index"
class="result-item p-2 sm:p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700/50 transition-colors border-b border-gray-100 dark:border-slate-700 last:border-0"
>
<!-- 标题行 -->
<div class="flex items-start gap-1.5 sm:gap-2">
<span class="text-gray-400 dark:text-slate-500 text-xs sm:text-sm mt-0.5 shrink-0">{{ getResultIndex(platformData, index) }}.</span>
<a
@@ -52,16 +87,11 @@
{{ result.title }}
</a>
</div>
<div v-if="result.tags && result.tags.length > 0" class="flex flex-wrap gap-1 mt-1.5 sm:mt-2 ml-4 sm:ml-6">
<span
v-for="(tag, tagIndex) in result.tags"
:key="tagIndex"
:class="getTagClass(tag)"
class="px-2 py-0.5 rounded-full text-xs font-medium flex items-center gap-1"
>
<i :class="getTagIcon(tag)" class="text-[10px]" />
<span>{{ getTagLabel(tag) }}</span>
</span>
<!-- 资源相对路径从URL中提取 -->
<div v-if="result.url" class="flex items-center gap-1.5 sm:gap-2 mt-1 ml-4 sm:ml-6">
<i class="fas fa-link text-gray-400 dark:text-slate-500 text-xs" />
<span class="text-xs text-gray-500 dark:text-slate-400 break-all">{{ extractPath(result.url) }}</span>
</div>
</div>
</div>
@@ -120,6 +150,29 @@ import type { PlatformData } from '@/stores/search'
const searchStore = useSearchStore()
// 从URL中提取路径
function extractPath(url: string): string {
try {
const urlObj = new URL(url)
// 返回路径部分(去掉域名)
return urlObj.pathname + urlObj.search + urlObj.hash
} catch {
// 如果URL解析失败返回完整URL
return url
}
}
// 获取站点所有结果的唯一标签
function getUniqueTags(platformData: PlatformData) {
const allTags = new Set<string>()
platformData.items.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags.forEach(tag => allTags.add(tag))
}
})
return Array.from(allTags)
}
function paginatedResults(platformData: PlatformData) {
const start = (platformData.currentPage - 1) * platformData.itemsPerPage
const end = start + platformData.itemsPerPage

View File

@@ -1,40 +1,25 @@
<template>
<!-- 遮罩层 -->
<Transition
enter-active-class="transition-opacity duration-200 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-opacity duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="isOpen"
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
@click.self="close"
>
<Transition enter-active-class="transition-opacity duration-200 ease-out" enter-from-class="opacity-0"
enter-to-class="opacity-100" leave-active-class="transition-opacity duration-200 ease-in"
leave-from-class="opacity-100" leave-to-class="opacity-0">
<div v-if="isOpen" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
@click.self="close">
<!-- 对话框 -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-if="isOpen"
class="bg-white/60 dark:bg-slate-800/60 backdrop-blur-2xl backdrop-saturate-150 rounded-2xl sm:rounded-3xl shadow-2xl w-full max-w-2xl max-h-[90vh] mx-4 flex flex-col overflow-hidden border border-white/40 dark:border-slate-700/40"
@click.stop
>
<Transition enter-active-class="transition-all duration-200 ease-out" enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100" leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
<div v-if="isOpen"
class="glassmorphism-modal rounded-2xl sm:rounded-3xl shadow-2xl w-full max-w-2xl max-h-[90vh] mx-4 flex flex-col overflow-hidden"
@click.stop>
<!-- 标题栏 -->
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-theme-primary/20 dark:border-slate-700">
<i class="fas fa-cog text-theme-primary dark:text-theme-accent text-xl sm:text-2xl" />
<div
class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-gray-200/50 dark:border-slate-700">
<i class="fas fa-cog text-[#ff1493] dark:text-[#ff69b4] text-xl sm:text-2xl" />
<h2 class="text-lg sm:text-xl font-bold text-gray-800 dark:text-slate-100 flex-1">设置</h2>
<button
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-theme-primary/10 text-gray-500 hover:text-theme-primary dark:text-slate-400 dark:hover:bg-slate-700/50 dark:hover:text-theme-accent transition-all duration-200"
@click="close"
>
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-gray-200/50 dark:hover:bg-slate-700/50 text-gray-500 hover:text-[#ff1493] dark:text-slate-400 dark:hover:text-[#ff69b4] transition-all duration-200"
@click="close">
<i class="fas fa-times text-xl" />
</button>
</div>
@@ -42,69 +27,42 @@
<!-- 内容区域 -->
<div class="flex-1 overflow-y-auto p-4 sm:p-6 custom-scrollbar">
<div class="space-y-6">
<!-- 主题配色 -->
<!-- 自定义样式 -->
<div class="setting-section">
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
<i class="fas fa-palette" style="color: var(--theme-primary)" />
<span>主题配色</span>
<h3
class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
<i class="fas fa-paint-brush text-[#ff1493] dark:text-[#ff69b4]" />
<span>自定义样式</span>
</h3>
<div class="space-y-4">
<p class="text-sm text-gray-600 dark:text-slate-400">
选择您喜欢的主题色温让界面更符合您的审美
输入自定义 CSS 样式代码
</p>
<!-- 主题色卡 -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
<button
v-for="(preset, key) in THEME_PRESETS"
:key="key"
class="theme-card group relative p-4 rounded-xl border-2 transition-all duration-300 hover:scale-105"
:class="[
localTheme === key
? 'border-current shadow-lg'
: 'border-gray-200 dark:border-slate-700 hover:border-gray-300 dark:hover:border-slate-600'
]"
:style="{
color: preset.colors.primary
}"
@click="selectTheme(key as ThemePresetKey)"
>
<!-- 选中标记 -->
<div
v-if="localTheme === key"
class="absolute -top-2 -right-2 w-6 h-6 rounded-full flex items-center justify-center text-white text-xs shadow-lg"
:style="{ backgroundColor: preset.colors.primary }"
>
<i class="fas fa-check" />
</div>
<!-- 颜色预览 -->
<div class="flex gap-2 mb-3">
<div
class="w-8 h-8 rounded-lg shadow-md transition-transform group-hover:scale-110"
:style="{ backgroundColor: preset.colors.primary }"
/>
<div
class="w-8 h-8 rounded-lg shadow-md transition-transform group-hover:scale-110"
:style="{ backgroundColor: preset.colors.accent }"
/>
</div>
<!-- 主题名称 -->
<p class="text-sm font-medium text-gray-800 dark:text-slate-100">
{{ preset.name }}
</p>
</button>
<!-- CSS 代码编辑器 -->
<div class="relative">
<textarea v-model="localCustomCSS" placeholder="*:hover {
display: none;
}" rows="10" class="w-full px-4 py-3 text-sm font-mono rounded-xl bg-white dark:bg-slate-700/50 backdrop-blur-md shadow-md focus:shadow-lg focus:scale-[1.01] transition-all outline-none border-2 border-transparent text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-500 resize-none"
@focus="$event.target.style.borderColor = '#ff1493'"
@blur="$event.target.style.borderColor = 'transparent'" />
</div>
<!-- 预览提示 -->
<div class="bg-gradient-to-r from-theme-primary/5 to-theme-accent/5 dark:from-theme-primary/10 dark:to-theme-accent/10 border border-theme-primary/30 dark:border-theme-primary/50 rounded-xl p-4">
<!-- 提示信息 -->
<div
class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800/50 rounded-xl p-4">
<div class="flex items-start gap-3">
<i class="fas fa-lightbulb" style="color: var(--theme-primary)" />
<div class="flex-1 text-sm text-gray-700 dark:text-slate-300">
<p class="font-semibold mb-1">实时预览</p>
<p>选择主题后界面会立即更新颜色点击"保存"按钮以保留您的选择</p>
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 text-lg mt-0.5" />
<div class="flex-1 text-sm text-blue-700 dark:text-blue-300">
<p class="font-semibold mb-1">使用说明</p>
<ul class="list-disc list-inside space-y-1">
<li>支持标准 CSS 语法</li>
<li>使用 <code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/50 rounded">.dark</code>
选择器定义暗色模式样式</li>
<li>可以覆盖现有样式或添加新样式</li>
<li>修改后点击"保存"即可应用</li>
</ul>
</div>
</div>
</div>
@@ -113,11 +71,12 @@
<!-- API 设置 -->
<div class="setting-section">
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
<i class="fas fa-server" style="color: var(--theme-primary)" />
<h3
class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
<i class="fas fa-server text-[#ff1493] dark:text-[#ff69b4]" />
<span>API 设置</span>
</h3>
<div class="space-y-4">
<!-- 自定义 API 地址 -->
<div>
@@ -125,16 +84,12 @@
自定义 API 地址
</label>
<div class="relative">
<i class="fas fa-link absolute left-3 sm:left-4 top-3 sm:top-4 text-gray-400 text-lg sm:text-xl pointer-events-none z-10" />
<input
v-model="localCustomApi"
type="url"
placeholder="https://cfapi.searchgal.homes"
<i
class="fas fa-link absolute left-3 sm:left-4 top-3 sm:top-4 text-gray-400 text-lg sm:text-xl pointer-events-none z-10" />
<input v-model="localCustomApi" type="url" placeholder="https://cfapi.searchgal.homes"
class="w-full pl-10 sm:pl-12 pr-4 py-3 sm:py-4 text-sm sm:text-base rounded-xl bg-white dark:bg-slate-700/50 backdrop-blur-md shadow-md focus:shadow-lg focus:scale-[1.01] transition-all outline-none border-2 border-transparent text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-400"
style="border-color: transparent"
@focus="$event.target.style.borderColor = 'var(--theme-primary)'"
@blur="$event.target.style.borderColor = 'transparent'"
/>
@focus="$event.target.style.borderColor = '#ff1493'"
@blur="$event.target.style.borderColor = 'transparent'" />
</div>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-2">
留空使用默认 API 地址例如: https://cfapi.searchgal.homes 或 http://127.0.0.1:8787
@@ -142,7 +97,8 @@
</div>
<!-- API 状态 -->
<div class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800/50 rounded-xl p-4">
<div
class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800/50 rounded-xl p-4">
<div class="flex items-start gap-3">
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 text-lg mt-0.5" />
<div class="flex-1 text-sm text-blue-700 dark:text-blue-300">
@@ -157,21 +113,17 @@
</div>
<!-- 底部操作栏 -->
<div class="flex items-center justify-end gap-3 px-4 sm:px-6 py-4 border-t border-gray-200 dark:border-slate-700">
<div
class="flex items-center justify-end gap-3 px-4 sm:px-6 py-4 border-t border-gray-200 dark:border-slate-700">
<button
class="px-4 py-2 rounded-xl text-gray-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-slate-700 transition-all font-medium"
@click="reset"
>
@click="reset">
<i class="fas fa-undo mr-2" />
重置
</button>
<button
class="px-6 py-2 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl hover:scale-105 transition-all"
:style="{
background: `linear-gradient(to right, var(--theme-primary), var(--theme-primary-dark))`
}"
@click="save"
>
class="px-6 py-2 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl hover:scale-105 transition-all bg-gradient-to-r from-[#ff1493] to-[#d946ef]"
@click="save">
<i class="fas fa-check mr-2" />
保存
</button>
@@ -184,71 +136,92 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { THEME_PRESETS, DEFAULT_THEME, type ThemePresetKey } from '@/types/theme'
import { getCurrentTheme, applyThemeColors, saveTheme } from '@/utils/themeColors'
const props = defineProps<{
isOpen: boolean
customApi: string
customCSS: string
}>()
const emit = defineEmits<{
close: []
save: [customApi: string, theme: ThemePresetKey]
save: [customApi: string, customCSS: string]
}>()
const localCustomApi = ref(props.customApi)
const localTheme = ref<ThemePresetKey>(getCurrentTheme())
const originalTheme = ref<ThemePresetKey>(getCurrentTheme())
const localCustomCSS = ref(props.customCSS)
const originalCustomCSS = ref(props.customCSS)
// 监听外部变化
watch(() => props.customApi, (newValue) => {
localCustomApi.value = newValue
})
watch(() => props.customCSS, (newValue) => {
localCustomCSS.value = newValue
})
// 监听打开状态,同步数据
watch(() => props.isOpen, (isOpen) => {
if (isOpen) {
localCustomApi.value = props.customApi
localTheme.value = getCurrentTheme()
originalTheme.value = getCurrentTheme()
localCustomCSS.value = props.customCSS
originalCustomCSS.value = props.customCSS
}
})
function selectTheme(themeKey: ThemePresetKey) {
localTheme.value = themeKey
// 实时预览
applyThemeColors(themeKey)
}
function close() {
// 如果取消,恢复原来的主题
if (localTheme.value !== originalTheme.value) {
applyThemeColors(originalTheme.value)
}
emit('close')
}
function save() {
// 保存主题到 localStorage
saveTheme(localTheme.value)
// 更新原始主题,避免关闭时恢复
originalTheme.value = localTheme.value
// 发出保存事件
emit('save', localCustomApi.value, localTheme.value)
// 关闭模态框(不会恢复主题,因为 originalTheme 已更新)
emit('save', localCustomApi.value, localCustomCSS.value)
emit('close')
}
function reset() {
localCustomApi.value = ''
localTheme.value = DEFAULT_THEME
applyThemeColors(DEFAULT_THEME)
localCustomCSS.value = ''
}
</script>
<style scoped>
/* 自定义滚动条 - 使用主题色 */
/* 苹果同款液态玻璃效果 */
.glassmorphism-modal {
/* 半透明背景 - 使用渐变增强层次感 */
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.7) 0%,
rgba(255, 255, 255, 0.5) 100%);
/* 背景模糊 - 苹果级别的模糊效果 */
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
/* 边框 - 使用渐变创建高光效果 */
border: 1px solid rgba(255, 255, 255, 0.8);
box-shadow:
0 8px 32px rgba(255, 20, 147, 0.08),
0 0 0 1px rgba(255, 255, 255, 0.5) inset,
0 1px 0 0 rgba(255, 255, 255, 0.9) inset;
}
/* 暗色模式的液态玻璃 */
.dark .glassmorphism-modal {
background: linear-gradient(135deg,
rgba(30, 41, 59, 0.8) 0%,
rgba(15, 23, 42, 0.7) 100%);
backdrop-filter: blur(40px) saturate(150%);
-webkit-backdrop-filter: blur(40px) saturate(150%);
border: 1px solid rgba(255, 105, 180, 0.2);
box-shadow:
0 8px 32px rgba(255, 105, 180, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.05) inset,
0 1px 0 0 rgba(255, 255, 255, 0.1) inset;
}
/* 自定义滚动条 */
.custom-scrollbar::-webkit-scrollbar {
width: 10px;
}
@@ -259,13 +232,13 @@ function reset() {
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--theme-primary), var(--theme-accent));
background: linear-gradient(180deg, #ff1493, #d946ef);
border-radius: 10px;
transition: background 0.3s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, var(--theme-primary-dark), var(--theme-accent-dark));
background: linear-gradient(180deg, #c71585, #c026d3);
}
/* 暗色模式滚动条 */
@@ -274,11 +247,11 @@ function reset() {
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--theme-accent), var(--theme-accent-dark));
background: linear-gradient(180deg, #ff69b4, #e879f9);
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, var(--theme-accent-dark), var(--theme-accent));
background: linear-gradient(180deg, #ff1493, #d946ef);
}
/* 设置区块 */
@@ -291,10 +264,10 @@ function reset() {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -24,7 +24,7 @@
<!-- GitHub 按钮 -->
<a
href="https://github.com/Moe-Sakura/SearchGal"
href="https://github.com/Moe-Sakura"
target="_blank"
rel="noopener noreferrer"
aria-label="访问 GitHub 仓库"
@@ -41,73 +41,13 @@
>
<i class="fas fa-cog text-lg sm:text-xl" />
</button>
<!-- 主题切换按钮 -->
<button
:aria-label="`切换主题: ${getThemeLabel(themeMode)}`"
class="toolbar-button theme-button"
@click="cycleTheme"
>
<Transition
mode="out-in"
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 scale-50 rotate-90"
enter-to-class="opacity-100 scale-100 rotate-0"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 scale-100 rotate-0"
leave-to-class="opacity-0 scale-50 -rotate-90"
>
<i
v-if="themeMode === 'light'"
key="light"
class="fas fa-sun text-yellow-500 text-lg sm:text-xl"
/>
<i
v-else-if="themeMode === 'dark'"
key="dark"
class="fas fa-moon text-indigo-500 text-lg sm:text-xl"
/>
<i
v-else
key="auto"
class="fas fa-circle-half-stroke text-gray-600 dark:text-gray-400 text-lg sm:text-xl"
/>
</Transition>
</button>
<!-- 主题提示 -->
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 translate-x-2"
enter-to-class="opacity-100 translate-x-0"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 translate-x-0"
leave-to-class="opacity-0 translate-x-2"
>
<div
v-if="showThemeTip"
class="absolute top-1/2 -translate-y-1/2 right-full mr-3 px-3 py-1.5 rounded-lg bg-white/75 dark:bg-gray-800/75 backdrop-blur-xl shadow-lg border border-gray-200 dark:border-gray-700 whitespace-nowrap"
>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ getThemeLabel(themeMode) }}
</span>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { ref, computed } from 'vue'
import { useSearchStore } from '@/stores/search'
import { generateShareURL } from '@/utils/urlParams'
import {
type ThemeMode,
loadThemePreference,
saveThemePreference,
getEffectiveTheme,
applyTheme,
watchSystemTheme,
} from '@/utils/theme'
const searchStore = useSearchStore()
@@ -124,47 +64,11 @@ const emit = defineEmits<{
// 状态
const showSaveTip = ref(false)
const showCopiedTip = ref(false)
const showThemeTip = ref(false)
const themeMode = ref<ThemeMode>('auto')
let systemThemeCleanup: (() => void) | null = null
let tipTimeout: number | null = null
// 计算属性
const hasBackgroundImage = computed(() => !!props.currentBackgroundUrl)
const hasSearchResults = computed(() => searchStore.hasResults)
// 主题相关函数
function getThemeLabel(mode: ThemeMode): string {
const labels = {
light: '白天模式',
dark: '黑夜模式',
auto: '跟随系统',
}
return labels[mode]
}
function cycleTheme() {
const modes: ThemeMode[] = ['light', 'dark', 'auto']
const currentIndex = modes.indexOf(themeMode.value)
const nextIndex = (currentIndex + 1) % modes.length
themeMode.value = modes[nextIndex]
showThemeTip.value = true
if (tipTimeout) {
clearTimeout(tipTimeout)
}
tipTimeout = window.setTimeout(() => {
showThemeTip.value = false
}, 1500)
}
function updateTheme() {
const effectiveTheme = getEffectiveTheme(themeMode.value)
applyTheme(effectiveTheme)
saveThemePreference(themeMode.value)
}
// 分享搜索
async function shareSearch() {
const shareURL = generateShareURL({
@@ -281,31 +185,6 @@ async function saveBackgroundImage() {
// 静默处理
}
}
// 生命周期
onMounted(() => {
themeMode.value = loadThemePreference()
updateTheme()
systemThemeCleanup = watchSystemTheme(() => {
if (themeMode.value === 'auto') {
updateTheme()
}
})
})
onUnmounted(() => {
if (systemThemeCleanup) {
systemThemeCleanup()
}
if (tipTimeout) {
clearTimeout(tipTimeout)
}
})
watch(themeMode, () => {
updateTheme()
})
</script>
<style scoped>
@@ -313,11 +192,24 @@ watch(themeMode, () => {
width: 40px;
height: 40px;
border-radius: 50%;
background: white;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 2px solid transparent;
/* 液态玻璃效果 - 艳粉主题 */
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.85) 0%,
rgba(255, 228, 242, 0.7) 100%
);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
/* 艳粉边框和阴影 */
border: 1.5px solid rgba(255, 20, 147, 0.2);
box-shadow:
0 8px 16px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.8) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset;
color: rgb(199, 21, 133);
cursor: pointer;
display: flex;
align-items: center;
@@ -342,19 +234,50 @@ watch(themeMode, () => {
/* 暗色主题 */
.dark .toolbar-button {
background: rgba(31, 41, 55, 0.9);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
background: linear-gradient(
135deg,
rgba(51, 65, 85, 0.85) 0%,
rgba(30, 41, 59, 0.75) 100%
);
backdrop-filter: blur(20px) saturate(150%);
-webkit-backdrop-filter: blur(20px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.3);
box-shadow:
0 8px 16px rgba(255, 105, 180, 0.2),
0 0 0 1px rgba(255, 105, 180, 0.1) inset,
0 1px 0 0 rgba(255, 255, 255, 0.1) inset;
color: rgb(255, 179, 217);
}
.toolbar-button:hover {
transform: scale(1.05);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
border-color: rgba(236, 72, 153, 0.5);
transform: scale(1.05) translateY(-2px);
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(255, 228, 242, 0.85) 100%
);
box-shadow:
0 12px 24px rgba(255, 20, 147, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.9) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 30px rgba(255, 105, 180, 0.2);
border-color: rgba(255, 20, 147, 0.35);
}
.dark .toolbar-button:hover {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
border-color: rgba(168, 85, 247, 0.5);
background: linear-gradient(
135deg,
rgba(51, 65, 85, 0.95) 0%,
rgba(30, 41, 59, 0.85) 100%
);
box-shadow:
0 12px 24px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.2) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 35px rgba(255, 20, 147, 0.25);
border-color: rgba(255, 105, 180, 0.45);
}
.toolbar-button:active {
@@ -363,41 +286,31 @@ watch(themeMode, () => {
/* GitHub 按钮特殊样式 */
.github-button {
color: rgb(31, 41, 55);
text-decoration: none;
}
.dark .github-button {
color: rgb(226, 232, 240);
}
.github-button:hover {
border-color: rgba(31, 41, 55, 0.5);
}
.dark .github-button:hover {
border-color: rgba(226, 232, 240, 0.5);
}
/* 主题按钮特殊样式 */
.theme-button:hover {
border-color: rgba(245, 158, 11, 0.5);
}
/* 保存成功状态 */
/* 保存成功状态 - 艳粉渐变 */
.save-success {
background: linear-gradient(135deg, rgb(16, 185, 129), rgb(5, 150, 105)) !important;
background: linear-gradient(135deg, rgb(236, 72, 153), rgb(219, 39, 119)) !important;
color: white !important;
border-color: rgba(236, 72, 153, 0.5) !important;
box-shadow:
0 8px 20px rgba(236, 72, 153, 0.4),
0 0 30px rgba(236, 72, 153, 0.3) !important;
}
.save-success i {
color: white !important;
}
/* 分享已复制状态 */
/* 分享已复制状态 - 艳粉渐变 */
.share-copied {
background: linear-gradient(135deg, rgb(16, 185, 129), rgb(5, 150, 105)) !important;
background: linear-gradient(135deg, rgb(236, 72, 153), rgb(219, 39, 119)) !important;
color: white !important;
border-color: rgba(236, 72, 153, 0.5) !important;
box-shadow:
0 8px 20px rgba(236, 72, 153, 0.4),
0 0 30px rgba(236, 72, 153, 0.3) !important;
}
.share-copied i {

View File

@@ -10,10 +10,10 @@
>
<div
v-if="searchStore.isVndbPanelOpen && searchStore.vndbInfo"
class="fixed inset-x-2 bottom-20 sm:inset-x-auto sm:bottom-24 sm:right-6 sm:w-96 md:w-[28rem] lg:w-[32rem] max-h-[75vh] sm:max-h-[80vh] bg-white/60 dark:bg-slate-800/60 backdrop-blur-2xl backdrop-saturate-150 rounded-2xl sm:rounded-3xl shadow-2xl overflow-hidden z-30 border border-white/40 dark:border-slate-700/40"
class="glassmorphism-panel fixed inset-x-2 bottom-20 sm:inset-x-auto sm:bottom-24 sm:right-6 sm:w-96 md:w-[28rem] lg:w-[32rem] max-h-[75vh] sm:max-h-[80vh] rounded-2xl sm:rounded-3xl shadow-2xl overflow-hidden z-30"
>
<!-- 标题栏 -->
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-3 sm:py-4 bg-gradient-to-r from-theme-accent to-theme-primary text-white">
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-3 sm:py-4 bg-gradient-to-r from-[#d946ef] to-[#ff1493] text-white">
<i class="fas fa-book text-lg sm:text-xl" />
<h3 class="text-base sm:text-lg font-bold flex-1">作品介绍</h3>
<button
@@ -28,7 +28,7 @@
<div class="overflow-y-auto max-h-[calc(75vh-56px)] sm:max-h-[calc(80vh-64px)] p-3 sm:p-4 md:p-6 custom-scrollbar">
<!-- 标题 -->
<h2 class="text-xl font-bold text-gray-800 dark:text-slate-100 mb-2 flex items-center gap-2">
<i class="fas fa-gamepad text-theme-primary" />
<i class="fas fa-gamepad text-[#ff1493]" />
{{ searchStore.vndbInfo.mainName }}
</h2>
@@ -57,7 +57,7 @@
<!-- 别名 -->
<div v-if="searchStore.vndbInfo.names.length > 1" class="mb-4">
<p class="text-sm font-semibold text-gray-700 dark:text-slate-200 mb-2 flex items-center gap-1">
<i class="fas fa-tag text-theme-accent dark:text-theme-accent" />
<i class="fas fa-tag text-[#d946ef] dark:text-[#e879f9]" />
<span>别名</span>
</p>
<div class="flex flex-wrap gap-2">

View File

@@ -5,9 +5,12 @@ import App from './App.vue'
// 全局基础样式Tailwind CSS @layer base
import './styles/base.css'
// 主题色全局样式CSS 变量)
// 艳粉主题配色系统
import './styles/theme.css'
// 苹果同款液态玻璃效果
import './styles/glassmorphism.css'
// Font Awesome - 高优先级
import '@fortawesome/fontawesome-free/css/all.min.css'

View File

@@ -28,6 +28,7 @@ export interface SearchResult {
export interface PlatformData {
name: string
color: 'lime' | 'white' | 'gold' | 'red'
url?: string
items: SearchResult[]
error: string
currentPage: number

View File

@@ -25,8 +25,8 @@
margin: 0;
padding: 0;
/* 背景 - 白天模式(粉色调) */
background: rgb(255, 251, 254);
/* 透明背景 - 不遮挡背景图 */
background: transparent;
/* 文字颜色 - 白天模式 */
color: rgb(29, 27, 30);
@@ -35,10 +35,10 @@
scroll-behavior: smooth;
/* 过渡动画 */
transition: background-color 0.3s ease, color 0.3s ease;
transition: background 0.3s ease, color 0.3s ease;
}
/* 暗色模式 - 移除背景色,不遮挡随机图 */
/* 暗色模式 - 透明背景 */
.dark body {
background: transparent;
color: rgb(226, 232, 240);

View File

@@ -0,0 +1,545 @@
/**
* 苹果同款液态玻璃效果 - 艳粉主题
* Apple-style Glassmorphism with Deep Pink Theme
*/
/* ========== 搜索输入框玻璃效果 ========== */
.glassmorphism-input {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.7) 0%,
rgba(255, 228, 242, 0.5) 100%
);
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border: 1.5px solid rgba(255, 20, 147, 0.15);
box-shadow:
0 8px 24px rgba(255, 20, 147, 0.08),
0 0 0 1px rgba(255, 255, 255, 0.6) inset,
0 1px 0 0 rgba(255, 255, 255, 0.95) inset;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-input:hover {
border-color: rgba(255, 20, 147, 0.25);
box-shadow:
0 10px 28px rgba(255, 20, 147, 0.12),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 30px rgba(255, 105, 180, 0.1);
}
.glassmorphism-input:focus {
border-color: rgba(255, 20, 147, 0.4);
box-shadow:
0 12px 32px rgba(255, 20, 147, 0.18),
0 0 0 1px rgba(255, 255, 255, 0.8) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 40px rgba(255, 20, 147, 0.15);
transform: scale(1.01);
}
.dark .glassmorphism-input {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.8) 0%,
rgba(51, 65, 85, 0.7) 100%
);
backdrop-filter: blur(30px) saturate(150%);
-webkit-backdrop-filter: blur(30px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.25);
box-shadow:
0 8px 24px rgba(255, 105, 180, 0.15),
0 0 0 1px rgba(255, 105, 180, 0.08) inset,
0 1px 0 0 rgba(255, 255, 255, 0.1) inset;
}
.dark .glassmorphism-input:hover {
border-color: rgba(255, 105, 180, 0.35);
box-shadow:
0 10px 28px rgba(255, 105, 180, 0.22),
0 0 0 1px rgba(255, 105, 180, 0.12) inset,
0 1px 0 0 rgba(255, 255, 255, 0.12) inset,
0 0 35px rgba(255, 20, 147, 0.18);
}
.dark .glassmorphism-input:focus {
border-color: rgba(255, 105, 180, 0.5);
box-shadow:
0 12px 32px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.18) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 45px rgba(255, 20, 147, 0.25);
transform: scale(1.01);
}
/* ========== 搜索按钮玻璃效果 ========== */
.glassmorphism-button {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 228, 242, 0.2) 100%
);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1.5px solid rgba(255, 20, 147, 0.3);
box-shadow:
0 10px 30px rgba(255, 20, 147, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.5) inset,
0 1px 0 0 rgba(255, 255, 255, 0.8) inset;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-button:hover:not(:disabled) {
border-color: rgba(255, 20, 147, 0.5);
box-shadow:
0 14px 40px rgba(255, 20, 147, 0.35),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 50px rgba(255, 105, 180, 0.3);
transform: scale(1.02) translateY(-1px);
}
.glassmorphism-button:active:not(:disabled) {
transform: scale(0.98);
box-shadow:
0 6px 20px rgba(255, 20, 147, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.4) inset;
}
.glassmorphism-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.dark .glassmorphism-button {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.5) 0%,
rgba(51, 65, 85, 0.4) 100%
);
backdrop-filter: blur(20px) saturate(150%);
-webkit-backdrop-filter: blur(20px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.35);
box-shadow:
0 10px 30px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.1) inset,
0 1px 0 0 rgba(255, 255, 255, 0.12) inset;
}
.dark .glassmorphism-button:hover:not(:disabled) {
border-color: rgba(255, 105, 180, 0.5);
box-shadow:
0 14px 40px rgba(255, 105, 180, 0.4),
0 0 0 1px rgba(255, 105, 180, 0.15) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 55px rgba(255, 20, 147, 0.35);
transform: scale(1.02) translateY(-1px);
}
.dark .glassmorphism-button:active:not(:disabled) {
transform: scale(0.98);
box-shadow:
0 6px 20px rgba(255, 105, 180, 0.25),
0 0 0 1px rgba(255, 105, 180, 0.08) inset;
}
/* ========== 内嵌搜索按钮(输入框内部)========== */
.glassmorphism-search-button {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 228, 242, 0.1) 100%
);
backdrop-filter: blur(15px) saturate(180%);
-webkit-backdrop-filter: blur(15px) saturate(180%);
border: 1px solid rgba(255, 20, 147, 0.25);
box-shadow:
0 4px 12px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.3) inset;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-search-button:hover:not(:disabled) {
border-color: rgba(255, 20, 147, 0.4);
box-shadow:
0 6px 16px rgba(255, 20, 147, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.5) inset,
0 0 20px rgba(255, 105, 180, 0.2);
transform: translateY(-1px) scale(1.02);
}
.glassmorphism-search-button:active:not(:disabled) {
transform: scale(0.96);
box-shadow:
0 2px 8px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.2) inset;
}
.glassmorphism-search-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.dark .glassmorphism-search-button {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.3) 0%,
rgba(51, 65, 85, 0.2) 100%
);
backdrop-filter: blur(15px) saturate(150%);
-webkit-backdrop-filter: blur(15px) saturate(150%);
border: 1px solid rgba(255, 105, 180, 0.3);
box-shadow:
0 4px 12px rgba(255, 105, 180, 0.2),
0 0 0 1px rgba(255, 105, 180, 0.08) inset;
}
.dark .glassmorphism-search-button:hover:not(:disabled) {
border-color: rgba(255, 105, 180, 0.45);
box-shadow:
0 6px 16px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.12) inset,
0 0 25px rgba(255, 20, 147, 0.25);
transform: translateY(-1px) scale(1.02);
}
.dark .glassmorphism-search-button:active:not(:disabled) {
transform: scale(0.96);
box-shadow:
0 2px 8px rgba(255, 105, 180, 0.18),
0 0 0 1px rgba(255, 105, 180, 0.06) inset;
}
/* ========== 模式切换器玻璃效果 ========== */
.glassmorphism-mode-switch {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.6) 0%,
rgba(255, 228, 242, 0.45) 100%
);
backdrop-filter: blur(25px) saturate(180%);
-webkit-backdrop-filter: blur(25px) saturate(180%);
border: 1.5px solid rgba(255, 20, 147, 0.2);
box-shadow:
0 8px 24px rgba(255, 20, 147, 0.12),
0 0 0 1px rgba(255, 255, 255, 0.6) inset,
0 1px 0 0 rgba(255, 255, 255, 0.9) inset;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-mode-switch:hover {
border-color: rgba(255, 20, 147, 0.3);
box-shadow:
0 10px 28px rgba(255, 20, 147, 0.18),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 35px rgba(255, 105, 180, 0.15);
}
.dark .glassmorphism-mode-switch {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.75) 0%,
rgba(51, 65, 85, 0.65) 100%
);
backdrop-filter: blur(25px) saturate(150%);
-webkit-backdrop-filter: blur(25px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.28);
box-shadow:
0 8px 24px rgba(255, 105, 180, 0.18),
0 0 0 1px rgba(255, 105, 180, 0.1) inset,
0 1px 0 0 rgba(255, 255, 255, 0.12) inset;
}
.dark .glassmorphism-mode-switch:hover {
border-color: rgba(255, 105, 180, 0.38);
box-shadow:
0 10px 28px rgba(255, 105, 180, 0.25),
0 0 0 1px rgba(255, 105, 180, 0.15) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 40px rgba(255, 20, 147, 0.22);
}
/* ========== 模态框液态玻璃效果 ========== */
.glassmorphism-modal {
/* 半透明背景 - 使用粉色渐变增强层次感 */
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.75) 0%,
rgba(255, 228, 242, 0.6) 50%,
rgba(255, 179, 217, 0.5) 100%
);
/* 背景模糊 - 苹果级别的模糊效果 */
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
/* 边框 - 粉色高光效果 */
border: 1.5px solid rgba(255, 20, 147, 0.15);
box-shadow:
0 8px 32px rgba(255, 20, 147, 0.12),
0 0 0 1px rgba(255, 255, 255, 0.6) inset,
0 1px 0 0 rgba(255, 255, 255, 0.95) inset,
0 0 40px rgba(255, 105, 180, 0.08);
/* 圆角 */
border-radius: 24px;
/* 过渡动画 */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-modal:hover {
border-color: rgba(255, 20, 147, 0.25);
box-shadow:
0 12px 40px rgba(255, 20, 147, 0.18),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 50px rgba(255, 105, 180, 0.15);
}
/* 暗色模式的液态玻璃 */
.dark .glassmorphism-modal {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.85) 0%,
rgba(51, 65, 85, 0.75) 50%,
rgba(15, 23, 42, 0.8) 100%
);
backdrop-filter: blur(40px) saturate(150%);
-webkit-backdrop-filter: blur(40px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.25);
box-shadow:
0 8px 32px rgba(255, 105, 180, 0.2),
0 0 0 1px rgba(255, 105, 180, 0.1) inset,
0 1px 0 0 rgba(255, 255, 255, 0.12) inset,
0 0 50px rgba(255, 20, 147, 0.15);
}
.dark .glassmorphism-modal:hover {
border-color: rgba(255, 105, 180, 0.35);
box-shadow:
0 12px 40px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.15) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 60px rgba(255, 20, 147, 0.25);
}
/* ========== 面板液态玻璃效果 ========== */
.glassmorphism-panel {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.8) 0%,
rgba(255, 228, 242, 0.65) 50%,
rgba(255, 179, 217, 0.55) 100%
);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 1.5px solid rgba(255, 20, 147, 0.18);
box-shadow:
0 12px 40px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 50px rgba(255, 105, 180, 0.1);
border-radius: 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-panel:hover {
border-color: rgba(255, 20, 147, 0.3);
box-shadow:
0 16px 48px rgba(255, 20, 147, 0.22),
0 0 0 1px rgba(255, 255, 255, 0.8) inset,
0 1px 0 0 rgba(255, 255, 255, 1) inset,
0 0 60px rgba(255, 105, 180, 0.18);
transform: translateY(-2px);
}
.dark .glassmorphism-panel {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.9) 0%,
rgba(51, 65, 85, 0.8) 50%,
rgba(15, 23, 42, 0.85) 100%
);
backdrop-filter: blur(40px) saturate(150%);
-webkit-backdrop-filter: blur(40px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.3);
box-shadow:
0 12px 40px rgba(255, 105, 180, 0.25),
0 0 0 1px rgba(255, 105, 180, 0.12) inset,
0 1px 0 0 rgba(255, 255, 255, 0.15) inset,
0 0 50px rgba(255, 20, 147, 0.2);
}
.dark .glassmorphism-panel:hover {
border-color: rgba(255, 105, 180, 0.4);
box-shadow:
0 16px 48px rgba(255, 105, 180, 0.35),
0 0 0 1px rgba(255, 105, 180, 0.18) inset,
0 1px 0 0 rgba(255, 255, 255, 0.18) inset,
0 0 60px rgba(255, 20, 147, 0.3);
transform: translateY(-2px);
}
/* ========== 工具栏按钮玻璃效果 ========== */
.glassmorphism-toolbar-button {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.4) 0%,
rgba(255, 228, 242, 0.3) 100%
);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 20, 147, 0.15);
box-shadow:
0 4px 12px rgba(255, 20, 147, 0.08),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
border-radius: 12px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-toolbar-button:hover {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.6) 0%,
rgba(255, 228, 242, 0.5) 100%
);
border-color: rgba(255, 20, 147, 0.25);
box-shadow:
0 6px 16px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 0 20px rgba(255, 105, 180, 0.1);
transform: translateY(-1px);
}
.glassmorphism-toolbar-button:active {
transform: translateY(0);
box-shadow:
0 2px 8px rgba(255, 20, 147, 0.12),
0 0 0 1px rgba(255, 255, 255, 0.4) inset;
}
.dark .glassmorphism-toolbar-button {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.6) 0%,
rgba(51, 65, 85, 0.5) 100%
);
backdrop-filter: blur(20px) saturate(150%);
-webkit-backdrop-filter: blur(20px) saturate(150%);
border: 1px solid rgba(255, 105, 180, 0.2);
box-shadow:
0 4px 12px rgba(255, 105, 180, 0.15),
0 0 0 1px rgba(255, 105, 180, 0.08) inset;
}
.dark .glassmorphism-toolbar-button:hover {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.8) 0%,
rgba(51, 65, 85, 0.7) 100%
);
border-color: rgba(255, 105, 180, 0.35);
box-shadow:
0 6px 16px rgba(255, 105, 180, 0.25),
0 0 0 1px rgba(255, 105, 180, 0.15) inset,
0 0 25px rgba(255, 20, 147, 0.2);
transform: translateY(-1px);
}
.dark .glassmorphism-toolbar-button:active {
transform: translateY(0);
box-shadow:
0 2px 8px rgba(255, 105, 180, 0.18),
0 0 0 1px rgba(255, 105, 180, 0.1) inset;
}
/* ========== 卡片玻璃效果 ========== */
.glassmorphism-card {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.65) 0%,
rgba(255, 228, 242, 0.5) 50%,
rgba(255, 179, 217, 0.45) 100%
);
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border: 1px solid rgba(255, 20, 147, 0.12);
box-shadow:
0 8px 24px rgba(255, 20, 147, 0.1),
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
border-radius: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glassmorphism-card:hover {
border-color: rgba(255, 20, 147, 0.2);
box-shadow:
0 12px 32px rgba(255, 20, 147, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.7) inset,
0 0 30px rgba(255, 105, 180, 0.12);
transform: translateY(-2px);
}
.dark .glassmorphism-card {
background: linear-gradient(
135deg,
rgba(30, 41, 59, 0.75) 0%,
rgba(51, 65, 85, 0.65) 50%,
rgba(15, 23, 42, 0.7) 100%
);
backdrop-filter: blur(30px) saturate(150%);
-webkit-backdrop-filter: blur(30px) saturate(150%);
border: 1px solid rgba(255, 105, 180, 0.22);
box-shadow:
0 8px 24px rgba(255, 105, 180, 0.18),
0 0 0 1px rgba(255, 105, 180, 0.08) inset;
}
.dark .glassmorphism-card:hover {
border-color: rgba(255, 105, 180, 0.32);
box-shadow:
0 12px 32px rgba(255, 105, 180, 0.28),
0 0 0 1px rgba(255, 105, 180, 0.15) inset,
0 0 35px rgba(255, 20, 147, 0.22);
transform: translateY(-2px);
}

View File

@@ -1,241 +1,131 @@
/**
* 主题色全局样式
* 使用 CSS 变量实现动态主题切换
* 艳粉主题配色方案
* Deep Pink Color Scheme
*/
/* 主题色工具类 - 使用 CSS 变量 */
.text-theme-primary {
color: var(--theme-primary);
:root {
/* ========== 主色调 - 艳粉色系 ========== */
--color-primary: #ff1493; /* DeepPink - 主要色 */
--color-primary-light: #ff69b4; /* HotPink - 浅色 */
--color-primary-lighter: #ffb3d9; /* 更浅的粉色 */
--color-primary-dark: #c71585; /* MediumVioletRed - 深色 */
--color-primary-darker: #8b0a50; /* 更深的粉色 */
/* ========== 渐变色 ========== */
--gradient-primary: linear-gradient(135deg, #ff1493 0%, #d946ef 100%);
--gradient-primary-hover: linear-gradient(135deg, #ff69b4 0%, #e879f9 100%);
--gradient-primary-reverse: linear-gradient(135deg, #d946ef 0%, #ff1493 100%);
/* ========== 辅助色 ========== */
--color-accent: #d946ef; /* 紫色辅助色 */
--color-accent-light: #e879f9;
--color-accent-dark: #c026d3;
/* ========== 背景色 ========== */
--bg-primary: #fff5fa; /* 浅粉色背景 */
--bg-secondary: #ffe4f2; /* 粉色背景 */
--bg-tertiary: #ffb3d9; /* 更深的粉色背景 */
/* ========== 文本色 ========== */
--text-primary: #1d1b1e; /* 主要文本 */
--text-secondary: #6b7280; /* 次要文本 */
--text-tertiary: #9ca3af; /* 第三级文本 */
/* ========== 阴影 ========== */
--shadow-sm: 0 1px 2px rgba(255, 20, 147, 0.1);
--shadow-md: 0 4px 6px rgba(255, 20, 147, 0.15);
--shadow-lg: 0 10px 15px rgba(255, 20, 147, 0.2);
--shadow-xl: 0 20px 25px rgba(255, 20, 147, 0.25);
--shadow-glow: 0 0 20px rgba(255, 20, 147, 0.4);
/* ========== 玻璃态效果 ========== */
--glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(255, 255, 255, 0.8);
--glass-shadow: 0 8px 32px rgba(255, 20, 147, 0.08);
}
.text-theme-primary-dark {
color: var(--theme-primary-dark);
/* 暗色模式 */
.dark {
/* ========== 主色调 ========== */
--color-primary: #ff69b4;
--color-primary-light: #ffb3d9;
--color-primary-lighter: #ffc9e6;
--color-primary-dark: #ff1493;
--color-primary-darker: #c71585;
/* ========== 渐变色 ========== */
--gradient-primary: linear-gradient(135deg, #ff69b4 0%, #e879f9 100%);
--gradient-primary-hover: linear-gradient(135deg, #ffb3d9 0%, #f0abfc 100%);
--gradient-primary-reverse: linear-gradient(135deg, #e879f9 0%, #ff69b4 100%);
/* ========== 辅助色 ========== */
--color-accent: #e879f9;
--color-accent-light: #f0abfc;
--color-accent-dark: #d946ef;
/* ========== 背景色 ========== */
--bg-primary: #0f172a; /* 深色背景 */
--bg-secondary: #1e293b; /* 次要背景 */
--bg-tertiary: #334155; /* 第三级背景 */
/* ========== 文本色 ========== */
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
/* ========== 阴影 ========== */
--shadow-sm: 0 1px 2px rgba(255, 105, 180, 0.15);
--shadow-md: 0 4px 6px rgba(255, 105, 180, 0.2);
--shadow-lg: 0 10px 15px rgba(255, 105, 180, 0.25);
--shadow-xl: 0 20px 25px rgba(255, 105, 180, 0.3);
--shadow-glow: 0 0 25px rgba(255, 105, 180, 0.5);
/* ========== 玻璃态效果 ========== */
--glass-bg: rgba(30, 41, 59, 0.8);
--glass-border: rgba(255, 105, 180, 0.2);
--glass-shadow: 0 8px 32px rgba(255, 105, 180, 0.15);
}
.text-theme-accent {
color: var(--theme-accent);
}
.text-theme-accent-dark {
color: var(--theme-accent-dark);
}
.bg-theme-primary {
background-color: var(--theme-primary);
}
.bg-theme-primary-dark {
background-color: var(--theme-primary-dark);
}
.bg-theme-accent {
background-color: var(--theme-accent);
}
.bg-theme-accent-dark {
background-color: var(--theme-accent-dark);
}
.border-theme-primary {
border-color: var(--theme-primary);
}
.border-theme-accent {
border-color: var(--theme-accent);
}
/* 渐变背景 */
.bg-gradient-theme {
background: linear-gradient(to right, var(--theme-primary), var(--theme-accent));
}
.bg-gradient-theme-vertical {
background: linear-gradient(to bottom, var(--theme-primary), var(--theme-accent));
}
/* Hover 状态 */
.hover\:text-theme-primary:hover {
color: var(--theme-primary);
}
.hover\:text-theme-primary-dark:hover {
color: var(--theme-primary-dark);
}
.hover\:bg-theme-primary:hover {
background-color: var(--theme-primary);
}
.hover\:bg-theme-primary-dark:hover {
background-color: var(--theme-primary-dark);
}
.hover\:border-theme-primary:hover {
border-color: var(--theme-primary);
}
/* Focus 状态 */
.focus\:border-theme-primary:focus {
border-color: var(--theme-primary);
}
.focus\:ring-theme-primary:focus {
--tw-ring-color: var(--theme-primary);
}
/* 带透明度的主题色 */
.bg-theme-primary\/80 {
background-color: color-mix(in srgb, var(--theme-primary) 80%, transparent);
}
.bg-theme-primary\/70 {
background-color: color-mix(in srgb, var(--theme-primary) 70%, transparent);
}
.bg-theme-primary\/60 {
background-color: color-mix(in srgb, var(--theme-primary) 60%, transparent);
}
.bg-theme-primary\/10 {
background-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
}
.bg-theme-primary\/5 {
background-color: color-mix(in srgb, var(--theme-primary) 5%, transparent);
}
.bg-theme-accent\/80 {
background-color: color-mix(in srgb, var(--theme-accent) 80%, transparent);
}
.bg-theme-accent\/70 {
background-color: color-mix(in srgb, var(--theme-accent) 70%, transparent);
}
.bg-theme-accent\/30 {
background-color: color-mix(in srgb, var(--theme-accent) 30%, transparent);
}
.bg-theme-accent\/10 {
background-color: color-mix(in srgb, var(--theme-accent) 10%, transparent);
}
.text-theme-primary\/80 {
color: color-mix(in srgb, var(--theme-primary) 80%, transparent);
}
.text-theme-accent\/70 {
color: color-mix(in srgb, var(--theme-accent) 70%, transparent);
}
.border-theme-primary\/60 {
border-color: color-mix(in srgb, var(--theme-primary) 60%, transparent);
}
.border-theme-primary\/30 {
border-color: color-mix(in srgb, var(--theme-primary) 30%, transparent);
}
.border-theme-primary\/20 {
border-color: color-mix(in srgb, var(--theme-primary) 20%, transparent);
}
.border-theme-primary\/10 {
border-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
}
/* Hover 状态带透明度 */
.hover\:bg-theme-primary\/10:hover {
background-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
}
.hover\:border-theme-primary\/30:hover {
border-color: color-mix(in srgb, var(--theme-primary) 30%, transparent);
}
.group:hover .group-hover\:text-theme-primary {
color: var(--theme-primary);
}
.group:hover .group-hover\:text-theme-primary\/40 {
color: color-mix(in srgb, var(--theme-primary) 40%, transparent);
}
/* 渐变背景(带透明度) */
.from-theme-primary\/70 {
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 70%, transparent);
--tw-gradient-to: rgb(255 255 255 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-theme-accent\/70 {
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 70%, transparent);
}
.from-theme-primary\/80 {
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 80%, transparent);
--tw-gradient-to: rgb(255 255 255 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-theme-accent\/80 {
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 80%, transparent);
}
.from-theme-primary\/90 {
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 90%, transparent);
--tw-gradient-to: rgb(255 255 255 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-theme-accent\/90 {
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 90%, transparent);
}
.from-theme-primary {
--tw-gradient-from: var(--theme-primary);
--tw-gradient-to: rgb(255 255 255 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-theme-accent {
--tw-gradient-to: var(--theme-accent);
}
.to-theme-primary-dark {
--tw-gradient-to: var(--theme-primary-dark);
}
/* 暗色模式下的主题色 */
.dark .dark\:text-theme-accent {
color: var(--theme-accent);
}
.dark .dark\:text-theme-primary {
color: var(--theme-primary);
}
.dark .dark\:from-theme-accent\/70 {
--tw-gradient-from: color-mix(in srgb, var(--theme-accent) 70%, transparent);
}
.dark .dark\:to-theme-accent-dark\/70 {
--tw-gradient-to: color-mix(in srgb, var(--theme-accent-dark) 70%, transparent);
}
.dark .dark\:from-theme-accent\/80 {
--tw-gradient-from: color-mix(in srgb, var(--theme-accent) 80%, transparent);
}
.dark .dark\:to-theme-accent-dark\/80 {
--tw-gradient-to: color-mix(in srgb, var(--theme-accent-dark) 80%, transparent);
}
.dark .dark\:focus\:border-theme-accent:focus {
border-color: var(--theme-accent);
}
.dark .dark\:focus\:border-theme-accent\/60:focus {
border-color: color-mix(in srgb, var(--theme-accent) 60%, transparent);
/* ========== Tailwind CSS 主题颜色扩展 ========== */
@layer utilities {
/* 文本渐变 */
.text-gradient-pink {
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 背景渐变 */
.bg-gradient-pink {
background: var(--gradient-primary);
}
.bg-gradient-pink-reverse {
background: var(--gradient-primary-reverse);
}
/* 边框渐变 */
.border-gradient-pink {
border: 1px solid transparent;
background: linear-gradient(white, white) padding-box,
var(--gradient-primary) border-box;
}
/* 阴影 */
.shadow-pink-sm { box-shadow: var(--shadow-sm); }
.shadow-pink-md { box-shadow: var(--shadow-md); }
.shadow-pink-lg { box-shadow: var(--shadow-lg); }
.shadow-pink-xl { box-shadow: var(--shadow-xl); }
.shadow-pink-glow { box-shadow: var(--shadow-glow); }
/* 悬停效果 */
.hover-lift {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hover-lift:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
}

View File

@@ -1,89 +0,0 @@
// 主题配色方案类型定义
export interface ThemeColors {
primary: string
primaryDark: string
accent: string
accentDark: string
}
// 预设主题配色
export const THEME_PRESETS = {
rose: {
name: '玫瑰粉',
colors: {
primary: '#ec4899', // pink-500
primaryDark: '#db2777', // pink-600
accent: '#8b5cf6', // purple-500
accentDark: '#7c3aed', // purple-600
},
},
sky: {
name: '天空蓝',
colors: {
primary: '#0ea5e9', // sky-500
primaryDark: '#0284c7', // sky-600
accent: '#06b6d4', // cyan-500
accentDark: '#0891b2', // cyan-600
},
},
emerald: {
name: '翡翠绿',
colors: {
primary: '#10b981', // emerald-500
primaryDark: '#059669', // emerald-600
accent: '#14b8a6', // teal-500
accentDark: '#0d9488', // teal-600
},
},
amber: {
name: '琥珀橙',
colors: {
primary: '#f59e0b', // amber-500
primaryDark: '#d97706', // amber-600
accent: '#f97316', // orange-500
accentDark: '#ea580c', // orange-600
},
},
violet: {
name: '紫罗兰',
colors: {
primary: '#8b5cf6', // violet-500
primaryDark: '#7c3aed', // violet-600
accent: '#a855f7', // purple-500
accentDark: '#9333ea', // purple-600
},
},
red: {
name: '热情红',
colors: {
primary: '#ef4444', // red-500
primaryDark: '#dc2626', // red-600
accent: '#f43f5e', // rose-500
accentDark: '#e11d48', // rose-600
},
},
indigo: {
name: '靛青蓝',
colors: {
primary: '#6366f1', // indigo-500
primaryDark: '#4f46e5', // indigo-600
accent: '#8b5cf6', // violet-500
accentDark: '#7c3aed', // violet-600
},
},
fuchsia: {
name: '洋红紫',
colors: {
primary: '#d946ef', // fuchsia-500
primaryDark: '#c026d3', // fuchsia-600
accent: '#ec4899', // pink-500
accentDark: '#db2777', // pink-600
},
},
} as const
export type ThemePresetKey = keyof typeof THEME_PRESETS
// 默认主题
export const DEFAULT_THEME: ThemePresetKey = 'rose'

View File

@@ -1,11 +1,9 @@
/**
* 主题管理工具
* 支持白天/黑夜模式和跟随系统
* 自动跟随系统主题
*/
export type ThemeMode = 'light' | 'dark' | 'auto'
const THEME_STORAGE_KEY = 'searchgal_theme'
const CUSTOM_CSS_STORAGE_KEY = 'searchgal_custom_css'
/**
* 获取系统主题偏好
@@ -17,16 +15,6 @@ export function getSystemTheme(): 'light' | 'dark' {
return 'light'
}
/**
* 获取当前应该应用的主题(考虑 auto 模式)
*/
export function getEffectiveTheme(mode: ThemeMode): 'light' | 'dark' {
if (mode === 'auto') {
return getSystemTheme()
}
return mode
}
/**
* 应用主题到 DOM
*/
@@ -40,32 +28,6 @@ export function applyTheme(theme: 'light' | 'dark'): void {
}
}
/**
* 保存主题偏好
*/
export function saveThemePreference(mode: ThemeMode): void {
try {
localStorage.setItem(THEME_STORAGE_KEY, mode)
} catch (error) {
// 静默处理
}
}
/**
* 加载主题偏好
*/
export function loadThemePreference(): ThemeMode {
try {
const saved = localStorage.getItem(THEME_STORAGE_KEY)
if (saved === 'light' || saved === 'dark' || saved === 'auto') {
return saved
}
} catch (error) {
// 静默处理
}
return 'auto' // 默认跟随系统
}
/**
* 监听系统主题变化
*/
@@ -87,3 +49,44 @@ export function watchSystemTheme(callback: (theme: 'light' | 'dark') => void): (
}
}
/**
* 保存自定义CSS
*/
export function saveCustomCSS(css: string): void {
try {
localStorage.setItem(CUSTOM_CSS_STORAGE_KEY, css)
} catch (error) {
// 静默处理
}
}
/**
* 加载自定义CSS
*/
export function loadCustomCSS(): string {
try {
return localStorage.getItem(CUSTOM_CSS_STORAGE_KEY) || ''
} catch (error) {
return ''
}
}
/**
* 应用自定义CSS到页面
*/
export function applyCustomCSS(css: string): void {
// 移除旧的自定义样式
const oldStyle = document.getElementById('custom-user-styles')
if (oldStyle) {
oldStyle.remove()
}
// 如果有新的CSS添加到页面
if (css.trim()) {
const style = document.createElement('style')
style.id = 'custom-user-styles'
style.textContent = css
document.head.appendChild(style)
}
}

View File

@@ -1,72 +0,0 @@
import { THEME_PRESETS, DEFAULT_THEME, type ThemePresetKey } from '@/types/theme'
const THEME_STORAGE_KEY = 'searchgal-theme-preset'
/**
* 应用主题颜色到 CSS 变量
*/
export function applyThemeColors(themeKey: ThemePresetKey) {
const theme = THEME_PRESETS[themeKey]
if (!theme) {
return
}
const root = document.documentElement
const { colors } = theme
// 设置 CSS 变量
root.style.setProperty('--theme-primary', colors.primary)
root.style.setProperty('--theme-primary-dark', colors.primaryDark)
root.style.setProperty('--theme-accent', colors.accent)
root.style.setProperty('--theme-accent-dark', colors.accentDark)
}
/**
* 获取当前主题
*/
export function getCurrentTheme(): ThemePresetKey {
try {
const saved = localStorage.getItem(THEME_STORAGE_KEY)
if (saved && saved in THEME_PRESETS) {
return saved as ThemePresetKey
}
} catch {
// 忽略错误
}
return DEFAULT_THEME
}
/**
* 保存主题设置
*/
export function saveTheme(themeKey: ThemePresetKey) {
try {
localStorage.setItem(THEME_STORAGE_KEY, themeKey)
applyThemeColors(themeKey)
} catch {
// 忽略错误
}
}
/**
* 初始化主题
*/
export function initTheme() {
const currentTheme = getCurrentTheme()
applyThemeColors(currentTheme)
}
/**
* 获取主题颜色的 RGB 值(用于 Tailwind 的 opacity 修饰符)
*/
export function hexToRgb(hex: string): string {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
if (!result) {
return '0, 0, 0'
}
const r = parseInt(result[1], 16)
const g = parseInt(result[2], 16)
const b = parseInt(result[3], 16)
return `${r}, ${g}, ${b}`
}

View File

@@ -8,26 +8,119 @@ export default {
theme: {
extend: {
colors: {
// 自定义暗色主题
// 艳粉主题
theme: {
primary: '#ff1493', // DeepPink - 主色
'primary-light': '#ff69b4', // HotPink - 浅色
'primary-lighter': '#ffb3d9', // 更浅
'primary-dark': '#c71585', // 深色
'primary-darker': '#8b0a50', // 更深
accent: '#d946ef', // 紫色辅助色
'accent-light': '#e879f9', // 浅紫色
'accent-dark': '#c026d3', // 深紫色
},
// 暗色主题颜色
dark: {
bg: {
primary: '#0f172a', // 主背景
secondary: '#1e293b', // 次要背景
tertiary: '#334155', // 第三背景
primary: '#0f172a', // 主背景
secondary: '#1e293b', // 次要背景
tertiary: '#334155', // 第三背景
},
text: {
primary: '#e2e8f0', // 主文本
secondary: '#cbd5e1', // 次要文本
tertiary: '#94a3b8', // 第三文本
primary: '#e2e8f0', // 主文本
secondary: '#cbd5e1', // 次要文本
tertiary: '#94a3b8', // 第三文本
},
border: {
DEFAULT: 'rgba(139, 92, 246, 0.3)',
hover: 'rgba(139, 92, 246, 0.5)',
DEFAULT: 'rgba(255, 105, 180, 0.3)', // 粉色边框
hover: 'rgba(255, 105, 180, 0.5)', // 悬停粉色边框
}
},
// 艳粉色系扩展
'deep-pink': {
50: '#fff5fa',
100: '#ffe4f2',
200: '#ffc9e6',
300: '#ffb3d9',
400: '#ff94cf',
500: '#ff1493', // 主色
600: '#d946ef',
700: '#c71585',
800: '#a0116b',
900: '#8b0a50',
},
// 粉紫色系
'pink-purple': {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
}
}
},
backgroundImage: {
'gradient-pink': 'linear-gradient(135deg, #ff1493 0%, #d946ef 100%)',
'gradient-pink-light': 'linear-gradient(135deg, #ff69b4 0%, #e879f9 100%)',
'gradient-pink-reverse': 'linear-gradient(135deg, #d946ef 0%, #ff1493 100%)',
'gradient-pink-soft': 'linear-gradient(135deg, #ffe4f2 0%, #ffc9e6 100%)',
},
boxShadow: {
'pink-sm': '0 1px 2px rgba(255, 20, 147, 0.1)',
'pink-md': '0 4px 6px rgba(255, 20, 147, 0.15)',
'pink-lg': '0 10px 15px rgba(255, 20, 147, 0.2)',
'pink-xl': '0 20px 25px rgba(255, 20, 147, 0.25)',
'pink-glow': '0 0 20px rgba(255, 20, 147, 0.4)',
'pink-glow-lg': '0 0 40px rgba(255, 20, 147, 0.6)',
},
animation: {
'fade-in': 'fadeIn 0.5s ease-out',
'fade-in-up': 'fadeInUp 0.6s ease-out',
'fade-in-down': 'fadeInDown 0.6s ease-out',
'scale-in': 'scaleIn 0.3s ease-out',
'pulse-pink': 'pulsePink 2s ease-in-out infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
fadeInDown: {
'0%': { opacity: '0', transform: 'translateY(-20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
scaleIn: {
'0%': { opacity: '0', transform: 'scale(0.9)' },
'100%': { opacity: '1', transform: 'scale(1)' },
},
pulsePink: {
'0%, 100%': {
boxShadow: '0 0 20px rgba(255, 20, 147, 0.4)',
opacity: '1'
},
'50%': {
boxShadow: '0 0 40px rgba(255, 20, 147, 0.6)',
opacity: '0.9'
},
},
},
},
},
plugins: [],
}