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,
});
},
};