style: apply oasis glass theme to workspace

This commit is contained in:
2026-05-19 15:55:18 +08:00
parent 2f2ea06767
commit 193708a836
7 changed files with 204 additions and 111 deletions

View File

@@ -3,7 +3,7 @@
@tailwind utilities;
html, body {
background: #0A0A0F;
background: #030603;
color: #FFFFFF;
font-feature-settings: "cv02", "cv03", "cv04", "cv11", "ss01";
-webkit-font-smoothing: antialiased;
@@ -12,88 +12,155 @@ html, body {
body {
font-family: ui-sans-serif, system-ui, -apple-system, "SF Pro Text", "PingFang SC", "Noto Sans SC", "Helvetica Neue", sans-serif;
background-color: #0A0A0F;
background-image:
radial-gradient(circle at 18% -10%, rgba(139, 92, 246, 0.22), transparent 55%),
radial-gradient(circle at 88% 8%, rgba(59, 130, 246, 0.18), transparent 50%),
radial-gradient(circle at 50% 110%, rgba(217, 70, 239, 0.12), transparent 60%);
background-color: #030603;
background-image: linear-gradient(180deg, rgba(13, 24, 12, 0.9), rgba(2, 4, 2, 1));
background-attachment: fixed;
min-height: 100vh;
}
::selection { background: rgba(139, 92, 246, 0.35); color: #fff; }
::selection { background: rgba(230, 245, 120, 0.28); color: #fff; }
.app-oasis .login-oasis-canvas {
z-index: 0;
opacity: 0.74;
filter: saturate(0.92) contrast(1.04);
}
.app-oasis-shade {
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
background:
linear-gradient(90deg, rgba(0, 0, 0, 0.76), rgba(3, 9, 3, 0.46) 42%, rgba(0, 0, 0, 0.72)),
linear-gradient(180deg, rgba(0, 0, 0, 0.68), rgba(0, 0, 0, 0.12) 36%, rgba(0, 0, 0, 0.82)),
linear-gradient(90deg, rgba(255, 255, 255, 0.026) 1px, transparent 1px),
linear-gradient(180deg, rgba(255, 255, 255, 0.022) 1px, transparent 1px);
background-size: auto, auto, 64px 64px, 64px 64px;
}
.app-grass-floor {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
height: 34vh;
pointer-events: none;
opacity: 0.38;
background:
linear-gradient(180deg, transparent, rgba(5, 12, 4, 0.78) 60%, rgba(0, 0, 0, 0.94)),
repeating-linear-gradient(103deg, transparent 0 13px, rgba(188, 218, 112, 0.13) 13px 14px, transparent 14px 28px),
repeating-linear-gradient(78deg, transparent 0 18px, rgba(84, 122, 67, 0.18) 18px 20px, transparent 20px 34px);
mask-image: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.8) 38%, #000 100%);
}
/* ===== Buttons ===== */
.btn {
@apply inline-flex items-center justify-center gap-1.5 px-4 py-2 rounded-xl text-sm font-medium transition-all;
@apply inline-flex items-center justify-center gap-1.5 px-4 py-2 rounded-[8px] text-sm font-medium transition-all;
}
.btn-primary {
@apply text-white bg-gradient-to-r from-violet-500 via-indigo-500 to-blue-500 hover:brightness-110 active:scale-[0.98];
@apply active:scale-[0.98];
background: linear-gradient(135deg, rgba(230, 245, 120, 0.96), rgba(214, 179, 106, 0.94));
color: #081006;
box-shadow: 0 6px 24px -8px rgba(99, 102, 241, 0.6), inset 0 1px 0 rgba(255,255,255,0.18);
}
.btn-primary:hover {
filter: brightness(1.06);
}
.btn-ghost {
@apply text-white/80 bg-white/[0.05] hover:bg-white/[0.09] border border-white/[0.08] active:scale-[0.98];
@apply text-white/80 active:scale-[0.98];
border: 1px solid rgba(140, 180, 120, 0.14);
background: rgba(10, 18, 10, 0.34);
}
.btn-outline {
@apply text-white/80 bg-white/[0.03] hover:bg-white/[0.07] border border-white/[0.1] hover:border-white/[0.18] active:scale-[0.98];
@apply text-white/80 active:scale-[0.98];
border: 1px solid rgba(140, 180, 120, 0.16);
background: rgba(10, 18, 10, 0.28);
}
.btn-glass {
@apply text-white bg-white/[0.06] hover:bg-white/[0.10] border border-white/[0.12] backdrop-blur-xl active:scale-[0.98];
@apply text-white backdrop-blur-xl active:scale-[0.98];
border: 1px solid rgba(140, 180, 120, 0.18);
background: rgba(10, 18, 10, 0.42);
}
/* ===== Cards (glass) ===== */
.card {
@apply relative rounded-3xl bg-white/[0.035] border border-white/[0.08] backdrop-blur-xl;
@apply relative rounded-[8px] backdrop-blur-xl;
border: 1px solid rgba(140, 180, 120, 0.14);
background:
linear-gradient(180deg, rgba(17, 28, 13, 0.62), rgba(5, 10, 5, 0.54)),
rgba(10, 18, 10, 0.42);
box-shadow:
0 1px 0 0 rgba(255,255,255,0.06) inset,
0 18px 60px -24px rgba(0,0,0,0.6);
0 1px 0 0 rgba(255,255,255,0.08) inset,
0 22px 70px -30px rgba(0,0,0,0.82),
0 0 42px -34px rgba(230, 245, 120, 0.64);
}
.card-2 {
@apply rounded-2xl bg-white/[0.03] border border-white/[0.07] backdrop-blur-xl;
@apply rounded-[8px] backdrop-blur-xl;
border: 1px solid rgba(140, 180, 120, 0.12);
background: rgba(8, 16, 8, 0.42);
}
.card-hover {
@apply transition-all hover:border-white/[0.14];
@apply transition-all;
}
.card-hover:hover,
.card:hover {
border-color: rgba(140, 180, 120, 0.28);
}
/* ===== Chips ===== */
.chip {
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-medium border;
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] text-[11px] font-medium border;
}
.chip-mock {
@apply bg-amber-500/10 text-amber-300 border-amber-400/30;
}
.chip-live {
@apply bg-emerald-500/10 text-emerald-300 border-emerald-400/30;
@apply border-emerald-400/30;
background: rgba(142, 190, 104, 0.13);
color: #dff5a8;
}
.chip-neutral {
@apply bg-white/[0.05] text-white/70 border-white/[0.12];
color: rgba(255, 255, 255, 0.7);
border-color: rgba(140, 180, 120, 0.14);
background: rgba(10, 18, 10, 0.34);
}
.chip-violet {
@apply bg-violet-500/15 text-violet-200 border-violet-400/30;
border-color: rgba(230, 245, 120, 0.28);
background: rgba(230, 245, 120, 0.12);
color: #e6f578;
}
/* ===== Segmented ===== */
.seg {
@apply inline-flex p-1 bg-white/[0.04] border border-white/[0.07] rounded-xl gap-1;
@apply inline-flex p-1 rounded-[8px] gap-1;
border: 1px solid rgba(140, 180, 120, 0.13);
background: rgba(10, 18, 10, 0.36);
backdrop-filter: blur(16px);
}
.seg-item {
@apply px-3 py-1.5 rounded-lg text-xs font-medium text-white/55 transition-all cursor-pointer;
@apply px-3 py-1.5 rounded-[7px] text-xs font-medium text-white/55 transition-all cursor-pointer;
}
.seg-item-active {
@apply text-white;
background: linear-gradient(135deg, rgba(139,92,246,0.35), rgba(59,130,246,0.25));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.18), 0 4px 16px -8px rgba(99,102,241,0.6);
color: #081006;
background: linear-gradient(135deg, rgba(230,245,120,0.92), rgba(214,179,106,0.86));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), 0 4px 16px -8px rgba(230,245,120,0.6);
}
/* ===== Tiles ===== */
.tile {
@apply relative aspect-square overflow-hidden rounded-2xl bg-white/[0.04] ring-1 ring-white/[0.08] transition-all cursor-pointer;
@apply relative aspect-square overflow-hidden rounded-[8px] transition-all cursor-pointer;
background: rgba(10, 18, 10, 0.44);
box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 18px 50px -28px rgba(0,0,0,0.8);
--tw-ring-color: rgba(140, 180, 120, 0.14);
border: 1px solid rgba(140, 180, 120, 0.1);
}
.tile-selected {
position: relative;
background: linear-gradient(#0A0A0F, #0A0A0F) padding-box, linear-gradient(135deg, #8B5CF6, #3B82F6) border-box;
background: linear-gradient(#071006, #071006) padding-box, linear-gradient(135deg, #e6f578, #d6b36a) border-box;
border: 2px solid transparent;
box-shadow: 0 0 0 1px rgba(139,92,246,0.25), 0 8px 32px -8px rgba(139,92,246,0.55);
box-shadow: 0 0 0 1px rgba(230,245,120,0.22), 0 8px 32px -8px rgba(230,245,120,0.46);
}
.tile-rejected {
@apply opacity-30 grayscale;
@@ -107,8 +174,8 @@ body {
box-shadow: 0 6px 18px -4px rgba(0,0,0,0.6);
}
.tile-badge-selected {
background: linear-gradient(135deg, #8B5CF6, #3B82F6);
color: #fff;
background: linear-gradient(135deg, #e6f578, #d6b36a);
color: #081006;
}
.tile-badge-rejected {
@apply bg-rose-500 text-white;
@@ -119,24 +186,44 @@ input, textarea {
font-family: inherit;
}
.field {
@apply w-full rounded-xl px-3.5 py-3 text-sm text-white placeholder:text-white/30 outline-none resize-none transition-colors;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
@apply w-full rounded-[8px] px-3.5 py-3 text-sm text-white placeholder:text-white/30 outline-none resize-none transition-colors;
background: rgba(10, 18, 10, 0.42);
border: 1px solid rgba(140, 180, 120, 0.14);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.field:focus {
border-color: rgba(139, 92, 246, 0.55);
box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.12);
border-color: rgba(230, 245, 120, 0.5);
box-shadow: 0 0 0 4px rgba(230, 245, 120, 0.12);
}
/* ===== KBD ===== */
.kbd {
@apply inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded text-[10px] font-medium text-white/70 ring-1 ring-white/10 bg-white/[0.06];
@apply inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-[6px] text-[10px] font-medium text-white/70;
border: 1px solid rgba(140, 180, 120, 0.16);
background: rgba(10, 18, 10, 0.46);
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}
/* ===== Section header ===== */
.section-eyebrow {
@apply inline-block text-[10px] font-semibold uppercase tracking-[0.18em] text-violet-300/80;
@apply inline-block text-[10px] font-semibold uppercase tracking-[0.18em];
color: rgba(230, 245, 120, 0.82);
text-shadow: 0 0 22px rgba(230, 245, 120, 0.16);
}
.glass-sidebar {
border-right: 1px solid rgba(140, 180, 120, 0.14);
background:
linear-gradient(180deg, rgba(8, 16, 7, 0.72), rgba(2, 5, 2, 0.62)),
rgba(10, 18, 10, 0.42);
box-shadow:
inset -1px 0 0 rgba(255,255,255,0.04),
16px 0 70px -44px rgba(0,0,0,0.95);
backdrop-filter: blur(18px);
}
.app-oasis main {
background: linear-gradient(90deg, rgba(0,0,0,0.18), rgba(10,18,10,0.08));
}
/* ===== Login (cloned from SKG source) ===== */

View File

@@ -5,6 +5,7 @@ import PromptPanel from '@/components/PromptPanel';
import ResultGrid from '@/components/ResultGrid';
import Sidebar from '@/components/Sidebar';
import PackPanel from '@/components/PackPanel';
import { OasisCanvas } from '@/components/login/OasisCanvas';
import type {
GenImage,
GenSession,
@@ -277,7 +278,11 @@ export default function Home() {
}
return (
<div className="flex h-screen text-white">
<div className="app-oasis relative h-screen overflow-hidden text-white">
<OasisCanvas />
<div className="app-oasis-shade" />
<div className="app-grass-floor" />
<div className="relative z-10 flex h-screen text-white">
<Sidebar
open={sidebarOpen}
onToggle={() => setSidebarOpen(v => !v)}
@@ -287,20 +292,20 @@ export default function Home() {
onNew={() => setCurrent(null)}
/>
<main className="flex-1 overflow-y-auto">
<div className="mx-auto max-w-[1200px] px-10 py-10">
<div className="mx-auto max-w-[1240px] px-10 py-8">
<header className="flex items-center justify-between mb-8 gap-4">
<div className="flex items-center gap-3 min-w-0">
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-blue-500 flex items-center justify-center shrink-0 shadow-glow-violet">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<div className="w-9 h-9 rounded-[8px] bg-gradient-to-br from-[#e6f578] to-[#d6b36a] flex items-center justify-center shrink-0 shadow-glow-violet">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#081006" strokeWidth="2.5">
<path d="M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" strokeLinejoin="round" />
</svg>
</div>
<div className="min-w-0">
<h1 className="text-base font-semibold tracking-tight leading-tight text-white">
<h1 className="text-base font-semibold tracking-tight leading-tight text-[#f8f7ef]">
AI Toy Patent
<span className="ml-2 text-white/40 font-normal text-sm"> / / </span>
<span className="ml-2 text-white/48 font-normal text-sm"> / / </span>
</h1>
<p className="text-[11px] text-white/35 mt-0.5"> · · · · Seedance </p>
<p className="text-[11px] text-[#e6f578]/55 mt-0.5"> · · · · Seedance </p>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
@@ -310,7 +315,7 @@ export default function Home() {
href={`/api/gallery/${encodeURIComponent(current.id)}`}
target="_blank"
rel="noreferrer"
className="chip chip-neutral hover:border-violet-300/40 hover:text-white transition-colors"
className="chip chip-neutral hover:border-[#e6f578]/40 hover:text-white transition-colors"
>
</a>
@@ -318,7 +323,7 @@ export default function Home() {
href={`/api/audit/${encodeURIComponent(current.id)}`}
target="_blank"
rel="noreferrer"
className="chip chip-neutral hover:border-violet-300/40 hover:text-white transition-colors"
className="chip chip-neutral hover:border-[#e6f578]/40 hover:text-white transition-colors"
>
</a>
@@ -326,7 +331,7 @@ export default function Home() {
)}
<button
onClick={handleLogout}
className="chip chip-neutral hover:border-violet-300/40 hover:text-white transition-colors"
className="chip chip-neutral hover:border-[#e6f578]/40 hover:text-white transition-colors"
>
退
</button>
@@ -375,5 +380,6 @@ export default function Home() {
</div>
</main>
</div>
</div>
);
}

View File

@@ -12,10 +12,10 @@ const PACK_DESCRIPTIONS: Record<PackKind, string> = {
};
const PACK_ACCENT: Record<PackKind, { bar: string; chip: string; icon: string }> = {
patent: { bar: 'from-violet-400 to-indigo-400', chip: 'bg-violet-500/15 text-violet-200 border-violet-400/30', icon: 'P' },
accessories: { bar: 'from-sky-400 to-cyan-400', chip: 'bg-sky-500/15 text-sky-200 border-sky-400/30', icon: 'A' },
production: { bar: 'from-amber-400 to-orange-400', chip: 'bg-amber-500/15 text-amber-200 border-amber-400/30', icon: 'F' },
marketing: { bar: 'from-emerald-400 to-teal-400', chip: 'bg-emerald-500/15 text-emerald-200 border-emerald-400/30', icon: 'M' },
patent: { bar: 'from-[#e6f578] to-[#d6b36a]', chip: 'bg-[#e6f578]/15 text-[#e6f578] border-[#e6f578]/30', icon: 'P' },
accessories: { bar: 'from-[#8cb478] to-[#547a43]', chip: 'bg-[#8cb478]/15 text-[#cfe7a7] border-[#8cb478]/30', icon: 'A' },
production: { bar: 'from-[#d6b36a] to-[#b6813c]', chip: 'bg-[#d6b36a]/15 text-[#f2d38c] border-[#d6b36a]/30', icon: 'F' },
marketing: { bar: 'from-[#b6df72] to-[#4f9a56]', chip: 'bg-[#b6df72]/15 text-[#dff5a8] border-[#b6df72]/30', icon: 'M' },
};
const ASPECT_PX: Record<AssetTemplate['aspectRatio'], string> = {
@@ -59,13 +59,13 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
}
}
return (
<div className="grid grid-cols-[76px_1fr_auto] gap-3 p-3 rounded-2xl bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
<div className="grid grid-cols-[76px_1fr_auto] gap-3 p-3 rounded-[8px] bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
{/* thumbnail */}
<div className="group/thumb relative w-[72px] h-[72px] rounded-xl overflow-visible bg-white/[0.04] ring-1 ring-white/[0.07] flex items-center justify-center">
<div className="group/thumb relative w-[72px] h-[72px] rounded-[8px] overflow-visible bg-white/[0.04] ring-1 ring-white/[0.07] flex items-center justify-center">
{ready ? (
<>
<img src={asset!.url} alt={template.title} className="max-w-full max-h-full object-contain rounded-lg bg-white" />
<div className="pointer-events-none fixed left-1/2 top-1/2 z-[80] hidden -translate-x-1/2 -translate-y-1/2 rounded-2xl bg-white p-2 shadow-2xl ring-1 ring-white/20 group-hover/thumb:block" style={{ width: 'min(640px, 86vw)' }}>
<div className="pointer-events-none fixed left-1/2 top-1/2 z-[80] hidden -translate-x-1/2 -translate-y-1/2 rounded-[8px] bg-white p-2 shadow-2xl ring-1 ring-white/20 group-hover/thumb:block" style={{ width: 'min(640px, 86vw)' }}>
<img src={asset!.url} alt="" className="max-h-[82vh] w-full object-contain" style={{ aspectRatio: aspectCss(asset!.aspectRatio) }} />
</div>
</>
@@ -75,7 +75,7 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
</div>
)}
{template.required && !ready && (
<span className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-violet-400 ring-2 ring-violet-400/30" />
<span className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-[#e6f578] ring-2 ring-[#e6f578]/30" />
)}
</div>
@@ -84,14 +84,14 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
<div className="flex items-center gap-1.5 flex-wrap">
<span className="text-[13px] font-medium text-white leading-snug">{template.title}</span>
{template.required && !ready && (
<span className="text-[9px] text-violet-300/80 uppercase tracking-widest"></span>
<span className="text-[9px] text-[#e6f578]/80 uppercase tracking-widest"></span>
)}
{ready && <span className="chip chip-live text-[10px] py-0"></span>}
</div>
<p className="text-[11px] text-white/45 leading-relaxed line-clamp-1">{template.description}</p>
<button
onClick={() => setShowPrompt(s => !s)}
className="text-[10px] text-white/30 hover:text-violet-300 transition-colors flex items-center gap-1"
className="text-[10px] text-white/30 hover:text-[#e6f578] transition-colors flex items-center gap-1"
>
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d={showPrompt ? 'M6 9l6 6 6-6' : 'M9 6l6 6-6 6'} strokeLinecap="round" />
@@ -125,7 +125,7 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
value={refinement}
onChange={event => setRefinement(event.target.value)}
placeholder="重做要求"
className="min-w-0 flex-1 rounded-lg bg-black/30 ring-1 ring-white/[0.08] px-2 py-1 text-[11px] text-white/80 outline-none focus:ring-violet-400/40"
className="min-w-0 flex-1 rounded-lg bg-black/30 ring-1 ring-white/[0.08] px-2 py-1 text-[11px] text-white/80 outline-none focus:ring-[#e6f578]/40"
/>
<button
onClick={handleRedo}
@@ -142,7 +142,7 @@ function AssetRow({ template, asset, accent, onRegenerate }: {
<div className="flex flex-col items-end justify-between text-right shrink-0">
<span className="text-[10px] font-mono text-white/40">{ASPECT_PX[template.aspectRatio]}</span>
<div className="flex flex-col items-end gap-1">
<span className={`text-[10px] ${ready ? 'text-emerald-300' : 'text-white/25'}`}>
<span className={`text-[10px] ${ready ? 'text-[#dff5a8]' : 'text-white/25'}`}>
{ready ? `L${asset!.derivationLevel}` : '待生成'}
</span>
</div>
@@ -249,7 +249,7 @@ function TextTemplateSection() {
return (
<section className="card overflow-hidden" id="pack-text">
<div className="p-4 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-sky-500 to-cyan-400 flex items-center justify-center text-white text-[11px] font-bold shrink-0">T</div>
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#8cb478] to-[#d6b36a] flex items-center justify-center text-white text-[11px] font-bold shrink-0">T</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-white"></span>
@@ -261,7 +261,7 @@ function TextTemplateSection() {
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="chip bg-sky-500/15 text-sky-200 border-sky-400/30 text-[10px]">GPT Text</span>
<span className="chip bg-[#8cb478]/15 text-[#cfe7a7] border-[#8cb478]/30 text-[10px]">GPT Text</span>
<button
onClick={() => setOpen(v => !v)}
title={open ? '收起' : '展开'}
@@ -279,23 +279,23 @@ function TextTemplateSection() {
{TEXT_TEMPLATES.map(template => {
const isOpen = showPromptId === template.id;
return (
<div key={template.id} className="grid grid-cols-[72px_1fr_auto] gap-3 p-3 rounded-2xl bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
<div className="aspect-square rounded-xl bg-gradient-to-br from-sky-500/15 to-cyan-500/15 ring-1 ring-sky-400/20 flex flex-col items-center justify-center text-sky-300 text-[9px] font-mono gap-0.5">
<div key={template.id} className="grid grid-cols-[72px_1fr_auto] gap-3 p-3 rounded-[8px] bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
<div className="aspect-square rounded-[8px] bg-gradient-to-br from-[#8cb478]/15 to-[#d6b36a]/15 ring-1 ring-[#8cb478]/20 flex flex-col items-center justify-center text-[#cfe7a7] text-[9px] font-mono gap-0.5">
<span>text</span>
<span className="text-[8px] text-sky-400/60">{template.outputFormat}</span>
<span className="text-[8px] text-[#cfe7a7]/60">{template.outputFormat}</span>
</div>
<div className="min-w-0 space-y-1">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="text-[13px] font-medium text-white">{template.title}</span>
<span className={`chip text-[10px] py-0 ${template.kind === 'patent' ? 'bg-violet-500/15 text-violet-200 border-violet-400/30' : template.kind === 'production' ? 'bg-amber-500/15 text-amber-200 border-amber-400/30' : template.kind === 'accessories' ? 'bg-sky-500/15 text-sky-200 border-sky-400/30' : template.kind === 'marketing' ? 'bg-emerald-500/15 text-emerald-200 border-emerald-400/30' : 'chip-neutral'}`}>
<span className={`chip text-[10px] py-0 ${template.kind === 'patent' ? 'bg-[#e6f578]/15 text-[#e6f578] border-[#e6f578]/30' : template.kind === 'production' ? 'bg-[#d6b36a]/15 text-[#f2d38c] border-[#d6b36a]/30' : template.kind === 'accessories' ? 'bg-[#8cb478]/15 text-[#cfe7a7] border-[#8cb478]/30' : template.kind === 'marketing' ? 'bg-[#b6df72]/15 text-[#dff5a8] border-[#b6df72]/30' : 'chip-neutral'}`}>
{template.kind}
</span>
{template.required && <span className="text-[9px] text-violet-300/80 uppercase tracking-widest"></span>}
{template.required && <span className="text-[9px] text-[#e6f578]/80 uppercase tracking-widest"></span>}
</div>
<p className="text-[11px] text-white/45 line-clamp-1">{template.description}</p>
<button
onClick={() => setShowPromptId(isOpen ? null : template.id)}
className="text-[10px] text-white/30 hover:text-sky-300 transition-colors flex items-center gap-1"
className="text-[10px] text-white/30 hover:text-[#cfe7a7] transition-colors flex items-center gap-1"
>
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d={isOpen ? 'M6 9l6 6 6-6' : 'M9 6l6 6-6 6'} strokeLinecap="round" />
@@ -333,7 +333,7 @@ function VideoSection({ videoLoading, primaryImage, onGenerateVideo }: {
return (
<section className="card overflow-hidden" id="pack-video">
<div className="p-4 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-fuchsia-500 to-violet-500 flex items-center justify-center text-white text-[11px] font-bold shrink-0">V</div>
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#e6f578] to-[#8cb478] flex items-center justify-center text-white text-[11px] font-bold shrink-0">V</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-white">Seedance </span>
@@ -363,8 +363,8 @@ function VideoSection({ videoLoading, primaryImage, onGenerateVideo }: {
{VIDEO_TEMPLATES.map(template => {
const isOpen = showPromptId === template.id;
return (
<div key={template.id} className="grid grid-cols-[72px_1fr_auto] gap-3 p-3 rounded-2xl bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
<div className="aspect-square rounded-xl bg-gradient-to-br from-fuchsia-500/15 to-violet-500/15 ring-1 ring-fuchsia-400/20 flex flex-col items-center justify-center text-fuchsia-300 text-[9px] font-mono gap-1">
<div key={template.id} className="grid grid-cols-[72px_1fr_auto] gap-3 p-3 rounded-[8px] bg-white/[0.025] ring-1 ring-white/[0.05] hover:ring-white/[0.1] transition-all">
<div className="aspect-square rounded-[8px] bg-gradient-to-br from-[#e6f578]/15 to-[#8cb478]/15 ring-1 ring-[#e6f578]/20 flex flex-col items-center justify-center text-[#e6f578] text-[9px] font-mono gap-1">
<span></span>
<span className="text-[8px]">{template.duration}s</span>
</div>
@@ -376,7 +376,7 @@ function VideoSection({ videoLoading, primaryImage, onGenerateVideo }: {
<p className="text-[11px] text-white/45 line-clamp-1">{template.description}</p>
<button
onClick={() => setShowPromptId(isOpen ? null : template.id)}
className="text-[10px] text-white/30 hover:text-fuchsia-300 transition-colors flex items-center gap-1"
className="text-[10px] text-white/30 hover:text-[#e6f578] transition-colors flex items-center gap-1"
>
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d={isOpen ? 'M6 9l6 6 6-6' : 'M9 6l6 6-6 6'} strokeLinecap="round" />
@@ -407,12 +407,12 @@ function VideoSection({ videoLoading, primaryImage, onGenerateVideo }: {
/* ── Section Nav ──────────────────────────────── */
const NAV_ITEMS = [
{ id: 'pack-patent', label: '专利', accent: 'from-violet-400 to-indigo-400' },
{ id: 'pack-accessories', label: '配件', accent: 'from-sky-400 to-cyan-400' },
{ id: 'pack-production', label: '生产', accent: 'from-amber-400 to-orange-400' },
{ id: 'pack-marketing', label: '宣发', accent: 'from-emerald-400 to-teal-400' },
{ id: 'pack-text', label: '文字', accent: 'from-sky-500 to-cyan-400' },
{ id: 'pack-video', label: '视频', accent: 'from-fuchsia-500 to-violet-500' },
{ id: 'pack-patent', label: '专利', accent: 'from-[#e6f578] to-[#d6b36a]' },
{ id: 'pack-accessories', label: '配件', accent: 'from-[#8cb478] to-[#547a43]' },
{ id: 'pack-production', label: '生产', accent: 'from-[#d6b36a] to-[#b6813c]' },
{ id: 'pack-marketing', label: '宣发', accent: 'from-[#b6df72] to-[#4f9a56]' },
{ id: 'pack-text', label: '文字', accent: 'from-[#8cb478] to-[#d6b36a]' },
{ id: 'pack-video', label: '视频', accent: 'from-[#e6f578] to-[#8cb478]' },
];
function SectionNav({ active, onChange }: { active: string; onChange: (id: string) => void }) {
@@ -427,8 +427,8 @@ function SectionNav({ active, onChange }: { active: string; onChange: (id: strin
}}
className={`shrink-0 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
active === item.id
? `text-white bg-gradient-to-r ${item.accent} shadow-sm`
: 'text-white/45 hover:text-white/70 hover:bg-white/[0.05]'
? `text-[#081006] bg-gradient-to-r ${item.accent} shadow-sm`
: 'text-white/45 hover:text-white/70 hover:bg-[#e6f578]/[0.06]'
}`}
>
{item.label}
@@ -473,7 +473,7 @@ export default function PackPanel({
return (
<section className="card p-6">
<div className="flex items-start gap-4">
<div className="w-8 h-8 rounded-xl bg-white/[0.05] border border-white/[0.08] flex items-center justify-center text-white/30 text-sm"></div>
<div className="w-8 h-8 rounded-[8px] bg-white/[0.05] border border-white/[0.08] flex items-center justify-center text-white/30 text-sm"></div>
<div>
<span className="section-eyebrow">Step · 03 · Assets</span>
<h2 className="mt-1.5 text-sm font-semibold text-white"></h2>
@@ -506,7 +506,7 @@ export default function PackPanel({
<div className="font-mono text-white/70">{generatedTotal} <span className="text-white/30">/ {totalImageSlots} </span></div>
<div className="text-[10px]"></div>
</div>
<div className="relative w-16 h-16 rounded-2xl overflow-hidden ring-1 ring-white/15 shadow-glow-violet shrink-0">
<div className="relative w-16 h-16 rounded-[8px] overflow-hidden ring-1 ring-white/15 shadow-glow-violet shrink-0">
<img src={primaryImage.url} alt="selected" className="w-full h-full object-contain bg-white" />
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
<div className="absolute bottom-1 inset-x-0 text-center text-[8px] font-semibold text-white/80 uppercase tracking-wider"></div>

View File

@@ -136,7 +136,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
</label>
<div className="flex flex-wrap gap-2.5">
{refs.map((r, i) => (
<div key={i} className="relative w-20 h-20 rounded-xl overflow-hidden ring-1 ring-white/[0.1] group">
<div key={i} className="relative w-20 h-20 rounded-[8px] overflow-hidden ring-1 ring-white/[0.1] group">
<img src={r} alt="ref" className="w-full h-full object-cover" />
<button
onClick={() => setRefs(prev => prev.filter((_, j) => j !== i))}
@@ -147,7 +147,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
{refs.length < 4 && (
<button
onClick={() => fileInput.current?.click()}
className="w-20 h-20 rounded-xl border-2 border-dashed border-white/15 hover:border-violet-400/50 hover:bg-white/[0.03] text-white/30 hover:text-violet-300 text-2xl transition-all flex items-center justify-center"
className="w-20 h-20 rounded-[8px] border-2 border-dashed border-[#8cb478]/25 hover:border-[#e6f578]/55 hover:bg-[#e6f578]/[0.04] text-white/30 hover:text-[#e6f578] text-2xl transition-all flex items-center justify-center"
>+</button>
)}
<input
@@ -171,7 +171,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
</label>
<div className="flex flex-wrap gap-2.5">
{remixFiles.map((file, i) => (
<div key={`${file.name}-${i}`} className="relative w-20 h-20 rounded-xl overflow-hidden ring-1 ring-white/[0.1] group">
<div key={`${file.name}-${i}`} className="relative w-20 h-20 rounded-[8px] overflow-hidden ring-1 ring-white/[0.1] group">
<img src={URL.createObjectURL(file)} alt={file.name} className="w-full h-full object-cover" />
<button
onClick={() => setRemixFiles(prev => prev.filter((_, j) => j !== i))}
@@ -182,7 +182,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
{remixFiles.length < 4 && (
<button
onClick={() => remixInput.current?.click()}
className="w-20 h-20 rounded-xl border-2 border-dashed border-white/15 hover:border-violet-400/50 hover:bg-white/[0.03] text-white/30 hover:text-violet-300 text-2xl transition-all flex items-center justify-center"
className="w-20 h-20 rounded-[8px] border-2 border-dashed border-[#8cb478]/25 hover:border-[#e6f578]/55 hover:bg-[#e6f578]/[0.04] text-white/30 hover:text-[#e6f578] text-2xl transition-all flex items-center justify-center"
>+</button>
)}
<input
@@ -220,7 +220,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
</label>
<button
onClick={() => replicateInput.current?.click()}
className="relative w-full aspect-square rounded-2xl border-2 border-dashed border-white/15 hover:border-violet-400/50 bg-white/[0.025] overflow-hidden transition-all flex items-center justify-center text-white/35"
className="relative w-full aspect-square rounded-[8px] border-2 border-dashed border-[#8cb478]/25 hover:border-[#e6f578]/55 bg-[#e6f578]/[0.025] overflow-hidden transition-all flex items-center justify-center text-white/35"
>
{replicateFile ? (
<img src={replicatePreview} alt={replicateFile.name} className="w-full h-full object-cover" />
@@ -249,7 +249,7 @@ export default function PromptPanel({ onGenerate, onUploadProject, loading, uplo
className="field text-[15px] leading-relaxed"
/>
</div>
<div className="rounded-xl bg-amber-500/10 ring-1 ring-amber-400/20 px-3 py-2 text-[11px] leading-relaxed text-amber-100/75">
<div className="rounded-[8px] bg-amber-500/10 ring-1 ring-amber-400/20 px-3 py-2 text-[11px] leading-relaxed text-amber-100/75">
使 IP
</div>
</div>

View File

@@ -45,7 +45,7 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
</div>
<div className="text-xs text-white/55">
<span className="ml-1 font-semibold bg-gradient-to-r from-violet-300 to-blue-300 bg-clip-text text-transparent">{selectedCount}</span>
<span className="ml-1 font-semibold bg-gradient-to-r from-[#e6f578] to-[#d6b36a] bg-clip-text text-transparent">{selectedCount}</span>
<span className="text-white/30"> / {images.length}</span>
</div>
</div>
@@ -57,7 +57,7 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
className={`tile group ${img.status === 'selected' ? 'tile-selected' : ''} ${img.status === 'rejected' ? 'tile-rejected' : ''}`}
>
<img src={img.url} alt={`gen ${i + 1}`} className="w-full h-full object-contain bg-white" />
<div className="pointer-events-none fixed left-1/2 top-1/2 z-[80] hidden -translate-x-1/2 -translate-y-1/2 rounded-2xl bg-white p-2 shadow-2xl ring-1 ring-white/20 group-hover:block" style={{ width: 'min(640px, 86vw)' }}>
<div className="pointer-events-none fixed left-1/2 top-1/2 z-[80] hidden -translate-x-1/2 -translate-y-1/2 rounded-[8px] bg-white p-2 shadow-2xl ring-1 ring-white/20 group-hover:block" style={{ width: 'min(640px, 86vw)' }}>
<img src={img.url} alt="" className="max-h-[82vh] w-full object-contain" />
</div>
<div className="tile-keynum">{i + 1}</div>
@@ -72,7 +72,7 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
<div className="absolute inset-x-0 bottom-0 p-2 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity bg-gradient-to-t from-black/80 via-black/40 to-transparent">
<button
onClick={() => onAction(img.id, img.status === 'selected' ? 'reset' : 'select')}
className="flex-1 px-3 py-1.5 rounded-lg bg-gradient-to-r from-violet-500 to-blue-500 text-white text-xs font-semibold hover:brightness-110 transition shadow-glow-violet"
className="flex-1 px-3 py-1.5 rounded-[8px] bg-gradient-to-r from-[#e6f578] to-[#d6b36a] text-[#081006] text-xs font-semibold hover:brightness-110 transition shadow-glow-violet"
>
{img.status === 'selected' ? '✓ 已选' : '选中'}
</button>

View File

@@ -19,7 +19,7 @@ export default function Sidebar({
}) {
if (!open) {
return (
<aside className="w-12 shrink-0 border-r border-white/[0.06] bg-black/30 backdrop-blur-xl flex flex-col items-center py-4">
<aside className="glass-sidebar w-12 shrink-0 flex flex-col items-center py-4">
<button
onClick={onToggle}
className="w-8 h-8 rounded-lg hover:bg-white/[0.08] flex items-center justify-center text-white/60 transition-colors"
@@ -34,10 +34,10 @@ export default function Sidebar({
}
return (
<aside className="w-72 shrink-0 border-r border-white/[0.06] bg-black/30 backdrop-blur-xl flex flex-col">
<aside className="glass-sidebar w-72 shrink-0 flex flex-col">
<div className="px-4 pt-5 pb-3 flex items-center gap-3">
<div className="w-8 h-8 rounded-xl bg-gradient-to-br from-violet-500 to-blue-500 flex items-center justify-center shadow-glow-violet">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
<div className="w-8 h-8 rounded-[8px] bg-gradient-to-br from-[#e6f578] to-[#d6b36a] flex items-center justify-center shadow-glow-violet">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#081006" strokeWidth="2.5">
<path d="M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" strokeLinejoin="round" />
</svg>
</div>
@@ -85,10 +85,10 @@ export default function Sidebar({
<button
key={s.id}
onClick={() => onPick(s.id)}
className={`w-full text-left px-3 py-2.5 rounded-xl text-xs transition-all ${
className={`w-full text-left px-3 py-2.5 rounded-[8px] text-xs transition-all ${
active
? 'bg-gradient-to-r from-violet-500/30 via-indigo-500/20 to-blue-500/20 ring-1 ring-violet-400/40 text-white shadow-glow-violet'
: 'hover:bg-white/[0.05] text-white/75 ring-1 ring-transparent hover:ring-white/[0.08]'
? 'bg-gradient-to-r from-[#e6f578]/24 via-[#8cb478]/18 to-[#d6b36a]/16 ring-1 ring-[#e6f578]/35 text-white shadow-glow-violet'
: 'hover:bg-[#e6f578]/[0.06] text-white/75 ring-1 ring-transparent hover:ring-[#8cb478]/20'
}`}
>
<div className={`line-clamp-2 font-medium leading-relaxed ${active ? 'text-white' : 'text-white/85'}`}>
@@ -101,7 +101,7 @@ export default function Sidebar({
<span className="flex items-center gap-1.5">
<span>{s.images.length} </span>
{selectedCount > 0 && (
<span className={active ? 'text-violet-200 font-semibold' : 'text-violet-300 font-semibold'}>
<span className={active ? 'text-[#e6f578] font-semibold' : 'text-[#d6b36a] font-semibold'}>
{selectedCount}
</span>
)}

View File

@@ -5,22 +5,22 @@ const config: Config = {
theme: {
extend: {
colors: {
ink: '#0a0a0a',
paper: '#fafafa',
accent: '#ff6b35',
noir: '#0A0A0F',
'noir-2': '#11111A',
'noir-3': '#171723',
ink: '#081006',
paper: '#f8f7ef',
accent: '#d6b36a',
noir: '#030603',
'noir-2': '#071006',
'noir-3': '#10180c',
},
backgroundImage: {
'accent-violet': 'linear-gradient(135deg, #8B5CF6 0%, #6366F1 50%, #3B82F6 100%)',
'accent-fuchsia': 'linear-gradient(135deg, #D946EF 0%, #8B5CF6 100%)',
'noir-radial': 'radial-gradient(circle at 20% 0%, rgba(139,92,246,0.18), transparent 60%), radial-gradient(circle at 90% 90%, rgba(59,130,246,0.12), transparent 50%)',
'accent-violet': 'linear-gradient(135deg, #e6f578 0%, #d6b36a 100%)',
'accent-fuchsia': 'linear-gradient(135deg, #d6b36a 0%, #8cb478 100%)',
'noir-radial': 'linear-gradient(180deg, rgba(12,28,10,0.8), rgba(3,6,3,1))',
},
boxShadow: {
'glow-violet': '0 0 40px -8px rgba(139, 92, 246, 0.45)',
'glow-blue': '0 0 40px -8px rgba(59, 130, 246, 0.45)',
'card-noir': '0 1px 0 0 rgba(255,255,255,0.06) inset, 0 18px 60px -20px rgba(0,0,0,0.6)',
'glow-violet': '0 0 40px -8px rgba(230, 245, 120, 0.38)',
'glow-blue': '0 0 40px -8px rgba(214, 179, 106, 0.34)',
'card-noir': '0 1px 0 0 rgba(255,255,255,0.08) inset, 0 18px 60px -20px rgba(0,0,0,0.72)',
},
},
},