auto-save 2026-05-19 00:17 (~8)
This commit is contained in:
@@ -182,6 +182,25 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 00:06 (+1, ~1)",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T00:11:58+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-19 00:11 (+3, ~1)",
|
||||
"hash": "c3a4637",
|
||||
"files_changed": 4
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-18T16:13:50Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 00:11 (+3, ~1)",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-18T16:16:50Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 分支 master · 7 项未提交变更 · 最近提交:auto-save 2026-05-19 00:11 (+3, ~1)",
|
||||
"files_changed": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,88 +3,150 @@
|
||||
@tailwind utilities;
|
||||
|
||||
html, body {
|
||||
background: #FAFAFA;
|
||||
color: #09090B;
|
||||
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
||||
background: #0A0A0F;
|
||||
color: #FFFFFF;
|
||||
font-feature-settings: "cv02", "cv03", "cv04", "cv11", "ss01";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
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-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
::selection { background: rgba(139, 92, 246, 0.35); color: #fff; }
|
||||
|
||||
/* ===== Buttons ===== */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center gap-1.5 px-4 py-2 rounded-xl text-sm font-medium transition-all;
|
||||
}
|
||||
.btn-primary {
|
||||
@apply bg-zinc-900 text-white hover:bg-zinc-800 active:scale-[0.98] shadow-sm;
|
||||
@apply text-white bg-gradient-to-r from-violet-500 via-indigo-500 to-blue-500 hover:brightness-110 active:scale-[0.98];
|
||||
box-shadow: 0 6px 24px -8px rgba(99, 102, 241, 0.6), inset 0 1px 0 rgba(255,255,255,0.18);
|
||||
}
|
||||
.btn-ghost {
|
||||
@apply bg-zinc-100 text-zinc-700 hover:bg-zinc-200 active:scale-[0.98];
|
||||
@apply text-white/80 bg-white/[0.05] hover:bg-white/[0.09] border border-white/[0.08] active:scale-[0.98];
|
||||
}
|
||||
.btn-outline {
|
||||
@apply bg-white text-zinc-700 border border-zinc-200 hover:border-zinc-300 hover:bg-zinc-50 active:scale-[0.98];
|
||||
@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];
|
||||
}
|
||||
.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];
|
||||
}
|
||||
|
||||
/* ===== Cards (glass) ===== */
|
||||
.card {
|
||||
@apply bg-white border border-zinc-200/70 rounded-2xl shadow-[0_1px_2px_rgba(0,0,0,0.04),0_4px_12px_rgba(0,0,0,0.04)];
|
||||
@apply relative rounded-3xl bg-white/[0.035] border border-white/[0.08] backdrop-blur-xl;
|
||||
box-shadow:
|
||||
0 1px 0 0 rgba(255,255,255,0.06) inset,
|
||||
0 18px 60px -24px rgba(0,0,0,0.6);
|
||||
}
|
||||
.card-2 {
|
||||
@apply rounded-2xl bg-white/[0.03] border border-white/[0.07] backdrop-blur-xl;
|
||||
}
|
||||
.card-hover {
|
||||
@apply transition-shadow hover:shadow-[0_2px_4px_rgba(0,0,0,0.04),0_8px_24px_rgba(0,0,0,0.06)];
|
||||
@apply transition-all hover:border-white/[0.14];
|
||||
}
|
||||
|
||||
/* ===== Chips ===== */
|
||||
.chip {
|
||||
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium;
|
||||
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-medium border;
|
||||
}
|
||||
.chip-mock {
|
||||
@apply bg-amber-50 text-amber-700 border border-amber-200;
|
||||
@apply bg-amber-500/10 text-amber-300 border-amber-400/30;
|
||||
}
|
||||
.chip-live {
|
||||
@apply bg-emerald-50 text-emerald-700 border border-emerald-200;
|
||||
@apply bg-emerald-500/10 text-emerald-300 border-emerald-400/30;
|
||||
}
|
||||
.chip-neutral {
|
||||
@apply bg-white/[0.05] text-white/70 border-white/[0.12];
|
||||
}
|
||||
.chip-violet {
|
||||
@apply bg-violet-500/15 text-violet-200 border-violet-400/30;
|
||||
}
|
||||
|
||||
/* ===== Segmented ===== */
|
||||
.seg {
|
||||
@apply inline-flex p-1 bg-zinc-100 rounded-xl gap-1;
|
||||
@apply inline-flex p-1 bg-white/[0.04] border border-white/[0.07] rounded-xl gap-1;
|
||||
}
|
||||
.seg-item {
|
||||
@apply px-3 py-1.5 rounded-lg text-xs font-medium text-zinc-600 transition-all cursor-pointer;
|
||||
@apply px-3 py-1.5 rounded-lg text-xs font-medium text-white/55 transition-all cursor-pointer;
|
||||
}
|
||||
.seg-item-active {
|
||||
@apply bg-white text-zinc-900 shadow-sm;
|
||||
@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);
|
||||
}
|
||||
|
||||
/* ===== Tiles ===== */
|
||||
.tile {
|
||||
@apply relative aspect-square overflow-hidden rounded-2xl bg-zinc-100 border-2 border-transparent ring-1 ring-zinc-200/70 transition-all cursor-pointer;
|
||||
@apply relative aspect-square overflow-hidden rounded-2xl bg-white/[0.04] ring-1 ring-white/[0.08] transition-all cursor-pointer;
|
||||
}
|
||||
.tile-selected {
|
||||
@apply border-zinc-900 ring-0 shadow-[0_4px_16px_rgba(0,0,0,0.12)];
|
||||
position: relative;
|
||||
background: linear-gradient(#0A0A0F, #0A0A0F) padding-box, linear-gradient(135deg, #8B5CF6, #3B82F6) 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);
|
||||
}
|
||||
.tile-rejected {
|
||||
@apply opacity-30 grayscale;
|
||||
}
|
||||
.tile-keynum {
|
||||
@apply absolute top-2 left-2 w-7 h-7 rounded-lg bg-white/95 backdrop-blur text-[11px] font-semibold text-zinc-700 flex items-center justify-center shadow-sm;
|
||||
@apply absolute top-2 left-2 w-7 h-7 rounded-lg bg-black/60 backdrop-blur text-[11px] font-semibold text-white/85 flex items-center justify-center ring-1 ring-white/10;
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||||
}
|
||||
.tile-badge {
|
||||
@apply absolute top-2 right-2 w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold shadow-sm;
|
||||
@apply absolute top-2 right-2 w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold;
|
||||
box-shadow: 0 6px 18px -4px rgba(0,0,0,0.6);
|
||||
}
|
||||
.tile-badge-selected {
|
||||
@apply bg-zinc-900 text-white;
|
||||
background: linear-gradient(135deg, #8B5CF6, #3B82F6);
|
||||
color: #fff;
|
||||
}
|
||||
.tile-badge-rejected {
|
||||
@apply bg-red-500 text-white;
|
||||
@apply bg-rose-500 text-white;
|
||||
}
|
||||
|
||||
/* ===== Fields ===== */
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
.field {
|
||||
@apply w-full bg-white border border-zinc-200 rounded-xl px-3.5 py-3 text-sm text-zinc-900 placeholder:text-zinc-400 transition-colors focus:border-zinc-900 focus:ring-2 focus:ring-zinc-900/5 outline-none resize-none;
|
||||
@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);
|
||||
}
|
||||
.field:focus {
|
||||
border-color: rgba(139, 92, 246, 0.55);
|
||||
box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.12);
|
||||
}
|
||||
|
||||
/* ===== KBD ===== */
|
||||
.kbd {
|
||||
@apply inline-flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded bg-white border border-zinc-200 text-[10px] font-medium text-zinc-600 shadow-sm;
|
||||
@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];
|
||||
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;
|
||||
}
|
||||
|
||||
/* ===== Divider ===== */
|
||||
.divider-line {
|
||||
@apply h-px w-full;
|
||||
background: linear-gradient(to right, transparent, rgba(255,255,255,0.10), transparent);
|
||||
}
|
||||
|
||||
/* ===== Scrollbar (subtle) ===== */
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.12); }
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function Home() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#FAFAFA]">
|
||||
<div className="flex h-screen text-white">
|
||||
<Sidebar
|
||||
open={sidebarOpen}
|
||||
onToggle={() => setSidebarOpen(v => !v)}
|
||||
@@ -186,20 +186,22 @@ export default function Home() {
|
||||
onNew={() => setCurrent(null)}
|
||||
/>
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="mx-auto max-w-[1180px] px-10 py-10">
|
||||
<div className="mx-auto max-w-[1200px] px-10 py-10">
|
||||
<header className="flex items-start justify-between mb-10">
|
||||
<div>
|
||||
<h1 className="text-[26px] font-semibold tracking-tight text-zinc-900 leading-tight">
|
||||
AI 玩具专利生成工作流
|
||||
<span className="section-eyebrow">AI Toy Workflow</span>
|
||||
<h1 className="mt-2 text-[30px] font-semibold tracking-tight leading-tight">
|
||||
把一句想法
|
||||
<span className="bg-gradient-to-r from-violet-300 via-fuchsia-300 to-blue-300 bg-clip-text text-transparent"> 变成专利、生产、宣发的素材包</span>
|
||||
</h1>
|
||||
<p className="text-sm text-zinc-500 mt-1.5">
|
||||
批量出意向 → 快筛 → 多角度尺寸 → 喂专利
|
||||
<p className="text-sm text-white/50 mt-2 max-w-[560px] leading-relaxed">
|
||||
批量出意向 · 九宫格快筛 · 锁定角色 · 一键三包 · Seedance 视频
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
<span className={provider === 'gpt' ? 'chip chip-live' : 'chip chip-mock'}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${provider === 'gpt' ? 'bg-emerald-500' : 'bg-amber-500'}`} />
|
||||
{provider === 'gpt' ? 'GPT · 最高规格' : provider === 'mock' ? 'Mock · 占位图' : provider}
|
||||
<div className="flex items-center gap-2 pt-1 shrink-0">
|
||||
<span className={provider === 'gpt' ? 'chip chip-live' : provider === '?' ? 'chip chip-neutral' : 'chip chip-mock'}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${provider === 'gpt' ? 'bg-emerald-400' : provider === '?' ? 'bg-white/40' : 'bg-amber-400'}`} />
|
||||
{provider === 'gpt' ? 'GPT · 最高规格' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
@@ -207,15 +209,16 @@ export default function Home() {
|
||||
<div className="space-y-8">
|
||||
<PromptPanel onGenerate={handleGenerate} loading={loading} />
|
||||
{current && (
|
||||
<section className="space-y-4">
|
||||
<section className="space-y-5">
|
||||
<div className="flex items-end justify-between">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium text-zinc-900">本次生成</h2>
|
||||
<p className="text-xs text-zinc-500 mt-0.5">
|
||||
<span className="section-eyebrow">Step · 02 · Quick Screen</span>
|
||||
<h2 className="mt-2 text-lg font-semibold text-white">本次生成</h2>
|
||||
<p className="text-xs text-white/40 mt-1">
|
||||
{new Date(current.createdAt).toLocaleString('zh-CN')}
|
||||
</p>
|
||||
</div>
|
||||
<code className="text-[11px] text-zinc-400 font-mono">{current.id}</code>
|
||||
<code className="text-[11px] text-white/30 font-mono">{current.id}</code>
|
||||
</div>
|
||||
<ResultGrid images={current.images} onAction={handleAction} />
|
||||
<PackPanel
|
||||
|
||||
@@ -4,11 +4,32 @@ import type { GenImage, GenSession, PackKind } from '@/lib/types';
|
||||
import { PACK_LABELS, PACK_ORDER, VIDEO_TEMPLATES } from '@/lib/templates';
|
||||
|
||||
const PACK_DESCRIPTIONS: Record<PackKind, string> = {
|
||||
patent: '六面视图、45 度立体图和局部放大,用于外观专利素材整理。',
|
||||
patent: '六面视图、45° 立体图和局部放大,用于外观专利素材整理。',
|
||||
production: '尺寸、材料、颜色、拆件和包装结构,用于工厂报价与打样沟通。',
|
||||
marketing: '白底商品图、场景图、细节图和社媒图,用于新品宣发。',
|
||||
};
|
||||
|
||||
const PACK_ACCENT: Record<PackKind, { ring: string; chip: string; dot: string; bar: string }> = {
|
||||
patent: {
|
||||
ring: 'ring-violet-400/30',
|
||||
chip: 'bg-violet-500/15 text-violet-200 border-violet-400/30',
|
||||
dot: 'bg-violet-400',
|
||||
bar: 'from-violet-400 to-indigo-400',
|
||||
},
|
||||
production: {
|
||||
ring: 'ring-amber-400/30',
|
||||
chip: 'bg-amber-500/15 text-amber-200 border-amber-400/30',
|
||||
dot: 'bg-amber-400',
|
||||
bar: 'from-amber-400 to-orange-400',
|
||||
},
|
||||
marketing: {
|
||||
ring: 'ring-emerald-400/30',
|
||||
chip: 'bg-emerald-500/15 text-emerald-200 border-emerald-400/30',
|
||||
dot: 'bg-emerald-400',
|
||||
bar: 'from-emerald-400 to-teal-400',
|
||||
},
|
||||
};
|
||||
|
||||
function manifestUrl(sessionId: string, kind: PackKind, version: string) {
|
||||
return `/api/export/${sessionId}_${kind}_${version}_manifest.json`;
|
||||
}
|
||||
@@ -40,26 +61,35 @@ export default function PackPanel({
|
||||
|
||||
if (!primaryImage) {
|
||||
return (
|
||||
<section className="card p-5">
|
||||
<h2 className="text-sm font-semibold text-zinc-900">下一步素材包</h2>
|
||||
<p className="mt-1 text-xs text-zinc-500">
|
||||
先在上方九宫格选中一个主方案,再生成专利、生产和宣发模板包。
|
||||
<section className="card p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-9 h-9 rounded-xl bg-white/[0.05] border border-white/[0.08] flex items-center justify-center text-white/40 text-sm">⌗</div>
|
||||
<div>
|
||||
<span className="section-eyebrow">Step · 03 · Lock Character</span>
|
||||
<h2 className="mt-2 text-sm font-semibold text-white">下一步素材包</h2>
|
||||
<p className="mt-1 text-[11px] text-white/45 leading-relaxed">
|
||||
先在上方九宫格选中一个主方案,再生成专利、生产、宣发模板包,以及 Seedance 视频。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card p-5 space-y-5">
|
||||
<section className="card p-6 space-y-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-zinc-900">角色锁定与素材包</h2>
|
||||
<p className="mt-1 text-xs text-zinc-500">
|
||||
当前以第一个选中图作为主方案,可先锁定角色,再全量生成三类素材包。
|
||||
<span className="section-eyebrow">Step · 03 · Lock Character</span>
|
||||
<h2 className="mt-2 text-lg font-semibold text-white">角色锁定 & 素材包</h2>
|
||||
<p className="mt-1 text-[11px] text-white/45 leading-relaxed max-w-[440px]">
|
||||
以第一个选中图作为主方案。先锁定角色设定,再全量生成三类素材包,所有图引用同一份 CharacterSpec 保持一致性。
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-16 h-16 rounded-xl overflow-hidden ring-1 ring-zinc-200 bg-zinc-100 shrink-0">
|
||||
<div className="relative w-20 h-20 rounded-2xl overflow-hidden ring-1 ring-white/15 shrink-0 shadow-glow-violet">
|
||||
<img src={primaryImage.url} alt="selected source" className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
|
||||
<div className="absolute bottom-1 left-1 right-1 text-[9px] font-semibold text-white/90 uppercase tracking-wider truncate">主方案</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,64 +97,88 @@ export default function PackPanel({
|
||||
<button
|
||||
onClick={() => onLockCharacter(primaryImage)}
|
||||
disabled={characterLoading || !!loadingKind || allLoading}
|
||||
className="btn btn-outline text-xs"
|
||||
className="btn btn-glass text-xs disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{characterLoading ? '锁定中' : session.characterSpec ? '刷新角色设定' : '锁定角色设定'}
|
||||
{characterLoading ? (
|
||||
<>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" className="animate-spin" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 2a10 10 0 0 1 10 10" strokeLinecap="round" />
|
||||
</svg>
|
||||
锁定中
|
||||
</>
|
||||
) : session.characterSpec ? '刷新角色设定' : '锁定角色设定'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onGenerateAll(primaryImage)}
|
||||
disabled={allLoading || !!loadingKind || characterLoading}
|
||||
className="btn btn-primary text-xs"
|
||||
className="btn btn-primary text-xs disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{allLoading ? '全量生成中' : '一键生成完整三包'}
|
||||
{allLoading ? '全量生成中' : '✨ 一键三包'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{session.characterSpec && (
|
||||
<div className="rounded-2xl bg-zinc-50 border border-zinc-200/70 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="card-2 p-4">
|
||||
<div className="flex items-center justify-between gap-3 mb-3">
|
||||
<div>
|
||||
<div className="text-xs font-semibold text-zinc-900">{session.characterSpec.name}</div>
|
||||
<div className="text-xs text-zinc-500 mt-1 line-clamp-2">{session.characterSpec.oneLiner}</div>
|
||||
<div className="text-sm font-semibold text-white">{session.characterSpec.name}</div>
|
||||
<div className="text-[11px] text-white/55 mt-1 line-clamp-2 max-w-[440px]">{session.characterSpec.oneLiner}</div>
|
||||
</div>
|
||||
<span className="chip bg-white text-zinc-600 border border-zinc-200">CharacterSpec</span>
|
||||
<span className="chip chip-violet shrink-0">CharacterSpec · v1</span>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-2 text-[11px] text-zinc-600">
|
||||
<div>形态:{session.characterSpec.speciesShape}</div>
|
||||
<div>比例:{session.characterSpec.bodyRatio}</div>
|
||||
<div>配色:{session.characterSpec.colorPalette.join('、')}</div>
|
||||
<div>材料:{session.characterSpec.materials.join('、')}</div>
|
||||
<div className="divider-line mb-3" />
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1.5 text-[11px]">
|
||||
<div className="flex gap-2"><span className="text-white/40 w-12">形态</span><span className="text-white/85">{session.characterSpec.speciesShape}</span></div>
|
||||
<div className="flex gap-2"><span className="text-white/40 w-12">比例</span><span className="text-white/85">{session.characterSpec.bodyRatio}</span></div>
|
||||
<div className="flex gap-2"><span className="text-white/40 w-12">配色</span><span className="text-white/85">{session.characterSpec.colorPalette.join('、')}</span></div>
|
||||
<div className="flex gap-2"><span className="text-white/40 w-12">材料</span><span className="text-white/85">{session.characterSpec.materials.join('、')}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{PACK_ORDER.map(kind => {
|
||||
const pack = packs.find(item => item.kind === kind && item.sourceImageId === primaryImage.id);
|
||||
const isLoading = loadingKind === kind;
|
||||
const accent = PACK_ACCENT[kind];
|
||||
return (
|
||||
<div key={kind} className="rounded-2xl border border-zinc-200 p-4 bg-white space-y-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-zinc-900">{PACK_LABELS[kind]}</div>
|
||||
<p className="text-[11px] text-zinc-500 mt-1 leading-relaxed">{PACK_DESCRIPTIONS[kind]}</p>
|
||||
<div key={kind} className={`relative rounded-2xl bg-white/[0.03] border border-white/[0.08] backdrop-blur-xl p-4 space-y-3 ring-1 ${accent.ring}`}>
|
||||
<div className={`absolute top-0 left-4 right-4 h-px bg-gradient-to-r ${accent.bar} opacity-60`} />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-1.5 h-1.5 rounded-full ${accent.dot}`} />
|
||||
<div className="text-sm font-semibold text-white">{PACK_LABELS[kind]}</div>
|
||||
</div>
|
||||
{pack && <span className="text-[10px] text-white/50 font-mono">{pack.version}</span>}
|
||||
</div>
|
||||
<p className="text-[11px] text-white/45 leading-relaxed">{PACK_DESCRIPTIONS[kind]}</p>
|
||||
<button
|
||||
onClick={() => onGenerate(primaryImage, kind)}
|
||||
disabled={!!loadingKind}
|
||||
className={pack ? 'btn btn-outline w-full text-xs' : 'btn btn-primary w-full text-xs'}
|
||||
className={`${pack ? 'btn btn-outline' : 'btn btn-primary'} w-full text-xs disabled:opacity-40 disabled:cursor-not-allowed`}
|
||||
>
|
||||
{isLoading ? '生成中' : pack ? '重新生成' : `生成${PACK_LABELS[kind]}`}
|
||||
{isLoading ? (
|
||||
<>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" className="animate-spin" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 2a10 10 0 0 1 10 10" strokeLinecap="round" />
|
||||
</svg>
|
||||
生成中
|
||||
</>
|
||||
) : pack ? '重新生成' : `生成 ${PACK_LABELS[kind]}`}
|
||||
</button>
|
||||
{pack && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-[11px] text-zinc-500">
|
||||
已生成 <span className="font-semibold text-zinc-900">{pack.assets.length}</span> 张 · {pack.version}
|
||||
<div className="pt-1 space-y-1.5">
|
||||
<div className="text-[10px] text-white/55">
|
||||
已生成 <span className="font-semibold text-white">{pack.assets.length}</span> 张
|
||||
</div>
|
||||
<a
|
||||
href={manifestUrl(session.id, kind, pack.version)}
|
||||
className="text-[11px] font-medium text-zinc-900 underline underline-offset-2"
|
||||
className="inline-flex items-center gap-1 text-[10px] text-violet-300 hover:text-violet-200 transition-colors"
|
||||
>
|
||||
下载 manifest JSON
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 4v12m0 0l-4-4m4 4l4-4M4 20h16" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
下载 manifest
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
@@ -134,48 +188,69 @@ export default function PackPanel({
|
||||
</div>
|
||||
|
||||
{packs.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
{packs.map(pack => (
|
||||
<div key={pack.id} className="space-y-2">
|
||||
<div className="space-y-5">
|
||||
{packs.map(pack => {
|
||||
const accent = PACK_ACCENT[pack.kind];
|
||||
return (
|
||||
<div key={pack.id} className="card-2 p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xs font-semibold text-zinc-900">{PACK_LABELS[pack.kind]} · {pack.assets.length} 张</h3>
|
||||
<code className="text-[10px] text-zinc-400">{pack.id}</code>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-1.5 h-1.5 rounded-full ${accent.dot}`} />
|
||||
<h3 className="text-xs font-semibold text-white">
|
||||
{PACK_LABELS[pack.kind]} <span className="text-white/40 font-normal">· {pack.assets.length} 张</span>
|
||||
</h3>
|
||||
</div>
|
||||
<code className="text-[10px] text-white/30 font-mono">{pack.id}</code>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
{pack.assets.map(asset => (
|
||||
<div key={asset.id} className="rounded-xl overflow-hidden border border-zinc-200 bg-zinc-50">
|
||||
<div className="aspect-square bg-zinc-100">
|
||||
<div key={asset.id} className="group relative rounded-xl overflow-hidden ring-1 ring-white/[0.08] bg-white/[0.02] hover:ring-white/20 transition-all">
|
||||
<div className="aspect-square bg-white/[0.03] overflow-hidden">
|
||||
<img src={asset.url} alt={asset.title} className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<div className="text-[11px] font-medium text-zinc-800 truncate">{asset.title}</div>
|
||||
<div className="text-[10px] text-zinc-400 truncate">{asset.view}</div>
|
||||
<div className="text-[11px] font-medium text-white/90 truncate">{asset.title}</div>
|
||||
<div className="text-[10px] text-white/40 truncate">{asset.view}</div>
|
||||
</div>
|
||||
{asset.required && (
|
||||
<span className="absolute top-1.5 right-1.5 px-1.5 py-0.5 rounded-md text-[9px] font-semibold text-violet-200 bg-violet-500/25 border border-violet-400/30 backdrop-blur">
|
||||
必备
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-2xl border border-zinc-200 p-4 bg-white space-y-3">
|
||||
<div className="card-2 p-4 space-y-3 ring-1 ring-fuchsia-400/20">
|
||||
<div className="absolute top-0 left-4 right-4 h-px bg-gradient-to-r from-fuchsia-400 to-violet-400 opacity-60" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-zinc-900">Seedance 视频</div>
|
||||
<p className="text-[11px] text-zinc-500 mt-1">
|
||||
视频固定走 Seedance。这里先用当前主方案生成标准宣发/展示短片任务。
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-fuchsia-400" />
|
||||
<div className="text-sm font-semibold text-white">Seedance 视频</div>
|
||||
</div>
|
||||
<p className="text-[11px] text-white/45 mt-1">
|
||||
视频固定走 Seedance · 用当前主方案生成宣发/展示短片
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<span className="chip chip-violet">异步任务</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
{VIDEO_TEMPLATES.map(template => (
|
||||
<button
|
||||
key={template.id}
|
||||
onClick={() => onGenerateVideo(primaryImage, template.promptTemplate)}
|
||||
disabled={videoLoading}
|
||||
className="btn btn-outline text-[11px] px-2 py-2"
|
||||
className="btn btn-outline text-[11px] px-3 py-2.5 disabled:opacity-40 disabled:cursor-not-allowed flex-col h-auto items-start gap-0.5"
|
||||
title={template.description}
|
||||
>
|
||||
{videoLoading ? '提交中' : template.title}
|
||||
<span className="text-white/90 font-medium text-left w-full">{template.title}</span>
|
||||
<span className="text-[9px] text-white/40 truncate w-full text-left normal-case font-normal">{template.description}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -38,9 +38,14 @@ export default function PromptPanel({ onGenerate, loading }: PromptPanelProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card p-7 space-y-6">
|
||||
<section className="card p-7 space-y-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="section-eyebrow">Step · 01 · Ideation</span>
|
||||
<span className="text-[10px] text-white/30">· 描述意向 + 数量 + 风格</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2.5">
|
||||
<label className="block text-[11px] font-medium text-white/55 uppercase tracking-[0.14em] mb-2.5">
|
||||
Prompt
|
||||
</label>
|
||||
<textarea
|
||||
@@ -51,29 +56,29 @@ export default function PromptPanel({ onGenerate, loading }: PromptPanelProps) {
|
||||
placeholder="描述要生成的玩具意向…"
|
||||
className="field text-[15px] leading-relaxed"
|
||||
/>
|
||||
<p className="mt-2 text-[11px] text-zinc-400 flex items-center gap-1.5">
|
||||
<p className="mt-2 text-[11px] text-white/35 flex items-center gap-1.5">
|
||||
按 <kbd className="kbd">⌘</kbd><kbd className="kbd">↵</kbd> 提交
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2.5">
|
||||
参考图 <span className="text-zinc-400 normal-case tracking-normal">· 可选,最多 4 张</span>
|
||||
<label className="block text-[11px] font-medium text-white/55 uppercase tracking-[0.14em] mb-2.5">
|
||||
参考图 <span className="text-white/35 normal-case tracking-normal">· 可选,最多 4 张</span>
|
||||
</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-zinc-200 group">
|
||||
<div key={i} className="relative w-20 h-20 rounded-xl 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))}
|
||||
className="absolute top-1 right-1 w-5 h-5 rounded-full bg-zinc-900 text-white text-[10px] opacity-0 group-hover:opacity-100 transition-opacity shadow-md"
|
||||
className="absolute top-1 right-1 w-5 h-5 rounded-full bg-black/80 text-white text-[10px] opacity-0 group-hover:opacity-100 transition-opacity shadow-md"
|
||||
>✕</button>
|
||||
</div>
|
||||
))}
|
||||
{refs.length < 4 && (
|
||||
<button
|
||||
onClick={() => fileInput.current?.click()}
|
||||
className="w-20 h-20 rounded-xl border-2 border-dashed border-zinc-200 hover:border-zinc-400 hover:bg-zinc-50 text-zinc-400 hover:text-zinc-600 text-2xl transition-colors flex items-center justify-center"
|
||||
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"
|
||||
>+</button>
|
||||
)}
|
||||
<input
|
||||
@@ -88,7 +93,7 @@ export default function PromptPanel({ onGenerate, loading }: PromptPanelProps) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2.5">
|
||||
<label className="block text-[11px] font-medium text-white/55 uppercase tracking-[0.14em] mb-2.5">
|
||||
风格
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
@@ -108,7 +113,7 @@ export default function PromptPanel({ onGenerate, loading }: PromptPanelProps) {
|
||||
|
||||
<div className="flex items-end justify-between gap-4 pt-2">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2.5">
|
||||
<label className="block text-[11px] font-medium text-white/55 uppercase tracking-[0.14em] mb-2.5">
|
||||
数量
|
||||
</label>
|
||||
<div className="seg">
|
||||
@@ -143,6 +148,6 @@ export default function PromptPanel({ onGenerate, loading }: PromptPanelProps) {
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,20 +32,21 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-[11px] text-zinc-500">
|
||||
<div className="flex items-center gap-2 text-[11px] text-white/55">
|
||||
<kbd className="kbd">1</kbd>
|
||||
<span>–</span>
|
||||
<span className="text-white/40">–</span>
|
||||
<kbd className="kbd">{Math.min(9, images.length)}</kbd>
|
||||
<span>选中</span>
|
||||
<span className="text-zinc-300 mx-1">/</span>
|
||||
<span className="text-white/20 mx-1">/</span>
|
||||
<kbd className="kbd">⇧</kbd>
|
||||
<span>+</span>
|
||||
<span className="text-white/40">+</span>
|
||||
<kbd className="kbd">1</kbd>
|
||||
<span>打叉</span>
|
||||
</div>
|
||||
<div className="text-xs text-zinc-600">
|
||||
已选 <span className="font-semibold text-zinc-900">{selectedCount}</span>
|
||||
<span className="text-zinc-400"> / {images.length}</span>
|
||||
<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="text-white/30"> / {images.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,16 +66,16 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
|
||||
<div className="tile-badge tile-badge-rejected">✕</div>
|
||||
)}
|
||||
|
||||
<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/70 via-black/30 to-transparent">
|
||||
<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-white text-zinc-900 text-xs font-medium hover:bg-zinc-100 transition-colors shadow-sm"
|
||||
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"
|
||||
>
|
||||
{img.status === 'selected' ? '✓ 已选' : '选中'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAction(img.id, img.status === 'rejected' ? 'reset' : 'reject')}
|
||||
className="px-3 py-1.5 rounded-lg bg-white/90 text-zinc-700 text-xs font-medium hover:bg-white hover:text-red-600 transition-colors shadow-sm"
|
||||
className="px-3 py-1.5 rounded-lg bg-white/[0.10] text-white/85 text-xs font-medium hover:bg-rose-500/60 hover:text-white transition-colors backdrop-blur"
|
||||
>✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,10 +19,10 @@ export default function Sidebar({
|
||||
}) {
|
||||
if (!open) {
|
||||
return (
|
||||
<aside className="w-12 shrink-0 border-r border-zinc-200 bg-white flex flex-col items-center py-4">
|
||||
<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">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="w-8 h-8 rounded-lg hover:bg-zinc-100 flex items-center justify-center text-zinc-500"
|
||||
className="w-8 h-8 rounded-lg hover:bg-white/[0.08] flex items-center justify-center text-white/60 transition-colors"
|
||||
title="展开侧栏"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
@@ -34,35 +34,47 @@ export default function Sidebar({
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="w-72 shrink-0 border-r border-zinc-200 bg-white flex flex-col">
|
||||
<div className="p-4 flex items-center gap-2">
|
||||
<button
|
||||
onClick={onNew}
|
||||
className="btn btn-primary flex-1 text-sm"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
<aside className="w-72 shrink-0 border-r border-white/[0.06] bg-black/30 backdrop-blur-xl 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">
|
||||
<path d="M12 2l2.5 6.5L21 11l-6.5 2.5L12 20l-2.5-6.5L3 11l6.5-2.5z" strokeLinejoin="round" />
|
||||
</svg>
|
||||
新会话
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-[13px] font-semibold tracking-tight">Toy Patent</div>
|
||||
<div className="text-[10px] text-white/40">AI 玩具工作流</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="w-9 h-9 rounded-xl hover:bg-zinc-100 flex items-center justify-center text-zinc-500 transition-colors"
|
||||
className="w-8 h-8 rounded-lg hover:bg-white/[0.08] flex items-center justify-center text-white/60 transition-colors"
|
||||
title="收起侧栏"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-5 pt-1 pb-2 text-[11px] font-medium text-zinc-400 uppercase tracking-wider">
|
||||
最近
|
||||
<div className="px-4 pb-4">
|
||||
<button
|
||||
onClick={onNew}
|
||||
className="btn btn-primary w-full text-sm"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 5v14M5 12h14" strokeLinecap="round" />
|
||||
</svg>
|
||||
新会话
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-5 pt-1 pb-2 text-[10px] font-semibold text-white/35 uppercase tracking-[0.18em]">
|
||||
最近会话
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-3 pb-4 space-y-1">
|
||||
{sessions.length === 0 && (
|
||||
<div className="px-3 py-8 text-center text-xs text-zinc-400">
|
||||
<div className="px-3 py-12 text-center text-xs text-white/30">
|
||||
还没有生成记录
|
||||
</div>
|
||||
)}
|
||||
@@ -75,22 +87,22 @@ export default function Sidebar({
|
||||
onClick={() => onPick(s.id)}
|
||||
className={`w-full text-left px-3 py-2.5 rounded-xl text-xs transition-all ${
|
||||
active
|
||||
? 'bg-zinc-900 text-white shadow-sm'
|
||||
: 'hover:bg-zinc-100 text-zinc-700'
|
||||
? '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]'
|
||||
}`}
|
||||
>
|
||||
<div className={`line-clamp-2 font-medium ${active ? 'text-white' : 'text-zinc-800'}`}>
|
||||
<div className={`line-clamp-2 font-medium leading-relaxed ${active ? 'text-white' : 'text-white/85'}`}>
|
||||
{s.prompt}
|
||||
</div>
|
||||
<div className={`mt-1.5 flex justify-between items-center text-[11px] ${active ? 'text-white/60' : 'text-zinc-500'}`}>
|
||||
<div className={`mt-1.5 flex justify-between items-center text-[10px] ${active ? 'text-white/65' : 'text-white/40'}`}>
|
||||
<span>
|
||||
{new Date(s.createdAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span>{s.images.length}</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span>{s.images.length} 张</span>
|
||||
{selectedCount > 0 && (
|
||||
<span className={active ? 'text-white' : 'text-zinc-900 font-semibold'}>
|
||||
· ✓{selectedCount}
|
||||
<span className={active ? 'text-violet-200 font-semibold' : 'text-violet-300 font-semibold'}>
|
||||
✓{selectedCount}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
@@ -99,6 +111,10 @@ export default function Sidebar({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/[0.06] px-5 py-3 text-[10px] text-white/30">
|
||||
本地端口 4560 · 数据 / data
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,19 @@ const config: Config = {
|
||||
ink: '#0a0a0a',
|
||||
paper: '#fafafa',
|
||||
accent: '#ff6b35',
|
||||
noir: '#0A0A0F',
|
||||
'noir-2': '#11111A',
|
||||
'noir-3': '#171723',
|
||||
},
|
||||
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%)',
|
||||
},
|
||||
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)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user