> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bfl.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# Video Editing

> How to word an edit for FLUX Video Edit [fast]: say what changes, place what you add, fit dialogue to the clip, and turn blockouts, settings and styles into finished shots.

export const PromptDisplay = ({prompt}) => {
  const [copied, setCopied] = useState(false);
  const copy = () => {
    navigator.clipboard.writeText(prompt);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
  return <div className="not-prose" style={{
    marginTop: "1rem"
  }}>
      <div style={{
    backgroundColor: "#1a1a1a",
    borderRadius: "1rem",
    padding: "1.25rem 1.5rem",
    display: "flex",
    flexDirection: "column",
    gap: "1rem"
  }}>
        <p style={{
    color: "#e5e5e5",
    fontSize: "1rem",
    lineHeight: 1.6,
    margin: 0,
    fontFamily: "inherit"
  }}>
          {prompt}
        </p>
        <div style={{
    display: "flex",
    justifyContent: "flex-end"
  }}>
          <button onClick={copy} style={{
    backgroundColor: copied ? "#3d8a5b" : "var(--aspen-evergreen, #486A58)",
    color: "#fff",
    border: "none",
    borderRadius: "0.375rem",
    padding: "0.25rem 0.6rem",
    fontSize: "0.7rem",
    fontWeight: 600,
    cursor: "pointer",
    transition: "background-color 0.2s"
  }}>
            {copied ? "Copied!" : "Copy prompt"}
          </button>
        </div>
      </div>
    </div>;
};

export const VideoComparisonSlider = ({beforeVideo, afterVideo, beforeLabel = "Before", afterLabel = "After", height = "500px", objectFit = "cover", poster}) => {
  const makeLogic = () => {
    const STEP = 2;
    const PAGE_STEP = 10;
    const clampPercent = value => Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
    const percentFromX = (clientX, rect) => {
      if (!rect || !rect.width || !Number.isFinite(clientX)) return 0;
      return clampPercent((clientX - rect.left) / rect.width * 100);
    };
    const keyPosition = (key, current) => {
      const at = clampPercent(current);
      if (key === "ArrowLeft" || key === "ArrowDown") return clampPercent(at - STEP);
      if (key === "ArrowRight" || key === "ArrowUp") return clampPercent(at + STEP);
      if (key === "PageDown") return clampPercent(at - PAGE_STEP);
      if (key === "PageUp") return clampPercent(at + PAGE_STEP);
      if (key === "Home") return 0;
      if (key === "End") return 100;
      return null;
    };
    const valueText = (position, before, after) => {
      const left = Math.round(clampPercent(position));
      return `${left}% ${before}, ${100 - left}% ${after}`;
    };
    return {
      clampPercent,
      percentFromX,
      keyPosition,
      valueText
    };
  };
  const L = makeLogic();
  const [position, setPosition] = useState(50);
  const [playing, setPlaying] = useState(true);
  const beforeRef = useRef(null);
  const afterRef = useRef(null);
  const userPausedRef = useRef(false);
  const lastAutoResumeRef = useRef(0);
  const DRIFT_S = 0.04;
  const SEEK_S = 0.5;
  const readPosition = (e, container) => {
    const rect = container.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    return L.percentFromX(clientX, rect);
  };
  const onPointerDown = e => {
    e.preventDefault();
    e.stopPropagation();
    const container = e.currentTarget;
    if (typeof container.focus === "function") container.focus();
    setPosition(readPosition(e, container));
    const onMove = ev => {
      ev.preventDefault();
      setPosition(readPosition(ev, container));
    };
    const onUp = () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("touchmove", onMove);
      window.removeEventListener("touchend", onUp);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    window.addEventListener("touchmove", onMove, {
      passive: false
    });
    window.addEventListener("touchend", onUp);
  };
  const onKeyDown = e => {
    const next = L.keyPosition(e.key, position);
    if (next === null) return;
    e.preventDefault();
    e.stopPropagation();
    setPosition(next);
  };
  const loopPoint = () => {
    const a = beforeRef.current;
    const b = afterRef.current;
    if (!a || !b) return 0;
    const da = Number.isFinite(a.duration) ? a.duration : 0;
    const db = Number.isFinite(b.duration) ? b.duration : 0;
    if (!da || !db) return da || db;
    return Math.min(da, db);
  };
  const resync = force => {
    const a = beforeRef.current;
    const b = afterRef.current;
    if (!a || !b) return;
    if (b.readyState < 1) return;
    const delta = b.currentTime - a.currentTime;
    if (force || Math.abs(delta) > SEEK_S) {
      try {
        b.currentTime = a.currentTime;
      } catch (e) {}
      b.playbackRate = 1;
    } else if (Math.abs(delta) > DRIFT_S) {
      const rate = 1 - Math.max(-0.08, Math.min(0.08, delta * 0.5));
      b.playbackRate = rate;
    } else {
      b.playbackRate = 1;
    }
    if (a.paused && !b.paused) b.pause();
    if (!a.paused && b.paused) {
      const p = b.play?.();
      if (p && typeof p.catch === "function") p.catch(() => {});
    }
  };
  const restart = () => {
    if (userPausedRef.current) return;
    const a = beforeRef.current;
    const b = afterRef.current;
    if (!a) return;
    try {
      a.currentTime = 0;
    } catch (e) {}
    if (b) {
      try {
        b.currentTime = 0;
      } catch (e) {}
    }
    const p = a.play?.();
    if (p && typeof p.catch === "function") p.catch(() => {});
    if (b) {
      const q = b.play?.();
      if (q && typeof q.catch === "function") q.catch(() => {});
    }
  };
  const setBeforeRef = el => {
    beforeRef.current = el;
    if (!el || el.dataset.vcsWired === "1") return;
    el.dataset.vcsWired = "1";
    el.addEventListener("timeupdate", () => {
      const end = loopPoint();
      if (end && el.currentTime >= end - 0.12) {
        restart();
        return;
      }
      resync(false);
    });
    el.addEventListener("ended", restart);
    el.addEventListener("seeked", () => resync(true));
    el.addEventListener("play", () => {
      if (userPausedRef.current) {
        el.pause();
        return;
      }
      setPlaying(true);
      resync(true);
    });
    el.addEventListener("pause", () => {
      const rect = el.getBoundingClientRect();
      const inView = rect.bottom > 0 && rect.top < window.innerHeight && rect.right > 0 && rect.left < window.innerWidth;
      const now = Date.now();
      if (!userPausedRef.current && !el.ended && !document.hidden && inView && now - lastAutoResumeRef.current > 250) {
        lastAutoResumeRef.current = now;
        const p = el.play?.();
        if (p && typeof p.catch === "function") {
          p.catch(() => {
            setPlaying(false);
            resync(true);
          });
        }
        return;
      }
      setPlaying(false);
      resync(true);
    });
    el.addEventListener("loadeddata", () => resync(true));
  };
  const setAfterRef = el => {
    afterRef.current = el;
    if (!el || el.dataset.vcsWired === "1") return;
    el.dataset.vcsWired = "1";
    el.addEventListener("play", () => {
      if (userPausedRef.current) el.pause();
    });
    el.addEventListener("loadeddata", () => resync(true));
  };
  const togglePlay = e => {
    e.preventDefault();
    e.stopPropagation();
    const a = beforeRef.current;
    if (!a) return;
    if (!playing) {
      userPausedRef.current = false;
      const p = a.play?.();
      if (p && typeof p.catch === "function") p.catch(() => {});
    } else {
      userPausedRef.current = true;
      setPlaying(false);
      a.pause();
    }
    resync(true);
  };
  const videoStyle = {
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%",
    objectFit,
    pointerEvents: "none"
  };
  const labelStyle = {
    position: "absolute",
    top: "12px",
    padding: "4px 10px",
    borderRadius: "6px",
    background: "rgba(0,0,0,0.55)",
    backdropFilter: "blur(4px)",
    color: "#fff",
    fontSize: "0.75rem",
    fontWeight: 600,
    letterSpacing: "0.02em",
    pointerEvents: "none",
    zIndex: 4
  };
  return <div className="not-prose" style={{
    position: "relative",
    borderRadius: "1rem",
    overflow: "hidden",
    height,
    width: "100%"
  }}>
      <div role="slider" tabIndex={0} aria-label={`Comparison position between ${beforeLabel} and ${afterLabel}`} aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(position)} aria-valuetext={L.valueText(position, beforeLabel, afterLabel)} onMouseDown={onPointerDown} onTouchStart={onPointerDown} onKeyDown={onKeyDown} onClick={e => {
    e.preventDefault();
    e.stopPropagation();
  }} style={{
    position: "relative",
    width: "100%",
    height,
    overflow: "hidden",
    cursor: "ew-resize",
    userSelect: "none",
    WebkitUserSelect: "none",
    background: "#000",
    outlineOffset: "-3px"
  }}>
        {}
        <video ref={setAfterRef} src={afterVideo} poster={poster} autoPlay muted playsInline preload="auto" aria-label={afterLabel} style={videoStyle} />

        {}
        <div style={{
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%",
    clipPath: `inset(0 ${100 - position}% 0 0)`
  }}>
          <video ref={setBeforeRef} src={beforeVideo} poster={poster} autoPlay muted playsInline preload="auto" aria-label={beforeLabel} style={videoStyle} />
        </div>

        {}
        <div style={{
    position: "absolute",
    top: 0,
    left: `${position}%`,
    transform: "translateX(-50%)",
    width: "3px",
    height: "100%",
    background: "rgba(255,255,255,0.85)",
    pointerEvents: "none",
    zIndex: 2
  }} />

        {}
        <div style={{
    position: "absolute",
    top: "50%",
    left: `${position}%`,
    transform: "translate(-50%, -50%)",
    width: "44px",
    height: "44px",
    borderRadius: "50%",
    background: "rgba(255,255,255,0.95)",
    border: "2px solid rgba(0,0,0,0.15)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "6px",
    zIndex: 3,
    pointerEvents: "none",
    boxShadow: "0 2px 8px rgba(0,0,0,0.3)"
  }}>
          <svg width="10" height="14" viewBox="0 0 10 14" fill="none" style={{
    marginRight: "-2px"
  }}>
            <path d="M8 1L2 7L8 13" stroke="#333" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          <svg width="10" height="14" viewBox="0 0 10 14" fill="none" style={{
    marginLeft: "-2px"
  }}>
            <path d="M2 1L8 7L2 13" stroke="#333" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </div>

        {}
        <div style={{
    ...labelStyle,
    left: "12px"
  }}>{beforeLabel}</div>
        <div style={{
    ...labelStyle,
    right: "12px"
  }}>{afterLabel}</div>
      </div>

      {}
      <button type="button" onMouseDown={e => e.stopPropagation()} onTouchStart={e => e.stopPropagation()} onClick={togglePlay} aria-label={playing ? "Pause both clips" : "Play both clips"} title={playing ? "Pause" : "Play"} style={{
    position: "absolute",
    bottom: "12px",
    left: "12px",
    width: "36px",
    height: "36px",
    padding: 0,
    border: "none",
    borderRadius: "50%",
    background: "rgba(0,0,0,0.55)",
    backdropFilter: "blur(4px)",
    color: "#fff",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    cursor: "pointer",
    zIndex: 5
  }}>
        {playing ? <svg width="12" height="14" viewBox="0 0 12 14" fill="none" aria-hidden="true">
            <rect x="1" y="1" width="3.5" height="12" rx="1" fill="#fff" />
            <rect x="7.5" y="1" width="3.5" height="12" rx="1" fill="#fff" />
          </svg> : <svg width="12" height="14" viewBox="0 0 12 14" fill="none" aria-hidden="true">
            <path d="M2 1.5L11 7L2 12.5V1.5Z" fill="#fff" />
          </svg>}
      </button>
    </div>;
};

export const VideoEditShowcase = ({source = {}, edits = [], stackable = [], results = [], aspectRatio = "16 / 9", title, compact = false, request = null, defaultOverlay = false}) => {
  const makeLogic = ({source = {}, edits = [], stackable = [], results = [], request = null} = {}) => {
    const list = Array.isArray(edits) ? edits : [];
    const stack = (Array.isArray(stackable) ? stackable : []).map(String);
    const rows = Array.isArray(results) ? results : [];
    const keyOf = (e, i) => e && e.key != null ? String(e.key) : String(i);
    const allKeys = list.map(keyOf);
    const findEdit = k => list.find((e, i) => keyOf(e, i) === String(k)) || null;
    const isStackable = k => stack.includes(String(k));
    const sameSet = (a, b) => a.length === b.length && a.every(k => b.includes(k));
    const findResult = sel => rows.find(r => sameSet((r && Array.isArray(r.keys) ? r.keys : []).map(String), sel)) || null;
    const mediaUrl = v => typeof v === "string" && v.trim() ? v.trim() : null;
    const segmentsOf = e => {
      if (e && Array.isArray(e.segments) && e.segments.length) return e.segments;
      if (e && typeof e.prompt === "string" && e.prompt) return [{
        t: e.prompt
      }];
      return [];
    };
    const textOf = segs => Array.isArray(segs) ? segs.map(s => s && s.t != null ? String(s.t) : "").join("") : "";
    const resolveSegments = (fallback, r) => {
      if (r && Array.isArray(r.segments) && r.segments.length) return r.segments;
      if (r && typeof r.prompt === "string" && r.prompt) return [{
        t: r.prompt
      }];
      return fallback;
    };
    const normalise = selected => (Array.isArray(selected) ? selected : []).map(String).filter(k => findEdit(k));
    const itemFor = selected => {
      const sel = normalise(selected);
      if (!sel.length) return null;
      if (sel.length === 1) {
        const e = findEdit(sel[0]);
        const r = findResult(sel);
        const merged = {
          ...e,
          ...r || ({})
        };
        return {
          ...merged,
          label: e.label,
          segments: resolveSegments(segmentsOf(e), r),
          speech: r && r.speech || e.speech || null,
          video: mediaUrl(merged.video),
          poster: mediaUrl(merged.poster),
          note: r && r.note || e.note || null
        };
      }
      const ordered = stack.filter(k => sel.includes(k));
      const parts = ordered.map(findEdit).filter(Boolean);
      const r = findResult(sel) || ({});
      const joined = parts.flatMap((p, i) => {
        const segs = segmentsOf(p);
        return i === 0 ? segs : [{
          t: " "
        }, ...segs];
      });
      const labelOrder = [sel[0], ...ordered.filter(k => k !== sel[0])];
      return {
        ...r,
        label: r.label || labelOrder.map(findEdit).filter(Boolean).map(p => p.label).join(" + "),
        segments: resolveSegments(joined, r),
        speech: r.speech || (parts.find(p => p.speech) || ({})).speech || null,
        video: mediaUrl(r.video),
        poster: mediaUrl(r.poster),
        note: r.note || null
      };
    };
    const promptFor = selected => textOf((itemFor(selected) || ({})).segments);
    const hasClipFor = selected => Boolean((itemFor(selected) || ({})).video);
    const canAdd = (selected, k) => {
      const sel = normalise(selected);
      return sel.length > 0 && !sel.includes(String(k)) && isStackable(k) && sel.every(isStackable);
    };
    const addonsFor = selected => {
      const sel = normalise(selected);
      const base = sel[0];
      if (!base || !isStackable(base)) return [];
      return stack.filter(k => k !== base && findEdit(k));
    };
    const pickBase = k => findEdit(k) ? [String(k)] : [];
    const toggleAddon = (selected, k) => {
      const sel = normalise(selected);
      const key = String(k);
      if (sel.includes(key)) return sel.length > 1 ? sel.filter(x => x !== key) : sel;
      return canAdd(sel, key) ? [...sel, key] : sel;
    };
    const shellQuote = s => "'" + String(s).split("'").join("'\\''") + "'";
    const requestFor = selected => {
      if (!request) return "";
      const fields = request.fields || ({});
      const body = {
        video: mediaUrl(request.source) || mediaUrl(source && source.video) || "<URL of the source clip>",
        prompt: promptFor(selected)
      };
      if (fields.safety_tolerance !== undefined) body.safety_tolerance = fields.safety_tolerance;
      const json = JSON.stringify(body, null, 2).replace(/\n/g, "\n  ");
      const endpoint = mediaUrl(request.endpoint) || "https://api.bfl.ai/v1/flux-tools/video-edit-v1";
      return "curl -X POST " + endpoint + ' \\\n  -H "x-key: $BFL_API_KEY"' + ' \\\n  -H "Content-Type: application/json"' + " \\\n  -d " + shellQuote(json);
    };
    const safeDuration = d => typeof d === "number" && isFinite(d) && d > 0 ? d : 0;
    const clampTime = (t, d) => {
      const max = safeDuration(d);
      const v = typeof t === "number" && isFinite(t) ? t : 0;
      if (!max) return 0;
      return Math.max(0, Math.min(max, v));
    };
    const sliderMax = d => safeDuration(d) || 1;
    const fmt = s => {
      const n = typeof s === "number" && isFinite(s) && s > 0 ? Math.floor(s) : 0;
      return Math.floor(n / 60) + ":" + String(n % 60).padStart(2, "0");
    };
    const safeAspect = a => typeof a === "string" && (/^\s*\d+(?:\.\d+)?\s*\/\s*\d+(?:\.\d+)?\s*$/).test(a) ? a.trim() : "16 / 9";
    const statusFor = (status, id) => status && status.id === id && status.state ? status.state : "loading";
    const DIFF_W = 192;
    const DIFF_H = 108;
    const pixelDiff = (a, b, i) => Math.abs(a[i] - b[i]) + Math.abs(a[i + 1] - b[i + 1]) + Math.abs(a[i + 2] - b[i + 2]);
    const medianDiff = (bins, n) => {
      let acc = 0;
      for (let k = 0; k < bins.length; k += 1) {
        acc += bins[k];
        if (acc >= n / 2) return k << 3;
      }
      return 0;
    };
    const diffCut = med => Math.max(56, Math.min(160, med * 2 + 40));
    const keepAt = cnt => cnt >= 4;
    const edgeAt = cnt => cnt < 9;
    const EDGE_RGBA = [255, 200, 90, 235];
    const FILL_RGBA = [255, 140, 30, 130];
    const CLEAR_RGBA = [0, 0, 0, 0];
    const overlayPixel = cnt => !keepAt(cnt) ? CLEAR_RGBA : edgeAt(cnt) ? EDGE_RGBA : FILL_RGBA;
    const makeScratch = () => ({});
    const diffPass = (a, b, w, h, scratch, out) => {
      const n = w * h;
      const s = scratch || makeScratch();
      if (!s.diffs || s.diffs.length !== n) {
        s.diffs = new Uint16Array(n);
        s.mask = new Uint8Array(n);
        s.raw = new Uint8Array(n);
        s.prev = new Uint8Array(n);
        s.bins = new Uint32Array(96);
      }
      const diffs = s.diffs;
      const mask = s.mask;
      const bins = s.bins;
      bins.fill(0);
      for (let p = 0, i = 0; p < n; (p += 1, i += 4)) {
        const d = pixelDiff(a, b, i);
        diffs[p] = d;
        bins[Math.min(95, d >> 3)] += 1;
      }
      const med = medianDiff(bins, n);
      const cut = diffCut(med);
      const raw = s.raw;
      const prev = s.prev;
      for (let p = 0; p < n; p += 1) {
        const hit = diffs[p] > cut ? 1 : 0;
        mask[p] = hit & prev[p];
        raw[p] = hit;
      }
      s.prev = raw;
      s.raw = prev;
      let changed = 0;
      for (let y = 0; y < h; y += 1) {
        for (let x = 0; x < w; x += 1) {
          const p = y * w + x;
          let cnt = 0;
          if (mask[p]) {
            for (let dy = -1; dy <= 1; dy += 1) {
              const yy = y + dy;
              if (yy < 0 || yy >= h) continue;
              for (let dx = -1; dx <= 1; dx += 1) {
                const xx = x + dx;
                if (xx < 0 || xx >= w) continue;
                cnt += mask[yy * w + xx];
              }
            }
          }
          if (keepAt(cnt)) changed += 1;
          if (out) {
            const px = overlayPixel(cnt);
            const i = p * 4;
            out[i] = px[0];
            out[i + 1] = px[1];
            out[i + 2] = px[2];
            out[i + 3] = px[3];
          }
        }
      }
      return {
        med,
        cut,
        changed,
        pixels: n,
        scratch: s
      };
    };
    const clearScratch = scratch => {
      if (scratch && scratch.prev) scratch.prev.fill(0);
      if (scratch && scratch.raw) scratch.raw.fill(0);
      return scratch;
    };
    return {
      allKeys,
      keyOf,
      findEdit,
      isStackable,
      findResult,
      mediaUrl,
      segmentsOf,
      textOf,
      resolveSegments,
      itemFor,
      promptFor,
      hasClipFor,
      canAdd,
      addonsFor,
      pickBase,
      toggleAddon,
      shellQuote,
      requestFor,
      safeDuration,
      clampTime,
      sliderMax,
      fmt,
      safeAspect,
      statusFor,
      DIFF_W,
      DIFF_H,
      pixelDiff,
      medianDiff,
      diffCut,
      keepAt,
      edgeAt,
      overlayPixel,
      makeScratch,
      diffPass,
      clearScratch
    };
  };
  const L = makeLogic({
    source,
    edits,
    stackable,
    results,
    request
  });
  const [selected, setSelected] = useState(L.allKeys.length ? [L.allKeys[0]] : []);
  const [playing, setPlaying] = useState(true);
  const [sound, setSound] = useState(false);
  const [srcState, setSrcState] = useState("loading");
  const [outStatus, setOutStatus] = useState({
    id: null,
    state: "loading"
  });
  const [time, setTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [promptCopy, setPromptCopy] = useState("");
  const [requestCopy, setRequestCopy] = useState("");
  const [overlay, setOverlay] = useState(Boolean(defaultOverlay));
  const [overlayDead, setOverlayDead] = useState(false);
  const [corsOk, setCorsOk] = useState(true);
  const srcRef = useRef(null);
  const outRef = useRef(null);
  const itemIdRef = useRef("none");
  const soundRef = useRef(false);
  const pausedRef = useRef(false);
  soundRef.current = sound;
  const canvasRef = useRef(null);
  const workRef = useRef(null);
  const scratchRef = useRef(null);
  const imgRef = useRef(null);
  const rafRef = useRef(0);
  const lastDiffRef = useRef(0);
  const overlayRef = useRef(false);
  const deadRef = useRef(false);
  overlayRef.current = overlay;
  const DRIFT_S = 0.04;
  const SEEK_S = 0.5;
  const DIFF_MS = 80;
  const item = L.itemFor(selected);
  const itemId = selected.join("+") || "none";
  itemIdRef.current = itemId;
  const outState = L.statusFor(outStatus, itemId);
  const resultUrl = item && item.video;
  const sourceUrl = L.mediaUrl(source && source.video);
  const addons = L.addonsFor(selected);
  const base = selected[0] || "";
  const baseLabel = (L.findEdit(base) || ({})).label || "the edit above";
  const combinable = L.allKeys.filter(k => L.isStackable(k)).length > 1;
  const promptText = L.promptFor(selected);
  const requestCommand = L.requestFor(selected);
  const canOverlay = Boolean(sourceUrl && resultUrl && !overlayDead);
  const overlayOn = canOverlay && overlay;
  const resetCopy = () => {
    setPromptCopy("");
    setRequestCopy("");
  };
  const playSafe = el => {
    if (!el || !el.play) return;
    const p = el.play();
    if (p && typeof p.catch === "function") p.catch(() => {});
  };
  const seekTo = (el, t) => {
    if (!el) return;
    try {
      el.currentTime = t;
    } catch (e) {}
  };
  const resync = force => {
    const a = srcRef.current;
    const b = outRef.current;
    if (!a || !b || b.readyState < 1) return;
    const delta = b.currentTime - a.currentTime;
    if (force || Math.abs(delta) > SEEK_S) {
      seekTo(b, a.currentTime);
      b.playbackRate = 1;
    } else if (Math.abs(delta) > DRIFT_S) {
      b.playbackRate = 1 - Math.max(-0.08, Math.min(0.08, delta * 0.5));
    } else {
      b.playbackRate = 1;
    }
    if (a.paused && !b.paused) b.pause();
    if (!a.paused && b.paused) playSafe(b);
  };
  const loopPoint = () => {
    const a = srcRef.current;
    const b = outRef.current;
    const da = L.safeDuration(a && a.duration);
    const db = L.safeDuration(b && b.duration);
    if (!da || !db) return da || db;
    return Math.min(da, db);
  };
  const restart = () => {
    if (pausedRef.current) return;
    const a = srcRef.current;
    if (!a) return;
    seekTo(a, 0);
    seekTo(outRef.current, 0);
    setTime(0);
    playSafe(a);
    playSafe(outRef.current);
  };
  const ensureWork = () => {
    if (workRef.current) return workRef.current;
    if (typeof document === "undefined") return null;
    const a = document.createElement("canvas");
    const b = document.createElement("canvas");
    a.width = L.DIFF_W;
    a.height = L.DIFF_H;
    b.width = L.DIFF_W;
    b.height = L.DIFF_H;
    workRef.current = {
      ca: a.getContext("2d", {
        willReadFrequently: true
      }),
      cb: b.getContext("2d", {
        willReadFrequently: true
      })
    };
    return workRef.current;
  };
  const clearOverlay = () => {
    const cv = canvasRef.current;
    if (cv && cv.getContext) cv.getContext("2d").clearRect(0, 0, cv.width, cv.height);
  };
  const resetOverlay = () => {
    L.clearScratch(scratchRef.current);
    lastDiffRef.current = 0;
    clearOverlay();
  };
  const step = () => {
    const a = srcRef.current;
    const b = outRef.current;
    if (!overlayRef.current || deadRef.current) {
      rafRef.current = 0;
      return;
    }
    if (!a || !b || !a.isConnected || !b.isConnected) {
      rafRef.current = 0;
      return;
    }
    const now = Date.now();
    if (now - lastDiffRef.current >= DIFF_MS && !a.seeking && !b.seeking && a.readyState >= 2 && b.readyState >= 2) {
      lastDiffRef.current = now;
      const w = ensureWork();
      const cv = canvasRef.current;
      if (w && cv) {
        let A;
        let B;
        try {
          w.ca.drawImage(a, 0, 0, L.DIFF_W, L.DIFF_H);
          w.cb.drawImage(b, 0, 0, L.DIFF_W, L.DIFF_H);
          A = w.ca.getImageData(0, 0, L.DIFF_W, L.DIFF_H);
          B = w.cb.getImageData(0, 0, L.DIFF_W, L.DIFF_H);
        } catch (e) {
          deadRef.current = true;
          setOverlayDead(true);
          clearOverlay();
          rafRef.current = 0;
          return;
        }
        const ctx = cv.getContext("2d");
        if (!imgRef.current) imgRef.current = ctx.createImageData(L.DIFF_W, L.DIFF_H);
        if (!scratchRef.current) scratchRef.current = L.makeScratch();
        L.diffPass(A.data, B.data, L.DIFF_W, L.DIFF_H, scratchRef.current, imgRef.current.data);
        ctx.putImageData(imgRef.current, 0, 0);
      }
    }
    rafRef.current = requestAnimationFrame(step);
  };
  const startOverlay = () => {
    if (rafRef.current || deadRef.current || !overlayRef.current) return;
    if (typeof requestAnimationFrame === "undefined") return;
    rafRef.current = requestAnimationFrame(step);
  };
  const toggleOverlay = () => {
    const next = !overlay;
    setOverlay(next);
    overlayRef.current = next;
    resetOverlay();
    if (next) startOverlay();
  };
  const onMediaError = report => {
    if (corsOk) {
      deadRef.current = true;
      setOverlayDead(true);
      setCorsOk(false);
      return;
    }
    report();
  };
  const setSrcRef = el => {
    srcRef.current = el;
    if (!el || el.dataset.vesWired === "1") return;
    el.dataset.vesWired = "1";
    const readMeta = () => {
      setDuration(L.safeDuration(el.duration));
      setTime(el.currentTime || 0);
    };
    if (el.readyState >= 1) readMeta();
    if (el.readyState >= 3) setSrcState("ready");
    el.addEventListener("loadedmetadata", readMeta);
    el.addEventListener("durationchange", readMeta);
    el.addEventListener("timeupdate", () => {
      const end = loopPoint();
      if (!pausedRef.current && end && el.currentTime >= end - 0.12) {
        restart();
        return;
      }
      setTime(el.currentTime || 0);
      resync(false);
    });
    el.addEventListener("ended", restart);
    el.addEventListener("seeked", () => {
      setTime(el.currentTime || 0);
      resync(true);
    });
    el.addEventListener("play", () => {
      if (pausedRef.current) {
        el.pause();
        return;
      }
      setPlaying(true);
      resync(true);
      startOverlay();
    });
    el.addEventListener("pause", () => {
      setPlaying(false);
      resync(true);
    });
    el.addEventListener("canplay", () => setSrcState("ready"));
    el.addEventListener("playing", () => setSrcState("ready"));
    el.addEventListener("waiting", () => setSrcState("loading"));
    el.addEventListener("loadeddata", () => {
      resync(true);
      startOverlay();
    });
    el.addEventListener("error", () => onMediaError(() => setSrcState("error")));
  };
  const setOutRef = el => {
    outRef.current = el;
    if (!el || el.dataset.vesWired === "1") return;
    el.dataset.vesWired = "1";
    const id = itemIdRef.current;
    const mark = state => {
      if (id === itemIdRef.current) setOutStatus({
        id,
        state
      });
    };
    el.muted = !soundRef.current;
    if (el.readyState >= 3) mark("ready");
    el.addEventListener("canplay", () => mark("ready"));
    el.addEventListener("playing", () => mark("ready"));
    el.addEventListener("waiting", () => mark("loading"));
    el.addEventListener("error", () => onMediaError(() => mark("error")));
    el.addEventListener("loadeddata", () => {
      resync(true);
      startOverlay();
    });
  };
  const setCanvasRef = el => {
    canvasRef.current = el;
    if (!el) return;
    if (el.width !== L.DIFF_W || el.height !== L.DIFF_H) {
      el.width = L.DIFF_W;
      el.height = L.DIFF_H;
    }
  };
  const togglePlay = () => {
    const a = srcRef.current;
    if (!a) return;
    if (!playing) {
      pausedRef.current = false;
      playSafe(a);
    } else {
      pausedRef.current = true;
      setPlaying(false);
      a.pause();
    }
    resync(true);
  };
  const toggleSound = () => {
    const next = !sound;
    setSound(next);
    if (outRef.current) outRef.current.muted = !next;
  };
  const onSeek = e => {
    const t = L.clampTime(Number(e.target.value), duration);
    setTime(t);
    seekTo(srcRef.current, t);
    resync(true);
  };
  const onSelectBase = k => {
    resetCopy();
    resetOverlay();
    setSelected(L.pickBase(k));
  };
  const onToggleAddon = k => {
    resetCopy();
    resetOverlay();
    setSelected(L.toggleAddon(selected, k));
  };
  const copyInto = (text, set) => {
    if (!text) return;
    const clear = () => setTimeout(() => set(""), 2400);
    let p = null;
    try {
      if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
        p = navigator.clipboard.writeText(text);
      }
    } catch (e) {
      p = null;
    }
    if (!p || typeof p.then !== "function") {
      set("failed");
      clear();
      return;
    }
    p.then(() => {
      set("copied");
      clear();
    }, () => {
      set("failed");
      clear();
    });
  };
  const copyLabel = (state, idle) => state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idle;
  const ORANGE = "255, 138, 40";
  const card = {
    background: "#0c0d0e",
    color: "#e8e8e8",
    borderRadius: "1.1rem",
    border: "1px solid rgba(255,255,255,0.08)",
    padding: "0.8rem",
    display: "flex",
    flexDirection: "column",
    gap: "0.7rem",
    margin: "1.25rem 0",
    boxShadow: "0 20px 60px rgba(0,0,0,0.35)"
  };
  const pane = {
    position: "relative",
    aspectRatio: L.safeAspect(aspectRatio),
    borderRadius: "0.7rem",
    overflow: "hidden",
    background: "#000"
  };
  const fill = {
    position: "absolute",
    inset: 0,
    width: "100%",
    height: "100%",
    objectFit: "cover",
    pointerEvents: "none"
  };
  const paneLabel = {
    position: "absolute",
    top: "10px",
    left: "10px",
    padding: "3px 9px",
    borderRadius: "6px",
    background: "rgba(0,0,0,0.6)",
    color: "#fff",
    fontSize: "0.7rem",
    fontWeight: 700,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    zIndex: 3
  };
  const statusBand = {
    position: "absolute",
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: 4,
    padding: "0.4rem 0.6rem",
    background: "rgba(0,0,0,0.65)",
    color: "#fff",
    fontSize: "0.72rem",
    fontWeight: 600,
    letterSpacing: "0.04em"
  };
  const button = on => ({
    padding: "0.35rem 0.8rem",
    borderRadius: "999px",
    border: `1px solid ${on ? `rgba(${ORANGE}, 0.9)` : "rgba(255,255,255,0.14)"}`,
    background: on ? `rgba(${ORANGE}, 0.16)` : "rgba(255,255,255,0.04)",
    color: on ? `rgb(${ORANGE})` : "#d8d8d8",
    fontSize: "0.74rem",
    fontWeight: 700,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const solidButton = {
    padding: "0.35rem 0.8rem",
    borderRadius: "999px",
    border: "none",
    background: "var(--aspen-evergreen, #486a58)",
    color: "#fff",
    fontSize: "0.74rem",
    fontWeight: 700,
    cursor: "pointer",
    lineHeight: 1.2
  };
  const fieldLabel = {
    fontSize: "0.72rem",
    fontWeight: 700,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    opacity: 0.6
  };
  const hint = {
    margin: 0,
    fontSize: "0.76rem",
    opacity: 0.7,
    lineHeight: 1.5
  };
  const buttonRow = {
    display: "flex",
    flexWrap: "wrap",
    gap: "0.35rem",
    minWidth: 0
  };
  const showPrompt = Boolean(promptText || item && (item.speech || item.note) || request && !compact);
  const sourceLabel = source && source.label || "Source";
  const sourceStatus = srcState === "error" ? "This source clip could not be loaded." : srcState === "ready" ? "" : "Loading the source clip.";
  const resultStatus = outState === "error" ? "This edited clip could not be loaded." : outState === "ready" ? "" : "Loading the edited clip.";
  const cors = corsOk ? "anonymous" : undefined;
  return <div className="not-prose" style={card}>
      {}
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    gap: "0.6rem",
    flexWrap: "wrap",
    padding: "0.1rem 0.2rem"
  }}>
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.6rem",
    minWidth: 0
  }}>
          <span style={fieldLabel}>{title || "Edit"}</span>
          <span style={{
    fontSize: "0.9rem",
    fontWeight: 600,
    whiteSpace: "nowrap",
    overflow: "hidden",
    textOverflow: "ellipsis"
  }}>
            {item && item.label || "Pick an edit"}
          </span>
        </div>
        <div style={{
    display: "flex",
    gap: "0.4rem",
    alignItems: "center",
    flexWrap: "wrap"
  }}>
          {canOverlay ? <button type="button" onClick={toggleOverlay} style={button(overlayOn)} aria-pressed={overlayOn}>
              {overlayOn ? "Hide pixel differences" : "Show pixel differences"}
            </button> : null}
          <button type="button" onClick={toggleSound} style={button(sound)} disabled={!resultUrl} aria-pressed={sound}>
            {sound ? "Sound on" : "Sound off"}
          </button>
          <button type="button" onClick={togglePlay} style={button(false)}>
            {playing ? "Pause" : "Play"}
          </button>
        </div>
      </div>

      {}
      <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 16rem), 1fr))",
    gap: "0.5rem"
  }}>
        <div style={{
    ...pane,
    cursor: sourceUrl ? "pointer" : "default"
  }} onClick={sourceUrl ? togglePlay : undefined}>
          {sourceUrl ? <video key={`source-${corsOk ? 1 : 0}`} ref={setSrcRef} src={sourceUrl} poster={L.mediaUrl(source && source.poster) || undefined} crossOrigin={cors} autoPlay muted playsInline preload="auto" aria-label={sourceLabel} style={fill} /> : <div style={{
    position: "absolute",
    inset: 0,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "1rem",
    textAlign: "center",
    fontSize: "0.85rem",
    opacity: 0.8
  }}>
              No source clip was given for this player.
            </div>}
          <span style={paneLabel}>{sourceLabel}</span>
          {sourceUrl && sourceStatus ? <div style={statusBand} role="status">
              {sourceStatus}
            </div> : null}
        </div>

        <div style={{
    ...pane,
    background: resultUrl ? "#000" : "transparent",
    border: resultUrl ? "none" : "1px dashed rgba(255,255,255,0.22)",
    cursor: resultUrl ? "pointer" : "default"
  }} onClick={resultUrl ? togglePlay : undefined}>
          {resultUrl ? <video key={`result-${itemId}-${corsOk ? 1 : 0}`} ref={setOutRef} src={resultUrl} poster={item && item.poster || undefined} crossOrigin={cors} muted={!sound} playsInline preload="auto" aria-label={item && item.label || "Edited"} style={fill} /> : <div style={{
    position: "absolute",
    inset: 0,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "1.25rem",
    textAlign: "center"
  }}>
              <span style={fieldLabel}>Not generated yet</span>
            </div>}
          {canOverlay ? <canvas ref={setCanvasRef} aria-hidden="true" style={{
    ...fill,
    zIndex: 2,
    opacity: overlayOn && outState === "ready" ? 1 : 0
  }} /> : null}
          <span style={paneLabel}>Edited</span>
          {resultUrl && resultStatus ? <div style={statusBand} role="status">
              {resultStatus}
            </div> : null}
        </div>
      </div>

      {}
      {overlayOn ? <p style={{
    margin: 0,
    padding: "0 0.2rem",
    fontSize: "0.78rem",
    lineHeight: 1.55,
    opacity: 0.75
  }}>
          Orange shows an approximate pixel difference between the clips. Pause
          playback to inspect a frame, or hide the highlights to see the result.
        </p> : null}

      {}
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.6rem",
    padding: "0 0.2rem"
  }}>
        <span style={{
    fontSize: "0.7rem",
    fontVariantNumeric: "tabular-nums",
    opacity: 0.6,
    width: "2.6rem"
  }}>{L.fmt(time)}</span>
        {}
        <input type="range" min={0} max={L.sliderMax(duration)} step={0.05} value={L.clampTime(time, duration)} onChange={onSeek} onKeyDown={e => e.stopPropagation()} disabled={!L.safeDuration(duration)} aria-label="Seek both clips" aria-valuetext={`${L.fmt(time)} of ${L.fmt(duration)}`} style={{
    flex: 1,
    accentColor: `rgb(${ORANGE})`,
    cursor: "pointer",
    minWidth: 0
  }} />
        <span style={{
    fontSize: "0.7rem",
    fontVariantNumeric: "tabular-nums",
    opacity: 0.6,
    width: "2.6rem",
    textAlign: "right"
  }}>{L.fmt(duration)}</span>
      </div>

      {}
      {edits.length < 2 ? null : <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem",
    padding: "0.1rem 0.2rem"
  }}>
          {}
          <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
            <span style={fieldLabel}>Edit</span>
            <div role="group" aria-label="Edit" style={buttonRow}>
              {edits.map((e, i) => {
    const k = L.keyOf(e, i);
    const on = base === k;
    return <button key={k} type="button" onClick={() => onSelectBase(k)} style={button(on)} aria-pressed={on}>
                    {`${e.label}${L.hasClipFor([k]) ? "" : " (not generated yet)"}`}
                  </button>;
  })}
            </div>
          </div>

          {}
          {!combinable || !base ? null : addons.length ? <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.4rem"
  }}>
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
                <span style={fieldLabel}>Combine edits</span>
                <div role="group" aria-label={`Combine with ${baseLabel}`} style={buttonRow}>
                  {addons.map(k => {
    const e = L.findEdit(k);
    const on = selected.includes(k);
    const allowed = on || L.canAdd(selected, k);
    const has = L.hasClipFor(on ? selected : [...selected, k]);
    return <button key={k} type="button" onClick={() => onToggleAddon(k)} disabled={!allowed} aria-pressed={on} style={{
      ...button(on),
      opacity: allowed ? 1 : 0.45,
      cursor: allowed ? "pointer" : "default"
    }}>
                        {`Also ${e.label}${has ? "" : " (not generated yet)"}`}
                      </button>;
  })}
                </div>
              </div>
              <p style={hint}>
                The additions are asked for in one prompt with the edit above.
                Picking a different edit clears them.
              </p>
            </div> : <p style={hint}>{`No combined examples are available for ${baseLabel}.`}</p>}
        </div>}

      {}
      {showPrompt ? <div style={{
    background: "#16181a",
    borderRadius: "0.8rem",
    padding: "0.95rem 1.1rem",
    display: "flex",
    flexDirection: "column",
    gap: "0.7rem"
  }}>
          {promptText ? <p style={{
    margin: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: "0.94rem",
    lineHeight: 1.8,
    whiteSpace: "pre-wrap"
  }}>
              {promptText}
            </p> : null}

          {item && item.speech ? <div style={{
    display: "grid",
    gridTemplateColumns: "auto 1fr",
    gap: "0.25rem 0.9rem",
    fontSize: "0.85rem",
    lineHeight: 1.5,
    borderTop: "1px solid rgba(255,255,255,0.08)",
    paddingTop: "0.65rem"
  }}>
              <span style={{
    opacity: 0.5
  }}>Before</span>
              <span>{item.speech.from}</span>
              <span style={{
    opacity: 0.5
  }}>After</span>
              <span>{item.speech.to}</span>
            </div> : null}

          {item && item.note ? <p style={{
    margin: 0,
    fontSize: "0.8rem",
    lineHeight: 1.55,
    opacity: 0.75
  }}>{item.note}</p> : null}

          {compact || !promptText ? null : <div style={{
    display: "flex",
    justifyContent: "flex-end",
    alignItems: "center",
    gap: "0.4rem",
    flexWrap: "wrap"
  }}>
              <button type="button" onClick={() => copyInto(promptText, setPromptCopy)} style={solidButton}>
                {copyLabel(promptCopy, "Copy prompt")}
              </button>
            </div>}

          {request && !compact ? <details style={{
    borderTop: "1px solid rgba(255,255,255,0.08)",
    paddingTop: "0.7rem"
  }}>
              <summary style={{
    cursor: "pointer",
    fontSize: "0.8rem",
    fontWeight: 600
  }}>Example request</summary>
              <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem",
    marginTop: "0.6rem"
  }}>
                <pre style={{
    margin: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: "0.78rem",
    lineHeight: 1.6,
    whiteSpace: "pre-wrap",
    wordBreak: "break-all",
    opacity: 0.92
  }}>{requestCommand}</pre>
                <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
                  <span style={{
    fontSize: "0.75rem",
    opacity: 0.6
  }}>
                    {request.note || "Example request with the prompt above."}
                  </span>
                  <button type="button" onClick={() => copyInto(requestCommand, setRequestCopy)} style={solidButton}>
                    {copyLabel(requestCopy, "Copy request")}
                  </button>
                </div>
              </div>
            </details> : null}
        </div> : null}
    </div>;
};

An edit prompt describes a change, not a shot. Everything it doesn't mention
stays as filmed, so the shortest prompt that names the change is usually the
right one, and detail goes where the edit needs it. The clips below show what
each kind of wording returned. Request fields, limits and pricing are on the
[FLUX Video Edit page](/flux_tools/flux_video_edit).

## Describe the change

Three edits of the same harbor clip. The bucket removal is a single
instruction, `Remove the orange bucket.` The seagull addition names a location
and a pose: `Add a seagull standing on the corner of the crate.` Neither prompt
describes the whole shot. Start with the change, then add details that matter
to the edit.

<VideoEditShowcase
  title="Harbor"
  compact
  source={{ video: "https://cdn.sanity.io/files/2gpum2i6/production/f1654af36e7694775939b1aa8a2bb0419ff819ea.mp4", poster: "https://cdn.sanity.io/images/2gpum2i6/production/82ececb45ad817f6f2d92bfd031a6d142cf1562b-1280x726.jpg", label: "Source" }}
  edits={[
{ key: "remove", label: "Remove bucket", video: "https://cdn.sanity.io/files/2gpum2i6/production/c447e0d2a8b0a516f98e657bf3153b2c6f774a4d.mp4", prompt: "Remove the orange bucket." },
{ key: "add", label: "Add seagull", video: "https://cdn.sanity.io/files/2gpum2i6/production/da3907304d8ca81e5898bd49a5ede11ab9927ff0.mp4", prompt: "Add a seagull standing on the corner of the crate." },
{ key: "dialogue", label: "Change dialogue", video: "https://cdn.sanity.io/files/2gpum2i6/production/a57e0921459494b5008db67d82d14030dd49d4da.mp4", prompt: "Make him say \"Fresh mackerel, four for ten.\"", speech: { from: "Straight off the boat this morning, four for ten.", to: "Fresh mackerel, four for ten." } },
]}
/>

## Add placement, appearance, and action

These two edits use the same harbor source. The short prompt leaves the
lighthouse's size, look and position open. The second asks for a tall white
tower with a red lantern room and a place on the end of the harbor wall to the
right of the boats.

<div style={{ aspectRatio: "1248 / 704", width: "100%", margin: "1.5rem auto" }}>
  <VideoComparisonSlider beforeVideo="https://cdn.sanity.io/files/2gpum2i6/production/09c56938e2d7cf4f65fc26b4dbc4db0c15ad323a.mp4" afterVideo="https://cdn.sanity.io/files/2gpum2i6/production/24c46177a79eea746f0abc6f206e018583169473.mp4" beforeLabel="Short prompt" afterLabel="Specific prompt" height="100%" objectFit="contain" />
</div>

Short prompt:

<PromptDisplay prompt="Add a lighthouse." />

Specific prompt:

<PromptDisplay prompt="Add a tall white lighthouse with a red lantern room standing on the end of the harbor wall to the right of the boats." />

The short prompt put a small lighthouse far off on the shoreline. The specific
prompt put a tall white lighthouse with a red lantern room on the end of the
harbor wall, right of the boats, where it stays for the whole clip.

## Fit dialogue to the clip

The fishmonger speaks for about two seconds, and the dialogue edit above gives
him a line that fits that time. An earlier attempt asked for a longer sentence,
and the result kept the first part and cut the rest. The new line can only be
as long as the speech it replaces, so write it to that length.

The result keeps the source audio unless the prompt asks for a dialogue or
sound change. A silent source returns a silent result, so there is no speech
to edit in a clip that has none.

## Turn a rough animation into a finished scene

The source is a Blender blockout: gray boxes stand in for two vehicles and the
pillars they weave between. The prompt names what each box becomes and what
stays the same.

<PromptDisplay prompt="Render this previz chase as a desert convoy pursuit at golden hour: the pillars become ruined highway columns in open dust, the lead car an armored pickup, the chaser a spiked buggy, both trailing dust plumes, heat shimmer. Same weave lines, same speeds, same camera overtake. V8 roar and wind." />

<div style={{ aspectRatio: "1248 / 704", width: "100%", margin: "1.5rem auto" }}>
  <VideoComparisonSlider beforeVideo="https://cdn.sanity.io/files/2gpum2i6/production/8c268197c1210f61156e646405e7a61a7645caf5.mp4" afterVideo="https://cdn.sanity.io/files/2gpum2i6/production/c3f577b32f7579e502eac4c6e52e3d8dd236938c.mp4" beforeLabel="Blockout" afterLabel="Desert chase" height="100%" objectFit="contain" />
</div>

## Apply another edit

The desert result becomes the source for the night edit below, so the sequence
has three stages: blockout, desert chase, night chase. For a second pass, use
the first result as the input and describe the next change, rather than
combining both instructions in one request against the blockout.

<PromptDisplay prompt="Change the desert chase to deep night. Turn on the headlights and add a huge low moon above the canyon." />

<div style={{ aspectRatio: "1248 / 704", width: "100%", margin: "1.5rem auto" }}>
  <VideoComparisonSlider beforeVideo="https://cdn.sanity.io/files/2gpum2i6/production/c3f577b32f7579e502eac4c6e52e3d8dd236938c.mp4" afterVideo="https://cdn.sanity.io/files/2gpum2i6/production/b4d31b76b80309054e655606f92bbf2f9946e3e1.mp4" beforeLabel="Desert chase" afterLabel="Deep night" height="100%" objectFit="contain" />
</div>

## Change the setting

The rock face becomes a glass skyscraper with a city below. The prompt names
both the new surroundings and the surfaces the climber touches, so the hands
and feet have something to hold in the new setting.

<PromptDisplay prompt="Replace the cliff with the reflective windows of a high-rise building. Add narrow metal window ledges at the climber’s hand and foot contact points. Far below, traffic and illuminated buildings fill a dense city in blue-hour light." />

<div style={{ aspectRatio: "704 / 1280", width: "100%", maxWidth: "360px", margin: "1.5rem auto" }}>
  <VideoComparisonSlider beforeVideo="https://cdn.sanity.io/files/2gpum2i6/production/1986ae6aef9cbabf73f58ac6f77714432e380d62.mp4" afterVideo="https://cdn.sanity.io/files/2gpum2i6/production/1fb7b2e731dc09eef95aadbc4a31ee97c4387710.mp4" beforeLabel="Rock face" afterLabel="Skyscraper" height="100%" objectFit="contain" />
</div>

## Restyle a clip

The illustrated claw-machine scene becomes a photographic-looking scene with a
purple plush toy. The prompt describes the new visual style through fabric,
seams, metal, and reflections, rather than naming a style alone.

<PromptDisplay prompt="Restyle this clip as live-action footage of a claw machine lifting a lavender octopus plush. Fine velour fibers and sewn seams replace the ink outlines. The plush compresses gently where the metal claw grips it. Soft arcade lights reflect in the glass." />

<div style={{ aspectRatio: "1248 / 704", width: "100%", margin: "1.5rem auto" }}>
  <VideoComparisonSlider beforeVideo="https://cdn.sanity.io/files/2gpum2i6/production/953ce1b726e3d26bf417f35e0f5f4e4aeb7c29d8.mp4" afterVideo="https://cdn.sanity.io/files/2gpum2i6/production/e6a3933e349ee0b0a314c08408f56ae03d433e5a.mp4" beforeLabel="Cartoon" afterLabel="Photoreal" height="100%" objectFit="contain" />
</div>

## Related pages

<CardGroup cols={2}>
  <Card title="FLUX Video Edit" icon="scissors" href="/flux_tools/flux_video_edit">
    Twelve edits on one clip, request fields, limits and pricing.
  </Card>

  <Card title="Video Prompting Overview" icon="book-open-cover" href="/guides/prompting_video_overview">
    Workflow selection and the broader FLUX 3 video prompting framework.
  </Card>

  <Card title="Audio and speech" icon="waveform-lines" href="/guides/prompting_video_audio">
    Direct dialogue, voiceover, ambience and effects when the edit changes sound.
  </Card>

  <Card title="Examples & Cheatsheet" icon="camera" href="/guides/prompting_video_camera_terms">
    Reusable framing, angle, movement, and focus phrasing for your prompts.
  </Card>
</CardGroup>
