> ## 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 Image Editing

> Edit an image with a written instruction, combine up to ten references, and use bounding boxes to place elements or target an edit with FLUX 3 Image.

export const FLUX3_EDITING_BBOX_EXAMPLES = [{
  "key": "editing-moth",
  "title": "Leopard-print moth",
  "op": "pattern",
  "instruction": "Change the solid black moth to a leopard print pattern.",
  "before": "https://cdn.sanity.io/images/2gpum2i6/production/5c92570a2416c438d6d4de79527c1f63119a500f-1800x1192.webp",
  "after": "https://cdn.sanity.io/images/2gpum2i6/production/3a7ea6a52fc19507ed2f9d17a5dc26f98a6e38f5-1800x1192.webp",
  "width": 1800,
  "height": 1192,
  "record": "Modify the sharply focused moth on the left side of the glowing light fixture <light_fixture_1> in <ref_image_0>. Replace it with a new moth <moth_1> that features a distinct leopard print pattern on its wings, while maintaining the original flat triangular shape against the white background. Keep all other elements exactly unchanged, including the two sharp moths <moth_2> and <moth_3> on the upper right, the blurred flying moths <moth_4> and <moth_5>, and the scattered tiny insects and shadows <insect_swarm_1>.",
  "rows": [{
    "id": "light_fixture_1",
    "from": "ref_image_0",
    "src_bbox": [0, 0, 850, 1000],
    "tgt_bbox": [0, 0, 850, 1000],
    "desc": "A large, glowing spherical light fixture filling the upper and central frame. Its surface is a textured, matte white with subtle imperfections and a faint horizontal seam running across the middle. It emits a diffuse, brilliant internal light, leaving the bottom area of the frame in deep black shadow.",
    "kind": "anchor"
  }, {
    "id": "moth_1",
    "from": null,
    "src_bbox": null,
    "tgt_bbox": [350, 150, 480, 300],
    "desc": "A sharply focused silhouetted moth with a distinct leopard print pattern on its wings, maintaining its flat triangular shape against the white background.",
    "kind": "new"
  }, {
    "id": "moth_2",
    "from": "ref_image_0",
    "src_bbox": [120, 750, 220, 880],
    "tgt_bbox": [120, 750, 220, 880],
    "desc": "A distinct, black silhouette of a moth with spread wings, resting near the top right edge of the light fixture.",
    "kind": "anchor"
  }, {
    "id": "moth_3",
    "from": "ref_image_0",
    "src_bbox": [150, 850, 260, 960],
    "tgt_bbox": [150, 850, 260, 960],
    "desc": "A sharp moth silhouette clustered near another moth, angled slightly downward, presenting a crisp, dark outline.",
    "kind": "anchor"
  }, {
    "id": "moth_4",
    "from": "ref_image_0",
    "src_bbox": [450, 800, 600, 950],
    "tgt_bbox": [450, 800, 600, 950],
    "desc": "A dark, blurred streak representing a moth in motion, leaving a soft, out-of-focus shadow trail across the white surface.",
    "kind": "anchor"
  }, {
    "id": "moth_5",
    "from": "ref_image_0",
    "src_bbox": [650, 200, 800, 350],
    "tgt_bbox": [650, 200, 800, 350],
    "desc": "An out-of-focus, dark shape of a moth caught in mid-flight, its wings blurring into a soft, grainy smudge.",
    "kind": "anchor"
  }, {
    "id": "insect_swarm_1",
    "from": "ref_image_0",
    "src_bbox": [50, 50, 820, 950],
    "tgt_bbox": [50, 50, 820, 950],
    "desc": "Dozens of tiny black dots, small gnats, and soft out-of-focus shadows scattered randomly across the white surface of the light fixture.",
    "kind": "anchor"
  }]
}, {
  "key": "editing-waterfall",
  "title": "Golden waterfall",
  "op": "recolour",
  "instruction": "Change the white waterfall to a golden-yellow hue.",
  "before": "https://cdn.sanity.io/images/2gpum2i6/production/a49c048fa5f9d5fccc31afbe8e08983213e2d31c-1192x1800.webp",
  "after": "https://cdn.sanity.io/images/2gpum2i6/production/e34420bc057863f90411c420f8478dc049ac0994-1192x1800.webp",
  "width": 1192,
  "height": 1800,
  "record": "Modify <ref_image_0> by changing the central cascading water into a long exposure silky golden-yellow waterfall <waterfall>. Keep the rest of the image exactly unchanged, including the textured dark rock face <rock_wall> behind the falls, the evergreen trees <trees> at the top of the cliff, the misty white water <mist_pool> at the base, and the dark wet rocks <foreground_rocks> in the bottom foreground.",
  "rows": [{
    "id": "waterfall",
    "from": null,
    "src_bbox": null,
    "tgt_bbox": [57, 349, 837, 784],
    "desc": "long exposure silky golden-yellow waterfall",
    "kind": "new"
  }, {
    "id": "rock_wall",
    "from": "ref_image_0",
    "src_bbox": [102, 0, 600, 1000],
    "tgt_bbox": [102, 0, 600, 1000],
    "desc": "textured dark rock face behind the waterfall",
    "kind": "anchor"
  }, {
    "id": "trees",
    "from": "ref_image_0",
    "src_bbox": [0, 132, 156, 762],
    "tgt_bbox": [0, 132, 156, 762],
    "desc": "evergreen trees at the top of the cliff",
    "kind": "anchor"
  }, {
    "id": "mist_pool",
    "from": "ref_image_0",
    "src_bbox": [765, 42, 912, 943],
    "tgt_bbox": [765, 42, 912, 943],
    "desc": "misty white water at the base of the falls",
    "kind": "anchor"
  }, {
    "id": "foreground_rocks",
    "from": "ref_image_0",
    "src_bbox": [901, 570, 985, 744],
    "tgt_bbox": [901, 570, 985, 744],
    "desc": "dark wet rocks in the bottom foreground",
    "kind": "anchor"
  }]
}, {
  "key": "editing-silk",
  "title": "Metallic silk",
  "op": "material",
  "instruction": "Change the purple fabric to metallic silk.",
  "before": "https://cdn.sanity.io/images/2gpum2i6/production/7b386fdaa3dab06baf4e1c9ed44f872442b82937-1800x1350.webp",
  "after": "https://cdn.sanity.io/images/2gpum2i6/production/beeec7b5f21a8ad135d9937a97b09f55b48c3c0e-1800x1350.webp",
  "width": 1800,
  "height": 1350,
  "record": "In <ref_image_0>, change the long, flowing purple semi-transparent fabric in the middle to a long, flowing purple metallic silk fabric <purple_fabric>. Keep the pink fabric <pink_fabric>, orange fabric <orange_fabric>, green fabric <green_fabric>, blue fabric <blue_fabric>, the ocean waves <ocean_waves>, and the sandy beach <sandy_beach> exactly unchanged.",
  "rows": [{
    "id": "pink_fabric",
    "from": "ref_image_0",
    "src_bbox": [379, 53, 722, 350],
    "tgt_bbox": [379, 53, 722, 350],
    "desc": "long, flowing pink semi-transparent fabric",
    "kind": "anchor"
  }, {
    "id": "orange_fabric",
    "from": "ref_image_0",
    "src_bbox": [394, 144, 740, 563],
    "tgt_bbox": [394, 144, 740, 563],
    "desc": "long, flowing orange semi-transparent fabric",
    "kind": "anchor"
  }, {
    "id": "purple_fabric",
    "from": null,
    "src_bbox": null,
    "tgt_bbox": [468, 252, 663, 705],
    "desc": "long, flowing purple metallic silk fabric",
    "kind": "new"
  }, {
    "id": "green_fabric",
    "from": "ref_image_0",
    "src_bbox": [530, 493, 737, 807],
    "tgt_bbox": [530, 493, 737, 807],
    "desc": "long, flowing green semi-transparent fabric",
    "kind": "anchor"
  }, {
    "id": "blue_fabric",
    "from": "ref_image_0",
    "src_bbox": [536, 627, 742, 936],
    "tgt_bbox": [536, 627, 742, 936],
    "desc": "long, flowing blue semi-transparent fabric",
    "kind": "anchor"
  }, {
    "id": "ocean_waves",
    "from": "ref_image_0",
    "src_bbox": [492, 0, 778, 1000],
    "tgt_bbox": [492, 0, 778, 1000],
    "desc": "ocean waves crashing onto the shore",
    "kind": "anchor"
  }, {
    "id": "sandy_beach",
    "from": "ref_image_0",
    "src_bbox": [662, 0, 1000, 1000],
    "tgt_bbox": [662, 0, 1000, 1000],
    "desc": "sandy beach with small pebbles",
    "kind": "anchor"
  }]
}, {
  "key": "editing-mesa",
  "title": "Obsidian mesa",
  "op": "material",
  "instruction": "Change the central mesa to polished black obsidian.",
  "before": "https://cdn.sanity.io/images/2gpum2i6/production/a1145500294444d48fe99ab5689594450b466e2e-1800x895.webp",
  "after": "https://cdn.sanity.io/images/2gpum2i6/production/da5417274d17263801f247c02f62a971bc570945-1800x895.webp",
  "width": 1800,
  "height": 895,
  "record": "In <ref_image_0>, change the flat-topped rocky mesa in the middle distance to a flat-topped mesa <central_mesa> made of polished black obsidian with a reflective surface. Ensure the glossy black material catches the ambient light and reflects the starry sky above. Keep the arching Milky Way <milky_way>, the eroded clay ridges <foreground_ridges> in the foreground, and the distant horizon <distant_horizon> completely untouched.",
  "rows": [{
    "id": "milky_way",
    "from": "ref_image_0",
    "src_bbox": [70, 119, 655, 917],
    "tgt_bbox": [70, 119, 655, 917],
    "desc": "the milky way galaxy arching across the night sky",
    "kind": "anchor"
  }, {
    "id": "central_mesa",
    "from": null,
    "src_bbox": null,
    "tgt_bbox": [657, 322, 746, 547],
    "desc": "a flat-topped mesa made of polished black obsidian with a reflective surface",
    "kind": "new"
  }, {
    "id": "foreground_ridges",
    "from": "ref_image_0",
    "src_bbox": [707, 3, 995, 996],
    "tgt_bbox": [707, 3, 995, 996],
    "desc": "eroded clay ridges and badlands in the foreground",
    "kind": "anchor"
  }, {
    "id": "distant_horizon",
    "from": "ref_image_0",
    "src_bbox": [652, 0, 724, 323],
    "tgt_bbox": [652, 0, 724, 323],
    "desc": "the far horizon line under the twilight glow",
    "kind": "anchor"
  }]
}];

export const FLUX3_PIXEL_EXAMPLES = [{
  "key": "cats",
  "title": "Remove a cat",
  "op": "Remove",
  "change": "Orange cat removed",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/8fc2d013a3912a101e10aa116841adae9b930f74-1600x898.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/cfd2c6cf8f18a212d053b5efe0e18ea7eb1e3c5b-1600x898.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/8b251636c81303d1217c97308b4e676b3c4692ce-1600x898.png",
  "width": 1600,
  "height": 898,
  "identical": "80.7%",
  "pixels": 3491600,
  "full_res": [2494, 1400]
}, {
  "key": "bird",
  "title": "Add a bird",
  "op": "Add",
  "change": "Small bird added on the currants",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/c5ebc86b4050a2844ccd12ef0ec6e6f1eb168790-1600x1206.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/3ecf4e4ead6470ffd183833f5cee24dce231c70a-1600x1206.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/7a95f780e59c40614ecc7765d04582ee9e837c5f-1600x1206.png",
  "width": 1600,
  "height": 1206,
  "identical": "86.4%",
  "pixels": 2601200,
  "full_res": [1858, 1400]
}, {
  "key": "burger",
  "title": "Swap the plate",
  "op": "Replace",
  "change": "Plate swapped for a burger",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/e80c3c8f6d928356f5f4479644cdce033193a827-1191x1400.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/6290ac98c2b96eaead51063171e6d2b6dcf29bdc-1191x1400.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/a5fc3618762649a4cef0a78bd86c25ec6b188894-1191x1400.png",
  "width": 1191,
  "height": 1400,
  "identical": "88.4%",
  "pixels": 1667400,
  "full_res": [1191, 1400]
}, {
  "key": "shirt",
  "title": "Print on a shirt",
  "op": "Add",
  "change": "Cat print added to the T-shirt",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/0558a5d7996849b1fbda6aa00572d9f3d14ba809-1400x1400.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/6eb5c77713772f7c46abea03f6a3be6dda2ba53f-1400x1400.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/0898adeaf7ccae59dbb4d3e44093b0d4ece67ac5-1400x1400.png",
  "width": 1400,
  "height": 1400,
  "identical": "89.7%",
  "pixels": 1960000,
  "full_res": [1400, 1400]
}, {
  "key": "duck",
  "title": "Replace the duck",
  "op": "Replace",
  "change": "Rubber duck replaced",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/1fc2e839e624487f872609395cf0a67dc303b0b5-1600x1189.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/3d12f59a624a022ac12554242aa404be64db5b9d-1600x1189.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/1d01fcec8d4273fe6bc998242a4950ac798b8543-1600x1189.png",
  "width": 1600,
  "height": 1189,
  "identical": "85.5%",
  "pixels": 2637600,
  "full_res": [1884, 1400]
}, {
  "key": "hair",
  "title": "Recolor hair",
  "op": "Recolour",
  "change": "Hair recolored blue",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/6c2f4e7142e08a45343f735da9c6979ac78fd528-1046x1400.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/381358b6b1fcda8eaba213bc70d27f7a754f2fb6-1046x1400.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/e65e9abb341ceaebf84893240d3f6428a49bb86b-1046x1400.png",
  "width": 1046,
  "height": 1400,
  "identical": "67.8%",
  "pixels": 1464400,
  "full_res": [1046, 1400]
}, {
  "key": "moto",
  "title": "Add a rider",
  "op": "Add",
  "change": "Motorcyclist added in the bend",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/942f0e77c30e850c4114f7adc0e45869a591edd2-1600x663.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/69ef6ce42514482eed365cc8c57b974e3947a27b-1600x663.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/c133740c9eb19b1b59f2a1da18822d84ce83c175-1600x663.png",
  "width": 1600,
  "height": 663,
  "identical": "89.2%",
  "pixels": 4732000,
  "full_res": [3380, 1400]
}, {
  "key": "sofa",
  "title": "New sofa",
  "op": "Replace",
  "change": "Armchairs replaced with a sofa",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/8a3546c271532f377c5707d5edc52265028b569d-1600x1067.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/5125bc353896ff5de2dd73d84cc7838e9ea8670a-1600x1067.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/ba22eacfac502719104a658652f2262e2280616a-1600x1067.png",
  "width": 1600,
  "height": 1067,
  "identical": "76.5%",
  "pixels": 2940000,
  "full_res": [2100, 1400]
}, {
  "key": "divers",
  "title": "Two tiny divers",
  "op": "Add",
  "change": "Two divers added",
  "input": "https://cdn.sanity.io/images/2gpum2i6/production/a48094ab64a9008f77088440bd8f2919aca25e8c-1600x1060.webp",
  "edited": "https://cdn.sanity.io/images/2gpum2i6/production/662d38c3e6d14780f62b0db45ae19333420d416f-1600x1060.webp",
  "mask": "https://cdn.sanity.io/images/2gpum2i6/production/2d37fae000aa2287e0b3cc39ffb5f61117739df5-1600x1060.png",
  "width": 1600,
  "height": 1060,
  "identical": "94.1%",
  "pixels": 2958200,
  "full_res": [2113, 1400]
}];

export const EditExplorer = ({examples = [], mediaBase = ""}) => {
  const KIND = {
    new: {
      label: "New",
      color: "#e0708a",
      note: "Generate at tgt_bbox"
    },
    move: {
      label: "Move",
      color: "#6fb3d9",
      note: "Move from src_bbox to tgt_bbox"
    },
    anchor: {
      label: "Anchor",
      color: "#9fb0c8",
      note: "Keep at the same position"
    }
  };
  const [index, setIndex] = useState(0);
  const [view, setView] = useState("after");
  const [anchors, setAnchors] = useState(false);
  const [active, setActive] = useState(null);
  const [copied, setCopied] = useState(false);
  const [loaded, setLoaded] = useState({});
  const [failed, setFailed] = useState({});
  const ex = examples[index];
  if (!ex) return null;
  const before = mediaBase + ex.before, after = mediaBase + ex.after;
  const ready = loaded[before] && loaded[after];
  const reveal = async (image, key) => {
    try {
      await image.decode();
      setLoaded(current => ({
        ...current,
        [key]: true
      }));
    } catch {
      setFailed(current => ({
        ...current,
        [key]: true
      }));
    }
  };
  const pct = ([y0, x0, y1, x1]) => ({
    top: y0 / 10 + "%",
    left: x0 / 10 + "%",
    height: (y1 - y0) / 10 + "%",
    width: (x1 - x0) / 10 + "%"
  });
  const wire = ex.rows.map(({id, from, src_bbox, tgt_bbox, desc}) => ({
    id,
    from,
    src_bbox,
    tgt_bbox,
    desc
  }));
  const body = {
    prompt: ex.record + " " + JSON.stringify(wire),
    images: [typeof window === "undefined" ? mediaBase + ex.before : new URL(mediaBase + ex.before, window.location.href).href]
  };
  const changed = ex.rows.filter(r => r.kind !== "anchor");
  const shown = anchors ? ex.rows : changed;
  const parts = ex.record.split(/(<[A-Za-z0-9_]+>)/g).filter(Boolean);
  const kindOf = id => (ex.rows.find(r => r.id === id) || ({})).kind;
  const pick = i => {
    setCopied(false);
    setIndex(i);
    setView("after");
    setActive(null);
  };
  const copy = async () => {
    try {
      await navigator.clipboard.writeText(JSON.stringify(body, null, 2));
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (e) {
      setCopied(false);
    }
  };
  const hot = id => ({
    onMouseEnter: () => setActive(id),
    onMouseLeave: () => setActive(null),
    onFocus: () => setActive(id),
    onBlur: () => setActive(null)
  });
  return <section className="not-prose edit-explorer" aria-label="Box edit explorer">
      <style>{`
.edit-explorer{container-type:inline-size;margin:1.5rem 0 2rem;border:1px solid rgba(128,128,128,.25);border-radius:1rem;overflow:hidden;background:rgba(127,127,127,.04)}
.edit-explorer .ex-picker{display:flex;align-items:center;font-size:.85rem;gap:.4rem;flex-wrap:wrap;padding:.85rem;border-bottom:1px solid rgba(128,128,128,.2)}
.edit-explorer .ex-pills{display:flex;flex-wrap:wrap;gap:.4rem}
.edit-explorer .ex-pills button{font:inherit;font-size:.8rem;padding:.3rem .75rem;border:1px solid rgba(128,128,128,.35);border-radius:999px;background:transparent;color:inherit;cursor:pointer}
.edit-explorer .ex-pills button:hover{border-color:var(--aspen-evergreen,#486a58)}
.edit-explorer .ex-pills button[aria-pressed="true"]{background:var(--aspen-evergreen,#486a58);border-color:var(--aspen-evergreen,#486a58);color:#fff}
.edit-explorer button{font:inherit}
.edit-explorer .ex-body{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:1.25rem;padding:1.1rem}
.edit-explorer .ex-stage{position:relative;overflow:hidden;border-radius:.6rem;background:#111}
.edit-explorer .ex-visual{opacity:0;transition:opacity .18s ease}
.edit-explorer .ex-visual.is-ready{opacity:1}
.edit-explorer .ex-loading{position:absolute;inset:0;display:grid;place-items:center;color:#ccc;font-size:.8rem;padding:1rem;text-align:center}
.edit-explorer summary{font-size:.85rem;cursor:pointer;padding:.3rem 0}
.edit-explorer details[open] summary{margin-bottom:.4rem}
.edit-explorer .ex-stage img{display:block;width:100%;height:auto;margin:0}
.edit-explorer .ex-stage img.ex-top{position:absolute;inset:0;height:100%;transition:opacity .35s}
.edit-explorer .ex-box{position:absolute;border:2px solid var(--c);border-radius:3px;pointer-events:none;transition:opacity .2s}
.edit-explorer .ex-box.is-src{border-style:dashed;opacity:.85}
.edit-explorer .ex-box.is-dim{opacity:.2}
.edit-explorer .ex-box.is-hot{border-width:3px;background:color-mix(in srgb,var(--c) 20%,transparent)}
.edit-explorer .ex-box span{position:absolute;left:-2px;bottom:100%;background:var(--c);color:#141414;font:600 10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;padding:1px 5px;border-radius:3px 3px 0 0;white-space:nowrap}
.edit-explorer .ex-box.is-inside span{bottom:auto;top:0;border-radius:0 0 3px 0}
.edit-explorer .ex-seg{display:inline-flex;border:1px solid rgba(128,128,128,.35);border-radius:.5rem;overflow:hidden}
.edit-explorer .ex-seg button{padding:.4rem .8rem;font-size:.8rem;background:transparent;border:0;color:inherit;cursor:pointer}
.edit-explorer .ex-seg button[aria-pressed="true"]{background:var(--aspen-evergreen,#486a58);color:#fff}
.edit-explorer .ex-tools{display:flex;gap:.75rem;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-top:.6rem;font-size:.8rem}
.edit-explorer .ex-tools label{display:flex;gap:.4rem;align-items:center;cursor:pointer;opacity:.85}
.edit-explorer .ex-side{display:flex;flex-direction:column;gap:.9rem;min-width:0}
.edit-explorer .ex-h{font:600 .7rem/1 system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;opacity:.6;margin:0 0 .4rem}
.edit-explorer .ex-ask{margin:0;font-size:.95rem;font-weight:600}
.edit-explorer .ex-record{margin:0;font-size:.85rem;line-height:1.6}
.edit-explorer .ex-token{font:600 .72rem ui-monospace,SFMono-Regular,Menlo,monospace;padding:.05rem .3rem;margin:0 .1rem;border-radius:.3rem;border:1px solid var(--c);background:color-mix(in srgb,var(--c) 14%,transparent)}
.edit-explorer .ex-token.is-hot{background:var(--c);color:#141414}
.edit-explorer .ex-rows{list-style:none;margin:0;padding:0 .25rem 0 0;display:flex;flex-direction:column;gap:.35rem;max-height:22rem;overflow-y:auto}
.edit-explorer .ex-row{padding:.45rem .6rem;border-radius:.5rem;background:rgba(127,127,127,.07);border:1px solid transparent;font-size:.78rem;outline:none}
.edit-explorer .ex-row.is-hot,.edit-explorer .ex-row:focus-visible{border-color:var(--c)}
.edit-explorer .ex-row header{display:flex;gap:.5rem;align-items:center;justify-content:space-between}
.edit-explorer .ex-row b{font:600 .66rem system-ui,sans-serif;letter-spacing:.06em;text-transform:uppercase;padding:.15rem .4rem;border-radius:.3rem;background:var(--c);color:#141414}
.edit-explorer .ex-row code{font-size:.72rem;background:none;padding:0}
.edit-explorer .ex-row .ex-desc{margin:.25rem 0 0;opacity:.8;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.edit-explorer .ex-copy{align-self:flex-start;font-size:.78rem;font-weight:600;padding:.45rem .8rem;border-radius:.45rem;cursor:pointer;background:var(--aspen-evergreen,#486a58);border:1px solid var(--aspen-evergreen,#486a58);color:#fff}
@container (max-width:760px){.edit-explorer .ex-body{grid-template-columns:minmax(0,1fr)}}
@media (prefers-reduced-motion:reduce){.edit-explorer *{transition:none!important}}
      `}</style>

      {examples.length > 1 ? <div className="ex-picker">
        <div className="ex-pills" role="group" aria-label="Example">
          {examples.map((example, i) => <button key={example.key} type="button" aria-pressed={i === index} onClick={() => pick(i)}>{example.title}</button>)}
        </div>
      </div> : null}

      <div className="ex-body">
        <div>
          <div className="ex-stage" aria-busy={!ready} style={{
    aspectRatio: `${ex.width} / ${ex.height}`
  }}>
            {!ready && <div className="ex-loading" role="status">{failed[before] || failed[after] ? "Image unavailable. Choose another example or reload to retry." : "Loading example…"}</div>}
            <div className={ready ? "ex-visual is-ready" : "ex-visual"} aria-hidden={!ready}>
            <img key={before} src={before} onLoad={event => reveal(event.currentTarget, before)} onError={() => setFailed(current => ({
    ...current,
    [before]: true
  }))} alt={ex.title + ": the image before the edit"} width={ex.width} height={ex.height} />
            <img key={after} className="ex-top" src={after} onLoad={event => reveal(event.currentTarget, after)} onError={() => setFailed(current => ({
    ...current,
    [after]: true
  }))} alt={ex.title + ": the image after the edit"} style={{
    opacity: view === "after" ? 1 : 0
  }} />
            {shown.map(r => {
    const c = KIND[r.kind].color;
    const cls = (...extra) => {
      const list = ["ex-box", ...extra];
      if (active && active !== r.id) list.push("is-dim");
      if (active === r.id) list.push("is-hot");
      return list.join(" ");
    };
    const boxes = [];
    if (r.kind === "move" && view === "before") boxes.push(<div key={r.id + "-src"} className={cls("is-src")} style={{
      ...pct(r.src_bbox),
      "--c": c
    }}><span>{r.id} · src_bbox</span></div>);
    if (r.tgt_bbox && !(r.kind === "move" && view === "before")) boxes.push(<div key={r.id} className={r.tgt_bbox[0] < 40 ? cls("is-inside") : cls()} style={{
      ...pct(r.tgt_bbox),
      "--c": c
    }}><span>{r.kind === "move" ? r.id + " · tgt_bbox" : r.id}</span></div>);
    return boxes;
  })}
            </div>
          </div>
          <div className="ex-tools">
            <div className="ex-seg" role="group" aria-label="Show image">
              <button type="button" aria-pressed={view === "before" ? "true" : "false"} onClick={() => setView("before")}>Before</button>
              <button type="button" aria-pressed={view === "after" ? "true" : "false"} onClick={() => setView("after")}>After</button>
            </div>
            <label>
              <input type="checkbox" checked={anchors} onChange={e => setAnchors(e.target.checked)} />
              Show anchor boxes
            </label>
          </div>
        </div>

        <div className="ex-side">
          <div>
            <p className="ex-h">The change</p>
            <p className="ex-ask">{ex.instruction}</p>
          </div>
          <details>
            <summary>Full prompt</summary>
            <p className="ex-record">
              {parts.map((part, i) => {
    const m = part.match(/^<([A-Za-z0-9_]+)>$/);
    if (!m) return <span key={i}>{part}</span>;
    const kind = kindOf(m[1]);
    if (!kind) return <code key={i} className="ex-token" style={{
      "--c": "#bbb"
    }}>{part}</code>;
    return <code key={i} tabIndex={0} className={active === m[1] ? "ex-token is-hot" : "ex-token"} style={{
      "--c": KIND[kind].color
    }} {...hot(m[1])}>{part}</code>;
  })}
            </p>
          </details>
          <div>
            <p className="ex-h">Elements</p>
            <ul className="ex-rows">
              {shown.map(r => <li key={r.id} tabIndex={0} className={active === r.id ? "ex-row is-hot" : "ex-row"} style={{
    "--c": KIND[r.kind].color
  }} {...hot(r.id)}>
                  <header>
                    <code>{r.id}</code>
                    <b title={KIND[r.kind].note}>{KIND[r.kind].label}</b>
                  </header>
                  <code>from: {JSON.stringify(r.from)} · src: {JSON.stringify(r.src_bbox)} · tgt: {JSON.stringify(r.tgt_bbox)}</code>
                  <div className="ex-desc">{r.desc}</div>
                </li>)}
            </ul>
          </div>
          <button type="button" className="ex-copy" onClick={copy} aria-live="polite">
            {copied ? "Copied" : "Copy request body"}
          </button>
        </div>
      </div>
    </section>;
};

export const ClipSlider = ({items = []}) => {
  const [copied, setCopied] = useState(-1);
  const [inView, setInView] = useState(0);
  const [edges, setEdges] = useState({
    left: false,
    right: items.length > 1
  });
  const [muted, setMuted] = useState(true);
  const [viewer, setViewer] = useState(null);
  const [split, setSplit] = useState({});
  const splitAt = idx => split[idx] === undefined ? items[idx] && items[idx].split !== undefined ? items[idx].split : 50 : split[idx];
  const moveSplit = (idx, e) => {
    const r = e.currentTarget.getBoundingClientRect();
    const pct = Math.max(0, Math.min(100, (e.clientX - r.left) / r.width * 100));
    setSplit(s => ({
      ...s,
      [idx]: pct
    }));
  };
  const BOX_COLORS = ["#e8b04a", "#6fb3d9", "#e0708a", "#8fcf7a", "#b99af0", "#f08b5b", "#5fd0c0", "#d9d36f", "#9fb0c8", "#f2a3c7", "#7aa0f0", "#c7e07a"];
  const fitBox = ([y0, x0, y1, x1], w, h) => {
    const frame = 16 / 9, ar = w / h;
    const sx = ar > frame ? 1 : ar / frame, sy = ar > frame ? frame / ar : 1;
    const ox = (1 - sx) / 2, oy = (1 - sy) / 2;
    return {
      top: (oy + y0 / 1000 * sy) * 100 + "%",
      left: (ox + x0 / 1000 * sx) * 100 + "%",
      height: (y1 - y0) / 1000 * sy * 100 + "%",
      width: (x1 - x0) / 1000 * sx * 100 + "%"
    };
  };
  const [actualSize, setActualSize] = useState(false);
  const trackRef = useRef(null);
  const cardRefs = useRef([]);
  const measuredRef = useRef(false);
  const mutedRef = useRef(true);
  const videosRef = useRef([]);
  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 = mutedRef.current;
          const p = v.play?.();
          if (p && typeof p.catch === "function") p.catch(() => {});
        } else {
          v.pause?.();
        }
      });
    }, {
      threshold: 0.4
    });
    return observerRef.current;
  };
  const attachVideo = el => {
    if (!el) return;
    if (!videosRef.current.includes(el)) videosRef.current.push(el);
    const io = getObserver();
    if (io) io.observe(el);
  };
  const toggleMute = () => {
    const next = !muted;
    setMuted(next);
    mutedRef.current = next;
    videosRef.current.forEach(v => {
      if (v) v.muted = next;
    });
  };
  const updateEdges = el => {
    if (!el) return;
    const max = el.scrollWidth - el.clientWidth;
    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 = mutedRef.current;
    const p = video.play?.();
    if (p && typeof p.catch === "function") p.catch(() => {});
  };
  const copy = (text, idx) => {
    try {
      navigator.clipboard.writeText(text);
      setCopied(idx);
      setTimeout(() => setCopied(c => c === idx ? -1 : c), 2000);
    } catch (e) {}
  };
  return <div className="not-prose clip-slider">
      <button type="button" className="clip-slider__arrow clip-slider__arrow--prev" onClick={() => scrollByCard(-1)} disabled={!edges.left} aria-label="Previous clip">
        <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="clip-slider__track" ref={attachTrack} onScroll={e => updateEdges(e.currentTarget)}>
        {items.map((item, idx) => <article key={idx} className="clip-slider__slide" ref={el => {
    cardRefs.current[idx] = el;
  }}>
            {item.title && <h4 className="clip-slider__title">{item.title}</h4>}
            <div className={item.boxes ? "clip-slider__media has-boxes" : item.fit === "contain" ? "clip-slider__media is-fit" : item.before ? "clip-slider__media has-compare" : "clip-slider__media"}>
              {item.before ? <div className="clip-slider__compare" role="slider" tabIndex={0} aria-label={`Compare before and after: ${item.alt || ""}`} aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(splitAt(idx))} onPointerDown={e => {
    e.currentTarget.setPointerCapture?.(e.pointerId);
    moveSplit(idx, e);
  }} onPointerMove={e => {
    if (e.buttons) moveSplit(idx, e);
  }} onKeyDown={e => {
    if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
      e.preventDefault();
      setSplit(s => ({
        ...s,
        [idx]: Math.max(0, Math.min(100, splitAt(idx) + (e.key === "ArrowLeft" ? -5 : 5)))
      }));
    }
  }}>
                  <img src={item.after} alt={item.alt ? "After: " + item.alt : ""} loading="lazy" className="clip-slider__media-el" draggable={false} />
                  <div className="clip-slider__before" style={{
    clipPath: `inset(0 ${100 - splitAt(idx)}% 0 0)`
  }}>
                    <img src={item.before} alt={item.alt ? "Before: " + item.alt : ""} loading="lazy" className="clip-slider__media-el" draggable={false} />
                  </div>
                  <span className="clip-slider__handle" style={{
    left: splitAt(idx) + "%"
  }} aria-hidden="true">
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 3 12 9 6" /><polyline points="15 6 21 12 15 18" /></svg>
                  </span>
                  <span className="clip-slider__tag clip-slider__tag--before" aria-hidden="true">Before</span>
                  <span className="clip-slider__tag clip-slider__tag--after" aria-hidden="true">After</span>
                </div> : item.image ? <button type="button" className="clip-slider__zoom" onClick={() => {
    setActualSize(false);
    setViewer(idx);
  }} aria-label={`View full size: ${item.alt || item.title || ""}`}>
                  <img src={item.image} alt={item.alt || item.title || ""} loading="lazy" className="clip-slider__media-el" />
                  {item.boxes && item.boxes.map((b, k) => <span key={b.id} className={"clip-slider__box" + (b.bbox[0] < 60 ? " is-top" : "")} style={{
    ...fitBox(b.bbox, item.w, item.h),
    "--c": BOX_COLORS[k % BOX_COLORS.length]
  }}>
                      <i>{b.id}</i>
                    </span>)}
                </button> : <>
              <video ref={attachVideo} src={item.video} poster={item.poster} loop muted playsInline preload="metadata" onLoadedData={startVideo} onCanPlay={startVideo} aria-label={item.alt || item.title || ""} className="clip-slider__media-el" />
              <button type="button" onClick={toggleMute} aria-label={muted ? "Unmute" : "Mute"} title={muted ? "Enable sound" : "Mute"} style={{
    position: "absolute",
    top: "0.6rem",
    right: "0.6rem",
    zIndex: 4,
    width: "2rem",
    height: "2rem",
    borderRadius: "999px",
    border: "none",
    background: "rgba(8, 18, 13, 0.5)",
    color: "#fff",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    cursor: "pointer",
    backdropFilter: "blur(4px)",
    WebkitBackdropFilter: "blur(4px)"
  }}>
                {muted ? <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5z" /><line x1="23" y1="9" x2="17" y2="15" /><line x1="17" y1="9" x2="23" y2="15" /></svg> : <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5z" /><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>}
              </button>
              </>}
            </div>

            {item.prompt && <div className="clip-slider__body">
                <div className="clip-slider__prompt">
                  <button type="button" className="clip-slider__copy" onClick={() => copy(item.prompt, idx)} aria-label={copied === idx ? "Prompt copied" : "Copy prompt"} title={copied === idx ? "Copied" : "Copy prompt"}>
                    {copied === idx ? "Copied!" : "Copy"}
                  </button>
                  <pre className="clip-slider__prompt-pre"><code>{item.prompt}</code></pre>
                </div>
              </div>}
          </article>)}
      </div>

      <button type="button" className="clip-slider__arrow clip-slider__arrow--next" onClick={() => scrollByCard(1)} disabled={!edges.right} aria-label="Next clip">
        <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>

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

      {viewer !== null && items[viewer] ? <dialog className={actualSize ? "clip-slider__viewer" : "clip-slider__viewer is-fit"} aria-label={items[viewer].alt || items[viewer].title || ""} ref={node => {
    if (node && !node.open) node.showModal();
  }} onClose={() => setViewer(null)} onClick={() => setViewer(null)}>
          <img src={items[viewer].full || items[viewer].image} alt={items[viewer].alt || items[viewer].title || ""} style={{
    cursor: actualSize ? "zoom-out" : "zoom-in"
  }} onClick={event => {
    event.stopPropagation();
    setActualSize(!actualSize);
  }} />
          <div className="clip-slider__viewer-bar" onClick={event => event.stopPropagation()}>
            <button type="button" onClick={() => setActualSize(!actualSize)}>
              {actualSize ? "Fit to screen" : "Actual size"}
            </button>
            <button type="button" onClick={() => setViewer(null)}>Close</button>
          </div>
        </dialog> : null}
    </div>;
};

export const PixelDiff = ({examples = [], mediaBase = ""}) => {
  const [index, setIndex] = useState(0);
  const [view, setView] = useState("edited");
  const ex = examples[index];
  if (!ex) return null;
  const pick = i => {
    setIndex(i);
    setView("edited");
  };
  return <section className="not-prose pixel-diff" aria-label="Pixel difference viewer">
      <style>{`
.pixel-diff{margin:1.5rem 0 2rem;border:1px solid rgba(128,128,128,.25);border-radius:1rem;overflow:hidden;background:rgba(127,127,127,.04)}
.pixel-diff button{font:inherit}
.pixel-diff .pd-picker{display:flex;gap:.4rem;flex-wrap:wrap;padding:.85rem;border-bottom:1px solid rgba(128,128,128,.2)}
.pixel-diff .pd-pill{font-size:.8rem;line-height:1;padding:.5rem .75rem;border-radius:999px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer}
.pixel-diff .pd-pill[aria-pressed="true"]{background:var(--aspen-evergreen,#486a58);border-color:var(--aspen-evergreen,#486a58);color:#fff}
.pixel-diff .pd-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:1rem;padding:1.1rem;align-items:start}
.pixel-diff .pd-tag{margin:0 0 .45rem;font-size:.8rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;opacity:.75}
.pixel-diff .pd-stage{position:relative;border-radius:.6rem;overflow:hidden;background:#111}
.pixel-diff .pd-stage img{display:block;width:100%;height:auto;margin:0}
.pixel-diff .pd-stage img.pd-layer{position:absolute;inset:0;height:100%;transition:opacity .3s}
.pixel-diff .pd-seg{display:inline-flex;margin-top:.6rem;border:1px solid rgba(128,128,128,.35);border-radius:.5rem;overflow:hidden}
.pixel-diff .pd-seg button{padding:.4rem .8rem;font-size:.8rem;background:transparent;border:0;color:inherit;cursor:pointer}
.pixel-diff .pd-seg button[aria-pressed="true"]{background:var(--aspen-evergreen,#486a58);color:#fff}
.pixel-diff .pd-change{margin:0;padding:0 1.1rem 1.1rem;font-size:.85rem;line-height:1.55;opacity:.85}
@media (max-width:720px){.pixel-diff .pd-body{grid-template-columns:minmax(0,1fr)}}
@media (prefers-reduced-motion:reduce){.pixel-diff *{transition:none!important}}
      `}</style>

      <div className="pd-picker" role="group" aria-label="Edits">
        {examples.map((e, i) => <button key={e.key} type="button" className="pd-pill" aria-pressed={i === index ? "true" : "false"} onClick={() => pick(i)}>
            {e.title}
          </button>)}
      </div>

      <div className="pd-body">
        <figure style={{
    margin: 0
  }}>
          <p className="pd-tag">Input</p>
          <div className="pd-stage">
            <img src={mediaBase + ex.input} alt={ex.title + ": input image"} width={ex.width} height={ex.height} />
          </div>
        </figure>
        <figure style={{
    margin: 0
  }}>
          <p className="pd-tag">{view === "mask" ? "Changed pixels" : "Edited"}</p>
          <div className="pd-stage">
            <img src={mediaBase + ex.edited} alt={ex.title + ": edited image"} width={ex.width} height={ex.height} />
            <img className="pd-layer" src={mediaBase + ex.mask} alt={ex.title + ": difference map; bright areas changed, black areas did not"} style={{
    opacity: view === "mask" ? 1 : 0
  }} aria-hidden={view === "mask" ? undefined : "true"} />
          </div>
          <div className="pd-seg" role="group" aria-label="Show image">
            <button type="button" aria-pressed={view === "edited" ? "true" : "false"} onClick={() => setView("edited")}>Edited</button>
            <button type="button" aria-pressed={view === "mask" ? "true" : "false"} onClick={() => setView("mask")}>Changed pixels</button>
          </div>
        </figure>
      </div>
      <p className="pd-change">
        {ex.change}. In Changed pixels, bright areas changed and black areas match the input.
      </p>
    </section>;
};

export const BboxExplorer = () => {
  const ROOT = "/images/flux3/bbox/";
  const SPOTS = [{
    id: "fire",
    box: [520, 340, 820, 640],
    items: ["campfire", "tent", "boulder"]
  }, {
    id: "meadow",
    box: [440, 650, 720, 920],
    items: ["fox", "fawn", "hedgehog", "badger"]
  }, {
    id: "roots",
    box: [380, 50, 660, 330],
    items: ["burrow", "toadstool", "rabbits"]
  }, {
    id: "canopy",
    box: [40, 280, 300, 660],
    items: ["swing", "birds", "treehouse"]
  }];
  const pct = ([y0, x0, y1, x1]) => ({
    top: y0 / 10 + "%",
    left: x0 / 10 + "%",
    height: (y1 - y0) / 10 + "%",
    width: (x1 - x0) / 10 + "%"
  });
  const stage = useRef(null);
  const [placed, setPlaced] = useState({});
  const [drag, setDrag] = useState(null);
  const [menu, setMenu] = useState(null);
  const [typing, setTyping] = useState(null);
  const [miss, setMiss] = useState(false);
  const [from, setFrom] = useState(null);
  useEffect(() => {
    if (!from) return;
    const r = requestAnimationFrame(() => requestAnimationFrame(() => setFrom(null)));
    return () => cancelAnimationFrame(r);
  }, [from]);
  useEffect(() => {
    SPOTS.forEach(s => s.items.forEach(i => {
      new Image().src = `${ROOT}${s.id}-${i}.webp`;
    }));
  }, []);
  useEffect(() => {
    if (!typing) return;
    const label = typing.item + "_1";
    if (typing.n >= label.length) {
      const t = setTimeout(() => {
        setPlaced(p => ({
          ...p,
          [typing.spot]: typing.item
        }));
        setTyping(null);
      }, 350);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => setTyping({
      ...typing,
      n: typing.n + 1
    }), 55);
    return () => clearTimeout(t);
  }, [typing]);
  const point = e => {
    const r = stage.current.getBoundingClientRect();
    const k = v => Math.max(0, Math.min(1000, Math.round(v * 1000)));
    return [k((e.clientY - r.top) / r.height), k((e.clientX - r.left) / r.width)];
  };
  const down = e => {
    if (typing || e.button > 0) return;
    const [y, x] = point(e);
    e.currentTarget.setPointerCapture(e.pointerId);
    setMenu(null);
    setMiss(false);
    setDrag({
      start: [y, x],
      box: [y, x, y, x]
    });
  };
  const move = e => {
    if (!drag) return;
    const [y, x] = point(e), [sy, sx] = drag.start;
    setDrag({
      ...drag,
      box: [Math.min(sy, y), Math.min(sx, x), Math.max(sy, y), Math.max(sx, x)]
    });
  };
  const up = () => {
    if (!drag) return;
    const [y0, x0, y1, x1] = drag.box;
    const overlap = s => Math.max(0, Math.min(y1, s.box[2]) - Math.max(y0, s.box[0])) * Math.max(0, Math.min(x1, s.box[3]) - Math.max(x0, s.box[1]));
    const hit = SPOTS.map(s => [s, overlap(s) || (y0 >= s.box[0] && y0 <= s.box[2] && x0 >= s.box[1] && x0 <= s.box[3] ? 1 : 0)]).sort((a, b) => b[1] - a[1])[0];
    setDrag(null);
    if (hit[1] > 0) {
      setFrom(drag.box);
      setMenu(hit[0].id);
    } else setMiss(true);
  };
  const choose = (spot, item) => {
    setMenu(null);
    setTyping({
      spot,
      item,
      n: 0
    });
  };
  const spot = id => SPOTS.find(s => s.id === id);
  const active = typing ? typing.spot : menu;
  const last = typing || Object.keys(placed).length ? typing || ({
    spot: Object.keys(placed).slice(-1)[0],
    item: Object.values(placed).slice(-1)[0]
  }) : null;
  const entity = last && JSON.stringify([{
    id: last.item + "_1",
    from: null,
    src_bbox: null,
    tgt_bbox: spot(last.spot).box,
    desc: last.item
  }]);
  const n = Object.keys(placed).length;
  const caption = typing ? ["Placing " + typing.item, "Writing <" + typing.item + "_1> into the box"] : menu ? ["Choose what goes here", "Pick one of the saved edits for this box"] : miss ? ["Nothing there yet", "Try a marked region"] : n ? [n + " of " + SPOTS.length + " spots filled", "Draw a box over another marked region"] : ["Choose a region", "Draw a box over a marked region"];
  return <section className="bbox-explorer" aria-label="Place objects in a forest clearing with boxes" data-placed={Object.keys(placed).length} style={{
    margin: "1.5rem 0"
  }}>
      <style>{`
.bbox-explorer .bx-stage{position:relative;aspect-ratio:4/3;overflow:hidden;touch-action:none;cursor:crosshair;user-select:none;background:#233d32;border:1px solid rgba(128,128,128,.3)}
.bbox-explorer .bx-stage img{position:absolute;inset:0;margin:0!important;max-width:none;width:100%;height:100%;object-fit:cover;pointer-events:none}
.bbox-explorer .bx-layer{animation:bx-in .8s ease}
.bbox-explorer .bx-spot{position:absolute;width:30px;height:30px;margin:-15px 0 0 -15px;border-radius:50%;border:1px solid #ffe1a1;background:rgba(255,225,161,.18);box-shadow:0 0 12px rgba(255,214,120,.3);pointer-events:none}
.bbox-explorer .bx-box{position:absolute;border:2px solid #ffe1a1;border-radius:4px;pointer-events:none;transition:all .45s cubic-bezier(.3,1.4,.5,1)}
.bbox-explorer .bx-box.drawing{transition:none;background:rgba(255,225,161,.12)}
.bbox-explorer .bx-tag{position:absolute;left:-2px;bottom:100%;background:#ffe1a1;color:#13251d;font:600 12px ui-monospace,monospace;padding:3px 7px;border-radius:4px 4px 0 0;white-space:nowrap}
.bbox-explorer .bx-menu{position:absolute;bottom:calc(100% + 6px);display:flex;gap:4px;padding:5px;border-radius:6px;box-shadow:0 6px 18px rgba(0,0,0,.3);background:rgba(16,35,29,.92);z-index:2;pointer-events:auto}
.bbox-explorer .bx-menu button,.bbox-explorer .bx-bar button{font-family:inherit;font-size:15px;min-height:36px;padding:6px 14px;border-radius:6px;border:1px solid rgba(255,255,255,.2);background:transparent;color:#f4efdc;cursor:pointer;text-transform:capitalize}
.bbox-explorer .bx-menu button:hover,.bbox-explorer .bx-menu button:focus-visible{background:#ffe1a1;color:#13251d}
.bbox-explorer .bx-card{border:1px solid #8885;border-radius:12px;overflow:hidden;background:rgba(127,127,127,.04)}
.bbox-explorer .bx-head{position:relative;padding:16px 18px;color:inherit}
.bbox-explorer .bx-eyebrow{font:500 11px/1 system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:inherit}
.bbox-explorer .bx-title{font-family:inherit;font-weight:600;font-size:20px;line-height:1.4;margin:0 0 4px}
.bbox-explorer .bx-intro{margin:0;font-family:inherit;font-size:15px;opacity:.9;max-width:28em}
.bbox-explorer .bx-badge{position:absolute;right:18px;top:18px;font:500 11px system-ui,sans-serif;padding:5px 11px;border-radius:20px;background:transparent;border:1px solid #8885;color:inherit;pointer-events:none}
.bbox-explorer .bx-composer{position:absolute;left:14px;bottom:14px;max-width:calc(100% - 28px);padding:9px 16px;border-radius:6px;background:rgba(16,35,29,.85);color:#f4efdc;pointer-events:none;display:flex;flex-direction:column;gap:2px}
.bbox-explorer .bx-composer small{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:#ffe1a1}
.bbox-explorer .bx-composer span{font-family:inherit;font-size:15px}
.bbox-explorer .bx-bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:12px}
.bbox-explorer .bx-bar button{color:inherit;border-color:rgba(128,128,128,.4);min-height:44px}
.bbox-explorer pre{margin-top:10px;white-space:pre-wrap;word-break:break-all;font-size:12px}
@keyframes bx-in{from{opacity:0;filter:blur(6px)}}
@media (max-width:600px){.bbox-explorer .bx-composer span{display:none}.bbox-explorer .bx-composer{padding:6px 10px}.bbox-explorer .bx-head{padding:12px 14px}.bbox-explorer .bx-intro,.bbox-explorer .bx-badge{display:none}.bbox-explorer .bx-title{font-size:22px}.bbox-explorer .bx-menu{flex-wrap:wrap;width:max-content;max-width:70vw}}
@media (prefers-reduced-motion:reduce){.bbox-explorer *{animation:none!important;transition:none!important}}
`}</style>
      <div className="bx-card">
      <div className="bx-head">
          <div className="bx-title">Place an object</div>
          <p className="bx-intro">Draw over a marked region and choose an object to see a saved edit in that region.</p>
          <span className="bx-badge">Pre-generated edits</span>
      </div>
      <div ref={stage} className="bx-stage" onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={() => setDrag(null)}>
        <img src={ROOT + "base.webp"} alt="A storybook forest clearing" fetchpriority="high" />
        {Object.entries(placed).map(([id, item]) => <img key={id + item} className="bx-layer" src={`${ROOT}${id}-${item}.webp`} alt={`${item} added in its box`} />)}
        {!drag && SPOTS.filter(s => !placed[s.id] && s.id !== active).map(s => <div key={s.id} className="bx-spot" style={{
    top: (s.box[0] + s.box[2]) / 20 + "%",
    left: (s.box[1] + s.box[3]) / 20 + "%"
  }} />)}
        {drag && <div className="bx-box drawing" style={pct(drag.box)} />}
        {active && <div className="bx-box" style={pct(from || spot(active).box)} onPointerDown={e => e.stopPropagation()}>
            {menu ? <div className="bx-menu" role="menu" aria-label="Choose what to add" style={{
    ...spot(menu).box[1] < 500 ? {
      left: 0
    } : {
      right: 0
    },
    ...spot(menu).box[0] < 150 && ({
      bottom: "auto",
      top: "calc(100% + 6px)"
    })
  }}>
                {spot(menu).items.map(item => <button key={item} type="button" role="menuitem" onClick={() => choose(menu, item)}>{item}</button>)}
              </div> : <span className="bx-tag" aria-live="polite">{(typing.item + "_1").slice(0, typing.n)}</span>}
          </div>}
        {!drag && <div className="bx-composer" aria-live="polite"><small>{caption[0]}</small><span>{caption[1]}</span></div>}
      </div>
      </div>
      <div className="bx-bar">
        <button type="button" disabled={!Object.keys(placed).length || !!typing} onClick={() => {
    setPlaced({});
    setMenu(null);
  }}>Reset edits</button>
        {SPOTS.map(s => <button key={"choose-" + s.id} type="button" disabled={!!typing} aria-pressed={menu === s.id} onClick={() => {
    setMenu(s.id);
    setMiss(false);
  }}>Choose {s.id}</button>)}
        {SPOTS.map(s => placed[s.id] && <button key={s.id} type="button" disabled={!!typing} onClick={() => setPlaced(({[s.id]: _, ...rest}) => rest)}>Remove {placed[s.id]}</button>)}
      </div>
      {entity && <pre><code>{entity}</code></pre>}
    </section>;
};

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 ImageExamples = ({examples = []}) => {
  const [copied, setCopied] = useState(null);
  const [expanded, setExpanded] = useState({});
  const [viewer, setViewer] = useState(null);
  const [actualSize, setActualSize] = useState(false);
  const openViewer = index => {
    setActualSize(false);
    setViewer(index);
  };
  const copyPrompt = async (prompt, index) => {
    try {
      await navigator.clipboard.writeText(prompt);
      setCopied(index);
      setTimeout(() => setCopied(null), 2000);
    } catch {
      setCopied(null);
    }
  };
  return <div className="not-prose flux3-image-examples" style={{
    "--example-columns": examples.length
  }}>
      <style>{`
        .flux3-image-examples {
          display: grid;
          grid-template-columns: repeat(var(--example-columns), minmax(0, 1fr));
          gap: 1rem;
          margin: 1.5rem 0;
        }
        .flux3-image-examples figure {
          display: flex;
          flex-direction: column;
          min-width: 0;
          margin: 0;
        }
        .flux3-image-examples .image-example-frame {
          display: flex;
          align-items: center;
          justify-content: center;
          aspect-ratio: 3 / 2;
          overflow: hidden;
          background: rgba(127, 127, 127, 0.08);
          border-radius: 0.75rem;
        }
        .flux3-image-examples .image-example-frame img {
          display: block;
          width: 100%;
          height: 100%;
          object-fit: contain;
          margin: 0;
          border-radius: 0;
        }
        .flux3-image-examples figcaption {
          margin-top: 0.75rem;
          font-size: 0.875rem;
        }
        .flux3-image-examples .image-example-prompt {
          display: flex;
          flex: 1;
          flex-direction: column;
          gap: 1rem;
          padding: 1.25rem;
          border-radius: 0.75rem;
          background: rgba(127, 127, 127, 0.08);
        }
        .flux3-image-examples .image-example-prompt p {
          margin: 0;
          line-height: 1.6;
        }
        .flux3-image-examples .image-example-prompt button {
          padding: 0.35rem 0.65rem;
          border-radius: 0.375rem;
          background: var(--aspen-evergreen, #486A58);
          color: white;
          font-size: 0.75rem;
          font-weight: 600;
          cursor: pointer;
        }
        .flux3-image-examples .image-example-frame button {
          display: block;
          width: 100%;
          height: 100%;
          padding: 0;
          border: 0;
          background: none;
          cursor: zoom-in;
        }
        .flux3-image-examples .image-example-prompt p.is-clamped {
          display: -webkit-box;
          -webkit-line-clamp: 4;
          -webkit-box-orient: vertical;
          overflow: hidden;
        }
        .flux3-image-examples .image-example-actions {
          display: flex;
          justify-content: flex-end;
          gap: 0.5rem;
          margin-top: auto;
        }
        .flux3-image-examples .image-example-actions .image-example-more {
          background: transparent;
          color: inherit;
          border: 1px solid rgba(128, 128, 128, 0.35);
        }
        .flux3-image-viewer {
          width: 100vw;
          height: 100vh;
          max-width: none;
          max-height: none;
          margin: 0;
          padding: 0;
          border: 0;
          overflow: auto;
          background: rgba(0, 0, 0, 0.95);
          cursor: zoom-out;
        }
        .flux3-image-viewer::backdrop {
          background: rgba(0, 0, 0, 0.6);
        }
        .flux3-image-viewer img {
          display: block;
          margin: auto;
          border-radius: 0;
        }
        .flux3-image-viewer.is-fit {
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 2rem;
        }
        .flux3-image-viewer.is-fit img {
          max-width: 100%;
          max-height: 100%;
          object-fit: contain;
        }
        .flux3-image-viewer .flux3-image-viewer__bar {
          position: fixed;
          top: 1rem;
          right: 1rem;
          display: flex;
          gap: 0.5rem;
        }
        .flux3-image-viewer .flux3-image-viewer__bar button {
          padding: 0.35rem 0.75rem;
          border-radius: 0.375rem;
          background: rgba(255, 255, 255, 0.15);
          color: white;
          font-size: 0.8rem;
          font-weight: 600;
          cursor: pointer;
        }
        @media (max-width: 640px) {
          .flux3-image-examples { grid-template-columns: minmax(0, 1fr); }
        }
      `}</style>
      {examples.map(({src, alt, label, prompt, zoom, focus = "50% 50%"}, index) => <figure key={`${src}-${index}`}>
          <div className="image-example-frame">
            <button type="button" onClick={() => openViewer(index)} aria-label={`View full size: ${alt}`}>
              <img src={src} alt={alt} loading="lazy" style={zoom ? {
    transform: `scale(${zoom})`,
    transformOrigin: focus
  } : undefined} />
            </button>
          </div>
          {prompt ? <figcaption className="image-example-prompt">
              <p className={expanded[index] ? undefined : "is-clamped"}>{prompt}</p>
              <div className="image-example-actions">
                {prompt.length > 240 ? <button type="button" className="image-example-more" aria-expanded={!!expanded[index]} onClick={() => setExpanded({
    ...expanded,
    [index]: !expanded[index]
  })}>
                    {expanded[index] ? "Show less" : "Full prompt"}
                  </button> : null}
                <button type="button" onClick={() => copyPrompt(prompt, index)} aria-live="polite">
                  {copied === index ? "Copied!" : "Copy prompt"}
                </button>
              </div>
            </figcaption> : label ? <figcaption>{label}</figcaption> : null}
        </figure>)}
      {viewer !== null ? <dialog className={actualSize ? "flux3-image-viewer" : "flux3-image-viewer is-fit"} aria-label={examples[viewer].alt} ref={node => {
    if (node && !node.open) node.showModal();
  }} onClose={() => setViewer(null)} onClick={() => setViewer(null)}>
          <img src={examples[viewer].full || examples[viewer].src} alt={examples[viewer].alt} style={{
    cursor: actualSize ? "zoom-out" : "zoom-in"
  }} onClick={event => {
    event.stopPropagation();
    setActualSize(!actualSize);
  }} />
          <div className="flux3-image-viewer__bar" onClick={event => event.stopPropagation()}>
            <button type="button" onClick={() => setActualSize(!actualSize)}>
              {actualSize ? "Fit to screen" : "Actual size"}
            </button>
            <button type="button" onClick={() => setViewer(null)}>Close</button>
          </div>
        </dialog> : null}
    </div>;
};

<div className="flux3-overview-page flux3-page flux3-toc-page">
  <div className="not-prose flux3-overview-copy">
    <p className="flux3-overview-copy__eyebrow">FLUX 3 Image</p>
    <h1>FLUX 3 Image Editing</h1>
    <p>Edit an image with a written instruction, combine up to ten references, and add bounding boxes to place elements or target an edit.</p>
  </div>

  ## Quickstart

  Send an image in `images` and say what to change. This request recolors an owl
  and keeps the rest of the photo as it was. Drag the handle to compare:

  <div className="flux3-usecase-slides flux3-image-slides">
    <ClipSlider items={[{ before: "https://cdn.sanity.io/images/2gpum2i6/production/1731db585c799dbb4e2c48731ea0b5bfc92a47d7-1800x1193.webp", after: "https://cdn.sanity.io/images/2gpum2i6/production/785b2fe272b900971e6521f3265f7596fbf4779e-1800x1193.webp", alt: "a short-eared owl in flight over dried wildflowers, recolored from brown and tan to snowy white and charcoal grey." }]} />
  </div>

  Set `BFL_API_KEY` to a key from the [BFL dashboard](https://dashboard.bfl.ai), then submit:

  <CodeGroup>
    ```python Python theme={null}
    import os
    import requests

    response = requests.post(
        "https://api.bfl.ai/v1/flux-3-image",
        headers={"x-key": os.environ["BFL_API_KEY"]},
        json={
            "prompt": "In this image, change <owl_subject>, previously described as \"a short-eared owl in flight with brown and tan mottled feathers and yellow eyes\", to: a short-eared owl in flight with striking snowy white and charcoal grey mottled feathers and bright yellow eyes. Keep everything else in the image — composition, other elements, lighting, background — exactly unchanged.",
            "images": ["https://cdn.sanity.io/images/2gpum2i6/production/1731db585c799dbb4e2c48731ea0b5bfc92a47d7-1800x1193.webp"],
        },
        timeout=60,
    )
    response.raise_for_status()
    print(response.json())
    ```

    ```bash cURL theme={null}
    curl --fail-with-body https://api.bfl.ai/v1/flux-3-image \
      -H "x-key: $BFL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "In this image, change <owl_subject>, previously described as \"a short-eared owl in flight with brown and tan mottled feathers and yellow eyes\", to: a short-eared owl in flight with striking snowy white and charcoal grey mottled feathers and bright yellow eyes. Keep everything else in the image — composition, other elements, lighting, background — exactly unchanged.",
        "images": ["https://cdn.sanity.io/images/2gpum2i6/production/1731db585c799dbb4e2c48731ea0b5bfc92a47d7-1800x1193.webp"]
      }'
    ```
  </CodeGroup>

  `aspect_ratio` defaults to `auto`, so the edit keeps the shape of the image
  you send. Poll the returned `polling_url` until `status` is `Ready`, then
  download the image from `result.sample` within 1 hour.
  [FLUX 3 Text to Image](/flux_3/flux3_image_generate#quickstart) has complete
  clients that submit, poll, and save.

  ## Edit with an instruction

  These examples call `generate()` from the
  [complete clients](/flux_3/flux3_image_generate#quickstart), which submits a
  request, polls until it is ready, and saves the image.

  Send the image in `images` and write the change as an instruction. Name the
  element precisely enough that only one thing in the image matches. This
  request changes the color of one bird in a flock:

  <div className="flux3-usecase-slides flux3-image-slides">
    <ClipSlider items={[{ before: "https://cdn.sanity.io/images/2gpum2i6/production/4792198dfeba9223bf3ecf020fed2942de7f0bd7-1800x1200.webp", after: "https://cdn.sanity.io/images/2gpum2i6/production/6995df5d4ab590ca0b4eec142c27a643582a5f01-1248x832.webp", alt: "a flock of black birds on a pale sky, with one bird in the middle recolored pink.", split: 30 }]} />
  </div>

  ```python theme={null}
  generate({
      "prompt": "change the color of only one bird in the middle to #e01075",
      "images": ["https://cdn.sanity.io/images/2gpum2i6/production/4792198dfeba9223bf3ecf020fed2942de7f0bd7-1800x1200.webp"],
  }, name="pink_bird")
  ```

  With `aspect_ratio` left at `auto`, the output keeps the aspect ratio of the
  image you send.

  You don't have to draw a box for a single edit. Before it generates, FLUX 3
  Image expands your instruction into a detailed prompt, and for many edits it
  also finds the element you named and adds a box for it. The expanded prompt,
  including any boxes it added, comes back in `result.prompt`. To control exactly
  which elements change, use [bounding boxes](/flux_3/flux3_image_bounding_boxes#edit-an-image).

  <span id="combine-references" />

  ## Multi reference

  Refer to each image by its position or by what it shows. "Image 1" is the
  first entry in `images`, "image 2" the second, and so on up to ten. Here,
  image 1 is the scene and image 2 supplies the object to place in it:

  <ImageExamples
    examples={[
{
src: "https://cdn.sanity.io/images/2gpum2i6/production/ec365238b6efa1b29f573e83b774c6d539408ea8-1800x1200.webp",
alt: "Close-up of a vinyl record with a red label spinning on a turntable, the tonearm cartridge resting on the grooves.",
label: "Image 1: the turntable"
},
{
src: "https://cdn.sanity.io/images/2gpum2i6/production/ff3bf81b5a8b38f0d870561217b68a72964259ab-1131x1414.webp",
alt: "A red and white vintage Volkswagen van driving on a highway at sunset.",
label: "Image 2: the van"
},
{
src: "https://cdn.sanity.io/images/2gpum2i6/production/0fc855dcc110a26d1689f1fd70005f4f80d58ce2-1800x1193.webp",
alt: "The turntable scene with a miniature red and white van in place of the cartridge, lit to match the record.",
label: "Result"
}
]}
  />

  <PromptDisplay prompt="replace the cartridge from image 1 with the van in image 2, adjust the lighting on the van to integrate well within image 1" />

  ```python theme={null}
  generate({
      "prompt": "replace the cartridge from image 1 with the van in image 2, adjust the lighting on the van to integrate well within image 1",
      "images": [
          "https://cdn.sanity.io/images/2gpum2i6/production/ec365238b6efa1b29f573e83b774c6d539408ea8-1800x1200.webp",
          "https://cdn.sanity.io/images/2gpum2i6/production/ff3bf81b5a8b38f0d870561217b68a72964259ab-1131x1414.webp",
      ],
  }, name="turntable_van")
  ```

  With `aspect_ratio` at `auto`, the output uses the aspect ratio of the first
  image in `images`. The turntable is first, so the result is landscape. For a
  different shape, put a different image first or set `aspect_ratio`.

  For product shots, try-ons, and group compositions, say how the images relate:
  "the person from image 1 wears the jacket from image 2 and stands in the
  kitchen from image 3".

  ## Examples

  Each example shows the inputs, the result, and the prompt that produced it.

  ### Single Image Reference

  Send one image and write the change as an instruction. Drag the handle to
  compare:

  <div className="flux3-usecase-slides flux3-image-slides">
    <ClipSlider
      items={[
{ title: "Recolor an object", before: "https://cdn.sanity.io/images/2gpum2i6/production/fcefd3884f37d5bccfe9db0cd9c675c451712e6d-1200x1600.webp", after: "https://cdn.sanity.io/images/2gpum2i6/production/c239b5e1960e2efb20494f59575d5dacfd550ad3-1350x1800.webp", alt: "a dome pendant lamp with a slatted lower shade over a living room, recolored from yellow and orange to green.", prompt: "Change the Lamp color to #34eb61" },
{ title: "Turn a texture into a product", before: "https://cdn.sanity.io/images/2gpum2i6/production/c76b5b5eb7ff5e4e8ed72fe2b0a84204bfe0d750-1265x1265.webp", after: "https://cdn.sanity.io/images/2gpum2i6/production/217b4ef022a6ad6bf80846967a5648d845bfabbb-1800x1800.webp", alt: "a close-up of chunky purple knit yarn, turned into a purple chunky-knit blanket draped over a white studio block.", prompt: "Create a blanket using the exact texture from the uploaded image, photographed in a clean studio setup." },
{ title: "Replace the background", before: "https://cdn.sanity.io/images/2gpum2i6/production/1d4748b785ee6342041c883a40e77073a366e7d6-960x1440.webp", after: "https://cdn.sanity.io/images/2gpum2i6/production/d78a7cb67e8d53b462ac5a9fdf53e27c2b547884-1192x1800.webp", alt: "a woman in a light blue robe riding a tiger through green forest, moved to a snowy Japanese landscape with red maple trees.", prompt: "Now they are in a snowy Japanese landscape with a Japanese Maple tree." }
]}
    />
  </div>

  ### Multi Image Reference

  Refer to each image by its position in `images`. The output keeps the
  aspect ratio of image 1.

  **Replace an object with a new design**

  <ImageExamples
    examples={[
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/03727e6f70df9b880b3981999520cc1596939d4e-1125x750.webp", alt: "A person in a tweed jacket and heels walking on a sunny street, holding a small dark studded handbag.", label: "Image 1: the scene" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/004b09dc7f88f18a6c92c757d9d6640a8d079997-500x333.webp", alt: "A single orange on a white background.", label: "Image 2: the shape and texture" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/ae8ec3db5d0e6e25353d813b587d60bd5d30c51c-1800x1192.webp", alt: "The same street scene with the handbag replaced by a round orange-textured bag in orange, amber, and olive green.", label: "Result" }
]}
  />

  <PromptDisplay prompt="use image 2 texture and shape to create a bag, and replace the bag in image 1. use #CC5500, #F5A623 #6B8E23 colors for the new bag." />

  **Add elements from several images**

  <ImageExamples
    examples={[
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/c2434a046ec9924f193c8d808d53521d39555c67-600x750.webp", alt: "A manor house in autumn with a lawn covered in fallen red and orange leaves.", label: "Image 1: the scene" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/ed59a358bc2bb01e52dd883884c9e15aeed2994a-501x750.webp", alt: "A calm river winding through autumn woods.", label: "Image 2: the lake" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/1115c978855de91712c50bb74e8fdf50800f7864-500x750.webp", alt: "Two white ducks standing on green grass.", label: "Image 3: the ducks" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/e7505021059d3dc37345b6fc24c3ae20faec9d4a-1350x1800.webp", alt: "The manor scene with a pond on the left and two white ducks on the leaf-covered lawn.", label: "Result" }
]}
  />

  <PromptDisplay prompt="Use image 1 as the main image. Insert the lake from image 2 on the left side of image 1, and make sure it blends naturally with the surroundings as if it is part of the garden view. Insert the ducks from image 3 into image 1, and make them appear as if they are near the lake on the grass" />

  **Furnish a room**

  <ImageExamples
    examples={[
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/14d7c84ee265510fdb305cb9604c93051df25470-1033x1549.webp", alt: "An empty room with white walls, a window, a radiator, and a wooden floor.", label: "Image 1: the room" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/3752b46e481f4130c0aaf9a74ca9a8907e8d3877-842x743.webp", alt: "A light blue metal children's bed with a white mattress.", label: "Image 2: the bed" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/1397b7d9dd91e713265c449397fa290fc5ea6c5b-789x715.webp", alt: "A white daisy-shaped cushion with a yellow center.", label: "Image 3: the cushion" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/89334989282a29aab334d901f2698b651807cfbd-653x712.webp", alt: "A pink rug with a pattern of small red hearts.", label: "Image 4: the rug" },
{ src: "https://cdn.sanity.io/images/2gpum2i6/production/46c0287003ef5978fb535275722c1d55246b0f6e-1192x1800.webp", alt: "The same room with the blue bed against the left wall, the daisy cushion on the bed, and the heart rug on the floor.", label: "Result" }
]}
  />

  <PromptDisplay prompt="Furnish the exact room in image 1 with the following items: the bed from image 2 on the left side of the room near the wall. on top of the bed the cushion from image 3. on the floor the carpet from image 4" />

  ### Edit with Bboxes

  Each edit changes one boxed element and anchors the rest. Select an
  example, then switch between before and after.

  <EditExplorer examples={FLUX3_EDITING_BBOX_EXAMPLES} />

  ## Send a request

  Copy a request body from the [bounding box examples](/flux_3/flux3_image_bounding_boxes#examples)
  and paste it where indicated below, including its outer braces. To adapt an edit to your image, update the image URL, element
  descriptions, and source and target boxes.

  <CodeGroup>
    ```python Python theme={null}
    import json

    body = r'''
    Paste the complete copied JSON body here.
    '''
    generate(json.loads(body))
    ```

    ```bash cURL theme={null}
    curl --fail-with-body https://api.bfl.ai/v1/flux-3-image \
      -H "x-key: $BFL_API_KEY" \
      -H "Content-Type: application/json" \
      -d @- <<'EOF'
    Paste the copied request body here.
    EOF
    ```
  </CodeGroup>

  `generate()` is defined in the [complete clients](/flux_3/flux3_image_generate#quickstart).
  It submits the request, polls until the image is ready, and saves it. With
  cURL, poll the returned `polling_url` until `status` is `Ready`, then download
  `result.sample`.

  <AccordionGroup>
    <Accordion title="Coordinate conversion and editing tips">
      Use the aspect ratio you designed the layout for, because the grid
      stretches with the frame. For edits, `auto`
      follows the first reference; without a reference, it produces a square.

      ```python theme={null}
      def to_bbox(left, top, right, bottom, width, height):
          return [round(top / height * 1000), round(left / width * 1000),
                  round(bottom / height * 1000), round(right / width * 1000)]

      to_bbox(384, 108, 1536, 972, 1920, 1080)  # [100, 200, 900, 800]
      ```

      To move or resize an element, use different source and target boxes.
      Use matching boxes to ask for it to stay in place. State additions and
      removals in the written instruction as well as the rows.

      Very small boxes can fail. In our tests, a new element in a box about
      40 × 25 pixels often did not appear, so give new elements a larger box.

      Give text its own box and quote the words in `desc`.
      See [Layout prompts](/guides/prompting_layout) for more examples.
    </Accordion>

    <Accordion title="Try drawing a box">
      <span id="try-a-box" />

      Draw over a marked region, or choose one below the image, then select
      an object. This demo uses saved edits and makes no API requests.

      <BboxExplorer />
    </Accordion>

    <Accordion title="Pixel-perfect edits">
      <span id="pixel-preservation" />

      Pixels outside the boxes usually stay the same. Shadows, reflections,
      or nearby lighting may still change.

      Compare each input with its edit. Switch to Changed pixels to see which
      areas the edit touched.

      <PixelDiff examples={FLUX3_PIXEL_EXAMPLES} />
    </Accordion>
  </AccordionGroup>

  ## Keep exploring

  <CardGroup cols={2}>
    <Card title="FLUX 3 Text to Image" icon="wand-magic-sparkles" href="/flux_3/flux3_image_generate">
      Complete clients that submit, poll, and save, plus edits and
      multi-reference requests.
    </Card>

    <Card title="API reference" icon="code" href="/api-reference/utility/generate-an-image-with-flux-3">
      The full request schema, limits, and response codes.
    </Card>

    <Card title="Layout prompts" icon="vector-square" href="/guides/prompting_layout">
      How to write the caption and element table for a layout.
    </Card>
  </CardGroup>
</div>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.