feat: full studio build -- light theme, canvas thumbnails, i18n (fa/en)
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import type { Scene } from "@/lib/studio-types";
|
||||
|
||||
export const TIMELINE_ZOOM_LEVELS = [30, 60, 90, 120] as const;
|
||||
export type TimelineZoomLevel = (typeof TIMELINE_ZOOM_LEVELS)[number];
|
||||
|
||||
export const DEFAULT_PX_PER_SECOND: TimelineZoomLevel = 60;
|
||||
|
||||
/** Compact scale for scene thumbnail strip (Renderforest-style) */
|
||||
export const STRIP_PX_PER_SECOND = 24;
|
||||
|
||||
export const MIN_SCENE_DURATION = 1;
|
||||
export const MAX_SCENE_DURATION = 30;
|
||||
|
||||
export const SCENE_BLOCK_COLORS = [
|
||||
{ base: "bg-blue-600", active: "bg-blue-500" },
|
||||
{ base: "bg-purple-600", active: "bg-purple-500" },
|
||||
{ base: "bg-green-600", active: "bg-green-500" },
|
||||
{ base: "bg-orange-600", active: "bg-orange-500" },
|
||||
] as const;
|
||||
|
||||
/** Inline gradient styles for scene thumbnails — avoids Tailwind purging dynamic class names */
|
||||
export const SCENE_THUMB_GRADIENTS = [
|
||||
{ backgroundImage: "linear-gradient(135deg,#60a5fa,#8b5cf6)" },
|
||||
{ backgroundImage: "linear-gradient(135deg,#a78bfa,#ec4899)" },
|
||||
{ backgroundImage: "linear-gradient(135deg,#22d3ee,#3b82f6)" },
|
||||
{ backgroundImage: "linear-gradient(135deg,#34d399,#14b8a6)" },
|
||||
{ backgroundImage: "linear-gradient(135deg,#fbbf24,#f97316)" },
|
||||
{ backgroundImage: "linear-gradient(135deg,#fb7185,#ef4444)" },
|
||||
] as const;
|
||||
|
||||
export function formatTimelineTime(seconds: number): string {
|
||||
const safe = Math.max(0, seconds);
|
||||
const mins = Math.floor(safe / 60);
|
||||
const secs = Math.floor(safe % 60);
|
||||
return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function getProjectDuration(scenes: Scene[]): number {
|
||||
return scenes.reduce((total, scene) => total + scene.duration, 0);
|
||||
}
|
||||
|
||||
export interface SceneTimelineSegment {
|
||||
scene: Scene;
|
||||
startTime: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export function getSceneTimelineSegments(
|
||||
scenes: Scene[]
|
||||
): SceneTimelineSegment[] {
|
||||
let startTime = 0;
|
||||
return scenes.map((scene, index) => {
|
||||
const segment = { scene, startTime, index };
|
||||
startTime += scene.duration;
|
||||
return segment;
|
||||
});
|
||||
}
|
||||
|
||||
export function getSceneAtTime(scenes: Scene[], time: number): Scene | undefined {
|
||||
const segments = getSceneTimelineSegments(scenes);
|
||||
if (segments.length === 0) return undefined;
|
||||
|
||||
const total = getProjectDuration(scenes);
|
||||
if (time >= total) {
|
||||
return segments[segments.length - 1]?.scene;
|
||||
}
|
||||
|
||||
return segments.find(
|
||||
(segment) =>
|
||||
time >= segment.startTime &&
|
||||
time < segment.startTime + segment.scene.duration
|
||||
)?.scene;
|
||||
}
|
||||
|
||||
export function getSceneStartTime(scenes: Scene[], sceneId: string): number {
|
||||
let start = 0;
|
||||
for (const scene of scenes) {
|
||||
if (scene.id === sceneId) return start;
|
||||
start += scene.duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function clampSceneDuration(duration: number): number {
|
||||
return Math.min(
|
||||
MAX_SCENE_DURATION,
|
||||
Math.max(MIN_SCENE_DURATION, Math.round(duration * 10) / 10)
|
||||
);
|
||||
}
|
||||
|
||||
export function getNextZoomLevel(
|
||||
current: number,
|
||||
direction: "in" | "out"
|
||||
): TimelineZoomLevel {
|
||||
const index = TIMELINE_ZOOM_LEVELS.findIndex((level) => level === current);
|
||||
const resolvedIndex = index === -1 ? 1 : index;
|
||||
|
||||
if (direction === "in") {
|
||||
return TIMELINE_ZOOM_LEVELS[
|
||||
Math.min(resolvedIndex + 1, TIMELINE_ZOOM_LEVELS.length - 1)
|
||||
];
|
||||
}
|
||||
|
||||
return TIMELINE_ZOOM_LEVELS[Math.max(resolvedIndex - 1, 0)];
|
||||
}
|
||||
|
||||
export function snapZoomLevel(value: number): TimelineZoomLevel {
|
||||
const snapped = TIMELINE_ZOOM_LEVELS.reduce((prev, level) =>
|
||||
Math.abs(level - value) < Math.abs(prev - value) ? level : prev
|
||||
);
|
||||
return snapped;
|
||||
}
|
||||
Reference in New Issue
Block a user