feat(ui): 全站统一首页墨黑中性风格,固定亮色并移除光暗切换

- 全站紫/青/粉写死色值与 CSS 变量改为墨黑中性色板
- 移除 aurora 光斑/星点装饰,登录弹窗改纯白卡片
- theme.js 固定亮色并清除历史 dark 选择,删除前台/后台/画布切换按钮
- 模型页空状态文案对齐「保存渠道即自动同步」

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-27 16:17:55 +08:00
co-authored by Devin
parent 8b85d5623a
commit 4d31219368
22 changed files with 155 additions and 240 deletions
+4 -4
View File
@@ -7,8 +7,8 @@ const contact = computed(() => site.contact || {})
const powers = [
{ icon: 't2i', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', tint: 'linear-gradient(135deg,#34d399,#2dd4bf)' },
{ icon: 'i2i', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', tint: 'linear-gradient(135deg,#e879f9,#f472b6)' },
{ icon: 't2v', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)' },
{ icon: 'i2v', title: '图生视频', desc: '给张参考图,让静态画面动起来。', tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)' },
{ icon: 't2v', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', tint: 'linear-gradient(135deg,#38bdf8,#52525b)' },
{ icon: 'i2v', title: '图生视频', desc: '给张参考图,让静态画面动起来。', tint: 'linear-gradient(135deg,#18181b,#3f3f46)' },
]
</script>
@@ -154,7 +154,7 @@ const powers = [
box-shadow: var(--glass-shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.power-card:hover { transform: translateY(-3px); border-color: rgb(124 92 252 / 0.45); box-shadow: var(--glass-shadow-lg); }
.power-card:hover { transform: translateY(-3px); border-color: rgb(24 24 27 / 0.45); box-shadow: var(--glass-shadow-lg); }
.power-ico {
width: 2.6rem;
height: 2.6rem;
@@ -162,7 +162,7 @@ const powers = [
display: grid;
place-items: center;
color: #fff;
box-shadow: 0 6px 16px rgb(124 92 252 / 0.28);
box-shadow: 0 6px 16px rgb(24 24 27 / 0.28);
}
.power-ico svg { width: 1.3rem; height: 1.3rem; }
@media (max-width: 768px) { .ab-cluster { display: none; } }
+22 -22
View File
@@ -1119,7 +1119,7 @@ const saveLabel = computed(() => ({
}
.cv-btn:hover:not(:disabled) {
background: var(--hover);
border-color: rgb(124 92 252 / 0.45);
border-color: rgb(24 24 27 / 0.45);
box-shadow: 0 4px 14px var(--accent-glow);
transform: translateY(-1px);
}
@@ -1136,13 +1136,13 @@ const saveLabel = computed(() => ({
box-shadow: 0 4px 14px rgb(229 72 77 / 0.25);
}
.cv-btn--accent {
background: linear-gradient(120deg, rgb(124 92 252 / 0.16), rgb(168 85 247 / 0.14));
border-color: rgb(124 92 252 / 0.42);
background: linear-gradient(120deg, rgb(24 24 27 / 0.16), rgb(168 85 247 / 0.14));
border-color: rgb(24 24 27 / 0.42);
color: var(--m-orange);
}
.cv-btn--accent:hover:not(:disabled) {
background: linear-gradient(120deg, rgb(124 92 252 / 0.26), rgb(168 85 247 / 0.22));
border-color: rgb(124 92 252 / 0.62);
background: linear-gradient(120deg, rgb(24 24 27 / 0.26), rgb(168 85 247 / 0.22));
border-color: rgb(24 24 27 / 0.62);
box-shadow: 0 4px 16px var(--accent-glow);
}
.cv-select,
@@ -1157,8 +1157,8 @@ const saveLabel = computed(() => ({
}
.cv-select:focus,
.cv-input:focus {
border-color: rgb(124 92 252 / 0.6);
box-shadow: 0 0 0 3px rgb(124 92 252 / 0.18);
border-color: rgb(24 24 27 / 0.6);
box-shadow: 0 0 0 3px rgb(24 24 27 / 0.18);
}
.cv-page {
@@ -1225,7 +1225,7 @@ const saveLabel = computed(() => ({
min-width: 46px;
text-align: center;
background: var(--hover);
border: 1px solid rgb(124 92 252 / 0.35);
border: 1px solid rgb(24 24 27 / 0.35);
border-radius: 999px;
padding: 1px 8px;
}
@@ -1266,9 +1266,9 @@ const saveLabel = computed(() => ({
font-size: 15px;
font-weight: 800;
color: var(--fg);
border: 2px dashed rgb(34 211 238 / 0.6);
border: 2px dashed rgb(24 24 27 / 0.6);
border-radius: 18px;
background: rgb(34 211 238 / 0.1);
background: rgb(24 24 27 / 0.1);
pointer-events: none;
}
.cv-loading,
@@ -1318,11 +1318,11 @@ const saveLabel = computed(() => ({
}
.cv-chip:hover {
background: var(--hover);
border-color: rgb(124 92 252 / 0.4);
border-color: rgb(24 24 27 / 0.4);
}
.cv-chip.is-on {
color: #fff;
background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
background: linear-gradient(120deg, #18181b, #27272a);
border-color: transparent;
box-shadow: 0 4px 14px var(--accent-glow);
}
@@ -1332,7 +1332,7 @@ const saveLabel = computed(() => ({
border: 1px solid var(--glass-border);
border-radius: 22px;
background:
linear-gradient(160deg, rgb(124 92 252 / 0.06), transparent 40%),
linear-gradient(160deg, rgb(24 24 27 / 0.06), transparent 40%),
var(--surface);
-webkit-backdrop-filter: blur(22px) saturate(1.15);
backdrop-filter: blur(22px) saturate(1.15);
@@ -1341,8 +1341,8 @@ const saveLabel = computed(() => ({
overflow: visible; /* 下拉面板要从输入框上方弹出,不能被裁切 */
}
.cv-composer:focus-within {
border-color: rgb(124 92 252 / 0.55);
box-shadow: var(--glass-shadow-lg), 0 0 0 3px rgb(124 92 252 / 0.16), 0 0 42px var(--accent-glow);
border-color: rgb(24 24 27 / 0.55);
box-shadow: var(--glass-shadow-lg), 0 0 0 3px rgb(24 24 27 / 0.16), 0 0 42px var(--accent-glow);
}
.cv-composer__refs {
display: flex;
@@ -1399,8 +1399,8 @@ const saveLabel = computed(() => ({
width: 56px;
height: 56px;
border-radius: 12px;
border: 1.5px dashed rgb(124 92 252 / 0.5);
background: rgb(124 92 252 / 0.04);
border: 1.5px dashed rgb(24 24 27 / 0.5);
background: rgb(24 24 27 / 0.04);
color: var(--m-orange);
display: flex;
flex-direction: column;
@@ -1414,7 +1414,7 @@ const saveLabel = computed(() => ({
}
.cv-refadd:hover {
border-color: var(--m-orange);
background: rgb(124 92 252 / 0.1);
background: rgb(24 24 27 / 0.1);
transform: translateY(-1px);
box-shadow: 0 6px 16px var(--accent-glow);
}
@@ -1468,7 +1468,7 @@ const saveLabel = computed(() => ({
padding: 10px;
border-radius: 11px;
border: 1px solid rgb(255 255 255 / 0.18);
background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
background: linear-gradient(120deg, #18181b, #27272a 55%, #52525b 140%);
color: #fff;
font-size: 14px;
font-weight: 800;
@@ -1593,14 +1593,14 @@ const saveLabel = computed(() => ({
}
.cv-picker__grid img:hover {
transform: translateY(-2px);
border-color: rgb(124 92 252 / 0.55);
border-color: rgb(24 24 27 / 0.55);
box-shadow: 0 8px 20px var(--accent-glow);
}
.cv-picker__up {
width: 100%;
aspect-ratio: 1;
border-radius: 10px;
border: 2px dashed rgb(124 92 252 / 0.45);
border: 2px dashed rgb(24 24 27 / 0.45);
background: transparent;
color: var(--m-orange);
display: flex;
@@ -1616,7 +1616,7 @@ const saveLabel = computed(() => ({
.cv-picker__up:hover {
transform: translateY(-2px);
border-color: var(--m-orange);
background: rgb(124 92 252 / 0.08);
background: rgb(24 24 27 / 0.08);
box-shadow: 0 8px 20px var(--accent-glow);
}
.cv-picker__up:disabled {
+1 -1
View File
@@ -279,7 +279,7 @@ onMounted(load)
:class="!picked.has(s.id) && 'opacity-45'">
<div class="flex items-center gap-2.5 min-w-0">
<span class="w-2 h-2 rounded-full shrink-0"
:class="s.kind === 'video' ? 'bg-sky-400' : s.kind === 'text' ? 'bg-amber-400' : 'bg-indigo-400'"></span>
:class="s.kind === 'video' ? 'bg-sky-400' : s.kind === 'text' ? 'bg-amber-400' : 'bg-zinc-500'"></span>
<code class="font-mono text-[13px] font-semibold text-white/90 truncate">{{ s.id }}</code>
<span class="text-[11px] font-semibold text-white/40 shrink-0">{{ KIND_LABEL[s.kind] || s.kind }}</span>
<button type="button" class="ml-auto sw" :class="picked.has(s.id) && 'sw-on'"
+4 -4
View File
@@ -656,7 +656,7 @@ html.dark .hint b { color: rgb(253 164 175); } /* rose — visible warning, no
box-shadow: 0 1px 2px rgb(15 23 42 / 0.2);
transition: transform 0.18s ease;
}
.sw:checked { background: #4f46e5; } /* sky-600 — matches btn-primary */
.sw:checked { background: #27272a; } /* sky-600 — matches btn-primary */
.sw:checked::after { transform: translateX(calc(2.25rem - 1.3rem)); }
.sw:focus-visible { box-shadow: 0 0 0 3px rgb(236 91 19 / 0.3); }
.sw:disabled { cursor: not-allowed; opacity: 0.55; }
@@ -677,8 +677,8 @@ html.dark .num, html.dark .txt, html.dark .field {
}
.num::placeholder, .txt::placeholder, .field::placeholder { color: var(--fg-faint); }
.num:focus, .txt:focus, .field:focus {
border-color: rgb(124 92 252 / 0.65);
box-shadow: 0 0 0 3px rgb(124 92 252 / 0.18);
border-color: rgb(24 24 27 / 0.65);
box-shadow: 0 0 0 3px rgb(24 24 27 / 0.18);
}
.num:disabled, .txt:disabled, .field:disabled { opacity: 0.45; cursor: not-allowed; }
.num { width: 6rem; padding: 0.4rem 0.55rem; font-size: 0.8rem; text-align: right; -moz-appearance: textfield; appearance: textfield; }
@@ -692,7 +692,7 @@ html.dark .num, html.dark .txt, html.dark .field {
.btn-primary {
padding: 0.55rem 1.15rem; border-radius: 0.6rem;
font-size: 0.8rem; font-weight: 800; color: white;
background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
background: linear-gradient(120deg, #18181b, #27272a);
border: 1px solid rgb(255 255 255 / 0.16);
box-shadow: 0 6px 18px var(--accent-glow);
transition: filter 0.15s, transform 0.12s, box-shadow 0.15s, opacity 0.15s;
+1 -1
View File
@@ -739,7 +739,7 @@ html.dark .art { border-color: var(--ink-soft); }
height: 40px;
border-radius: 12px;
background: rgba(24, 24, 27, 0.12);
color: #7c3aed;
color: #18181b;
}
.feature h3 {
margin-top: 16px;
+1 -1
View File
@@ -328,7 +328,7 @@ onUnmounted(() => window.removeEventListener('keydown', onKey))
<span v-if="f.resolution" class="text-emerald-300/90">{{ f.resolution }}</span>
<span v-if="f.ratio" class="text-white/40">{{ f.ratio }}</span>
<span v-if="f.kind === 'video' && f.duration" class="text-rose-300/80">{{ f.duration }}</span>
<span v-if="f.deai" class="text-[#a78bfa]">去AI</span>
<span v-if="f.deai" class="text-[#71717a]">去AI</span>
</span>
</div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</div>
+2 -2
View File
@@ -88,7 +88,7 @@ function toast(m) {
<section class="relative rounded-2xl p-7 md:p-8 overflow-hidden ink-card">
<div class="memphis-dots absolute inset-0 opacity-30 pointer-events-none"></div>
<div class="relative inline-grid w-11 h-11 rounded-xl text-white place-items-center"
style="background: linear-gradient(135deg, #7c5cfc, #22d3ee); box-shadow: 0 8px 20px var(--accent-glow)">
style="background: linear-gradient(135deg, #18181b, #52525b); box-shadow: 0 8px 20px var(--accent-glow)">
<Icon name="accounts" class="w-5 h-5" />
</div>
@@ -114,7 +114,7 @@ function toast(m) {
</button>
<button @click="copyInvite"
class="rounded-xl border border-white/20 text-white px-5 py-3 text-sm font-extrabold hover:-translate-y-0.5 active:translate-y-0.5 transition-all"
style="background: linear-gradient(120deg, #7c5cfc, #22d3ee); box-shadow: 0 8px 20px var(--accent-glow)">
style="background: linear-gradient(120deg, #18181b, #52525b); box-shadow: 0 8px 20px var(--accent-glow)">
复制链接
</button>
</div>
+1 -1
View File
@@ -327,7 +327,7 @@ const sourcePill = (s) => ({
</template>
<template v-if="e.deai">
<span class="text-white/25 mx-1.5">·</span>
<span class="text-[#a78bfa]">去AI特征</span>
<span class="text-[#71717a]">去AI特征</span>
</template>
</td>
<td class="px-3 py-3.5 text-right text-xs tabular-nums align-middle whitespace-nowrap">
+1 -1
View File
@@ -123,7 +123,7 @@ onMounted(loadModels)
<div v-else-if="!filtered.length" class="flex flex-col items-center gap-3 text-white/40 py-20">
<span class="w-14 h-14 rounded-2xl bg-white/[0.04] grid place-items-center"><Icon name="models" class="w-6 h-6" /></span>
<span v-if="models.length" class="text-sm">没有匹配的模型</span>
<router-link v-else to="/admin/channels" class="text-sm text-sky-300 underline underline-offset-2">还没有模型,先到「渠道管理」勾选上游模型自动导入</router-link>
<router-link v-else to="/admin/channels" class="text-sm text-sky-300 underline underline-offset-2">还没有模型,先到「渠道管理」添加渠道,保存后会自动同步上游模型</router-link>
</div>
<table v-else class="w-full text-sm table-fixed">
+1 -1
View File
@@ -1076,7 +1076,7 @@ onUnmounted(() => {
<div class="pg-cap text-[11px] leading-tight font-medium line-clamp-2 transition-colors"
:class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''"
:title="item.prompt ? '点击复制提示词' : ''" @click.stop="copyPrompt(item)">{{ item.prompt }}</div>
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.deai"> · <span class="text-[#a78bfa]">去AI</span></span><span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
<div class="pg-cap-sub text-[9px] mt-0.5 font-mono truncate">{{ item.model }}<span v-if="item.deai"> · <span class="text-[#71717a]">去AI</span></span><span v-if="item.elapsed_ms"> · {{ (item.elapsed_ms / 1000).toFixed(1) }}s</span></div>
</div>
</template>
<!-- pending / running -->
+1 -1
View File
@@ -273,7 +273,7 @@ async function recharge() {
<section class="lg:col-span-2 card overflow-hidden">
<div class="p-7 md:p-8 relative">
<div class="absolute -top-16 -right-16 w-48 h-48 rounded-full opacity-30 blur-3xl"
style="background: radial-gradient(circle,#8b5cf6,transparent 60%)"></div>
style="background: radial-gradient(circle,#27272a,transparent 60%)"></div>
<div class="relative grid md:grid-cols-[260px_1fr] gap-8 items-start">
<div>
<div class="inline-grid w-10 h-10 rounded-xl bg-orange-500/15 ring-1 ring-orange-400/30 grid place-items-center text-orange-300">
+1 -1
View File
@@ -257,7 +257,7 @@ const params = (e) => {
:title="e.error + ' — 点击复制'"
@click.stop="copyError(e)">⚠ {{ e.error }}</div>
</td>
<td class="px-3 py-3 align-middle text-xs text-slate-500 tabular-nums">{{ params(e) || '—' }}<span v-if="e.deai"> · <span class="text-[#a78bfa]">去AI特征</span></span></td>
<td class="px-3 py-3 align-middle text-xs text-slate-500 tabular-nums">{{ params(e) || '—' }}<span v-if="e.deai"> · <span class="text-[#71717a]">去AI特征</span></span></td>
<td class="px-3 py-3 align-middle text-right text-xs text-slate-700 tabular-nums">{{ e.cost ? points(e.cost) : '—' }}</td>
<td class="px-3 py-3 align-middle text-right text-xs text-slate-500 tabular-nums">{{ e.elapsed_ms ? (e.elapsed_ms / 1000).toFixed(1) + 's' : '—' }}</td>
</tr>
+1 -1
View File
@@ -361,7 +361,7 @@ onUnmounted(() => {
<span v-if="e.resolution" class="text-emerald-300/90">{{ e.resolution }}</span>
<span v-if="e.ratio" class="text-white/40">{{ e.ratio }}</span>
<span v-if="e.kind === 'video' && e.duration" class="text-rose-300/80">{{ e.duration }}</span>
<span v-if="e.deai" class="text-[#a78bfa]">去AI</span>
<span v-if="e.deai" class="text-[#71717a]">去AI</span>
</span>
</div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtTs(e.ts) }}<span v-if="e.elapsed_ms"> · {{ fmtMs(e.elapsed_ms) }}</span></div>