function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [focused, setFocused] = useState(() => planetFromHash());
  const [scale, setScale] = useState(1);
  const [pan, setPan] = useState({ x: 0, y: 0 });
  const [tour, setTour] = useState(false);
  const [soundOn, setSoundOn] = useSoundToggle();
  const [astronaut, setAstronaut] = useState(null);
  const [homeAstronaut, setHomeAstronaut] = useState(() => ({
    x: Math.min(240, Math.max(104, window.innerWidth * 0.16)),
    y: Math.min(
      window.innerHeight - 116,
      Math.max(160, window.innerHeight * 0.68),
    ),
  }));
  const [mazeOn, setMazeOn] = useState(false);
  const [mazeRun, setMazeRun] = useState(0);
  const [moonRoverOn, setMoonRoverOn] = useState(false);
  const [moonRoverRun, setMoonRoverRun] = useState(0);
  const [pilotOn, setPilotOn] = useState(false);
  const [pilotRun, setPilotRun] = useState(0);
  const [treasureOn, setTreasureOn] = useState(false);
  const [rocketPackOn, setRocketPackOn] = useState(false);
  const [planetSortOn, setPlanetSortOn] = useState(false);
  const [engineMatchOn, setEngineMatchOn] = useState(false);
  const [rocketCauseOn, setRocketCauseOn] = useState(false);
  const [ufoBuilderOn, setUfoBuilderOn] = useState(false);
  const [mashupMakerOn, setMashupMakerOn] = useState(false);
  const [connectionQuestOn, setConnectionQuestOn] = useState(false);
  const [asteroidDodgeOn, setAsteroidDodgeOn] = useState(false);
  const [missionControlOn, setMissionControlOn] = useState(false);
  const [gravityJumpOn, setGravityJumpOn] = useState(false);
  const [alienLanguageOn, setAlienLanguageOn] = useState(false);
  const [moonAliensOn, setMoonAliensOn] = useState(false);
  const [moonGardenOn, setMoonGardenOn] = useState(false);
  const [meteorShieldOn, setMeteorShieldOn] = useState(false);
  const [petFeederOn, setPetFeederOn] = useState(false);
  const [storybookOn, setStorybookOn] = useState(null);
  const [storyShelfOn, setStoryShelfOn] = useState(false);
  const [bhOn, setBhOn] = useState(false);
  const [bhRun, setBhRun] = useState(0);
  const [helmetSticker, setHelmetSticker] = useHelmetSticker();
  const [stickerPlacement, setStickerPlacement] = useStickerPlacement();
  const [helmetSide, setHelmetSide] = useHelmetSide(
    "planets_helmet_view_side",
    "front",
  );
  const [stickerSide, setStickerSide] = useHelmetSide(
    "planets_sticker_side",
    "front",
  );
  const [stickerPos, setStickerPos] = useStickerPosition();
  const [suitColor, setSuitColor] = useSuitColor();
  const [helmetOpen, setHelmetOpen] = useState(false);
  const [helmetPage, setHelmetPage] = useState(false);
  const constellations = useConstellations();
  const [drawingConstellation, setDrawingConstellation] = useState(false);
  const kidPlanets = React.useMemo(() => PLANETS.filter((p) => !p.isStar), []);
  const [kidMode, setKidMode] = useState(
    () => readStorageFlag("planets_kid_mode", false),
  );
  const [kidMissionIndex, setKidMissionIndex] = useState(0);
  const [kidStickers, addKidSticker] = useKidStickers();
  const [visitedPlanets, recordPlanetVisit, resetPlanetVisits] =
    useVisitedPlanets();
  const [favoritePlanetId, setFavoritePlanetId] = useFavoritePlanet();
  const [kidReward, setKidReward] = useState(null);
  const [kidStarScore, setKidStarScore] = useState(0);
  const [collectedTreasures, setCollectedTreasures] = useState([]);
  const [treasurePickup, setTreasurePickup] = useState(null);
  const [rocketParts, setRocketParts] = useState([]);
  const [rocketColor, setRocketColor] = useState(() =>
    readStorageEnum(
      "planets_rocket_color",
      ROCKET_COLORS.map((color) => color.id),
      "red",
    ),
  );
  const [rocketMode, setRocketMode] = useState(() =>
    readStorageEnum("planets_rocket_mode", ["engineering", "simple"], "simple"),
  );
  const [rocketMessage, setRocketMessage] = useState(null);
  const [rocketLaunched, setRocketLaunched] = useState(false);
  const [solarRocketOrbiting, setSolarRocketOrbiting] = useState(
    () => readStorageFlag("planets_solar_rocket_orbiting", false),
  );
  const kidRewardTimer = useRef(null);
  const rocketOrbitTimer = useRef(null);
  const planetDanceTimer = useRef(null);
  const warpTimer = useRef(null);
  const [astronautWaveKey, setAstronautWaveKey] = useState(0);
  const [comet, setComet] = useState(null);
  const [menuOpen, setMenuOpen] = useState(false);
  const [planetDance, setPlanetDance] = useState(false);
  const [orbitSpeed, setOrbitSpeed] = useState(1);
  const [quietMode, setQuietMode] = useState(
    () => readStorageFlag("planets_quiet_mode", false),
  );
  const [astronautName, setAstronautName] = useState(() =>
    readStorageValue("planets_astronaut_name", ""),
  );
  const kidTarget =
    kidPlanets[kidMissionIndex % kidPlanets.length] || PLANETS[0];

  // Round-3 (code review F4.1): poll window.__spaceErrors so the toast
  // surfaces when something fails. Before this, the array was collected
  // by index.html but never read — a kid hit "play game" on a level with
  // a broken texture and saw a blank screen with no recovery path.
  const [hasError, setHasError] = useState(false);
  const [errorMessage, setErrorMessage] = useState("");
  useEffect(() => {
    if (!window.__spaceErrors) return undefined;
    const tick = () => {
      const n = window.__spaceErrors.length;
      const next = n > 0;
      setHasError((prev) => (prev !== next ? next : prev));
      if (next) {
        const latest = window.__spaceErrors[n - 1];
        const msg = String(latest?.message || "Something glitched.").slice(
          0,
          80,
        );
        setErrorMessage((prev) => (prev === msg ? prev : msg));
      }
    };
    tick();
    const id = window.setInterval(tick, 2500);
    return () => window.clearInterval(id);
  }, []);
  const dismissError = () => {
    try {
      if (window.__spaceErrors) window.__spaceErrors.length = 0;
    } catch {}
    setHasError(false);
    setErrorMessage("");
  };

  // Read the "blocked" status through a ref so the keydown listener stays
  // attached for the App's lifetime instead of rebinding on every game-state
  // toggle (which used to drop fast Arrow presses in the rebind micro-tick).
  const homeKeyBlockedRef = useRef(false);
  homeKeyBlockedRef.current = Boolean(
    focused ||
    mazeOn ||
    moonRoverOn ||
    pilotOn ||
    treasureOn ||
    rocketPackOn ||
    planetSortOn ||
    engineMatchOn ||
    rocketCauseOn ||
    ufoBuilderOn ||
    mashupMakerOn ||
    connectionQuestOn ||
    asteroidDodgeOn ||
    missionControlOn ||
    gravityJumpOn ||
    alienLanguageOn ||
    moonAliensOn ||
    moonGardenOn ||
    meteorShieldOn ||
    petFeederOn ||
    storybookOn ||
    storyShelfOn ||
    bhOn ||
    helmetPage ||
    menuOpen ||
    drawingConstellation,
  );
  useEffect(() => {
    const onKey = (event) => {
      if (
        !["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)
      )
        return;
      if (homeKeyBlockedRef.current) return;
      event.preventDefault();
      setHomeAstronaut((current) => {
        const step = event.repeat ? 18 : 26;
        const next = { ...current };
        if (event.key === "ArrowLeft") next.x -= step;
        if (event.key === "ArrowRight") next.x += step;
        if (event.key === "ArrowUp") next.y -= step;
        if (event.key === "ArrowDown") next.y += step;
        return {
          x: Math.max(58, Math.min(window.innerWidth - 58, next.x)),
          y: Math.max(88, Math.min(window.innerHeight - 58, next.y)),
        };
      });
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const updateKidMode = (next) => {
    setKidMode(next);
    writeStorageFlag("planets_kid_mode", next);
    if (next) {
      setTour(false);
      setDrawingConstellation(false);
      setHelmetOpen(false);
    }
  };

  const normalizeKidReward = (emoji, text, asset) => ({
    emoji: String(emoji || "★").trim().slice(0, 4) || "★",
    text: String(text || "Nice work!").replace(/\s+/g, " ").trim().slice(0, 90),
    asset: asset || null,
    key: Date.now(),
  });

  const hideKidReward = () => {
    window.clearTimeout(kidRewardTimer.current);
    kidRewardTimer.current = null;
    setKidReward(null);
  };

  const showKidReward = (emoji, text, asset) => {
    setKidReward(normalizeKidReward(emoji, text, asset));
    window.clearTimeout(kidRewardTimer.current);
    kidRewardTimer.current = window.setTimeout(hideKidReward, 1800);
  };
  const generatedAsset = (name, fallback = "") =>
    window.SpaceExplorerFoundation?.getGeneratedAsset?.(name, fallback) ||
    fallback;
  const reportAppError = (message, detail) => {
    window.SpaceExplorerFoundation?.reportSpaceError?.(message, detail);
    setHasError(true);
    setErrorMessage(String(message || "Something glitched.").slice(0, 80));
  };

  const startPlanetDance = () => {
    window.clearTimeout(planetDanceTimer.current);
    setPlanetDance(true);
    playKidSound("chime");
    showKidReward(
      "♪",
      "Planet dance!",
      generatedAsset("sparkleStar", GENERATED_ASSETS.sparkleStar),
    );
    planetDanceTimer.current = window.setTimeout(
      () => setPlanetDance(false),
      9000,
    );
  };

  const showSurpriseFact = () => {
    const world = PLANETS[Math.floor(Math.random() * PLANETS.length)];
    if (!world) return;
    const shortFact = world.blurb.split(".")[0] + ".";
    showKidReward(planetAbbrev(world), shortFact, null);
    playKidSound("chime");
  };

  const startWarpSpeed = () => {
    window.clearTimeout(warpTimer.current);
    setOrbitSpeed(8);
    playKidSound("whoosh");
    showKidReward(
      "8x",
      "Warp speed!",
      generatedAsset("rocket", GENERATED_ASSETS.rocket),
    );
    warpTimer.current = window.setTimeout(() => setOrbitSpeed(1), 10000);
  };

  useEffect(() => {
    return () => window.clearTimeout(planetDanceTimer.current);
  }, []);

  useEffect(() => {
    return () => window.clearTimeout(warpTimer.current);
  }, []);

  useEffect(() => {
    writeStorageValue("planets_rocket_color", rocketColor);
  }, [rocketColor]);

  useEffect(() => {
    writeStorageValue("planets_rocket_mode", rocketMode);
  }, [rocketMode]);

  useEffect(() => {
    writeStorageFlag("planets_quiet_mode", quietMode);
    if (quietMode) stopNarration();
  }, [quietMode]);

  useEffect(() => {
    if (astronautName) {
      writeStorageValue("planets_astronaut_name", astronautName);
    } else {
      removeStorageValue("planets_astronaut_name");
    }
  }, [astronautName]);

  const sanitizeAstronautName = (value) =>
    String(value || "")
      .replace(/[\u0000-\u001f\u007f]/g, "")
      .replace(/\s+/g, " ")
      .trim()
      .slice(0, 16);

  const askAstronautName = () => {
    const next = window.prompt("Astronaut name", astronautName || "Adam");
    if (next === null) return;
    setAstronautName(sanitizeAstronautName(next));
    playKidSound("boop");
  };

  const resetInteractiveSurfaceFlags = () => {
    setRocketPackOn(false);
    setPlanetSortOn(false);
    setEngineMatchOn(false);
    setRocketCauseOn(false);
    setUfoBuilderOn(false);
    setMashupMakerOn(false);
    setConnectionQuestOn(false);
    setAsteroidDodgeOn(false);
    setMissionControlOn(false);
    setGravityJumpOn(false);
    setAlienLanguageOn(false);
    setMoonAliensOn(false);
    setMoonGardenOn(false);
    setMeteorShieldOn(false);
    setPetFeederOn(false);
    setStorybookOn(null);
    setStoryShelfOn(false);
    setBhOn(false);
    setMazeOn(false);
    setMoonRoverOn(false);
    setPilotOn(false);
    setTreasureOn(false);
  };

  const closeInteractiveSurfaces = () => {
    stopNarration();
    setMenuOpen(false);
    setComet(null);
    setTreasurePickup(null);
    setHelmetOpen(false);
    setDrawingConstellation(false);
    setHelmetPage(false);
    resetInteractiveSurfaceFlags();
    setFocused(null);
    setTour(false);
  };

  const prepareActivityLaunch = () => {
    stopNarration();
    setMenuOpen(false);
    setTour(false);
    setFocused(null);
    setHelmetPage(false);
    setHelmetOpen(false);
    setDrawingConstellation(false);
    resetInteractiveSurfaceFlags();
  };

  const playActivityNarration = (narration) => {
    if (!narration) return;
    const clip = typeof narration === "function" ? narration() : narration;
    if (typeof clip === "string" && clip.trim()) playNarration(clip);
  };

  const openActivity = ({ activate, narration, sound = "chime" } = {}) => {
    if (typeof activate !== "function") {
      reportAppError("Activity launch missing an activate handler.", {
        source: "openActivity",
      });
      playKidSound("pop");
      return;
    }
    prepareActivityLaunch();
    try {
      activate();
    } catch (error) {
      reportAppError("Activity launch failed.", {
        source: "openActivity",
        error: error && (error.stack || error.message || String(error)),
      });
      resetInteractiveSurfaceFlags();
      return;
    }
    if (sound) playKidSound(sound);
    playActivityNarration(narration);
  };

  const openRunActivity = (setRun, setOpen, options = {}) =>
    openActivity({
      ...options,
      activate: () => {
        setRun((n) => n + 1);
        setOpen(true);
      },
    });

  const openPanelActivity = (setOpen, options = {}) =>
    openActivity({
      ...options,
      activate: () => setOpen(true),
    });

  const runMenuCommand = (action, sound = null) => {
    if (typeof action !== "function") {
      reportAppError("Menu command missing an action handler.", {
        source: "runMenuCommand",
      });
      playKidSound("pop");
      return;
    }
    setMenuOpen(false);
    try {
      action();
    } catch (error) {
      reportAppError("Menu command failed.", {
        source: "runMenuCommand",
        error: error && (error.stack || error.message || String(error)),
      });
      playKidSound("pop");
      return;
    }
    if (sound) playKidSound(sound);
  };

  const openStoryBook = (bookId) => {
    const resolveBook = window.SpaceExplorerStorybooks?.resolveStoryBookId;
    const nextBook =
      typeof resolveBook === "function"
        ? resolveBook(bookId)
        : bookId || "starHome";
    setStoryShelfOn(false);
    setStorybookOn(nextBook);
    playKidSound("chime");
  };

  const rocketStartNarration = (mode = rocketMode) =>
    mode === "engineering"
      ? "game_rocket_start_engineering.mp3"
      : "game_rocket_start_simple.mp3";

  const completeKidMission = (planet) => {
    if (!kidMode || planet.id !== kidTarget.id) return false;
    const emoji = addKidSticker(planet);
    showKidReward(
      emoji,
      `${planet.name} sticker! ${KID_FACT_REWARDS[planet.id] || ""}`,
    );
    setKidMissionIndex((i) => i + 1);
    playNarration("found_" + planet.id + ".mp3");
    return true;
  };

  const finishTreasurePickup = (treasure) => {
    if (collectedTreasures.includes(treasure.id)) {
      setTreasurePickup(null);
      return;
    }
    const nextCollected = [...collectedTreasures, treasure.id];
    setCollectedTreasures(nextCollected);
    setKidStarScore((n) => n + 1);
    if (nextCollected.length < KID_TREASURES.length) {
      showKidReward(
        "⭐",
        `${treasure.label}! ${treasure.fact}`,
        treasure.asset,
      );
    } else {
      hideKidReward();
    }
    playKidSound("chime");
    playNarration(
      nextCollected.length === KID_TREASURES.length
        ? "game_treasure_done.mp3"
        : "game_treasure_" + treasure.id + ".mp3",
    );
    setTreasurePickup(null);
  };

  const tapKidStar = (treasure, coords) => {
    if (treasurePickup || collectedTreasures.includes(treasure.id)) return;
    setTreasurePickup({
      id: `${treasure.id}-${Date.now()}`,
      treasure,
      from: {
        x: 48,
        y: Math.min(
          window.innerHeight - 132,
          Math.max(180, window.innerHeight * 0.72),
        ),
      },
      to: coords,
    });
    playKidSound("whoosh");
  };

  const resetTreasureHunt = () => {
    setCollectedTreasures([]);
    setKidStarScore(0);
    setComet(null);
    setTreasurePickup(null);
  };

  const addRocketPart = (part) => {
    setRocketParts((current) => {
      if (current.includes(part.id)) return current;
      // Side effects gated on the *real* "first add" so a fast double-tap
      // can't double-fire narration via the stale-closure includes() check.
      setRocketMessage({
        title: `${part.label} snapped on!`,
        fact: part.fact,
        key: Date.now(),
      });
      playKidSound("chime");
      const partAudioId = part.id.replace(/([A-Z])/g, "_$1").toLowerCase();
      playNarration("game_rocket_" + partAudioId + ".mp3");
      return [...current, part.id];
    });
  };

  const resetRocketPack = () => {
    setRocketParts([]);
    setRocketMessage(null);
    setRocketLaunched(false);
    playNarration("game_rocket_reset.mp3");
  };

  const resetSolarRocket = () => {
    window.clearTimeout(rocketOrbitTimer.current);
    rocketOrbitTimer.current = null;
    writeStorageFlag("planets_solar_rocket_orbiting", false);
    setRocketLaunched(false);
    setSolarRocketOrbiting(false);
    resetRocketPack();
    showKidReward("🚀", "Rocket reset!", GENERATED_ASSETS.rocket);
  };

  const launchBuiltRocket = () => {
    setRocketLaunched(true);
    setSolarRocketOrbiting(false);
    writeStorageFlag("planets_solar_rocket_orbiting", false);
    setRocketMessage({
      title: "Blast off!",
      fact: "Rockets push down to go up.",
      key: Date.now(),
    });
    playKidSound("whoosh");
    playNarration("game_rocket_launch.mp3");
    window.clearTimeout(rocketOrbitTimer.current);
    rocketOrbitTimer.current = window.setTimeout(() => {
      writeStorageFlag("planets_solar_rocket_orbiting", true);
      setRocketPackOn(false);
      setSolarRocketOrbiting(true);
      setFocused(null);
      setTour(false);
      setPan({ x: 0, y: 0 });
      setScale(1);
      showKidReward("🚀", "Rocket in orbit!", GENERATED_ASSETS.rocket);
    }, 2300);
  };

  useEffect(() => {
    if (
      !treasureOn ||
      focused ||
      helmetPage ||
      mazeOn ||
      moonRoverOn ||
      pilotOn ||
      bhOn ||
      comet
    )
      return;
    const delay = 5200;
    const t = setTimeout(() => {
      setComet({ id: Date.now() });
    }, delay);
    return () => clearTimeout(t);
  }, [
    treasureOn,
    focused,
    helmetPage,
    mazeOn,
    moonRoverOn,
    pilotOn,
    bhOn,
    comet && comet.id,
  ]);

  useEffect(() => {
    if (!comet) return;
    const t = setTimeout(() => setComet(null), 5200);
    return () => clearTimeout(t);
  }, [comet && comet.id]);

  const exitMaze = (opts) => {
    if (opts && opts.replay) {
      setMazeRun((n) => n + 1);
      return;
    }
    closeInteractiveSurfaces();
  };

  const exitBh = (opts) => {
    if (opts && opts.replay) {
      setBhRun((n) => n + 1);
      return;
    }
    closeInteractiveSurfaces();
  };

  // Click handler shared by overview and tweaks-panel jump buttons.
  // `coords` is optional click point; if omitted the astronaut spawns from
  // off-screen-bottom-center.
  const focusPlanet = (planet, coords) => {
    const wonKidMission = completeKidMission(planet);
    setFocused(planet);
    const c = detailCenterFor();
    const from = coords || {
      x: window.innerWidth / 2,
      y: window.innerHeight + 120,
    };
    setAstronaut({
      phase: "flying",
      from,
      to: { x: c.x, y: c.y },
      center: { x: c.x, y: c.y },
      radius: c.radius,
      planet,
      rewardPause: wonKidMission,
    });
  };
  const handleFlyComplete = () => {
    setAstronaut((a) => (a ? { ...a, phase: "orbiting" } : null));
  };

  // When the narration audio ends, depart.
  useEffect(() => {
    if (!astronaut || astronaut.phase !== "orbiting") return;
    const audio = document.getElementById("narration");
    if (!audio) return;
    const onEnded = () =>
      setAstronaut((a) => (a ? { ...a, phase: "departing" } : null));
    audio.addEventListener("ended", onEnded);
    return () => audio.removeEventListener("ended", onEnded);
  }, [astronaut && astronaut.phase]);

  // After the depart animation, remove the astronaut.
  useEffect(() => {
    if (!astronaut || astronaut.phase !== "departing") return;
    const t = setTimeout(() => setAstronaut(null), 850);
    return () => clearTimeout(t);
  }, [astronaut && astronaut.phase]);

  // Closing the detail view (Esc, close button, tour stop) cancels the
  // astronaut too.
  useEffect(() => {
    if (!focused && astronaut) setAstronaut(null);
  }, [focused]);

  // When focused changes (prev/next, tour), restart the orbit on the new planet.
  useEffect(() => {
    if (!focused) return;
    recordPlanetVisit(focused);
    const c = detailCenterFor();
    setAstronaut({
      phase: "orbiting",
      from: { x: c.x, y: c.y },
      to: { x: c.x, y: c.y },
      center: { x: c.x, y: c.y },
      radius: c.radius,
      planet: focused,
    });
  }, [focused && focused.id]);

  useEffect(() => {
    setPlanetHash(focused);
  }, [focused && focused.id]);

  useEffect(() => {
    const onHashChange = () => {
      const nextPlanet = planetFromHash();
      closeInteractiveSurfaces();
      setFocused(nextPlanet);
    };
    window.addEventListener("hashchange", onHashChange);
    return () => window.removeEventListener("hashchange", onHashChange);
  }, []);

  useEffect(() => {
    if (!tour) return;
    let i = 0;
    setFocused(PLANETS[0]);
    // 30s/planet — matches the longer Grok narration clips (~25-30s).
    // Round-3 (code-review F6.3): pause the interval when the tab is
    // hidden. Without this, iOS/Safari fires every queued tick on
    // resume — the tour would jump several planets at once.
    let id = null;
    const startInterval = () => {
      if (id !== null) return;
      id = setInterval(() => {
        i = (i + 1) % PLANETS.length;
        setFocused(PLANETS[i]);
      }, 30000);
    };
    const stopInterval = () => {
      if (id === null) return;
      clearInterval(id);
      id = null;
    };
    const onVisibility = () => {
      if (document.visibilityState === "hidden") stopInterval();
      else startInterval();
    };
    if (document.visibilityState !== "hidden") startInterval();
    document.addEventListener("visibilitychange", onVisibility);
    return () => {
      stopInterval();
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, [tour]);

  // Round-3: split the previous combined Escape + arrow handler into:
  //   (1) a base-level Escape handler registered via useEscapeHandler so
  //       per-level components can stack their own and win (LIFO);
  //   (2) a separate arrow-key handler that drives planet focus and is
  //       only active when a planet is focused.
  useEscapeHandler(closeInteractiveSurfaces, true);
  useEffect(() => {
    if (!focused) return undefined;
    const onArrow = (e) => {
      const i = PLANETS.findIndex((p) => p.id === focused.id);
      if (i < 0) return;
      if (e.key === "ArrowRight") setFocused(PLANETS[(i + 1) % PLANETS.length]);
      else if (e.key === "ArrowLeft")
        setFocused(PLANETS[(i - 1 + PLANETS.length) % PLANETS.length]);
    };
    window.addEventListener("keydown", onArrow);
    return () => window.removeEventListener("keydown", onArrow);
  }, [focused]);

  useEffect(() => {
    if (!menuOpen) return;
    const closeMenu = (event) => {
      if (event.target.closest(".actions")) return;
      setMenuOpen(false);
    };
    window.addEventListener("pointerdown", closeMenu);
    return () => window.removeEventListener("pointerdown", closeMenu);
  }, [menuOpen]);

  if (mazeOn) {
    return (
      <div className={`app labels-on`}>
        <Starfield count={tweaks.starDensity} />
        <MazeLevel key={mazeRun} onExit={exitMaze} />
      </div>
    );
  }

  if (moonRoverOn) {
    return (
      <div className={`app labels-on`}>
        <MoonRoverLevel key={moonRoverRun} onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (pilotOn) {
    return (
      <div className="app labels-on">
        <PilotControlLevel key={pilotRun} onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (treasureOn) {
    return (
      <div className="app labels-on">
        <TreasureHuntLevel
          collected={collectedTreasures}
          score={kidStarScore}
          comet={comet}
          pickupFlight={treasurePickup}
          onTreasure={tapKidStar}
          onPickupComplete={finishTreasurePickup}
          onComet={() => {
            setComet(null);
            showKidReward(
              "☄",
              "Comet catch!",
              generatedAsset("comet", GENERATED_ASSETS.comet),
            );
            playKidSound("whoosh");
          }}
          onReset={resetTreasureHunt}
          onExit={closeInteractiveSurfaces}
        />
        <KidRewardBurst reward={kidReward} />
      </div>
    );
  }

  if (planetSortOn) {
    return (
      <div className="app labels-on">
        <PlanetSortLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (engineMatchOn) {
    return (
      <div className="app labels-on">
        <EngineMatchLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (rocketCauseOn) {
    return (
      <div className="app labels-on">
        <RocketCauseLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (ufoBuilderOn) {
    return (
      <div className="app labels-on">
        <UfoBuilderLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (mashupMakerOn) {
    return (
      <div className="app labels-on">
        <MashupMakerLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (connectionQuestOn) {
    return (
      <div className="app labels-on">
        <ConnectionQuestLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (asteroidDodgeOn) {
    return (
      <div className="app labels-on">
        <AsteroidDodgeLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (missionControlOn) {
    return (
      <div className="app labels-on">
        <MissionControlLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (gravityJumpOn) {
    return (
      <div className="app labels-on">
        <GravityJumpLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (alienLanguageOn) {
    return (
      <div className="app labels-on">
        <AlienLanguageLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (moonAliensOn) {
    return (
      <div className="app labels-on">
        <MoonAliensPhaserLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (moonGardenOn) {
    return (
      <div className="app labels-on">
        <MoonGardenRescuePhaserLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (meteorShieldOn) {
    return (
      <div className="app labels-on">
        <MeteorShieldPhaserLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (petFeederOn) {
    return (
      <div className="app labels-on">
        <PlanetPetFeederLevel onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (storybookOn) {
    return (
      <div className="app labels-on">
        <StoryBookPage book={storybookOn} onExit={closeInteractiveSurfaces} />
      </div>
    );
  }

  if (storyShelfOn) {
    return (
      <div className="app labels-on">
        <StoryShelfPage
          onRead={openStoryBook}
          onExit={closeInteractiveSurfaces}
        />
      </div>
    );
  }

  if (bhOn) {
    return (
      <div className={`app labels-on`}>
        <BlackHoleLevel key={bhRun} onExit={exitBh} />
      </div>
    );
  }

  if (rocketPackOn) {
    return (
      <div className="app labels-on">
        <RocketPackLevel
          builtParts={rocketParts}
          color={rocketColor}
          message={rocketMessage}
          launched={rocketLaunched}
          mode={rocketMode}
          onMode={(nextMode) => {
            setRocketMode(nextMode);
            setRocketParts((current) =>
              nextMode === "simple"
                ? current.filter((id) => SIMPLE_ROCKET_PART_IDS.includes(id))
                : current,
            );
            setRocketMessage({
              title:
                nextMode === "engineering"
                  ? "Engineering mode!"
                  : "Simple mode!",
              fact:
                nextMode === "engineering"
                  ? "Now add fuel, pipes, and the igniter."
                  : "Core rocket pieces only.",
              key: Date.now(),
            });
            playNarration(rocketStartNarration(nextMode));
          }}
          onPart={addRocketPart}
          onColor={setRocketColor}
          onLaunch={launchBuiltRocket}
          onReset={resetRocketPack}
          onExit={closeInteractiveSurfaces}
        />
      </div>
    );
  }

  if (helmetPage) {
    return (
      <div className={`app labels-on`}>
        <Starfield count={tweaks.starDensity} />
        <HelmetLab
          helmetSticker={helmetSticker}
          onHelmetChange={setHelmetSticker}
          stickerPlacement={stickerPlacement}
          onPlacementChange={setStickerPlacement}
          helmetSide={helmetSide}
          onHelmetSideChange={setHelmetSide}
          stickerSide={stickerSide}
          onStickerSideChange={setStickerSide}
          stickerPos={stickerPos}
          onStickerPosChange={setStickerPos}
          suitColor={suitColor}
          onSuitChange={setSuitColor}
          onClose={() => setHelmetPage(false)}
        />
      </div>
    );
  }

  return (
    <div
      className={`app ${tweaks.showLabels ? "labels-on" : "labels-off"} ${quietMode ? "quiet-mode" : ""}`}
    >
      <Starfield count={tweaks.starDensity} />

      {!focused && (
        <SolarOverview
          onSelect={focusPlanet}
          paused={!tweaks.autoOrbit}
          tilt={tweaks.tilt}
          orbitOpacity={tweaks.orbitOpacity}
          scale={scale}
          panX={pan.x}
          panY={pan.y}
          onPan={(x, y) => setPan({ x, y })}
          onZoom={(s) => setScale(Math.max(0.4, Math.min(3, s)))}
          rocketOrbiting={solarRocketOrbiting}
          rocketColor={rocketColor}
          onResetRocket={resetSolarRocket}
          planetDance={planetDance}
          orbitSpeed={quietMode && orbitSpeed === 1 ? 0.35 : orbitSpeed}
        />
      )}

      {!focused && (
        <ConstellationOverlay constellations={constellations.list} />
      )}

      {!focused && (
        <ConstellationDrawLayer
          active={drawingConstellation}
          onFinish={(c) => {
            constellations.add({
              ...c,
              name: `Star Trail ${constellations.list.length + 1}`,
            });
            setDrawingConstellation(false);
          }}
          onCancel={() => setDrawingConstellation(false)}
        />
      )}

      {focused && (
        <PlanetDetail
          planet={focused}
          favoritePlanetId={favoritePlanetId}
          onClose={() => setFocused(null)}
          onPrev={() => {
            const i = PLANETS.findIndex((p) => p.id === focused.id);
            setFocused(PLANETS[(i - 1 + PLANETS.length) % PLANETS.length]);
          }}
          onNext={() => {
            const i = PLANETS.findIndex((p) => p.id === focused.id);
            setFocused(PLANETS[(i + 1) % PLANETS.length]);
          }}
          onAstronautWave={() => setAstronautWaveKey((n) => n + 1)}
          onFavoriteToggle={setFavoritePlanetId}
        />
      )}

      {!focused && (
        <header className="header">
          <div className="title-block">
            <div className="title">Planets</div>
            <div className="subtitle">
              A field guide to the solar system · tap any world to zoom in
            </div>
            <FieldJournalStrip
              visited={visitedPlanets}
              favoritePlanetId={favoritePlanetId}
              onSelect={focusPlanet}
              onReset={resetPlanetVisits}
            />
          </div>
          <div className="actions">
            <button
              className={`btn-tour ${kidMode ? "on" : ""}`}
              onClick={() => updateKidMode(!kidMode)}
            >
              🌟 Little Explorer
            </button>
            <button
              className={`btn-menu ${menuOpen ? "on" : ""}`}
              onClick={() => setMenuOpen((v) => !v)}
              aria-expanded={menuOpen ? "true" : "false"}
              aria-haspopup="true"
            >
              ☰ Menu
            </button>
            {menuOpen ? (
              <div className="action-menu">
                <div className="menu-map-heading">
                  <span>Mission Map</span>
                  <strong>Choose a game</strong>
                </div>
                <div className="game-map-window" aria-label="Game mission map">
                <button
                  className="menu-item game-map-tile"
                  onClick={() =>
                    openRunActivity(setMazeRun, setMazeOn, {
                      narration: "game_maze_start.mp3",
                      sound: null,
                    })
                  }
                >
                  <span>🛰</span>
                  <strong>Play Maze</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() =>
                    openRunActivity(setMoonRoverRun, setMoonRoverOn, {
                      narration: "game_rover_start.mp3",
                    })
                  }
                >
                  <span>🌕</span>
                  <strong>Moon Rover</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() =>
                    openRunActivity(setPilotRun, setPilotOn, {
                      narration: "game_pilot_start.mp3",
                    })
                  }
                >
                  <span>🕹</span>
                  <strong>Pilot School</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setMissionControlOn)}
                >
                  <span>MC</span>
                  <strong>Mission Control</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => {
                    openActivity({
                      activate: () => {
                        resetTreasureHunt();
                        setTreasureOn(true);
                      },
                      narration: "game_treasure_start.mp3",
                      sound: null,
                    });
                  }}
                >
                  <span>⭐</span>
                  <strong>Treasure Hunt</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setPlanetSortOn)}
                >
                  <span>🪐</span>
                  <strong>Planet Sort</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => {
                    openActivity({
                      activate: () => {
                        setSolarRocketOrbiting(false);
                        writeStorageFlag("planets_solar_rocket_orbiting", false);
                        setRocketPackOn(true);
                      },
                      narration: rocketStartNarration(),
                    });
                  }}
                >
                  <span>🚀</span>
                  <strong>Build Rocket</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setEngineMatchOn)}
                >
                  <span>🔥</span>
                  <strong>Engine Match</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setRocketCauseOn)}
                >
                  <span>↕</span>
                  <strong>Rocket Lab</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setUfoBuilderOn)}
                >
                  <span>🛸</span>
                  <strong>UFO Builder</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setMashupMakerOn)}
                >
                  <span>＋</span>
                  <strong>Mashup Maker</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setConnectionQuestOn)}
                >
                  <span>↔</span>
                  <strong>Connection Quest</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setAsteroidDodgeOn)}
                >
                  <span>☄</span>
                  <strong>Asteroid Dodge</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setGravityJumpOn)}
                >
                  <span>G</span>
                  <strong>Gravity Jump</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setAlienLanguageOn)}
                >
                  <span>AL</span>
                  <strong>Alien Language</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setMoonAliensOn)}
                >
                  <span>MV</span>
                  <strong>Moon Aliens</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setMoonGardenOn)}
                >
                  <span>MG</span>
                  <strong>Moon Garden</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setMeteorShieldOn)}
                >
                  <span>MS</span>
                  <strong>Meteor Shield</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setPetFeederOn)}
                >
                  <span>🍴</span>
                  <strong>Planet Pet-Feeder</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() => openPanelActivity(setStoryShelfOn)}
                >
                  <span>📚</span>
                  <strong>Story Books</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() =>
                    openRunActivity(setBhRun, setBhOn, {
                      narration: "game_blackhole_start.mp3",
                      sound: null,
                    })
                  }
                >
                  <span>🕳️</span>
                  <strong>Black Hole</strong>
                </button>
                <button
                  className="menu-item game-map-tile"
                  onClick={() =>
                    openPanelActivity(setHelmetPage, {
                      narration: "game_helmet_start.mp3",
                    })
                  }
                >
                  <span>🪖</span>
                  <strong>Helmet Lab</strong>
                </button>
                </div>
                <details className="menu-more">
                  <summary>More tools</summary>
                  <button
                    className={`menu-item ${planetDance ? "on" : ""}`}
                    onClick={() => runMenuCommand(startPlanetDance)}
                  >
                    <span>♪</span>
                    <strong>Planet Dance</strong>
                  </button>
                  <button
                    className="menu-item"
                    onClick={() => runMenuCommand(showSurpriseFact)}
                  >
                    <span>?</span>
                    <strong>Surprise Fact</strong>
                  </button>
                  <button
                    className={`menu-item ${orbitSpeed > 1 ? "on" : ""}`}
                    onClick={() => runMenuCommand(startWarpSpeed)}
                  >
                    <span>8x</span>
                    <strong>Warp Speed</strong>
                  </button>
                  <button
                    className={`menu-item ${quietMode ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setQuietMode((current) => !current);
                      }, "boop")
                    }
                  >
                    <span>☾</span>
                    <strong>Quiet Mode</strong>
                  </button>
                  <button
                    className={`menu-item ${tweaks.showLabels ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setTweak("showLabels", !tweaks.showLabels);
                      }, "boop")
                    }
                  >
                    <span>{tweaks.showLabels ? "Aa" : "A"}</span>
                    <strong>
                      {tweaks.showLabels ? "Hide Labels" : "Show Labels"}
                    </strong>
                  </button>
                  <button
                    className={`menu-item ${astronautName ? "on" : ""}`}
                    onClick={() => runMenuCommand(askAstronautName)}
                  >
                    <span>ID</span>
                    <strong>
                      {astronautName ? "Rename Astronaut" : "Name Astronaut"}
                    </strong>
                  </button>
                  <button
                    className="menu-item"
                    onClick={() =>
                      runMenuCommand(() => {
                        showKidReward(
                          "🔔",
                          "Silly sound!",
                          GENERATED_ASSETS.soundBell,
                        );
                      }, "random")
                    }
                  >
                    <span>🔔</span>
                    <strong>Silly Sounds</strong>
                  </button>
                  <button
                    className={`menu-item ${tour ? "on" : ""}`}
                    onClick={() => runMenuCommand(() => setTour((t) => !t))}
                  >
                    <span>{tour ? "■" : "▶"}</span>
                    <strong>{tour ? "End tour" : "Take a tour"}</strong>
                  </button>
                  <button
                    className={`menu-item ${helmetOpen ? "on" : ""}`}
                    onClick={() => runMenuCommand(() => setHelmetOpen((v) => !v))}
                  >
                    <span>👨‍🚀</span>
                    <strong>Quick helmet picker</strong>
                  </button>
                  <button
                    className={`menu-item ${drawingConstellation ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setDrawingConstellation((v) => !v);
                        setHelmetOpen(false);
                      })
                    }
                  >
                    <span>✦</span>
                    <strong>
                      {drawingConstellation
                        ? "Cancel constellation"
                        : "Draw constellation"}
                    </strong>
                  </button>
                  <button
                    className={`menu-item ${soundOn ? "on" : ""}`}
                    onClick={() => runMenuCommand(() => setSoundOn(!soundOn))}
                  >
                    <span>{soundOn ? "🔊" : "🔇"}</span>
                    <strong>{soundOn ? "Sound on" : "Sound off"}</strong>
                  </button>
                  <button
                    className="menu-item"
                    onClick={() =>
                      runMenuCommand(() => {
                        setScale(1);
                        setPan({ x: 0, y: 0 });
                        window.clearTimeout(warpTimer.current);
                        window.clearTimeout(planetDanceTimer.current);
                        setOrbitSpeed(1);
                        setPlanetDance(false);
                        setQuietMode(false);
                      })
                    }
                  >
                    <span>◎</span>
                    <strong>Reset view</strong>
                  </button>
                </details>
              </div>
            ) : null}
          </div>
        </header>
      )}

      {!focused && !drawingConstellation && !menuOpen && (
        <KidMissionPanel
          active={kidMode}
          target={kidTarget}
          stickers={kidStickers}
          starScore={kidStarScore}
          onToggle={() => updateKidMode(!kidMode)}
          onSkip={() => setKidMissionIndex((i) => i + 1)}
        />
      )}

      {!focused && !kidMode && !solarRocketOrbiting && (
        <div className="hint">
          {(() => {
            const touch =
              typeof window !== "undefined" &&
              ("ontouchstart" in window || navigator.maxTouchPoints > 0);
            return touch ? (
              <>
                <span className="hint-key">drag</span> to pan
                <span className="hint-sep">·</span>
                <span className="hint-key">pinch</span> to zoom
                <span className="hint-sep">·</span>
                <span className="hint-key">tap</span> a planet to explore
              </>
            ) : (
              <>
                <span className="hint-key">drag</span> to pan
                <span className="hint-sep">·</span>
                <span className="hint-key">scroll</span> to zoom
                <span className="hint-sep">·</span>
                <span className="hint-key">click</span> a planet to explore
              </>
            );
          })()}
        </div>
      )}

      <TweaksPanel>
        <TweakSection title="View">
          <TweakSlider
            label="Orbital tilt"
            value={tweaks.tilt}
            min={0}
            max={60}
            step={1}
            onChange={(v) => setTweak("tilt", v)}
            suffix="°"
          />
          <TweakSlider
            label="Orbit lines"
            value={tweaks.orbitOpacity}
            min={0}
            max={1}
            step={0.05}
            onChange={(v) => setTweak("orbitOpacity", v)}
          />
          <TweakSlider
            label="Star density"
            value={tweaks.starDensity}
            min={50}
            max={800}
            step={25}
            onChange={(v) => setTweak("starDensity", v)}
          />
          <TweakToggle
            label="Show planet labels"
            checked={tweaks.showLabels}
            onChange={(v) => setTweak("showLabels", v)}
          />
          <TweakToggle
            label="Animate orbits"
            checked={tweaks.autoOrbit}
            onChange={(v) => setTweak("autoOrbit", v)}
          />
        </TweakSection>
        <TweakSection title="Jump to planet">
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "1fr 1fr 1fr",
              gap: 6,
            }}
          >
            {PLANETS.map((p) => (
              <button
                key={p.id}
                className="tw-jump-btn"
                onClick={(e) => {
                  const r = e.currentTarget.getBoundingClientRect();
                  focusPlanet(p, {
                    x: r.left + r.width / 2,
                    y: r.top + r.height / 2,
                  });
                }}
              >
                {p.name}
              </button>
            ))}
          </div>
        </TweakSection>
      </TweaksPanel>

      {helmetOpen && !focused && (
        <HelmetPicker
          value={helmetSticker}
          onChange={setHelmetSticker}
          onClose={() => setHelmetOpen(false)}
        />
      )}

      {!focused && !drawingConstellation && constellations.list.length > 0 && (
        <button
          className="constellation-clear-btn"
          onClick={() => {
            if (window.confirm("Clear all your constellations?")) {
              constellations.clear();
            }
          }}
          title="Clear constellations"
        >
          Clear constellations ({constellations.list.length})
        </button>
      )}

      <AstronautFlight
        astronaut={astronaut}
        helmetSticker={helmetSticker}
        stickerPlacement={stickerPlacement}
        suitColor={suitColor}
        waveKey={astronautWaveKey}
        astronautName={astronautName}
        onFlyComplete={handleFlyComplete}
      />
      {!focused && !drawingConstellation && !menuOpen ? (
        <DraggableAstronaut
          position={homeAstronaut}
          onPositionChange={setHomeAstronaut}
          onPlanetDrop={focusPlanet}
          helmetSticker={helmetSticker}
          stickerPlacement={stickerPlacement}
          suitColor={suitColor}
          astronautName={astronautName}
        />
      ) : null}
      <KidRewardBurst reward={kidReward} />
      {hasError && (
        <button
          type="button"
          className="space-error-toast"
          onClick={dismissError}
          aria-label={`${errorMessage || "Something went wrong"}, tap to dismiss`}
        >
          Hmm, something glitched. Tap to try again.
          {errorMessage ? <span>{errorMessage}</span> : null}
        </button>
      )}
    </div>
  );
}

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