mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-03-26 06:59:44 +08:00
127
THEME_SYSTEM.md
Normal file
127
THEME_SYSTEM.md
Normal 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. 测试两种模式下的显示效果,确保可读性
|
||||
|
||||
85
index.html
85
index.html
@@ -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;
|
||||
|
||||
38
src/App.vue
38
src/App.vue
@@ -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>
|
||||
|
||||
|
||||
@@ -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 || '',
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
// 静默处理错误
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
545
src/styles/glassmorphism.css
Normal file
545
src/styles/glassmorphism.css
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user