style(ui): 全站 Memphis 换肤为玻璃拟态+极光渐变

设计 token 与中央类名保持不变,重写视觉:磨砂玻璃卡片、
紫→青渐变按钮、柔光阴影、发丝边框;emoji 图标换 SVG;
登录/支付/公告弹窗改为随主题的玻璃卡片。

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-12 11:05:11 +08:00
co-authored by Devin
parent 27d58cfb5d
commit 91e6881623
21 changed files with 753 additions and 579 deletions
+56 -35
View File
@@ -1,14 +1,14 @@
<script setup>
<script setup>
import { computed } from 'vue'
import { site } from '../site'
const contact = computed(() => site.contact || {})
const powers = [
{ icon: '🎨', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', bg: 'var(--m-mint)' },
{ icon: '🖼️', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', bg: 'var(--m-pink)' },
{ icon: '🎬', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', bg: 'var(--m-blue)' },
{ icon: '✨', title: '图生视频', desc: '给张参考图,让静态画面动起来。', bg: 'var(--m-yellow)' },
{ 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)' },
]
</script>
@@ -29,12 +29,12 @@ const powers = [
</p>
<router-link to="/user" class="bubble-btn mt-7">开始创作 →</router-link>
</div>
<!-- memphis tile cluster standing in for the old mascot illustration -->
<!-- floating glass tile cluster standing in for a mascot illustration -->
<div class="ab-cluster" aria-hidden="true">
<span class="ab-tile ab-1">🎨</span>
<span class="ab-tile ab-2">🖼️</span>
<span class="ab-tile ab-3">🎬</span>
<span class="ab-tile ab-4">✨</span>
<span class="ab-tile ab-1"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3z"/></svg></span>
<span class="ab-tile ab-2"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 16 5-5 3 3 4-4 6 6"/><circle cx="9" cy="9" r="1.4"/></svg></span>
<span class="ab-tile ab-3"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h13v12H3z"/><path d="M16 10l5-3v10l-5-3"/></svg></span>
<span class="ab-tile ab-4"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg></span>
<span class="ab-star">✦</span>
</div>
</div>
@@ -45,11 +45,15 @@ const powers = [
<div class="anime-eyebrow text-[var(--m-orange)]">能做什么</div>
<h2 class="mt-3 text-2xl md:text-3xl font-black tracking-tight text-[color:var(--fg)]">四种方式,把想象变成画面</h2>
<div class="mt-7 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div v-for="p in powers" :key="p.title"
class="power-card p-5" :style="{ background: p.bg }">
<div class="text-3xl">{{ p.icon }}</div>
<div class="mt-3 text-base font-extrabold text-[#111]">{{ p.title }}</div>
<div class="mt-1.5 text-[13px] font-medium leading-relaxed text-[#111]/60">{{ p.desc }}</div>
<div v-for="p in powers" :key="p.title" class="power-card p-5">
<div class="power-ico" :style="{ background: p.tint }">
<svg v-if="p.icon === 't2i'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v14H4z"/><path d="M4 15l4.5-4.5 3 3L15 10l5 5"/><circle cx="9" cy="9" r="1.3"/></svg>
<svg v-else-if="p.icon === 'i2i'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7h11v11H3z"/><path d="M10 4h11v11"/></svg>
<svg v-else-if="p.icon === 't2v'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h13v12H3z"/><path d="M16 10l5-3v10l-5-3"/></svg>
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v14H4z"/><path d="M9 9l6 3-6 3z"/></svg>
</div>
<div class="mt-3 text-base font-extrabold text-[color:var(--fg)]">{{ p.title }}</div>
<div class="mt-1.5 text-[13px] font-medium leading-relaxed text-[color:var(--fg-3)]">{{ p.desc }}</div>
</div>
</div>
</section>
@@ -64,7 +68,7 @@ const powers = [
<a v-if="contact.shop" :href="contact.shop" target="_blank" rel="noopener"
class="group anime-card flex items-center justify-between gap-3 p-5">
<div>
<div class="sticker">🛍️ 商店</div>
<div class="sticker">商店</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 group-hover:text-[var(--m-orange)] transition-colors">前往商店</div>
</div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
@@ -72,33 +76,33 @@ const powers = [
<a v-if="contact.email" :href="`mailto:${contact.email}`"
class="group anime-card p-5">
<div class="sticker">✉️ 邮箱</div>
<div class="sticker">邮箱</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 break-all group-hover:text-sky-600 transition-colors">{{ contact.email }}</div>
</a>
<a v-if="contact.qq && contact.qq_link" :href="contact.qq_link" target="_blank" rel="noopener"
class="group anime-card flex items-center justify-between gap-3 p-5">
<div>
<div class="sticker">💬 QQ</div>
<div class="sticker">QQ</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-sky-600 transition-colors">{{ contact.qq }}</div>
</div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
</a>
<div v-else-if="contact.qq" class="anime-card p-5">
<div class="sticker">💬 QQ</div>
<div class="sticker">QQ</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all">{{ contact.qq }}</div>
</div>
<a v-if="contact.qq_group && contact.qq_group_link" :href="contact.qq_group_link" target="_blank" rel="noopener"
class="group anime-card flex items-center justify-between gap-3 p-5">
<div>
<div class="sticker">🎀 QQ 群</div>
<div class="sticker">QQ 群</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all group-hover:text-emerald-600 transition-colors">{{ contact.qq_group }}</div>
</div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
</a>
<div v-else-if="contact.qq_group" class="anime-card p-5">
<div class="sticker">🎀 QQ 群</div>
<div class="sticker">QQ 群</div>
<div class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all">{{ contact.qq_group }}</div>
</div>
</div>
@@ -110,7 +114,7 @@ const powers = [
</template>
<style scoped>
/* Crayon tile cluster — a loose collage of rotated ink-outlined squares. */
/* Floating glass tile cluster — a loose collage of rotated frosted squares. */
.ab-cluster {
position: relative;
min-height: 240px;
@@ -121,15 +125,19 @@ const powers = [
place-items: center;
width: 5.5rem;
height: 5.5rem;
font-size: 2.2rem;
border: 3px solid var(--ink);
border-radius: 1rem;
box-shadow: 6px 6px 0 var(--shadow-ink);
border: 1px solid var(--glass-border);
border-radius: 1.2rem;
background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: var(--glass-shadow-lg);
color: var(--m-orange);
}
.ab-1 { left: 12%; top: 6%; background: var(--m-mint); transform: rotate(-9deg); animation: memphis-float 6s ease-in-out infinite; }
.ab-2 { right: 14%; top: 0; background: var(--m-pink); transform: rotate(7deg); animation: memphis-float 7s ease-in-out infinite reverse; }
.ab-3 { left: 26%; bottom: 4%; background: var(--m-blue); transform: rotate(5deg); animation: memphis-bounce 4.5s ease-in-out infinite; }
.ab-4 { right: 8%; bottom: 14%; background: var(--m-yellow); transform: rotate(-6deg); animation: memphis-float 5.5s ease-in-out infinite; }
.ab-tile svg { width: 2.3rem; height: 2.3rem; }
.ab-1 { left: 12%; top: 6%; transform: rotate(-9deg); animation: memphis-float 6s ease-in-out infinite; }
.ab-2 { right: 14%; top: 0; color: var(--m-orange-2); transform: rotate(7deg); animation: memphis-float 7s ease-in-out infinite reverse; }
.ab-3 { left: 26%; bottom: 4%; color: var(--m-pink); transform: rotate(5deg); animation: memphis-bounce 4.5s ease-in-out infinite; }
.ab-4 { right: 8%; bottom: 14%; color: var(--m-mint); transform: rotate(-6deg); animation: memphis-float 5.5s ease-in-out infinite; }
.ab-star {
position: absolute;
left: 50%; top: 44%;
@@ -138,12 +146,25 @@ const powers = [
animation: memphis-wiggle 3.5s ease-in-out infinite;
}
.power-card {
border: 2.5px solid var(--ink);
border-radius: 1rem;
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.18s ease, box-shadow 0.18s ease;
border: 1px solid var(--glass-border);
border-radius: 1.1rem;
background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: var(--glass-shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.power-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); }
.power-card:hover { transform: translateY(-3px); border-color: rgb(124 92 252 / 0.45); box-shadow: var(--glass-shadow-lg); }
.power-ico {
width: 2.6rem;
height: 2.6rem;
border-radius: 0.75rem;
display: grid;
place-items: center;
color: #fff;
box-shadow: 0 6px 16px rgb(124 92 252 / 0.28);
}
.power-ico svg { width: 1.3rem; height: 1.3rem; }
@media (max-width: 768px) { .ab-cluster { display: none; } }
@media (prefers-reduced-motion: reduce) { .ab-tile, .ab-star { animation: none; } }
</style>
+58 -41
View File
@@ -883,26 +883,28 @@ const saveLabel = computed(() => ({
</template>
<style scoped>
/* ===== 画布:Memphis/neo-brutalism 全屏工作台(随站点明暗主题走变量) ===== */
/* ===== 画布:玻璃拟态全屏工作台(随站点明暗主题走变量) ===== */
.cv-btn {
padding: 5px 11px;
border-radius: 9px;
border: 2.5px solid var(--ink);
border: 1px solid var(--ink-soft);
background: var(--surface);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
color: var(--fg);
font-size: 13px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
box-shadow: 3px 3px 0 var(--shadow-ink);
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
box-shadow: var(--glass-shadow);
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
.cv-btn:hover:not(:disabled) {
background: var(--m-yellow);
background: var(--hover);
border-color: rgb(124 92 252 / 0.4);
}
.cv-btn:active:not(:disabled) {
transform: translate(2px, 2px);
box-shadow: 1px 1px 0 var(--shadow-ink);
transform: translateY(1px);
}
.cv-btn:disabled {
opacity: 0.45;
@@ -912,16 +914,18 @@ const saveLabel = computed(() => ({
background: #ffb4ab;
}
.cv-btn--accent {
background: var(--m-blue);
background: rgb(34 211 238 / 0.16);
border-color: rgb(34 211 238 / 0.4);
}
.cv-btn--accent:hover:not(:disabled) {
background: var(--m-mint);
background: rgb(52 211 153 / 0.16);
border-color: rgb(52 211 153 / 0.4);
}
.cv-select,
.cv-input {
padding: 6px 10px;
border-radius: 9px;
border: 2.5px solid var(--ink);
border: 1px solid var(--ink-soft);
background: var(--surface);
color: var(--fg);
font-size: 13px;
@@ -929,7 +933,8 @@ const saveLabel = computed(() => ({
}
.cv-select:focus,
.cv-input:focus {
box-shadow: 3px 3px 0 var(--m-orange);
border-color: rgb(124 92 252 / 0.6);
box-shadow: 0 0 0 3px rgb(124 92 252 / 0.18);
}
.cv-page {
@@ -947,8 +952,10 @@ const saveLabel = computed(() => ({
justify-content: space-between;
gap: 12px;
padding: 9px 14px;
border-bottom: 3px solid var(--ink);
border-bottom: 1px solid var(--ink-soft);
background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
flex-wrap: wrap;
}
.cv-top__left,
@@ -975,8 +982,8 @@ const saveLabel = computed(() => ({
color: var(--fg);
min-width: 46px;
text-align: center;
background: var(--m-yellow);
border: 2px solid var(--ink);
background: var(--hover);
border: 1px solid rgb(124 92 252 / 0.35);
border-radius: 999px;
padding: 1px 8px;
}
@@ -1017,9 +1024,9 @@ const saveLabel = computed(() => ({
font-size: 15px;
font-weight: 800;
color: var(--fg);
border: 3px dashed var(--ink);
border: 2px dashed rgb(34 211 238 / 0.6);
border-radius: 18px;
background: color-mix(in srgb, var(--m-blue) 55%, transparent);
background: rgb(34 211 238 / 0.1);
pointer-events: none;
}
.cv-loading,
@@ -1039,12 +1046,14 @@ const saveLabel = computed(() => ({
color: #dc2626;
}
/* 右侧生成面板 = 白纸卡片 */
/* 右侧生成面板 = 玻璃侧栏 */
.cv-panel {
width: 264px;
flex-shrink: 0;
border-left: 3px solid var(--ink);
border-left: 1px solid var(--ink-soft);
background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
padding: 16px 14px;
display: flex;
flex-direction: column;
@@ -1081,19 +1090,23 @@ const saveLabel = computed(() => ({
.cv-chip {
padding: 3px 11px;
border-radius: 999px;
border: 2.5px solid var(--ink);
border: 1px solid var(--ink-soft);
background: var(--surface);
color: var(--fg);
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: background 0.13s ease, border-color 0.13s ease, box-shadow 0.13s ease;
}
.cv-chip:hover {
background: var(--m-blue);
background: var(--hover);
border-color: rgb(124 92 252 / 0.4);
}
.cv-chip.is-on {
background: var(--m-yellow);
box-shadow: 2.5px 2.5px 0 var(--shadow-ink);
color: #fff;
background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: transparent;
box-shadow: 0 4px 14px var(--accent-glow);
}
.cv-refs {
font-size: 11px;
@@ -1106,22 +1119,23 @@ const saveLabel = computed(() => ({
.cv-gen {
padding: 10px;
border-radius: 11px;
border: 2.5px solid var(--ink);
background: var(--m-orange);
border: 1px solid rgb(255 255 255 / 0.18);
background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
color: #fff;
font-size: 14px;
font-weight: 800;
cursor: pointer;
box-shadow: 4px 4px 0 var(--shadow-ink);
transition: transform 0.12s ease, box-shadow 0.12s ease;
box-shadow: 0 8px 22px var(--accent-glow);
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
}
.cv-gen:hover:not(:disabled) {
transform: translate(-1px, -1px);
box-shadow: 5px 5px 0 var(--shadow-ink);
transform: translateY(-1px);
filter: brightness(1.07);
box-shadow: 0 12px 30px var(--accent-glow);
}
.cv-gen:active:not(:disabled) {
transform: translate(3px, 3px);
box-shadow: 1px 1px 0 var(--shadow-ink);
transform: translateY(1px) scale(0.99);
box-shadow: 0 4px 12px var(--accent-glow);
}
.cv-gen:disabled {
opacity: 0.5;
@@ -1137,9 +1151,9 @@ const saveLabel = computed(() => ({
font-weight: 600;
color: #92400e;
line-height: 1.6;
border: 2px dashed #f59e0b;
border: 1px dashed rgb(245 158 11 / 0.6);
border-radius: 9px;
background: var(--m-yellow);
background: rgb(245 158 11 / 0.12);
padding: 8px 10px;
}
.cv-tips {
@@ -1163,8 +1177,8 @@ const saveLabel = computed(() => ({
max-width: 92vw;
max-height: 92vh;
border-radius: 12px;
border: 3px solid #fff;
box-shadow: 10px 10px 0 var(--m-orange);
border: 1px solid rgb(255 255 255 / 0.5);
box-shadow: 0 24px 60px rgb(0 0 0 / 0.5), 0 0 40px var(--accent-glow);
}
.cv-picker {
@@ -1179,10 +1193,12 @@ const saveLabel = computed(() => ({
.cv-picker__box {
width: min(720px, 92vw);
max-height: 80vh;
background: var(--surface);
border: 3px solid var(--ink);
background: var(--menu-bg);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: 8px 8px 0 var(--shadow-ink);
box-shadow: var(--glass-shadow-lg);
padding: 16px;
display: flex;
flex-direction: column;
@@ -1212,13 +1228,14 @@ const saveLabel = computed(() => ({
aspect-ratio: 1;
object-fit: cover;
border-radius: 10px;
border: 2.5px solid var(--ink);
border: 1px solid var(--ink-soft);
cursor: pointer;
background: var(--surface-2);
transition: transform 0.12s ease, box-shadow 0.12s ease;
transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.cv-picker__grid img:hover {
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--m-orange);
transform: translateY(-2px);
border-color: rgb(124 92 252 / 0.55);
box-shadow: 0 8px 20px var(--accent-glow);
}
</style>
+14 -15
View File
@@ -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: var(--ink);
box-shadow: 3px 3px 0 var(--m-orange);
border-color: rgb(124 92 252 / 0.65);
box-shadow: 0 0 0 3px rgb(124 92 252 / 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; }
@@ -686,24 +686,23 @@ html.dark .num, html.dark .txt, html.dark .field {
.txt { width: 16rem; max-width: 60%; padding: 0.4rem 0.65rem; font-size: 0.8rem; }
.field { width: 100%; padding: 0.55rem 0.75rem; font-size: 0.85rem; }
/* Section save buttons — solid memphis orange with a hard offset shadow.
The global .btn-primary under .public-dark already uses this palette; we
re-assert it here so the save action stays visually distinct. */
/* Section save buttons — violet→cyan gradient with a glow. The global
.btn-primary under .public-dark already uses this palette; we re-assert it
here so the save action stays visually distinct. */
.btn-primary {
padding: 0.55rem 1.15rem; border-radius: 0.6rem;
font-size: 0.8rem; font-weight: 800; color: white;
background: var(--m-orange);
border: 2px solid var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
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;
}
/* Re-assert the flat orange on hover for ALL states — the global
/* Keep the gradient on hover for ALL states — the global
`.public-dark .btn-primary:hover` in style.css repaints the background,
so even disabled save buttons (注册与登录 / 邮件服务 default to disabled)
would flash on hover. Keep this rule unconditional; only the
brightness/lift below is gated on :not(:disabled). */
.btn-primary:hover { background: #f97316; }
.btn-primary:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); }
.btn-primary:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: none; }
so even disabled save buttons would flash on hover. Keep this rule
unconditional; only the brightness/lift below is gated on :not(:disabled). */
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); }
.btn-primary:active:not(:disabled) { transform: translateY(1px); box-shadow: none; }
.btn-primary:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
</style>
+172 -107
View File
@@ -121,32 +121,32 @@ const caps = computed(() => {
{
key: 't2i', title: '文字生图', model: imageModels.value[0]?.id,
desc: '一句话出图,画幅与分辨率按所选模型的可用范围给出。',
tint: '#a7f3d0', color: '#065f46',
tint: 'linear-gradient(135deg,#34d399,#2dd4bf)', color: '#fff',
metas: [resRange.value, `${imageModels.value.length} 个模型`].filter(Boolean),
},
{
key: 'i2i', title: '参考图生图', model: i2iModels[0]?.id,
desc: '上传参考图换风格、改构图、做变体,支持多张参考。',
tint: '#ffc1cc', color: '#9f1239',
tint: 'linear-gradient(135deg,#e879f9,#f472b6)', color: '#fff',
metas: ['多图参考', `${i2iModels.length} 个模型`],
},
{
key: 't2v', title: '文字生视频', model: videoModels.value[0]?.id,
desc: '一段描述生成短片,时长与分辨率按模型能力可选。',
tint: '#bae6fd', color: '#075985',
tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)', color: '#fff',
metas: [durationRange.value, videoRes.value, `${videoModels.value.length} 个模型`].filter(Boolean),
},
{
key: 'i2v', title: '图生视频', model: i2vModels[0]?.id,
desc: '给参考图或首帧,让静态画面动起来。',
tint: '#fef08a', color: '#92400e',
tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)', color: '#fff',
metas: ['参考图', `${i2vModels.length} 个模型`],
},
].filter((c) => c.model)
})
// ---- model cards: the admin-set alias wins, so no upstream naming leaks ----
const MODEL_TINT = ['#ec5b13', '#ffc1cc', '#bae6fd', '#a7f3d0', '#fef08a', '#f472b6', '#94a3b8']
const MODEL_TINT = ['#7c5cfc', '#22d3ee', '#e879f9', '#34d399', '#a78bfa', '#38bdf8', '#94a3b8']
const modelCards = computed(() => managed.value.map((m, i) => {
const bits = []
if (m.resolutions?.length) bits.push(m.resolutions.join(' / '))
@@ -201,12 +201,18 @@ function useExample(ex) {
<section class="hero-band relative overflow-hidden pt-14 md:pt-20 pb-12">
<div class="anime-stars"></div>
<!-- floating memphis shapes (desktop only) -->
<!-- floating aurora shapes (desktop only) -->
<div aria-hidden="true" class="pointer-events-none absolute inset-0 hidden lg:block">
<span class="deco deco-a">🎨</span>
<span class="deco deco-b">⚡</span>
<span class="deco deco-a">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3z"/></svg>
</span>
<span class="deco deco-b">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg>
</span>
<span class="deco deco-c">4K</span>
<span class="deco deco-d">🎬</span>
<span class="deco deco-d">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h13v12H3z"/><path d="M16 10l5-3v10l-5-3"/></svg>
</span>
<span class="deco deco-star">✦</span>
<span class="deco deco-star deco-star-2">✦</span>
</div>
@@ -241,7 +247,7 @@ function useExample(ex) {
</section>
<!-- ============ GALLERY MARQUEE (hero + bento imagery) ============ -->
<section v-if="gallery.length" class="mt-6 -mx-4 md:-mx-8 overflow-hidden marquee-mask border-y-[3px] border-y-[color:var(--ink)] bg-[var(--surface)] py-4">
<section v-if="gallery.length" class="mt-6 -mx-4 md:-mx-8 overflow-hidden marquee-mask border-y border-y-[color:var(--ink-soft)] bg-[var(--surface)] py-4">
<div class="marquee flex gap-4 px-4 md:px-8 w-max">
<button v-for="(card, i) in [...gallery, ...gallery]" :key="card.id + '-' + i" type="button"
class="gcard" :style="cardBg(card)" :aria-label="card.title" @click="useExample(card)">
@@ -257,17 +263,22 @@ function useExample(ex) {
<!-- ============ KPI STRIP ============ -->
<section class="mt-12 grid grid-cols-2 md:grid-cols-4 gap-3">
<div v-for="k in [
{ v: modelCount, l: '在线模型', e: '🧩', bg: 'var(--m-yellow)' },
{ v: caps.length, l: '创作方式', e: '🎨', bg: 'var(--m-pink)' },
{ v: resRange, l: '输出分辨率', e: '🖼️', bg: 'var(--m-blue)' },
{ v: avgLabel, l: '平均出片 · 近 24h', e: '⚡', bg: 'var(--m-mint)' },
{ v: modelCount, l: '在线模型', i: 'models', tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)' },
{ v: caps.length, l: '创作方式', i: 'wand', tint: 'linear-gradient(135deg,#e879f9,#f472b6)' },
{ v: resRange, l: '输出分辨率', i: 'image', tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)' },
{ v: avgLabel, l: '平均出片 · 近 24h', i: 'bolt', tint: 'linear-gradient(135deg,#34d399,#2dd4bf)' },
]" :key="k.l"
class="kpi-card px-5 py-4" :style="{ background: k.bg }">
<div class="flex items-center gap-2">
<span class="text-lg">{{ k.e }}</span>
<span class="text-[26px] font-black tabular-nums text-[#111]">{{ k.v }}</span>
class="kpi-card px-5 py-4">
<div class="flex items-center gap-2.5">
<span class="kpi-ico" :style="{ background: k.tint }">
<svg v-if="k.i === 'models'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>
<svg v-else-if="k.i === 'wand'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 4 5 5L7 22l-5-5z"/><path d="m14 7 3 3"/><path d="M9 6V4M7 8H5M9 10v2M11 8h2"/></svg>
<svg v-else-if="k.i === 'image'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 16 5-5 3 3 4-4 6 6"/><circle cx="9" cy="9" r="1.4"/></svg>
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg>
</span>
<span class="text-[26px] font-black tabular-nums text-[color:var(--fg)]">{{ k.v }}</span>
</div>
<div class="mt-1.5 text-[10px] font-bold uppercase tracking-[0.18em] text-[#111]/60">{{ k.l }}</div>
<div class="mt-1.5 text-[10px] font-bold uppercase tracking-[0.18em] text-[color:var(--fg-3)]">{{ k.l }}</div>
</div>
</section>
@@ -371,15 +382,15 @@ function useExample(ex) {
注册即可开始,图像与视频都能试;不满意就换个模型,额度按次计费。
</p>
<button type="button" @click="go('/user')"
class="mt-7 group inline-flex items-center gap-3 rounded-xl bg-[var(--m-orange)] text-white border-[3px] border-[color:var(--ink)] shadow-[5px_5px_0_var(--shadow-ink)] hover:shadow-[7px_7px_0_var(--shadow-ink)] hover:-translate-x-0.5 hover:-translate-y-0.5 active:translate-y-0.5 active:shadow-none pl-6 pr-3 py-3 text-sm font-extrabold transition-all">
class="mt-7 group inline-flex items-center gap-3 rounded-xl text-white pl-6 pr-3 py-3 text-sm font-extrabold transition-all cta-go">
免费开始
<span class="w-8 h-8 rounded-lg bg-white text-[#111] border-2 border-[#111] grid place-items-center font-black group-hover:translate-x-1 transition-transform">→</span>
<span class="w-8 h-8 rounded-lg bg-white/20 border border-white/30 grid place-items-center font-black group-hover:translate-x-1 transition-transform">→</span>
</button>
</div>
</section>
<!-- thin footer line -->
<footer class="mt-16 pt-10 border-t-[3px] border-t-[color:var(--ink)] flex flex-wrap items-center justify-between gap-4 text-xs font-semibold text-[color:var(--fg-3)]">
<footer class="mt-16 pt-10 border-t border-t-[color:var(--ink-soft)] flex flex-wrap items-center justify-between gap-4 text-xs font-semibold text-[color:var(--fg-3)]">
<div class="flex items-center gap-3">
<span class="font-semibold text-[color:var(--fg-2)]">{{ site.title }}</span>
<span>·</span>
@@ -401,15 +412,18 @@ function useExample(ex) {
gap: 0.5rem;
max-width: 36rem;
padding: 0.5rem;
border-radius: 1rem;
border-radius: 1.1rem;
background: var(--surface);
border: 3px solid var(--ink);
box-shadow: 7px 7px 0 var(--shadow-ink);
transition: box-shadow 0.2s ease, transform 0.2s ease;
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow-lg);
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}
.composer:focus-within {
transform: translate(-2px, -2px);
box-shadow: 10px 10px 0 var(--m-orange);
transform: translateY(-2px);
border-color: rgb(124 92 252 / 0.5);
box-shadow: 0 18px 50px var(--accent-glow), var(--glass-shadow);
}
.composer-input {
flex: 1;
@@ -425,48 +439,50 @@ function useExample(ex) {
.composer-input::placeholder { color: var(--fg-faint); }
.composer-go {
flex: none;
border: 2.5px solid var(--ink);
border: 1px solid rgb(255 255 255 / 0.16);
cursor: pointer;
color: #fff;
font-size: 14px;
font-weight: 800;
padding: 0.65rem 1.2rem;
border-radius: 0.7rem;
background: var(--m-orange);
box-shadow: 3px 3px 0 var(--shadow-ink);
border-radius: 0.75rem;
background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
box-shadow: 0 6px 18px var(--accent-glow);
transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.composer-go:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); filter: brightness(1.06); }
.composer-go:active { transform: scale(0.96) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
.composer-go:hover { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); filter: brightness(1.07); }
.composer-go:active { transform: scale(0.96) translateY(1px); box-shadow: 0 3px 10px var(--accent-glow); }
.chip {
font-size: 12.5px;
font-weight: 600;
color: var(--fg-2);
cursor: pointer;
border: 2px solid var(--ink);
border: 1px solid var(--ink-soft);
padding: 0.4rem 0.8rem;
border-radius: 0.6rem;
border-radius: 999px;
background: var(--surface);
transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}
.chip:hover {
color: #111;
background: var(--m-yellow);
box-shadow: 3px 3px 0 var(--shadow-ink);
transform: translate(-1px, -1px);
color: var(--fg);
background: var(--hover);
border-color: rgb(124 92 252 / 0.45);
box-shadow: 0 4px 14px rgb(124 92 252 / 0.18);
transform: translateY(-1px);
}
html.dark .chip:hover { color: #111; }
/* ---------- hero cards (kind=hero) ---------- */
.fcard {
position: absolute;
border: 3px solid var(--ink);
border: 1px solid var(--glass-border);
padding: 0;
cursor: pointer;
border-radius: 1rem;
border-radius: 1.1rem;
overflow: hidden;
box-shadow: 7px 7px 0 var(--shadow-ink);
box-shadow: var(--glass-shadow-lg);
transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
animation: fcardIn 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}
@@ -511,28 +527,46 @@ html.dark .chip:hover { color: #111; }
overflow: hidden;
text-align: left;
cursor: pointer;
border: 2.5px solid var(--ink);
border: 1px solid var(--glass-border);
padding: 1.35rem;
border-radius: 1rem;
border-radius: 1.1rem;
background: var(--surface);
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.18s ease, box-shadow 0.18s ease;
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: var(--glass-shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.cap-card::before {
content: '';
position: absolute;
top: -3rem;
right: -3rem;
width: 9rem;
height: 9rem;
border-radius: 50%;
background: radial-gradient(circle, rgb(124 92 252 / 0.22), transparent 65%);
opacity: 0;
transition: opacity 0.25s ease;
}
.cap-card:hover {
transform: translate(-3px, -3px);
box-shadow: 9px 9px 0 var(--m-orange);
transform: translateY(-4px);
border-color: rgb(124 92 252 / 0.45);
box-shadow: var(--glass-shadow-lg), 0 10px 30px rgb(124 92 252 / 0.14);
}
html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
.cap-arrow { position: absolute; top: 1.1rem; right: 1.1rem; color: var(--fg-3); transition: color 0.15s ease; }
.cap-card:hover .cap-arrow { color: var(--m-orange); }
.cap-card:hover::before { opacity: 1; }
.cap-arrow { position: absolute; top: 1.1rem; right: 1.1rem; color: var(--fg-3); transition: color 0.15s ease, transform 0.15s ease; }
.cap-card:hover .cap-arrow { color: var(--m-orange); transform: translate(2px, -2px); }
.cap-icon {
position: relative;
width: 2.6rem;
height: 2.6rem;
border-radius: 0.7rem;
border: 2px solid var(--ink);
border-radius: 0.8rem;
border: 1px solid var(--ink-soft);
display: grid;
place-items: center;
margin-bottom: 0.9rem;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
}
.cap-icon svg { width: 1.25rem; height: 1.25rem; }
.cap-meta {
@@ -542,7 +576,7 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
color: var(--fg-2);
padding: 0.18rem 0.5rem;
border-radius: 0.4rem;
border: 1.5px solid var(--ink-soft);
border: 1px solid var(--ink-soft);
background: var(--surface-2);
}
@@ -550,14 +584,14 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
.tile {
position: relative;
overflow: hidden;
border: 2.5px solid var(--ink);
border: 1px solid var(--glass-border);
padding: 0;
cursor: pointer;
border-radius: 1rem;
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-radius: 1.1rem;
box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.tile:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-blue); }
.tile:hover { transform: translateY(-3px); border-color: rgb(34 211 238 / 0.5); box-shadow: var(--glass-shadow-lg), 0 10px 30px rgb(34 211 238 / 0.14); }
.tile-veil {
position: absolute;
inset: 0;
@@ -584,14 +618,14 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
display: block;
width: 100%;
overflow: hidden;
border: 2.5px solid var(--ink);
border: 1px solid var(--glass-border);
padding: 0;
cursor: pointer;
border-radius: 1rem;
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-radius: 1.1rem;
box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.work:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-pink); }
.work:hover { transform: translateY(-3px); border-color: rgb(232 121 249 / 0.5); box-shadow: var(--glass-shadow-lg), 0 10px 30px rgb(232 121 249 / 0.14); }
.work-img { display: block; width: 100%; aspect-ratio: 4 / 5; transition: transform 0.5s ease; }
.work:hover .work-img { transform: scale(1.04); }
.work-overlay {
@@ -627,11 +661,12 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
font-style: normal;
font-size: 12px;
font-weight: 800;
color: rgb(11 13 19);
background: var(--m-yellow);
border: 2px solid #111;
color: #fff;
background: linear-gradient(120deg, #7c5cfc, #22d3ee);
border: 1px solid rgb(255 255 255 / 0.25);
border-radius: 0.5rem;
padding: 0.4rem 0.8rem;
box-shadow: 0 4px 14px rgb(124 92 252 / 0.4);
}
/* ---------- model row ---------- */
@@ -643,14 +678,15 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
width: 14.5rem;
overflow: hidden;
padding: 1.25rem;
border-radius: 1rem;
border: 2.5px solid var(--ink);
border-radius: 1.1rem;
border: 1px solid var(--glass-border);
background: var(--surface);
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: box-shadow 0.18s ease, transform 0.18s ease;
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: var(--glass-shadow);
transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}
.model-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-mint); }
html.dark .model-card { background: rgb(255 255 255 / 0.05); }
.model-card:hover { transform: translateY(-3px); border-color: rgb(124 92 252 / 0.45); box-shadow: var(--glass-shadow-lg); }
.model-glow {
position: absolute;
top: -2.5rem;
@@ -659,39 +695,43 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
height: 8rem;
border-radius: 50%;
filter: blur(40px);
opacity: 0.3;
opacity: 0.35;
}
.model-tag {
font-size: 10.5px;
font-weight: 800;
padding: 0.18rem 0.55rem;
border-radius: 0.4rem;
border: 1.5px solid var(--ink);
border: 1px solid var(--ink-soft);
}
.model-tag.is-image { background: var(--m-mint); color: #065f46; }
.model-tag.is-video { background: var(--m-blue); color: #075985; }
.model-tag.is-image { background: rgb(52 211 153 / 0.16); color: rgb(5 150 105); border-color: rgb(52 211 153 / 0.4); }
.model-tag.is-video { background: rgb(34 211 238 / 0.16); color: rgb(8 145 178); border-color: rgb(34 211 238 / 0.4); }
html.dark .model-tag.is-image { color: rgb(110 231 183); }
html.dark .model-tag.is-video { color: rgb(103 232 249); }
/* ---------- hero band + memphis deco ---------- */
/* ---------- hero band + aurora deco ---------- */
.hero-band {
border-radius: 2rem;
}
/* Floating crayon chips scattered around the headline — each is a small
ink-outlined tile with a hard shadow and its own idle animation. */
/* Floating glass chips scattered around the headline — each is a small
frosted tile with a soft shadow and its own idle animation. */
.deco {
position: absolute;
display: grid;
place-items: center;
border: 3px solid var(--ink);
box-shadow: 5px 5px 0 var(--shadow-ink);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
font-weight: 900;
color: #111;
color: var(--fg);
background: var(--surface);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
.deco svg { width: 45%; height: 45%; color: var(--m-orange); }
.deco-a {
left: 7%; top: 22%;
width: 3.6rem; height: 3.6rem;
border-radius: 0.9rem;
background: var(--m-yellow);
font-size: 1.5rem;
border-radius: 1rem;
transform: rotate(-8deg);
animation: memphis-float 6s ease-in-out infinite;
}
@@ -699,34 +739,35 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
right: 8%; top: 16%;
width: 3rem; height: 3rem;
border-radius: 999px;
background: var(--m-pink);
font-size: 1.2rem;
transform: rotate(6deg);
animation: memphis-wiggle 3.5s ease-in-out infinite;
}
.deco-b svg { color: var(--m-orange-2); }
.deco-c {
left: 13%; bottom: 16%;
width: 3.2rem; height: 2.4rem;
border-radius: 0.6rem;
background: var(--m-blue);
border-radius: 0.7rem;
font-size: 0.95rem;
letter-spacing: 0.05em;
color: var(--m-orange);
transform: rotate(9deg);
animation: memphis-float 7s ease-in-out infinite reverse;
}
.deco-d {
right: 13%; bottom: 20%;
width: 3.4rem; height: 2.6rem;
border-radius: 0.7rem;
background: var(--m-mint);
font-size: 1.15rem;
border-radius: 0.8rem;
transform: rotate(-7deg);
animation: memphis-bounce 4s ease-in-out infinite;
}
.deco-d svg { color: #34d399; }
.deco-star {
left: 22%; top: 10%;
border: none;
box-shadow: none;
background: transparent;
-webkit-backdrop-filter: none;
backdrop-filter: none;
font-size: 1.7rem;
color: var(--m-orange);
animation: memphis-wiggle 4s ease-in-out infinite;
@@ -739,14 +780,37 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
}
@media (prefers-reduced-motion: reduce) { .deco { animation: none; } }
/* KPI tiles — flat crayon fills, dark ink type (readable in both themes). */
/* KPI tiles — frosted cards with a gradient icon chip. */
.kpi-card {
border: 2.5px solid var(--ink);
border-radius: 1rem;
box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.18s ease, box-shadow 0.18s ease;
border: 1px solid var(--glass-border);
border-radius: 1.1rem;
background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
box-shadow: var(--glass-shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.kpi-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); }
.kpi-card:hover { transform: translateY(-3px); border-color: rgb(124 92 252 / 0.4); box-shadow: var(--glass-shadow-lg); }
.kpi-ico {
width: 2rem;
height: 2rem;
border-radius: 0.6rem;
display: grid;
place-items: center;
color: #fff;
flex: none;
box-shadow: 0 4px 12px rgb(124 92 252 / 0.3);
}
.kpi-ico svg { width: 1.05rem; height: 1.05rem; }
/* ---------- CTA button ---------- */
.cta-go {
background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
border: 1px solid rgb(255 255 255 / 0.2);
box-shadow: 0 10px 30px var(--accent-glow);
}
.cta-go:hover { transform: translateY(-2px); box-shadow: 0 14px 38px var(--accent-glow); filter: brightness(1.07); }
.cta-go:active { transform: translateY(1px) scale(0.98); box-shadow: 0 4px 14px var(--accent-glow); }
/* ---------- gallery marquee ---------- */
.marquee-mask {
@@ -767,18 +831,19 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
width: 15rem;
height: 10rem;
overflow: hidden;
border: 2.5px solid var(--ink);
border: 1px solid var(--glass-border);
padding: 0;
cursor: pointer;
border-radius: 1rem;
background-size: cover;
background-position: center;
box-shadow: 4px 4px 0 var(--shadow-ink);
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.gcard:hover {
transform: translate(-2px, -2px);
box-shadow: 7px 7px 0 var(--m-orange);
transform: translateY(-3px);
border-color: rgb(124 92 252 / 0.5);
box-shadow: var(--glass-shadow-lg), 0 10px 30px rgb(124 92 252 / 0.16);
}
@media (prefers-reduced-motion: reduce) {
.marquee { animation: none; }
+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-[#ec5b13]">去AI</span>
<span v-if="f.deai" class="text-[#a78bfa]">去AI</span>
</span>
</div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</div>
+12 -10
View File
@@ -80,26 +80,27 @@ function toast(m) {
<div class="theme-text space-y-10">
<!-- header -->
<header>
<span class="sticker">🎀 奖励</span>
<span class="sticker">✦ 奖励</span>
<h1 class="mt-3 text-4xl md:text-5xl font-bold tracking-tight">邀请好友</h1>
<p class="text-white/45 mt-2">好友用你的链接注册,并完成首次生图后,你得 {{ INVITE_REWARD }} 积分。</p>
</header>
<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 bg-[var(--m-yellow)] border-2 border-[color:var(--ink)] shadow-[3px_3px_0_var(--shadow-ink)] grid place-items-center text-[#111]">
<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)">
<Icon name="accounts" class="w-5 h-5" />
</div>
<!-- summary -->
<div class="relative mt-6 grid grid-cols-2 gap-3">
<div class="rounded-xl px-4 py-3 border-2 border-[color:var(--ink)] bg-[var(--m-mint)] shadow-[3px_3px_0_var(--shadow-ink)]">
<div class="text-2xl font-black tabular-nums text-[#111]">{{ inviteCount }}</div>
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">已邀请</div>
<div class="rounded-xl px-4 py-3 border border-[color:var(--glass-border)] bg-[var(--surface)] shadow-[var(--glass-shadow)]">
<div class="text-2xl font-black tabular-nums text-[color:var(--fg)]">{{ inviteCount }}</div>
<div class="text-[10px] text-[color:var(--fg-3)] mt-1 uppercase tracking-widest font-bold">已邀请</div>
</div>
<div class="rounded-xl px-4 py-3 border-2 border-[color:var(--ink)] bg-[var(--m-blue)] shadow-[3px_3px_0_var(--shadow-ink)]">
<div class="text-2xl font-black tabular-nums text-[#111]">{{ inviteEarned.toLocaleString('en-US') }}</div>
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">累计积分</div>
<div class="rounded-xl px-4 py-3 border border-[color:var(--glass-border)] bg-[var(--surface)] shadow-[var(--glass-shadow)]">
<div class="text-2xl font-black tabular-nums text-[color:var(--fg)]">{{ inviteEarned.toLocaleString('en-US') }}</div>
<div class="text-[10px] text-[color:var(--fg-3)] mt-1 uppercase tracking-widest font-bold">累计积分</div>
</div>
</div>
@@ -108,11 +109,12 @@ function toast(m) {
<label class="block text-xs font-bold text-white/50 mb-2">邀请码</label>
<div class="flex gap-2">
<button @click="copyCode"
class="flex-1 rounded-xl border-2 border-[color:var(--ink)] bg-[var(--surface)] px-4 py-3 text-sm font-mono text-left shadow-[3px_3px_0_var(--shadow-ink)] hover:-translate-y-0.5 hover:shadow-[4px_4px_0_var(--shadow-ink)] transition-all">
class="flex-1 rounded-xl border border-[color:var(--ink-soft)] bg-[var(--surface)] px-4 py-3 text-sm font-mono text-left shadow-[var(--glass-shadow)] hover:-translate-y-0.5 hover:border-[rgb(124_92_252/0.45)] transition-all">
{{ inviteCode || '—' }}
</button>
<button @click="copyInvite"
class="rounded-xl border-[2.5px] border-[color:var(--ink)] bg-[var(--m-orange)] text-white px-5 py-3 text-sm font-extrabold shadow-[3px_3px_0_var(--shadow-ink)] hover:-translate-y-0.5 hover:shadow-[4px_4px_0_var(--shadow-ink)] active:translate-y-0.5 active:shadow-none transition-all">
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)">
复制链接
</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-[#ec5b13]">去AI特征</span>
<span class="text-[#a78bfa]">去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
@@ -1058,7 +1058,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-[#ec5b13]">去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-[#a78bfa]">去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,#f97316,transparent 60%)"></div>
style="background: radial-gradient(circle,#8b5cf6,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-[#ec5b13]">去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-[#a78bfa]">去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-[#ec5b13]">去AI</span>
<span v-if="e.deai" class="text-[#a78bfa]">去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>