diff --git a/apps/web/src/components/editor/media-panel/views/settings.tsx b/apps/web/src/components/editor/media-panel/views/settings.tsx
index 6cdd1b65..d2d21f36 100644
--- a/apps/web/src/components/editor/media-panel/views/settings.tsx
+++ b/apps/web/src/components/editor/media-panel/views/settings.tsx
@@ -178,9 +178,9 @@ const BackgroundPreviews = memo(
}) => {
return useMemo(
() =>
- backgrounds.map((bg) => (
+ backgrounds.map((bg, index) => (
{
+ if (!p) return (full || 0) / 2;
+ if (p.endsWith("%")) return (parseFloat(p) / 100) * (full || 0);
+ if (p === "left") return 0;
+ if (p === "right") return full || 0;
+ if (p === "top") return 0;
+ if (p === "bottom") return full || 0;
+ if (p === "center") return (full || 0) / 2;
+ return (full || 0) / 2;
+ };
+ if (px && py) {
+ cx = parsePos(px, width);
+ cy = parsePos(py, height);
+ } else if (px) {
+ cx = parsePos(px, width);
+ cy = parsePos(px, height);
+ }
+ } else {
+ parts.unshift(first);
+ }
+ const r = Math.hypot(width, height);
+ const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
+ const colorStops = parts;
+ const n = Math.max(1, colorStops.length - 1);
+ for (let i = 0; i < colorStops.length; i += 1) {
+ const color = extractColorFromStop(colorStops[i] as string);
+ grad.addColorStop(Math.min(1, Math.max(0, i / n)), color);
+ }
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, width, height);
+}
+
+export function drawCssBackground(
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ css: string
+): void {
+ const layers = splitBackgroundLayers(css).filter(Boolean);
+ for (let i = layers.length - 1; i >= 0; i -= 1) {
+ const layer = layers[i] as string;
+ if (layer.startsWith("linear-gradient(")) {
+ drawLinearGradient(ctx, width, height, layer);
+ } else if (layer.startsWith("radial-gradient(")) {
+ drawRadialGradient(ctx, width, height, layer);
+ } else if (
+ layer.startsWith("#") ||
+ layer.startsWith("rgb") ||
+ layer.startsWith("hsl")
+ ) {
+ ctx.fillStyle = layer;
+ ctx.fillRect(0, 0, width, height);
+ }
+ }
+}
diff --git a/apps/web/src/lib/timeline-renderer.ts b/apps/web/src/lib/timeline-renderer.ts
index ddc361b5..4d00129c 100644
--- a/apps/web/src/lib/timeline-renderer.ts
+++ b/apps/web/src/lib/timeline-renderer.ts
@@ -2,6 +2,7 @@ import type { TimelineTrack } from "@/types/timeline";
import type { MediaFile } from "@/types/media";
import type { BlurIntensity } from "@/types/project";
import { videoCache } from "./video-cache";
+import { drawCssBackground } from "./canvas-gradients";
export interface RenderContext {
ctx: CanvasRenderingContext2D;
@@ -48,11 +49,24 @@ export async function renderTimelineFrame({
}: RenderContext): Promise {
// Background
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
- if (backgroundColor && backgroundColor !== "transparent") {
+ if (
+ backgroundColor &&
+ backgroundColor !== "transparent" &&
+ !backgroundColor.includes("gradient")
+ ) {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
+ // If backgroundColor is a CSS gradient string, draw it using JS (no foreignObject) to avoid CORS tainting
+ if (backgroundColor && backgroundColor.includes("gradient")) {
+ try {
+ drawCssBackground(ctx, canvasWidth, canvasHeight, backgroundColor);
+ } catch {
+ // best-effort; ignore failures
+ }
+ }
+
const scaleX = projectCanvasSize ? canvasWidth / projectCanvasSize.width : 1;
const scaleY = projectCanvasSize
? canvasHeight / projectCanvasSize.height