diff --git a/messages/en.json b/messages/en.json index e626422..1eede62 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1148,8 +1148,13 @@ "description": "Generate voiceovers from your script directly in the studio." }, "componentsStudioSidebarColorsCustomTab": { - "mainColor": "Main Color", - "additionalColor": "Additional Color", + "mainColor": "Background", + "additionalColor": "Accent", + "secondaryColor": "Secondary", + "textColor": "Text", + "themePresets": "Themes", + "applyThemePreset": "Apply {name} theme", + "applyTheme": "Apply theme", "applyToAllScenes": "Apply to all scenes" }, "componentsStudioSidebarColorsPalettesTab": { diff --git a/messages/fa.json b/messages/fa.json index 401b609..0abf193 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -1148,8 +1148,13 @@ "description": "صداگذاری را مستقیماً از روی متن خود در استودیو بسازید." }, "componentsStudioSidebarColorsCustomTab": { - "mainColor": "رنگ اصلی", - "additionalColor": "رنگ مکمل", + "mainColor": "پس‌زمینه", + "additionalColor": "رنگ اصلی", + "secondaryColor": "رنگ دوم", + "textColor": "رنگ متن", + "themePresets": "تم‌ها", + "applyThemePreset": "اعمال تم {name}", + "applyTheme": "اعمال تم", "applyToAllScenes": "اعمال به همه صحنه‌ها" }, "componentsStudioSidebarColorsPalettesTab": { diff --git a/src/components/studio/sidebar/ColorsCustomTab.tsx b/src/components/studio/sidebar/ColorsCustomTab.tsx index cfa7386..6ea67e0 100644 --- a/src/components/studio/sidebar/ColorsCustomTab.tsx +++ b/src/components/studio/sidebar/ColorsCustomTab.tsx @@ -4,63 +4,91 @@ import { Button } from "@/components/ui/button"; import { useStudioStore } from "@/lib/studio-store"; import { useTranslations } from "next-intl"; +/** Curated brand themes (accent / secondary / background / text) — the cohesion + * lever: one click re-skins the whole project. Mirrors the Remotion theme set. */ +const THEME_PRESETS: { id: string; colors: [string, string, string, string] }[] = [ + { id: "warm", colors: ["#cf8a76", "#6f9d96", "#ece4d6", "#2b3a55"] }, + { id: "berry", colors: ["#d6477e", "#8b5cf6", "#f3ebf0", "#3b2440"] }, + { id: "midnight", colors: ["#7c93ff", "#22d3ee", "#11141f", "#eef1f8"] }, + { id: "ocean", colors: ["#2a9d8f", "#4895ef", "#eaf4f4", "#1d3557"] }, + { id: "sunset", colors: ["#f4795b", "#f9c74f", "#fff4e6", "#4a2c2a"] }, + { id: "mono", colors: ["#b08d57", "#8a8a8a", "#f5f3ee", "#1a1a1a"] }, +]; + export function ColorsCustomTab() { const t = useTranslations("auto.componentsStudioSidebarColorsCustomTab"); - const sceneBackgroundColor = useStudioStore( - (state) => state.sceneBackgroundColor - ); - const sceneAccentColor = useStudioStore((state) => state.sceneAccentColor); - const setSceneBackgroundColor = useStudioStore( - (state) => state.setSceneBackgroundColor - ); - const setSceneAccentColor = useStudioStore((state) => state.setSceneAccentColor); - const applyPaletteToAllScenes = useStudioStore( - (state) => state.applyPaletteToAllScenes + const sceneBackgroundColor = useStudioStore((s) => s.sceneBackgroundColor); + const sceneAccentColor = useStudioStore((s) => s.sceneAccentColor); + const sceneSecondaryColor = useStudioStore((s) => s.sceneSecondaryColor); + const sceneTextColor = useStudioStore((s) => s.sceneTextColor); + const setSceneBackgroundColor = useStudioStore((s) => s.setSceneBackgroundColor); + const setSceneAccentColor = useStudioStore((s) => s.setSceneAccentColor); + const setSceneSecondaryColor = useStudioStore((s) => s.setSceneSecondaryColor); + const setSceneTextColor = useStudioStore((s) => s.setSceneTextColor); + const applySceneTheme = useStudioStore((s) => s.applySceneTheme); + + const swatch = ( + id: string, + label: string, + value: string, + onChange: (c: string) => void + ) => ( +
+ + onChange(e.target.value)} + className="h-8 w-12 cursor-pointer rounded border border-gray-200 bg-white p-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500" + /> +
); return (
-
- - setSceneBackgroundColor(event.target.value)} - className="h-8 w-12 cursor-pointer rounded border border-gray-200 bg-white p-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500" - /> + {/* Preset themes — one-click cohesive re-skin */} +
+

{t("themePresets")}

+
+ {THEME_PRESETS.map((preset) => ( + + ))} +
-
- - setSceneAccentColor(event.target.value)} - className="h-8 w-12 cursor-pointer rounded border border-gray-200 bg-white p-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500" - /> -
+ {/* Manual 4-color tweak */} + {swatch("colors-bg", t("mainColor"), sceneBackgroundColor, setSceneBackgroundColor)} + {swatch("colors-accent", t("additionalColor"), sceneAccentColor, setSceneAccentColor)} + {swatch("colors-secondary", t("secondaryColor"), sceneSecondaryColor, setSceneSecondaryColor)} + {swatch("colors-text", t("textColor"), sceneTextColor, setSceneTextColor)}
); diff --git a/src/lib/studio-scene-data.ts b/src/lib/studio-scene-data.ts index 5deca1f..1774d61 100644 --- a/src/lib/studio-scene-data.ts +++ b/src/lib/studio-scene-data.ts @@ -172,6 +172,8 @@ export interface VideoPersistedSceneData { audioVolume?: number; sceneBackgroundColor?: string; sceneAccentColor?: string; + sceneSecondaryColor?: string; + sceneTextColor?: string; /** Project render mode (FIX / FLEXIBLE / MusicVisualizer / …). FIX/MusicVisualizer * scenes come from AE layer names, so adding scenes is disabled. */ chooseMode?: string; @@ -190,6 +192,8 @@ export function buildVideoSceneDataPayload( audioVolume: input.audioVolume, sceneBackgroundColor: input.sceneBackgroundColor, sceneAccentColor: input.sceneAccentColor, + sceneSecondaryColor: input.sceneSecondaryColor, + sceneTextColor: input.sceneTextColor, }; } @@ -225,6 +229,14 @@ export function parseVideoSceneData( typeof sceneData.sceneAccentColor === "string" ? sceneData.sceneAccentColor : DEFAULT_SCENE_ACCENT_COLOR, + sceneSecondaryColor: + typeof sceneData.sceneSecondaryColor === "string" + ? sceneData.sceneSecondaryColor + : DEFAULT_SCENE_ACCENT_COLOR, + sceneTextColor: + typeof sceneData.sceneTextColor === "string" + ? sceneData.sceneTextColor + : "#111827", chooseMode: typeof sceneData.chooseMode === "string" ? sceneData.chooseMode diff --git a/src/lib/studio-store.ts b/src/lib/studio-store.ts index b6df7c1..c4f941b 100644 --- a/src/lib/studio-store.ts +++ b/src/lib/studio-store.ts @@ -159,6 +159,10 @@ export interface StudioState { audioVolume: number; sceneBackgroundColor: string; sceneAccentColor: string; + /** The 4-color brand theme (matches Remotion SceneColors; secondary+text added + * for the scene-engine theme picker). */ + sceneSecondaryColor: string; + sceneTextColor: string; /** Project render mode (FIX / FLEXIBLE / MusicVisualizer / …). Empty until hydrated. */ chooseMode: string; past: StudioHistorySnapshot[]; @@ -197,7 +201,12 @@ export interface StudioActions { setAudioVolume: (volume: number) => void; setSceneBackgroundColor: (color: string) => void; setSceneAccentColor: (color: string) => void; + setSceneSecondaryColor: (color: string) => void; + setSceneTextColor: (color: string) => void; applyPaletteToAllScenes: (mainColor: string, accentColor: string) => void; + /** Apply a full 4-color brand theme (accent/secondary/background/text) to the + * project + recolor canvas layers. */ + applySceneTheme: (accent: string, secondary: string, background: string, text: string) => void; applyTransitionToAllScenes: (transitionType: SceneTransition) => void; applyFontFamilyToAllTextLayers: (fontFamily: string) => void; hydrateFromSceneData: (sceneData: Record) => boolean; @@ -251,6 +260,8 @@ export const useStudioStore = create((set, get) => { audioVolume: 100, sceneBackgroundColor: DEFAULT_SCENE_BACKGROUND_COLOR, sceneAccentColor: DEFAULT_SCENE_ACCENT_COLOR, + sceneSecondaryColor: DEFAULT_SCENE_ACCENT_COLOR, + sceneTextColor: "#111827", chooseMode: "", past: [], future: [], @@ -638,6 +649,10 @@ export const useStudioStore = create((set, get) => { setSceneAccentColor: (color) => set({ sceneAccentColor: color }), + setSceneSecondaryColor: (color) => set({ sceneSecondaryColor: color }), + + setSceneTextColor: (color) => set({ sceneTextColor: color }), + applyPaletteToAllScenes: (mainColor, accentColor) => { const state = get(); @@ -712,6 +727,51 @@ export const useStudioStore = create((set, get) => { scheduleActiveSceneThumbnailUpdate(); }, + applySceneTheme: (accent, secondary, background, text) => { + const state = get(); + // Same canvas-shape heuristic as applyPaletteToAllScenes, but with an explicit + // text color (not auto-contrast) and a secondary accent for overlay shapes. + const isCanvasBg = (layer: Layer) => + layer.type === "shape" && + layer.zIndex === 0 && + layer.x <= 10 && + layer.y <= 10 && + layer.width >= 1200 && + layer.height >= 600; + const isOverlay = (layer: Layer) => + layer.type === "shape" && layer.zIndex >= 2 && layer.opacity <= 0.65; + + const nextScenes = state.scenes.map((scene) => ({ + ...scene, + layers: scene.layers.map((layer) => { + if (isCanvasBg(layer)) { + return { ...layer, props: { ...layer.props, fill: background, stroke: background } }; + } + if (isOverlay(layer)) { + return { ...layer, props: { ...layer.props, fill: secondary, stroke: secondary } }; + } + if (layer.type === "shape") { + return { ...layer, props: { ...layer.props, fill: accent, stroke: accent } }; + } + if (layer.type === "text") { + return { ...layer, props: { ...layer.props, fill: text } }; + } + return layer; + }), + })); + + set( + pushHistory(state, { + scenes: nextScenes, + sceneAccentColor: accent, + sceneSecondaryColor: secondary, + sceneBackgroundColor: background, + sceneTextColor: text, + }) + ); + scheduleActiveSceneThumbnailUpdate(); + }, + applyTransitionToAllScenes: (transitionType) => { set({ scenes: get().scenes.map((scene) => ({ ...scene, transitionType })), @@ -760,6 +820,9 @@ export const useStudioStore = create((set, get) => { parsed.sceneBackgroundColor ?? DEFAULT_SCENE_BACKGROUND_COLOR, sceneAccentColor: parsed.sceneAccentColor ?? DEFAULT_SCENE_ACCENT_COLOR, + sceneSecondaryColor: + parsed.sceneSecondaryColor ?? DEFAULT_SCENE_ACCENT_COLOR, + sceneTextColor: parsed.sceneTextColor ?? "#111827", chooseMode: parsed.chooseMode ?? "", past: [], future: [], @@ -779,6 +842,8 @@ export const useStudioStore = create((set, get) => { audioVolume: state.audioVolume, sceneBackgroundColor: state.sceneBackgroundColor, sceneAccentColor: state.sceneAccentColor, + sceneSecondaryColor: state.sceneSecondaryColor, + sceneTextColor: state.sceneTextColor, }); }, };