/* RADAR1 — Program Out: stage composition, layout state machine, scaling */
const DSa = window.Radar1DesignSystem_1f4661;

const STAGE_W = 1920, STAGE_H = 1080, HERO_W = 1280, HERO_H = 720, MARGIN = 48, RAIL_GAP = 44, INFO_H = 64;

const APP_DEFAULTS = {
  layout: "A", auto: true, mode: "normal",
  heroScene: "studio", tile: null, boxes: 0, cams: [], camTimes: {}, countdown: false,
  h24: true, l3Variant: "story",
  insets: { l3: true, ots: false, phone: false, map: false, loc: false, source: false },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "tickerDur": 46,
  "railDwell": 18,
  "swapMin": 10,
  "safeGuides": false,
  "backdrop": "auto"
}/*EDITMODE-END*/;

function loadState() {
  try {
    const raw = localStorage.getItem("r1-program-v1");
    if (!raw) return APP_DEFAULTS;
    const p = JSON.parse(raw);
    const merged = { ...APP_DEFAULTS, ...p, insets: { ...APP_DEFAULTS.insets, ...(p.insets || {}) } };
    if (merged.mode === "critical") merged.mode = "situation"; /* legacy name */
    if (merged.mode !== "normal" && merged.mode !== "situation") merged.mode = "normal";
    if (merged.tile === "cutspot" && !(window.R1DATA || {}).cutspot) merged.tile = null; /* persisted spot with no content — don't blank the hero */
    return merged;
  } catch (e) { return APP_DEFAULTS; }
}

/* ---------- Hero inset (1280×720 framed program video) ---------- */
function HeroInset({ st, h24 }) {
  const now = useNow();
  const ins = st.insets;
  const tileActive = !!st.tile, boxesActive = st.boxes > 0;
  const situation = st.mode === "situation";
  const showL3 = ins.l3 && !tileActive;
  const TileComp = tileActive ? window.TILES[st.tile].comp : null;
  return (
    <div data-screen-label="Hero Inset" style={{
      position: "absolute", width: HERO_W, height: HERO_H,
      borderRadius: "var(--sweep-lg)", overflow: "hidden",
      /* hairline drawn as an inset ring so the content box stays a true 1280×720 */
      boxShadow: "var(--shadow-lg)",
      background: "#060C17",
    }}>
      {/* content layer — program video is keyed in downstream: the inlay is a clean chroma hole.
         Magenta: green/blue/cyan all collide with our signal accents; nothing in the package is magenta. */}
      {tileActive ? <TileComp h24={h24} />
        : boxesActive ? <MultiBox count={st.boxes} />
        : <div style={{ position: "absolute", inset: 0, background: "#FF00FF" }} />}

      {/* scrim under lower third (never over the chroma hole — it would spoil the key) */}
      {showL3 && tileActive && (
        <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: 220, background: "var(--scrim-bottom)", pointerEvents: "none" }} />
      )}

      {/* dressings */}
      {ins.ots && !tileActive && !boxesActive && !ins.phone && <OTSBox />}
      {ins.phone && !tileActive && !boxesActive && <PhonePanel />}
      {ins.map && !tileActive && <MapInlay />}
      {showL3 && <HeroLowerThird variant={st.l3Variant} situation={situation} />}

      {/* hairline frame drawn OVER the feed — content box stays a true 1280×720 */}
      <div style={{ position: "absolute", inset: 0, zIndex: 60, pointerEvents: "none", borderRadius: "var(--sweep-lg)",
        boxShadow: "inset 0 0 0 1px var(--border-soft)" }} />
    </div>
  );
}

/* ---------- Corner flag (unused in current modes; kept for future events) ---------- */
function CornerBrand() { return null; }

/* ---------- Animated stage backdrop (subtle, 4 looks, slow crossfade) ---------- */
const BACKDROPS = ["sweep", "grid3d", "spotlight", "lake"];

/* deterministic droplet field for the lake look */
const LAKE_DROPS = Array.from({ length: 9 }, (_, i) => {
  let s = i * 971 + 31;
  const r = () => (s = (s * 16807) % 2147483647) / 2147483647;
  return { x: 4 + r() * 90, y: 10 + r() * 78, dur: 5 + r() * 4, delay: -r() * 9, size: 220 + r() * 260 };
});

function StageBackdrop({ mode, situation }) {
  const auto = mode === "auto" || !BACKDROPS.includes(mode);
  const ai = useCycle(BACKDROPS.length, 150000);
  const active = situation ? "sweep" : (auto ? BACKDROPS[ai] : mode);
  const beam = situation ? "rgba(228,3,46,0.05)" : "rgba(31,168,224,0.065)";
  const beamFaint = situation ? "rgba(228,3,46,0.014)" : "rgba(31,168,224,0.018)";
  const layer = (name, on, children) => (
    <div key={name} style={{ position: "absolute", inset: 0, opacity: on ? 1 : 0, transition: "opacity 2400ms var(--ease-out)", pointerEvents: "none" }}>{children}</div>
  );
  const GRID_C = 20, GRID_R = 12;
  return (
    <div aria-hidden="true" style={{ position: "absolute", inset: 0, overflow: "hidden", pointerEvents: "none" }}>
      {/* radar sweep — faint rotating beam + range rings from stage center */}
      {layer("sweep", active === "sweep", (
        <React.Fragment>
          <div style={{ position: "absolute", left: "50%", top: "50%", width: 2400, height: 2400, margin: "-1200px 0 0 -1200px", borderRadius: "50%",
            background: `conic-gradient(from 0deg, ${beam} 0deg, ${beamFaint} 42deg, transparent 78deg, transparent 360deg)`,
            animation: "r1-bg-rot 90s linear infinite", transition: "background 1400ms var(--ease-out)" }} />
          {[520, 860, 1240].map((d) => (
            <div key={d} style={{ position: "absolute", left: "50%", top: "50%", width: d, height: d, margin: `${-d / 2}px 0 0 ${-d / 2}px`, borderRadius: "50%", border: "1px solid rgba(255,255,255,0.028)" }} />
          ))}
        </React.Fragment>
      ))}
      {/* floating 3D tile plane — a diagonal ripple breathes through the squares */}
      {layer("grid3d", active === "grid3d", (
        <div style={{ position: "absolute", inset: 0, perspective: 1000, perspectiveOrigin: "50% 30%" }}>
          <div style={{ position: "absolute", left: "50%", top: "58%", width: 3200, height: 2000, margin: "-1000px 0 0 -1600px",
            transform: "rotateX(56deg)", transformStyle: "preserve-3d",
            display: "grid", gridTemplateColumns: `repeat(${GRID_C}, 1fr)`, gridTemplateRows: `repeat(${GRID_R}, 1fr)`, gap: 10,
            animation: "r1-bg-gridfloat 26s ease-in-out infinite alternate" }}>
            {Array.from({ length: GRID_C * GRID_R }, (_, i) => {
              const r = i / GRID_C | 0, c = i % GRID_C;
              return (
                <div key={i} style={{ background: "rgba(31,168,224,0.028)", border: "1px solid rgba(255,255,255,0.03)", borderRadius: 3,
                  animation: `r1-bg-tile 7s ease-in-out ${-((r + c) % 14) * 0.5}s infinite`, willChange: "transform, opacity" }} />
              );
            })}
          </div>
        </div>
      ))}
      {/* searching spotlight — a soft beam wanders the studio floor */}
      {layer("spotlight", active === "spotlight", (
        <React.Fragment>
          <div style={{ position: "absolute", left: -450, top: -450, width: 900, height: 900, borderRadius: "50%",
            background: "radial-gradient(circle, rgba(205,228,255,0.075), rgba(31,168,224,0.02) 55%, transparent 68%)",
            animation: "r1-bg-spot 74s ease-in-out infinite" }} />
          <div style={{ position: "absolute", left: -700, top: -700, width: 1400, height: 1400, borderRadius: "50%",
            background: "radial-gradient(circle, rgba(31,168,224,0.028), transparent 62%)",
            animation: "r1-bg-spot 74s ease-in-out -1.2s infinite" }} />
        </React.Fragment>
      ))}
      {/* lake — droplets strike and rings spread across still water */}
      {layer("lake", active === "lake", (
        <React.Fragment>
          {LAKE_DROPS.map((d, i) => (
            <span key={i} style={{ position: "absolute", left: d.x + "%", top: d.y + "%" }}>
              {[0, 0.45].map((off, k) => (
                <span key={k} style={{ position: "absolute", left: -d.size / 2, top: -d.size / 7, width: d.size, height: d.size / 3.5,
                  border: "1.5px solid rgba(31,168,224,0.16)", borderRadius: "50%",
                  animation: `r1-bg-ripple ${d.dur}s ease-out ${d.delay + off * d.dur}s infinite`, opacity: 0, willChange: "transform, opacity" }} />
              ))}
              <span style={{ position: "absolute", left: -3, top: -3, width: 6, height: 6, borderRadius: "50%", background: "rgba(205,228,255,0.5)",
                animation: `r1-bg-plink ${d.dur}s ease-out ${d.delay}s infinite`, opacity: 0 }} />
            </span>
          ))}
        </React.Fragment>
      ))}
    </div>
  );
}

/* ---------- The 1080p stage ---------- */
function Stage({ st, tweaks, pendingLayout, pendingMode, lastSwapKind, breakout, onBreakoutDone }) {
  const situation = st.mode === "situation";
  /* furniture height is identical in both modes so the hero never shifts */
  const stackH = 104;
  const furn = INFO_H + stackH;
  const isA = st.layout === "A";
  const swapping = pendingLayout != null && pendingLayout !== st.layout;
  const modeSwapping = pendingMode != null && pendingMode !== st.mode;
  /* layout swaps: hero + furniture share ONE 980ms broadcast-ease glide — old furniture
     slides out while the new slides in, all in lockstep, like tiles moved as a single unit.
     Mode swaps keep the slower fade choreography. */
  const SWAP_MS = 980;
  const exitDur = modeSwapping ? 1500 : SWAP_MS;
  const enterDur = lastSwapKind === "mode" ? 1440 : 480;
  /* hero slides toward the TARGET layout as soon as a swap starts */
  const heroIsA = (pendingLayout || st.layout) === "A";
  const heroY = heroIsA ? Math.round((STAGE_H - furn - HERO_H) / 2) : furn + Math.round((STAGE_H - furn - HERO_H) / 2);
  const heroX = heroIsA ? STAGE_W - HERO_W - MARGIN : MARGIN;
  const railW = STAGE_W - HERO_W - 2 * MARGIN - RAIL_GAP;
  const railXFor = (a) => (a ? MARGIN : MARGIN + HERO_W + RAIL_GAP);
  const railYFor = (a) => (a ? Math.round((STAGE_H - furn - HERO_H) / 2) : furn + Math.round((STAGE_H - furn - HERO_H) / 2));
  const railX = railXFor(isA);
  const railHeroY = railYFor(isA);
  /* furniture choreography — the main rail/bars element lives in the TARGET slot during a swap
     and persists after it (same key, same animation string), so nothing remounts or snaps at the end */
  const railAnim = modeSwapping
    ? `r1-out-${isA ? "left" : "right"} ${exitDur}ms var(--ease-in) both`
    : lastSwapKind === "mode" && !swapping
    ? `r1-in-${isA ? "left" : "right"} ${enterDur}ms var(--ease-broadcast) both`
    : `r1-tile-in-${heroIsA ? "left" : "right"} ${SWAP_MS}ms var(--ease-broadcast) both`;
  const barAnim = modeSwapping
    ? `r1-out-${isA ? "bottom" : "top"} ${exitDur}ms var(--ease-in) both`
    : lastSwapKind === "mode" && !swapping
    ? `r1-in-${isA ? "bottom" : "top"} ${enterDur}ms var(--ease-broadcast) both`
    : `r1-tile-in-${heroIsA ? "down" : "up"} ${SWAP_MS}ms var(--ease-broadcast) both`;
  const brandAnim = swapping || modeSwapping
    ? `r1-out-fade ${exitDur}ms var(--ease-in) both`
    : `r1-in-fade ${enterDur}ms var(--ease-out) both`;
  const BreakoutComp = breakout ? window.BREAKOUTS[breakout] : null;
  return (
    <div data-screen-label="Program Out 1080p" style={{
      position: "relative", width: STAGE_W, height: STAGE_H, overflow: "hidden",
      background: "linear-gradient(160deg, #0D1B33 0%, var(--radar-ink) 48%, #060C17 100%)",
    }}>
      {/* crisis tone — the studio navy shifts to a somber deep red */}
      <div style={{ position: "absolute", inset: 0, background: "linear-gradient(160deg, #2E0A14 0%, #1C0710 50%, #0D0409 100%)", opacity: situation || pendingMode === "situation" ? 1 : 0, transition: "opacity 2800ms var(--ease-out)", pointerEvents: "none" }} />
      <StageBackdrop mode={tweaks.backdrop} situation={situation} />

      {/* corner brand in negative space */}
      <div key={"brand-" + st.layout + st.mode} style={{ position: "absolute", left: isA ? MARGIN : railX, ...(isA ? { top: 0, height: railHeroY } : { bottom: 0, height: STAGE_H - (railHeroY + HERO_H) }), display: "flex", alignItems: "center", animation: brandAnim }}>
        <CornerBrand st={st} />
      </div>

      {/* side rail */}
      {/* outgoing rail — keyed as the pre-swap main element, so the SAME instance slides out */}
      {swapping && (
        <div key={"rail-" + st.layout + st.mode} style={{ position: "absolute", left: railXFor(isA), top: railYFor(isA), width: railW, height: HERO_H, animation: `r1-tile-out-${isA ? "left" : "right"} ${SWAP_MS}ms var(--ease-broadcast) both` }}>
          <R1Boundary name="rail"><SideRail key={st.mode} mode={st.mode} h24={st.h24} dwell={tweaks.railDwell * 1000} cams={st.cams || []} camTimes={st.camTimes || {}} />
          {st.countdown && <RailCountdown label={(window.R1DATA.countdownMeta || {}).label || "NEXT POLLS CLOSE"} target={(window.R1DATA.countdownMeta || {}).target} accent="var(--signal-cyan)" />}</R1Boundary>
        </div>
      )}
      {/* main rail — sits in the target slot from swap start and persists past the flip */}
      <div key={"rail-" + (pendingLayout || st.layout) + st.mode} style={{ position: "absolute", left: railXFor(heroIsA), top: railYFor(heroIsA), width: railW, height: HERO_H, animation: railAnim }}>
        <R1Boundary name="rail"><SideRail key={st.mode} mode={st.mode} h24={st.h24} dwell={tweaks.railDwell * 1000} cams={st.cams || []} camTimes={st.camTimes || {}} />
        {st.countdown && <RailCountdown label={(window.R1DATA.countdownMeta || {}).label || "NEXT POLLS CLOSE"} target={(window.R1DATA.countdownMeta || {}).target} accent="var(--signal-cyan)" />}</R1Boundary>
      </div>

      {/* hero — glides between corners on layout swap */}
      <div style={{ position: "absolute", left: heroX, top: heroY, transition: "left 980ms var(--ease-broadcast), top 980ms var(--ease-broadcast)", zIndex: 10 }}>
        {/* frame-attached boxes (outside the inlay) */}
        {st.insets.loc && !st.tile && (
          <LocatorBox text={(window.HERO_SCENES[st.heroScene] || {}).locator} tz={(window.HERO_SCENES[st.heroScene] || {}).tz} h24={st.h24} />
        )}
        {st.insets.source && !st.tile && (
          <AttributionBox text={(window.HERO_SCENES[st.heroScene] || {}).source} />
        )}
        <R1Boundary name="hero"><HeroInset st={st} h24={st.h24} /></R1Boundary>
      </div>

      {/* info bar + tickers move as one edge group; situation merges the info bar into the double flipper */}
      {/* outgoing bars — same instance as the pre-swap main bars, sliding off its edge */}
      {swapping && (
        <div key={"bars-" + st.layout + st.mode} style={{ position: "absolute", left: 0, right: 0, height: furn, ...(isA ? { bottom: 0 } : { top: 0 }), animation: `r1-tile-out-${isA ? "down" : "up"} ${SWAP_MS}ms var(--ease-broadcast) both` }}>
          {!situation && (
            <div style={{ position: "absolute", left: 0, right: 0, height: INFO_H, ...(isA ? { top: 0 } : { bottom: 0 }) }}>
              <R1Boundary name="infobar"><InfoBar h24={st.h24} mode={st.mode} /></R1Boundary>
            </div>
          )}
          <R1Boundary name="tickers"><TickerStack mode={st.mode} dur={tweaks.tickerDur} edge={isA ? "bottom" : "top"} h24={st.h24} /></R1Boundary>
        </div>
      )}
      {/* main bars — target edge from swap start, persists past the flip */}
      <div key={"bars-" + (pendingLayout || st.layout) + st.mode} style={{ position: "absolute", left: 0, right: 0, height: furn, ...(heroIsA ? { bottom: 0 } : { top: 0 }), animation: barAnim }}>
        {!situation && (
          <div style={{ position: "absolute", left: 0, right: 0, height: INFO_H, ...(heroIsA ? { top: 0 } : { bottom: 0 }) }}>
            <R1Boundary name="infobar"><InfoBar h24={st.h24} mode={st.mode} /></R1Boundary>
          </div>
        )}
        <R1Boundary name="tickers"><TickerStack mode={st.mode} dur={tweaks.tickerDur} edge={heroIsA ? "bottom" : "top"} h24={st.h24} /></R1Boundary>
      </div>

      {/* title-safe guides */}
      {tweaks.safeGuides && (
        <div style={{ position: "absolute", inset: "6% 5%", border: "1px dashed rgba(31,168,224,0.55)", pointerEvents: "none", zIndex: 95 }}>
          <span style={{ position: "absolute", left: 8, top: 6, color: "var(--signal-cyan)", fontFamily: "var(--font-ui)", fontSize: 12, fontWeight: 700, letterSpacing: "0.16em" }}>TITLE SAFE</span>
        </div>
      )}

      {/* breakout scenes */}
      {BreakoutComp && <R1Boundary name="breakout"><BreakoutComp onDone={onBreakoutDone} /></R1Boundary>}
    </div>
  );
}

/* ---------- App: scaling + state + console ---------- */
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [st, setSt] = React.useState(loadState);
  const [pendingLayout, setPendingLayout] = React.useState(null);
  const pendingRef = React.useRef(null);
  const [pendingMode, setPendingMode] = React.useState(null);
  const pendingModeRef = React.useRef(null);
  const [lastSwapKind, setLastSwapKind] = React.useState("layout");
  const [breakout, setBreakout] = React.useState(null);
  const [nextSwapAt, setNextSwapAt] = React.useState(() => Date.now() + t.swapMin * 60000);
  const [scale, setScale] = React.useState(0.5);
  const [fit, setFit] = React.useState("fit");
  const monitorRef = React.useRef(null);

  /* mode swap: same choreography as a layout swap but at 1/3 speed —
     background fades while the old furniture leaves and the new bar styles come in */
  const swapModeTo = React.useCallback((next) => {
    setSt((s) => {
      if (next === s.mode || pendingModeRef.current || pendingRef.current) return s;
      pendingModeRef.current = next;
      setPendingMode(next);
      setTimeout(() => {
        setSt((s2) => ({ ...s2, mode: next }));
        setPendingMode(null);
        pendingModeRef.current = null;
        setLastSwapKind("mode");
      }, 1500);
      return s;
    });
  }, []);

  const set = React.useCallback((k, v) => {
    if (k === "mode") { swapModeTo(v); return; }
    setSt((s) => {
      const val = typeof v === "function" ? v(s[k]) : v;
      const n = { ...s, [k]: val };
      return n;
    });
  }, [swapModeTo]);

  React.useEffect(() => {
    try { localStorage.setItem("r1-program-v1", JSON.stringify(st)); } catch (e) {}
  }, [st]);

  /* stage scaling */
  React.useEffect(() => {
    const el = monitorRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => {
      const r = el.getBoundingClientRect();
      setScale(Math.min(r.width / STAGE_W, r.height / STAGE_H));
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  /* layout swap: hero glides to its new corner while old furniture exits
     toward its edge and new furniture pushes in from the opposite side */
  const swapNow = React.useCallback((target) => {
    if (pendingRef.current) return;
    const next = target || (st.layout === "A" ? "B" : "A");
    if (next === st.layout) return;
    pendingRef.current = next;
    setPendingLayout(next);
    setTimeout(() => {
      setSt((s) => ({ ...s, layout: next }));
      setPendingLayout(null);
      pendingRef.current = null;
      setLastSwapKind("layout");
      setNextSwapAt(Date.now() + t.swapMin * 60000);
    }, 980);
  }, [st.layout, t.swapMin]);

  /* auto-swap timer */
  const now = useNow();
  React.useEffect(() => { setNextSwapAt(Date.now() + t.swapMin * 60000); }, [t.swapMin, st.auto]);
  React.useEffect(() => {
    if (st.auto && Date.now() >= nextSwapAt && !breakout) swapNow();
  }, [now, st.auto, nextSwapAt, breakout, swapNow]);
  const nextSwapIn = Math.max(0, Math.round((nextSwapAt - now.getTime()) / 1000));

  const playBreakout = React.useCallback((k) => setBreakout((b) => (b === k ? null : k)), []);

  /* live director (js/live.js dispatches "r1director" on change): the server drives
     the on-air state through the same API the hidden console uses */
  const lastBreakoutRef = React.useRef(null);
  React.useEffect(() => {
    const applyDirector = (d) => {
      if (!d) return;
      if (d.mode === "normal" || d.mode === "situation") set("mode", d.mode);
      const tile = d.tile && window.TILES[d.tile] ? d.tile : null;
      set("tile", tile);
      set("countdown", !!d.countdown);
      /* lower third: register server content as a variant, toggle the inset from l3_on */
      if (d.l3 && d.l3.headline && window.L3_VARIANTS) {
        window.L3_VARIANTS.live_server = {
          kicker: d.l3.kicker || "TOP STORY", accent: d.l3.accent || "var(--radar-red)",
          headline: d.l3.headline, subline: d.l3.subline || "",
        };
        set("l3Variant", "live_server");
      }
      if (typeof d.l3_on === "boolean") set("insets", (p) => ({ ...p, l3: d.l3_on }));
      /* breakout is one-shot: fire only when the value changes; null clears nothing */
      const prev = lastBreakoutRef.current;
      lastBreakoutRef.current = d.breakout || null;
      if (d.breakout && d.breakout !== prev && window.BREAKOUTS[d.breakout]) {
        if (window.R1LIVE) window.R1LIVE.appliedBreakout = d.breakout;
        playBreakout(d.breakout);
      }
    };
    const onDirector = (e) => applyDirector((e && e.detail) || (window.R1LIVE && window.R1LIVE.director));
    window.addEventListener("r1director", onDirector);
    /* live.js may have connected before we mounted */
    if (window.R1LIVE && window.R1LIVE.director) applyDirector(window.R1LIVE.director);
    return () => window.removeEventListener("r1director", onDirector);
  }, [set, playBreakout]);

  useLucide([st]);

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100vh", background: "#04070D" }}>
      {/* monitor area */}
      <div ref={monitorRef} style={{ flex: 1, minHeight: 0, position: "relative", overflow: fit === "exact" ? "auto" : "hidden" }}>
        {fit === "exact" ? (
          <div style={{ width: STAGE_W, height: STAGE_H, margin: "0 auto" }}>
            <R1Boundary name="stage"><Stage st={st} tweaks={t} pendingLayout={pendingLayout} pendingMode={pendingMode} lastSwapKind={lastSwapKind} breakout={breakout} onBreakoutDone={() => setBreakout(null)} /></R1Boundary>
          </div>
        ) : (
          <div style={{ width: STAGE_W, height: STAGE_H, flex: "none", position: "absolute", left: "50%", top: "50%", transform: `translate(-50%, -50%) scale(${scale})`, outline: "1px solid var(--grey-800)" }}>
            <R1Boundary name="stage"><Stage st={st} tweaks={t} pendingLayout={pendingLayout} pendingMode={pendingMode} lastSwapKind={lastSwapKind} breakout={breakout} onBreakoutDone={() => setBreakout(null)} /></R1Boundary>
          </div>
        )}
      </div>

      {/* director console — HIDDEN, not removed: the state API (set/swapNow/playBreakout) is the
         toggle surface the companion app will drive; keep it mounted so that wiring stays live */}
      <div data-screen-label="Director Console" style={{ display: "none" }}>
        <ControlStrip st={st} set={set} swapNow={swapNow} playBreakout={playBreakout} nextSwapIn={nextSwapIn} breakout={breakout} scale={fit === "exact" ? 1 : scale} fit={fit} setFit={setFit} />
      </div>

      <TweaksPanel>
        <TweakSection label="Motion" />
        <TweakSlider label="Ticker crawl" value={t.tickerDur} min={28} max={80} step={2} unit="s" onChange={(v) => setTweak("tickerDur", v)} />
        <TweakSlider label="Card dwell" value={t.railDwell} min={8} max={40} step={1} unit="s" onChange={(v) => setTweak("railDwell", v)} />
        <TweakSection label="Burn-in rotation" />
        <TweakSlider label="Auto-swap interval" value={t.swapMin} min={1} max={30} step={1} unit="min" onChange={(v) => setTweak("swapMin", v)} />
        <TweakSection label="Backdrop" />
        <TweakSelect label="Ambient look" value={t.backdrop} options={[{ value: "auto", label: "Auto-cycle" }, { value: "sweep", label: "Radar sweep" }, { value: "grid3d", label: "Floating grid" }, { value: "spotlight", label: "Searchlight" }, { value: "lake", label: "Rain on lake" }]} onChange={(v) => setTweak("backdrop", v)} />
        <TweakSection label="Production" />
        <TweakToggle label="Title-safe guides" value={t.safeGuides} onChange={(v) => setTweak("safeGuides", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
