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
+16 -14
View File
@@ -13,22 +13,24 @@ const html = computed(() => marked.parse(announcement.content.trim() || '暂无
<div v-if="announcement.show" <div v-if="announcement.show"
class="fixed inset-0 z-[80] bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4" class="fixed inset-0 z-[80] bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4"
@click.self="dismissAnnouncement"> @click.self="dismissAnnouncement">
<div class="w-full max-w-lg rounded-xl bg-white text-slate-800 border-[3px] border-[#111] shadow-[8px_8px_0_#111] overflow-hidden flex flex-col max-h-[80vh]"> <div class="w-full max-w-lg rounded-2xl bg-[var(--menu-bg)] backdrop-blur-xl text-[color:var(--fg)] border border-[color:var(--glass-border)] shadow-[var(--glass-shadow-lg)] overflow-hidden flex flex-col max-h-[80vh]">
<div class="px-6 py-4 border-b-[2.5px] border-b-[#111] flex items-center justify-between gap-3 shrink-0"> <div class="px-6 py-4 border-b border-b-[color:var(--ink-soft)] flex items-center justify-between gap-3 shrink-0">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="w-7 h-7 rounded-lg bg-[#fef08a] text-[#111] border-2 border-[#111] grid place-items-center"> <span class="w-7 h-7 rounded-lg text-white grid place-items-center"
style="background: linear-gradient(135deg, #7c5cfc, #22d3ee); box-shadow: 0 4px 12px var(--accent-glow)">
<Icon name="spark" class="w-4 h-4" /> <Icon name="spark" class="w-4 h-4" />
</span> </span>
<h2 class="text-base font-extrabold">公告</h2> <h2 class="text-base font-extrabold">公告</h2>
</div> </div>
<button @click="dismissAnnouncement" class="text-slate-400 hover:text-slate-700 transition-colors"> <button @click="dismissAnnouncement" class="text-[color:var(--fg-3)] hover:text-[color:var(--fg)] transition-colors">
<Icon name="close" class="w-5 h-5" /> <Icon name="close" class="w-5 h-5" />
</button> </button>
</div> </div>
<div class="ann-body px-6 py-5 overflow-y-auto" v-html="html"></div> <div class="ann-body px-6 py-5 overflow-y-auto" v-html="html"></div>
<div class="px-6 py-4 border-t-[2.5px] border-t-[#111] flex justify-end shrink-0"> <div class="px-6 py-4 border-t border-t-[color:var(--ink-soft)] flex justify-end shrink-0">
<button @click="dismissAnnouncement" <button @click="dismissAnnouncement"
class="rounded-lg bg-[#ec5b13] text-white border-[2.5px] border-[#111] shadow-[3px_3px_0_#111] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[4px_4px_0_#111] active:translate-y-0.5 active:shadow-none px-5 py-2 text-sm font-extrabold transition-all"> class="rounded-lg text-white border border-white/20 px-5 py-2 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> </button>
</div> </div>
@@ -42,21 +44,21 @@ const html = computed(() => marked.parse(announcement.content.trim() || '暂无
.ann-fade-enter-from, .ann-fade-leave-to { opacity: 0; } .ann-fade-enter-from, .ann-fade-leave-to { opacity: 0; }
/* Minimal markdown typography for the announcement body. */ /* Minimal markdown typography for the announcement body. */
.ann-body { font-size: 0.9rem; line-height: 1.65; color: rgb(51 65 85); } .ann-body { font-size: 0.9rem; line-height: 1.65; color: var(--fg-2); }
.ann-body :deep(h1) { font-size: 1.25rem; font-weight: 700; margin: 0.6em 0 0.4em; color: rgb(15 23 42); } .ann-body :deep(h1) { font-size: 1.25rem; font-weight: 700; margin: 0.6em 0 0.4em; color: var(--fg); }
.ann-body :deep(h2) { font-size: 1.1rem; font-weight: 700; margin: 0.6em 0 0.4em; color: rgb(15 23 42); } .ann-body :deep(h2) { font-size: 1.1rem; font-weight: 700; margin: 0.6em 0 0.4em; color: var(--fg); }
.ann-body :deep(h3) { font-size: 1rem; font-weight: 600; margin: 0.6em 0 0.3em; color: rgb(15 23 42); } .ann-body :deep(h3) { font-size: 1rem; font-weight: 600; margin: 0.6em 0 0.3em; color: var(--fg); }
.ann-body :deep(p) { margin: 0.5em 0; } .ann-body :deep(p) { margin: 0.5em 0; }
.ann-body :deep(ul), .ann-body :deep(ol) { margin: 0.5em 0; padding-left: 1.4em; } .ann-body :deep(ul), .ann-body :deep(ol) { margin: 0.5em 0; padding-left: 1.4em; }
.ann-body :deep(ul) { list-style: disc; } .ann-body :deep(ul) { list-style: disc; }
.ann-body :deep(ol) { list-style: decimal; } .ann-body :deep(ol) { list-style: decimal; }
.ann-body :deep(li) { margin: 0.2em 0; } .ann-body :deep(li) { margin: 0.2em 0; }
.ann-body :deep(a) { color: var(--m-orange); text-decoration: underline; } .ann-body :deep(a) { color: var(--m-orange); text-decoration: underline; }
.ann-body :deep(strong) { font-weight: 700; color: rgb(15 23 42); } .ann-body :deep(strong) { font-weight: 700; color: var(--fg); }
.ann-body :deep(code) { background: rgb(254 240 138 / 0.5); padding: 0.1em 0.35em; border-radius: 0.3rem; font-size: 0.85em; } .ann-body :deep(code) { background: rgb(124 92 252 / 0.12); padding: 0.1em 0.35em; border-radius: 0.3rem; font-size: 0.85em; }
.ann-body :deep(pre) { background: #111; color: #f5f5f5; padding: 0.8em; border-radius: 0.5rem; overflow-x: auto; margin: 0.6em 0; } .ann-body :deep(pre) { background: #111; color: #f5f5f5; padding: 0.8em; border-radius: 0.5rem; overflow-x: auto; margin: 0.6em 0; }
.ann-body :deep(pre code) { background: none; padding: 0; } .ann-body :deep(pre code) { background: none; padding: 0; }
.ann-body :deep(blockquote) { border-left: 3px solid var(--m-orange); padding-left: 0.9em; color: rgb(100 116 139); margin: 0.6em 0; } .ann-body :deep(blockquote) { border-left: 3px solid var(--m-orange); padding-left: 0.9em; color: var(--fg-3); margin: 0.6em 0; }
.ann-body :deep(hr) { border: none; border-top: 1px solid rgb(226 232 240); margin: 1em 0; } .ann-body :deep(hr) { border: none; border-top: 1px solid var(--hairline); margin: 1em 0; }
.ann-body :deep(img) { max-width: 100%; border-radius: 0.5rem; margin: 0.5em 0; } .ann-body :deep(img) { max-width: 100%; border-radius: 0.5rem; margin: 0.5em 0; }
</style> </style>
+29 -30
View File
@@ -164,9 +164,9 @@ async function submit() {
<div class="absolute inset-0 bg-black/70 backdrop-blur-md" @click="closeLogin"></div> <div class="absolute inset-0 bg-black/70 backdrop-blur-md" @click="closeLogin"></div>
<div class="card relative z-10 w-full max-w-[26rem]"> <div class="card relative z-10 w-full max-w-[26rem]">
<!-- ambient memphis crayon washes --> <!-- ambient aurora washes -->
<div class="pointer-events-none absolute -top-24 -right-16 h-56 w-56 rounded-full bg-[#fef08a]/60 blur-3xl"></div> <div class="pointer-events-none absolute -top-24 -right-16 h-56 w-56 rounded-full bg-[#8b5cf6]/30 blur-3xl"></div>
<div class="pointer-events-none absolute -bottom-24 -left-16 h-56 w-56 rounded-full bg-[#ffc1cc]/50 blur-3xl"></div> <div class="pointer-events-none absolute -bottom-24 -left-16 h-56 w-56 rounded-full bg-[#22d3ee]/25 blur-3xl"></div>
<div class="relative p-7"> <div class="relative p-7">
<button class="absolute top-4 right-4 grid h-7 w-7 place-items-center rounded-lg text-[color:var(--fg-3)] hover:text-[color:var(--fg)] hover:bg-[var(--hover)] transition-colors" <button class="absolute top-4 right-4 grid h-7 w-7 place-items-center rounded-lg text-[color:var(--fg-3)] hover:text-[color:var(--fg)] hover:bg-[var(--hover)] transition-colors"
@@ -262,21 +262,22 @@ async function submit() {
<style scoped> <style scoped>
.card { .card {
border-radius: 1.1rem; border-radius: 1.2rem;
background: var(--surface); background: var(--menu-bg);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
color: var(--fg); color: var(--fg);
border: 3px solid var(--ink); border: 1px solid var(--glass-border);
box-shadow: 10px 10px 0 var(--shadow-ink); box-shadow: var(--glass-shadow-lg);
overflow: hidden; overflow: hidden;
} }
html.dark .card { html.dark .card {
background: #1c1d24;
color: rgb(255 255 255 / 0.9); color: rgb(255 255 255 / 0.9);
} }
.logo-halo { .logo-halo {
position: relative; position: relative;
filter: drop-shadow(0 6px 0 rgb(236 91 19 / 0.35)); filter: drop-shadow(0 6px 14px rgb(124 92 252 / 0.4));
} }
/* segmented tab control */ /* segmented tab control */
@@ -284,22 +285,21 @@ html.dark .card {
display: flex; gap: 0.3rem; padding: 0.3rem; display: flex; gap: 0.3rem; padding: 0.3rem;
border-radius: 0.7rem; border-radius: 0.7rem;
background: var(--surface-2); background: var(--surface-2);
border: 2px solid var(--ink); border: 1px solid var(--ink-soft);
} }
.tab { .tab {
flex: 1; padding: 0.45rem 0; border-radius: 0.5rem; flex: 1; padding: 0.45rem 0; border-radius: 0.5rem;
font-size: 0.8125rem; font-weight: 700; color: var(--fg-3); font-size: 0.8125rem; font-weight: 700; color: var(--fg-3);
border: 2px solid transparent; border: 1px solid transparent;
transition: background 0.18s, color 0.18s, box-shadow 0.18s, border-color 0.18s; transition: background 0.18s, color 0.18s, box-shadow 0.18s, border-color 0.18s;
} }
.tab:hover { color: var(--fg); } .tab:hover { color: var(--fg); }
.tab-on { .tab-on {
color: #111; color: #fff;
background: var(--m-yellow); background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: var(--ink); border-color: transparent;
box-shadow: 2px 2px 0 var(--shadow-ink); box-shadow: 0 4px 14px var(--accent-glow);
} }
html.dark .tab-on { color: #fff; background: var(--m-orange); }
/* icon-prefixed input */ /* icon-prefixed input */
.lm-field { position: relative; } .lm-field { position: relative; }
@@ -311,14 +311,14 @@ html.dark .tab-on { color: #fff; background: var(--m-orange); }
.lm-field:focus-within .ic { color: var(--m-orange); } .lm-field:focus-within .ic { color: var(--m-orange); }
.fld { .fld {
width: 100%; padding: 0.7rem 0.85rem 0.7rem 2.4rem; border-radius: 0.6rem; width: 100%; padding: 0.7rem 0.85rem 0.7rem 2.4rem; border-radius: 0.6rem;
background: var(--surface); border: 2px solid var(--ink-soft); background: var(--surface); border: 1px solid var(--ink-soft);
color: var(--fg); font-size: 0.875rem; outline: none; color: var(--fg); font-size: 0.875rem; outline: none;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
} }
html.dark .fld { background: rgb(255 255 255 / 0.05); } html.dark .fld { background: rgb(255 255 255 / 0.05); }
.fld:focus { .fld:focus {
border-color: var(--ink); border-color: rgb(124 92 252 / 0.65);
box-shadow: 3px 3px 0 var(--m-orange); box-shadow: 0 0 0 3px rgb(124 92 252 / 0.18);
} }
.fld::placeholder { color: var(--fg-faint); } .fld::placeholder { color: var(--fg-faint); }
/* Keep autofill on-theme in dark (otherwise it paints white/yellow). */ /* Keep autofill on-theme in dark (otherwise it paints white/yellow). */
@@ -344,13 +344,12 @@ html.dark .fld:-webkit-autofill:focus {
min-width: 5.5rem; /* keep width stable between text / spinner */ min-width: 5.5rem; /* keep width stable between text / spinner */
display: inline-flex; align-items: center; justify-content: center; display: inline-flex; align-items: center; justify-content: center;
font-size: 0.75rem; font-weight: 800; white-space: nowrap; font-size: 0.75rem; font-weight: 800; white-space: nowrap;
color: #111; color: var(--fg);
background: var(--m-yellow); border: 2px solid var(--ink); background: var(--hover); border: 1px solid rgb(124 92 252 / 0.4);
box-shadow: 2px 2px 0 var(--shadow-ink); box-shadow: 0 2px 10px rgb(124 92 252 / 0.14);
transition: background 0.15s, box-shadow 0.15s, transform 0.15s, opacity 0.15s; transition: background 0.15s, box-shadow 0.15s, transform 0.15s, opacity 0.15s;
} }
html.dark .code-btn { color: #111; } .code-btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 4px 14px rgb(124 92 252 / 0.22); }
.code-btn:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 var(--shadow-ink); }
.code-btn:active:not(:disabled) { transform: scale(0.96) translateY(1px); box-shadow: none; } .code-btn:active:not(:disabled) { transform: scale(0.96) translateY(1px); box-shadow: none; }
/* Disabled (sending / countdown): just dim — keep the theme text color so it /* Disabled (sending / countdown): just dim — keep the theme text color so it
never washes out to invisible white on the light card. */ never washes out to invisible white on the light card. */
@@ -362,13 +361,13 @@ html.dark .code-btn { color: #111; }
display: flex; align-items: center; justify-content: center; gap: 0.5rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem;
padding: 0.72rem 0; border-radius: 0.65rem; font-size: 0.9rem; font-weight: 800; padding: 0.72rem 0; border-radius: 0.65rem; font-size: 0.9rem; font-weight: 800;
letter-spacing: 0.02em; color: white; margin-top: 0.35rem; letter-spacing: 0.02em; color: white; margin-top: 0.35rem;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.16);
box-shadow: 4px 4px 0 var(--shadow-ink); box-shadow: 0 8px 22px var(--accent-glow);
transition: transform 0.13s, box-shadow 0.13s, filter 0.15s; transition: transform 0.13s, box-shadow 0.13s, filter 0.15s;
} }
.btn-primary:hover:not(:disabled) { filter: brightness(1.06); transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--shadow-ink); } .btn-primary:hover:not(:disabled) { filter: brightness(1.07); transform: translateY(-1px); box-shadow: 0 12px 30px var(--accent-glow); }
.btn-primary:active:not(:disabled) { transform: scale(0.98) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); } .btn-primary:active:not(:disabled) { transform: scale(0.98) translateY(1px); box-shadow: 0 4px 12px var(--accent-glow); }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.spinner { .spinner {
@@ -382,7 +381,7 @@ html.dark .code-btn { color: #111; }
display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.85rem; display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.85rem;
padding: 0.6rem 0.75rem; border-radius: 0.6rem; font-size: 0.75rem; line-height: 1.4; padding: 0.6rem 0.75rem; border-radius: 0.6rem; font-size: 0.75rem; line-height: 1.4;
color: #065f46; font-weight: 600; color: #065f46; font-weight: 600;
background: var(--m-mint); border: 2px solid var(--ink); background: rgb(52 211 153 / 0.14); border: 1px solid rgb(52 211 153 / 0.4);
} }
html.dark .invite-banner { color: rgb(167 243 208); background: rgb(16 185 129 / 0.15); } html.dark .invite-banner { color: rgb(167 243 208); background: rgb(16 185 129 / 0.15); }
.invite-banner svg { color: rgb(5 150 105); } .invite-banner svg { color: rgb(5 150 105); }
+6 -6
View File
@@ -2,7 +2,7 @@
// Vivid mark: an open spectrum ring (lens/aperture) around a play triangle, // Vivid mark: an open spectrum ring (lens/aperture) around a play triangle,
// with a shutter dot closing the gap — image + video in one glyph. // with a shutter dot closing the gap — image + video in one glyph.
// Transparent background so it reads on both the dark and light shells. // Transparent background so it reads on both the dark and light shells.
// Colors follow the memphis crayon palette (orange / pink / blue). // Colors follow the aurora palette (violet / pink / cyan).
// Each instance gets unique gradient ids (useId) so multiple logos on one // Each instance gets unique gradient ids (useId) so multiple logos on one
// page don't share/clobber defs. // page don't share/clobber defs.
import { useId } from 'vue' import { useId } from 'vue'
@@ -18,9 +18,9 @@ const gRing = `vring-${uid}`
xmlns="http://www.w3.org/2000/svg" class="shrink-0"> xmlns="http://www.w3.org/2000/svg" class="shrink-0">
<defs> <defs>
<linearGradient :id="gRing" x1="6" y1="6" x2="42" y2="42" gradientUnits="userSpaceOnUse"> <linearGradient :id="gRing" x1="6" y1="6" x2="42" y2="42" gradientUnits="userSpaceOnUse">
<stop stop-color="#ec5b13" /> <stop stop-color="#7c5cfc" />
<stop offset="0.5" stop-color="#f472b6" /> <stop offset="0.5" stop-color="#e879f9" />
<stop offset="1" stop-color="#38bdf8" /> <stop offset="1" stop-color="#22d3ee" />
</linearGradient> </linearGradient>
</defs> </defs>
@@ -29,10 +29,10 @@ const gRing = `vring-${uid}`
<circle r="17.5" fill="none" :stroke="`url(#${gRing})`" stroke-width="4.4" <circle r="17.5" fill="none" :stroke="`url(#${gRing})`" stroke-width="4.4"
stroke-linecap="round" stroke-dasharray="84 32" transform="rotate(-58)" /> stroke-linecap="round" stroke-dasharray="84 32" transform="rotate(-58)" />
<!-- play triangle = image + video --> <!-- play triangle = image + video -->
<path d="M-3 -6.5 L8.5 0 L-3 6.5 Z" fill="#ec5b13" stroke="#111" stroke-width="1.4" <path d="M-3 -6.5 L8.5 0 L-3 6.5 Z" fill="#7c5cfc" stroke="#111" stroke-width="1.4"
stroke-linejoin="round" /> stroke-linejoin="round" />
<!-- shutter dot --> <!-- shutter dot -->
<circle cx="13" cy="-13" r="4.2" fill="#fef08a" stroke="#111" stroke-width="1.6" /> <circle cx="13" cy="-13" r="4.2" fill="#22d3ee" stroke="#111" stroke-width="1.6" />
</g> </g>
</svg> </svg>
</template> </template>
+26 -24
View File
@@ -103,31 +103,33 @@ onUnmounted(clearTimers)
<transition name="pay-fade"> <transition name="pay-fade">
<div v-if="payment.show" class="fixed inset-0 z-[85] bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4" <div v-if="payment.show" class="fixed inset-0 z-[85] bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4"
@click.self="!paid && finish()"> @click.self="!paid && finish()">
<div class="w-full max-w-sm rounded-xl bg-white text-slate-800 border-[3px] border-[#111] shadow-[8px_8px_0_#111] overflow-hidden"> <div class="w-full max-w-sm rounded-2xl bg-[var(--menu-bg)] backdrop-blur-xl text-[color:var(--fg)] border border-[color:var(--glass-border)] shadow-[var(--glass-shadow-lg)] overflow-hidden">
<div class="px-5 py-4 border-b-[2.5px] border-b-[#111] flex items-center justify-between"> <div class="px-5 py-4 border-b border-b-[color:var(--ink-soft)] flex items-center justify-between">
<h2 class="text-base font-semibold">{{ paid ? '支付成功' : dead ? '支付超时' : (isJump ? '支付监控中' : '扫码支付') }}</h2> <h2 class="text-base font-semibold">{{ paid ? '支付成功' : dead ? '支付超时' : (isJump ? '支付监控中' : '扫码支付') }}</h2>
<button v-if="!paid" @click="finish" class="text-slate-400 hover:text-slate-700"><Icon name="close" class="w-5 h-5" /></button> <button v-if="!paid" @click="finish" class="text-[color:var(--fg-3)] hover:text-[color:var(--fg)] transition-colors"><Icon name="close" class="w-5 h-5" /></button>
</div> </div>
<!-- paid --> <!-- paid -->
<div v-if="paid" class="px-6 py-10 text-center"> <div v-if="paid" class="px-6 py-10 text-center">
<div class="w-16 h-16 mx-auto rounded-full bg-[#a7f3d0] text-emerald-700 border-[2.5px] border-[#111] shadow-[4px_4px_0_#111] grid place-items-center mb-4"> <div class="w-16 h-16 mx-auto rounded-full text-white grid place-items-center mb-4"
style="background: linear-gradient(135deg, #34d399, #2dd4bf); box-shadow: 0 10px 28px rgb(52 211 153 / 0.4)">
<Icon name="spark" class="w-8 h-8" /> <Icon name="spark" class="w-8 h-8" />
</div> </div>
<p class="text-lg font-semibold text-slate-800">充值成功</p> <p class="text-lg font-semibold text-[color:var(--fg)]">充值成功</p>
<p class="text-sm text-slate-500 mt-1">已到账 <strong class="text-emerald-600">{{ order.points }}</strong> 积分</p> <p class="text-sm text-[color:var(--fg-3)] mt-1">已到账 <strong class="text-emerald-500">{{ order.points }}</strong> 积分</p>
<p class="text-xs text-slate-400 mt-4">{{ closeIn }} 秒后自动关闭</p> <p class="text-xs text-[color:var(--fg-faint)] mt-4">{{ closeIn }} 秒后自动关闭</p>
</div> </div>
<!-- expired / cancelled --> <!-- expired / cancelled -->
<div v-else-if="dead" class="px-6 py-10 text-center"> <div v-else-if="dead" class="px-6 py-10 text-center">
<div class="w-16 h-16 mx-auto rounded-full bg-slate-100 text-slate-400 grid place-items-center mb-4"> <div class="w-16 h-16 mx-auto rounded-full bg-[var(--surface-2)] text-[color:var(--fg-3)] grid place-items-center mb-4">
<Icon name="close" class="w-8 h-8" /> <Icon name="close" class="w-8 h-8" />
</div> </div>
<p class="text-lg font-semibold text-slate-700">支付超时</p> <p class="text-lg font-semibold text-[color:var(--fg)]">支付超时</p>
<p class="text-sm text-slate-500 mt-1">二维码已失效,订单已取消</p> <p class="text-sm text-[color:var(--fg-3)] mt-1">二维码已失效,订单已取消</p>
<p class="text-xs text-slate-400 mt-1">如需充值请重新下单</p> <p class="text-xs text-[color:var(--fg-faint)] mt-1">如需充值请重新下单</p>
<button @click="finish" class="mt-5 rounded-lg bg-[#ec5b13] text-white border-[2.5px] border-[#111] shadow-[3px_3px_0_#111] hover:-translate-y-0.5 active:translate-y-0.5 active:shadow-none px-5 py-2 text-sm font-extrabold transition-all">关闭</button> <button @click="finish" class="mt-5 rounded-lg text-white border border-white/20 px-5 py-2 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> </div>
<!-- pending --> <!-- pending -->
@@ -135,31 +137,31 @@ onUnmounted(clearTimers)
<!-- qrcode: scan to pay --> <!-- qrcode: scan to pay -->
<template v-if="!isJump"> <template v-if="!isJump">
<div class="flex justify-center mb-3"> <div class="flex justify-center mb-3">
<div class="w-[228px] h-[228px] rounded-xl border-[2.5px] border-[#111] shadow-[4px_4px_0_#111] grid place-items-center overflow-hidden bg-white p-3.5"> <div class="w-[228px] h-[228px] rounded-xl border border-[color:var(--glass-border)] shadow-[var(--glass-shadow)] grid place-items-center overflow-hidden bg-white p-3.5">
<img v-if="qrUrl" :src="qrUrl" alt="支付二维码" class="w-full h-full rounded-lg" /> <img v-if="qrUrl" :src="qrUrl" alt="支付二维码" class="w-full h-full rounded-lg" />
<span v-else class="text-xs text-slate-400">二维码生成中…</span> <span v-else class="text-xs text-slate-400">二维码生成中…</span>
</div> </div>
</div> </div>
<p class="text-center text-xs text-slate-500 mb-4">请使用<strong class="text-slate-700">{{ methodLabel }}</strong>扫码支付</p> <p class="text-center text-xs text-[color:var(--fg-3)] mb-4">请使用<strong class="text-[color:var(--fg)]">{{ methodLabel }}</strong>扫码支付</p>
</template> </template>
<!-- jump (no qrcode): the cashier opened in a new tab — just monitor --> <!-- jump (no qrcode): the cashier opened in a new tab — just monitor -->
<template v-else> <template v-else>
<div class="flex flex-col items-center justify-center py-7 mb-2"> <div class="flex flex-col items-center justify-center py-7 mb-2">
<div class="w-12 h-12 rounded-full border-[3px] border-[#fecaca] border-t-[#ec5b13] animate-spin mb-3"></div> <div class="w-12 h-12 rounded-full border-[3px] border-[rgb(124_92_252/0.2)] border-t-[#7c5cfc] animate-spin mb-3"></div>
<p class="text-sm font-medium text-slate-700">支付监控中…</p> <p class="text-sm font-medium text-[color:var(--fg)]">支付监控中…</p>
<p class="text-xs text-slate-400 mt-1">已打开支付页面,完成后自动到账</p> <p class="text-xs text-[color:var(--fg-faint)] mt-1">已打开支付页面,完成后自动到账</p>
</div> </div>
</template> </template>
<dl class="text-sm space-y-2"> <dl class="text-sm space-y-2">
<div class="flex justify-between"><dt class="text-slate-400">订单号</dt><dd class="font-mono text-xs text-slate-700">{{ order.id }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">订单号</dt><dd class="font-mono text-xs text-[color:var(--fg)]">{{ order.id }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">状态</dt><dd class="text-amber-600 font-medium">{{ statusLabel }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">状态</dt><dd class="text-amber-500 font-medium">{{ statusLabel }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">金额</dt><dd class="font-semibold text-slate-800">¥{{ order.amount }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">金额</dt><dd class="font-semibold text-[color:var(--fg)]">¥{{ order.amount }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">充值积分</dt><dd class="text-[#ec5b13] font-semibold">{{ order.points }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">充值积分</dt><dd class="text-[color:var(--m-orange)] font-semibold">{{ order.points }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">下单时间</dt><dd class="text-xs text-slate-600">{{ fmtTime(order.created_at) }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">下单时间</dt><dd class="text-xs text-[color:var(--fg-2)]">{{ fmtTime(order.created_at) }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">支付倒计时</dt><dd class="tabular-nums font-medium" :class="remaining > 0 ? 'text-slate-700' : 'text-rose-500'">{{ remaining > 0 ? mmss : '已超时' }}</dd></div> <div class="flex justify-between"><dt class="text-[color:var(--fg-faint)]">支付倒计时</dt><dd class="tabular-nums font-medium" :class="remaining > 0 ? 'text-[color:var(--fg)]' : 'text-rose-500'">{{ remaining > 0 ? mmss : '已超时' }}</dd></div>
</dl> </dl>
<p class="text-[11px] text-slate-400 mt-4 text-center">支付完成后将自动到账,请勿关闭本窗口</p> <p class="text-[11px] text-[color:var(--fg-faint)] mt-4 text-center">支付完成后将自动到账,请勿关闭本窗口</p>
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -90,8 +90,8 @@ onUnmounted(() => document.removeEventListener('mousedown', onDocClick))
leave-from-class="opacity-100 translate-y-0" leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 -translate-y-1"> leave-to-class="opacity-0 -translate-y-1">
<div v-if="open" ref="panel" <div v-if="open" ref="panel"
class="absolute z-30 mt-2 w-full max-h-64 overflow-auto rounded-xl border-[2.5px] border-[color:var(--ink)] class="absolute z-30 mt-2 w-full max-h-64 overflow-auto rounded-xl border border-[color:var(--glass-border)]
bg-[var(--menu-bg)] p-1.5 shadow-[5px_5px_0_var(--shadow-ink)]"> bg-[var(--menu-bg)] backdrop-blur-xl p-1.5 shadow-[var(--glass-shadow-lg)]">
<button v-for="(o, i) in options" :key="o.value" type="button" <button v-for="(o, i) in options" :key="o.value" type="button"
:data-idx="i" @click="pick(o)" @mouseenter="active = i" :data-idx="i" @click="pick(o)" @mouseenter="active = i"
class="w-full flex items-center justify-between gap-2 rounded-lg px-3 py-2 text-left text-sm font-medium transition-colors" class="w-full flex items-center justify-between gap-2 rounded-lg px-3 py-2 text-left text-sm font-medium transition-colors"
@@ -153,18 +153,19 @@ defineExpose({ startEdit, commitNote })
.cv-node { .cv-node {
position: absolute; position: absolute;
border-radius: 14px; border-radius: 14px;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
overflow: hidden; overflow: hidden;
contain: layout style paint; contain: layout style paint;
user-select: none; user-select: none;
touch-action: none; touch-action: none;
cursor: grab; cursor: grab;
background: var(--surface); background: var(--surface-solid);
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
transition: box-shadow 0.15s ease; transition: box-shadow 0.15s ease, border-color 0.15s ease;
} }
.cv-node.is-selected { .cv-node.is-selected {
box-shadow: 7px 7px 0 var(--m-orange); border-color: rgb(124 92 252 / 0.7);
box-shadow: var(--glass-shadow-lg), 0 0 0 3px rgb(124 92 252 / 0.25), 0 10px 30px var(--accent-glow);
} }
.cv-node:active { .cv-node:active {
cursor: grabbing; cursor: grabbing;
@@ -188,19 +189,24 @@ defineExpose({ startEdit, commitNote })
font-weight: 600; font-weight: 600;
line-height: 1.35; line-height: 1.35;
color: var(--fg); color: var(--fg);
background: var(--surface); background: var(--surface-solid);
border-top: 2.5px solid var(--ink); border-top: 1px solid var(--ink-soft);
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
pointer-events: none; pointer-events: none;
} }
/* 笔记 = 孟菲斯黄贴纸 */ /* 笔记 = 淡黄色贴纸 */
.cv-node--note { .cv-node--note {
background: var(--m-yellow); background: #fdf6c9;
border-color: rgb(180 140 20 / 0.35);
cursor: text; cursor: text;
} }
html.dark .cv-node--note {
background: rgb(250 204 21 / 0.14);
border-color: rgb(250 204 21 / 0.35);
}
.cv-node--note:not(.is-editing) { .cv-node--note:not(.is-editing) {
cursor: grab; cursor: grab;
} }
@@ -217,6 +223,7 @@ defineExpose({ startEdit, commitNote })
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
} }
html.dark .cv-node__note { color: rgb(255 255 255 / 0.88); }
.is-editing .cv-node__note { .is-editing .cv-node__note {
user-select: text; user-select: text;
cursor: text; cursor: text;
@@ -229,6 +236,8 @@ defineExpose({ startEdit, commitNote })
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--surface); background: var(--surface);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border-style: dashed; border-style: dashed;
color: var(--fg-2); color: var(--fg-2);
font-size: 13px; font-size: 13px;
@@ -266,8 +275,9 @@ defineExpose({ startEdit, commitNote })
width: 15px; width: 15px;
height: 15px; height: 15px;
border-radius: 50%; border-radius: 50%;
background: var(--m-orange); background: linear-gradient(135deg, #7c5cfc, #22d3ee);
border: 2.5px solid var(--ink); border: 1.5px solid rgb(255 255 255 / 0.7);
box-shadow: 0 2px 10px var(--accent-glow);
opacity: 0; opacity: 0;
transition: opacity 0.12s; transition: opacity 0.12s;
cursor: crosshair; cursor: crosshair;
@@ -286,8 +296,8 @@ defineExpose({ startEdit, commitNote })
width: 15px; width: 15px;
height: 15px; height: 15px;
border-radius: 4px; border-radius: 4px;
background: var(--m-blue); background: var(--surface-solid);
border: 2.5px solid var(--ink); border: 1px solid var(--ink);
cursor: nwse-resize; cursor: nwse-resize;
z-index: 3; z-index: 3;
} }
@@ -305,22 +315,28 @@ defineExpose({ startEdit, commitNote })
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 7px; border-radius: 7px;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
background: var(--m-yellow); background: var(--menu-bg);
color: #1a1a1a; -webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
color: var(--fg);
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
cursor: pointer; cursor: pointer;
box-shadow: 2.5px 2.5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
display: grid; display: grid;
place-items: center; place-items: center;
padding: 0; padding: 0;
transition: transform 0.12s ease, background 0.12s ease, color 0.12s ease;
} }
.cv-tools button:hover { .cv-tools button:hover {
transform: translate(1px, 1px); transform: translateY(-1px);
box-shadow: 1.5px 1.5px 0 var(--shadow-ink); background: var(--hover);
} }
.cv-tools__del { .cv-tools__del {
background: #ffb4ab !important; color: #e5484d;
}
.cv-tools__del:hover {
background: rgb(229 72 77 / 0.14) !important;
} }
</style> </style>
@@ -617,8 +617,8 @@ defineExpose({
} }
.cv-marquee { .cv-marquee {
position: absolute; position: absolute;
border: 2px dashed var(--ink); border: 2px dashed rgb(124 92 252 / 0.5);
background: color-mix(in srgb, var(--m-yellow) 35%, transparent); background: rgb(124 92 252 / 0.08);
pointer-events: none; pointer-events: none;
border-radius: 4px; border-radius: 4px;
} }
@@ -631,10 +631,12 @@ defineExpose({
font-weight: 600; font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: var(--surface); background: var(--surface);
border: 2px solid var(--ink); -webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 999px; border-radius: 999px;
padding: 3px 14px; padding: 3px 14px;
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
pointer-events: none; pointer-events: none;
white-space: nowrap; white-space: nowrap;
max-width: 92%; max-width: 92%;
+23 -27
View File
@@ -49,9 +49,9 @@ watch(() => route.path, () => {
<template> <template>
<div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-orange-400/30 overflow-hidden"> <div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-orange-400/30 overflow-hidden">
<!-- ===== Sidebar ===== --> <!-- ===== Sidebar ===== -->
<aside class="w-60 shrink-0 border-r-[3px] border-r-[color:var(--ink)] bg-[var(--surface)] backdrop-blur-md flex flex-col"> <aside class="w-60 shrink-0 border-r border-r-[color:var(--ink-soft)] bg-[var(--surface)] backdrop-blur-xl flex flex-col">
<router-link to="/" class="h-16 flex items-center gap-2.5 px-5 border-b-[3px] border-b-[color:var(--ink)] group"> <router-link to="/" class="h-16 flex items-center gap-2.5 px-5 border-b border-b-[color:var(--ink-soft)] group">
<img v-if="site.logo" :src="site.logo" :alt="site.title" class="w-8 h-8 rounded-[10px] object-contain ring-2 ring-[color:var(--ink)]" /> <img v-if="site.logo" :src="site.logo" :alt="site.title" class="w-8 h-8 rounded-[10px] object-contain ring-1 ring-[color:var(--ink)]" />
<Logo v-else :size="32" /> <Logo v-else :size="32" />
<div class="leading-tight min-w-0"> <div class="leading-tight min-w-0">
<div class="text-sm font-extrabold truncate tracking-tight text-[color:var(--fg)]">{{ site.title }}</div> <div class="text-sm font-extrabold truncate tracking-tight text-[color:var(--fg)]">{{ site.title }}</div>
@@ -89,7 +89,7 @@ watch(() => route.path, () => {
</template> </template>
</nav> </nav>
<div class="p-3 border-t-[3px] border-t-[color:var(--ink)] space-y-1"> <div class="p-3 border-t border-t-[color:var(--ink-soft)] space-y-1">
<button type="button" @click="toggleTheme" <button type="button" @click="toggleTheme"
class="w-full flex items-center gap-2.5 rounded-lg px-3 py-2 text-xs text-[color:var(--fg-2)] hover:bg-[var(--hover)] hover:text-[color:var(--fg)] transition-colors"> class="w-full flex items-center gap-2.5 rounded-lg px-3 py-2 text-xs text-[color:var(--fg-2)] hover:bg-[var(--hover)] hover:text-[color:var(--fg)] transition-colors">
<svg v-if="isDark" class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg> <svg v-if="isDark" class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
@@ -106,18 +106,18 @@ watch(() => route.path, () => {
<!-- ===== Main ===== --> <!-- ===== Main ===== -->
<div class="flex-1 min-w-0 flex flex-col relative"> <div class="flex-1 min-w-0 flex flex-col relative">
<!-- memphis backdrop: polka dots + flat crayon washes --> <!-- aurora backdrop: drifting gradient orbs + faint grain -->
<div aria-hidden="true" class="pointer-events-none absolute inset-0 overflow-hidden"> <div aria-hidden="true" class="pointer-events-none absolute inset-0 overflow-hidden">
<div class="memphis-dots absolute inset-0 opacity-50"></div> <div class="memphis-dots absolute inset-0 opacity-40"></div>
<div class="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.16]" <div class="absolute -top-40 left-1/4 w-[40rem] h-[40rem] rounded-full opacity-[0.45]"
style="background: radial-gradient(circle, #fef08a, transparent 60%); filter: blur(100px)"></div> style="background: radial-gradient(circle, rgb(139 92 246 / 0.3), transparent 60%); filter: blur(110px)"></div>
<div class="absolute top-1/2 -right-40 w-[36rem] h-[36rem] rounded-full opacity-[0.14]" <div class="absolute top-1/2 -right-48 w-[36rem] h-[36rem] rounded-full opacity-[0.4]"
style="background: radial-gradient(circle, #bae6fd, transparent 60%); filter: blur(100px)"></div> style="background: radial-gradient(circle, rgb(34 211 238 / 0.24), transparent 60%); filter: blur(110px)"></div>
<div class="absolute bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.12]" <div class="absolute bottom-0 -left-24 w-[32rem] h-[32rem] rounded-full opacity-[0.35]"
style="background: radial-gradient(circle, #ffc1cc, transparent 60%); filter: blur(100px)"></div> style="background: radial-gradient(circle, rgb(232 121 249 / 0.2), transparent 60%); filter: blur(110px)"></div>
</div> </div>
<header class="relative z-10 h-14 shrink-0 border-b-[3px] border-b-[color:var(--ink)] bg-[var(--app-bg)]/70 backdrop-blur-md flex items-center px-8"> <header class="relative z-10 h-14 shrink-0 border-b border-b-[color:var(--ink-soft)] bg-[var(--app-bg)]/60 backdrop-blur-xl flex items-center px-8">
<div class="text-[10px] uppercase tracking-[0.25em] text-[color:var(--m-orange)] font-extrabold mr-3">Admin</div> <div class="text-[10px] uppercase tracking-[0.25em] text-[color:var(--m-orange)] font-extrabold mr-3">Admin</div>
<div class="text-[color:var(--fg-faint)] mr-3">/</div> <div class="text-[color:var(--fg-faint)] mr-3">/</div>
<h1 class="text-sm font-semibold tracking-tight text-[color:var(--fg)]">{{ currentLabel }}</h1> <h1 class="text-sm font-semibold tracking-tight text-[color:var(--fg)]">{{ currentLabel }}</h1>
@@ -144,36 +144,32 @@ watch(() => route.path, () => {
gap: 0.75rem; gap: 0.75rem;
padding: 0.55rem 0.875rem; padding: 0.55rem 0.875rem;
border-radius: 0.625rem; border-radius: 0.625rem;
border: 2px solid transparent; border: 1px solid transparent;
color: var(--fg-2); color: var(--fg-2);
font-weight: 600; font-weight: 600;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
} }
.admin-link:hover { background: rgb(254 240 138 / 0.5); color: var(--fg); border-color: var(--ink); } .admin-link:hover { background: var(--hover); color: var(--fg); border-color: rgb(124 92 252 / 0.3); }
.admin-link.active { .admin-link.active {
color: var(--fg); color: #fff;
background: var(--m-yellow); background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: var(--ink); border-color: transparent;
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 4px 14px var(--accent-glow);
} }
html.dark .admin-link:hover { background: rgb(236 91 19 / 0.18); } .admin-link.active > span:first-child { opacity: 0; }
html.dark .admin-link.active { color: #fff; background: var(--m-orange); }
.admin-link.active > span:first-child { opacity: 1; }
.admin-sublink { .admin-sublink {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0.45rem 0.875rem 0.45rem 2.6rem; padding: 0.45rem 0.875rem 0.45rem 2.6rem;
border-radius: 0.625rem; border-radius: 0.625rem;
border: 2px solid transparent; border: 1px solid transparent;
color: var(--fg-2); color: var(--fg-2);
font-weight: 600; font-weight: 600;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
} }
.admin-sublink:hover { background: rgb(254 240 138 / 0.5); color: var(--fg); border-color: var(--ink); } .admin-sublink:hover { background: var(--hover); color: var(--fg); border-color: rgb(124 92 252 / 0.3); }
.admin-sublink.active { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); } .admin-sublink.active { color: var(--m-orange); background: var(--hover); border-color: rgb(124 92 252 / 0.35); font-weight: 700; }
html.dark .admin-sublink:hover { background: rgb(236 91 19 / 0.18); }
html.dark .admin-sublink.active { color: #fff; background: var(--m-orange); }
.fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease, transform 0.15s ease; } .fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease, transform 0.15s ease; }
.fade-enter-from { opacity: 0; transform: translateY(4px); } .fade-enter-from { opacity: 0; transform: translateY(4px); }
+49 -47
View File
@@ -142,7 +142,7 @@ async function onLogout() {
</div> </div>
<!-- mobile panel --> <!-- mobile panel -->
<div v-if="mobileOpen" class="md:hidden border-t-2 border-[color:var(--ink)] px-3 py-3 bg-[var(--menu-bg)]"> <div v-if="mobileOpen" class="md:hidden border-t border-[color:var(--ink-soft)] px-3 py-3 bg-[var(--menu-bg)]">
<router-link v-for="n in nav" :key="n.to" :to="n.to" <router-link v-for="n in nav" :key="n.to" :to="n.to"
:exact-active-class="n.to === '/' ? 'm-on' : ''" :exact-active-class="n.to === '/' ? 'm-on' : ''"
:active-class="n.to === '/' ? '' : 'm-on'" :active-class="n.to === '/' ? '' : 'm-on'"
@@ -164,15 +164,15 @@ async function onLogout() {
<!-- ===== Main column ===== --> <!-- ===== Main column ===== -->
<div class="relative"> <div class="relative">
<!-- memphis backdrop: polka dots + flat crayon washes --> <!-- aurora backdrop: drifting gradient orbs + faint grain -->
<div aria-hidden="true" class="pointer-events-none absolute inset-0 overflow-hidden"> <div aria-hidden="true" class="pointer-events-none absolute inset-0 overflow-hidden">
<div class="memphis-dots absolute inset-0 opacity-60"></div> <div class="memphis-dots absolute inset-0 opacity-50"></div>
<div class="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.22]" <div class="absolute -top-40 left-1/4 w-[42rem] h-[42rem] rounded-full opacity-[0.5]"
style="background: radial-gradient(circle, #fef08a, transparent 60%); filter: blur(90px)"></div> style="background: radial-gradient(circle, rgb(139 92 246 / 0.35), transparent 60%); filter: blur(100px)"></div>
<div class="absolute top-1/2 -right-40 w-[36rem] h-[36rem] rounded-full opacity-[0.18]" <div class="absolute top-1/3 -right-48 w-[38rem] h-[38rem] rounded-full opacity-[0.45]"
style="background: radial-gradient(circle, #bae6fd, transparent 60%); filter: blur(90px)"></div> style="background: radial-gradient(circle, rgb(34 211 238 / 0.3), transparent 60%); filter: blur(100px)"></div>
<div class="absolute bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.16]" <div class="absolute bottom-0 -left-24 w-[34rem] h-[34rem] rounded-full opacity-[0.4]"
style="background: radial-gradient(circle, #ffc1cc, transparent 60%); filter: blur(90px)"></div> style="background: radial-gradient(circle, rgb(232 121 249 / 0.28), transparent 60%); filter: blur(100px)"></div>
<div class="anime-stars"></div> <div class="anime-stars"></div>
</div> </div>
@@ -196,8 +196,8 @@ async function onLogout() {
<style scoped> <style scoped>
.top-bar { .top-bar {
background: color-mix(in srgb, var(--app-bg) 82%, transparent); background: color-mix(in srgb, var(--app-bg) 62%, transparent);
border-bottom: 2.5px solid var(--ink); border-bottom: 1px solid var(--ink-soft);
transition: border-color 0.2s ease; transition: border-color 0.2s ease;
} }
@@ -208,21 +208,17 @@ async function onLogout() {
font-weight: 600; font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: transparent; background: transparent;
border: 2px solid transparent; border: 1px solid transparent;
cursor: pointer; cursor: pointer;
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
} }
.top-link:hover { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); } .top-link:hover { color: var(--fg); background: var(--hover); border-color: rgb(124 92 252 / 0.3); }
.top-link.on { .top-link.on {
color: #111;
font-weight: 800;
background: var(--m-yellow);
border-color: var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
}
html.dark .top-link.on {
color: #fff; color: #fff;
background: var(--m-orange); font-weight: 700;
background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: transparent;
box-shadow: 0 4px 14px var(--accent-glow);
} }
.brand-spark { .brand-spark {
@@ -245,19 +241,18 @@ html.dark .top-link.on {
font-weight: 600; font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: transparent; background: transparent;
border: 2px solid transparent; border: 1px solid transparent;
cursor: pointer; cursor: pointer;
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
} }
.m-link:hover { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); } .m-link:hover { color: var(--fg); background: var(--hover); border-color: rgb(124 92 252 / 0.3); }
.m-link.m-on { .m-link.m-on {
color: #111; color: #fff;
font-weight: 800; font-weight: 700;
background: var(--m-yellow); background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: var(--ink); border-color: transparent;
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 4px 14px var(--accent-glow);
} }
html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
.round-btn { .round-btn {
width: 2.125rem; width: 2.125rem;
@@ -267,10 +262,12 @@ html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
place-items: center; place-items: center;
color: var(--fg-3); color: var(--fg-3);
background: var(--surface); background: var(--surface);
border: 2px solid var(--ink); -webkit-backdrop-filter: blur(10px);
transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease; backdrop-filter: blur(10px);
border: 1px solid var(--ink-soft);
transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
} }
.round-btn:hover { color: #111; background: var(--m-yellow); box-shadow: 3px 3px 0 var(--shadow-ink); transform: translate(-1px, -1px); } .round-btn:hover { color: var(--fg); background: var(--hover); border-color: rgb(124 92 252 / 0.4); box-shadow: 0 4px 14px rgb(124 92 252 / 0.16); transform: translateY(-1px); }
.round-btn:active { transform: scale(0.95) translateY(1px); box-shadow: none; } .round-btn:active { transform: scale(0.95) translateY(1px); box-shadow: none; }
/* mobile-only: .round-btn's display:grid outranks Tailwind's md:hidden, so /* mobile-only: .round-btn's display:grid outranks Tailwind's md:hidden, so
@@ -286,14 +283,14 @@ html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
font-size: 13.5px; font-size: 13.5px;
font-weight: 800; font-weight: 800;
color: #fff; color: #fff;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.16);
cursor: pointer; cursor: pointer;
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 6px 18px var(--accent-glow);
transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease; transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease;
} }
.solid-btn:hover { filter: brightness(1.06); transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--shadow-ink); } .solid-btn:hover { filter: brightness(1.07); transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); }
.solid-btn:active { transform: scale(0.97) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); } .solid-btn:active { transform: scale(0.97) translateY(1px); box-shadow: 0 3px 10px var(--accent-glow); }
.name-btn { .name-btn {
display: inline-flex; display: inline-flex;
@@ -303,18 +300,21 @@ html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
height: 2.125rem; height: 2.125rem;
padding: 0 0.7rem 0 0.85rem; padding: 0 0.7rem 0 0.85rem;
border-radius: 0.6rem; border-radius: 0.6rem;
border: 2px solid var(--ink); border: 1px solid var(--ink-soft);
cursor: pointer; cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
color: var(--fg); color: var(--fg);
background: var(--surface); background: var(--surface);
transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease; -webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
} }
.name-btn:hover { .name-btn:hover {
background: var(--m-yellow); background: var(--hover);
box-shadow: 3px 3px 0 var(--shadow-ink); border-color: rgb(124 92 252 / 0.4);
transform: translate(-1px, -1px); box-shadow: 0 4px 14px rgb(124 92 252 / 0.16);
transform: translateY(-1px);
} }
.name-txt { .name-txt {
overflow: hidden; overflow: hidden;
@@ -328,10 +328,12 @@ html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
top: 2.75rem; top: 2.75rem;
width: 11.5rem; width: 11.5rem;
padding: 0.4rem; padding: 0.4rem;
border-radius: 0.75rem; border-radius: 0.85rem;
background: var(--menu-bg); background: var(--menu-bg);
border: 2.5px solid var(--ink); -webkit-backdrop-filter: blur(18px);
box-shadow: 6px 6px 0 var(--shadow-ink); backdrop-filter: blur(18px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow-lg);
z-index: 50; z-index: 50;
} }
.menu-item { .menu-item {
@@ -345,8 +347,8 @@ html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
.menu-item:hover { background: var(--m-yellow); color: #111; } .menu-item:hover { background: var(--hover); color: var(--fg); }
.menu-sep { height: 2px; background: var(--ink); opacity: 0.15; margin: 0.35rem 0.25rem; } .menu-sep { height: 1px; background: var(--hairline); margin: 0.35rem 0.25rem; }
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; } .fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; }
.fade-enter-from { opacity: 0; transform: translateY(8px); } .fade-enter-from { opacity: 0; transform: translateY(8px); }
+242 -191
View File
@@ -9,72 +9,95 @@ video {
:root { :root {
font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
--tw-ring-color: rgb(17 17 17 / 0.12); --tw-ring-color: rgb(124 92 252 / 0.25);
/* ===== Theme palette — LIGHT (default): Memphis / neo-brutalism ===== /* ===== Theme palette — LIGHT (default): frosted glass / aurora =====
Drives the app chrome (rails/header) and the public marketing pages. Drives the app chrome (rails/header) and the public marketing pages.
The `.dark` class on <html> flips these to the dark values below. The `.dark` class on <html> flips these to the dark values below.
Text over generated images keeps a literal text-white (handled in markup). */ Text over generated images keeps a literal text-white (handled in markup). */
--app-bg: #f8f6f2; /* warm paper */ --app-bg: #eef0f8; /* cool porcelain */
--fg: #1a1a1a; /* primary heading/text */ --fg: #181524; /* primary heading/text */
--fg-2: rgb(26 26 26 / 0.64); /* body / secondary */ --fg-2: rgb(24 21 36 / 0.66); /* body / secondary */
--fg-3: rgb(26 26 26 / 0.48); /* muted */ --fg-3: rgb(24 21 36 / 0.48); /* muted */
--fg-faint: rgb(26 26 26 / 0.34); /* labels / hints */ --fg-faint: rgb(24 21 36 / 0.36); /* labels / hints */
--surface: #ffffff; /* raised card on page bg */ --surface: rgb(255 255 255 / 0.66); /* frosted card on page bg */
--surface-2: rgb(26 26 26 / 0.045); /* subtle inset panel (counter cells) */ --surface-solid: #ffffff; /* opaque version where blur is unwanted */
--hairline: rgb(26 26 26 / 0.12); /* faint borders / separators */ --surface-2: rgb(24 21 36 / 0.045); /* subtle inset panel (counter cells) */
--hover: rgb(254 240 138 / 0.55); /* hover wash — memphis yellow tint */ --hairline: rgb(24 21 36 / 0.1); /* faint borders / separators */
/* ink = the heavy outline color; shadow-ink = the offset "chunky" shadow */ --hover: rgb(124 92 252 / 0.1); /* hover wash — violet tint */
--ink: #111111; /* ink = the hairline edge color; shadow-ink = the soft shadow tint.
--ink-soft: rgb(17 17 17 / 0.45); Kept under their legacy names so existing markup keeps working — only the
--shadow-ink: #111111; values changed from memphis-solid to glass-subtle. */
/* memphis accent crayons */ --ink: rgb(24 21 36 / 0.14);
--m-orange: #ec5b13; --ink-soft: rgb(24 21 36 / 0.09);
--m-yellow: #fef08a; --shadow-ink: rgb(88 80 200 / 0.14);
--m-pink: #ffc1cc; /* aurora accent crayons — primary violet + soft pastel companions */
--m-blue: #bae6fd; --m-orange: #7c5cfc;
--m-mint: #a7f3d0; --m-orange-2: #22d3ee;
--m-yellow: #ece7fe;
--m-pink: #fad9f0;
--m-blue: #d8eefb;
--m-mint: #d9f4e8;
/* solid CTA button that sits on the page bg — inverts per theme */ /* solid CTA button that sits on the page bg — inverts per theme */
--btn-solid-bg: #1a1a1a; --btn-solid-bg: #181524;
--btn-solid-bg-h: #000000; --btn-solid-bg-h: #000000;
--btn-solid-fg: #fff; --btn-solid-fg: #fff;
--menu-bg: #ffffff; /* dropdown / popover panel */ --menu-bg: rgb(255 255 255 / 0.82); /* dropdown / popover panel */
/* shared glass treatments */
--glass-border: rgb(255 255 255 / 0.55);
--glass-shadow: 0 8px 28px rgb(80 74 190 / 0.1), 0 2px 8px rgb(24 21 36 / 0.05);
--glass-shadow-lg: 0 16px 44px rgb(80 74 190 / 0.16), 0 4px 12px rgb(24 21 36 / 0.06);
--accent-glow: rgb(124 92 252 / 0.35);
} }
html.dark { html.dark {
--app-bg: #131318; --app-bg: #08070f;
--fg: rgb(255 255 255 / 0.92); --fg: rgb(255 255 255 / 0.93);
--fg-2: rgb(255 255 255 / 0.55); --fg-2: rgb(255 255 255 / 0.6);
--fg-3: rgb(255 255 255 / 0.4); --fg-3: rgb(255 255 255 / 0.42);
--fg-faint: rgb(255 255 255 / 0.3); --fg-faint: rgb(255 255 255 / 0.3);
--surface: rgb(255 255 255 / 0.05); --surface: rgb(255 255 255 / 0.05);
--surface-solid: #14131f;
--surface-2: rgb(255 255 255 / 0.05); --surface-2: rgb(255 255 255 / 0.05);
--hairline: rgb(255 255 255 / 0.1); --hairline: rgb(255 255 255 / 0.1);
--hover: rgb(236 91 19 / 0.2); --hover: rgb(139 92 246 / 0.16);
--ink: rgb(244 244 245 / 0.85); --ink: rgb(255 255 255 / 0.14);
--ink-soft: rgb(244 244 245 / 0.4); --ink-soft: rgb(255 255 255 / 0.09);
--shadow-ink: rgb(244 244 245 / 0.22); --shadow-ink: rgb(0 0 0 / 0.4);
--btn-solid-bg: #ec5b13; --m-orange: #8b5cf6;
--btn-solid-bg-h: #f97316; --m-orange-2: #22d3ee;
--m-yellow: #a78bfa;
--m-pink: #f0a6d8;
--m-blue: #6cd0f2;
--m-mint: #5fd9ac;
--btn-solid-bg: #8b5cf6;
--btn-solid-bg-h: #7c3aed;
--btn-solid-fg: #fff; --btn-solid-fg: #fff;
--menu-bg: #1d1e26; --menu-bg: rgb(24 23 36 / 0.86);
--glass-border: rgb(255 255 255 / 0.12);
--glass-shadow: 0 8px 28px rgb(0 0 0 / 0.35), 0 2px 8px rgb(0 0 0 / 0.2);
--glass-shadow-lg: 0 18px 50px rgb(0 0 0 / 0.5), 0 4px 14px rgb(0 0 0 / 0.3);
--accent-glow: rgb(139 92 246 / 0.45);
} }
/* Smooth the flip so toggling doesn't hard-cut. */ /* Smooth the flip so toggling doesn't hard-cut. */
body, .theme-x { transition: background-color 0.25s ease, color 0.25s ease; } body, .theme-x { transition: background-color 0.25s ease, color 0.25s ease; }
/* Footer CTA band — flat memphis-yellow panel with a heavy ink outline. */ /* Footer CTA band — aurora gradient panel with a glass edge. */
.cta-band { .cta-band {
position: relative;
background: background:
radial-gradient(circle, rgb(17 17 17 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px, radial-gradient(120% 160% at 0% 0%, rgb(124 92 252 / 0.2), transparent 55%),
var(--m-yellow); radial-gradient(120% 160% at 100% 100%, rgb(34 211 238 / 0.16), transparent 55%),
border: 3px solid var(--ink); var(--surface);
box-shadow: 10px 10px 0 var(--shadow-ink); border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow-lg);
} }
html.dark .cta-band { html.dark .cta-band {
background: background:
radial-gradient(circle, rgb(255 255 255 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px, radial-gradient(120% 160% at 0% 0%, rgb(139 92 246 / 0.22), transparent 55%),
rgb(236 91 19 / 0.16); radial-gradient(120% 160% at 100% 100%, rgb(34 211 238 / 0.14), transparent 55%),
rgb(255 255 255 / 0.04);
} }
/* ===== Light-mode rescue for hardcoded-dark pages ===== /* ===== Light-mode rescue for hardcoded-dark pages =====
@@ -83,10 +106,10 @@ html.dark .cta-band {
LIGHT mode, remap those neutral whites to the dark-on-light palette. Scoped LIGHT mode, remap those neutral whites to the dark-on-light palette. Scoped
to a `.theme-text` wrapper so the marketing homepage's over-image whites are to a `.theme-text` wrapper so the marketing homepage's over-image whites are
never touched. Un-layered, so these win over Tailwind's @layer utilities. */ never touched. Un-layered, so these win over Tailwind's @layer utilities. */
html:not(.dark) .theme-text :is(.text-white, [class*="text-white/9"], [class*="text-white/8"]) { color: rgb(15 23 42 / 0.95); } html:not(.dark) .theme-text :is(.text-white, [class*="text-white/9"], [class*="text-white/8"]) { color: rgb(24 21 36 / 0.95); }
html:not(.dark) .theme-text :is([class*="text-white/7"], [class*="text-white/6"]) { color: rgb(15 23 42 / 0.7); } html:not(.dark) .theme-text :is([class*="text-white/7"], [class*="text-white/6"]) { color: rgb(24 21 36 / 0.7); }
html:not(.dark) .theme-text :is([class*="text-white/5"], [class*="text-white/4"]) { color: rgb(15 23 42 / 0.52); } html:not(.dark) .theme-text :is([class*="text-white/5"], [class*="text-white/4"]) { color: rgb(24 21 36 / 0.52); }
html:not(.dark) .theme-text :is([class*="text-white/3"], [class*="text-white/2"], [class*="text-white/1"]) { color: rgb(15 23 42 / 0.4); } html:not(.dark) .theme-text :is([class*="text-white/3"], [class*="text-white/2"], [class*="text-white/1"]) { color: rgb(24 21 36 / 0.4); }
html:not(.dark) .theme-text [class*="bg-white/"] { background-color: var(--surface-2); } html:not(.dark) .theme-text [class*="bg-white/"] { background-color: var(--surface-2); }
html:not(.dark) .theme-text [class*="border-white/"] { border-color: var(--hairline); } html:not(.dark) .theme-text [class*="border-white/"] { border-color: var(--hairline); }
html:not(.dark) .theme-text [class*="ring-white/"] { --tw-ring-color: var(--hairline); } html:not(.dark) .theme-text [class*="ring-white/"] { --tw-ring-color: var(--hairline); }
@@ -102,7 +125,7 @@ html:not(.dark) .theme-text :is([class*="bg-slate-900"], [class*="bg-slate-800"]
matcher also catches the /80 opacity variants. Badges keep their pale tinted matcher also catches the /80 opacity variants. Badges keep their pale tinted
bg (bg-*-500/10) which reads fine on white. */ bg (bg-*-500/10) which reads fine on white. */
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(79 70 229); } html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(79 70 229); }
html:not(.dark) .theme-text :is([class*="text-orange-300"], [class*="text-orange-200"]) { color: rgb(234 88 12); } html:not(.dark) .theme-text :is([class*="text-orange-300"], [class*="text-orange-200"]) { color: rgb(124 58 237); }
html:not(.dark) .theme-text :is([class*="text-rose-300"], [class*="text-rose-200"]) { color: rgb(225 29 72); } html:not(.dark) .theme-text :is([class*="text-rose-300"], [class*="text-rose-200"]) { color: rgb(225 29 72); }
html:not(.dark) .theme-text :is([class*="text-emerald-300"], [class*="text-emerald-200"]) { color: rgb(5 150 105); } html:not(.dark) .theme-text :is([class*="text-emerald-300"], [class*="text-emerald-200"]) { color: rgb(5 150 105); }
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(2 132 199); } html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(2 132 199); }
@@ -120,17 +143,23 @@ html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset
html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset-btn, .opt, .chip-x):hover { html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset-btn, .opt, .chip-x):hover {
color: var(--fg); background: var(--hover); color: var(--fg); background: var(--hover);
} }
html:not(.dark) .theme-text :is(.fp-on, .pg-on, .seg-on, .opt-on) { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); } html:not(.dark) .theme-text :is(.fp-on, .pg-on, .seg-on, .opt-on) {
background: linear-gradient(120deg, #7c5cfc, #5f6cf8); color: #fff;
box-shadow: 0 4px 16px var(--accent-glow);
}
/* Tag chips (TagInput) — near-white orange text is unreadable on white; keep /* Tag chips (TagInput) — near-white orange text is unreadable on white; keep
the orange tint but switch the label to dark ink. */ the tint but switch the label to dark ink. */
html:not(.dark) .theme-text .chip { color: rgb(15 23 42 / 0.9); } html:not(.dark) .theme-text .chip { color: rgb(24 21 36 / 0.9); }
html:not(.dark) .theme-text .chip .chip-x { color: rgb(15 23 42 / 0.55); background: transparent; box-shadow: none; } html:not(.dark) .theme-text .chip .chip-x { color: rgb(24 21 36 / 0.55); background: transparent; box-shadow: none; }
html:not(.dark) .theme-text .chip .chip-x:hover { color: #fff; background: rgb(244 63 94 / 0.6); } html:not(.dark) .theme-text .chip .chip-x:hover { color: #fff; background: rgb(244 63 94 / 0.6); }
/* ShowcaseView marks the selected filter/kind tab with an `.on` modifier on /* ShowcaseView marks the selected filter/kind tab with an `.on` modifier on
`.filter-pill`/`.kind-btn` (not the `-on` class above). Without this, the `.filter-pill`/`.kind-btn` (not the `-on` class above). Without this, the
neutral `.filter-pill`/`.kind-btn` rescue paints selected == unselected and neutral `.filter-pill`/`.kind-btn` rescue paints selected == unselected and
the highlight vanishes on a white page. Equal-or-higher specificity, later. */ the highlight vanishes on a white page. Equal-or-higher specificity, later. */
html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); } html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on {
background: linear-gradient(120deg, #7c5cfc, #5f6cf8); color: #fff;
box-shadow: 0 4px 16px var(--accent-glow);
}
/* Selected COLORED filter pills — their scoped (dark) selected colors get /* Selected COLORED filter pills — their scoped (dark) selected colors get
overridden by the neutral `.fp` rescue above, so re-state a light-mode overridden by the neutral `.fp` rescue above, so re-state a light-mode
variant (tinted bg + dark-enough text + colored ring) with equal specificity variant (tinted bg + dark-enough text + colored ring) with equal specificity
@@ -138,16 +167,14 @@ html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on { background: var(--
html:not(.dark) .theme-text .fp-emerald { background: rgb(16 185 129 / 0.14); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.45); } html:not(.dark) .theme-text .fp-emerald { background: rgb(16 185 129 / 0.14); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.45); }
html:not(.dark) .theme-text .fp-rose { background: rgb(244 63 94 / 0.12); color: rgb(190 18 60); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.45); } html:not(.dark) .theme-text .fp-rose { background: rgb(244 63 94 / 0.12); color: rgb(190 18 60); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.45); }
html:not(.dark) .theme-text .fp-amber { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.5); } html:not(.dark) .theme-text .fp-amber { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.5); }
html:not(.dark) .theme-text .fp-orange { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.45); } html:not(.dark) .theme-text .fp-orange { background: rgb(139 92 246 / 0.14); color: rgb(109 40 217); box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.45); }
html:not(.dark) .theme-text .fp-pink { background: rgb(244 114 182 / 0.14); color: rgb(190 24 93); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); } html:not(.dark) .theme-text .fp-pink { background: rgb(244 114 182 / 0.14); color: rgb(190 24 93); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); }
html:not(.dark) .theme-text .fp-slate { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.45); } html:not(.dark) .theme-text .fp-slate { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.45); }
html:not(.dark) .theme-text .fp-sky { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.45); } html:not(.dark) .theme-text .fp-sky { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.45); }
html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color: rgb(15 118 110); box-shadow: inset 0 0 0 1px rgb(20 184 166 / 0.45); } html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color: rgb(15 118 110); box-shadow: inset 0 0 0 1px rgb(20 184 166 / 0.45); }
/* Custom checkbox for admin tables (<input type="checkbox" class="chk">). /* Custom checkbox for admin tables (<input type="checkbox" class="chk">).
Replaces the plain accent-color box with a rounded, branded one + white check. Rounded glass box + white check; fills with the violet gradient when on. */
Neutral border + transparent bg so it reads on both the dark admin shell and
light mode; fills rose when checked. */
.chk { .chk {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
@@ -161,14 +188,14 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
display: inline-grid; display: inline-grid;
place-content: center; place-content: center;
vertical-align: middle; vertical-align: middle;
transition: background 0.15s, border-color 0.15s; transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
} }
.chk { border-color: var(--ink-soft); } .chk { border-color: var(--ink); }
.chk:hover:not(:checked) { border-color: var(--m-orange); } .chk:hover:not(:checked) { border-color: var(--m-orange); }
.chk:checked { .chk:checked {
background: var(--m-orange); background: linear-gradient(135deg, #7c5cfc, #22d3ee);
border-color: var(--ink); border-color: transparent;
box-shadow: 2px 2px 0 var(--shadow-ink); box-shadow: 0 2px 10px var(--accent-glow);
} }
.chk:checked::after { .chk:checked::after {
content: ""; content: "";
@@ -179,7 +206,7 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
transform: rotate(45deg); transform: rotate(45deg);
margin-top: -0.12rem; margin-top: -0.12rem;
} }
.chk:focus-visible { outline: 2px solid rgb(236 91 19 / 0.5); outline-offset: 1px; } .chk:focus-visible { outline: 2px solid rgb(124 92 252 / 0.5); outline-offset: 1px; }
/* Account identity tags (AccountsView: 子号 / 母号 / 普号 / 限额). The colors live /* Account identity tags (AccountsView: 子号 / 母号 / 普号 / 限额). The colors live
here rather than as inline dark-palette utilities, which were invisible on the here rather than as inline dark-palette utilities, which were invisible on the
light theme: light values first, html.dark override after. */ light theme: light values first, html.dark override after. */
@@ -195,11 +222,11 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
white-space: nowrap; white-space: nowrap;
} }
.acct-tag-sub { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.4); } .acct-tag-sub { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.4); }
.acct-tag-master { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.4); } .acct-tag-master { background: rgb(139 92 246 / 0.14); color: rgb(109 40 217); box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.4); }
.acct-tag-free { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.4); } .acct-tag-free { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.4); }
.acct-tag-limit { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.4); } .acct-tag-limit { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.4); }
html.dark .acct-tag-sub { background: rgb(14 165 233 / 0.18); color: rgb(125 211 252); box-shadow: inset 0 0 0 1px rgb(56 189 248 / 0.35); } html.dark .acct-tag-sub { background: rgb(14 165 233 / 0.18); color: rgb(125 211 252); box-shadow: inset 0 0 0 1px rgb(56 189 248 / 0.35); }
html.dark .acct-tag-master { background: rgb(249 115 22 / 0.22); color: rgb(253 186 116); box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.35); } html.dark .acct-tag-master { background: rgb(139 92 246 / 0.22); color: rgb(196 181 253); box-shadow: inset 0 0 0 1px rgb(167 139 250 / 0.35); }
html.dark .acct-tag-free { background: rgb(148 163 184 / 0.22); color: rgb(226 232 240); box-shadow: inset 0 0 0 1px rgb(148 163 184 / 0.35); } html.dark .acct-tag-free { background: rgb(148 163 184 / 0.22); color: rgb(226 232 240); box-shadow: inset 0 0 0 1px rgb(148 163 184 / 0.35); }
html.dark .acct-tag-limit { background: rgb(245 158 11 / 0.18); color: rgb(252 211 77); box-shadow: inset 0 0 0 1px rgb(251 191 36 / 0.35); } html.dark .acct-tag-limit { background: rgb(245 158 11 / 0.18); color: rgb(252 211 77); box-shadow: inset 0 0 0 1px rgb(251 191 36 / 0.35); }
/* Capability chips (ModelsView): all variants need bg + ring + color reset in light mode. /* Capability chips (ModelsView): all variants need bg + ring + color reset in light mode.
@@ -224,7 +251,7 @@ html:not(.dark) .theme-text .price-chip {
background: var(--surface-2); background: var(--surface-2);
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: inset 0 0 0 1px var(--hairline);
} }
/* fp-white selected pill: on white bg the all-white fill vanishes — use a neutral dark variant. */ /* fp-white selected pill: on white bg the all-white fill vanishes — use a neutral variant. */
html:not(.dark) .theme-text .fp-white { html:not(.dark) .theme-text .fp-white {
background: var(--surface-2); background: var(--surface-2);
color: var(--fg-2); color: var(--fg-2);
@@ -256,7 +283,7 @@ html:not(.dark) .media-card :is([class*="border-white/"], [class*="ring-white/"]
html:not(.dark) .media-card :is([class*="text-emerald-3"], [class*="text-emerald-2"]) { color: rgb(110 231 183) !important; } html:not(.dark) .media-card :is([class*="text-emerald-3"], [class*="text-emerald-2"]) { color: rgb(110 231 183) !important; }
html:not(.dark) .media-card :is([class*="text-rose-3"], [class*="text-rose-2"]) { color: rgb(251 207 232) !important; } html:not(.dark) .media-card :is([class*="text-rose-3"], [class*="text-rose-2"]) { color: rgb(251 207 232) !important; }
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(147 197 253) !important; } html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(147 197 253) !important; }
html:not(.dark) .media-card :is([class*="text-orange-3"], [class*="text-orange-2"]) { color: rgb(253 186 116) !important; } html:not(.dark) .media-card :is([class*="text-orange-3"], [class*="text-orange-2"]) { color: rgb(196 181 253) !important; }
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(125 211 252) !important; } html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(125 211 252) !important; }
html:not(.dark) .media-card :is([class*="text-amber-3"], [class*="text-amber-2"]) { color: rgb(252 211 77) !important; } html:not(.dark) .media-card :is([class*="text-amber-3"], [class*="text-amber-2"]) { color: rgb(252 211 77) !important; }
@@ -271,8 +298,8 @@ html:not(.dark) .doc-code code { background: rgb(255 255 255 / 0.08); color: rgb
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
/* Kill the bounce/glow when scrolling past the top or bottom of any /* Kill the bounce/glow when scrolling past the top or bottom of any
scroll surface (the dark admin shell has fixed viewport height and we scroll surface (the dark admin shell has fixed viewport height and we
don't want a rubber-band-y feel at the edges of inner scroll areas). */ don't want a rubber-band-y feel at the edges of inner scroll areas). */
overscroll-behavior: none; overscroll-behavior: none;
} }
@@ -281,10 +308,11 @@ body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
overscroll-behavior: none; overscroll-behavior: none;
/* Subtle ambient gradient + grain (used by PublicLayout via .ambient-bg) */ /* Aurora ambient gradient + grain (used by PublicLayout via .ambient-bg) */
} }
::selection { background: var(--m-yellow); color: #111; } ::selection { background: rgb(139 92 246 / 0.32); color: inherit; }
html:not(.dark) ::selection { color: #181524; }
/* Tailwind v4 / preflight leaves <button> on the default arrow cursor, so /* Tailwind v4 / preflight leaves <button> on the default arrow cursor, so
admins miss the affordance on icon-only actions (复制 / 删除 / 生成 等). admins miss the affordance on icon-only actions (复制 / 删除 / 生成 等).
@@ -300,36 +328,45 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
.card:has(> table) { overflow-x: auto; } .card:has(> table) { overflow-x: auto; }
::-webkit-scrollbar { width: 9px; height: 9px; } ::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: rgb(26 26 26 / 0.28); border-radius: 5px; border: 2px solid transparent; background-clip: content-box; } ::-webkit-scrollbar-thumb { background: rgb(24 21 36 / 0.2); border-radius: 5px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: rgb(26 26 26 / 0.45); background-clip: content-box; } ::-webkit-scrollbar-thumb:hover { background: rgb(24 21 36 / 0.38); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-track { background: transparent; }
html.dark ::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.22); background-clip: content-box; } html.dark ::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.18); background-clip: content-box; }
html.dark ::-webkit-scrollbar-thumb:hover { background: rgb(255 255 255 / 0.35); background-clip: content-box; } html.dark ::-webkit-scrollbar-thumb:hover { background: rgb(255 255 255 / 0.32); background-clip: content-box; }
/* ===== Ambient background — used by the public shell ===== */ /* ===== Ambient background — aurora wash used by the public shell ===== */
.ambient-bg { .ambient-bg {
background-color: var(--app-bg); background-color: var(--app-bg);
background-image: radial-gradient(circle, rgb(17 17 17 / 0.07) 1.3px, transparent 1.3px); background-image:
background-size: 22px 22px; radial-gradient(60rem 40rem at 12% -10%, rgb(124 92 252 / 0.14), transparent 60%),
radial-gradient(52rem 36rem at 92% 8%, rgb(34 211 238 / 0.11), transparent 60%),
radial-gradient(46rem 32rem at 8% 96%, rgb(240 171 252 / 0.1), transparent 60%);
} }
html.dark .ambient-bg { html.dark .ambient-bg {
background-image: radial-gradient(circle, rgb(255 255 255 / 0.06) 1.3px, transparent 1.3px); background-image:
radial-gradient(60rem 40rem at 12% -10%, rgb(139 92 246 / 0.2), transparent 60%),
radial-gradient(52rem 36rem at 92% 8%, rgb(34 211 238 / 0.13), transparent 60%),
radial-gradient(46rem 32rem at 8% 96%, rgb(232 121 249 / 0.1), transparent 60%);
} }
/* ===== Memphis / neo-brutalist design kit ===== /* ===== Glass / aurora design kit =====
Heavy ink outlines, hard offset shadows, flat crayon accents (orange / yellow Frosted surfaces, hairline edges, soft layered shadows and violet→cyan
/ pink / blue / mint), rotated stickers and squishy press feedback. Reusable gradient accents. Same class names as the previous memphis kit so every
across the workbench + user subpages + public pages so the whole front-end view picks the new look up without markup changes. */
shares one punchy, print-like look. */
/* Highlighter text — black words "marked" with a yellow swipe. */ /* Gradient headline text — violet → cyan swipe. */
.anime-grad-text { .anime-grad-text {
background: linear-gradient(transparent 55%, var(--m-yellow) 55%); background: linear-gradient(100deg, #7c5cfc 0%, #a855f7 45%, #22d3ee 100%);
padding: 0 0.1em; -webkit-background-clip: text;
color: inherit; background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
padding: 0 0.05em;
} }
html.dark .anime-grad-text { html.dark .anime-grad-text {
background: linear-gradient(transparent 55%, rgb(236 91 19 / 0.55) 55%); background: linear-gradient(100deg, #a78bfa 0%, #e879f9 45%, #67e8f9 100%);
-webkit-background-clip: text;
background-clip: text;
} }
/* Section eyebrow — tiny uppercase kicker with a leading star. */ /* Section eyebrow — tiny uppercase kicker with a leading star. */
@@ -344,23 +381,24 @@ html.dark .anime-grad-text {
} }
.anime-eyebrow::before { content: '✦'; font-size: 0.85em; } .anime-eyebrow::before { content: '✦'; font-size: 0.85em; }
/* Sticker label — rotated yellow chip with a hard shadow. */ /* Sticker label — glass chip with a soft glow. */
.sticker { .sticker {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.3rem; gap: 0.4rem;
padding: 0.3rem 0.75rem; padding: 0.34rem 0.85rem;
border-radius: 0.55rem; border-radius: 999px;
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 700;
color: #111; color: var(--fg);
background: var(--m-yellow); background: var(--surface);
border: 2.5px solid var(--ink); -webkit-backdrop-filter: blur(12px);
box-shadow: 4px 4px 0 var(--shadow-ink); backdrop-filter: blur(12px);
transform: rotate(-1.5deg); border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
} }
/* Chunky CTA — flat orange with an offset ink shadow and a press feel. */ /* Chunky CTA — violet→cyan gradient with a glow. */
.bubble-btn { .bubble-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -370,42 +408,37 @@ html.dark .anime-grad-text {
font-size: 14px; font-size: 14px;
font-weight: 800; font-weight: 800;
color: #fff; color: #fff;
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.18);
cursor: pointer; cursor: pointer;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee);
box-shadow: 4px 4px 0 var(--shadow-ink); box-shadow: 0 8px 24px var(--accent-glow);
transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease; transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
} }
.bubble-btn:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--shadow-ink); filter: brightness(1.05); } .bubble-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 32px var(--accent-glow); filter: brightness(1.06); }
.bubble-btn:active { transform: scale(0.97) translateY(2px); box-shadow: 1px 1px 0 var(--shadow-ink); } .bubble-btn:active { transform: scale(0.97) translateY(1px); box-shadow: 0 4px 12px var(--accent-glow); }
/* Memphis card — flat surface, heavy ink outline, hard offset shadow. */ /* Glass card — frosted surface, hairline edge, soft shadow. */
.anime-card { .anime-card {
position: relative; position: relative;
border-radius: 1rem; border-radius: 1.1rem;
background: var(--surface); background: var(--surface);
border: 2.5px solid var(--ink); -webkit-backdrop-filter: blur(14px);
box-shadow: 5px 5px 0 var(--shadow-ink); backdrop-filter: blur(14px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
transition: transform 0.18s ease, box-shadow 0.18s ease; transition: transform 0.18s ease, box-shadow 0.18s ease;
} }
.anime-card:hover { .anime-card:hover {
transform: translate(-2px, -2px); transform: translateY(-3px);
box-shadow: 8px 8px 0 var(--shadow-ink); box-shadow: var(--glass-shadow-lg);
}
html.dark .anime-card {
background: rgb(255 255 255 / 0.05);
} }
/* Selected pill — solid ink with an orange offset shadow. */ /* Selected pill — gradient fill with a glow. */
.chip-on { .chip-on {
color: #fff !important; color: #fff !important;
background: #1a1a1a !important; background: linear-gradient(120deg, #7c5cfc, #5f6cf8) !important;
border: 2px solid var(--ink) !important; border: 1px solid transparent !important;
box-shadow: 3px 3px 0 var(--m-orange) !important; box-shadow: 0 4px 16px var(--accent-glow) !important;
}
html.dark .chip-on {
background: var(--m-orange) !important;
box-shadow: 3px 3px 0 var(--shadow-ink) !important;
} }
/* Twinkling star field — drop into any relatively-positioned decorative layer. /* Twinkling star field — drop into any relatively-positioned decorative layer.
@@ -415,12 +448,12 @@ html.dark .chip-on {
inset: 0; inset: 0;
pointer-events: none; pointer-events: none;
background-image: background-image:
radial-gradient(1.5px 1.5px at 12% 22%, rgb(236 91 19 / 0.7), transparent), radial-gradient(1.5px 1.5px at 12% 22%, rgb(139 92 246 / 0.7), transparent),
radial-gradient(1.5px 1.5px at 78% 12%, rgb(56 189 248 / 0.7), transparent), radial-gradient(1.5px 1.5px at 78% 12%, rgb(34 211 238 / 0.7), transparent),
radial-gradient(1px 1px at 42% 62%, rgb(236 91 19 / 0.6), transparent), radial-gradient(1px 1px at 42% 62%, rgb(139 92 246 / 0.6), transparent),
radial-gradient(1.5px 1.5px at 88% 72%, rgb(244 114 182 / 0.6), transparent), radial-gradient(1.5px 1.5px at 88% 72%, rgb(232 121 249 / 0.6), transparent),
radial-gradient(1px 1px at 24% 84%, rgb(56 189 248 / 0.55), transparent), radial-gradient(1px 1px at 24% 84%, rgb(34 211 238 / 0.55), transparent),
radial-gradient(1px 1px at 62% 34%, rgb(250 204 21 / 0.55), transparent); radial-gradient(1px 1px at 62% 34%, rgb(167 139 250 / 0.55), transparent);
animation: twinkle 4.5s ease-in-out infinite; animation: twinkle 4.5s ease-in-out infinite;
} }
@keyframes twinkle { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.4; } } @keyframes twinkle { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.4; } }
@@ -445,35 +478,38 @@ html.dark .chip-on {
z-index: 0; z-index: 0;
} }
/* ===== Memphis utilities ===== */ /* ===== Glass utilities ===== */
/* Polka-dot texture layer (drop on a decorative absolutely-positioned div). */ /* Dot texture layer — kept faint so it reads as grain, not polka dots. */
.memphis-dots { .memphis-dots {
background-image: radial-gradient(circle, rgb(17 17 17 / 0.14) 1.4px, transparent 1.4px); background-image: radial-gradient(circle, rgb(24 21 36 / 0.07) 1.1px, transparent 1.1px);
background-size: 22px 22px; background-size: 26px 26px;
} }
html.dark .memphis-dots { html.dark .memphis-dots {
background-image: radial-gradient(circle, rgb(255 255 255 / 0.1) 1.4px, transparent 1.4px); background-image: radial-gradient(circle, rgb(255 255 255 / 0.05) 1.1px, transparent 1.1px);
} }
/* Heavy outline + hard offset shadow helpers for one-off markup. */ /* Frosted surface helpers for one-off markup. */
.ink-card { .ink-card {
background: var(--surface); background: var(--surface);
border: 2.5px solid var(--ink); -webkit-backdrop-filter: blur(14px);
box-shadow: 6px 6px 0 var(--shadow-ink); backdrop-filter: blur(14px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow-lg);
} }
.ink-card-sm { .ink-card-sm {
background: var(--surface); background: var(--surface);
border: 2px solid var(--ink); -webkit-backdrop-filter: blur(12px);
box-shadow: 4px 4px 0 var(--shadow-ink); backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
} }
/* Lift on hover — the signature memphis card interaction. */ /* Lift on hover — the signature card interaction. */
.chunky-hover { transition: transform 0.18s ease, box-shadow 0.18s ease; } .chunky-hover { transition: transform 0.18s ease, box-shadow 0.18s ease; }
.chunky-hover:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); } .chunky-hover:hover { transform: translateY(-3px); box-shadow: var(--glass-shadow-lg); }
/* Squishy press for buttons (mirrors the reference site's .memphis-press). */ /* Squishy press for buttons. */
.memphis-press { transition: transform 0.15s ease; } .memphis-press { transition: transform 0.15s ease; }
.memphis-press:active { transform: scale(0.97) translateY(2px); } .memphis-press:active { transform: scale(0.97) translateY(1px); }
/* Empty-state badge — a rotated crayon tile with a star, replaces the old /* Empty-state badge — frosted tile with a gradient star. */
mascot illustration in empty lists. */
.empty-badge { .empty-badge {
width: 4.5rem; width: 4.5rem;
height: 4.5rem; height: 4.5rem;
@@ -481,12 +517,11 @@ html.dark .memphis-dots {
place-items: center; place-items: center;
font-size: 1.8rem; font-size: 1.8rem;
font-weight: 900; font-weight: 900;
color: #111; color: #fff;
background: var(--m-yellow); background: linear-gradient(135deg, #7c5cfc, #22d3ee);
border: 3px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.2);
border-radius: 1rem; border-radius: 1.2rem;
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: 0 12px 32px var(--accent-glow);
transform: rotate(-6deg);
animation: memphis-float 6s ease-in-out infinite; animation: memphis-float 6s ease-in-out infinite;
} }
@media (prefers-reduced-motion: reduce) { .empty-badge { animation: none; } } @media (prefers-reduced-motion: reduce) { .empty-badge { animation: none; } }
@@ -507,40 +542,41 @@ html.dark .memphis-dots {
remap them to the dark palette. */ remap them to the dark palette. */
.public-dark { color: rgb(255 255 255 / 0.9); } .public-dark { color: rgb(255 255 255 / 0.9); }
.public-dark .card { .public-dark .card {
background: rgb(255 255 255 / 0.035); background: rgb(255 255 255 / 0.04);
border-color: var(--ink); border-color: rgb(255 255 255 / 0.1);
color: rgb(255 255 255 / 0.92); color: rgb(255 255 255 / 0.92);
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
} }
.public-dark .field { .public-dark .field {
background: rgb(255 255 255 / 0.04); background: rgb(255 255 255 / 0.04);
border-color: var(--ink-soft); border-color: rgb(255 255 255 / 0.12);
color: rgb(255 255 255 / 0.95); color: rgb(255 255 255 / 0.95);
transition: border-color 0.18s, background 0.18s, box-shadow 0.18s; transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
} }
.public-dark .field::placeholder { color: rgb(255 255 255 / 0.3); } .public-dark .field::placeholder { color: rgb(255 255 255 / 0.3); }
/* Single focus treatment shared by every <.field> across the admin shell — /* Single focus treatment shared by every <.field> across the admin shell —
an orange offset shadow, matching the light theme's focus affordance. */ a violet glow ring, matching the light theme's focus affordance. */
.public-dark .field:focus, .public-dark .field:focus,
.public-dark .field:focus-visible { .public-dark .field:focus-visible {
border-color: var(--ink); border-color: rgb(139 92 246 / 0.7);
background: rgb(255 255 255 / 0.06); background: rgb(255 255 255 / 0.06);
box-shadow: 3px 3px 0 rgb(236 91 19 / 0.6); box-shadow: 0 0 0 3px rgb(139 92 246 / 0.25);
outline: none; outline: none;
} }
.public-dark .btn-soft { .public-dark .btn-soft {
background: rgb(255 255 255 / 0.06); background: rgb(255 255 255 / 0.05);
border-color: var(--ink-soft); border-color: rgb(255 255 255 / 0.12);
color: rgb(255 255 255 / 0.85); color: rgb(255 255 255 / 0.85);
} }
.public-dark .btn-soft:hover { background: rgb(236 91 19 / 0.25); color: white; } .public-dark .btn-soft:hover { background: rgb(139 92 246 / 0.25); color: white; }
.public-dark .btn-ghost { color: rgb(255 255 255 / 0.65); border-color: transparent; } .public-dark .btn-ghost { color: rgb(255 255 255 / 0.65); border-color: transparent; }
.public-dark .btn-ghost:hover { background: rgb(236 91 19 / 0.18); color: white; } .public-dark .btn-ghost:hover { background: rgb(139 92 246 / 0.18); color: white; }
.public-dark .btn-primary { .public-dark .btn-primary {
background: var(--m-orange); color: #fff; box-shadow: 3px 3px 0 var(--shadow-ink); background: linear-gradient(120deg, #7c5cfc, #5f6cf8); color: #fff;
box-shadow: 0 6px 18px var(--accent-glow);
} }
.public-dark .btn-primary:hover { background: #f97316; } .public-dark .btn-primary:hover { filter: brightness(1.08); }
.public-dark .pill { background: rgb(255 255 255 / 0.06); color: rgb(255 255 255 / 0.85); border-color: var(--ink-soft); } .public-dark .pill { background: rgb(255 255 255 / 0.06); color: rgb(255 255 255 / 0.85); border-color: rgb(255 255 255 / 0.12); }
/* Remap slate text tokens */ /* Remap slate text tokens */
.public-dark .text-slate-900, .public-dark .text-slate-900,
@@ -564,7 +600,7 @@ html.dark .memphis-dots {
/* Tables in dark */ /* Tables in dark */
.public-dark .th { color: rgb(255 255 255 / 0.4); } .public-dark .th { color: rgb(255 255 255 / 0.4); }
.public-dark .row { border-color: rgb(255 255 255 / 0.06); } .public-dark .row { border-color: rgb(255 255 255 / 0.06); }
.public-dark .row:hover { background: rgb(255 255 255 / 0.03); } .public-dark .row:hover { background: rgb(139 92 246 / 0.07); }
/* Inline code blocks (pill highlight) — only for inline <code>, NOT code inside /* Inline code blocks (pill highlight) — only for inline <code>, NOT code inside
a <pre> block (docs examples), which would get an unwanted full-width tint. */ a <pre> block (docs examples), which would get an unwanted full-width tint. */
@@ -590,11 +626,14 @@ html.dark .memphis-dots {
.public-dark .hover\:bg-slate-100:hover { background: rgb(255 255 255 / 0.06); } .public-dark .hover\:bg-slate-100:hover { background: rgb(255 255 255 / 0.06); }
@layer components { @layer components {
/* surfaces — flat white card with the memphis ink outline + offset shadow */ /* surfaces — frosted card with a hairline glass edge + soft shadow */
.card { .card {
@apply bg-white rounded-xl; @apply rounded-xl;
border: 2.5px solid var(--ink); background: var(--surface);
box-shadow: 5px 5px 0 var(--shadow-ink); -webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
} }
/* buttons — each variant is self-contained (Tailwind v4 can't @apply a custom class) */ /* buttons — each variant is self-contained (Tailwind v4 can't @apply a custom class) */
@@ -602,56 +641,64 @@ html.dark .memphis-dots {
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-bold @apply inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-bold
disabled:opacity-50 disabled:cursor-not-allowed px-3.5 py-2; disabled:opacity-50 disabled:cursor-not-allowed px-3.5 py-2;
color: #fff; color: #fff;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 60%, #22d3ee 140%);
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.16);
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 6px 18px var(--accent-glow);
transition: transform 0.13s ease, box-shadow 0.13s ease, filter 0.13s ease; transition: transform 0.13s ease, box-shadow 0.13s ease, filter 0.13s ease;
} }
.btn-primary:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); filter: brightness(1.05); } .btn-primary:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); filter: brightness(1.07); }
.btn-primary:active:not(:disabled) { transform: scale(0.97) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); } .btn-primary:active:not(:disabled) { transform: scale(0.97) translateY(1px); box-shadow: 0 3px 10px var(--accent-glow); }
.btn-soft { .btn-soft {
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-semibold @apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-semibold
disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5; disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
color: var(--fg-2); color: var(--fg-2);
background: var(--surface); background: var(--surface);
border: 2px solid var(--ink-soft); -webkit-backdrop-filter: blur(10px);
transition: transform 0.13s ease, box-shadow 0.13s ease, background 0.15s ease, color 0.15s ease; backdrop-filter: blur(10px);
border: 1px solid var(--ink-soft);
transition: transform 0.13s ease, box-shadow 0.13s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
} }
.btn-soft:hover:not(:disabled) { .btn-soft:hover:not(:disabled) {
color: var(--fg); color: var(--fg);
background: var(--m-yellow); background: var(--hover);
border-color: var(--ink); border-color: rgb(124 92 252 / 0.45);
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 4px 14px rgb(124 92 252 / 0.16);
} }
.btn-soft:active:not(:disabled) { transform: scale(0.97) translateY(2px); box-shadow: none; } html.dark .btn-soft:hover:not(:disabled) { border-color: rgb(139 92 246 / 0.5); box-shadow: 0 4px 14px rgb(139 92 246 / 0.22); }
.btn-soft:active:not(:disabled) { transform: scale(0.97) translateY(1px); box-shadow: none; }
.btn-ghost { .btn-ghost {
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-medium @apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-medium
disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5; disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
color: var(--fg-2); color: var(--fg-2);
border: 2px solid transparent; border: 1px solid transparent;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
} }
.btn-ghost:hover:not(:disabled) { .btn-ghost:hover:not(:disabled) {
color: var(--fg); color: var(--fg);
background: var(--m-yellow); background: var(--hover);
border-color: var(--ink); border-color: rgb(124 92 252 / 0.3);
} }
/* text link / quiet action */ /* text link / quiet action */
.link { @apply hover:text-slate-900 transition-colors cursor-pointer; color: var(--fg-3); } .link { @apply transition-colors cursor-pointer; color: var(--fg-3); }
.link:hover { color: var(--m-orange); } .link:hover { color: var(--m-orange); }
/* form controls — heavy outline, orange offset shadow on focus */ /* form controls — glass field, violet glow ring on focus */
.field { .field {
@apply w-full rounded-lg bg-white px-3 py-2 text-sm @apply w-full rounded-lg px-3 py-2 text-sm
outline-none placeholder:text-slate-400; outline-none placeholder:text-slate-400;
color: var(--fg); color: var(--fg);
border: 2px solid var(--ink-soft); background: var(--surface);
transition: border-color 0.15s ease, box-shadow 0.15s ease; border: 1px solid var(--ink-soft);
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
} }
.field:focus, .field:focus-visible { .field:focus, .field:focus-visible {
border-color: var(--ink); border-color: rgb(124 92 252 / 0.65);
box-shadow: 3px 3px 0 var(--m-orange); box-shadow: 0 0 0 3px rgb(124 92 252 / 0.18);
}
html.dark .field:focus, html.dark .field:focus-visible {
border-color: rgb(139 92 246 / 0.7);
box-shadow: 0 0 0 3px rgb(139 92 246 / 0.25);
} }
/* Native <select> using .field: drop the OS arrow for a custom chevron that /* Native <select> using .field: drop the OS arrow for a custom chevron that
@@ -680,15 +727,19 @@ html.dark .memphis-dots {
/* status pill */ /* status pill */
.pill { .pill {
@apply inline-flex items-center rounded-md px-2 py-0.5 text-xs font-semibold; @apply inline-flex items-center rounded-md px-2 py-0.5 text-xs font-semibold;
border: 1.5px solid var(--ink-soft); border: 1px solid var(--ink-soft);
} }
/* table primitives */ /* table primitives */
.th { @apply text-left px-4 py-3 text-[11px] font-bold uppercase tracking-wider; color: var(--fg-faint); } .th { @apply text-left px-4 py-3 text-[11px] font-bold uppercase tracking-wider; color: var(--fg-faint); }
.td { @apply px-4 py-3.5 align-middle; } .td { @apply px-4 py-3.5 align-middle; }
.row { @apply border-t transition-colors; border-color: var(--hairline); } .row { @apply border-t transition-colors; border-color: var(--hairline); }
.row:hover { background: rgb(254 240 138 / 0.35); } .row:hover { background: rgb(124 92 252 / 0.06); }
/* avatar chip for identity columns */ /* avatar chip for identity columns */
.avatar { @apply w-8 h-8 rounded-full grid place-items-center text-xs font-bold shrink-0; border: 2px solid var(--ink); } .avatar {
@apply w-8 h-8 rounded-full grid place-items-center text-xs font-bold shrink-0;
border: 1px solid var(--ink);
background: var(--surface-2);
}
} }
+56 -35
View File
@@ -1,14 +1,14 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { site } from '../site' import { site } from '../site'
const contact = computed(() => site.contact || {}) const contact = computed(() => site.contact || {})
const powers = [ const powers = [
{ icon: '🎨', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', bg: 'var(--m-mint)' }, { icon: 't2i', title: '文字生图', desc: '一句话出图,画幅与分辨率随模型可选。', tint: 'linear-gradient(135deg,#34d399,#2dd4bf)' },
{ icon: '🖼️', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', bg: 'var(--m-pink)' }, { icon: 'i2i', title: '参考图生图', desc: '上传参考换风格、改构图、做变体。', tint: 'linear-gradient(135deg,#e879f9,#f472b6)' },
{ icon: '🎬', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', bg: 'var(--m-blue)' }, { icon: 't2v', title: '文字生视频', desc: '一段描述生成短片,时长分辨率可选。', tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)' },
{ icon: '✨', title: '图生视频', desc: '给张参考图,让静态画面动起来。', bg: 'var(--m-yellow)' }, { icon: 'i2v', title: '图生视频', desc: '给张参考图,让静态画面动起来。', tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)' },
] ]
</script> </script>
@@ -29,12 +29,12 @@ const powers = [
</p> </p>
<router-link to="/user" class="bubble-btn mt-7">开始创作 →</router-link> <router-link to="/user" class="bubble-btn mt-7">开始创作 →</router-link>
</div> </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"> <div class="ab-cluster" aria-hidden="true">
<span class="ab-tile ab-1">🎨</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">🖼️</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">🎬</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">✨</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> <span class="ab-star">✦</span>
</div> </div>
</div> </div>
@@ -45,11 +45,15 @@ const powers = [
<div class="anime-eyebrow text-[var(--m-orange)]">能做什么</div> <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> <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 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" <div v-for="p in powers" :key="p.title" class="power-card p-5">
class="power-card p-5" :style="{ background: p.bg }"> <div class="power-ico" :style="{ background: p.tint }">
<div class="text-3xl">{{ p.icon }}</div> <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>
<div class="mt-3 text-base font-extrabold text-[#111]">{{ p.title }}</div> <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>
<div class="mt-1.5 text-[13px] font-medium leading-relaxed text-[#111]/60">{{ p.desc }}</div> <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>
</div> </div>
</section> </section>
@@ -64,7 +68,7 @@ const powers = [
<a v-if="contact.shop" :href="contact.shop" target="_blank" rel="noopener" <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"> class="group anime-card flex items-center justify-between gap-3 p-5">
<div> <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 class="text-base font-semibold text-[color:var(--fg)] mt-2 group-hover:text-[var(--m-orange)] transition-colors">前往商店</div>
</div> </div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span> <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}`" <a v-if="contact.email" :href="`mailto:${contact.email}`"
class="group anime-card p-5"> 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> <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>
<a v-if="contact.qq && contact.qq_link" :href="contact.qq_link" target="_blank" rel="noopener" <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"> class="group anime-card flex items-center justify-between gap-3 p-5">
<div> <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 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> </div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span> <span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
</a> </a>
<div v-else-if="contact.qq" class="anime-card p-5"> <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 class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all">{{ contact.qq }}</div>
</div> </div>
<a v-if="contact.qq_group && contact.qq_group_link" :href="contact.qq_group_link" target="_blank" rel="noopener" <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"> class="group anime-card flex items-center justify-between gap-3 p-5">
<div> <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 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> </div>
<span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span> <span class="text-[color:var(--fg-faint)] group-hover:translate-x-1 transition-transform">→</span>
</a> </a>
<div v-else-if="contact.qq_group" class="anime-card p-5"> <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 class="text-base font-semibold text-[color:var(--fg)] mt-2 tabular-nums break-all">{{ contact.qq_group }}</div>
</div> </div>
</div> </div>
@@ -110,7 +114,7 @@ const powers = [
</template> </template>
<style scoped> <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 { .ab-cluster {
position: relative; position: relative;
min-height: 240px; min-height: 240px;
@@ -121,15 +125,19 @@ const powers = [
place-items: center; place-items: center;
width: 5.5rem; width: 5.5rem;
height: 5.5rem; height: 5.5rem;
font-size: 2.2rem; border: 1px solid var(--glass-border);
border: 3px solid var(--ink); border-radius: 1.2rem;
border-radius: 1rem; background: var(--surface);
box-shadow: 6px 6px 0 var(--shadow-ink); -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-tile svg { width: 2.3rem; height: 2.3rem; }
.ab-2 { right: 14%; top: 0; background: var(--m-pink); transform: rotate(7deg); animation: memphis-float 7s ease-in-out infinite reverse; } .ab-1 { left: 12%; top: 6%; transform: rotate(-9deg); animation: memphis-float 6s ease-in-out infinite; }
.ab-3 { left: 26%; bottom: 4%; background: var(--m-blue); transform: rotate(5deg); animation: memphis-bounce 4.5s 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-4 { right: 8%; bottom: 14%; background: var(--m-yellow); transform: rotate(-6deg); animation: memphis-float 5.5s ease-in-out infinite; } .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 { .ab-star {
position: absolute; position: absolute;
left: 50%; top: 44%; left: 50%; top: 44%;
@@ -138,12 +146,25 @@ const powers = [
animation: memphis-wiggle 3.5s ease-in-out infinite; animation: memphis-wiggle 3.5s ease-in-out infinite;
} }
.power-card { .power-card {
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
border-radius: 1rem; border-radius: 1.1rem;
box-shadow: 5px 5px 0 var(--shadow-ink); background: var(--surface);
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;
} }
.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 (max-width: 768px) { .ab-cluster { display: none; } }
@media (prefers-reduced-motion: reduce) { .ab-tile, .ab-star { animation: none; } } @media (prefers-reduced-motion: reduce) { .ab-tile, .ab-star { animation: none; } }
</style> </style>
+58 -41
View File
@@ -883,26 +883,28 @@ const saveLabel = computed(() => ({
</template> </template>
<style scoped> <style scoped>
/* ===== 画布:Memphis/neo-brutalism 全屏工作台(随站点明暗主题走变量) ===== */ /* ===== 画布:玻璃拟态全屏工作台(随站点明暗主题走变量) ===== */
.cv-btn { .cv-btn {
padding: 5px 11px; padding: 5px 11px;
border-radius: 9px; border-radius: 9px;
border: 2.5px solid var(--ink); border: 1px solid var(--ink-soft);
background: var(--surface); background: var(--surface);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
color: var(--fg); color: var(--fg);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease; transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease, border-color 0.12s ease;
} }
.cv-btn:hover:not(:disabled) { .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) { .cv-btn:active:not(:disabled) {
transform: translate(2px, 2px); transform: translateY(1px);
box-shadow: 1px 1px 0 var(--shadow-ink);
} }
.cv-btn:disabled { .cv-btn:disabled {
opacity: 0.45; opacity: 0.45;
@@ -912,16 +914,18 @@ const saveLabel = computed(() => ({
background: #ffb4ab; background: #ffb4ab;
} }
.cv-btn--accent { .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) { .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-select,
.cv-input { .cv-input {
padding: 6px 10px; padding: 6px 10px;
border-radius: 9px; border-radius: 9px;
border: 2.5px solid var(--ink); border: 1px solid var(--ink-soft);
background: var(--surface); background: var(--surface);
color: var(--fg); color: var(--fg);
font-size: 13px; font-size: 13px;
@@ -929,7 +933,8 @@ const saveLabel = computed(() => ({
} }
.cv-select:focus, .cv-select:focus,
.cv-input: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 { .cv-page {
@@ -947,8 +952,10 @@ const saveLabel = computed(() => ({
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
padding: 9px 14px; padding: 9px 14px;
border-bottom: 3px solid var(--ink); border-bottom: 1px solid var(--ink-soft);
background: var(--surface); background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
flex-wrap: wrap; flex-wrap: wrap;
} }
.cv-top__left, .cv-top__left,
@@ -975,8 +982,8 @@ const saveLabel = computed(() => ({
color: var(--fg); color: var(--fg);
min-width: 46px; min-width: 46px;
text-align: center; text-align: center;
background: var(--m-yellow); background: var(--hover);
border: 2px solid var(--ink); border: 1px solid rgb(124 92 252 / 0.35);
border-radius: 999px; border-radius: 999px;
padding: 1px 8px; padding: 1px 8px;
} }
@@ -1017,9 +1024,9 @@ const saveLabel = computed(() => ({
font-size: 15px; font-size: 15px;
font-weight: 800; font-weight: 800;
color: var(--fg); color: var(--fg);
border: 3px dashed var(--ink); border: 2px dashed rgb(34 211 238 / 0.6);
border-radius: 18px; border-radius: 18px;
background: color-mix(in srgb, var(--m-blue) 55%, transparent); background: rgb(34 211 238 / 0.1);
pointer-events: none; pointer-events: none;
} }
.cv-loading, .cv-loading,
@@ -1039,12 +1046,14 @@ const saveLabel = computed(() => ({
color: #dc2626; color: #dc2626;
} }
/* 右侧生成面板 = 白纸卡片 */ /* 右侧生成面板 = 玻璃侧栏 */
.cv-panel { .cv-panel {
width: 264px; width: 264px;
flex-shrink: 0; flex-shrink: 0;
border-left: 3px solid var(--ink); border-left: 1px solid var(--ink-soft);
background: var(--surface); background: var(--surface);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
padding: 16px 14px; padding: 16px 14px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1081,19 +1090,23 @@ const saveLabel = computed(() => ({
.cv-chip { .cv-chip {
padding: 3px 11px; padding: 3px 11px;
border-radius: 999px; border-radius: 999px;
border: 2.5px solid var(--ink); border: 1px solid var(--ink-soft);
background: var(--surface); background: var(--surface);
color: var(--fg); color: var(--fg);
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
transition: background 0.13s ease, border-color 0.13s ease, box-shadow 0.13s ease;
} }
.cv-chip:hover { .cv-chip:hover {
background: var(--m-blue); background: var(--hover);
border-color: rgb(124 92 252 / 0.4);
} }
.cv-chip.is-on { .cv-chip.is-on {
background: var(--m-yellow); color: #fff;
box-shadow: 2.5px 2.5px 0 var(--shadow-ink); background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border-color: transparent;
box-shadow: 0 4px 14px var(--accent-glow);
} }
.cv-refs { .cv-refs {
font-size: 11px; font-size: 11px;
@@ -1106,22 +1119,23 @@ const saveLabel = computed(() => ({
.cv-gen { .cv-gen {
padding: 10px; padding: 10px;
border-radius: 11px; border-radius: 11px;
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.18);
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
font-weight: 800; font-weight: 800;
cursor: pointer; cursor: pointer;
box-shadow: 4px 4px 0 var(--shadow-ink); box-shadow: 0 8px 22px var(--accent-glow);
transition: transform 0.12s ease, box-shadow 0.12s ease; transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
} }
.cv-gen:hover:not(:disabled) { .cv-gen:hover:not(:disabled) {
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 5px 5px 0 var(--shadow-ink); filter: brightness(1.07);
box-shadow: 0 12px 30px var(--accent-glow);
} }
.cv-gen:active:not(:disabled) { .cv-gen:active:not(:disabled) {
transform: translate(3px, 3px); transform: translateY(1px) scale(0.99);
box-shadow: 1px 1px 0 var(--shadow-ink); box-shadow: 0 4px 12px var(--accent-glow);
} }
.cv-gen:disabled { .cv-gen:disabled {
opacity: 0.5; opacity: 0.5;
@@ -1137,9 +1151,9 @@ const saveLabel = computed(() => ({
font-weight: 600; font-weight: 600;
color: #92400e; color: #92400e;
line-height: 1.6; line-height: 1.6;
border: 2px dashed #f59e0b; border: 1px dashed rgb(245 158 11 / 0.6);
border-radius: 9px; border-radius: 9px;
background: var(--m-yellow); background: rgb(245 158 11 / 0.12);
padding: 8px 10px; padding: 8px 10px;
} }
.cv-tips { .cv-tips {
@@ -1163,8 +1177,8 @@ const saveLabel = computed(() => ({
max-width: 92vw; max-width: 92vw;
max-height: 92vh; max-height: 92vh;
border-radius: 12px; border-radius: 12px;
border: 3px solid #fff; border: 1px solid rgb(255 255 255 / 0.5);
box-shadow: 10px 10px 0 var(--m-orange); box-shadow: 0 24px 60px rgb(0 0 0 / 0.5), 0 0 40px var(--accent-glow);
} }
.cv-picker { .cv-picker {
@@ -1179,10 +1193,12 @@ const saveLabel = computed(() => ({
.cv-picker__box { .cv-picker__box {
width: min(720px, 92vw); width: min(720px, 92vw);
max-height: 80vh; max-height: 80vh;
background: var(--surface); background: var(--menu-bg);
border: 3px solid var(--ink); -webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 16px; border-radius: 16px;
box-shadow: 8px 8px 0 var(--shadow-ink); box-shadow: var(--glass-shadow-lg);
padding: 16px; padding: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1212,13 +1228,14 @@ const saveLabel = computed(() => ({
aspect-ratio: 1; aspect-ratio: 1;
object-fit: cover; object-fit: cover;
border-radius: 10px; border-radius: 10px;
border: 2.5px solid var(--ink); border: 1px solid var(--ink-soft);
cursor: pointer; cursor: pointer;
background: var(--surface-2); 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 { .cv-picker__grid img:hover {
transform: translate(-2px, -2px); transform: translateY(-2px);
box-shadow: 4px 4px 0 var(--m-orange); border-color: rgb(124 92 252 / 0.55);
box-shadow: 0 8px 20px var(--accent-glow);
} }
</style> </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::placeholder, .txt::placeholder, .field::placeholder { color: var(--fg-faint); }
.num:focus, .txt:focus, .field:focus { .num:focus, .txt:focus, .field:focus {
border-color: var(--ink); border-color: rgb(124 92 252 / 0.65);
box-shadow: 3px 3px 0 var(--m-orange); 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: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; } .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; } .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; } .field { width: 100%; padding: 0.55rem 0.75rem; font-size: 0.85rem; }
/* Section save buttons — solid memphis orange with a hard offset shadow. /* Section save buttons — violet→cyan gradient with a glow. The global
The global .btn-primary under .public-dark already uses this palette; we .btn-primary under .public-dark already uses this palette; we re-assert it
re-assert it here so the save action stays visually distinct. */ here so the save action stays visually distinct. */
.btn-primary { .btn-primary {
padding: 0.55rem 1.15rem; border-radius: 0.6rem; padding: 0.55rem 1.15rem; border-radius: 0.6rem;
font-size: 0.8rem; font-weight: 800; color: white; font-size: 0.8rem; font-weight: 800; color: white;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8);
border: 2px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.16);
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 6px 18px var(--accent-glow);
transition: filter 0.15s, transform 0.12s, box-shadow 0.15s, opacity 0.15s; 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, `.public-dark .btn-primary:hover` in style.css repaints the background,
so even disabled save buttons (注册与登录 / 邮件服务 default to disabled) so even disabled save buttons would flash on hover. Keep this rule
would flash on hover. Keep this rule unconditional; only the unconditional; only the brightness/lift below is gated on :not(:disabled). */
brightness/lift below is gated on :not(:disabled). */ .btn-primary:hover { filter: brightness(1.08); }
.btn-primary:hover { background: #f97316; } .btn-primary:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); }
.btn-primary:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); } .btn-primary:active:not(:disabled) { transform: translateY(1px); box-shadow: none; }
.btn-primary:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: none; }
.btn-primary:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; } .btn-primary:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
</style> </style>
+172 -107
View File
@@ -121,32 +121,32 @@ const caps = computed(() => {
{ {
key: 't2i', title: '文字生图', model: imageModels.value[0]?.id, key: 't2i', title: '文字生图', model: imageModels.value[0]?.id,
desc: '一句话出图,画幅与分辨率按所选模型的可用范围给出。', desc: '一句话出图,画幅与分辨率按所选模型的可用范围给出。',
tint: '#a7f3d0', color: '#065f46', tint: 'linear-gradient(135deg,#34d399,#2dd4bf)', color: '#fff',
metas: [resRange.value, `${imageModels.value.length} 个模型`].filter(Boolean), metas: [resRange.value, `${imageModels.value.length} 个模型`].filter(Boolean),
}, },
{ {
key: 'i2i', title: '参考图生图', model: i2iModels[0]?.id, key: 'i2i', title: '参考图生图', model: i2iModels[0]?.id,
desc: '上传参考图换风格、改构图、做变体,支持多张参考。', desc: '上传参考图换风格、改构图、做变体,支持多张参考。',
tint: '#ffc1cc', color: '#9f1239', tint: 'linear-gradient(135deg,#e879f9,#f472b6)', color: '#fff',
metas: ['多图参考', `${i2iModels.length} 个模型`], metas: ['多图参考', `${i2iModels.length} 个模型`],
}, },
{ {
key: 't2v', title: '文字生视频', model: videoModels.value[0]?.id, key: 't2v', title: '文字生视频', model: videoModels.value[0]?.id,
desc: '一段描述生成短片,时长与分辨率按模型能力可选。', desc: '一段描述生成短片,时长与分辨率按模型能力可选。',
tint: '#bae6fd', color: '#075985', tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)', color: '#fff',
metas: [durationRange.value, videoRes.value, `${videoModels.value.length} 个模型`].filter(Boolean), metas: [durationRange.value, videoRes.value, `${videoModels.value.length} 个模型`].filter(Boolean),
}, },
{ {
key: 'i2v', title: '图生视频', model: i2vModels[0]?.id, key: 'i2v', title: '图生视频', model: i2vModels[0]?.id,
desc: '给参考图或首帧,让静态画面动起来。', desc: '给参考图或首帧,让静态画面动起来。',
tint: '#fef08a', color: '#92400e', tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)', color: '#fff',
metas: ['参考图', `${i2vModels.length} 个模型`], metas: ['参考图', `${i2vModels.length} 个模型`],
}, },
].filter((c) => c.model) ].filter((c) => c.model)
}) })
// ---- model cards: the admin-set alias wins, so no upstream naming leaks ---- // ---- 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 modelCards = computed(() => managed.value.map((m, i) => {
const bits = [] const bits = []
if (m.resolutions?.length) bits.push(m.resolutions.join(' / ')) 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"> <section class="hero-band relative overflow-hidden pt-14 md:pt-20 pb-12">
<div class="anime-stars"></div> <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"> <div aria-hidden="true" class="pointer-events-none absolute inset-0 hidden lg:block">
<span class="deco deco-a">🎨</span> <span class="deco deco-a">
<span class="deco deco-b">⚡</span> <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-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">✦</span>
<span class="deco deco-star deco-star-2">✦</span> <span class="deco deco-star deco-star-2">✦</span>
</div> </div>
@@ -241,7 +247,7 @@ function useExample(ex) {
</section> </section>
<!-- ============ GALLERY MARQUEE (hero + bento imagery) ============ --> <!-- ============ 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"> <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" <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)"> class="gcard" :style="cardBg(card)" :aria-label="card.title" @click="useExample(card)">
@@ -257,17 +263,22 @@ function useExample(ex) {
<!-- ============ KPI STRIP ============ --> <!-- ============ KPI STRIP ============ -->
<section class="mt-12 grid grid-cols-2 md:grid-cols-4 gap-3"> <section class="mt-12 grid grid-cols-2 md:grid-cols-4 gap-3">
<div v-for="k in [ <div v-for="k in [
{ v: modelCount, l: '在线模型', e: '🧩', bg: 'var(--m-yellow)' }, { v: modelCount, l: '在线模型', i: 'models', tint: 'linear-gradient(135deg,#7c5cfc,#a855f7)' },
{ v: caps.length, l: '创作方式', e: '🎨', bg: 'var(--m-pink)' }, { v: caps.length, l: '创作方式', i: 'wand', tint: 'linear-gradient(135deg,#e879f9,#f472b6)' },
{ v: resRange, l: '输出分辨率', e: '🖼️', bg: 'var(--m-blue)' }, { v: resRange, l: '输出分辨率', i: 'image', tint: 'linear-gradient(135deg,#38bdf8,#22d3ee)' },
{ v: avgLabel, l: '平均出片 · 近 24h', e: '⚡', bg: 'var(--m-mint)' }, { v: avgLabel, l: '平均出片 · 近 24h', i: 'bolt', tint: 'linear-gradient(135deg,#34d399,#2dd4bf)' },
]" :key="k.l" ]" :key="k.l"
class="kpi-card px-5 py-4" :style="{ background: k.bg }"> class="kpi-card px-5 py-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2.5">
<span class="text-lg">{{ k.e }}</span> <span class="kpi-ico" :style="{ background: k.tint }">
<span class="text-[26px] font-black tabular-nums text-[#111]">{{ k.v }}</span> <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>
<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> </div>
</section> </section>
@@ -371,15 +382,15 @@ function useExample(ex) {
注册即可开始,图像与视频都能试;不满意就换个模型,额度按次计费。 注册即可开始,图像与视频都能试;不满意就换个模型,额度按次计费。
</p> </p>
<button type="button" @click="go('/user')" <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> </button>
</div> </div>
</section> </section>
<!-- thin footer line --> <!-- 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"> <div class="flex items-center gap-3">
<span class="font-semibold text-[color:var(--fg-2)]">{{ site.title }}</span> <span class="font-semibold text-[color:var(--fg-2)]">{{ site.title }}</span>
<span>·</span> <span>·</span>
@@ -401,15 +412,18 @@ function useExample(ex) {
gap: 0.5rem; gap: 0.5rem;
max-width: 36rem; max-width: 36rem;
padding: 0.5rem; padding: 0.5rem;
border-radius: 1rem; border-radius: 1.1rem;
background: var(--surface); background: var(--surface);
border: 3px solid var(--ink); -webkit-backdrop-filter: blur(16px);
box-shadow: 7px 7px 0 var(--shadow-ink); backdrop-filter: blur(16px);
transition: box-shadow 0.2s ease, transform 0.2s ease; 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 { .composer:focus-within {
transform: translate(-2px, -2px); transform: translateY(-2px);
box-shadow: 10px 10px 0 var(--m-orange); border-color: rgb(124 92 252 / 0.5);
box-shadow: 0 18px 50px var(--accent-glow), var(--glass-shadow);
} }
.composer-input { .composer-input {
flex: 1; flex: 1;
@@ -425,48 +439,50 @@ function useExample(ex) {
.composer-input::placeholder { color: var(--fg-faint); } .composer-input::placeholder { color: var(--fg-faint); }
.composer-go { .composer-go {
flex: none; flex: none;
border: 2.5px solid var(--ink); border: 1px solid rgb(255 255 255 / 0.16);
cursor: pointer; cursor: pointer;
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
font-weight: 800; font-weight: 800;
padding: 0.65rem 1.2rem; padding: 0.65rem 1.2rem;
border-radius: 0.7rem; border-radius: 0.75rem;
background: var(--m-orange); background: linear-gradient(120deg, #7c5cfc, #5f6cf8 55%, #22d3ee 140%);
box-shadow: 3px 3px 0 var(--shadow-ink); box-shadow: 0 6px 18px var(--accent-glow);
transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease; 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:hover { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); filter: brightness(1.07); }
.composer-go:active { transform: scale(0.96) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); } .composer-go:active { transform: scale(0.96) translateY(1px); box-shadow: 0 3px 10px var(--accent-glow); }
.chip { .chip {
font-size: 12.5px; font-size: 12.5px;
font-weight: 600; font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
cursor: pointer; cursor: pointer;
border: 2px solid var(--ink); border: 1px solid var(--ink-soft);
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
border-radius: 0.6rem; border-radius: 999px;
background: var(--surface); 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 { .chip:hover {
color: #111; color: var(--fg);
background: var(--m-yellow); background: var(--hover);
box-shadow: 3px 3px 0 var(--shadow-ink); border-color: rgb(124 92 252 / 0.45);
transform: translate(-1px, -1px); box-shadow: 0 4px 14px rgb(124 92 252 / 0.18);
transform: translateY(-1px);
} }
html.dark .chip:hover { color: #111; }
/* ---------- hero cards (kind=hero) ---------- */ /* ---------- hero cards (kind=hero) ---------- */
.fcard { .fcard {
position: absolute; position: absolute;
border: 3px solid var(--ink); border: 1px solid var(--glass-border);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1rem; border-radius: 1.1rem;
overflow: hidden; 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); 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; 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; overflow: hidden;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
padding: 1.35rem; padding: 1.35rem;
border-radius: 1rem; border-radius: 1.1rem;
background: var(--surface); background: var(--surface);
box-shadow: 5px 5px 0 var(--shadow-ink); -webkit-backdrop-filter: blur(14px);
transition: transform 0.18s ease, box-shadow 0.18s ease; 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 { .cap-card:hover {
transform: translate(-3px, -3px); transform: translateY(-4px);
box-shadow: 9px 9px 0 var(--m-orange); 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-card:hover::before { opacity: 1; }
.cap-arrow { position: absolute; top: 1.1rem; right: 1.1rem; color: var(--fg-3); transition: color 0.15s ease; } .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); } .cap-card:hover .cap-arrow { color: var(--m-orange); transform: translate(2px, -2px); }
.cap-icon { .cap-icon {
position: relative;
width: 2.6rem; width: 2.6rem;
height: 2.6rem; height: 2.6rem;
border-radius: 0.7rem; border-radius: 0.8rem;
border: 2px solid var(--ink); border: 1px solid var(--ink-soft);
display: grid; display: grid;
place-items: center; place-items: center;
margin-bottom: 0.9rem; margin-bottom: 0.9rem;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
} }
.cap-icon svg { width: 1.25rem; height: 1.25rem; } .cap-icon svg { width: 1.25rem; height: 1.25rem; }
.cap-meta { .cap-meta {
@@ -542,7 +576,7 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
color: var(--fg-2); color: var(--fg-2);
padding: 0.18rem 0.5rem; padding: 0.18rem 0.5rem;
border-radius: 0.4rem; border-radius: 0.4rem;
border: 1.5px solid var(--ink-soft); border: 1px solid var(--ink-soft);
background: var(--surface-2); background: var(--surface-2);
} }
@@ -550,14 +584,14 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
.tile { .tile {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1rem; border-radius: 1.1rem;
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease; 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 { .tile-veil {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -584,14 +618,14 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
display: block; display: block;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1rem; border-radius: 1.1rem;
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease; 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-img { display: block; width: 100%; aspect-ratio: 4 / 5; transition: transform 0.5s ease; }
.work:hover .work-img { transform: scale(1.04); } .work:hover .work-img { transform: scale(1.04); }
.work-overlay { .work-overlay {
@@ -627,11 +661,12 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
font-style: normal; font-style: normal;
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 800;
color: rgb(11 13 19); color: #fff;
background: var(--m-yellow); background: linear-gradient(120deg, #7c5cfc, #22d3ee);
border: 2px solid #111; border: 1px solid rgb(255 255 255 / 0.25);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
box-shadow: 0 4px 14px rgb(124 92 252 / 0.4);
} }
/* ---------- model row ---------- */ /* ---------- model row ---------- */
@@ -643,14 +678,15 @@ html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
width: 14.5rem; width: 14.5rem;
overflow: hidden; overflow: hidden;
padding: 1.25rem; padding: 1.25rem;
border-radius: 1rem; border-radius: 1.1rem;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
background: var(--surface); background: var(--surface);
box-shadow: 5px 5px 0 var(--shadow-ink); -webkit-backdrop-filter: blur(14px);
transition: box-shadow 0.18s ease, transform 0.18s ease; 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); } .model-card:hover { transform: translateY(-3px); border-color: rgb(124 92 252 / 0.45); box-shadow: var(--glass-shadow-lg); }
html.dark .model-card { background: rgb(255 255 255 / 0.05); }
.model-glow { .model-glow {
position: absolute; position: absolute;
top: -2.5rem; top: -2.5rem;
@@ -659,39 +695,43 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
height: 8rem; height: 8rem;
border-radius: 50%; border-radius: 50%;
filter: blur(40px); filter: blur(40px);
opacity: 0.3; opacity: 0.35;
} }
.model-tag { .model-tag {
font-size: 10.5px; font-size: 10.5px;
font-weight: 800; font-weight: 800;
padding: 0.18rem 0.55rem; padding: 0.18rem 0.55rem;
border-radius: 0.4rem; 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-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: var(--m-blue); color: #075985; } .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 { .hero-band {
border-radius: 2rem; border-radius: 2rem;
} }
/* Floating crayon chips scattered around the headline — each is a small /* Floating glass chips scattered around the headline — each is a small
ink-outlined tile with a hard shadow and its own idle animation. */ frosted tile with a soft shadow and its own idle animation. */
.deco { .deco {
position: absolute; position: absolute;
display: grid; display: grid;
place-items: center; place-items: center;
border: 3px solid var(--ink); border: 1px solid var(--glass-border);
box-shadow: 5px 5px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
font-weight: 900; 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 { .deco-a {
left: 7%; top: 22%; left: 7%; top: 22%;
width: 3.6rem; height: 3.6rem; width: 3.6rem; height: 3.6rem;
border-radius: 0.9rem; border-radius: 1rem;
background: var(--m-yellow);
font-size: 1.5rem;
transform: rotate(-8deg); transform: rotate(-8deg);
animation: memphis-float 6s ease-in-out infinite; 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%; right: 8%; top: 16%;
width: 3rem; height: 3rem; width: 3rem; height: 3rem;
border-radius: 999px; border-radius: 999px;
background: var(--m-pink);
font-size: 1.2rem;
transform: rotate(6deg); transform: rotate(6deg);
animation: memphis-wiggle 3.5s ease-in-out infinite; animation: memphis-wiggle 3.5s ease-in-out infinite;
} }
.deco-b svg { color: var(--m-orange-2); }
.deco-c { .deco-c {
left: 13%; bottom: 16%; left: 13%; bottom: 16%;
width: 3.2rem; height: 2.4rem; width: 3.2rem; height: 2.4rem;
border-radius: 0.6rem; border-radius: 0.7rem;
background: var(--m-blue);
font-size: 0.95rem; font-size: 0.95rem;
letter-spacing: 0.05em; letter-spacing: 0.05em;
color: var(--m-orange);
transform: rotate(9deg); transform: rotate(9deg);
animation: memphis-float 7s ease-in-out infinite reverse; animation: memphis-float 7s ease-in-out infinite reverse;
} }
.deco-d { .deco-d {
right: 13%; bottom: 20%; right: 13%; bottom: 20%;
width: 3.4rem; height: 2.6rem; width: 3.4rem; height: 2.6rem;
border-radius: 0.7rem; border-radius: 0.8rem;
background: var(--m-mint);
font-size: 1.15rem;
transform: rotate(-7deg); transform: rotate(-7deg);
animation: memphis-bounce 4s ease-in-out infinite; animation: memphis-bounce 4s ease-in-out infinite;
} }
.deco-d svg { color: #34d399; }
.deco-star { .deco-star {
left: 22%; top: 10%; left: 22%; top: 10%;
border: none; border: none;
box-shadow: none; box-shadow: none;
background: transparent;
-webkit-backdrop-filter: none;
backdrop-filter: none;
font-size: 1.7rem; font-size: 1.7rem;
color: var(--m-orange); color: var(--m-orange);
animation: memphis-wiggle 4s ease-in-out infinite; 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; } } @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 { .kpi-card {
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
border-radius: 1rem; border-radius: 1.1rem;
box-shadow: 5px 5px 0 var(--shadow-ink); background: var(--surface);
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;
} }
.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 ---------- */ /* ---------- gallery marquee ---------- */
.marquee-mask { .marquee-mask {
@@ -767,18 +831,19 @@ html.dark .model-card { background: rgb(255 255 255 / 0.05); }
width: 15rem; width: 15rem;
height: 10rem; height: 10rem;
overflow: hidden; overflow: hidden;
border: 2.5px solid var(--ink); border: 1px solid var(--glass-border);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1rem; border-radius: 1rem;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
box-shadow: 4px 4px 0 var(--shadow-ink); box-shadow: var(--glass-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
} }
.gcard:hover { .gcard:hover {
transform: translate(-2px, -2px); transform: translateY(-3px);
box-shadow: 7px 7px 0 var(--m-orange); 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) { @media (prefers-reduced-motion: reduce) {
.marquee { animation: none; } .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.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.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.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> </span>
</div> </div>
<div class="text-[10px] text-white/35 mt-0.5 tabular-nums">{{ fmtSize(f.size) }} · {{ fmtTs(f.mtime) }}</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"> <div class="theme-text space-y-10">
<!-- header --> <!-- header -->
<header> <header>
<span class="sticker">🎀 奖励</span> <span class="sticker">✦ 奖励</span>
<h1 class="mt-3 text-4xl md:text-5xl font-bold tracking-tight">邀请好友</h1> <h1 class="mt-3 text-4xl md:text-5xl font-bold tracking-tight">邀请好友</h1>
<p class="text-white/45 mt-2">好友用你的链接注册,并完成首次生图后,你得 {{ INVITE_REWARD }} 积分。</p> <p class="text-white/45 mt-2">好友用你的链接注册,并完成首次生图后,你得 {{ INVITE_REWARD }} 积分。</p>
</header> </header>
<section class="relative rounded-2xl p-7 md:p-8 overflow-hidden ink-card"> <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="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" /> <Icon name="accounts" class="w-5 h-5" />
</div> </div>
<!-- summary --> <!-- summary -->
<div class="relative mt-6 grid grid-cols-2 gap-3"> <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="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-[#111]">{{ inviteCount }}</div> <div class="text-2xl font-black tabular-nums text-[color:var(--fg)]">{{ inviteCount }}</div>
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">已邀请</div> <div class="text-[10px] text-[color:var(--fg-3)] mt-1 uppercase tracking-widest font-bold">已邀请</div>
</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="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-[#111]">{{ inviteEarned.toLocaleString('en-US') }}</div> <div class="text-2xl font-black tabular-nums text-[color:var(--fg)]">{{ inviteEarned.toLocaleString('en-US') }}</div>
<div class="text-[10px] text-[#111]/55 mt-1 uppercase tracking-widest font-bold">累计积分</div> <div class="text-[10px] text-[color:var(--fg-3)] mt-1 uppercase tracking-widest font-bold">累计积分</div>
</div> </div>
</div> </div>
@@ -108,11 +109,12 @@ function toast(m) {
<label class="block text-xs font-bold text-white/50 mb-2">邀请码</label> <label class="block text-xs font-bold text-white/50 mb-2">邀请码</label>
<div class="flex gap-2"> <div class="flex gap-2">
<button @click="copyCode" <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 || '—' }} {{ inviteCode || '—' }}
</button> </button>
<button @click="copyInvite" <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> </button>
</div> </div>
+1 -1
View File
@@ -327,7 +327,7 @@ const sourcePill = (s) => ({
</template> </template>
<template v-if="e.deai"> <template v-if="e.deai">
<span class="text-white/25 mx-1.5">·</span> <span class="text-white/25 mx-1.5">·</span>
<span class="text-[#ec5b13]">去AI特征</span> <span class="text-[#a78bfa]">去AI特征</span>
</template> </template>
</td> </td>
<td class="px-3 py-3.5 text-right text-xs tabular-nums align-middle whitespace-nowrap"> <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" <div class="pg-cap text-[11px] leading-tight font-medium line-clamp-2 transition-colors"
:class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''" :class="item.prompt ? 'pointer-events-auto cursor-pointer' : ''"
:title="item.prompt ? '点击复制提示词' : ''" @click.stop="copyPrompt(item)">{{ item.prompt }}</div> :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> </div>
</template> </template>
<!-- pending / running --> <!-- pending / running -->
+1 -1
View File
@@ -273,7 +273,7 @@ async function recharge() {
<section class="lg:col-span-2 card overflow-hidden"> <section class="lg:col-span-2 card overflow-hidden">
<div class="p-7 md:p-8 relative"> <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" <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 class="relative grid md:grid-cols-[260px_1fr] gap-8 items-start">
<div> <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"> <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 + ' — 点击复制'" :title="e.error + ' — 点击复制'"
@click.stop="copyError(e)">⚠ {{ e.error }}</div> @click.stop="copyError(e)">⚠ {{ e.error }}</div>
</td> </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-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> <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> </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.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.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.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> </span>
</div> </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> <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>