style: add board light mode

This commit is contained in:
2026-05-18 16:51:34 +08:00
parent cdffc4ba08
commit 78bd294d57
4 changed files with 188 additions and 16 deletions

View File

@@ -4,7 +4,7 @@ import { type ReactNode, type RefObject, useEffect, useMemo, useRef, useState }
import { createPortal } from "react-dom"
import {
AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Info, Link2, Loader2,
Mic, Package, PanelRight, Play, Plus, RefreshCw, Scissors, Sparkles, Trash2, Upload, Wand2,
Mic, Moon, Package, PanelRight, Play, Plus, RefreshCw, Scissors, Sparkles, Sun, Trash2, Upload, Wand2,
} from "lucide-react"
import { toast } from "sonner"
import {
@@ -78,6 +78,9 @@ const VIDEO_MODELS = [
] as const
type VideoModel = (typeof VIDEO_MODELS)[number]["value"]
type BoardThemeMode = "dark" | "light"
const BOARD_THEME_STORAGE_KEY = "skg-board-theme"
type DraftSegment = {
id: string
@@ -1268,6 +1271,7 @@ export function AdRecreationBoard({
const [sixViewBusyKey, setSixViewBusyKey] = useState<string | null>(null)
const [generatingAll, setGeneratingAll] = useState(false)
const [runtimeModels, setRuntimeModels] = useState<RuntimeModels | undefined>()
const [boardTheme, setBoardTheme] = useState<BoardThemeMode>("dark")
const fileRef = useRef<HTMLInputElement | null>(null)
const selectedFrames = job
? job.frames.filter((frame) => data.selectedFrames.has(frame.index)).sort((a, b) => a.timestamp - b.timestamp)
@@ -1307,6 +1311,15 @@ export function AdRecreationBoard({
setSelectedVideoIds(new Set())
}, [activeJobId])
useEffect(() => {
try {
const saved = window.localStorage.getItem(BOARD_THEME_STORAGE_KEY)
if (saved === "light" || saved === "dark") setBoardTheme(saved)
} catch {
// Ignore storage failures; dark mode remains the product default.
}
}, [])
useEffect(() => {
let cancelled = false
getRuntimeHealth()
@@ -1334,6 +1347,18 @@ export function AdRecreationBoard({
if (trimmed) setUrl("")
}
const toggleBoardTheme = () => {
setBoardTheme((current) => {
const next: BoardThemeMode = current === "dark" ? "light" : "dark"
try {
window.localStorage.setItem(BOARD_THEME_STORAGE_KEY, next)
} catch {
// Ignore storage failures; the in-memory theme still switches.
}
return next
})
}
const selectAllFrames = () => {
if (!job) return
for (const frame of job.frames) {
@@ -1477,7 +1502,7 @@ export function AdRecreationBoard({
}
return (
<section className="skg-board-theme relative z-20 h-screen w-screen overflow-hidden bg-black text-white">
<section className={`skg-board-theme ${boardTheme === "light" ? "skg-board-theme--light" : ""} relative z-20 h-screen w-screen overflow-hidden bg-black text-white`}>
<div className="skg-board-ambient pointer-events-none absolute inset-0" />
<div className="relative z-10 flex h-full flex-col px-4 py-4">
<header className="skg-board-topbar mb-3 flex items-center justify-between gap-4 rounded-lg border border-white/10 bg-white/[0.04] px-4 py-3">
@@ -1485,12 +1510,23 @@ export function AdRecreationBoard({
<div className="text-[11px] font-medium uppercase tracking-[0.18em] text-white/40">feed ad recreation worksheet</div>
<h1 className="mt-1 text-[22px] font-semibold leading-tight text-white">广</h1>
</div>
<div className="grid min-w-[520px] grid-cols-5 gap-2 text-[11px] text-white/48">
<Metric label="素材" value={`${jobs.length}`} />
<Metric label="当前" value={shortId(activeJobId)} />
<Metric label="视频" value={job?.video_url ? "ready" : "-"} />
<Metric label="文案段" value={`${transcriptCount}`} />
<Metric label="背景音" value={backgroundReady ? "ready" : "-"} />
<div className="flex shrink-0 items-center gap-2">
<button
type="button"
onClick={toggleBoardTheme}
className="skg-board-theme-toggle inline-flex h-10 items-center gap-1.5 rounded-md border border-white/10 bg-black/24 px-3 text-[11px] font-semibold text-white/62 transition hover:border-[#d6b36a]/45 hover:text-white"
title={boardTheme === "dark" ? "切换到明亮模式" : "切换到暗色模式"}
>
{boardTheme === "dark" ? <Sun className="h-3.5 w-3.5" /> : <Moon className="h-3.5 w-3.5" />}
{boardTheme === "dark" ? "明亮" : "暗色"}
</button>
<div className="grid min-w-[520px] grid-cols-5 gap-2 text-[11px] text-white/48">
<Metric label="素材" value={`${jobs.length}`} />
<Metric label="当前" value={shortId(activeJobId)} />
<Metric label="视频" value={job?.video_url ? "ready" : "-"} />
<Metric label="文案段" value={`${transcriptCount}`} />
<Metric label="背景音" value={backgroundReady ? "ready" : "-"} />
</div>
</div>
</header>