> ## 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.

# FLUX 3

> FLUX 3 is one multimodal model. Video with synchronized audio, one request shape keyed by mode.

export const VideoReelMosaic = ({title = "Generated with FLUX 3", rows = [], speed = 6.5, itemWidth = "clamp(14rem, 20vw, 18rem)", itemAspectRatio = "16 / 9", margin = "2.75rem 0 3.5rem", showTopBorder = true, paddingTop = "2rem"}) => {
  const preparedRows = rows.filter(row => Array.isArray(row) && row.length > 0);
  const startVideo = event => {
    const video = event.currentTarget;
    video.muted = true;
    const playback = video.play?.();
    if (playback && typeof playback.catch === "function") playback.catch(() => {});
  };
  return <div className="not-prose flux3-video-reel-mosaic" style={{
    margin
  }}>
      <style>{`
        @keyframes flux3VideoReelLeft {
          from { transform: translateX(0); }
          to { transform: translateX(-50%); }
        }

        @keyframes flux3VideoReelRight {
          from { transform: translateX(-50%); }
          to { transform: translateX(0); }
        }

        .flux3-video-reel-mosaic:hover .flux3-video-reel-mosaic__track {
          animation-play-state: paused;
        }

        .flux3-video-reel-mosaic__frame {
          border-top: 1px solid rgba(22,49,38,0.14);
        }
        .dark .flux3-video-reel-mosaic__frame {
          border-top-color: rgba(246,241,231,0.12);
        }
        .flux3-video-reel-mosaic__frame--borderless {
          border-top: none;
        }

        .flux3-video-reel-mosaic__title {
          color: var(--aspen-ink, #163126);
        }
        .dark .flux3-video-reel-mosaic__title {
          color: rgba(246,241,231,0.94);
        }

        .flux3-video-reel-mosaic__fade {
          background: linear-gradient(90deg, var(--aspen-paper, #fffdf9) 0%, transparent 8%, transparent 92%, var(--aspen-paper, #fffdf9) 100%);
        }
        .dark .flux3-video-reel-mosaic__fade {
          background: linear-gradient(90deg, rgba(4,16,11,1) 0%, rgba(4,16,11,0) 8%, rgba(4,16,11,0) 92%, rgba(4,16,11,1) 100%);
        }
      `}</style>

      <div className={`flux3-video-reel-mosaic__frame${showTopBorder ? "" : " flux3-video-reel-mosaic__frame--borderless"}`} style={{
    paddingTop
  }}>
        <h2 className="flux3-video-reel-mosaic__title" style={{
    margin: "0 0 1.5rem",
    textAlign: "center",
    fontFamily: '"Instrument Sans", sans-serif',
    fontSize: "clamp(2rem, 4vw, 3.15rem)",
    fontWeight: 500,
    letterSpacing: "-0.04em"
  }}>
          {title}
        </h2>

        <div style={{
    position: "relative",
    display: "grid",
    gap: "0.75rem",
    overflow: "hidden"
  }}>
          {preparedRows.map((row, rowIdx) => {
    const direction = rowIdx % 2 === 0 ? "flux3VideoReelLeft" : "flux3VideoReelRight";
    const duration = `${Math.max(row.length * speed, 18)}s`;
    const looped = [...row, ...row];
    return <div key={rowIdx} style={{
      overflow: "hidden"
    }}>
                <div className="flux3-video-reel-mosaic__track" style={{
      display: "flex",
      gap: "0.75rem",
      width: "max-content",
      animation: `${direction} ${duration} linear infinite`
    }}>
                  {looped.map((item, idx) => <div key={`${rowIdx}-${idx}-${item.video}`} style={{
      position: "relative",
      width: item.width || itemWidth,
      aspectRatio: item.height ? undefined : item.aspectRatio || itemAspectRatio,
      height: item.height,
      flexShrink: 0,
      overflow: "hidden",
      borderRadius: "0",
      background: "#08110d"
    }}>
                      <video src={item.video} poster={item.poster} autoPlay defaultMuted muted loop playsInline preload="auto" onCanPlay={startVideo} onLoadedData={startVideo} aria-label={item.title || title} style={{
      display: "block",
      width: "100%",
      height: "100%",
      objectFit: "contain",
      background: "#08110d"
    }} />
                    </div>)}
                </div>
              </div>;
  })}

          <span aria-hidden="true" className="flux3-video-reel-mosaic__fade" style={{
    position: "absolute",
    inset: 0,
    pointerEvents: "none"
  }} />
        </div>
      </div>
    </div>;
};

export const FeatureSlider = ({items = []}) => {
  const [active, setActive] = useState({});
  const [inView, setInView] = useState(0);
  const [soundOn, setSoundOn] = useState({});
  const activeOf = cardIdx => active[cardIdx] || 0;
  const goTo = (cardIdx, next) => setActive(prev => ({
    ...prev,
    [cardIdx]: next
  }));
  const step = (cardIdx, dir, n) => setActive(prev => ({
    ...prev,
    [cardIdx]: ((prev[cardIdx] || 0) + dir + n) % n
  }));
  const observerRef = useRef(null);
  const getObserver = () => {
    if (observerRef.current) return observerRef.current;
    if (typeof IntersectionObserver === "undefined") return null;
    observerRef.current = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        const v = entry.target;
        if (entry.isIntersecting) {
          v.muted = v.dataset.soundOn !== "1";
          const p = v.play?.();
          if (p && typeof p.catch === "function") p.catch(() => {});
        } else {
          v.pause?.();
        }
      });
    }, {
      threshold: 0.4
    });
    return observerRef.current;
  };
  const videoRefs = useRef({});
  const attachVideo = (el, cardIdx) => {
    if (!el) return;
    videoRefs.current[cardIdx] = el;
    const io = getObserver();
    if (io) io.observe(el);
  };
  const toggleSound = cardIdx => {
    const turningOn = !soundOn[cardIdx];
    const el = videoRefs.current[cardIdx];
    if (el) {
      el.dataset.soundOn = turningOn ? "1" : "0";
      el.muted = !turningOn;
      if (turningOn) {
        const p = el.play?.();
        if (p && typeof p.catch === "function") p.catch(() => {});
      }
    }
    setSoundOn(prev => ({
      ...prev,
      [cardIdx]: turningOn
    }));
  };
  const trackRef = useRef(null);
  const cardRefs = useRef([]);
  const measuredRef = useRef(false);
  const [edges, setEdges] = useState({
    left: false,
    right: true
  });
  const [hasOverflow, setHasOverflow] = useState(true);
  const updateEdges = el => {
    if (!el) return;
    const max = el.scrollWidth - el.clientWidth;
    setHasOverflow(prev => prev === max > 8 ? prev : max > 8);
    const next = {
      left: el.scrollLeft > 4,
      right: el.scrollLeft < max - 4
    };
    setEdges(prev => prev.left === next.left && prev.right === next.right ? prev : next);
    const cards = cardRefs.current.filter(Boolean);
    if (cards.length) {
      let best = 0;
      let bestDist = Infinity;
      cards.forEach((card, i) => {
        const dist = Math.abs(card.offsetLeft - el.scrollLeft - 4);
        if (dist < bestDist) {
          bestDist = dist;
          best = i;
        }
      });
      setInView(prev => prev === best ? prev : best);
    }
  };
  const attachTrack = el => {
    trackRef.current = el;
    if (!el || measuredRef.current) return;
    measuredRef.current = true;
    if (typeof requestAnimationFrame !== "undefined") {
      requestAnimationFrame(() => updateEdges(el));
    } else {
      updateEdges(el);
    }
  };
  const scrollByCard = dir => {
    const el = trackRef.current;
    if (!el) return;
    const first = cardRefs.current.find(Boolean);
    const cardW = first ? first.offsetWidth + 20 : el.clientWidth * 0.8;
    el.scrollBy({
      left: dir * cardW,
      behavior: "smooth"
    });
  };
  const scrollToCard = idx => {
    const el = trackRef.current;
    const card = cardRefs.current[idx];
    if (!el || !card) return;
    el.scrollTo({
      left: card.offsetLeft - 4,
      behavior: "smooth"
    });
  };
  const startVideo = event => {
    const video = event.currentTarget;
    video.muted = video.dataset.soundOn !== "1";
    const p = video.play?.();
    if (p && typeof p.catch === "function") p.catch(() => {});
  };
  const renderIO = io => {
    const parts = io.split(/→|->/);
    return parts.map((p, i) => <span key={i} className="feature-slide__io-part">
        <span>{p.trim()}</span>
        {i < parts.length - 1 && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <line x1="5" y1="12" x2="19" y2="12" />
            <polyline points="12 5 19 12 12 19" />
          </svg>}
      </span>);
  };
  const renderMedia = (ex, title, cardIdx, exIdx, soundIsOn) => ex.video ? <video key={`v-${cardIdx}-${exIdx}`} ref={el => attachVideo(el, cardIdx)} src={ex.video} poster={ex.poster || ex.img} loop muted={!soundIsOn} data-sound-on={soundIsOn ? "1" : "0"} playsInline preload="metadata" onLoadedData={startVideo} onCanPlay={startVideo} aria-label={title || ""} className="feature-slide__media-el" /> : <img key={`i-${cardIdx}-${exIdx}`} src={ex.img} alt={title || ""} draggable={false} className="feature-slide__media-el" />;
  return <div className="not-prose feature-slider">
      {hasOverflow && <button type="button" className="feature-slider__arrow feature-slider__arrow--prev" onClick={() => scrollByCard(-1)} disabled={!edges.left} aria-label="Previous feature">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polyline points="15 18 9 12 15 6" />
          </svg>
        </button>}

      <div className="feature-slider__track" ref={attachTrack} onScroll={e => updateEdges(e.currentTarget)}>
        {items.map((item, idx) => {
    const examples = item.examples && item.examples.length ? item.examples : [{
      video: item.video,
      img: item.img,
      poster: item.poster
    }];
    const n = examples.length;
    const cur = activeOf(idx);
    const ex = examples[Math.min(cur, n - 1)];
    const soundIsOn = !!soundOn[idx];
    const linkLabel = item.linkLabel || (item.eyebrow ? `Explore ${item.eyebrow}` : "Explore");
    return <article key={idx} className="feature-slide" ref={el => {
      cardRefs.current[idx] = el;
    }}>
              <div className="feature-slide__media" onTouchStart={e => {
      __fsMediaTouchStartX = e.touches[0].clientX;
    }} onTouchEnd={e => {
      if (n < 2) return;
      const dx = e.changedTouches[0].clientX - __fsMediaTouchStartX;
      if (dx < -40) step(idx, 1, n); else if (dx > 40) step(idx, -1, n);
    }}>
                {renderMedia(ex, item.title, idx, cur, soundIsOn)}
                <span className="feature-slide__wash" />

                {ex.video && <button type="button" onClick={() => toggleSound(idx)} aria-label={soundIsOn ? "Mute" : "Unmute"} aria-pressed={soundIsOn} className="feature-slide__media-btn feature-slide__sound-btn" style={{
      position: "absolute",
      top: "0.6rem",
      right: "0.6rem",
      zIndex: 2
    }}>
                    {soundIsOn ? <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
                        <path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
                        <path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
                      </svg> : <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                        <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
                        <line x1="23" y1="9" x2="17" y2="15" />
                        <line x1="17" y1="9" x2="23" y2="15" />
                      </svg>}
                  </button>}

                {n > 1 && <>
                    <div className="feature-slide__media-arrows">
                      <button type="button" onClick={() => step(idx, -1, n)} aria-label="Previous example" className="feature-slide__media-btn">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                          <polyline points="15 18 9 12 15 6" />
                        </svg>
                      </button>
                      <button type="button" onClick={() => step(idx, 1, n)} aria-label="Next example" className="feature-slide__media-btn">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                          <polyline points="9 18 15 12 9 6" />
                        </svg>
                      </button>
                    </div>
                    <div className="feature-slide__media-dots">
                      {examples.map((_, exIdx) => <button key={exIdx} type="button" onClick={() => goTo(idx, exIdx)} aria-label={`Show example ${exIdx + 1}`} className={`feature-slide__media-dot${exIdx === cur ? " is-active" : ""}`} />)}
                    </div>
                  </>}
              </div>

              <div className="feature-slide__body">
                {item.eyebrow && <span className="feature-slide__eyebrow">{item.eyebrow}</span>}
                {item.title && <h3 className="feature-slide__title">{item.title}</h3>}
                {item.io && <span className="feature-slide__io">{renderIO(item.io)}</span>}
                {item.desc && <p className="feature-slide__desc">{item.desc}</p>}
                {item.href && <a className="feature-slide__link" href={item.href}>
                    {linkLabel}
                    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <line x1="5" y1="12" x2="19" y2="12" />
                      <polyline points="12 5 19 12 12 19" />
                    </svg>
                  </a>}
              </div>
            </article>;
  })}
      </div>

      {hasOverflow && <button type="button" className="feature-slider__arrow feature-slider__arrow--next" onClick={() => scrollByCard(1)} disabled={!edges.right} aria-label="Next feature">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <polyline points="9 18 15 12 9 6" />
          </svg>
        </button>}

      {hasOverflow && items.length > 1 && <div className="feature-slider__dots">
          {items.map((item, idx) => <button key={idx} type="button" onClick={() => scrollToCard(idx)} aria-label={`Go to ${item.eyebrow || `feature ${idx + 1}`}`} className={`feature-slider__dot${idx === inView ? " is-active" : ""}`} />)}
        </div>}
    </div>;
};

export const RequestLifecycle = ({resultVideo = "https://cdn.sanity.io/files/2gpum2i6/production/9b5ab883bfd9c68fec643908cd283357954b8187.mp4", apiKeyHref = "https://dashboard.bfl.ai"}) => {
  const MODES = [{
    uid: "t2v",
    id: "t2v",
    name: "Text to Video",
    json: [],
    js: []
  }, {
    uid: "i2v-still",
    id: "i2v",
    name: "Animate a Still",
    json: ['"keyframes": "https://…/start.jpg",'],
    js: ['keyframes: "https://…/start.jpg",']
  }, {
    uid: "i2v-pair",
    id: "i2v",
    name: "Start + End Frame",
    json: ['"keyframes": ["https://…/a.jpg", "https://…/b.jpg"],'],
    js: ['keyframes: ["https://…/a.jpg", "https://…/b.jpg"],']
  }, {
    uid: "i2v-timed",
    id: "i2v",
    name: "Timestamped Keyframes",
    json: ['"keyframes": [[0, "https://…/a.jpg"], [2.5, "https://…/b.jpg"], [5, "https://…/c.jpg"]],'],
    js: ['keyframes: [[0, "https://…/a.jpg"], [2.5, "https://…/b.jpg"], [5, "https://…/c.jpg"]],']
  }, {
    uid: "v2v",
    id: "v2v",
    name: "Video Continuation",
    json: ['"start_video": "https://…/clip.mp4",'],
    js: ['start_video: "https://…/clip.mp4",']
  }];
  const indentJoin = (lines, pad) => lines.length ? "\n" + pad + lines.join("\n" + pad) : "";
  const submitCode = (m, lang) => {
    const modeList = "t2v, i2v, v2v";
    if (lang === "python") {
      return `import os, requests

resp = requests.post(
    "https://api.bfl.ai/v1/flux-3-video",
    headers={
        "x-key": os.environ["BFL_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "mode": "${m.id}",${m.json.length ? "" : "                 # " + modeList}
        "prompt": "a fox running through dawn mist",${indentJoin(m.json, "        ")}
        "resolution": "hd",           # or fhd
        "duration": 5,                # 5–20, or "auto"
        "generate_audio": True,       # synchronized sound, same call
    },
).json()

request_id = resp["id"]
polling_url = resp["polling_url"]   # always poll this exact URL`;
    }
    if (lang === "typescript") {
      return `const resp = await fetch("https://api.bfl.ai/v1/flux-3-video", {
  method: "POST",
  headers: {
    "x-key": process.env.BFL_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    mode: "${m.id}",${m.js.length ? "" : "              // " + modeList}
    prompt: "a fox running through dawn mist",${indentJoin(m.js, "    ")}
    resolution: "hd",            // or fhd
    duration: 5,                 // 5-20, or "auto"
    generate_audio: true,        // synchronized sound, same call
  }),
}).then((r) => r.json());

const { id: requestId, polling_url: pollingUrl } = resp;`;
    }
    return `request=$(curl -s -X POST https://api.bfl.ai/v1/flux-3-video \\
  -H "x-key: $BFL_API_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{
    "mode": "${m.id}",
    "prompt": "a fox running through dawn mist",${indentJoin(m.json, "    ")}
    "resolution": "hd",
    "duration": 5,
    "generate_audio": true
  }')

polling_url=$(echo $request | jq -r .polling_url)`;
  };
  const pollCode = lang => {
    if (lang === "python") {
      return `import time

while True:
    time.sleep(1)
    result = requests.get(
        polling_url,
        headers={"x-key": os.environ["BFL_API_KEY"]},
    ).json()

    if result["status"] == "Ready":
        print("Video:", result["result"]["sample"])
        break
    if result["status"] in ("Error", "Failed"):
        print("Generation failed:", result)
        break`;
    }
    if (lang === "typescript") {
      return `while (true) {
  await new Promise((r) => setTimeout(r, 1000));

  const result = await fetch(pollingUrl, {
    headers: { "x-key": process.env.BFL_API_KEY },
  }).then((r) => r.json());

  if (result.status === "Ready") {
    console.log("Video:", result.result.sample);
    break;
  }
  if (["Error", "Failed"].includes(result.status)) {
    console.error("Generation failed:", result);
    break;
  }
}`;
    }
    return `while true; do
  sleep 1
  result=$(curl -s -X GET "$polling_url" -H "x-key: $BFL_API_KEY")
  status=$(echo $result | jq -r .status)
  echo "Status: $status"

  if [ "$status" = "Ready" ]; then
    echo "Video: $(echo $result | jq -r .result.sample)"
    break
  elif [ "$status" = "Error" ] || [ "$status" = "Failed" ]; then
    echo "Generation failed: $result"
    break
  fi
done`;
  };
  const cream = "rgba(246, 241, 231, 0.94)";
  const muted = "rgba(246, 241, 231, 0.46)";
  const faint = "rgba(246, 241, 231, 0.09)";
  const fainter = "rgba(246, 241, 231, 0.05)";
  const mono = '"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace';
  const sans = '"Instrument Sans", sans-serif';
  const accent = "#a6c98a";
  const syn = {
    kw: "#c98aa6",
    str: "#a9c98a",
    num: "#e3b467",
    com: "rgba(246,241,231,0.32)",
    punc: "rgba(246,241,231,0.55)",
    fn: "#7bb0c4"
  };
  const KW = {
    python: "import|from|while|True|False|None|in|if|break|def|return|and|or|not",
    typescript: "const|let|await|new|while|true|false|null|return|if|function|includes",
    curl: "curl|while|do|done|if|then|elif|else|fi|echo|break|sleep"
  };
  const highlightLine = (line, lang) => {
    const marker = lang === "typescript" ? "//" : "#";
    let codePart = line;
    let commentPart = "";
    const ci = line.indexOf(marker);
    if (ci >= 0) {
      const before = line.slice(0, ci);
      if ((before.match(/"/g) || []).length % 2 === 0) {
        codePart = before;
        commentPart = line.slice(ci);
      }
    }
    const kw = KW[lang] || KW.python;
    const re = new RegExp('("(?:[^"\\\\]|\\\\.)*")|(\'(?:[^\'\\\\]|\\\\.)*\')|(\\b\\d[\\w]*\\b)|(\\b(?:' + kw + ')\\b)|([A-Za-z_]\\w*)(?=\\()', "g");
    const tokens = [];
    let last = 0;
    let mm;
    while ((mm = re.exec(codePart)) !== null) {
      if (mm.index > last) tokens.push({
        t: codePart.slice(last, mm.index),
        c: syn.punc
      });
      let color = cream;
      if (mm[1] || mm[2]) color = syn.str; else if (mm[3]) color = syn.num; else if (mm[4]) color = syn.kw; else if (mm[5]) color = syn.fn;
      tokens.push({
        t: mm[0],
        c: color
      });
      last = mm.index + mm[0].length;
    }
    if (last < codePart.length) tokens.push({
      t: codePart.slice(last),
      c: syn.punc
    });
    if (commentPart) tokens.push({
      t: commentPart,
      c: syn.com
    });
    return tokens;
  };
  const renderCode = (code, lang) => {
    const lines = code.split("\n");
    return lines.map((ln, i) => <div key={i} style={{
      minHeight: "1.05rem"
    }}>
        {ln === "" ? "​" : highlightLine(ln, lang).map((tk, j) => <span key={j} style={{
      color: tk.c
    }}>
                {tk.t}
              </span>)}
      </div>);
  };
  const [modeId, setModeId] = useState("t2v");
  const [stage, setStage] = useState("submit");
  const [lang, setLang] = useState("python");
  const [copied, setCopied] = useState(false);
  const mode = MODES.find(m => m.uid === modeId) || MODES[0];
  const stageIndex = stage === "submit" ? 0 : stage === "poll" ? 1 : 2;
  const code = stage === "submit" ? submitCode(mode, lang) : stage === "poll" ? pollCode(lang) : "";
  const copy = () => {
    try {
      navigator.clipboard.writeText(code);
      setCopied(true);
      setTimeout(() => setCopied(false), 1600);
    } catch (e) {}
  };
  const STEPS = [{
    key: "submit",
    label: "Submit",
    color: "#e3b467"
  }, {
    key: "poll",
    label: "Poll",
    color: "#7bb0c4"
  }, {
    key: "ready",
    label: "Ready",
    color: "#6cc38a"
  }];
  const langs = [{
    id: "python",
    label: "Python"
  }, {
    id: "typescript",
    label: "TypeScript"
  }, {
    id: "curl",
    label: "cURL"
  }];
  const Segmented = ({options, value, onChange, size = "md"}) => <div style={{
    display: "inline-flex",
    padding: "0.2rem",
    gap: "0.15rem",
    borderRadius: "999px",
    border: `1px solid ${faint}`,
    background: fainter
  }}>
      {options.map(o => {
    const active = o.id === value;
    return <button key={o.id} type="button" onClick={() => onChange(o.id)} title={o.title} style={{
      padding: size === "sm" ? "0.24rem 0.6rem" : "0.3rem 0.7rem",
      borderRadius: "999px",
      border: "none",
      cursor: "pointer",
      fontFamily: o.mono ? mono : sans,
      fontSize: size === "sm" ? "0.76rem" : "0.8rem",
      fontWeight: active ? 600 : 500,
      letterSpacing: o.mono ? "0" : "0.01em",
      color: active ? "#0c1712" : muted,
      background: active ? accent : "transparent",
      transition: "color 140ms ease, background 140ms ease"
    }}>
            {o.label}
          </button>;
  })}
    </div>;
  return <div className="not-prose req-flow" style={{
    borderRadius: "1rem",
    border: `1px solid ${faint}`,
    background: "linear-gradient(180deg, #0e1a14 0%, #0a120e 100%)",
    color: cream,
    overflow: "hidden",
    boxShadow: "0 24px 60px -30px rgba(0,0,0,0.7)"
  }}>
      {}
      <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    gap: "1rem",
    flexWrap: "wrap",
    padding: "0.95rem 1.15rem"
  }}>
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.35rem"
  }}>
          {STEPS.map((s, i) => {
    const done = i < stageIndex;
    const current = i === stageIndex;
    const col = done ? "#6cc38a" : current ? s.color : muted;
    return <div key={s.key} style={{
      display: "flex",
      alignItems: "center",
      gap: "0.35rem"
    }}>
                <button type="button" onClick={() => setStage(s.key)} style={{
      display: "inline-flex",
      alignItems: "center",
      gap: "0.5rem",
      padding: "0.28rem 0.6rem 0.28rem 0.3rem",
      borderRadius: "999px",
      background: current ? "rgba(255,255,255,0.05)" : "transparent",
      border: "none",
      cursor: "pointer",
      transition: "background 140ms ease"
    }}>
                  <span style={{
      display: "inline-flex",
      alignItems: "center",
      justifyContent: "center",
      width: "1.35rem",
      height: "1.35rem",
      borderRadius: "999px",
      border: `1.5px solid ${col}`,
      color: done ? "#0c1712" : col,
      background: done ? "#6cc38a" : "transparent",
      fontFamily: mono,
      fontSize: "0.68rem",
      fontWeight: 700
    }}>
                    {done ? "✓" : i + 1}
                  </span>
                  <span style={{
      fontFamily: sans,
      fontSize: "0.86rem",
      fontWeight: 600,
      color: current || done ? cream : muted
    }}>
                    {s.label}
                  </span>
                </button>
                {i < STEPS.length - 1 ? <span style={{
      width: "1.6rem",
      height: "1px",
      background: faint
    }} /> : null}
              </div>;
  })}
        </div>

        <a href={apiKeyHref} target="_blank" rel="noreferrer" style={{
    display: "inline-flex",
    alignItems: "center",
    gap: "0.4rem",
    padding: "0.4rem 0.85rem",
    borderRadius: "999px",
    border: "none",
    background: accent,
    color: "#0c1712",
    fontFamily: sans,
    fontSize: "0.82rem",
    fontWeight: 600,
    textDecoration: "none",
    whiteSpace: "nowrap"
  }}>
          Get API key
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M7 17 17 7" />
            <path d="M7 7h10v10" />
          </svg>
        </a>
      </div>

      {}
      <div style={{
    height: "2px",
    background: faint
  }}>
        <div style={{
    height: "100%",
    width: `${(stageIndex + 1) / 3 * 100}%`,
    background: `linear-gradient(90deg, ${STEPS[0].color}, ${STEPS[stageIndex].color})`,
    transition: "width 300ms cubic-bezier(0.4,0,0.2,1)"
  }} />
      </div>

      <div style={{
    padding: "1.15rem 1.15rem 1.25rem"
  }}>
        {}
        {stage === "submit" ? <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.7rem",
    flexWrap: "wrap",
    marginBottom: "1rem"
  }}>
            <span style={{
    fontFamily: sans,
    fontSize: "0.72rem",
    fontWeight: 700,
    letterSpacing: "0.09em",
    textTransform: "uppercase",
    color: muted
  }}>
              Mode
            </span>
            <Segmented size="sm" value={modeId} onChange={setModeId} options={MODES.map(m => ({
    id: m.uid,
    label: m.name,
    title: m.name + " (" + m.id + ")",
    mono: false
  }))} />
            <span style={{
    fontFamily: sans,
    fontSize: "0.84rem",
    color: cream,
    fontWeight: 500
  }}>
              {mode.name}
            </span>
          </div> : null}

        {stage === "poll" ? <p style={{
    margin: "0 0 1rem",
    fontFamily: sans,
    fontSize: "0.86rem",
    lineHeight: 1.5,
    color: muted
  }}>
            One loop for every mode — poll the{" "}
            <code style={{
    fontFamily: mono,
    color: cream
  }}>polling_url</code> once a second until the status is{" "}
            <code style={{
    fontFamily: mono,
    color: "#6cc38a"
  }}>Ready</code>.
          </p> : null}

        {}
        {stage !== "ready" ? <div style={{
    borderRadius: "0.7rem",
    border: `1px solid ${faint}`,
    overflow: "hidden",
    background: "#0a0f0c"
  }}>
            {}
            <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    gap: "0.75rem",
    flexWrap: "wrap",
    padding: "0.6rem 0.75rem",
    borderBottom: `1px solid ${faint}`,
    background: "rgba(255,255,255,0.015)"
  }}>
              <div style={{
    display: "flex",
    gap: "0.4rem",
    alignItems: "center"
  }}>
                <span style={{
    width: "0.7rem",
    height: "0.7rem",
    borderRadius: "999px",
    background: "#ff5f57"
  }} />
                <span style={{
    width: "0.7rem",
    height: "0.7rem",
    borderRadius: "999px",
    background: "#febc2e"
  }} />
                <span style={{
    width: "0.7rem",
    height: "0.7rem",
    borderRadius: "999px",
    background: "#28c840"
  }} />
              </div>
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.6rem"
  }}>
                <Segmented size="sm" value={lang} onChange={setLang} options={langs} />
                <button type="button" onClick={copy} aria-label="Copy code" style={{
    display: "inline-flex",
    alignItems: "center",
    gap: "0.35rem",
    padding: "0.32rem 0.65rem",
    borderRadius: "0.5rem",
    border: `1px solid ${faint}`,
    background: "transparent",
    color: copied ? "#6cc38a" : muted,
    fontFamily: sans,
    fontSize: "0.76rem",
    fontWeight: 600,
    cursor: "pointer",
    transition: "color 140ms ease"
  }}>
                  {copied ? <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <polyline points="20 6 9 17 4 12" />
                    </svg> : <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
                      <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
                    </svg>}
                  {copied ? "Copied" : "Copy"}
                </button>
              </div>
            </div>
            <pre style={{
    margin: 0,
    padding: "1rem 1.1rem",
    maxHeight: "22rem",
    overflow: "auto",
    background: "transparent",
    fontFamily: mono,
    fontSize: "0.82rem",
    lineHeight: 1.6
  }}>
              <code style={{
    fontFamily: mono,
    whiteSpace: "pre"
  }}>{renderCode(code, lang)}</code>
            </pre>
          </div> : <div style={{
    display: "grid",
    gridTemplateColumns: "minmax(0, 1fr) 9.5rem",
    gap: "1.25rem",
    alignItems: "center"
  }}>
            <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.7rem",
    minWidth: 0
  }}>
              <span style={{
    display: "inline-flex",
    alignItems: "center",
    gap: "0.45rem",
    alignSelf: "flex-start",
    padding: "0.3rem 0.75rem",
    borderRadius: "999px",
    border: "1px solid rgba(108,195,138,0.5)",
    background: "rgba(108,195,138,0.14)",
    color: "#6cc38a",
    fontFamily: sans,
    fontSize: "0.82rem",
    fontWeight: 600
  }}>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                  <polyline points="20 6 9 17 4 12" />
                </svg>
                status: Ready
              </span>
              <div style={{
    fontFamily: mono,
    fontSize: "0.82rem",
    lineHeight: 1.7,
    color: muted
  }}>
                <div>
                  <span style={{
    color: "#7bb0c4"
  }}>GET</span>{" "}
                  <span style={{
    color: cream
  }}>polling_url</span> →{" "}
                  <span style={{
    color: "#6cc38a"
  }}>Ready</span>
                </div>
                <div>
                  <span style={{
    color: syn.punc
  }}>result.sample</span> →{" "}
                  <span style={{
    color: "#a9c98a",
    wordBreak: "break-all"
  }}>https://delivery…/out.mp4</span>
                </div>
              </div>
              <p style={{
    margin: "0.1rem 0 0",
    fontFamily: sans,
    fontSize: "0.84rem",
    lineHeight: 1.5,
    color: muted
  }}>
                The signed result URL is valid for about 10 minutes — download it promptly.
              </p>
            </div>
            <div style={{
    position: "relative",
    aspectRatio: "3 / 4",
    borderRadius: "0.6rem",
    overflow: "hidden",
    background: "#08110d",
    border: `1px solid ${faint}`,
    boxShadow: "0 16px 40px -24px rgba(0,0,0,0.9)"
  }}>
              <video src={resultVideo} autoPlay loop muted playsInline preload="metadata" aria-hidden="true" style={{
    position: "absolute",
    inset: 0,
    width: "100%",
    height: "100%",
    objectFit: "cover",
    display: "block"
  }} />
            </div>
          </div>}
      </div>
    </div>;
};

export const DraftTimeline = ({draftSeconds, enhanceSeconds, fullSeconds, title = "Generation time", note = "Bar lengths are measured submit-to-Ready times for the same prompt and settings at hd, shown relative to the draft preview. Absolute times vary with load."}) => {
  const ratio = s => `${(s / draftSeconds).toFixed(1).replace(/\.0$/, "")}x`;
  const max = Math.max(fullSeconds, draftSeconds + (enhanceSeconds || 0)) * 1.08;
  const rows = [{
    label: "Draft preview",
    sub: "draft: true",
    segments: [{
      from: 0,
      len: draftSeconds,
      kind: "draft",
      tag: "baseline"
    }]
  }, {
    label: "Full render",
    sub: "one request",
    segments: [{
      from: 0,
      len: fullSeconds,
      kind: "full",
      tag: ratio(fullSeconds)
    }]
  }];
  if (enhanceSeconds) {
    rows.push({
      label: "Draft, then enhance the winner",
      sub: "draft_enhance",
      segments: [{
        from: 0,
        len: draftSeconds,
        kind: "draft",
        tag: null
      }, {
        from: draftSeconds,
        len: enhanceSeconds,
        kind: "enhance",
        tag: `${ratio(draftSeconds + enhanceSeconds)} total`
      }]
    });
  }
  return <div className="not-prose draft-timeline">
      <style>{`
        .draft-timeline {
          margin: 1.75rem 0;
          border: 1px solid rgba(72,106,88,0.2);
          border-radius: 1rem;
          padding: 1.4rem 1.5rem 1.1rem;
          background: var(--aspen-paper, #fffdf9);
        }
        .dark .draft-timeline {
          background: rgba(72,106,88,0.06);
          border-color: rgba(246,241,231,0.14);
        }
        .draft-timeline__title {
          margin: 0 0 1.1rem;
          font-size: 0.78rem; font-weight: 700;
          letter-spacing: 0.08em; text-transform: uppercase;
          color: var(--aspen-ink, #163126);
        }
        .dark .draft-timeline__title { color: rgba(246,241,231,0.92); }
        .draft-timeline__row { margin-bottom: 1.05rem; }
        .draft-timeline__row:last-of-type { margin-bottom: 0.4rem; }
        .draft-timeline__label {
          display: flex; align-items: baseline; gap: 0.5rem;
          margin-bottom: 0.35rem;
          font-size: 0.86rem; font-weight: 600;
          color: var(--aspen-ink, #163126);
        }
        .dark .draft-timeline__label { color: rgba(246,241,231,0.92); }
        .draft-timeline__sub {
          font-family: "IBM Plex Mono", ui-monospace, monospace;
          font-size: 0.72rem; font-weight: 500;
          color: rgba(22,49,38,0.55);
        }
        .dark .draft-timeline__sub { color: rgba(246,241,231,0.55); }
        .draft-timeline__track {
          position: relative; height: 1.5rem;
          border-radius: 0.4rem;
          background: rgba(72,106,88,0.08);
          overflow: hidden;
        }
        .dark .draft-timeline__track { background: rgba(246,241,231,0.07); }
        .draft-timeline__seg {
          position: absolute; top: 0; bottom: 0;
          display: flex; align-items: center; justify-content: flex-end;
          padding-right: 0.55rem;
          font-family: "IBM Plex Mono", ui-monospace, monospace;
          font-size: 0.72rem; font-weight: 600; white-space: nowrap;
        }
        .draft-timeline__seg--draft { background: var(--aspen-evergreen, #486a58); color: #fffdf9; border-radius: 0.4rem; }
        .draft-timeline__seg--enhance { background: rgba(72,106,88,0.45); color: #fffdf9; border-radius: 0 0.4rem 0.4rem 0; }
        .draft-timeline__seg--full { background: var(--aspen-stone, #b5ae9a); color: var(--aspen-ink, #163126); border-radius: 0.4rem; }
        .draft-timeline__note {
          margin-top: 0.9rem;
          font-size: 0.74rem; line-height: 1.5;
          color: rgba(22,49,38,0.55);
        }
        .dark .draft-timeline__note { color: rgba(246,241,231,0.5); }
      `}</style>

      <p className="draft-timeline__title">{title}</p>

      {rows.map((row, i) => <div key={i} className="draft-timeline__row">
          <div className="draft-timeline__label">
            <span>{row.label}</span>
            <span className="draft-timeline__sub">{row.sub}</span>
          </div>
          <div className="draft-timeline__track">
            {row.segments.map((seg, j) => <span key={j} className={`draft-timeline__seg draft-timeline__seg--${seg.kind}`} style={{
    left: `${seg.from / max * 100}%`,
    width: `${seg.len / max * 100}%`
  }}>
                {seg.tag}
              </span>)}
          </div>
        </div>)}

      <p className="draft-timeline__note">{note}</p>
    </div>;
};

<div className="flux3-overview-page flux3-page">
  <VideoReelMosaic
    title="Generated with FLUX 3"
    itemWidth="clamp(14rem, 20vw, 18rem)"
    margin="0 0 2.5rem"
    showTopBorder={false}
    paddingTop="0.5rem"
    rows={[
[
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/b5e1de89047f707c5717ee5d712e19fddbe5884d.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/9fc7558baa172434b09a8935b611ca8f3886266c.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/42c41436937dc187bca741107e014dd56e0caa10.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/b5bf636b03765786e3b6d06d506f1584de93fc24.mp4" },
],
[
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/9b5ab883bfd9c68fec643908cd283357954b8187.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/c600a7611a8d7331a923706f73993a34232cbe14.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/37ae0ee4b6ad3190ea55cc2378bff99a40dad6c1.mp4" },
  { video: "https://cdn.sanity.io/files/2gpum2i6/production/0179ca5004d5cbbf466f42e2c9a7e3a0b3549317.mp4" },
],
]}
  />

  <div className="not-prose flux3-overview-copy">
    <p className="flux3-overview-copy__eyebrow">FLUX 3</p>
    <h1>Overview</h1>

    <p>
      One model trained across image, video, and audio. Video with synchronized
      sound, live now.
    </p>
  </div>

  ## One model, multiple modalities

  FLUX 3 generates **video with synchronized audio**:
  from a text prompt, from pinned keyframes, or continuing an existing clip.
  More modalities ship on the same request shape as they land.

  * **Up to 20 seconds at FHD** (1920 × 1088 for 16:9), 24 fps, in a single request.
  * **Multilingual speech with strong lipsync**, plus effects and ambience, generated with the frames.
  * **Multiple scenes and camera angles in one generation.** Shots hold together across cuts.
  * **Stylistic range beyond cinematic:** animation, motion design, stylized artistic looks.
  * **Accurate text and typography** rendered inside the scene.

  <Note>
    FLUX 3 is a **preview** model. **Video editing** and **Omni Reference with
    images and videos** will be available soon.
  </Note>

  ## Modes

  Every request names a mode. The mode is how you tell FLUX 3 what you're
  starting from, and it decides what the model does with your media: start a
  clip from words alone, build one around images you pin, or carry an existing
  clip forward.

  There are three:

  * **Text to Video** (`t2v`) starts from nothing but your prompt.
  * **Image to Video** (`i2v`) starts from your images. They become frames of the clip itself.
  * **Video Continuation** (`v2v`) starts from a clip you already have and keeps it going.

  All three run on the same [flux-3-video endpoint](/api-reference/utility/generate-a-video-with-flux-3), and
  the rest of the request looks identical. To show how they connect, everything
  below is **one scene passed through the whole API**: the first request
  generates a clip, and every request after it runs on that clip's own frames.
  Each tab shows the request we sent and the video that came back.

  <Tabs>
    <Tab title="Text to Video">
      Describe the shot. `t2v` turns the prompt into a clip, sound included.
      This is the request that started the scene:

      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "mode": "t2v",
          "prompt": "she takes his hand and pulls him laughing through the lantern-lit alley, the camera chasing them, paper lanterns swaying overhead, their footsteps and laughter echoing off the walls",
          "duration": 8
        }'
      ```

      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem", marginTop: "1rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/modes-demo/alley.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=b64172fd2beca22bc00c5df0caea553d" data-path="images/flux3/modes-demo/alley.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>The clip this request returned. The next two tabs build on it.</p>
    </Tab>

    <Tab title="Image to Video">
      There is no separate start-frame field: `keyframes` is how you hand `i2v`
      images, and one image is the start frame. We extracted the **opening frame
      of the clip in the first tab** and pinned it, with a new camera direction:

      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "mode": "i2v",
          "prompt": "from this frame the camera rises slowly above the alley as they rush away beneath the lanterns, their laughter fading into the night",
          "keyframes": "data:image/png;base64,<opening frame>"
        }'
      ```

      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem", marginTop: "1rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/modes-demo/alley_alt.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=b47f41eb8ac928a614c18aa57cf348ac" data-path="images/flux3/modes-demo/alley_alt.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>Same first frame, different film: the pinned image opens the clip exactly, then the prompt takes over.</p>

      More images, more control: two pin the start and end frames, up to ten
      storyboard the clip, and `[seconds, image]` pairs pin each one to an
      exact moment. Here the hero clip's **first and last frames** are pinned at
      `0` and `8` seconds and the model finds its own path between them:

      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "mode": "i2v",
          "prompt": "they sprint the length of the lantern-lit alley, lanterns blurring past, footsteps quick on the wet stones",
          "keyframes": [[0, "data:image/png;base64,<first frame>"], [8, "data:image/png;base64,<last frame>"]],
          "duration": 8
        }'
      ```

      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem", marginTop: "1rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/modes-demo/alley_between.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=89b5095f3a8b17d4f62142b41e2ed27d" data-path="images/flux3/modes-demo/alley_between.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>Both pins hit exactly; the sprint in between is the model's.</p>
    </Tab>

    <Tab title="Video Continuation">
      `v2v` picks up where your clip ends. Momentum, framing, and scene logic
      carry into the new footage. We sent the **first tab's clip** as
      `start_video` and asked for the scene's next beat:

      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "mode": "v2v",
          "prompt": "they burst out of the alley into a crowded night market, drums and street chatter swelling, she pulls him into the lantern light",
          "start_video": "data:video/mp4;base64,<the clip from the first tab>",
          "duration": 8
        }'
      ```

      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem", marginTop: "1rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/modes-demo/alley_continued.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=39fb34873ddf20e3f746a06d313f8481" data-path="images/flux3/modes-demo/alley_continued.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>The same couple, out of the alley and into the market: continuation keeps the scene's logic and sound.</p>
    </Tab>
  </Tabs>

  Everything else is optional. Set any field explicitly and it is used exactly;
  leave it out and the default takes over:

  * **`aspect_ratio`** and **`duration`** default to `auto` and fit themselves to the content.
  * **`resolution`** defaults to `hd`; `fhd` finishes the result at a higher resolution via the video upsampler.
  * **`generate_audio`** defaults to `true`.

  The full field list, constraints, and dimensions live in the
  [API reference](/api-reference/utility/generate-a-video-with-flux-3).

  ## Draft mode

  Iterate in draft, commit once. Drafts generate faster and cost about a third
  of a full render, so you can explore variants freely and only pay full price
  for the shot you keep.

  * **`draft: true`** returns a fast preview instead of a full render, plus a `draft_cache` bundle in the result.
  * **`mode: "draft_enhance"`** renders the preview you picked at full quality: send its bundle as `draft_cache` and the original generation is reproduced. Same shot, same seed, nothing re-interpreted.

  Here is the same prompt run through all three paths:

  <DraftTimeline draftSeconds={61} enhanceSeconds={83} fullSeconds={108} />

  Each fresh submit is its own generation, so the direct full render can
  interpret the shot differently from the draft you liked. Enhancing keeps it:

  <Columns cols={3}>
    <div>
      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/draft-demo/mocap_draft.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=ed697a1f09d420f180c1ffaa804051ea" data-path="images/flux3/draft-demo/mocap_draft.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>Draft preview</p>
    </div>

    <div>
      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/draft-demo/mocap_enhanced.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=291613d06877c8cadfe3286656f473d1" data-path="images/flux3/draft-demo/mocap_enhanced.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>Enhanced: the draft's shot, full quality</p>
    </div>

    <div>
      <video controls muted playsInline preload="metadata" style={{ width: "100%", display: "block", borderRadius: "0.5rem" }} src="https://mintcdn.com/bfl/gUyNpVEEjK1_M2Gr/images/flux3/draft-demo/mocap_full.mp4?fit=max&auto=format&n=gUyNpVEEjK1_M2Gr&q=85&s=998207a79cfaa9fb13a99851a421f99a" data-path="images/flux3/draft-demo/mocap_full.mp4" />

      <p style={{ textAlign: "center", marginTop: "0.5rem", opacity: 0.7 }}>Direct full render: its own take</p>
    </div>
  </Columns>

  <Tabs>
    <Tab title="1 — Draft">
      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "mode": "t2v",
          "prompt": "a fox running through dawn mist",
          "draft": true
        }'
      ```
    </Tab>

    <Tab title="2 — Enhance the one you picked">
      Download the winning preview's `draft_cache` URL from its result, then send
      the bundle back:

      ```bash theme={null}
      curl -X POST https://api.bfl.ai/v1/flux-3-video \
        -H "x-key: $BFL_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{
          \"mode\": \"draft_enhance\",
          \"draft_cache\": \"$(base64 -i draft_cache.bin)\"
        }"
      ```
    </Tab>
  </Tabs>

  Limits and constraints are in the [API reference](/api-reference/utility/generate-a-video-with-flux-3).

  ## Specifications

  | Mode                   | You send               | Length | Full render               | Draft    |
  | ---------------------- | ---------------------- | ------ | ------------------------- | -------- |
  | **Text to Video**      | a prompt               | 5–20 s | \$0.17/s hd, \$0.29/s fhd | \$0.06/s |
  | **Image to Video**     | a prompt + 1–10 images | 5–20 s | \$0.17/s hd, \$0.29/s fhd | \$0.06/s |
  | **Video Continuation** | a prompt + your clip   | 5–15 s | \$0.43/s hd, \$0.54/s fhd | \$0.12/s |

  Every mode outputs 24 fps at `hd` or `fhd`, in aspect ratios 21:9, 2:1, 16:9,
  4:3, 1:1, 3:4, and 9:16. Drafts render at `hd`.

  ## What it can do

  <FeatureSlider
    items={[
{
  eyebrow: "Video",
  title: "Motion, from a single line.",
  io: "Text, Image, Video → Video",
  desc: "Generate up to 20 seconds at FHD from a prompt, animate a still, choreograph a shot through pinned keyframes, or continue an existing clip. All through the same model.",
  href: "/flux_3/flux3_video",
  examples: [
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/678a974c9a9f41c9619309d06a6bfa508edf05f0.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/bac965ff8ea5d670a4632c56a45abcf9909000c8.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/b07adc41e6eacf5511d832575474fc54dc15000b.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/da815568ca416cd62a392dd1d7fed0e51980a830.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/9b5ab883bfd9c68fec643908cd283357954b8187.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/b5e1de89047f707c5717ee5d712e19fddbe5884d.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/c600a7611a8d7331a923706f73993a34232cbe14.mp4" },
  ],
},
{
  eyebrow: "Image to Video",
  title: "From still to motion.",
  io: "Image → Video",
  desc: "Pin a still as the opening frame, interpolate between a start and end frame, or choreograph a shot through pinned keyframes.",
  href: "/flux_3/flux3_video",
  examples: [
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/3bf3b94bfd068396e9b7b7c73421c5316fbc3048.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/37ae0ee4b6ad3190ea55cc2378bff99a40dad6c1.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/9fc7558baa172434b09a8935b611ca8f3886266c.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/42c41436937dc187bca741107e014dd56e0caa10.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/0179ca5004d5cbbf466f42e2c9a7e3a0b3549317.mp4" },
  ],
},
{
  eyebrow: "Audio",
  title: "Audio and speech.",
  io: "Video with sound, same generation",
  desc: "Audio is on by default: multilingual speech with lipsync, effects, and ambience rendered scene-aware alongside the frames. No second model, no second pass.",
  href: "/flux_3/flux3_video#audio",
  examples: [
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/64b71d1f3a26538b75b70effee5c05b38370448f.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/4a9e0ac0ccf00cb54dfd32606d3dc41a14760e48.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/ba9e8cac2a35ef23a9e5e6d6e2cf930fc467b2cb.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/d3f4192d655774daec57fdae9b626025a6bc914b.mp4" },
    { video: "https://cdn.sanity.io/files/2gpum2i6/production/7dc7fd79a1d7e37157247b66890d479039ad3e05.mp4" },
  ],
},
]}
  />

  ## Start in ten seconds

  FLUX 3 is asynchronous. You **submit** a request and get back an `id` and a
  `polling_url`; then you **poll** that URL until the job turns `Ready` and returns
  your result. Here is the full round trip, text to video with audio.

  <RequestLifecycle />

  <Warning>
    Result URLs are signed and expire about 2 hours after the job finishes.
    Download the video promptly once the status is `Ready`.
  </Warning>

  ## Getting started

  <CardGroup cols={2}>
    <Card title="Quickstart" icon="rocket" href="/quick_start/get_started">
      Create an account, add credits, and make your first FLUX 3 call.
    </Card>

    <Card title="Try in Playground" icon="play" href="https://playground.bfl.ai">
      Test FLUX 3 in your browser. No setup required.
    </Card>

    <Card title="Video" icon="film" href="/flux_3/flux3_video">
      Generate from text, animate stills, pin keyframes, continue clips, all with audio.
    </Card>

    <Card title="API reference" icon="code" href="/api-reference/utility/generate-a-video-with-flux-3">
      The full flux-3-video request contract: modes, fields, constraints.
    </Card>
  </CardGroup>
</div>
