style(ui): 换用 Memphis/neo-brutalism 视觉风格

参考 skills.yangsir.net:粗墨描边 + 硬偏移投影 + 平涂蜡笔色(橙/黄/粉/蓝/薄荷),
替换原二次元粉紫渐变体系。设计令牌集中在 style.css 的 --ink/--shadow-ink/
--m-* 变量,共享组件 .card/.btn-*/.field 与新工具类 (.memphis-dots/.ink-card/
.memphis-press) 使全站统一;暗色模式反转为浅色描边版本。

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-08 16:58:14 +08:00
co-authored by Devin
parent 910651ddba
commit 313d54a5ec
9 changed files with 507 additions and 362 deletions
+10 -10
View File
@@ -13,22 +13,22 @@ 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-2xl bg-white text-slate-800 shadow-2xl overflow-hidden flex flex-col max-h-[80vh]"> <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="px-6 py-4 border-b border-slate-100 flex items-center justify-between gap-3 shrink-0"> <div class="px-6 py-4 border-b-[2.5px] border-b-[#111] 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-violet-500/15 text-violet-600 grid place-items-center"> <span class="w-7 h-7 rounded-lg bg-[#fef08a] text-[#111] border-2 border-[#111] grid place-items-center">
<Icon name="spark" class="w-4 h-4" /> <Icon name="spark" class="w-4 h-4" />
</span> </span>
<h2 class="text-base font-semibold">公告</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-slate-400 hover:text-slate-700 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 border-slate-100 flex justify-end shrink-0"> <div class="px-6 py-4 border-t-[2.5px] border-t-[#111] flex justify-end shrink-0">
<button @click="dismissAnnouncement" <button @click="dismissAnnouncement"
class="rounded-lg bg-slate-900 text-white hover:bg-slate-700 px-5 py-2 text-sm font-medium transition-colors"> 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">
我知道了 我知道了
</button> </button>
</div> </div>
@@ -51,12 +51,12 @@ const html = computed(() => marked.parse(announcement.content.trim() || '暂无
.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: rgb(124 58 237); 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: rgb(15 23 42); }
.ann-body :deep(code) { background: rgb(241 245 249); padding: 0.1em 0.35em; border-radius: 0.3rem; font-size: 0.85em; } .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(pre) { background: rgb(241 245 249); 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 rgb(196 181 253); 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: rgb(100 116 139); 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 rgb(226 232 240); 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>
+57 -53
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 brand glow --> <!-- ambient memphis crayon washes -->
<div class="pointer-events-none absolute -top-24 -right-16 h-56 w-56 rounded-full bg-fuchsia-500/20 blur-3xl"></div> <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 -bottom-24 -left-16 h-56 w-56 rounded-full bg-violet-500/20 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="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"
@@ -195,7 +195,7 @@ async function submit() {
<transition name="swap" mode="out-in"> <transition name="swap" mode="out-in">
<p v-if="mode === 'register' && auth.pendingInvite" class="invite-banner"> <p v-if="mode === 'register' && auth.pendingInvite" class="invite-banner">
<svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 12v10H4V12"/><path d="M2 7h20v5H2z"/><path d="M12 22V7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/></svg> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 12v10H4V12"/><path d="M2 7h20v5H2z"/><path d="M12 22V7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/></svg>
<span>已应用邀请码 <span class="font-mono font-semibold text-emerald-200">{{ auth.pendingInvite }}</span>,完成首次生图后邀请人得 3 积分</span> <span>已应用邀请码 <span class="font-mono font-bold text-emerald-800 dark:text-emerald-200">{{ auth.pendingInvite }}</span>,完成首次生图后邀请人得 3 积分</span>
</p> </p>
</transition> </transition>
@@ -262,44 +262,44 @@ async function submit() {
<style scoped> <style scoped>
.card { .card {
border-radius: 1.25rem; border-radius: 1.1rem;
background: linear-gradient(165deg, #ffffff 0%, #f6f6fb 100%); background: var(--surface);
color: var(--fg); color: var(--fg);
border: 1px solid var(--hairline); border: 3px solid var(--ink);
box-shadow: 0 30px 70px -22px rgb(15 23 42 / 0.25); box-shadow: 10px 10px 0 var(--shadow-ink);
overflow: hidden; overflow: hidden;
} }
html.dark .card { html.dark .card {
background: linear-gradient(165deg, #14161f 0%, #0d0f15 100%); background: #1c1d24;
color: rgb(255 255 255 / 0.9); color: rgb(255 255 255 / 0.9);
border: none;
box-shadow: 0 30px 70px -20px rgb(0 0 0 / 0.7);
} }
.logo-halo { .logo-halo {
position: relative; position: relative;
filter: drop-shadow(0 8px 18px rgb(168 85 247 / 0.45)); filter: drop-shadow(0 6px 0 rgb(236 91 19 / 0.35));
} }
/* segmented tab control */ /* segmented tab control */
.tabs { .tabs {
display: flex; gap: 0.25rem; padding: 0.25rem; display: flex; gap: 0.3rem; padding: 0.3rem;
border-radius: 0.75rem; border-radius: 0.7rem;
background: var(--surface-2); background: var(--surface-2);
border: 1px solid var(--hairline); border: 2px solid var(--ink);
} }
.tab { .tab {
flex: 1; padding: 0.45rem 0; border-radius: 0.55rem; flex: 1; padding: 0.45rem 0; border-radius: 0.5rem;
font-size: 0.8125rem; color: var(--fg-3); font-size: 0.8125rem; font-weight: 700; color: var(--fg-3);
transition: background 0.18s, color 0.18s, box-shadow 0.18s; border: 2px solid transparent;
transition: background 0.18s, color 0.18s, box-shadow 0.18s, border-color 0.18s;
} }
.tab:hover { color: var(--fg-2); } .tab:hover { color: var(--fg); }
.tab-on { .tab-on {
color: rgb(124 58 237); color: #111;
background: linear-gradient(135deg, rgb(167 139 250 / 0.22), rgb(236 72 153 / 0.18)); background: var(--m-yellow);
box-shadow: 0 1px 0 rgb(255 255 255 / 0.08) inset, 0 4px 12px -4px rgb(168 85 247 / 0.5); border-color: var(--ink);
box-shadow: 2px 2px 0 var(--shadow-ink);
} }
html.dark .tab-on { color: white; } html.dark .tab-on { color: #fff; background: var(--m-orange); }
/* icon-prefixed input */ /* icon-prefixed input */
.lm-field { position: relative; } .lm-field { position: relative; }
@@ -308,21 +308,18 @@ html.dark .tab-on { color: white; }
width: 1.05rem; height: 1.05rem; color: var(--fg-3); width: 1.05rem; height: 1.05rem; color: var(--fg-3);
pointer-events: none; transition: color 0.18s; pointer-events: none; transition: color 0.18s;
} }
.lm-field:focus-within .ic { color: rgb(124 58 237 / 0.95); } .lm-field:focus-within .ic { color: var(--m-orange); }
html.dark .lm-field:focus-within .ic { color: rgb(196 181 253 / 0.95); }
.fld { .fld {
width: 100%; padding: 0.7rem 0.85rem 0.7rem 2.4rem; border-radius: 0.7rem; width: 100%; padding: 0.7rem 0.85rem 0.7rem 2.4rem; border-radius: 0.6rem;
background: rgb(15 23 42 / 0.03); border: 1px solid var(--hairline); background: var(--surface); border: 2px 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.18s, box-shadow 0.18s, background 0.18s; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
} }
html.dark .fld { background: rgb(255 255 255 / 0.04); } html.dark .fld { background: rgb(255 255 255 / 0.05); }
.fld:focus { .fld:focus {
border-color: rgb(167 139 250 / 0.65); border-color: var(--ink);
background: rgb(124 58 237 / 0.04); box-shadow: 3px 3px 0 var(--m-orange);
box-shadow: 0 0 0 3px rgb(167 139 250 / 0.18);
} }
html.dark .fld:focus { background: rgb(255 255 255 / 0.06); }
.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). */
html.dark .fld:-webkit-autofill, html.dark .fld:-webkit-autofill,
@@ -343,16 +340,18 @@ html.dark .fld:-webkit-autofill:focus {
.eye:hover { color: var(--fg); background: var(--hover); } .eye:hover { color: var(--fg); background: var(--hover); }
.code-btn { .code-btn {
flex-shrink: 0; height: 2.7rem; padding: 0 0.85rem; border-radius: 0.7rem; flex-shrink: 0; height: 2.7rem; padding: 0 0.85rem; border-radius: 0.6rem;
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; white-space: nowrap; font-size: 0.75rem; font-weight: 800; white-space: nowrap;
color: rgb(124 58 237); /* violet-600 — readable on the light card */ color: #111;
background: rgb(167 139 250 / 0.12); border: 1px solid rgb(167 139 250 / 0.4); background: var(--m-yellow); border: 2px solid var(--ink);
transition: background 0.15s, opacity 0.15s; box-shadow: 2px 2px 0 var(--shadow-ink);
transition: background 0.15s, box-shadow 0.15s, transform 0.15s, opacity 0.15s;
} }
html.dark .code-btn { color: rgb(196 181 253 / 0.95); background: rgb(167 139 250 / 0.1); border-color: rgb(167 139 250 / 0.3); } html.dark .code-btn { color: #111; }
.code-btn:hover:not(:disabled) { background: rgb(167 139 250 / 0.2); } .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; }
/* 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. */
.code-btn:disabled { opacity: 0.55; cursor: not-allowed; } .code-btn:disabled { opacity: 0.55; cursor: not-allowed; }
@@ -361,14 +360,15 @@ html.dark .code-btn { color: rgb(196 181 253 / 0.95); background: rgb(167 139 25
.btn-primary { .btn-primary {
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.7rem; font-size: 0.9rem; font-weight: 600; 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: linear-gradient(135deg, #a855f7 0%, #7c3aed 50%, #ec4899 100%); background: var(--m-orange);
box-shadow: 0 10px 24px -8px rgb(168 85 247 / 0.6); border: 2.5px solid var(--ink);
transition: transform 0.12s, box-shadow 0.18s, filter 0.18s; box-shadow: 4px 4px 0 var(--shadow-ink);
transition: transform 0.13s, box-shadow 0.13s, filter 0.15s;
} }
.btn-primary:hover:not(:disabled) { filter: brightness(1.08); box-shadow: 0 12px 28px -8px rgb(168 85 247 / 0.75); } .btn-primary:hover:not(:disabled) { filter: brightness(1.06); transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--shadow-ink); }
.btn-primary:active:not(:disabled) { transform: translateY(1px); } .btn-primary:active:not(:disabled) { transform: scale(0.98) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.spinner { .spinner {
@@ -380,18 +380,22 @@ html.dark .code-btn { color: rgb(196 181 253 / 0.95); background: rgb(167 139 25
.invite-banner { .invite-banner {
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.7rem; 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: rgb(167 243 208); color: #065f46; font-weight: 600;
background: rgb(16 185 129 / 0.1); border: 1px solid rgb(16 185 129 / 0.25); background: var(--m-mint); border: 2px solid var(--ink);
} }
.invite-banner svg { color: rgb(110 231 183); } html.dark .invite-banner { color: rgb(167 243 208); background: rgb(16 185 129 / 0.15); }
.invite-banner svg { color: rgb(5 150 105); }
html.dark .invite-banner svg { color: rgb(110 231 183); }
.msg { .msg {
display: flex; align-items: center; gap: 0.4rem; margin-top: 0.85rem; display: flex; align-items: center; gap: 0.4rem; margin-top: 0.85rem;
font-size: 0.78rem; line-height: 1.4; font-size: 0.78rem; font-weight: 600; line-height: 1.4;
} }
.msg-err { color: rgb(253 164 175); } .msg-err { color: rgb(220 38 38); }
.msg-ok { color: rgb(110 231 183); } html.dark .msg-err { color: rgb(253 164 175); }
.msg-ok { color: rgb(5 150 105); }
html.dark .msg-ok { color: rgb(110 231 183); }
/* modal in/out */ /* modal in/out */
.modal-enter-active { transition: opacity 0.2s ease; } .modal-enter-active { transition: opacity 0.2s ease; }
+7 -7
View File
@@ -103,15 +103,15 @@ 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-2xl bg-white text-slate-800 shadow-2xl overflow-hidden"> <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="px-5 py-4 border-b border-slate-100 flex items-center justify-between"> <div class="px-5 py-4 border-b-[2.5px] border-b-[#111] 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-slate-400 hover:text-slate-700"><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-emerald-100 text-emerald-600 grid place-items-center mb-4"> <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">
<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-slate-800">充值成功</p>
@@ -127,7 +127,7 @@ onUnmounted(clearTimers)
<p class="text-lg font-semibold text-slate-700">支付超时</p> <p class="text-lg font-semibold text-slate-700">支付超时</p>
<p class="text-sm text-slate-500 mt-1">二维码已失效,订单已取消</p> <p class="text-sm text-slate-500 mt-1">二维码已失效,订单已取消</p>
<p class="text-xs text-slate-400 mt-1">如需充值请重新下单</p> <p class="text-xs text-slate-400 mt-1">如需充值请重新下单</p>
<button @click="finish" class="mt-5 rounded-lg bg-slate-900 text-white hover:bg-slate-700 px-5 py-2 text-sm font-medium transition-colors">关闭</button> <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>
</div> </div>
<!-- pending --> <!-- pending -->
@@ -135,7 +135,7 @@ 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-2xl ring-1 ring-slate-200 shadow-sm grid place-items-center overflow-hidden bg-white p-3.5"> <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">
<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>
@@ -145,7 +145,7 @@ onUnmounted(clearTimers)
<!-- 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-2 border-violet-200 border-t-violet-500 animate-spin mb-3"></div> <div class="w-12 h-12 rounded-full border-[3px] border-[#fecaca] border-t-[#ec5b13] animate-spin mb-3"></div>
<p class="text-sm font-medium text-slate-700">支付监控中…</p> <p class="text-sm font-medium text-slate-700">支付监控中…</p>
<p class="text-xs text-slate-400 mt-1">已打开支付页面,完成后自动到账</p> <p class="text-xs text-slate-400 mt-1">已打开支付页面,完成后自动到账</p>
</div> </div>
@@ -155,7 +155,7 @@ onUnmounted(clearTimers)
<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-slate-400">订单号</dt><dd class="font-mono text-xs text-slate-700">{{ 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-slate-400">状态</dt><dd class="text-amber-600 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-slate-400">金额</dt><dd class="font-semibold text-slate-800">¥{{ order.amount }}</dd></div>
<div class="flex justify-between"><dt class="text-slate-400">充值积分</dt><dd class="text-violet-600 font-semibold">{{ order.points }}</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-slate-400">下单时间</dt><dd class="text-xs text-slate-600">{{ fmtTime(order.created_at) }}</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-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-slate-400">支付倒计时</dt><dd class="tabular-nums font-medium" :class="remaining > 0 ? 'text-slate-700' : 'text-rose-500'">{{ remaining > 0 ? mmss : '已超时' }}</dd></div>
</dl> </dl>
+5 -5
View File
@@ -90,17 +90,17 @@ 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 border-[color:var(--hairline)] class="absolute z-30 mt-2 w-full max-h-64 overflow-auto rounded-xl border-[2.5px] border-[color:var(--ink)]
bg-[var(--menu-bg)] backdrop-blur-xl p-1.5 shadow-2xl shadow-black/20 ring-1 ring-[color:var(--hairline)]"> bg-[var(--menu-bg)] p-1.5 shadow-[5px_5px_0_var(--shadow-ink)]">
<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 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"
:class="[ :class="[
mono ? 'font-mono' : '', mono ? 'font-mono' : '',
i === active ? 'bg-[var(--hover)] text-[color:var(--fg)]' : 'text-[color:var(--fg-2)]', i === active ? 'bg-[var(--m-yellow)] text-[#111]' : 'text-[color:var(--fg-2)]',
]"> ]">
<span class="truncate">{{ o.label }}</span> <span class="truncate">{{ o.label }}</span>
<Icon v-if="o.value === modelValue" name="check" class="w-4 h-4 shrink-0 text-violet-400" /> <Icon v-if="o.value === modelValue" name="check" class="w-4 h-4 shrink-0 text-[var(--m-orange)]" />
</button> </button>
<div v-if="!options.length" class="px-3 py-2 text-xs text-[color:var(--fg-3)]">无选项</div> <div v-if="!options.length" class="px-3 py-2 text-xs text-[color:var(--fg-3)]">无选项</div>
</div> </div>
+39 -27
View File
@@ -49,13 +49,13 @@ watch(() => route.path, () => {
<template> <template>
<div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30 overflow-hidden"> <div class="theme-x h-screen flex bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30 overflow-hidden">
<!-- ===== Sidebar ===== --> <!-- ===== Sidebar ===== -->
<aside class="w-60 shrink-0 border-r border-[color:var(--hairline)] bg-[var(--surface)] backdrop-blur-md flex flex-col"> <aside class="w-60 shrink-0 border-r-[3px] border-r-[color:var(--ink)] bg-[var(--surface)] backdrop-blur-md flex flex-col">
<router-link to="/" class="h-16 flex items-center gap-2.5 px-5 border-b border-[color:var(--hairline)] group"> <router-link to="/" class="h-16 flex items-center gap-2.5 px-5 border-b-[3px] border-b-[color:var(--ink)] group">
<img v-if="site.logo" :src="site.logo" :alt="site.title" class="w-8 h-8 rounded-[10px] object-contain shadow-lg shadow-violet-500/20 ring-1 ring-white/10" /> <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)]" />
<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-semibold 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>
<div class="text-[11px] text-[color:var(--fg-3)] truncate">Admin</div> <div class="text-[11px] font-bold uppercase tracking-widest text-[color:var(--m-orange)] truncate">Admin</div>
</div> </div>
</router-link> </router-link>
@@ -64,16 +64,16 @@ watch(() => route.path, () => {
<router-link v-if="!t.children" :to="t.to" <router-link v-if="!t.children" :to="t.to"
class="admin-link group" class="admin-link group"
active-class="active"> active-class="active">
<span class="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-r-full opacity-0 transition-opacity" <span class="absolute left-1 top-1/2 -translate-y-1/2 w-[6px] h-6 rounded-sm opacity-0 transition-opacity"
style="background: linear-gradient(180deg, #f0abfc, #a78bfa)"></span> style="background: var(--m-orange)"></span>
<Icon :name="t.icon" class="w-4 h-4 shrink-0 opacity-70 group-hover:opacity-100 transition-opacity" /> <Icon :name="t.icon" class="w-4 h-4 shrink-0 opacity-70 group-hover:opacity-100 transition-opacity" />
<span class="text-sm">{{ t.label }}</span> <span class="text-sm">{{ t.label }}</span>
</router-link> </router-link>
<div v-else> <div v-else>
<button type="button" @click="toggleGroup(t.label)" <button type="button" @click="toggleGroup(t.label)"
class="admin-link group w-full text-left" :class="groupActive(t) && 'active'"> class="admin-link group w-full text-left" :class="groupActive(t) && 'active'">
<span class="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-r-full opacity-0 transition-opacity" <span class="absolute left-1 top-1/2 -translate-y-1/2 w-[6px] h-6 rounded-sm opacity-0 transition-opacity"
style="background: linear-gradient(180deg, #f0abfc, #a78bfa)"></span> style="background: var(--m-orange)"></span>
<Icon :name="t.icon" class="w-4 h-4 shrink-0 opacity-70 group-hover:opacity-100 transition-opacity" /> <Icon :name="t.icon" class="w-4 h-4 shrink-0 opacity-70 group-hover:opacity-100 transition-opacity" />
<span class="text-sm">{{ t.label }}</span> <span class="text-sm">{{ t.label }}</span>
<svg class="w-3 h-3 ml-auto transition-transform" :class="openGroups.has(t.label) && 'rotate-90'" <svg class="w-3 h-3 ml-auto transition-transform" :class="openGroups.has(t.label) && 'rotate-90'"
@@ -89,7 +89,7 @@ watch(() => route.path, () => {
</template> </template>
</nav> </nav>
<div class="p-3 border-t border-[color:var(--hairline)] space-y-1"> <div class="p-3 border-t-[3px] border-t-[color:var(--ink)] 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,19 @@ 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">
<!-- soft background mesh, mirrors the public shell --> <!-- memphis backdrop: polka dots + flat crayon washes -->
<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="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.12]" <div class="memphis-dots absolute inset-0 opacity-50"></div>
style="background: radial-gradient(circle, #a855f7, transparent 60%); filter: blur(110px)"></div> <div class="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.16]"
<div class="absolute top-1/2 -right-40 w-[36rem] h-[36rem] rounded-full opacity-[0.10]" style="background: radial-gradient(circle, #fef08a, transparent 60%); filter: blur(100px)"></div>
style="background: radial-gradient(circle, #06b6d4, 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 bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.08]" style="background: radial-gradient(circle, #bae6fd, transparent 60%); filter: blur(100px)"></div>
style="background: radial-gradient(circle, #f43f5e, transparent 60%); filter: blur(110px)"></div> <div class="absolute bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.12]"
style="background: radial-gradient(circle, #ffc1cc, transparent 60%); filter: blur(100px)"></div>
</div> </div>
<header class="relative z-10 h-14 shrink-0 border-b border-[color:var(--hairline)] bg-[var(--app-bg)]/70 backdrop-blur-md flex items-center px-8"> <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">
<div class="text-[10px] uppercase tracking-[0.25em] text-[color:var(--fg-3)] font-medium 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>
</header> </header>
@@ -143,12 +144,20 @@ 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;
color: var(--fg-2); color: var(--fg-2);
font-weight: 500; font-weight: 600;
transition: background 0.15s ease, color 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: var(--hover); color: var(--fg); } .admin-link:hover { background: rgb(254 240 138 / 0.5); color: var(--fg); border-color: var(--ink); }
.admin-link.active { color: var(--fg); background: var(--hover); } .admin-link.active {
color: var(--fg);
background: var(--m-yellow);
border-color: var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
}
html.dark .admin-link:hover { background: rgb(236 91 19 / 0.18); }
html.dark .admin-link.active { color: #fff; background: var(--m-orange); }
.admin-link.active > span:first-child { opacity: 1; } .admin-link.active > span:first-child { opacity: 1; }
.admin-sublink { .admin-sublink {
@@ -156,12 +165,15 @@ watch(() => route.path, () => {
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;
color: var(--fg-2); color: var(--fg-2);
font-weight: 500; font-weight: 600;
transition: background 0.15s ease, color 0.15s ease; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
} }
.admin-sublink:hover { background: var(--hover); color: var(--fg); } .admin-sublink:hover { background: rgb(254 240 138 / 0.5); color: var(--fg); border-color: var(--ink); }
.admin-sublink.active { color: var(--fg); background: var(--hover); } .admin-sublink.active { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); }
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); }
+62 -57
View File
@@ -69,7 +69,7 @@ async function onLogout() {
<template> <template>
<div class="theme-x min-h-screen bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30"> <div class="theme-x min-h-screen bg-[var(--app-bg)] text-[color:var(--fg-2)] selection:bg-violet-400/30">
<!-- ===== Top bar ===== --> <!-- ===== Top bar ===== -->
<header class="fixed top-0 inset-x-0 z-40 border-b border-transparent backdrop-blur-xl top-bar"> <header class="fixed top-0 inset-x-0 z-40 backdrop-blur-xl top-bar">
<div class="mx-auto max-w-[1400px] px-4 md:px-8 h-[58px] flex items-center gap-3"> <div class="mx-auto max-w-[1400px] px-4 md:px-8 h-[58px] flex items-center gap-3">
<!-- brand --> <!-- brand -->
<router-link to="/" class="flex items-center gap-2.5 shrink-0 transition-transform hover:scale-[1.03]"> <router-link to="/" class="flex items-center gap-2.5 shrink-0 transition-transform hover:scale-[1.03]">
@@ -142,7 +142,7 @@ async function onLogout() {
</div> </div>
<!-- mobile panel --> <!-- mobile panel -->
<div v-if="mobileOpen" class="md:hidden border-t border-[color:var(--hairline)] px-3 py-3 bg-[var(--menu-bg)]"> <div v-if="mobileOpen" class="md:hidden border-t-2 border-[color:var(--ink)] 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,14 +164,15 @@ async function onLogout() {
<!-- ===== Main column ===== --> <!-- ===== Main column ===== -->
<div class="relative"> <div class="relative">
<!-- soft background mesh --> <!-- memphis backdrop: polka dots + flat crayon washes -->
<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="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.16]" <div class="memphis-dots absolute inset-0 opacity-60"></div>
style="background: radial-gradient(circle, #a855f7, transparent 60%); filter: blur(100px)"></div> <div class="absolute -top-32 left-1/3 w-[40rem] h-[40rem] rounded-full opacity-[0.22]"
<div class="absolute top-1/2 -right-40 w-[36rem] h-[36rem] rounded-full opacity-[0.14]" style="background: radial-gradient(circle, #fef08a, transparent 60%); filter: blur(90px)"></div>
style="background: radial-gradient(circle, #06b6d4, 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 bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.10]" style="background: radial-gradient(circle, #bae6fd, transparent 60%); filter: blur(90px)"></div>
style="background: radial-gradient(circle, #f43f5e, transparent 60%); filter: blur(100px)"></div> <div class="absolute bottom-0 left-0 w-[32rem] h-[32rem] rounded-full opacity-[0.16]"
style="background: radial-gradient(circle, #ffc1cc, transparent 60%); filter: blur(90px)"></div>
<div class="anime-stars"></div> <div class="anime-stars"></div>
</div> </div>
@@ -195,42 +196,39 @@ async function onLogout() {
<style scoped> <style scoped>
.top-bar { .top-bar {
background: color-mix(in srgb, var(--app-bg) 72%, transparent); background: color-mix(in srgb, var(--app-bg) 82%, transparent);
border-bottom: 2.5px solid var(--ink);
transition: border-color 0.2s ease; transition: border-color 0.2s ease;
} }
.top-bar:hover { border-bottom-color: var(--hairline); }
.top-link { .top-link {
padding: 0.45rem 0.8rem; padding: 0.45rem 0.8rem;
border-radius: 999px; border-radius: 0.6rem;
font-size: 13.5px; font-size: 13.5px;
font-weight: 500; font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: transparent; background: transparent;
border: none; border: 2px solid transparent;
cursor: pointer; cursor: pointer;
transition: color 0.15s ease, background 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(--hover); } .top-link:hover { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); }
.top-link.on { .top-link.on {
color: #a21caf; color: #111;
font-weight: 700; font-weight: 800;
background: linear-gradient(135deg, rgb(244 114 182 / 0.16), rgb(129 140 248 / 0.16)); background: var(--m-yellow);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.3); border-color: var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
} }
html.dark .top-link.on { html.dark .top-link.on {
color: #f5d0fe; color: #fff;
background: linear-gradient(135deg, rgb(244 114 182 / 0.2), rgb(129 140 248 / 0.2)); background: var(--m-orange);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.35), 0 0 16px -6px rgb(217 70 239 / 0.55);
} }
.brand-spark { .brand-spark {
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
background: linear-gradient(135deg, #f472b6, #38bdf8); color: var(--m-orange);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: brandSpark 3.5s ease-in-out infinite; animation: brandSpark 3.5s ease-in-out infinite;
} }
@keyframes brandSpark { @keyframes brandSpark {
@@ -242,34 +240,38 @@ html.dark .top-link.on {
.m-link { .m-link {
display: block; display: block;
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;
border-radius: 0.85rem; border-radius: 0.6rem;
font-size: 14px; font-size: 14px;
font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: transparent; background: transparent;
border: none; border: 2px solid transparent;
cursor: pointer; cursor: pointer;
transition: color 0.15s ease, background 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(--hover); } .m-link:hover { color: var(--fg); background: var(--m-yellow); border-color: var(--ink); }
.m-link.m-on { .m-link.m-on {
color: #a21caf; color: #111;
font-weight: 700; font-weight: 800;
background: linear-gradient(135deg, rgb(244 114 182 / 0.16), rgb(129 140 248 / 0.16)); background: var(--m-yellow);
border-color: var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
} }
html.dark .m-link.m-on { color: #f5d0fe; background: linear-gradient(135deg, rgb(244 114 182 / 0.2), rgb(129 140 248 / 0.2)); } html.dark .m-link.m-on { color: #fff; background: var(--m-orange); }
.round-btn { .round-btn {
width: 2.125rem; width: 2.125rem;
height: 2.125rem; height: 2.125rem;
border-radius: 999px; border-radius: 0.6rem;
display: grid; display: grid;
place-items: center; place-items: center;
color: var(--fg-3); color: var(--fg-3);
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); border: 2px solid var(--ink);
transition: color 0.15s ease, background 0.15s ease; transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
} }
.round-btn:hover { color: var(--fg); background: var(--hover); } .round-btn:hover { color: #111; background: var(--m-yellow); box-shadow: 3px 3px 0 var(--shadow-ink); transform: translate(-1px, -1px); }
.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
the breakpoint lives here instead */ the breakpoint lives here instead */
@@ -280,17 +282,18 @@ html.dark .m-link.m-on { color: #f5d0fe; background: linear-gradient(135deg, rgb
align-items: center; align-items: center;
gap: 0.4rem; gap: 0.4rem;
padding: 0.5rem 1.1rem; padding: 0.5rem 1.1rem;
border-radius: 999px; border-radius: 0.65rem;
font-size: 13.5px; font-size: 13.5px;
font-weight: 700; font-weight: 800;
color: #fff; color: #fff;
background: linear-gradient(135deg, #f472b6, #a855f7 55%, #38bdf8); background: var(--m-orange);
border: none; border: 2.5px solid var(--ink);
cursor: pointer; cursor: pointer;
box-shadow: 0 12px 26px -14px rgb(168 85 247 / 0.75); box-shadow: 3px 3px 0 var(--shadow-ink);
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: translateY(-1px); box-shadow: 0 16px 30px -14px rgb(168 85 247 / 0.85); } .solid-btn:hover { filter: brightness(1.06); transform: translate(-1px, -1px); box-shadow: 5px 5px 0 var(--shadow-ink); }
.solid-btn:active { transform: scale(0.97) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
.name-btn { .name-btn {
display: inline-flex; display: inline-flex;
@@ -299,19 +302,19 @@ html.dark .m-link.m-on { color: #f5d0fe; background: linear-gradient(135deg, rgb
max-width: 10rem; max-width: 10rem;
height: 2.125rem; height: 2.125rem;
padding: 0 0.7rem 0 0.85rem; padding: 0 0.7rem 0 0.85rem;
border-radius: 999px; border-radius: 0.6rem;
border: none; border: 2px solid var(--ink);
cursor: pointer; cursor: pointer;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 700;
color: var(--fg); color: var(--fg);
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
transition: background 0.15s ease, box-shadow 0.15s ease;
} }
.name-btn:hover { .name-btn:hover {
background: linear-gradient(135deg, rgb(244 114 182 / 0.14), rgb(129 140 248 / 0.14)); background: var(--m-yellow);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.3); box-shadow: 3px 3px 0 var(--shadow-ink);
transform: translate(-1px, -1px);
} }
.name-txt { .name-txt {
overflow: hidden; overflow: hidden;
@@ -325,23 +328,25 @@ html.dark .m-link.m-on { color: #f5d0fe; background: linear-gradient(135deg, rgb
top: 2.75rem; top: 2.75rem;
width: 11.5rem; width: 11.5rem;
padding: 0.4rem; padding: 0.4rem;
border-radius: 0.9rem; border-radius: 0.75rem;
background: var(--menu-bg); background: var(--menu-bg);
box-shadow: inset 0 0 0 1px var(--hairline), 0 20px 44px rgb(0 0 0 / 0.24); border: 2.5px solid var(--ink);
box-shadow: 6px 6px 0 var(--shadow-ink);
z-index: 50; z-index: 50;
} }
.menu-item { .menu-item {
display: block; display: block;
padding: 0.5rem 0.65rem; padding: 0.5rem 0.65rem;
border-radius: 0.55rem; border-radius: 0.5rem;
font-size: 13px; font-size: 13px;
font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
background: transparent; background: transparent;
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
.menu-item:hover { background: var(--hover); color: var(--fg); } .menu-item:hover { background: var(--m-yellow); color: #111; }
.menu-sep { height: 1px; background: var(--hairline); margin: 0.35rem 0.25rem; } .menu-sep { height: 2px; background: var(--ink); opacity: 0.15; 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); }
+220 -121
View File
@@ -9,59 +9,72 @@ 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(15 23 42 / 0.08); --tw-ring-color: rgb(17 17 17 / 0.12);
/* ===== Theme palette — LIGHT (default) ===== /* ===== Theme palette — LIGHT (default): Memphis / neo-brutalism =====
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: rgb(250 250 251); /* page background */ --app-bg: #f8f6f2; /* warm paper */
--fg: rgb(15 23 42); /* primary heading/text */ --fg: #1a1a1a; /* primary heading/text */
--fg-2: rgb(15 23 42 / 0.62); /* body / secondary */ --fg-2: rgb(26 26 26 / 0.64); /* body / secondary */
--fg-3: rgb(15 23 42 / 0.45); /* muted */ --fg-3: rgb(26 26 26 / 0.48); /* muted */
--fg-faint: rgb(15 23 42 / 0.3); /* labels / hints */ --fg-faint: rgb(26 26 26 / 0.34); /* labels / hints */
--surface: rgb(255 255 255 / 0.7); /* raised card on page bg */ --surface: #ffffff; /* raised card on page bg */
--surface-2: rgb(15 23 42 / 0.035); /* subtle inset panel (counter cells) */ --surface-2: rgb(26 26 26 / 0.045); /* subtle inset panel (counter cells) */
--hairline: rgb(15 23 42 / 0.08); /* borders / rings */ --hairline: rgb(26 26 26 / 0.12); /* faint borders / separators */
--hover: rgb(15 23 42 / 0.05); /* hover wash */ --hover: rgb(254 240 138 / 0.55); /* hover wash — memphis yellow tint */
/* ink = the heavy outline color; shadow-ink = the offset "chunky" shadow */
--ink: #111111;
--ink-soft: rgb(17 17 17 / 0.45);
--shadow-ink: #111111;
/* memphis accent crayons */
--m-orange: #ec5b13;
--m-yellow: #fef08a;
--m-pink: #ffc1cc;
--m-blue: #bae6fd;
--m-mint: #a7f3d0;
/* 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: rgb(15 23 42); --btn-solid-bg: #1a1a1a;
--btn-solid-bg-h: rgb(30 41 59); --btn-solid-bg-h: #000000;
--btn-solid-fg: #fff; --btn-solid-fg: #fff;
--menu-bg: #ffffff; /* dropdown / popover panel */ --menu-bg: #ffffff; /* dropdown / popover panel */
} }
html.dark { html.dark {
--app-bg: #08090d; --app-bg: #131318;
--fg: rgb(255 255 255 / 0.92); --fg: rgb(255 255 255 / 0.92);
--fg-2: rgb(255 255 255 / 0.55); --fg-2: rgb(255 255 255 / 0.55);
--fg-3: rgb(255 255 255 / 0.4); --fg-3: rgb(255 255 255 / 0.4);
--fg-faint: rgb(255 255 255 / 0.3); --fg-faint: rgb(255 255 255 / 0.3);
--surface: rgb(255 255 255 / 0.03); --surface: rgb(255 255 255 / 0.05);
--surface-2: rgb(255 255 255 / 0.04); --surface-2: rgb(255 255 255 / 0.05);
--hairline: rgb(255 255 255 / 0.06); --hairline: rgb(255 255 255 / 0.1);
--hover: rgb(255 255 255 / 0.06); --hover: rgb(236 91 19 / 0.2);
--btn-solid-bg: #fff; --ink: rgb(244 244 245 / 0.85);
--btn-solid-bg-h: rgb(255 255 255 / 0.9); --ink-soft: rgb(244 244 245 / 0.4);
--btn-solid-fg: rgb(2 6 23); --shadow-ink: rgb(244 244 245 / 0.22);
--menu-bg: #15171f; --btn-solid-bg: #ec5b13;
--btn-solid-bg-h: #f97316;
--btn-solid-fg: #fff;
--menu-bg: #1d1e26;
} }
/* 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 — soft tinted panel that flips with the theme. */ /* Footer CTA band — flat memphis-yellow panel with a heavy ink outline. */
.cta-band { .cta-band {
background: background:
radial-gradient(at 30% 20%, rgb(168 85 247 / 0.16) 0%, transparent 50%), radial-gradient(circle, rgb(17 17 17 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px,
radial-gradient(at 80% 70%, rgb(244 114 182 / 0.14) 0%, transparent 55%), var(--m-yellow);
linear-gradient(180deg, rgb(255 255 255), rgb(246 244 251)); border: 3px solid var(--ink);
box-shadow: 10px 10px 0 var(--shadow-ink);
} }
html.dark .cta-band { html.dark .cta-band {
background: background:
radial-gradient(at 30% 20%, rgb(168 85 247 / 0.35) 0%, transparent 50%), radial-gradient(circle, rgb(255 255 255 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px,
radial-gradient(at 80% 70%, rgb(244 114 182 / 0.3) 0%, transparent 55%), rgb(236 91 19 / 0.16);
linear-gradient(180deg, #0f1117, #080a10);
} }
/* ===== Light-mode rescue for hardcoded-dark pages ===== /* ===== Light-mode rescue for hardcoded-dark pages =====
@@ -107,7 +120,7 @@ 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: rgb(15 23 42); color: #fff; box-shadow: none; } 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); }
/* Tag chips (TagInput) — near-white violet text is unreadable on white; keep /* Tag chips (TagInput) — near-white violet text is unreadable on white; keep
the violet tint but switch the label to dark ink. */ the violet 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(15 23 42 / 0.9); }
@@ -117,7 +130,7 @@ html:not(.dark) .theme-text .chip .chip-x:hover { color: #fff; background: rgb(2
`.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: rgb(15 23 42); color: #fff; box-shadow: none; } html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); }
/* 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
@@ -151,10 +164,12 @@ html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color
vertical-align: middle; vertical-align: middle;
transition: background 0.15s, border-color 0.15s; transition: background 0.15s, border-color 0.15s;
} }
.chk:hover:not(:checked) { border-color: rgb(217 70 239 / 0.7); } .chk { border-color: var(--ink-soft); }
.chk:hover:not(:checked) { border-color: var(--m-orange); }
.chk:checked { .chk:checked {
background: rgb(217 70 239); background: var(--m-orange);
border-color: rgb(217 70 239); border-color: var(--ink);
box-shadow: 2px 2px 0 var(--shadow-ink);
} }
.chk:checked::after { .chk:checked::after {
content: ""; content: "";
@@ -165,7 +180,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(217 70 239 / 0.45); outline-offset: 1px; } .chk:focus-visible { outline: 2px solid rgb(236 91 19 / 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. */
@@ -270,7 +285,7 @@ body {
/* Subtle ambient gradient + grain (used by PublicLayout via .ambient-bg) */ /* Subtle ambient gradient + grain (used by PublicLayout via .ambient-bg) */
} }
::selection { background: rgb(99 102 241 / 0.22); color: rgb(15 23 42); } ::selection { background: var(--m-yellow); color: #111; }
/* 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 (复制 / 删除 / 生成 等).
@@ -285,31 +300,37 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
own utility so vertical clipping / rounded corners are preserved. */ own utility so vertical clipping / rounded corners are preserved. */
.card:has(> table) { overflow-x: auto; } .card:has(> table) { overflow-x: auto; }
::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: rgb(203 213 225); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: rgb(26 26 26 / 0.28); border-radius: 5px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: rgb(148 163 184); } ::-webkit-scrollbar-thumb:hover { background: rgb(26 26 26 / 0.45); 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:hover { background: rgb(255 255 255 / 0.35); background-clip: content-box; }
/* ===== Ambient background — used by the public shell ===== */ /* ===== Ambient background — used by the public shell ===== */
.ambient-bg { .ambient-bg {
background: background-color: var(--app-bg);
radial-gradient(at 15% 10%, rgb(238 242 255) 0%, transparent 45%), background-image: radial-gradient(circle, rgb(17 17 17 / 0.07) 1.3px, transparent 1.3px);
radial-gradient(at 85% 25%, rgb(253 244 255) 0%, transparent 50%), background-size: 22px 22px;
radial-gradient(at 50% 90%, rgb(236 254 255) 0%, transparent 50%), }
rgb(250 250 251); html.dark .ambient-bg {
background-image: radial-gradient(circle, rgb(255 255 255 / 0.06) 1.3px, transparent 1.3px);
} }
/* ===== 二次元 (anime) design kit ===== /* ===== Memphis / neo-brutalist design kit =====
Soft pink/violet/sky gradients, rounded bubbly surfaces, sparkle overlays and Heavy ink outlines, hard offset shadows, flat crayon accents (orange / yellow
sticker labels. Reusable across the workbench + user subpages + public pages / pink / blue / mint), rotated stickers and squishy press feedback. Reusable
so the whole front-end shares one cute, neon-tinged look. */ across the workbench + user subpages + public pages so the whole front-end
shares one punchy, print-like look. */
/* Gradient text used for eyebrows / accents. */ /* Highlighter text — black words "marked" with a yellow swipe. */
.anime-grad-text { .anime-grad-text {
background: linear-gradient(100deg, #f472b6, #a855f7 46%, #38bdf8); background: linear-gradient(transparent 55%, var(--m-yellow) 55%);
-webkit-background-clip: text; padding: 0 0.1em;
background-clip: text; color: inherit;
color: transparent; }
html.dark .anime-grad-text {
background: linear-gradient(transparent 55%, rgb(236 91 19 / 0.55) 55%);
} }
/* Section eyebrow — tiny uppercase kicker with a leading star. */ /* Section eyebrow — tiny uppercase kicker with a leading star. */
@@ -318,71 +339,74 @@ button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: poi
align-items: center; align-items: center;
gap: 0.4rem; gap: 0.4rem;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 800;
letter-spacing: 0.22em; letter-spacing: 0.22em;
text-transform: uppercase; text-transform: uppercase;
} }
.anime-eyebrow::before { content: '✦'; font-size: 0.85em; } .anime-eyebrow::before { content: '✦'; font-size: 0.85em; }
/* Sticker / kawaii pill label. */ /* Sticker label — rotated yellow chip with a hard shadow. */
.sticker { .sticker {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.3rem; gap: 0.3rem;
padding: 0.28rem 0.7rem; padding: 0.3rem 0.75rem;
border-radius: 999px; border-radius: 0.55rem;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 800;
color: #a21caf; color: #111;
background: linear-gradient(135deg, rgb(244 114 182 / 0.16), rgb(129 140 248 / 0.16)); background: var(--m-yellow);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.28); border: 2.5px solid var(--ink);
} box-shadow: 4px 4px 0 var(--shadow-ink);
html.dark .sticker { transform: rotate(-1.5deg);
color: #f5d0fe;
background: linear-gradient(135deg, rgb(244 114 182 / 0.2), rgb(129 140 248 / 0.2));
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.35), 0 0 18px -6px rgb(217 70 239 / 0.5);
} }
/* Bubbly gradient CTA — pill button with a squishy hover. */ /* Chunky CTA — flat orange with an offset ink shadow and a press feel. */
.bubble-btn { .bubble-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.7rem 1.4rem; padding: 0.7rem 1.4rem;
border-radius: 999px; border-radius: 0.8rem;
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 800;
color: #fff; color: #fff;
border: none; border: 2.5px solid var(--ink);
cursor: pointer; cursor: pointer;
background: linear-gradient(135deg, #f472b6, #a855f7 55%, #38bdf8); background: var(--m-orange);
box-shadow: 0 14px 30px -12px rgb(168 85 247 / 0.7); box-shadow: 4px 4px 0 var(--shadow-ink);
transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease; transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
} }
.bubble-btn:hover { transform: translateY(-2px) scale(1.02); filter: brightness(1.06); } .bubble-btn:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--shadow-ink); filter: brightness(1.05); }
.bubble-btn:active { transform: translateY(0) scale(0.99); } .bubble-btn:active { transform: scale(0.97) translateY(2px); box-shadow: 1px 1px 0 var(--shadow-ink); }
/* Rounded anime card surface with a soft pastel wash + neon ring in dark. */ /* Memphis card — flat surface, heavy ink outline, hard offset shadow. */
.anime-card { .anime-card {
position: relative; position: relative;
border-radius: 1.5rem; border-radius: 1rem;
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); border: 2.5px solid var(--ink);
transition: transform 0.2s ease, box-shadow 0.2s ease; box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.18s ease, box-shadow 0.18s ease;
} }
.anime-card:hover { .anime-card:hover {
transform: translateY(-3px); transform: translate(-2px, -2px);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.4), 0 22px 44px -26px rgb(168 85 247 / 0.5); box-shadow: 8px 8px 0 var(--shadow-ink);
} }
html.dark .anime-card:hover { html.dark .anime-card {
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.5), 0 0 30px -10px rgb(217 70 239 / 0.45); background: rgb(255 255 255 / 0.05);
} }
/* Selected pill — replaces the old bg-slate-900 look with a candy gradient. */ /* Selected pill — solid ink with an orange offset shadow. */
.chip-on { .chip-on {
color: #fff !important; color: #fff !important;
background: linear-gradient(135deg, #e879f9, #8b5cf6) !important; background: #1a1a1a !important;
box-shadow: 0 8px 20px -10px rgb(168 85 247 / 0.7) !important; border: 2px solid var(--ink) !important;
box-shadow: 3px 3px 0 var(--m-orange) !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.
@@ -392,9 +416,9 @@ html.dark .anime-card:hover {
inset: 0; inset: 0;
pointer-events: none; pointer-events: none;
background-image: background-image:
radial-gradient(1.5px 1.5px at 12% 22%, rgb(217 70 239 / 0.7), transparent), radial-gradient(1.5px 1.5px at 12% 22%, rgb(236 91 19 / 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(56 189 248 / 0.7), transparent),
radial-gradient(1px 1px at 42% 62%, rgb(168 85 247 / 0.65), transparent), radial-gradient(1px 1px at 42% 62%, rgb(236 91 19 / 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(244 114 182 / 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(56 189 248 / 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(250 204 21 / 0.55), transparent);
@@ -422,46 +446,83 @@ html.dark .anime-card:hover {
z-index: 0; z-index: 0;
} }
/* ===== Memphis utilities ===== */
/* Polka-dot texture layer (drop on a decorative absolutely-positioned div). */
.memphis-dots {
background-image: radial-gradient(circle, rgb(17 17 17 / 0.14) 1.4px, transparent 1.4px);
background-size: 22px 22px;
}
html.dark .memphis-dots {
background-image: radial-gradient(circle, rgb(255 255 255 / 0.1) 1.4px, transparent 1.4px);
}
/* Heavy outline + hard offset shadow helpers for one-off markup. */
.ink-card {
background: var(--surface);
border: 2.5px solid var(--ink);
box-shadow: 6px 6px 0 var(--shadow-ink);
}
.ink-card-sm {
background: var(--surface);
border: 2px solid var(--ink);
box-shadow: 4px 4px 0 var(--shadow-ink);
}
/* Lift on hover — the signature memphis card interaction. */
.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); }
/* Squishy press for buttons (mirrors the reference site's .memphis-press). */
.memphis-press { transition: transform 0.15s ease; }
.memphis-press:active { transform: scale(0.97) translateY(2px); }
@keyframes memphis-wiggle { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
@keyframes memphis-float { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-10px) rotate(1.5deg); } }
@keyframes memphis-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
.animate-memphis-wiggle { animation: memphis-wiggle 2s ease-in-out infinite; }
.animate-memphis-float { animation: memphis-float 6s ease-in-out infinite; }
.animate-memphis-bounce { animation: memphis-bounce 2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.animate-memphis-wiggle, .animate-memphis-float, .animate-memphis-bounce { animation: none; }
}
/* ===== Dark public-shell theme overrides ===== /* ===== Dark public-shell theme overrides =====
Apply by adding `.public-dark` to a wrapper element. Views can keep their Apply by adding `.public-dark` to a wrapper element. Views can keep their
existing Tailwind classes (text-slate-*, .card, .field) — these rules existing Tailwind classes (text-slate-*, .card, .field) — these rules
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.03); background: rgb(255 255 255 / 0.035);
border-color: rgb(255 255 255 / 0.07); border-color: var(--ink);
color: rgb(255 255 255 / 0.92); color: rgb(255 255 255 / 0.92);
box-shadow: 0 1px 0 rgb(255 255 255 / 0.04) inset, 0 8px 24px rgb(0 0 0 / 0.25); box-shadow: 5px 5px 0 var(--shadow-ink);
} }
.public-dark .field { .public-dark .field {
background: rgb(255 255 255 / 0.04); background: rgb(255 255 255 / 0.04);
border-color: rgb(255 255 255 / 0.1); border-color: var(--ink-soft);
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 —
matches the per-component overrides in ConfigView/LoginModal so all an orange offset shadow, matching the light theme's focus affordance. */
inputs glow with the same violet ring on focus. */
.public-dark .field:focus, .public-dark .field:focus,
.public-dark .field:focus-visible { .public-dark .field:focus-visible {
border-color: rgb(167 139 250 / 0.65); border-color: var(--ink);
background: rgb(255 255 255 / 0.06); background: rgb(255 255 255 / 0.06);
box-shadow: 0 0 0 3px rgb(167 139 250 / 0.15); box-shadow: 3px 3px 0 rgb(236 91 19 / 0.6);
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.06);
border-color: var(--ink-soft);
color: rgb(255 255 255 / 0.85); color: rgb(255 255 255 / 0.85);
} }
.public-dark .btn-soft:hover { background: rgb(255 255 255 / 0.12); color: white; } .public-dark .btn-soft:hover { background: rgb(236 91 19 / 0.25); color: white; }
.public-dark .btn-ghost { color: rgb(255 255 255 / 0.65); } .public-dark .btn-ghost { color: rgb(255 255 255 / 0.65); border-color: transparent; }
.public-dark .btn-ghost:hover { background: rgb(255 255 255 / 0.06); color: white; } .public-dark .btn-ghost:hover { background: rgb(236 91 19 / 0.18); color: white; }
.public-dark .btn-primary { .public-dark .btn-primary {
background: white; color: rgb(2 6 23); box-shadow: 0 1px 0 rgb(0 0 0 / 0.1); background: var(--m-orange); color: #fff; box-shadow: 3px 3px 0 var(--shadow-ink);
} }
.public-dark .btn-primary:hover { background: rgb(255 255 255 / 0.92); } .public-dark .btn-primary:hover { background: #f97316; }
.public-dark .pill { background: rgb(255 255 255 / 0.06); color: rgb(255 255 255 / 0.85); } .public-dark .pill { background: rgb(255 255 255 / 0.06); color: rgb(255 255 255 / 0.85); border-color: var(--ink-soft); }
/* Remap slate text tokens */ /* Remap slate text tokens */
.public-dark .text-slate-900, .public-dark .text-slate-900,
@@ -511,34 +572,68 @@ html.dark .anime-card:hover {
.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 */ /* surfaces — flat white card with the memphis ink outline + offset shadow */
.card { @apply bg-white rounded-xl border border-slate-200 shadow-sm; } .card {
@apply bg-white rounded-xl;
border: 2.5px solid var(--ink);
box-shadow: 5px 5px 0 var(--shadow-ink);
}
/* 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) */
.btn-primary { .btn-primary {
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-medium @apply inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-bold
transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:cursor-not-allowed px-3.5 py-2;
bg-slate-900 text-white hover:bg-slate-700 px-3.5 py-2; color: #fff;
background: var(--m-orange);
border: 2.5px solid var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
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:active:not(:disabled) { transform: scale(0.97) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
.btn-soft { .btn-soft {
@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-semibold
transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
bg-slate-100 text-slate-700 hover:bg-slate-200 px-3 py-1.5; color: var(--fg-2);
background: var(--surface);
border: 2px solid var(--ink-soft);
transition: transform 0.13s ease, box-shadow 0.13s ease, background 0.15s ease, color 0.15s ease;
} }
.btn-soft:hover:not(:disabled) {
color: var(--fg);
background: var(--m-yellow);
border-color: var(--ink);
box-shadow: 3px 3px 0 var(--shadow-ink);
}
.btn-soft:active:not(:disabled) { transform: scale(0.97) translateY(2px); 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
transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
text-slate-600 hover:bg-slate-100 hover:text-slate-900 px-3 py-1.5; color: var(--fg-2);
border: 2px solid transparent;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.btn-ghost:hover:not(:disabled) {
color: var(--fg);
background: var(--m-yellow);
border-color: var(--ink);
} }
/* text link / quiet action */ /* text link / quiet action */
.link { @apply text-slate-500 hover:text-slate-900 transition-colors cursor-pointer; } .link { @apply hover:text-slate-900 transition-colors cursor-pointer; color: var(--fg-3); }
.link:hover { color: var(--m-orange); }
/* form controls */ /* form controls — heavy outline, orange offset shadow on focus */
.field { .field {
@apply w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-800 @apply w-full rounded-lg bg-white px-3 py-2 text-sm
outline-none transition-colors placeholder:text-slate-400 outline-none placeholder:text-slate-400;
focus:border-slate-400 focus:ring-2; color: var(--fg);
border: 2px solid var(--ink-soft);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.field:focus, .field:focus-visible {
border-color: var(--ink);
box-shadow: 3px 3px 0 var(--m-orange);
} }
/* 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
@@ -565,13 +660,17 @@ html.dark .anime-card:hover {
} }
/* status pill */ /* status pill */
.pill { @apply inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium; } .pill {
@apply inline-flex items-center rounded-md px-2 py-0.5 text-xs font-semibold;
border: 1.5px solid var(--ink-soft);
}
/* table primitives */ /* table primitives */
.th { @apply text-left px-4 py-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400; } .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 border-slate-100 transition-colors hover:bg-slate-50; } .row { @apply border-t transition-colors; border-color: var(--hairline); }
.row:hover { background: rgb(254 240 138 / 0.35); }
/* 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-semibold shrink-0 ring-1 ring-black/5; } .avatar { @apply w-8 h-8 rounded-full grid place-items-center text-xs font-bold shrink-0; border: 2px solid var(--ink); }
} }
+24 -25
View File
@@ -658,29 +658,27 @@ html.dark .hint b { color: rgb(253 164 175); } /* rose — visible warning, no
} }
.sw:checked { background: #4f46e5; } /* indigo-600 — matches btn-primary */ .sw:checked { background: #4f46e5; } /* indigo-600 — matches btn-primary */
.sw:checked::after { transform: translateX(calc(2.25rem - 1.3rem)); } .sw:checked::after { transform: translateX(calc(2.25rem - 1.3rem)); }
.sw:focus-visible { box-shadow: 0 0 0 3px rgb(99 102 241 / 0.25); } .sw:focus-visible { box-shadow: 0 0 0 3px rgb(236 91 19 / 0.3); }
.sw:disabled { cursor: not-allowed; opacity: 0.55; } .sw:disabled { cursor: not-allowed; opacity: 0.55; }
/* All three input variants now share the same dark-glass surface as the rest /* All three input variants now share the same surface as the rest of the
of the admin shell. Background + border use white-alpha so they read admin shell. Background + border use ink-alpha so they read against the
against the card; placeholders are tuned for legibility, not noise. */ card; placeholders are tuned for legibility, not noise. */
.num, .txt, .field { .num, .txt, .field {
background: rgb(15 23 42 / 0.03); background: rgb(15 23 42 / 0.03);
border: 1px solid var(--hairline); border: 2px solid var(--ink-soft);
color: var(--fg); color: var(--fg);
border-radius: 0.55rem; border-radius: 0.55rem;
outline: none; outline: none;
transition: border-color 0.18s, background 0.18s, box-shadow 0.18s; transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
} }
html.dark .num, html.dark .txt, html.dark .field { html.dark .num, html.dark .txt, html.dark .field {
background: rgb(255 255 255 / 0.04); background: rgb(255 255 255 / 0.04);
border-color: rgb(255 255 255 / 0.1);
color: white; color: white;
} }
.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: rgb(167 139 250 / 0.65); border-color: var(--ink);
background: rgb(255 255 255 / 0.06); box-shadow: 3px 3px 0 var(--m-orange);
box-shadow: 0 0 0 3px rgb(167 139 250 / 0.15);
} }
.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; }
@@ -688,23 +686,24 @@ 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 violet to match brand. The global .btn-primary /* Section save buttons — solid memphis orange with a hard offset shadow.
under .public-dark goes to white; we override here so the save action stays The global .btn-primary under .public-dark already uses this palette; we
visually distinct as the primary action on a form. */ re-assert it 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: 600; color: white; font-size: 0.8rem; font-weight: 800; color: white;
background: linear-gradient(135deg, #a855f7 0%, #7c3aed 50%, #ec4899 100%); background: var(--m-orange);
box-shadow: 0 8px 20px -8px rgb(168 85 247 / 0.55); border: 2px solid var(--ink);
transition: filter 0.15s, transform 0.12s, box-shadow 0.18s, opacity 0.15s; box-shadow: 3px 3px 0 var(--shadow-ink);
transition: filter 0.15s, transform 0.12s, box-shadow 0.15s, opacity 0.15s;
} }
/* Re-assert the gradient on hover for ALL states: the global /* Re-assert the flat orange on hover for ALL states — the global
`.public-dark .btn-primary:hover` in style.css repaints the background white `.public-dark .btn-primary:hover` in style.css repaints the background,
with no `:not(:disabled)` guard, so even disabled save buttons (注册与登录 / so even disabled save buttons (注册与登录 / 邮件服务 default to disabled)
邮件服务 default to disabled) flashed white 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 { background: linear-gradient(135deg, #a855f7 0%, #7c3aed 50%, #ec4899 100%); } .btn-primary:hover { background: #f97316; }
.btn-primary:hover:not(:disabled) { filter: brightness(1.08); box-shadow: 0 10px 24px -8px rgb(168 85 247 / 0.7); } .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); } .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>
+83 -57
View File
@@ -359,7 +359,7 @@ function useExample(ex) {
</section> </section>
<!-- ============ CTA ============ --> <!-- ============ CTA ============ -->
<section class="mt-24 cta-band relative overflow-hidden rounded-[2rem] ring-1 ring-[color:var(--hairline)] p-10 md:p-16"> <section class="mt-24 cta-band relative overflow-hidden rounded-[1.4rem] p-10 md:p-16">
<div class="relative max-w-xl"> <div class="relative max-w-xl">
<h3 class="text-[clamp(1.7rem,3.4vw,2.8rem)] font-extrabold tracking-tight leading-[1.2] text-[color:var(--fg)]"> <h3 class="text-[clamp(1.7rem,3.4vw,2.8rem)] font-extrabold tracking-tight leading-[1.2] text-[color:var(--fg)]">
与其继续想,<br />不如现在写一句。 与其继续想,<br />不如现在写一句。
@@ -368,15 +368,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-full bg-[var(--btn-solid-bg)] text-[color:var(--btn-solid-fg)] hover:bg-[var(--btn-solid-bg-h)] pl-6 pr-3 py-3 text-sm font-semibold transition-all"> 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">
免费开始 免费开始
<span class="w-8 h-8 rounded-full bg-[var(--btn-solid-fg)] text-[color:var(--btn-solid-bg)] grid place-items-center group-hover:translate-x-1 transition-transform">→</span> <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>
</button> </button>
</div> </div>
</section> </section>
<!-- thin footer line --> <!-- thin footer line -->
<footer class="mt-16 pt-10 border-t border-[color:var(--hairline)] flex flex-wrap items-center justify-between gap-4 text-xs text-[color:var(--fg-3)]"> <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)]">
<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>
@@ -398,13 +398,15 @@ function useExample(ex) {
gap: 0.5rem; gap: 0.5rem;
max-width: 36rem; max-width: 36rem;
padding: 0.5rem; padding: 0.5rem;
border-radius: 1.25rem; border-radius: 1rem;
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline), 0 20px 50px -22px rgb(139 92 246 / 0.3); border: 3px solid var(--ink);
transition: box-shadow 0.25s ease; box-shadow: 7px 7px 0 var(--shadow-ink);
transition: box-shadow 0.2s ease, transform 0.2s ease;
} }
.composer:focus-within { .composer:focus-within {
box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.55), 0 20px 60px -20px rgb(139 92 246 / 0.45); transform: translate(-2px, -2px);
box-shadow: 10px 10px 0 var(--m-orange);
} }
.composer-input { .composer-input {
flex: 1; flex: 1;
@@ -414,45 +416,54 @@ function useExample(ex) {
outline: none; outline: none;
color: var(--fg); color: var(--fg);
font-size: 15px; font-size: 15px;
font-weight: 500;
padding: 0.7rem 0.85rem; padding: 0.7rem 0.85rem;
} }
.composer-input::placeholder { color: var(--fg-faint); } .composer-input::placeholder { color: var(--fg-faint); }
.composer-go { .composer-go {
flex: none; flex: none;
border: none; border: 2.5px solid var(--ink);
cursor: pointer; cursor: pointer;
color: #fff; color: #fff;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 800;
padding: 0.7rem 1.25rem; padding: 0.65rem 1.2rem;
border-radius: 0.9rem; border-radius: 0.7rem;
background: linear-gradient(135deg, #e879f9, #8b5cf6); background: var(--m-orange);
transition: transform 0.15s ease, filter 0.15s ease; box-shadow: 3px 3px 0 var(--shadow-ink);
transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
} }
.composer-go:hover { transform: translateY(-1px); filter: brightness(1.1); } .composer-go:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); filter: brightness(1.06); }
.composer-go:active { transform: scale(0.96) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
.chip { .chip {
font-size: 12.5px; font-size: 12.5px;
font-weight: 600;
color: var(--fg-2); color: var(--fg-2);
cursor: pointer; cursor: pointer;
border: none; border: 2px solid var(--ink);
padding: 0.42rem 0.8rem; padding: 0.4rem 0.8rem;
border-radius: 999px; border-radius: 0.6rem;
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
transition: color 0.15s ease, box-shadow 0.15s ease;
} }
.chip:hover { color: var(--fg); box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.5); } .chip:hover {
color: #111;
background: var(--m-yellow);
box-shadow: 3px 3px 0 var(--shadow-ink);
transform: translate(-1px, -1px);
}
html.dark .chip:hover { color: #111; }
/* ---------- hero cards (kind=hero) ---------- */ /* ---------- hero cards (kind=hero) ---------- */
.fcard { .fcard {
position: absolute; position: absolute;
border: none; border: 3px solid var(--ink);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1.4rem; border-radius: 1rem;
overflow: hidden; overflow: hidden;
box-shadow: 0 30px 70px -24px rgb(0 0 0 / 0.55), inset 0 0 0 1px rgb(255 255 255 / 0.14); box-shadow: 7px 7px 0 var(--shadow-ink);
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;
} }
@@ -497,23 +508,25 @@ function useExample(ex) {
overflow: hidden; overflow: hidden;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
border: none; border: 2.5px solid var(--ink);
padding: 1.35rem; padding: 1.35rem;
border-radius: 1.25rem; border-radius: 1rem;
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.18s ease, box-shadow 0.18s ease;
} }
.cap-card:hover { .cap-card:hover {
transform: translateY(-4px); transform: translate(-3px, -3px);
box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.45), 0 20px 40px -24px rgb(139 92 246 / 0.45); box-shadow: 9px 9px 0 var(--m-orange);
} }
html.dark .cap-card { background: rgb(255 255 255 / 0.05); }
.cap-arrow { position: absolute; top: 1.1rem; right: 1.1rem; color: var(--fg-3); transition: color 0.15s ease; } .cap-arrow { position: absolute; top: 1.1rem; right: 1.1rem; color: var(--fg-3); transition: color 0.15s ease; }
.cap-card:hover .cap-arrow { color: var(--fg); } .cap-card:hover .cap-arrow { color: var(--m-orange); }
.cap-icon { .cap-icon {
width: 2.6rem; width: 2.6rem;
height: 2.6rem; height: 2.6rem;
border-radius: 0.85rem; border-radius: 0.7rem;
border: 2px solid var(--ink);
display: grid; display: grid;
place-items: center; place-items: center;
margin-bottom: 0.9rem; margin-bottom: 0.9rem;
@@ -522,9 +535,11 @@ function useExample(ex) {
.cap-meta { .cap-meta {
font-style: normal; font-style: normal;
font-size: 10px; font-size: 10px;
font-weight: 700;
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);
background: var(--surface-2); background: var(--surface-2);
} }
@@ -532,14 +547,14 @@ function useExample(ex) {
.tile { .tile {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: none; border: 2.5px solid var(--ink);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1.25rem; border-radius: 1rem;
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.35s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.tile:hover { transform: translateY(-3px); } .tile:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-blue); }
.tile-veil { .tile-veil {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -566,12 +581,14 @@ function useExample(ex) {
display: block; display: block;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
border: none; border: 2.5px solid var(--ink);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1.1rem; border-radius: 1rem;
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: 5px 5px 0 var(--shadow-ink);
transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.work:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-pink); }
.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 {
@@ -606,10 +623,11 @@ function useExample(ex) {
margin-top: 0.7rem; margin-top: 0.7rem;
font-style: normal; font-style: normal;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 800;
color: rgb(11 13 19); color: rgb(11 13 19);
background: #fff; background: var(--m-yellow);
border-radius: 999px; border: 2px solid #111;
border-radius: 0.5rem;
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
} }
@@ -622,12 +640,14 @@ function useExample(ex) {
width: 14.5rem; width: 14.5rem;
overflow: hidden; overflow: hidden;
padding: 1.25rem; padding: 1.25rem;
border-radius: 1.25rem; border-radius: 1rem;
border: 2.5px solid var(--ink);
background: var(--surface); background: var(--surface);
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: 5px 5px 0 var(--shadow-ink);
transition: box-shadow 0.2s ease; transition: box-shadow 0.18s ease, transform 0.18s ease;
} }
.model-card:hover { box-shadow: inset 0 0 0 1px rgb(139 92 246 / 0.42); } .model-card:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--m-mint); }
html.dark .model-card { background: rgb(255 255 255 / 0.05); }
.model-glow { .model-glow {
position: absolute; position: absolute;
top: -2.5rem; top: -2.5rem;
@@ -638,9 +658,15 @@ function useExample(ex) {
filter: blur(40px); filter: blur(40px);
opacity: 0.3; opacity: 0.3;
} }
.model-tag { font-size: 10.5px; padding: 0.18rem 0.55rem; border-radius: 0.4rem; } .model-tag {
.model-tag.is-image { background: rgb(52 211 153 / 0.14); color: rgb(52 211 153); } font-size: 10.5px;
.model-tag.is-video { background: rgb(125 211 252 / 0.14); color: rgb(56 189 248); } font-weight: 800;
padding: 0.18rem 0.55rem;
border-radius: 0.4rem;
border: 1.5px solid var(--ink);
}
.model-tag.is-image { background: var(--m-mint); color: #065f46; }
.model-tag.is-video { background: var(--m-blue); color: #075985; }
/* ---------- hero band + mascot aura ---------- */ /* ---------- hero band + mascot aura ---------- */
.hero-band { .hero-band {
@@ -654,7 +680,7 @@ function useExample(ex) {
height: 260px; height: 260px;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
border-radius: 50%; border-radius: 50%;
background: radial-gradient(circle, rgb(217 70 239 / 0.35), rgb(56 189 248 / 0.12) 55%, transparent 72%); background: radial-gradient(circle, rgb(254 240 138 / 0.9), rgb(236 91 19 / 0.2) 55%, transparent 72%);
filter: blur(14px); filter: blur(14px);
animation: auraPulse 5s ease-in-out infinite; animation: auraPulse 5s ease-in-out infinite;
pointer-events: none; pointer-events: none;
@@ -683,18 +709,18 @@ function useExample(ex) {
width: 15rem; width: 15rem;
height: 10rem; height: 10rem;
overflow: hidden; overflow: hidden;
border: none; border: 2.5px solid var(--ink);
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
border-radius: 1.4rem; border-radius: 1rem;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
box-shadow: inset 0 0 0 1px var(--hairline); box-shadow: 4px 4px 0 var(--shadow-ink);
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.gcard:hover { .gcard:hover {
transform: translateY(-4px) scale(1.02); transform: translate(-2px, -2px);
box-shadow: inset 0 0 0 1px rgb(217 70 239 / 0.5), 0 22px 44px -26px rgb(168 85 247 / 0.6); box-shadow: 7px 7px 0 var(--m-orange);
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.marquee { animation: none; } .marquee { animation: none; }