/* ============================================================
   APP — setup (fleets→scenario) then full guided round loop:
   command → ship → squadron → status, with attack resolver,
   secret command planning, squadrons, scoring.
   ============================================================ */
const { useState: useStateApp, useEffect: useEffApp, useMemo: useMemoApp, useRef: useRefApp } = React;

const PALETTES = {
  vivid: { empire: "#4ea3ff", rebel: "#ff5a52", republic: "#ffb13d", separatist: "#6be07a" },
  muted: { empire: "#8fb7ff", rebel: "#e8643c", republic: "#d9b24a", separatist: "#56b0a0" },
  deep:  { empire: "#5a8dd6", rebel: "#c0492f", republic: "#c98a2e", separatist: "#4f9e6a" },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "tactical",
  "palette": "vivid",
  "accentFollowsPlayer": true,
  "showArcs": true,
  "tokenStyle": "silhouette"
}/*EDITMODE-END*/;

const emptySide = () => ({ faction: null, fleetName: "", ships: [], squadrons: [] });

/* ---- persistence: a refresh resumes the game (safe + versioned) ---- */
const SAVE_KEY = "armada_save_v6";
const OLD_SAVE_KEY_V5 = "armada_save_v5";
const OLD_SAVE_KEY_V4 = "armada_save_v4";
const OLD_SAVE_KEY_V3 = "armada_save_v3";
const OLD_SAVE_KEY = "armada_save_v2";
// v2 → v3: nieuwe activatie-velden per schip, commandTokens vervalt, g.sqTurn toegevoegd
function migrateV2(s) {
  const ships = (s.ships || []).map((sh) => {
    const { commandTokens, ...rest } = sh;
    return { ...rest, activationStarted: !!sh.revealedDial && !sh.activated, squadronPtsLeft: 0 };
  });
  return { ...s, v: 3, ships, g: s.g ? { ...s.g, sqTurn: null } : s.g };
}
// v3 → v4: generieke objective-tokens toegevoegd (missing field → lege lijst, geen crash)
function migrateV3(s) { return { ...s, v: 4, objectiveTokens: s.objectiveTokens || [] }; }
// v4 → v5: schadekaartendek + per-schip critCards toegevoegd; ontbrekend dek herbouwen i.p.v. crashen
function migrateV4(s) {
  const ships = (s.ships || []).map((sh) => ({ critCards: [], ...sh }));
  const damage = (typeof window !== "undefined" && window.CATALOG_DATA && window.CATALOG_DATA.damage) || [];
  const g = s.g ? { ...s.g, damageDeck: s.g.damageDeck || buildDamageDeck(damage, Math.random) } : s.g;
  return { ...s, v: 5, ships, g };
}
// v5 → v6: obstakels (asteroïde/debris/station) toegevoegd; ontbrekend veld → lege lijst
function migrateV5(s) { return { ...s, v: 6, obstacles: s.obstacles || [] }; }
function loadSave() {
  try {
    const s = JSON.parse(localStorage.getItem(SAVE_KEY));
    if (s && s.v === 6) return { objectiveTokens: [], obstacles: [], ...s };
  } catch (e) {}
  try {
    const v5 = JSON.parse(localStorage.getItem(OLD_SAVE_KEY_V5));
    if (v5 && v5.v === 5) {
      const m = migrateV5(v5);
      localStorage.setItem(SAVE_KEY, JSON.stringify(m));
      localStorage.removeItem(OLD_SAVE_KEY_V5);
      return m;
    }
  } catch (e) {}
  try {
    const v4 = JSON.parse(localStorage.getItem(OLD_SAVE_KEY_V4));
    if (v4 && v4.v === 4) {
      const m = migrateV5(migrateV4(v4));
      localStorage.setItem(SAVE_KEY, JSON.stringify(m));
      localStorage.removeItem(OLD_SAVE_KEY_V4);
      return m;
    }
  } catch (e) {}
  try {
    const v3 = JSON.parse(localStorage.getItem(OLD_SAVE_KEY_V3));
    if (v3 && v3.v === 3) {
      const m = migrateV5(migrateV4(migrateV3(v3)));
      localStorage.setItem(SAVE_KEY, JSON.stringify(m));
      localStorage.removeItem(OLD_SAVE_KEY_V3);
      return m;
    }
  } catch (e) {}
  try {
    const old = JSON.parse(localStorage.getItem(OLD_SAVE_KEY));
    if (old && old.v === 2) {
      const m = migrateV5(migrateV4(migrateV3(migrateV2(old))));
      localStorage.setItem(SAVE_KEY, JSON.stringify(m));
      localStorage.removeItem(OLD_SAVE_KEY);
      return m;
    }
  } catch (e) {}
  return null;
}
function clearSave() { try { localStorage.removeItem(SAVE_KEY); } catch (e) {} }
const SAVED = loadSave();

function deployShips(list, faction, edge) {
  const cols = Math.min(Math.max(list.length, 1), 6);
  const span = 980;
  return list.map((s, i) => {
    const col = i % cols, row = Math.floor(i / cols);
    const x = (col - (cols - 1) / 2) * (span / Math.max(cols, 1));
    const baseY = edge === "top" ? -248 : 248, dir = edge === "top" ? 1 : -1;
    const y = baseY + dir * (48 + row * 74);
    return { ...s, faction, x, y, facing: edge === "top" ? 180 : 0 };
  });
}
function deploySquadrons(list, faction, edge) {
  const cols = Math.min(Math.max(list.length, 1), 8);
  const span = 720;
  return list.map((q, i) => {
    const col = i % cols, row = Math.floor(i / cols);
    const x = (col - (cols - 1) / 2) * (span / Math.max(cols, 1));
    const baseY = edge === "top" ? -160 : 160, dir = edge === "top" ? 1 : -1;
    const y = baseY + dir * row * 56;
    return { ...q, faction, x, y };
  });
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const { g, setG, addLog } = useGame();

  const [phase, setPhase] = useStateApp(SAVED ? SAVED.phase : "menu"); // menu | rules | setup | deploy | play
  const [step, setStep] = useStateApp("fleets");
  const [sideA, setSideA] = useStateApp(SAVED ? SAVED.sideA : emptySide);
  const [sideB, setSideB] = useStateApp(SAVED ? SAVED.sideB : emptySide);
  const [scenario, setScenario] = useStateApp(SAVED ? SAVED.scenario : null);
  const [setupDialog, setSetupDialog] = useStateApp(null);

  const [ships, setShips] = useStateApp(SAVED ? SAVED.ships : []);
  const [squadrons, setSquadrons] = useStateApp(SAVED ? SAVED.squadrons : []);
  const [objectiveTokens, setObjectiveTokens] = useStateApp(SAVED && SAVED.objectiveTokens ? SAVED.objectiveTokens : []);
  const [selectedObjTokenId, setSelectedObjTokenId] = useStateApp(null);
  const [obstacles, setObstacles] = useStateApp(SAVED && SAVED.obstacles ? SAVED.obstacles : []);
  const [selectedObstacleId, setSelectedObstacleId] = useStateApp(null);
  const [selectedId, setSelectedId] = useStateApp(null);
  const [dialog, setDialog] = useStateApp(null);
  const [objectiveHelpOpen, setObjectiveHelpOpen] = useStateApp(false);
  const [maneuver, setManeuver] = useStateApp(null);
  const [gameOverlay, setGameOverlay] = useStateApp(null); // "command" | "end"
  const [attackShipId, setAttackShipId] = useStateApp(null);
  const [statsShipId, setStatsShipId] = useStateApp(null);
  const [helpPhase, setHelpPhase] = useStateApp(null);
  const [tutorial, setTutorial] = useStateApp(false);
  const [guided, setGuided] = useStateApp(SAVED && SAVED.guided != null ? SAVED.guided : true);
  const [coachOn, setCoachOn] = useStateApp(SAVED && SAVED.coachOn != null ? SAVED.coachOn : true);
  const [sheetOpen, setSheetOpen] = useStateApp(false);
  const [tab, setTab] = useStateApp("detail");
  const [showEffects, setShowEffects] = useStateApp(false);
  const [effTick, setEffTick] = useStateApp(0); // her-render als effecten wijzigen

  const scrollRef = useRefApp(null);
  const secRefs = { detail: useRefApp(null), dice: useRefApp(null), roster: useRefApp(null) };

  const sides = useMemoApp(() => FACTION_ORDER.filter((f) => ships.some((s) => s.faction === f)), [ships]);
  const scenarioObj = SCENARIOS.find((s) => s.id === scenario) || null;
  const selectedShip = ships.find((s) => s.id === selectedId) || null;
  const selectedSquadron = squadrons.find((s) => s.id === selectedId) || null;
  const activeFaction = g ? g.activeFaction : (sides[0] || null);

  // ============ CPU-tegenstander ============
  const cpuFactions = useMemoApp(() => [sideA, sideB].filter((sd) => sd && sd.isCpu && sd.faction).map((sd) => sd.faction), [sideA, sideB]);
  const isCpu = (f) => cpuFactions.includes(f);
  // verse snapshots voor beslissingen binnen getimede CPU-stappen
  const shipsRef = useRefApp(ships); shipsRef.current = ships;
  const squadronsRef = useRefApp(squadrons); squadronsRef.current = squadrons;
  const obstaclesRef = useRefApp(obstacles); obstaclesRef.current = obstacles;
  const gRef = useRefApp(g); gRef.current = g;
  const cpuBusy = useRefApp(false);

  // trekt de bovenste kaart van het gedeelde schadekaartendek; werkt gRef direct bij zodat
  // opeenvolgende trekkingen binnen dezelfde activatie (bv. CPU-dubbelaanval) niet dezelfde kaart hergebruiken.
  const drawCritCard = () => {
    const deck = gRef.current && gRef.current.damageDeck;
    if (!deck) return null;
    const r = drawDamageCard(deck, Math.random);
    if (!r.card) return null;
    gRef.current = { ...gRef.current, damageDeck: r.deck };
    setG((s) => s ? { ...s, damageDeck: r.deck } : s);
    return r.card;
  };

  // restore in-progress game once on mount
  useEffApp(() => { if (SAVED && SAVED.g) setG(SAVED.g); }, []);
  // persist: a refresh resumes exactly where you were
  useEffApp(() => {
    try { localStorage.setItem(SAVE_KEY, JSON.stringify({ v: 6, phase, sideA, sideB, scenario, ships, squadrons, objectiveTokens, obstacles, g, guided, coachOn })); } catch (e) {}
  }, [phase, sideA, sideB, scenario, ships, squadrons, objectiveTokens, obstacles, g, guided, coachOn]);

  // ============ SETUP ============
  const mutateSide = (slot, patch) => {
    const set = slot === "A" ? setSideA : setSideB;
    set((sd) => {
      let next = { ...sd };
      if (patch.faction !== undefined) next.faction = patch.faction;
      if (patch.isCpu !== undefined) next.isCpu = patch.isCpu;
      if (patch.fleetName !== undefined) next.fleetName = patch.fleetName;
      if (patch.addShip) next.ships = [...next.ships, patch.addShip];
      if (patch.addShips) next.ships = [...next.ships, ...patch.addShips];
      if (patch.addSquadrons) next.squadrons = [...next.squadrons, ...patch.addSquadrons];
      if (patch.removeShip) next.ships = next.ships.filter((s) => s.id !== patch.removeShip);
      if (patch.removeSquadron) next.squadrons = next.squadrons.filter((q) => q.id !== patch.removeSquadron);
      if (patch.updateShip) next.ships = next.ships.map((s) => s.id === patch.updateShip.id ? { ...s, ...patch.updateShip.patch } : s);
      if (patch.addSquadron) {
        const ex = next.squadrons.find((q) => q.catalogId === patch.addSquadron.catalogId);
        if (ex && !patch.addSquadron.unique) next.squadrons = next.squadrons.map((q) => q === ex ? { ...q, count: (q.count || 1) + 1 } : q);
        else if (!ex || !patch.addSquadron.unique) next.squadrons = [...next.squadrons, patch.addSquadron];
      }
      if (patch.setSquadronCount) {
        const { id, count } = patch.setSquadronCount;
        next.squadrons = count <= 0 ? next.squadrons.filter((q) => q.id !== id) : next.squadrons.map((q) => q.id === id ? { ...q, count } : q);
      }
      return next;
    });
  };
  const setupAddShip = (ship) => { if (setupDialog) mutateSide(setupDialog.slot, { addShip: ship }); };
  // catalogus: echt schip toevoegen (stats uit base-size, echte punten/slots)
  const setupAddCatalogShip = (entry) => {
    if (!setupDialog) return;
    mutateSide(setupDialog.slot, { addShip: makeShip({ name: entry.title, faction: entry.faction, baseSize: entry.size, points: entry.points, slots: entry.slots, catalogType: entry.id || null, unique: entry.unique, ...statsForCatalogShip(entry) }) });
  };
  const setupAddSquadron = (entry) => {
    if (!setupDialog) return;
    mutateSide(setupDialog.slot, { addSquadron: { id: uid("sq"), catalogId: entry.id, name: entry.title, faction: entry.faction, points: entry.points, count: 1, unique: entry.unique, keywords: entry.keywords } });
  };
  const setupSetUpgrades = (slot, shipId, upgrades) => mutateSide(slot, { updateShip: { id: shipId, patch: { upgrades } } });
  const setupImport = (fleet) => {
    if (!setupDialog) return;
    const slot = setupDialog.slot;
    const fac = (slot === "A" ? sideA : sideB).faction || fleet.faction;
    const placed = fleetToShips(fleet, -1).map((s) => ({ ...s, faction: fac }));
    mutateSide(slot, { addShips: placed, addSquadrons: fleet.squadrons.map((q) => ({ ...q, faction: fac })),
      fleetName: fleet.name || (slot === "A" ? sideA : sideB).fleetName });
  };

  // vult de resterende punten van een vloot aan met een willekeurige, geldige selectie
  const randomFill = (slot, budget) => {
    const side = slot === "A" ? sideA : sideB;
    if (!side.faction) return;
    const cur = side.ships.reduce((n, s) => n + s.points + (s.upgrades || []).reduce((u, x) => u + (x.points || 0), 0), 0)
              + side.squadrons.reduce((n, q) => n + q.points * (q.count || 1), 0);
    const remaining = Math.max(0, (budget || 400) - cur);
    if (remaining <= 0) return;
    const shipPool = shipsForFaction(side.faction);
    const squadronPool = squadronsForFaction(side.faction);
    const upgradePool = CATALOG.upgrades.filter((u) => factionMatch(u.faction, side.faction));
    const res = fillRandomFleet({ budget: remaining, existingShips: side.ships, existingSquadrons: side.squadrons,
      shipPool, squadronPool, upgradePool });
    const addShips = res.ships.map((d) => makeShip({
      name: d.entry.title, faction: side.faction, baseSize: d.entry.size, points: d.entry.points,
      slots: d.entry.slots, catalogType: d.entry.id || null, unique: d.entry.unique, upgrades: d.upgrades, ...statsForCatalogShip(d.entry) }));
    // gelijke niet-uniek squadrons samenvoegen tot één regel met count
    const sqMap = {};
    res.squadrons.forEach((d) => {
      const key = d.entry.unique ? d.entry.id + "|u" + Object.keys(sqMap).length : d.entry.id;
      if (sqMap[key]) sqMap[key].count += d.count;
      else sqMap[key] = { id: uid("sq"), catalogId: d.entry.id, name: d.entry.title, faction: side.faction,
        points: d.entry.points, count: d.count, unique: d.entry.unique, keywords: d.entry.keywords };
    });
    const addSquadrons = Object.values(sqMap);
    if (!addShips.length && !addSquadrons.length) return;
    mutateSide(slot, { addShips, addSquadrons, fleetName: side.fleetName || "Willekeurige vloot" });
  };

  const quickSeed = (cpuB = false) => {
    const seed = buildSeed();
    const a = { faction: "empire", fleetName: "Imperial Task Force",
      ships: seed.ships.filter((s) => s.faction === "empire"), squadrons: seed.squadrons.filter((q) => q.faction === "empire") };
    const b = { faction: "rebel", fleetName: "Rebel Strike Group", isCpu: cpuB,
      ships: seed.ships.filter((s) => s.faction === "rebel"), squadrons: seed.squadrons.filter((q) => q.faction === "rebel") };
    setSideA(a); setSideB(b); setScenario("outpost");
    startGame(a, b, "outpost", null);
  };

  // build & launch a game. placements: optional {shipId:{x,y,facing}}; sqPlacements: {"faction#i":{x,y}} from guided deploy
  const startGame = (a = sideA, b = sideB, sc = scenario, placements = null, sqPlacements = null) => {
    const place = (list, faction, edge) => {
      const auto = deployShips(list, faction, edge);
      if (!placements) return auto;
      return auto.map((s) => placements[s.id] ? { ...s, ...placements[s.id] } : s);
    };
    const aShips = place(a.ships, a.faction, "top");
    const bShips = place(b.ships, b.faction, "bottom");
    const all = [...aShips, ...bShips];
    // flat-index van expandSquadrons matcht de "faction#i"-keys uit DeployStage
    const placeSq = (side, edge) => {
      const auto = deploySquadrons(expandSquadrons(side.squadrons.map((q) => ({ ...q, faction: side.faction }))), side.faction, edge);
      if (!sqPlacements) return auto;
      return auto.map((q, i) => sqPlacements[`${side.faction}#${i}`] ? { ...q, ...sqPlacements[`${side.faction}#${i}`] } : q);
    };
    const aSq = placeSq(a, "top");
    const bSq = placeSq(b, "bottom");
    setShips(all);
    setSquadrons([...aSq, ...bSq]);
    setObjectiveTokens([]); setSelectedObjTokenId(null);
    setObstacles([]); setSelectedObstacleId(null);
    setSelectedId(null); setManeuver(null); setGameOverlay(null);
    const order = FACTION_ORDER.filter((f) => all.some((s) => s.faction === f));
    setG({ ...freshGame(order, sc), started: true });
    setPhase("play");
  };

  // from Setup → go to guided deployment
  const goDeploy = () => { setManeuver(null); setPhase("deploy"); };
  const onDeployDone = (placements, sqPlacements) => { startGame(sideA, sideB, scenario, placements, sqPlacements); };

  // tutorial: a small, clear 2-v-2 demo at long range, no squadrons
  const startTutorial = () => {
    const mk = (name, faction, baseSize, x, y, facing) => makeShip({ name, faction, baseSize, points: baseSize === "large" ? 120 : 70, x, y, facing });
    const a = { faction: "empire", fleetName: "Trainingseskader", squadrons: [],
      ships: [mk("Vigil-Class Cruiser", "empire", "medium", -150, -190, 180), mk("Dominator Dreadnought", "empire", "large", 150, -190, 180)] };
    const b = { faction: "rebel", fleetName: "Rebellen-patrouille", squadrons: [],
      ships: [mk("Liberator Frigate", "rebel", "medium", -150, 190, 0), mk("Hammerhead Razor", "rebel", "small", 150, 190, 0)] };
    setSideA(a); setSideB(b); setScenario("open");
    const placements = {};
    [...a.ships, ...b.ships].forEach((s) => { placements[s.id] = { x: s.x, y: s.y, facing: s.facing }; });
    setTutorial(true);
    startGame(a, b, "open", placements);
  };

  const backToMenu = () => { setPhase("menu"); setManeuver(null); };
  const backToSetup = () => { clearSave(); setPhase("setup"); setStep("fleets"); setManeuver(null); setG(null); };

  // ============ ship ops ============
  const updateShip = (id, patch) => setShips((ss) => ss.map((s) => s.id === id ? { ...s, ...patch, destroyed: (patch.hull != null ? patch.hull : s.hull) <= 0 } : s));
  const rotateShip = (id, d) => setShips((ss) => ss.map((s) => s.id === id ? { ...s, facing: ((s.facing + d) % 360 + 360) % 360 } : s));
  // Vrij slepen = handmatig herpositioneren; bewust vrijgesteld van off-board-dood/botsschade (R3/R4). Die gelden alleen bij een echte manoeuvre via resolveMove (dial + CPU).
  const moveShip = (id, x, y) => setShips((ss) => ss.map((s) => s.id === id ? { ...s, x, y } : s));
  const moveSquadron = (id, x, y) => setSquadrons((qs) => qs.map((q) => q.id === id ? { ...q, x, y, moved: g && g.phase === "squadron" ? true : q.moved } : q));
  const removeShip = (id) => { setShips((ss) => ss.filter((s) => s.id !== id)); if (selectedId === id) setSelectedId(null); };
  const removeSquadron = (id) => { setSquadrons((qs) => qs.filter((q) => q.id !== id)); if (selectedId === id) setSelectedId(null); };
  const updateSquadron = (id, patch) => setSquadrons((qs) => qs.map((q) => q.id === id ? { ...q, ...patch, destroyed: (patch.hull != null ? patch.hull : q.hull) <= 0 } : q));
  const cycleToken = (id, tokenId) => setShips((ss) => ss.map((s) => {
    if (s.id !== id) return s;
    const order = ["ready", "exhausted", "discarded"];
    return { ...s, defenseTokens: s.defenseTokens.map((dt) => dt.id === tokenId ? { ...dt, state: order[(order.indexOf(dt.state) + 1) % 3] } : dt) };
  }));
  const fillDefaults = (id) => setShips((ss) => ss.map((s) => {
    if (s.id !== id) return s;
    const def = BASE_SIZES[s.baseSize];
    return { ...s, maxHull: def.hull, hull: def.hull, maxShields: { ...def.shields }, shields: { ...def.shields },
      command: def.command, attackDice: cloneArm(DEFAULT_ARMAMENT[s.baseSize]),
      defenseTokens: def.defaults.map((tp) => ({ id: uid("dt"), type: tp, state: "ready" })), statsMissing: false };
  }));
  const saveStats = (id, patch) => setShips((ss) => ss.map((s) => s.id === id ? { ...s, ...patch } : s));

  // ============ maneuver ============
  // Navigate-dial: ±1 speed en één extra yaw-klik (één joint mag naar ±3)
  const toggleManeuver = (id) => {
    const ship = ships.find((s) => s.id === id);
    // begeleide ship-fase: alleen het actieve (gecommitte) schip mag manoeuvreren
    if (guided && g && g.phase === "ship" && !(ship && ship.activationStarted && !ship.activated && !ship.dialPending)) {
      addLog(`${ship ? ship.name : "Schip"}: onthul eerst de dial (en kies gebruiken/bewaren).`, "warn");
      return;
    }
    setManeuver((m) => m && m.shipId === id ? null : { shipId: id, yaw: [0, 0, 0, 0], nav: !!ship && (ship.revealedDial === "navigate" || ship.navToken), speedDelta: 0 });
  };
  const setYaw = (k, d) => setManeuver((m) => {
    if (!m) return m;
    const yaw = m.yaw.slice();
    const extraElders = yaw.some((v, i) => i !== k && Math.abs(v || 0) > MV_MAX);
    // Nav Team / Engine Techs: extra yaw-klik(ken) per gewricht, bovenop de Navigate-dial-bonus
    const ship = ships.find((s) => s.id === m.shipId);
    const extraYaw = ship ? upgradeExtraYaw(ship) : 0;
    const lim = (m.nav && !extraElders ? MV_MAX + 1 : MV_MAX) + extraYaw;
    yaw[k] = Math.max(-lim, Math.min(lim, (yaw[k] || 0) + d));
    return { ...m, yaw };
  });
  const setSpeedDelta = (d) => setManeuver((m) => {
    if (!m || !m.nav) return m;
    const ship = ships.find((s) => s.id === m.shipId);
    if (!ship) return m;
    const nd = Math.max(-1, Math.min(1, (m.speedDelta || 0) + d));
    if (ship.speed + nd < 0 || ship.speed + nd > 4) return m;
    return { ...m, speedDelta: nd };
  });
  // Verwerkt eindpositie: terugschuiven bij schip-overlap → botsschade → squadrons wegzetten → off-board-dood.
  // extra = { facing, speed? } wordt in dezelfde setShips-mutatie meegenomen (voorkomt een race met een losse updateShip).
  const resolveMove = (shipId, startPos, endPos, extra) => {
    const mover = shipsRef.current.find((s) => s.id === shipId);
    if (!mover) return;
    const others = shipsRef.current.filter((s) => s.id !== shipId && !s.destroyed);
    const moverUnit = { baseSize: mover.baseSize };

    // R4a schip⨯schip: terugschuiven tot geen overlap
    const overlappedShips = others.filter((o) => basesOverlap({ ...endPos, baseSize: mover.baseSize }, o, BASE_SIZES));
    let finalPos = endPos;
    if (overlappedShips.length) finalPos = backOffAlongPath(startPos, endPos, others, moverUnit, BASE_SIZES);

    // R4a schade: 1 romp aan beweger + dichtstbijzijnde geraakte schip (bypass shields)
    let collideWith = null;
    if (overlappedShips.length) {
      const nearest = overlappedShips.reduce((a, b) =>
        Math.hypot(finalPos.x - a.x, finalPos.y - a.y) < Math.hypot(finalPos.x - b.x, finalPos.y - b.y) ? a : b);
      collideWith = { id: nearest.id, dmg: 1 };
    }

    // R4b schip⨯squadron: overreden squadrons tegen de basis
    const sqMoves = [];
    const shipAt = { ...mover, x: finalPos.x, y: finalPos.y };
    squadronsRef.current.forEach((q) => {
      if (q.destroyed) return;
      if (basesOverlap({ x: q.x, y: q.y, baseSize: "medium" }, { x: finalPos.x, y: finalPos.y, baseSize: mover.baseSize }, BASE_SIZES)) {
        const p = placeSquadronClear({ x: q.x, y: q.y, baseSize: "medium" }, shipAt, [], BASE_SIZES);
        sqMoves.push({ id: q.id, x: p.x, y: p.y });
      }
    });

    // R3 off-board na alle correcties
    const dead = offBoard({ x: finalPos.x, y: finalPos.y, baseSize: mover.baseSize }, BASE_SIZES);

    // obstakels: asteroïde/debris → 1 romp-schade (bypass shields, geen crit-trekking); station → geen schade
    const moverRadius = baseRadius(mover.baseSize, BASE_SIZES);
    const overlapping = (obstaclesRef.current || []).filter((o) =>
      Math.hypot(finalPos.x - o.x, finalPos.y - o.y) < moverRadius + (o.r || 40));
    const hitObstacle = overlapping.find((o) => o.type === "asteroid" || o.type === "debris");
    const obstacleDmg = hitObstacle ? 1 : 0;

    // state toepassen — één setShips-mutatie voor mover (positie + facing/speed + botsschade + obstakelschade + off-board-dood) en target
    setShips((ss) => ss.map((s) => {
      if (s.id === shipId) {
        let hull = s.hull;
        if (collideWith) hull = Math.max(0, hull - 1);
        if (obstacleDmg) hull = Math.max(0, hull - obstacleDmg);
        if (dead) hull = 0;
        const destroyed = dead || hull <= 0;
        const facingPatch = extra && extra.facing != null ? { facing: extra.facing } : {};
        const speedPatch = extra && extra.speed != null ? { speed: extra.speed } : {};
        return { ...s, x: finalPos.x, y: finalPos.y, ...facingPatch, ...speedPatch, hull, destroyed };
      }
      if (collideWith && s.id === collideWith.id) {
        const hull = Math.max(0, s.hull - collideWith.dmg);
        return { ...s, hull, destroyed: hull <= 0 };
      }
      return s;
    }));
    if (sqMoves.length) setSquadrons((qs) => qs.map((q) => {
      const mv = sqMoves.find((m) => m.id === q.id); return mv ? { ...q, x: mv.x, y: mv.y } : q;
    }));

    if (collideWith) addLog(`${mover.name} botst — 1 schade aan beide schepen.`, "atk");
    if (obstacleDmg) addLog(`${mover.name} raakt ${hitObstacle.type === "asteroid" ? "een asteroïde" : "debris"} — 1 romp-schade.`, "atk");
    if (dead) addLog(`${mover.name} vaart van het bord en gaat verloren — punten naar de tegenstander.`, "bad");
  };

  const confirmManeuver = () => {
    if (!maneuver) return;
    const ship = ships.find((s) => s.id === maneuver.shipId);
    if (ship) {
      const startPos = { x: ship.x, y: ship.y };
      const delta = maneuver.nav ? (maneuver.speedDelta || 0) : 0;
      const r = computeManeuver(ship, maneuver.yaw, delta);
      const endPos = { x: r.end.x, y: r.end.y };
      resolveMove(ship.id, startPos, endPos, { facing: Math.round(r.end.facing), speed: delta ? ship.speed + delta : undefined });
      addLog(`${ship.name} manoeuvreert (speed ${r.speed})${delta ? ` — Navigate: speed ${ship.speed}→${ship.speed + delta}` : ""}.`, "info");
    }
    setManeuver(null);
  };

  // ============ GAME FLOW ============
  // CPU plant zijn dials automatisch; alleen menselijke facties zien de planner
  const humanOrder = (g && g.firstFaction ? [g.firstFaction, ...sides.filter((f) => f !== g.firstFaction)] : sides).filter((f) => !isCpu(f));
  const planCommands = () => { if (humanOrder.length === 0) onCommandsComplete({}); else setGameOverlay("command"); };
  const onCommandsComplete = (queues) => {
    const all = { ...queues };
    ships.forEach((s) => {
      if (isCpu(s.faction) && !s.destroyed && !all[s.id]) {
        const enemies = ships.filter((x) => x.faction !== s.faction && !x.destroyed);
        all[s.id] = cpuPlanCommands(s, dialsToPlan(s, g.round), enemies);
      }
    });
    setShips((ss) => ss.map((s) => {
      const q = all[s.id];
      if (!q) return s;
      return { ...s, commandQueue: [...s.commandQueue, ...q.filter(Boolean)] };
    }));
    setGameOverlay(null);
    setG((s) => ({ ...s, phase: "ship", activeFaction: s.firstFaction, lastActivated: null }));
    addLog("Command dials gepland. Ship-fase begint.", "info");
  };

  // auto=true (CPU): dial wordt direct voluit gebruikt, geen keuzemoment.
  // auto=false (mens): dial wordt onthuld maar niet toegepast — dialPending=true tot
  // resolveDialChoice (voluit) of storeDialAsToken (bewaren) gekozen wordt.
  const revealDial = (shipId, auto) => {
    setShips((ss) => ss.map((s) => {
      if (s.id !== shipId) return s;
      const q = s.commandQueue.slice();
      const dial = q.shift() || null;
      if (!dial || auto) {
        let engineeringPts = 0;
        if (dial === "repair") engineeringPts = critEngineeringValue(s, s.engineering);
        const squadronPtsLeft = dial === "squadron" ? s.squadronValue : 0;
        // activationStarted óók bij lege queue, anders is er geen weg meer uit de activatie
        return { ...s, commandQueue: q, revealedDial: dial, engineeringPts, squadronPtsLeft, activationStarted: true, dialPending: false };
      }
      return { ...s, commandQueue: q, revealedDial: dial, engineeringPts: 0, squadronPtsLeft: 0, activationStarted: true, dialPending: true };
    }));
    const sh = ships.find((s) => s.id === shipId);
    const dial = sh && sh.commandQueue[0];
    if (!dial) addLog(`${sh ? sh.name : "Schip"} activeert zonder dial.`, "info");
    else if (auto) addLog(`${sh.name} onthult ${DIALS[dial].name}.`, "info");
    else addLog(`${sh.name} onthult ${DIALS[dial].name} — gebruik de dial of bewaar hem als token.`, "info");
    setSelectedId(shipId);
  };

  // mens kiest "Gebruik dial": pas alsnog het volledige effect toe
  const resolveDialChoice = (shipId) => {
    setShips((ss) => ss.map((s) => {
      if (s.id !== shipId || !s.dialPending) return s;
      const dial = s.revealedDial;
      let engineeringPts = 0;
      if (dial === "repair") engineeringPts = critEngineeringValue(s, s.engineering);
      const squadronPtsLeft = dial === "squadron" ? s.squadronValue : 0;
      return { ...s, engineeringPts, squadronPtsLeft, dialPending: false };
    }));
    const sh = ships.find((s) => s.id === shipId);
    if (sh && sh.revealedDial) addLog(`${sh.name} gebruikt ${DIALS[sh.revealedDial].name} voluit.`, "info");
  };

  // mens kiest "Bewaar als token": geen effect deze activatie, dial gaat naar commandTokens (cap = command-waarde)
  const storeDialAsToken = (shipId) => {
    setShips((ss) => ss.map((s) => {
      if (s.id !== shipId || !s.dialPending) return s;
      const dial = s.revealedDial;
      const tokens = s.commandTokens || [];
      const gained = dial && tokens.length < s.command ? [...tokens, dial] : tokens;
      return { ...s, commandTokens: gained, revealedDial: null, engineeringPts: 0, squadronPtsLeft: 0, dialPending: false };
    }));
    const sh = ships.find((s) => s.id === shipId);
    const dial = sh && sh.revealedDial;
    if (dial) {
      const capped = (sh.commandTokens || []).length >= sh.command;
      addLog(`${sh.name} bewaart ${DIALS[dial].name} als commandtoken${capped ? " (max al bereikt — token gaat verloren)" : ""}.`, "info");
    }
  };

  // besteed een opgeslagen commandtoken tijdens de eigen activatie — gereduceerd effect (§ command tokens)
  const spendCommandToken = (shipId, kind) => {
    const ship = ships.find((s) => s.id === shipId);
    if (!ship || !ship.activationStarted || ship.activated) return;
    const tokens = ship.commandTokens || [];
    if (!tokens.includes(kind)) return;
    setShips((ss) => ss.map((s) => {
      if (s.id !== shipId) return s;
      const t = s.commandTokens || [];
      const idx = t.indexOf(kind);
      if (idx === -1) return s;
      const next = t.slice(); next.splice(idx, 1);
      const patch = { commandTokens: next };
      if (kind === "repair") patch.engineeringPts = s.engineeringPts + Math.ceil(s.engineering / 2);
      if (kind === "squadron") patch.squadronPtsLeft = s.squadronPtsLeft + 1;
      if (kind === "navigate") patch.navToken = true; // toggleManeuver leest dit als extra yaw-toestemming
      if (kind === "cf") patch.cfToken = true;         // useCF-wiring leest dit (zie AttackResolver)
      return { ...s, ...patch };
    }));
    addLog(`${ship.name} besteedt een ${DIALS[kind].name}-token (gereduceerd effect).`, "info");
  };

  const endActivation = (shipId) => {
    setShips((ss) => ss.map((s) => s.id === shipId ? { ...s, activated: true, revealedDial: null, engineeringPts: 0, activationStarted: false, squadronPtsLeft: 0, dialPending: false, navToken: false, cfToken: false } : s));
    setG((s) => {
      const updated = ships.map((sh) => sh.id === shipId ? { ...sh, activated: true } : sh);
      const next = nextToActivate(updated, sides, s.activeFaction, s.firstFaction);
      if (!next) {
        const sqNext = nextSquadronTurn(squadrons, sides, null, s.firstFaction);
        addLog("Alle schepen geactiveerd. Squadron-fase.", "info");
        return { ...s, phase: "squadron", lastActivated: s.activeFaction,
                 sqTurn: sqNext ? { faction: sqNext, left: 2 } : null,
                 activeFaction: sqNext || s.activeFaction };
      }
      return { ...s, activeFaction: next, lastActivated: s.activeFaction };
    });
    setManeuver(null);
  };

  const spendEngineering = (id, kind, arc) => setShips((ss) => ss.map((s) => {
    if (s.id !== id || s.engineeringPts <= 0) return s;
    if (kind === "hull") { if (s.engineeringPts < 2 || s.hull >= s.maxHull) return s; return { ...s, hull: s.hull + 1, engineeringPts: s.engineeringPts - 2 }; }
    if (kind === "shield") { if (s.shields[arc] >= s.maxShields[arc]) return s; return { ...s, shields: { ...s.shields, [arc]: s.shields[arc] + 1 }, engineeringPts: s.engineeringPts - 1 }; }
    return s;
  }));

  const openAttack = (shipId) => {
    const ship = ships.find((s) => s.id === shipId);
    if (!ship) return;
    if (!shipCanAttack(ship, g)) {
      addLog(`${ship.name}: kan nu niet vuren — alleen tijdens de eigen activatie.`, "warn");
      return;
    }
    setAttackShipId(shipId);
  };
  const applyAttack = (res) => {
    // Schadekaartendek: alleen trekken bij een echte crit (Contain zet tookCrit al terug op false — dus
    // een contained aanval trekt hier niets, wat Contain eindelijk zinvol maakt) én als de verdediger overleeft.
    let drawn = null, structHullLoss = 0;
    if (res.tookCrit && !res.destroyed) {
      const card = drawCritCard();
      if (card) {
        if (card.effect_id === "structural_damage") structHullLoss = 1;
        else drawn = card;
      }
    }
    setShips((ss) => ss.map((s) => {
      if (s.id === res.targetId) {
        const hull = structHullLoss ? Math.max(0, res.hull - structHullLoss) : res.hull;
        // uitgegeven tokens exhausten per id; accuracy-locks blijven ready (lock geldt alleen deze aanval).
        // discardTokenIds komt van NK-7 Ion Cannons / Intel Officer: die tokens gaan wég i.p.v. exhausted.
        const spent = res.spentTokenIds || [], discarded = res.discardTokenIds || [];
        return { ...s, shields: res.shields, hull, destroyed: hull <= 0, critical: s.critical || res.tookCrit,
          critCards: drawn ? [...(s.critCards || []), drawn] : s.critCards,
          defenseTokens: s.defenseTokens.map((dt) => discarded.includes(dt.id) ? { ...dt, state: "discarded" }
            : (spent.includes(dt.id) && dt.state === "ready" ? { ...dt, state: "exhausted" } : dt)) };
      }
      if (s.id === res.attackerId) {
        return { ...s, attacksMade: s.attacksMade + 1, arcsFired: [...s.arcsFired, res.arc],
          attackedZones: [...(s.attackedZones || []), res.targetId + ":" + res.hitArc],
          upgrades: withUpgradesExhausted(s.upgrades, res.usedUpgradeIds || []) };
      }
      return s;
    }));
    if (drawn) {
      const t = shipsRef.current.find((s) => s.id === res.targetId);
      addLog(`${t ? t.name : "Schip"} trekt een schadekaart: ${drawn.title}.`, "bad");
    }
    if (structHullLoss) {
      const t = shipsRef.current.find((s) => s.id === res.targetId);
      addLog(`${t ? t.name : "Schip"}: Structural Damage — 1 extra rompschade.`, "bad");
    }
  };

  // ============ squadrons ============
  const applySquadronDamage = (sqId, targetId, dmg) => {
    const aq = squadronsRef.current.find((q) => q.id === sqId);
    const tq = squadronsRef.current.find((q) => q.id === targetId);
    if (!tq) return;
    const hull = Math.max(0, tq.hull - dmg);
    updateSquadron(targetId, { hull });
    addLog(`${aq ? aq.name : "Squadron"} → ${tq.name}: ${dmg} schade${hull <= 0 ? " — VERNIETIGD" : ""}.`, hull <= 0 ? "bad" : "atk");
    // Counter: als de verdediger de aanval overleeft en Counter heeft binnen bereik, kaatst schade terug op de aanvaller
    if (hull > 0 && aq) {
      const defenderAfter = { ...tq, hull };
      const cd = counterDamage(defenderAfter, aq);
      if (cd > 0) {
        const atkHull = Math.max(0, aq.hull - cd);
        updateSquadron(sqId, { hull: atkHull });
        addLog(`${tq.name} (Counter) → ${aq.name}: ${cd} schade${atkHull <= 0 ? " — VERNIETIGD" : ""}.`, atkHull <= 0 ? "bad" : "atk");
      }
    }
  };
  const applySquadronShipDamage = (sqId, shipId, dmg, crit) => {
    const aq = squadrons.find((q) => q.id === sqId);
    const t = ships.find((s) => s.id === shipId);
    if (!aq || !t) return;
    // squadrons hebben geen vuurbogen: geraakt wordt de hull zone die het dichtst bij
    // het squadron ligt én naar hem toe wijst (zelfde randmeting als bij schepen)
    const facing = ARCS.filter((z) => zoneFaces(t, z, aq, BASE_SIZES));
    const hitArc = nearestZone(aq, t, facing, BASE_SIZES) || "front";
    const res = resolveDamage(t, hitArc, dmg, null);
    const tookCrit = crit > 0;
    let drawn = null, structHullLoss = 0;
    if (tookCrit && !res.destroyed) {
      const card = drawCritCard();
      if (card) {
        if (card.effect_id === "structural_damage") structHullLoss = 1;
        else drawn = card;
      }
    }
    const hull = structHullLoss ? Math.max(0, res.hull - structHullLoss) : res.hull;
    setShips((ss) => ss.map((s) => s.id === shipId ? { ...s, shields: res.shields, hull, destroyed: hull <= 0, critical: s.critical || tookCrit,
      critCards: drawn ? [...(s.critCards || []), drawn] : s.critCards } : s));
    addLog(`${aq.name} → ${t.name} (${ARC_LABEL[hitArc]}): ${dmg} schade${(hull <= 0) ? " — VERNIETIGD" : ""}.`, (hull <= 0) ? "bad" : "atk");
    if (drawn) addLog(`${t.name} trekt een schadekaart: ${drawn.title}.`, "bad");
    if (structHullLoss) addLog(`${t.name}: Structural Damage — 1 extra rompschade.`, "bad");
  };

  // ship → squadron (anti-squadron armament): telt als één van de max. 2 aanvallen van het schip
  const applyShipSquadronDamage = (shipId, sqId, dmg, spentTokenIds) => {
    const ship = ships.find((s) => s.id === shipId);
    const sq = squadronsRef.current.find((q) => q.id === sqId);
    if (!ship || !sq) return;
    const hull = Math.max(0, sq.hull - dmg);
    updateSquadron(sqId, {
      hull,
      defenseTokens: sq.defenseTokens.map((dt) => (spentTokenIds || []).includes(dt.id) && dt.state === "ready" ? { ...dt, state: "exhausted" } : dt),
    });
    addLog(`${ship.name} → ${sq.name} (anti-squadron): ${dmg} schade${hull <= 0 ? " — VERNIETIGD" : ""}.`, hull <= 0 ? "bad" : "atk");
    setShips((ss) => ss.map((s) => s.id === shipId
      ? { ...s, attacksMade: s.attacksMade + 1, attackedZones: [...(s.attackedZones || []), sqId] }
      : s));
  };

  // eén squadron-activatie afronden — via Squadron-dial (ship-fase) of in de squadron-fase
  const finishSquadronActivation = (id) => {
    const q = squadrons.find((x) => x.id === id);
    if (!q || q.activated) return;
    if (g && g.phase === "squadron" && !(g.sqTurn && q.faction === g.sqTurn.faction)) {
      addLog(`${q.name}: niet aan de beurt.`, "warn");
      return;
    }
    if (g && g.phase === "ship") {
      setSquadrons((qs) => qs.map((x) => x.id === id ? { ...x, activated: true } : x));
      setShips((ss) => ss.map((s) => s.activationStarted && !s.activated && s.squadronPtsLeft > 0 ? { ...s, squadronPtsLeft: s.squadronPtsLeft - 1 } : s));
      addLog(`${q.name} geactiveerd (Squadron-command).`, "info");
      setSelectedId(null);
      return;
    }
    const updated = squadrons.map((x) => x.id === id ? { ...x, activated: true } : x);
    setSquadrons((qs) => qs.map((x) => x.id === id ? { ...x, activated: true } : x));
    setG((s) => {
      if (!s.sqTurn) return s;
      const left = s.sqTurn.left - 1;
      const stillHas = updated.some((x) => x.faction === s.sqTurn.faction && !x.activated && !x.destroyed);
      if (left > 0 && stillHas) return { ...s, sqTurn: { ...s.sqTurn, left } };
      const next = nextSquadronTurn(updated, sides, s.sqTurn.faction, s.firstFaction);
      if (!next) { addLog("Alle squadrons geactiveerd. Status-fase.", "info"); return { ...s, phase: "status", sqTurn: null }; }
      return { ...s, sqTurn: { faction: next, left: 2 }, activeFaction: next };
    });
    addLog(`${q.name} geactiveerd.`, "info");
    setSelectedId(null);
  };

  const passSquadronTurn = () => setG((s) => {
    if (!s.sqTurn) return { ...s, phase: "status" };
    const next = nextSquadronTurn(squadrons, sides, s.sqTurn.faction, s.firstFaction);
    if (!next || next === s.sqTurn.faction) { addLog("Squadron-fase klaar. Status-fase.", "info"); return { ...s, phase: "status", sqTurn: null }; }
    return { ...s, sqTurn: { faction: next, left: 2 }, activeFaction: next };
  });

  // fallback: alles in één keer activeren en door naar status
  const resolveSquadrons = () => {
    const unact = squadrons.filter((q) => !q.activated && !q.destroyed);
    if (unact.length) { setSquadrons((qs) => qs.map((q) => ({ ...q, activated: true }))); addLog(`${unact.length} squadrons geactiveerd.`, "info"); }
    setG((s) => ({ ...s, phase: "status", sqTurn: null }));
  };

  // ============ objective tokens (generic board markers, no rules attached) ============
  const addObjectiveToken = () => {
    setObjectiveTokens((list) => {
      const label = String(list.length + 1);
      const jitter = list.length % 5;
      return [...list, { id: uid("objtok"), x: jitter * 24 - 48, y: jitter * 18 - 36, label }];
    });
  };
  const moveObjectiveToken = (id, x, y) => setObjectiveTokens((list) => list.map((t) => t.id === id ? { ...t, x, y } : t));
  const removeObjectiveToken = (id) => {
    setObjectiveTokens((list) => list.filter((t) => t.id !== id));
    if (selectedObjTokenId === id) setSelectedObjTokenId(null);
  };
  const selectObjectiveToken = (id) => { setSelectedObjTokenId(id); if (id) setSelectedId(null); };

  // ============ obstacles (asteroïde/debris/station) ============
  const OBSTACLE_DEFAULT_R = 40;
  const addObstacle = (type) => {
    setObstacles((list) => {
      const jitter = list.length % 5;
      return [...list, { id: uid("obst"), type, x: jitter * 30 - 60, y: jitter * 22 - 44, r: OBSTACLE_DEFAULT_R }];
    });
  };
  const moveObstacle = (id, x, y) => setObstacles((list) => list.map((o) => o.id === id ? { ...o, x, y } : o));
  const removeObstacle = (id) => {
    setObstacles((list) => list.filter((o) => o.id !== id));
    if (selectedObstacleId === id) setSelectedObstacleId(null);
  };
  const selectObstacle = (id) => { setSelectedObstacleId(id); if (id) setSelectedId(null); };

  const addScore = (f, d) => {
    setG((s) => ({ ...s, score: { ...s.score, [f]: Math.max(0, (s.score[f] || 0) + d) } }));
    if (d > 0) addLog(`Objectief: ${FACTIONS[f].name} +${d} punten.`, "info");
  };

  const endStatus = () => {
    setShips((ss) => ss.map(resetForRound));
    setSquadrons((qs) => qs.map((q) => ({ ...q, activated: false, attacked: false, moved: false })));
    setG((s) => {
      if (s.round >= 6) { addLog("Ronde 6 voltooid — potje afgelopen.", "info"); return { ...s, over: true }; }
      const nextFirst = sides.find((f) => f !== s.firstFaction) || s.firstFaction;
      addLog(`Ronde ${s.round + 1} begint. Initiatief naar ${FACTIONS[nextFirst].name}.`, "info");
      return { ...s, round: s.round + 1, phase: "command", firstFaction: nextFirst, activeFaction: nextFirst, lastActivated: null, sqTurn: null };
    });
  };

  useEffApp(() => { if (g && g.over) setGameOverlay("end"); }, [g && g.over]);

  // eliminatie: zodra een factie geen schepen meer heeft, is het potje afgelopen
  useEffApp(() => {
    if (!g || !g.started || g.over || sides.length < 2) return;
    const alive = (f) => ships.some((s) => s.faction === f && !s.destroyed);
    const living = sides.filter(alive);
    if (living.length <= 1) {
      addLog(living.length === 1 ? `${FACTIONS[living[0]].name} heeft de vijandelijke vloot vernietigd — potje afgelopen.` : "Beide vloten vernietigd — potje afgelopen.", "bad");
      setG((s) => ({ ...s, over: true }));
    }
  }, [ships, g && g.started, g && g.over, sides.length]);

  // ============ CPU-aandrijving ============
  // eigen einde-activatie voor de CPU (leest verse state via refs i.p.v. closure)
  const cpuEndActivation = (shipId) => {
    setShips((ss) => ss.map((s) => s.id === shipId ? { ...s, activated: true, revealedDial: null, engineeringPts: 0, activationStarted: false, squadronPtsLeft: 0, dialPending: false, navToken: false, cfToken: false } : s));
    setG((s) => {
      const updated = shipsRef.current.map((sh) => sh.id === shipId ? { ...sh, activated: true } : sh);
      const next = nextToActivate(updated, sides, s.activeFaction, s.firstFaction);
      if (!next) {
        const sqNext = nextSquadronTurn(squadronsRef.current, sides, null, s.firstFaction);
        addLog("Alle schepen geactiveerd. Squadron-fase.", "info");
        return { ...s, phase: "squadron", lastActivated: s.activeFaction, sqTurn: sqNext ? { faction: sqNext, left: 2 } : null, activeFaction: sqNext || s.activeFaction };
      }
      return { ...s, activeFaction: next, lastActivated: s.activeFaction };
    });
    setManeuver(null); setSelectedId(null);
    cpuBusy.current = false;
  };
  const cpuAttackStep = (shipId, n) => {
    if (n >= 2) { cpuEndActivation(shipId); return; }
    const ss = shipsRef.current;
    const atk = ss.find((s) => s.id === shipId);
    if (!atk || atk.destroyed) { cpuEndActivation(shipId); return; }
    const enemies = ss.filter((s) => s.faction !== atk.faction && !s.destroyed);
    const plan = cpuBestAttack(atk, enemies, atk.arcsFired);
    if (!plan) { cpuEndActivation(shipId); return; }
    const res = cpuResolveAttack(atk, plan.target, plan.arc, plan.range, plan.hitZone);
    applyAttack({ attackerId: atk.id, targetId: plan.target.id, arc: plan.arc, hitArc: res.hitArc, range: plan.range,
      shields: res.shields, hull: res.hull, destroyed: res.destroyed, tookCrit: res.crit > 0, spentTokenIds: [] });
    addLog(`${atk.name} → ${plan.target.name} (${ARC_LABEL[plan.arc]}/${RANGE_LABEL[plan.range]}): ${res.dmg} schade${res.destroyed ? " — VERNIETIGD" : ""}`, res.destroyed ? "bad" : "atk");
    setTimeout(() => cpuAttackStep(shipId, n + 1), 750);
  };
  const runCpuShipTurn = () => {
    const ss = shipsRef.current;
    const mine = ss.filter((s) => s.faction === g.activeFaction && !s.activated && !s.destroyed);
    if (!mine.length) { cpuBusy.current = false; return; }
    const enemies = ss.filter((s) => s.faction !== g.activeFaction && !s.destroyed);
    const ship = cpuPickShip(mine, enemies);
    revealDial(ship.id, true); // CPU: geen keuzemoment, dial wordt altijd voluit gebruikt
    setTimeout(() => {
      const live = shipsRef.current.find((s) => s.id === ship.id);
      if (live) {
        const en = shipsRef.current.filter((s) => s.faction !== live.faction && !s.destroyed);
        const mv = cpuPlanShipMove(live, en, live.revealedDial === "navigate");
        if (mv && (Math.round(mv.end.x) !== Math.round(live.x) || Math.round(mv.end.y) !== Math.round(live.y) || Math.round(mv.end.facing) !== live.facing)) {
          const newSpeed = mv.speedDelta ? Math.max(0, Math.min(4, live.speed + mv.speedDelta)) : undefined;
          resolveMove(ship.id, { x: live.x, y: live.y }, { x: mv.end.x, y: mv.end.y }, { facing: Math.round(mv.end.facing), speed: newSpeed });
          addLog(`${ship.name} manoeuvreert${newSpeed != null ? ` (speed → ${newSpeed})` : ""}.`, "info");
        }
      }
      setTimeout(() => cpuAttackStep(ship.id, 0), 650);
    }, 650);
  };
  const runCpuSquadronTurn = () => {
    const f = g.sqTurn && g.sqTurn.faction;
    const mine = squadronsRef.current.filter((q) => q.faction === f && !q.activated && !q.destroyed);
    if (!mine.length) { cpuBusy.current = false; passSquadronTurn(); return; }
    const foesSq = squadronsRef.current.filter((q) => q.faction !== f && !q.destroyed);
    const foesShip = shipsRef.current.filter((s) => s.faction !== f && !s.destroyed);
    const q = cpuPickShip(mine, foesSq.concat(foesShip)) || mine[0]; // hergebruik: dichtst bij / kan schieten
    const rogue = (q.keywords || []).some((k) => String(k).toLowerCase() === "rogue");
    const tgt = cpuNearest(q, foesSq.concat(foesShip));
    // R2: een niet-Rogue eskader mag bewegen ÓF aanvallen, niet beide — check nu al of er
    // vanaf de huidige positie een aanvalsdoel binnen bereik is; zo ja, dan slaat een
    // niet-Rogue eskader de beweging over en valt het meteen aan.
    const esqNow = foesSq.filter((x) => worldDist(q, x) <= SQD_RANGE);
    const eshNow = shipsRef.current.filter((s) => s.faction !== f && !s.destroyed && worldDist(q, s) <= RANGE_DIST.close);
    const canAttackNow = (esqNow.length && poolSize(q.antiSquad) > 0) || (eshNow.length && q.keywords.includes("Bomber") && poolSize(q.antiShip) > 0);
    const willMove = rogue || !canAttackNow;
    if (tgt && willMove) {
      const maxMove = (q.speed || 3) * SQD_MOVE;
      const d = Math.hypot(tgt.x - q.x, tgt.y - q.y);
      const want = Math.max(0, d - SQD_RANGE * 0.6); // net binnen dogfight-bereik komen
      if (want > 1) { const k = Math.min(maxMove, want) / d; updateSquadron(q.id, { x: q.x + (tgt.x - q.x) * k, y: q.y + (tgt.y - q.y) * k }); }
    }
    setTimeout(() => {
      // niet-Rogue: alleen aanvallen als het niet al bewogen is (canAttackNow); Rogue mag altijd nog aanvallen na de move.
      if (rogue || canAttackNow) {
        const q2 = squadronsRef.current.find((x) => x.id === q.id) || q;
        const esq = squadronsRef.current.filter((x) => x.faction !== f && !x.destroyed && worldDist(q2, x) <= SQD_RANGE);
        const esh = shipsRef.current.filter((s) => s.faction !== f && !s.destroyed && worldDist(q2, s) <= RANGE_DIST.close);
        if (esq.length && poolSize(q2.antiSquad) > 0) { const t = esq[0]; const tally = cpuRollSquadron(q2.antiSquad); applySquadronDamage(q2.id, t.id, tally.dmg); }
        else if (esh.length && q2.keywords.includes("Bomber") && poolSize(q2.antiShip) > 0) { const t = esh[0]; const tally = cpuRollSquadron(q2.antiShip); applySquadronShipDamage(q2.id, t.id, tally.dmg, tally.crit); }
      }
      setTimeout(() => { finishSquadronActivation(q.id); cpuBusy.current = false; }, 550);
    }, 550);
  };
  // driver: als het de beurt van de CPU is en er geen dialoog openstaat, zet de volgende stap
  useEffApp(() => {
    if (!g || !g.started || g.over || phase !== "play") return;
    if (gameOverlay || attackShipId || statsShipId || dialog || helpPhase || cpuBusy.current) return;
    let timer;
    if (g.phase === "ship" && isCpu(g.activeFaction)) { cpuBusy.current = true; timer = setTimeout(runCpuShipTurn, 650); }
    else if (g.phase === "squadron" && g.sqTurn && isCpu(g.sqTurn.faction)) { cpuBusy.current = true; timer = setTimeout(runCpuSquadronTurn, 650); }
    return () => { if (timer) { clearTimeout(timer); cpuBusy.current = false; } };
    // activatie-tellers hertriggeren het effect na elke activatie, ook als de factie hetzelfde blijft
  }, [phase, g && g.phase, g && g.activeFaction, g && g.sqTurn && g.sqTurn.faction, g && g.over, g && g.started, gameOverlay, attackShipId, statsShipId, dialog, helpPhase, ships.filter((s) => s.activated).length, squadrons.filter((q) => q.activated).length]);

  // ============ selection / nav ============
  const cpuActing = !!(g && g.started && !g.over && ((g.phase === "ship" && isCpu(g.activeFaction)) || (g.phase === "squadron" && g.sqTurn && isCpu(g.sqTurn.faction))));
  const selectShip = (id) => {
    if (cpuActing) return; // computer is aan zet — mens grijpt niet in
    // guided mode: ignore clicks on ships/squadrons that aren't part of the current step
    if (id && guided && g) {
      const s = ships.find((x) => x.id === id);
      const q = squadrons.find((x) => x.id === id);
      const committed = ships.find((x) => x.activationStarted && !x.activated);
      const sqCmdShip = ships.find((x) => x.activationStarted && !x.activated && x.squadronPtsLeft > 0);
      // na commit alleen het actieve schip; daarvóór elk eligible eigen schip
      const okShip = s && g.phase === "ship" && (committed
        ? s.id === committed.id
        : (s.faction === g.activeFaction && !s.activated && !s.destroyed));
      const okSq = q && !q.activated && !q.destroyed &&
        ((g.phase === "squadron" && q.faction === g.activeFaction) ||
         (g.phase === "ship" && sqCmdShip && q.faction === sqCmdShip.faction));
      if (!okShip && !okSq) return;
    }
    setSelectedId(id);
    if (id) setSelectedObjTokenId(null);
    if (maneuver && id !== maneuver.shipId) setManeuver(null);
    if (id && window.matchMedia("(max-width: 860px)").matches) { setTab("detail"); setSheetOpen(true); scrollSec("detail"); }
  };
  const addShip = (ship) => { setShips((ss) => [...ss, ship]); setSelectedId(ship.id); };
  const importFleet = (fleet) => {
    const side = sides.length && sides[0] === fleet.faction ? 1 : -1;
    setShips((ss) => [...ss, ...fleetToShips(fleet, sides.length ? side : -1)]);
    if (fleet.squadrons.length) setSquadrons((qs) => [...qs, ...deploySquadrons(expandSquadrons(fleet.squadrons.map((q) => ({ ...q, faction: fleet.faction }))), fleet.faction, side < 0 ? "top" : "bottom")]);
  };
  const scrollSec = (key) => {
    setTab(key);
    requestAnimationFrame(() => { const sc = scrollRef.current, el = secRefs[key].current; if (sc && el) sc.scrollTop = el.offsetTop - sc.offsetTop; });
  };

  // ============ theme ============
  const pal = PALETTES[t.palette] || PALETTES.vivid;
  const rootStyle = { "--empire": pal.empire, "--rebel": pal.rebel, "--republic": pal.republic, "--separatist": pal.separatist };
  if (t.accentFollowsPlayer && activeFaction) rootStyle["--accent"] = pal[activeFaction];

  // ============ MENU / RULES / SETUP / DEPLOY ============
  if (phase === "menu") {
    return (
      <div className="app" data-theme={t.theme} style={rootStyle}>
        <MainMenu hasGame={!!g && !g.over} onResume={() => setPhase("play")}
                  onBuild={backToSetup}
                  onDemo={() => { setTutorial(false); quickSeed(false); }}
                  onDemoCpu={() => { setTutorial(false); quickSeed(true); }} onRules={() => setPhase("rules")}
                  onEffects={() => setShowEffects(true)}
                  onTutorial={startTutorial} />
        {showEffects ? <EffectsDialog onClose={() => setShowEffects(false)} onChanged={() => setEffTick((n) => n + 1)} /> : null}
        <TweaksPanel>
          <TweakSection label="Sfeer" />
          <TweakSelect label="Thema" value={t.theme} options={[{ value: "tactical", label: "Tactical console" }, { value: "warroom", label: "Naval war-room" }, { value: "minimal", label: "Minimal tactical" }]} onChange={(v) => setTweak("theme", v)} />
          <TweakRadio label="Factie-palet" value={t.palette} options={[{ value: "vivid", label: "Vivid" }, { value: "muted", label: "Muted" }, { value: "deep", label: "Diep" }]} onChange={(v) => setTweak("palette", v)} />
        </TweaksPanel>
      </div>
    );
  }

  if (phase === "rules") {
    return (
      <div className="app" data-theme={t.theme} style={rootStyle}>
        <RulesScreen onClose={() => setPhase("menu")} onStart={quickSeed} />
      </div>
    );
  }

  if (phase === "deploy") {
    const order = [sideA.faction, sideB.faction].filter(Boolean);
    const sideShips = { [sideA.faction]: sideA.ships, [sideB.faction]: sideB.ships };
    const sideSquadrons = { [sideA.faction]: sideA.squadrons, [sideB.faction]: sideB.squadrons };
    return (
      <div className="app" data-theme={t.theme} style={rootStyle}>
        <DeployStage order={order} sideShips={sideShips} sideSquadrons={sideSquadrons} onDone={onDeployDone} onCancel={() => setPhase("setup")} />
      </div>
    );
  }

  if (phase === "setup") {
    const slot = setupDialog ? setupDialog.slot : null;
    const lockFac = slot ? (slot === "A" ? sideA.faction : sideB.faction) : null;
    return (
      <div className="app" data-theme={t.theme} style={rootStyle}>
        <Setup step={step} setStep={setStep} sideA={sideA} sideB={sideB} scenario={scenario}
               mutateSide={mutateSide} openDialog={(s, k, extra) => setSetupDialog({ slot: s, kind: k, ...(extra || {}) })}
               onChoose={setScenario} onStart={goDeploy} onQuickSeed={quickSeed} onRandomFill={randomFill} onMenu={backToMenu} />
        {setupDialog && setupDialog.kind === "add" ? <AddShipDialog lockFaction={lockFac} onClose={() => setSetupDialog(null)} onAdd={setupAddShip} /> : null}
        {setupDialog && setupDialog.kind === "import" ? <ImportDialog onClose={() => setSetupDialog(null)} onImport={setupImport} /> : null}
        {setupDialog && setupDialog.kind === "catalog" && lockFac ? <ShipCatalogDialog faction={lockFac} onClose={() => setSetupDialog(null)} onAdd={setupAddCatalogShip} /> : null}
        {setupDialog && setupDialog.kind === "squadrons" && lockFac ? <SquadronCatalogDialog faction={lockFac} current={(slot === "A" ? sideA : sideB).squadrons} onClose={() => setSetupDialog(null)} onAdd={setupAddSquadron} /> : null}
        {setupDialog && setupDialog.kind === "upgrades" ? (() => {
          const side = slot === "A" ? sideA : sideB;
          const sh = side.ships.find((s) => s.id === setupDialog.shipId);
          return sh ? <UpgradePicker ship={sh} fleetShips={side.ships} onClose={() => setSetupDialog(null)} onSet={(ups) => setupSetUpgrades(slot, sh.id, ups)} /> : null;
        })() : null}
        <TweaksPanel>
          <TweakSection label="Sfeer" />
          <TweakSelect label="Thema" value={t.theme} options={[{ value: "tactical", label: "Tactical console" }, { value: "warroom", label: "Naval war-room" }, { value: "minimal", label: "Minimal tactical" }]} onChange={(v) => setTweak("theme", v)} />
          <TweakRadio label="Factie-palet" value={t.palette} options={[{ value: "vivid", label: "Vivid" }, { value: "muted", label: "Muted" }, { value: "deep", label: "Diep" }]} onChange={(v) => setTweak("palette", v)} />
        </TweaksPanel>
      </div>
    );
  }

  // ============ PLAY PHASE ============
  const attackShip = ships.find((s) => s.id === attackShipId) || null;
  const statsShip = ships.find((s) => s.id === statsShipId) || null;
  const order = sides.length ? (g && g.firstFaction ? [g.firstFaction, ...sides.filter((f) => f !== g.firstFaction)] : sides) : [];

  return (
    <div className="app" data-theme={t.theme} style={rootStyle}>
      <HotseatBar sides={sides} active={activeFaction} onActive={(f) => setG((s) => ({ ...s, activeFaction: f }))}
                  round={g ? g.round : 1} onRound={(r) => setG((s) => ({ ...s, round: r }))}
                  scenarioName={scenarioObj ? scenarioObj.name : null} onSetup={backToMenu}
                  phase={g ? g.phase : null} guided={guided} onGuided={() => setGuided((v) => !v)} cpuFactions={cpuFactions}
                  coachOn={coachOn} onCoach={() => setCoachOn((v) => !v)} />

      <div className="app-main">
        <div className="app-board-col">
          {g ? (
            <FlowBanner g={g} ships={ships} squadrons={squadrons} selected={selectedShip} cpuActing={cpuActing}
                        maneuvering={!!maneuver} onPick={selectShip} onHelp={() => setHelpPhase(g.phase)}
                        onPlanCommands={planCommands} onRevealDial={(id) => revealDial(id, false)} onEndActivation={endActivation}
                        onAttack={openAttack} onManeuver={toggleManeuver}
                        onResolveDial={resolveDialChoice} onStoreToken={storeDialAsToken} onSpendToken={spendCommandToken}
                        onPassSquadronTurn={passSquadronTurn}
                        onResolveSquadrons={resolveSquadrons} onEndStatus={endStatus} />
          ) : null}
          <Board ships={ships} squadrons={squadrons} sides={sides} selectedId={selectedId} showArcs={t.showArcs} tokenStyle={t.tokenStyle}
                 phase={g ? g.phase : null} activeFaction={activeFaction} guided={guided} cpuActing={cpuActing}
                 maneuver={maneuver} onYaw={setYaw} onSpeedDelta={setSpeedDelta} onManeuverConfirm={confirmManeuver} onManeuverCancel={() => setManeuver(null)}
                 onSelect={selectShip} onMove={moveShip} onMoveSquadron={moveSquadron}
                 objectiveTokens={objectiveTokens} selectedObjTokenId={selectedObjTokenId}
                 onAddObjectiveToken={addObjectiveToken} onMoveObjectiveToken={moveObjectiveToken}
                 onSelectObjectiveToken={selectObjectiveToken} onRemoveObjectiveToken={removeObjectiveToken}
                 obstacles={obstacles} selectedObstacleId={selectedObstacleId}
                 onAddObstacle={addObstacle} onMoveObstacle={moveObstacle}
                 onSelectObstacle={selectObstacle} onRemoveObstacle={removeObstacle} />
        </div>

        <div className="app-side" data-open={sheetOpen}>
          <div className="side-grip" onClick={() => setSheetOpen((o) => !o)}>
            <div className="sg-tabs" onClick={(e) => e.stopPropagation()}>
              {[["detail", "Schip"], ["dice", "Dice"], ["roster", "Roster"]].map(([k, lab]) => (
                <button key={k} className="sg-tab" data-active={tab === k} onClick={() => { setSheetOpen(true); scrollSec(k); }}>{lab}</button>
              ))}
            </div>
          </div>

          <div className="side-scroll" ref={scrollRef}>
            <div ref={secRefs.detail} className="panel-sec">
              {selectedSquadron ? (
                <SquadronPanel sq={selectedSquadron} phase={g ? g.phase : null} guided={guided} g={g}
                               sqCmdShipFaction={ships.find((x) => x.activationStarted && !x.activated && x.squadronPtsLeft > 0)?.faction ?? null}
                               squadrons={squadrons} ships={ships}
                               onUpdate={updateSquadron} onActivate={(id) => updateSquadron(id, { activated: !selectedSquadron.activated })}
                               onFinish={finishSquadronActivation}
                               onDamageSquadron={applySquadronDamage} onDamageShip={applySquadronShipDamage}
                               onRemove={removeSquadron} />
              ) : (
                <ShipPanel ship={selectedShip} maneuvering={!!(maneuver && selectedShip && maneuver.shipId === selectedShip.id)}
                           phase={g ? g.phase : null} activeFaction={activeFaction} guided={guided}
                           cpuControlled={selectedShip ? isCpu(selectedShip.faction) : false}
                           onUpdate={updateShip} onRotate={rotateShip} onRemove={removeShip}
                           onCycleToken={cycleToken} onFillDefaults={fillDefaults} onManeuver={toggleManeuver}
                           onEditStats={(id) => setStatsShipId(id)} onAttack={openAttack}
                           onSpendEng={spendEngineering} />
              )}
            </div>

            {!guided ? (
              <div ref={secRefs.dice} className="panel-sec">
                <div className="sec-head"><span className="sec-accent" /><span className="lbl" style={{ color: "var(--text)" }}>Dobbelpaneel</span></div>
                <DicePanel />
              </div>
            ) : <div ref={secRefs.dice} style={{ display: "none" }} />}

            {g ? (
              <div className="panel-sec">
                <div className="sec-head"><span className="sec-accent" /><span className="lbl" style={{ color: "var(--text)" }}>Stand &amp; log</span></div>
                <div className="sec-body" style={{ paddingTop: 4 }}>
                  <ScorePanel g={g} sides={sides} ships={ships} squadrons={squadrons} scenarioObj={scenarioObj} onAddScore={addScore}
                              onOpenObjectiveHelp={() => setObjectiveHelpOpen(true)} />
                  <div style={{ marginTop: 12 }}><LogPanel log={g.log} /></div>
                </div>
              </div>
            ) : null}

            <div ref={secRefs.roster} className="panel-sec" style={{ borderBottom: "none" }}>
              <Roster ships={ships} squadrons={squadrons} selectedId={selectedId}
                      guided={guided} phase={g ? g.phase : null} activeFaction={activeFaction}
                      onSelect={selectShip} onAddShip={() => setDialog("add")} onImport={() => setDialog("import")}
                      onLoadStats={() => setDialog("stats")} />
            </div>
          </div>
        </div>
      </div>

      {dialog === "add" ? <AddShipDialog onClose={() => setDialog(null)} onAdd={addShip} /> : null}
      {dialog === "import" ? <ImportDialog onClose={() => setDialog(null)} onImport={importFleet} /> : null}
      {dialog === "stats" ? <StatsDatasetDialog ships={ships} onClose={() => setDialog(null)} onApply={(updated) => { setShips(updated); setDialog(null); }} /> : null}
      {statsShip ? <StatsEditor ship={statsShip} onSave={saveStats} onClose={() => setStatsShipId(null)} /> : null}
      {attackShip ? (
        <AttackResolver attacker={attackShip} ships={ships} squadrons={squadrons} obstacles={obstacles} useCF={attackShip.revealedDial === "cf" || attackShip.cfToken}
                        onApply={applyAttack} onApplySquadron={applyShipSquadronDamage} onClose={() => setAttackShipId(null)} addLog={addLog} />
      ) : null}
      {gameOverlay === "command" && g ? (
        <CommandPlanner order={humanOrder.length ? humanOrder : order} ships={ships} round={g.round}
                        onComplete={onCommandsComplete} onCancel={() => setGameOverlay(null)} />
      ) : null}
      {gameOverlay === "end" && g ? (
        <EndScreen g={g} sides={sides} ships={ships} squadrons={squadrons} scenarioObj={scenarioObj}
                   onRestart={() => startGame(sideA.faction ? sideA : { faction: sides[0], ships: ships.filter((s) => s.faction === sides[0]), squadrons: [] }, sideB.faction ? sideB : { faction: sides[1], ships: ships.filter((s) => s.faction === sides[1]), squadrons: [] }, scenario)}
                   onSetup={backToSetup} />
      ) : null}
      {helpPhase ? <PhaseHelp phase={helpPhase} onClose={() => setHelpPhase(null)} /> : null}
      {objectiveHelpOpen && scenarioObj ? <ObjectiveHelp scenario={scenarioObj} onClose={() => setObjectiveHelpOpen(false)} /> : null}
      {tutorial && g ? (
        <Tutorial onFinish={() => setTutorial(false)}
                  ctx={{
                    phase: g.phase,
                    activeFaction: g.activeFaction,
                    selectedActivatable: !!(selectedShip && selectedShip.faction === g.activeFaction && !selectedShip.activated && !selectedShip.destroyed),
                    hasInProgress: ships.some((s) => s.faction === g.activeFaction && s.activationStarted && !s.activated),
                    round: g.round,
                  }} />
      ) : null}

      {g && coachOn && guided && !tutorial && !cpuActing && !gameOverlay && !attackShipId && !dialog && !statsShipId && !helpPhase ? (
        (() => {
          const committed = ships.find((s) => s.activationStarted && !s.activated && !isCpu(s.faction));
          return (
            <Coach onDismiss={() => setCoachOn(false)}
                   ctx={{
                     phase: g.phase,
                     round: g.round,
                     committed: !!committed,
                     committedShip: committed ? committed.name : null,
                     selectedEligible: !!(selectedShip && !selectedShip.activated && !selectedShip.destroyed && selectedShip.faction === g.activeFaction && !isCpu(g.activeFaction)),
                     myUnact: ships.filter((s) => s.faction === g.activeFaction && !s.activated && !s.destroyed).length,
                     sqTurn: g.sqTurn ? g.sqTurn.faction : null,
                   }} />
          );
        })()
      ) : null}

      <TweaksPanel>
        <TweakSection label="Sfeer" />
        <TweakSelect label="Thema" value={t.theme} options={[{ value: "tactical", label: "Tactical console" }, { value: "warroom", label: "Naval war-room" }, { value: "minimal", label: "Minimal tactical" }]} onChange={(v) => setTweak("theme", v)} />
        <TweakRadio label="Factie-palet" value={t.palette} options={[{ value: "vivid", label: "Vivid" }, { value: "muted", label: "Muted" }, { value: "deep", label: "Diep" }]} onChange={(v) => setTweak("palette", v)} />
        <TweakToggle label="Accent volgt speler" value={t.accentFollowsPlayer} onChange={(v) => setTweak("accentFollowsPlayer", v)} />
        <TweakSection label="Bord" />
        <TweakToggle label="Bereik & arcs tonen" value={t.showArcs} onChange={(v) => setTweak("showArcs", v)} />
        <TweakRadio label="Token-stijl" value={t.tokenStyle} options={[{ value: "silhouette", label: "Silhouet" }, { value: "chit", label: "Chit" }]} onChange={(v) => setTweak("tokenStyle", v)} />
      </TweaksPanel>
    </div>
  );
}

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