// app.jsx
// React shell, route-like mode state, planet detail, and game-launch routing.
// Mission Map data and presentational pieces live in mission-map.jsx
// (loaded earlier in index.html). Quiz games live in quiz-*.jsx siblings.

function HubAmbientComet() {
  const [streak, setStreak] = useState(null);
  const showTimerRef = useRef(null);
  const hideTimerRef = useRef(null);
  const scheduleNextRef = useRef(null);

  useEffect(() => {
    const reduceMotion =
      window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches || false;
    if (reduceMotion) return undefined;
    let cancelled = false;
    const nextDelay = (initial = false) => {
      const testDelay = Number(window.__spaceAmbientCometTestDelay);
      if (Number.isFinite(testDelay) && testDelay >= 0) return testDelay;
      return initial
        ? 12000 + Math.random() * 8000
        : 20000 + Math.random() * 20000;
    };
    const schedule = (initial = false) => {
      window.clearTimeout(showTimerRef.current);
      showTimerRef.current = window.setTimeout(() => {
        if (cancelled) return;
        setStreak({
          id: Date.now(),
          top: `${14 + Math.random() * 58}%`,
          scale: (0.82 + Math.random() * 0.3).toFixed(2),
          reverse:
            typeof window.__spaceAmbientCometTestReverse === "boolean"
              ? window.__spaceAmbientCometTestReverse
              : Math.random() > 0.5,
        });
        window.clearTimeout(hideTimerRef.current);
        hideTimerRef.current = window.setTimeout(() => {
          setStreak(null);
          if (!cancelled) schedule(false);
        }, 5600);
      }, nextDelay(initial));
    };
    scheduleNextRef.current = () => schedule(false);
    schedule(true);
    return () => {
      cancelled = true;
      window.clearTimeout(showTimerRef.current);
      window.clearTimeout(hideTimerRef.current);
      scheduleNextRef.current = null;
    };
  }, []);

  const catchComet = () => {
    if (!streak || streak.caught) return;
    window.clearTimeout(hideTimerRef.current);
    setStreak((current) => (current ? { ...current, caught: true } : current));
    playKidSound("chime");
    window.dispatchEvent(
      new CustomEvent("space:zephy-praise", {
        detail: { text: "Wish caught!" },
      }),
    );
    hideTimerRef.current = window.setTimeout(() => {
      setStreak(null);
      scheduleNextRef.current?.();
    }, 950);
  };

  if (!streak) return null;
  return (
    <button
      type="button"
      key={streak.id}
      className={`hub-ambient-comet ${streak.reverse ? "reverse" : ""} ${
        streak.caught ? "caught" : ""
      }`}
      style={{
        "--ambient-comet-top": streak.top,
        "--ambient-comet-scale": streak.scale,
      }}
      onClick={catchComet}
      aria-label="Catch the shooting-star wish"
    >
      <SafeAssetImage
        className="hub-ambient-comet-img"
        src={GENERATED_ASSETS.comet}
        alt=""
        context="HubAmbientComet"
        fallbackText=""
      />
      {streak.caught ? (
        <span className="hub-ambient-comet-caught" role="status">
          Wish caught!
        </span>
      ) : null}
      {streak.caught ? (
        <span className="hub-ambient-comet-sparkles" aria-hidden="true">
          {[0, 1, 2, 3].map((index) => (
            <SafeAssetImage
              key={index}
              className={`hub-ambient-comet-sparkle sparkle-${index + 1}`}
              src={GENERATED_ASSETS.sparkleStar}
              alt=""
              context={`HubAmbientComet:sparkle:${index}`}
              fallbackText=""
            />
          ))}
        </span>
      ) : null}
    </button>
  );
}

function MissionBadgeAlienCameo({ cameo }) {
  if (!cameo) return null;
  return (
    <div
      key={cameo.key}
      className={`mission-badge-alien-cameo helper-${cameo.helperIndex}`}
      role="status"
      aria-live="polite"
    >
      <SafeAssetImage
        className="mission-badge-alien-img"
        src={GENERATED_ASSETS.alien}
        alt=""
        context="MissionBadgeAlienCameo"
        fallbackText=""
      />
      <span>Badge buddy!</span>
    </div>
  );
}

function App() {
  const MISSION_BADGES_STORAGE_KEY = "planets_mission_badges";
  const MISSION_BADGE_LIMIT = 12;
  const initialHashPlanet = planetFromHash();
  const cinematicReels = {
    mars: {
      label: "Mars Story",
      src: "data/cinematic-intro.mp4?v=mars-story-20260512",
      poster: "data/generated-assets/cinematic/mars-story-poster.png",
      aria: "Astronaut Mars story video",
      cue: "Watch the astronaut arrive at Mars.",
    },
    flight: {
      label: "Space Flight",
      src: "data/space-flight-story.mp4?v=space-flight-20260512",
      poster: "data/generated-assets/cinematic/space-flight-poster.png",
      aria: "Astronaut flying through space story video",
      cue: "Fly past rocks, stars, and faraway worlds.",
    },
    badgeParade: {
      label: "Badge Parade",
      src: "data/post-game-videos/mission-badge-parade.webm?v=voiceover-20260522",
      poster:
        "data/generated-assets/post-game-videos/mission-badge-parade-poster.png",
      aria: "Mission badge parade reward video",
      cue: "Watch a tiny space parade for your hard work.",
    },
    quietCruise: {
      label: "Quiet Planet Cruise",
      src: "data/post-game-videos/quiet-planet-cruise.webm?v=voiceover-20260522",
      poster:
        "data/generated-assets/post-game-videos/quiet-planet-cruise-poster.png",
      aria: "Quiet planet cruise reward video",
      cue: "Take a slow, peaceful orbit after the game.",
    },
    astronautLap: {
      label: "Astronaut Victory Lap",
      src: "data/post-game-videos/astronaut-victory-lap-v4.webm?v=separated-padded-characters-20260522",
      poster:
        "data/generated-assets/post-game-videos/astronaut-victory-lap-poster-v4.png",
      aria: "Child astronaut victory lap reward video",
      cue: "Watch your astronaut wave and fly through the glowing gate.",
    },
    alienDance: {
      label: "Alien Dance Party",
      src: "data/post-game-videos/alien-dance-party-v4.webm?v=separated-padded-characters-20260522",
      poster:
        "data/generated-assets/post-game-videos/alien-dance-party-poster-v4.png",
      aria: "Alien and rover dance party reward video",
      cue: "Dance with moon friends after the game.",
    },
  };
  const postGameVideoChoices = [
    {
      reelId: "badgeParade",
      emoji: "🏅",
      label: "Badge Parade",
      cue: "Celebration cruise",
    },
    {
      reelId: "quietCruise",
      emoji: "🪐",
      label: "Quiet Cruise",
      cue: "Slow planet movie",
    },
    {
      reelId: "astronautLap",
      emoji: "👨‍🚀",
      label: "Astronaut Lap",
      cue: "Flying wave",
    },
    {
      reelId: "alienDance",
      emoji: "🛸",
      label: "Alien Dance",
      cue: "Sprite party",
    },
  ];
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  // AGENT_TARGET: texture-quality-preset — device tier drives star density scaling + CSS class
  const quality = useQualityPreset();
  const [focused, setFocused] = useState(() => initialHashPlanet);
  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 [marsRoverOn, setMarsRoverOn] = useState(false);
  const [marsRoverRun, setMarsRoverRun] = useState(0);
  const [marsCityOn, setMarsCityOn] = useState(false);
  const [legoRoverGarageOn, setLegoRoverGarageOn] = useState(false);
  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 [wireFixOn, setWireFixOn] = 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 [controlBoardMemoryOn, setControlBoardMemoryOn] = useState(false);
  const [gravityJumpOn, setGravityJumpOn] = useState(false);
  const [physicsPlayOn, setPhysicsPlayOn] = useState(false);
  const [gravityGoldfishOn, setGravityGoldfishOn] = useState(false);
  const [cometCurlingOn, setCometCurlingOn] = useState(false);
  const [solarSailOn, setSolarSailOn] = useState(false);
  const [alienLanguageOn, setAlienLanguageOn] = useState(false);
  const [babyRocketsOn, setBabyRocketsOn] = useState(false);
  const [moonAliensOn, setMoonAliensOn] = useState(false);
  const [moonGardenOn, setMoonGardenOn] = useState(false);
  const [meteorShieldOn, setMeteorShieldOn] = useState(false);
  const [shootingStarOn, setShootingStarOn] = useState(false);
  const [petFeederOn, setPetFeederOn] = useState(false);
  const [marsSuitcaseOn, setMarsSuitcaseOn] = useState(false);
  const [spaceMemoryOn, setSpaceMemoryOn] = useState(false);
  const [cinematicReel, setCinematicReel] = useState(null);
  const [spaceReadingOn, setSpaceReadingOn] = 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 [finishCelebration, setFinishCelebration] = useState(null);
  const [badgeAlienCameo, setBadgeAlienCameo] = useState(null);
  const [missionBadges, setMissionBadges] = useState(readMissionBadges);
  const [kidStarScore, setKidStarScore] = useState(0);
  const [collectedTreasures, setCollectedTreasures] = useState([]);
  const [treasurePickup, setTreasurePickup] = useState(null);
  const [rocketParts, setRocketParts] = useState([]);
  const [rocketColor, setRocketColor] = useState(() =>
    normalizeRocketColor(readStorageValue("planets_rocket_color", "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 finishCelebrationTimer = useRef(null);
  const badgeAlienTimer = useRef(null);
  const rocketOrbitTimer = useRef(null);
  const planetDanceTimer = useRef(null);
  const warpTimer = useRef(null);
  const sceneTransitionTimer = useRef(null);
  const missionLaunchTimer = useRef(null);
  const [astronautWaveKey, setAstronautWaveKey] = useState(0);
  const [comet, setComet] = useState(null);
  const [menuOpen, setMenuOpen] = useState(false);
  const [missionLaunchPlan, setMissionLaunchPlan] = useState(null);
  const [planetDance, setPlanetDance] = useState(false);
  const [orbitSpeed, setOrbitSpeed] = useState(1);
  const [quietMode, setQuietMode] = useState(() =>
    readStorageFlag("planets_quiet_mode", false),
  );
  // AGENT_TARGET: deuteranopia-mode — color-blind orange-blue palette toggle
  const [deuteranopiaMode, setDeuteranopiaMode] = useState(() =>
    readStorageFlag("planets_deuteranopia_mode", false),
  );
  // AGENT_TARGET: night-sky-view — tonight's planet visibility from backyard
  const [nightSkyOpen, setNightSkyOpen] = useState(false);
  // AGENT_TARGET: exoplanet-system-browser — persist selected system (null = Solar System)
  const [selectedSystem, setSelectedSystem] = useState(
    () =>
      systemIdForPlanetId(initialHashPlanet && initialHashPlanet.id) ||
      readStorageValue("planets_selected_system", null),
  );
  // AGENT_TARGET: exoplanet-system-browser — active planet list switches to exoplanet system
  const activePlanets =
    selectedSystem &&
    typeof EXOPLANET_SYSTEMS !== "undefined" &&
    EXOPLANET_SYSTEMS[selectedSystem]
      ? EXOPLANET_SYSTEMS[selectedSystem].planets
      : PLANETS;
  // AGENT_TARGET: venus-rain — acid rain overlay state
  const [venusRaining, setVenusRaining] = useState(false);
  // AGENT_TARGET: galileo-eyepiece — era discovery slider
  const [galileoOn, setGalileoOn] = useState(false);
  // AGENT_TARGET: distance-budget-tour — open/close distance budget overlay
  const [distanceBudgetOpen, setDistanceBudgetOpen] = useState(false);
  // AGENT_TARGET: mission-timeline — open/close mission history timeline overlay
  const [missionTimelineOpen, setMissionTimelineOpen] = useState(false);
  // AGENT_TARGET: body-compare — open/close planet body comparison panel
  const [bodyCompareOpen, setBodyCompareOpen] = useState(false);
  // AGENT_TARGET: fuel-gauge-travel — open/close fuel gauge travel mini-game
  const [fuelGameOpen, setFuelGameOpen] = useState(false);
  // AGENT_TARGET: probe-scrapbook — open/close spacecraft scrapbook
  const [probeScrapbookOpen, setProbeScrapbookOpen] = useState(false);
  // AGENT_TARGET: moon-phase-calendar — open/close moon phase wheel
  const [moonPhaseOpen, setMoonPhaseOpen] = useState(false);
  // AGENT_TARGET: gravity-slingshot — open/close gravity slingshot launchpad overlay
  const [slingshotOpen, setSlingshotOpen] = useState(false);
  // AGENT_TARGET: spacecraft-trajectory — open/close Hohmann transfer arc planner
  const [trajOn, setTrajOn] = useState(false);
  // AGENT_TARGET: scavenger-checklist — show/hide corner scavenger hunt checklist
  const [scavengerOn, setScavengerOn] = useState(() =>
    readStorageFlag("planets_scavenger_on", true),
  );
  // AGENT_TARGET: constellation-spotlight — show on-launch spotlight once per session
  const [constellationSpotlightOn, setConstellationSpotlightOn] = useState(
    () => {
      try {
        return !sessionStorage.getItem("planets_constellation_seen");
      } catch {
        return true;
      }
    },
  );
  // AGENT_TARGET: lullaby-orbit-mode — bedtime toggle state
  const [lullabyMode, setLullabyMode] = useState(() =>
    readStorageFlag("planets_lullaby_mode", false),
  );
  // AGENT_TARGET: hard-mute-quiet-mode — save soundOn state before hard muting
  const soundOnBeforeQuietRef = useRef(null);
  // AGENT_TARGET: kepler-orbital-mechanics — toggle realistic elliptical Kepler orbits
  const [keplerMode, setKeplerMode] = useState(() =>
    readStorageFlag("planets_kepler_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(() => {
    const tick = () => {
      const errors = Array.isArray(window.__spaceErrors)
        ? window.__spaceErrors
        : [];
      const n = errors.length;
      const next = n > 0;
      setHasError((prev) => (prev !== next ? next : prev));
      if (next) {
        const latest = errors[n - 1];
        const normalized =
          window.SpaceExplorerFoundation?.normalizeSpaceErrorRecord?.(latest) ||
          latest ||
          {};
        const msg = String(normalized.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("");
  };

  const hasInteractiveSurfaceOpen = Boolean(
    focused ||
      mazeOn ||
      moonRoverOn ||
      marsRoverOn ||
      marsCityOn ||
      legoRoverGarageOn ||
      pilotOn ||
      treasureOn ||
      rocketPackOn ||
      planetSortOn ||
      engineMatchOn ||
      wireFixOn ||
      rocketCauseOn ||
      ufoBuilderOn ||
      mashupMakerOn ||
      connectionQuestOn ||
      asteroidDodgeOn ||
      missionControlOn ||
      controlBoardMemoryOn ||
      gravityJumpOn ||
      physicsPlayOn ||
      gravityGoldfishOn ||
      cometCurlingOn ||
      solarSailOn ||
      alienLanguageOn ||
      babyRocketsOn ||
      moonAliensOn ||
      moonGardenOn ||
      meteorShieldOn ||
      shootingStarOn ||
      petFeederOn ||
      marsSuitcaseOn ||
      spaceMemoryOn ||
      cinematicReel ||
      spaceReadingOn ||
      storybookOn ||
      storyShelfOn ||
      bhOn ||
      helmetPage ||
      drawingConstellation ||
      missionTimelineOpen ||
      bodyCompareOpen,
  );

  // 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 = hasInteractiveSurfaceOpen || menuOpen;
  useWindowKeyHandler((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)),
      };
    });
  });

  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) => {
    const reward = normalizeKidReward(emoji, text, asset);
    setKidReward(reward);
    try {
      window.dispatchEvent(
        new CustomEvent("space:zephy-praise", {
          detail: { text: reward.text },
        }),
      );
    } catch (_) {}
    window.clearTimeout(kidRewardTimer.current);
    kidRewardTimer.current = window.setTimeout(hideKidReward, 1800);
  };

  function normalizeMissionBadge(source) {
    const detail = source && typeof source === "object" ? source : {};
    const gameId =
      String(detail.gameId || "space-game")
        .replace(/\s+/g, "-")
        .trim()
        .slice(0, 48) || "space-game";
    return {
      gameId,
      emoji: String(detail.emoji || "⭐").trim().slice(0, 4) || "⭐",
      title:
        String(detail.title || "Mission complete!")
          .replace(/\s+/g, " ")
          .trim()
          .slice(0, 48) || "Mission complete!",
      message:
        String(detail.message || "You did it!")
          .replace(/\s+/g, " ")
          .trim()
          .slice(0, 76) || "You did it!",
      earnedAt: Date.now(),
    };
  }

  function readMissionBadges() {
    return safeArray(readStorageJson(MISSION_BADGES_STORAGE_KEY, []))
      .map(normalizeMissionBadge)
      .filter((badge) => badge && badge.gameId)
      .slice(0, MISSION_BADGE_LIMIT);
  }

  const awardMissionBadge = (detail) => {
    const nextBadge = normalizeMissionBadge(detail);
    setMissionBadges((current) => {
      const next = [
        nextBadge,
        ...safeArray(current).filter((badge) => badge.gameId !== nextBadge.gameId),
      ].slice(0, MISSION_BADGE_LIMIT);
      writeStorageJson(MISSION_BADGES_STORAGE_KEY, next);
      return next;
    });
  };

  const showBadgeAlienCameo = () => {
    window.clearTimeout(badgeAlienTimer.current);
    setBadgeAlienCameo({
      key: Date.now(),
      helperIndex: Math.floor(Math.random() * 3),
    });
    playKidSound("giggle");
    badgeAlienTimer.current = window.setTimeout(() => {
      setBadgeAlienCameo(null);
    }, 3600);
  };

  useEffect(() => {
    const onGameFinishCelebration = (event) => {
      const detail =
        event && event.detail && typeof event.detail === "object"
          ? event.detail
          : {};
      setFinishCelebration({
        gameId: String(detail.gameId || "space-game"),
        emoji: String(detail.emoji || "⭐").slice(0, 4) || "⭐",
        title:
          String(detail.title || "Mission complete!")
            .replace(/\s+/g, " ")
            .trim()
            .slice(0, 48) || "Mission complete!",
        message:
          String(detail.message || "You did it!")
            .replace(/\s+/g, " ")
            .trim()
            .slice(0, 96) || "You did it!",
        key: Date.now(),
      });
      awardMissionBadge(detail);
      showBadgeAlienCameo();
      playKidSound("success");
      window.clearTimeout(finishCelebrationTimer.current);
      finishCelebrationTimer.current = window.setTimeout(
        () => setFinishCelebration(null),
        10000,
      );
    };
    window.addEventListener(
      "space-explorer:game-finish-celebration",
      onGameFinishCelebration,
    );
    return () => {
      window.removeEventListener(
        "space-explorer:game-finish-celebration",
        onGameFinishCelebration,
      );
      window.clearTimeout(finishCelebrationTimer.current);
    };
  }, []);

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

  const generatedAsset = (name, fallback = "") =>
    window.SpaceExplorerFoundation?.getGeneratedAsset?.(name, fallback) ||
    fallback;
  const reportAppError = (message, detail) => {
    const normalized =
      window.SpaceExplorerFoundation?.normalizeSpaceErrorRecord?.(
        message,
        detail,
      ) || { message: String(message || "Something glitched.") };
    window.SpaceExplorerFoundation?.reportSpaceError?.(message, detail);
    setHasError(true);
    setErrorMessage(
      String(normalized.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(() => {
    return () => {
      window.clearTimeout(sceneTransitionTimer.current);
      window.clearTimeout(missionLaunchTimer.current);
      document.body.classList.remove(
        "scene-transition-active",
        "scene-transition-launch",
        "scene-transition-return",
      );
    };
  }, []);

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

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

  useEffect(() => {
    writeStorageFlag("planets_deuteranopia_mode", deuteranopiaMode);
  }, [deuteranopiaMode]);

  useEffect(() => {
    writeStorageFlag("planets_quiet_mode", quietMode);
    if (quietMode) {
      stopNarration();
      window.__stopSpaceExplorerProceduralAudio?.();
      // Hard mute: disable all sounds (narration + playKidSound) and remember prev state
      soundOnBeforeQuietRef.current = window.__narration?.isEnabled() ?? true;
      window.__narration?.setEnabled(false);
    } else if (soundOnBeforeQuietRef.current !== null) {
      // Restore sound to what it was before quiet mode was engaged
      window.__narration?.setEnabled(soundOnBeforeQuietRef.current);
      soundOnBeforeQuietRef.current = null;
    }
  }, [quietMode]);

  // AGENT_TARGET: lullaby-orbit-mode — quiet visual bedtime mode
  useEffect(() => {
    writeStorageFlag("planets_lullaby_mode", lullabyMode);
  }, [lullabyMode]);

  useEffect(() => {
    writeStorageFlag("planets_kepler_mode", keplerMode);
  }, [keplerMode]);

  useEffect(() => {
    if (selectedSystem)
      writeStorageValue("planets_selected_system", selectedSystem);
    else {
      try {
        localStorage.removeItem("planets_selected_system");
      } catch {}
    }
  }, [selectedSystem]);
  useEffect(() => {
    setScale(1);
    setPan({ x: 0, y: 0 });
  }, [selectedSystem]);

  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);
    setWireFixOn(false);
    setRocketCauseOn(false);
    setUfoBuilderOn(false);
    setMashupMakerOn(false);
    setConnectionQuestOn(false);
    setAsteroidDodgeOn(false);
    setMissionControlOn(false);
    setControlBoardMemoryOn(false);
    setGravityJumpOn(false);
    setPhysicsPlayOn(false);
    setGravityGoldfishOn(false);
    setCometCurlingOn(false);
    setSolarSailOn(false);
    setAlienLanguageOn(false);
    setBabyRocketsOn(false);
    setMoonAliensOn(false);
    setMoonGardenOn(false);
    setMeteorShieldOn(false);
    setShootingStarOn(false);
    setPetFeederOn(false);
    setMarsSuitcaseOn(false);
    setSpaceMemoryOn(false);
    setCinematicReel(null);
    setSpaceReadingOn(false);
    setStorybookOn(null);
    setStoryShelfOn(false);
    setBhOn(false);
    setMazeOn(false);
    setMoonRoverOn(false);
    setMarsRoverOn(false);
    setMarsCityOn(false);
    setLegoRoverGarageOn(false);
    setPilotOn(false);
    setTreasureOn(false);
  };

  const startSceneTransition = (mode = "launch") => {
    const body = document.body;
    if (!body) return;
    window.clearTimeout(sceneTransitionTimer.current);
    body.classList.remove(
      "scene-transition-active",
      "scene-transition-launch",
      "scene-transition-return",
    );
    // Restart the CSS animation even when two scene changes happen quickly.
    void body.offsetWidth;
    body.classList.add(
      "scene-transition-active",
      mode === "return" ? "scene-transition-return" : "scene-transition-launch",
    );
    sceneTransitionTimer.current = window.setTimeout(() => {
      body.classList.remove(
        "scene-transition-active",
        "scene-transition-launch",
        "scene-transition-return",
      );
    }, 380);
  };

  const closeInteractiveSurfaces = () => {
    if (hasInteractiveSurfaceOpen) startSceneTransition("return");
    window.SpaceExplorerFoundation?.stopSpaceGameRuntimes?.("close");
    setMenuOpen(false);
    setComet(null);
    setTreasurePickup(null);
    setHelmetOpen(false);
    setDrawingConstellation(false);
    setHelmetPage(false);
    setMissionTimelineOpen(false);
    setBodyCompareOpen(false);
    resetInteractiveSurfaceFlags();
    setFocused(null);
    setTour(false);
  };

  const dismissFinishCelebration = () => {
    window.clearTimeout(finishCelebrationTimer.current);
    finishCelebrationTimer.current = null;
    setFinishCelebration(null);
  };

  const openPostGameVideo = (reelId) => {
    const nextReel = cinematicReels[reelId] ? reelId : "badgeParade";
    startSceneTransition("launch");
    dismissFinishCelebration();
    window.SpaceExplorerFoundation?.stopSpaceGameRuntimes?.("post-game-video");
    setMenuOpen(false);
    setComet(null);
    setTreasurePickup(null);
    setHelmetOpen(false);
    setDrawingConstellation(false);
    setHelmetPage(false);
    setMissionTimelineOpen(false);
    setBodyCompareOpen(false);
    resetInteractiveSurfaceFlags();
    setFocused(null);
    setTour(false);
    setCinematicReel(nextReel);
  };

  const prepareActivityLaunch = ({ transition = true } = {}) => {
    if (transition) startSceneTransition("launch");
    window.SpaceExplorerFoundation?.stopSpaceGameRuntimes?.("launch");
    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 (hasNarrationClip(clip)) playNarration(clip);
  };

  const openActivity = ({
    activate,
    narration,
    sound = "chime",
    skipSceneTransition = false,
  } = {}) => {
    if (typeof activate !== "function") {
      reportAppError("Activity launch missing an activate handler.", {
        source: "openActivity",
      });
      playKidSound("pop");
      return;
    }
    prepareActivityLaunch({ transition: !skipSceneTransition });
    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";
    startSceneTransition("launch");
    setStoryShelfOn(false);
    setStorybookOn(nextBook);
    playKidSound("chime");
  };

  const returnToStoryShelf = () => {
    startSceneTransition("return");
    setStorybookOn(null);
    setStoryShelfOn(true);
    playKidSound("boop");
  };

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

  const missionMapActivities = MISSION_MAP_ACTIVITY_META.map((activity) =>
    bindMissionMapActivity(activity, {
      openActivity,
      openRunActivity,
      openPanelActivity,
      runTargets: {
        maze: { setRun: setMazeRun, setOpen: setMazeOn },
        moonRover: { setRun: setMoonRoverRun, setOpen: setMoonRoverOn },
        marsRover: { setRun: setMarsRoverRun, setOpen: setMarsRoverOn },
        pilot: { setRun: setPilotRun, setOpen: setPilotOn },
        blackHole: { setRun: setBhRun, setOpen: setBhOn },
      },
      panelTargets: {
        missionControl: setMissionControlOn,
        planetSort: setPlanetSortOn,
        engineMatch: setEngineMatchOn,
        wireFix: setWireFixOn,
        rocketCause: setRocketCauseOn,
        ufoBuilder: setUfoBuilderOn,
        mashupMaker: setMashupMakerOn,
        connectionQuest: setConnectionQuestOn,
        asteroidDodge: setAsteroidDodgeOn,
        gravityJump: setGravityJumpOn,
        physicsPlayLab: setPhysicsPlayOn,
        gravityGoldfish: setGravityGoldfishOn,
        cometCurling: setCometCurlingOn,
        solarSail: setSolarSailOn,
        controlBoardMemory: setControlBoardMemoryOn,
        alienLanguage: setAlienLanguageOn,
        babyRockets: setBabyRocketsOn,
        moonAliens: setMoonAliensOn,
        moonGarden: setMoonGardenOn,
        meteorShield: setMeteorShieldOn,
        shootingStar: setShootingStarOn,
        petFeeder: setPetFeederOn,
        marsSuitcase: setMarsSuitcaseOn,
        marsCity: setMarsCityOn,
        spaceMemory: setSpaceMemoryOn,
        legoRoverGarage: setLegoRoverGarageOn,
        spaceReading: setSpaceReadingOn,
        storyShelf: setStoryShelfOn,
        helmetPage: setHelmetPage,
      },
      customTargets: {
        treasureHunt: () => {
          resetTreasureHunt();
          setTreasureOn(true);
        },
        buildRocket: () => {
          setSolarRocketOrbiting(false);
          writeStorageFlag("planets_solar_rocket_orbiting", false);
          setRocketPackOn(true);
        },
        galileoEyepiece: () => setGalileoOn(true),
        venusRain: () => setVenusRaining(true),
        trappistSystem: () => setSelectedSystem("trappist1"),
        keplerSystem: () => setSelectedSystem("kepler90"),
        marsStory: () => setCinematicReel("mars"),
        spaceFlight: () => setCinematicReel("flight"),
      },
      optionResolvers: {
        buildRocket: () => ({
          narration: rocketStartNarration(),
        }),
      },
    }),
  );
  const missionMapConfigIssues = findMissionMapConfigIssues(
    MISSION_MAP_ACTIVITY_META,
    missionMapActivities,
  );
  const missionMapConfigIssueKey = missionMapConfigIssues.join("|");
  const missionMapGroups = groupMissionMapActivities(missionMapActivities);
  const [missionMapCategoryId, setMissionMapCategoryId] = useState(() =>
    readStorageEnum(
      MISSION_MAP_CATEGORY_STORAGE_KEY,
      missionMapCategoryIds(true),
      MISSION_MAP_DEFAULT_CATEGORY_ID,
    ),
  );
  const missionMapView = createMissionMapView(
    missionMapGroups,
    missionMapCategoryId,
  );
  const selectMissionMapCategory = (categoryId) => {
    const nextCategoryId = normalizeMissionMapCategoryId(categoryId);
    setMissionMapCategoryId(nextCategoryId);
    writeStorageValue(MISSION_MAP_CATEGORY_STORAGE_KEY, nextCategoryId);
  };

  React.useEffect(() => {
    if (!missionMapConfigIssues.length) return;
    reportAppError("Mission Map configuration issue.", {
      source: "missionMapConfig",
      issues: missionMapConfigIssues.slice(0, 8),
    });
  }, [missionMapConfigIssueKey]);

  const launchMissionMapActivity = (activity) => {
    window.clearTimeout(missionLaunchTimer.current);
    if (
      !activity ||
      activity.unavailable ||
      typeof activity.launch !== "function"
    ) {
      reportAppError("Mission Map activity is not available.", {
        source: "launchMissionMapActivity",
        activityId: activity && activity.id,
        launchType: activity && activity.launchType,
        target: activity && activity.target,
      });
      playKidSound("pop");
      return;
    }
    setMissionLaunchPlan({
      id: activity.id,
      icon: activity.icon,
      label: activity.label,
      summary: activity.summary,
      categoryId: activity.categoryId,
    });
    playKidSound(activity.sound || "boop");
    missionLaunchTimer.current = window.setTimeout(() => {
      setMissionLaunchPlan(null);
      try {
        activity.launch();
      } catch (error) {
        reportAppError("Mission Map activity failed to launch.", {
          source: "launchMissionMapActivity",
          activityId: activity.id,
          launchType: activity.launchType,
          target: activity.target,
          error: error && (error.stack || error.message || String(error)),
        });
        playKidSound("pop");
      }
    }, 260);
  };

  const missionLaunchCategory =
    missionLaunchPlan &&
    missionMapView.categoryOptions.find(
      (category) => category.id === missionLaunchPlan.categoryId,
    );

  const renderMissionLaunchPlan = () => {
    if (!missionLaunchPlan) return null;
    try {
      const categoryLabel =
        (missionLaunchCategory && missionLaunchCategory.label) || "Mission";
      return (
        <div
          className="mission-launch-plan"
          role="status"
          aria-live="polite"
        >
          <div className="mission-launch-plan-route" aria-hidden="true">
            <span>Home ship</span>
            <i />
            <strong>{missionLaunchPlan.icon}</strong>
            <i />
            <span>{categoryLabel}</span>
          </div>
          <div className="mission-launch-plan-copy">
            <span>Flight path set</span>
            <strong>{missionLaunchPlan.label}</strong>
            <small>{missionLaunchPlan.summary}</small>
          </div>
        </div>
      );
    } catch (error) {
      reportAppError("Mission Map launch plan failed to render.", {
        source: "renderMissionLaunchPlan",
        activityId: missionLaunchPlan && missionLaunchPlan.id,
        error: error && (error.stack || error.message || String(error)),
      });
      return null;
    }
  };

  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) => {
    // AGENT_TARGET: venus-rain — trigger acid rain when Venus is tapped
    if (planet.id === "venus") setVenusRaining(true);
    // AGENT_TARGET: scavenger-checklist — notify checklist component when a planet is focused
    if (scavengerOn && window.__scavengerNotify) {
      const found = window.__scavengerNotify(planet.id);
      if (found && found.matched) {
        showKidReward(
          found.matched.emoji,
          found.allDone
            ? "All planets found! Explorer badge earned!"
            : `Found! ${found.matched.text}`,
        );
      }
    }
    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);
      const nextSystemId = systemIdForPlanetId(nextPlanet && nextPlanet.id);
      if (nextSystemId) setSelectedSystem(nextSystemId);
    };
    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 = activePlanets.findIndex((p) => p.id === focused.id);
      if (i < 0) return;
      if (e.key === "ArrowRight")
        setFocused(activePlanets[(i + 1) % activePlanets.length]);
      else if (e.key === "ArrowLeft")
        setFocused(
          activePlanets[(i - 1 + activePlanets.length) % activePlanets.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]);

  const renderZephyBuddy = (gameId, props = {}) => {
    const ZephyGameBuddy = window.SpaceExplorerZephyBuddy;
    return ZephyGameBuddy ? <ZephyGameBuddy gameId={gameId} {...props} /> : null;
  };
  const appShellClassName = (labelClass = "labels-on") =>
    [
      "app",
      labelClass,
      quietMode ? "quiet-mode" : "",
      lullabyMode ? "lullaby-mode" : "",
      deuteranopiaMode ? "deuteranopia-mode" : "",
      `quality-${quality.tier}`,
    ]
      .filter(Boolean)
      .join(" ");
  const renderGameFinishFireworks = () => (
    <GameFinishFireworks
      celebration={finishCelebration}
      videoChoices={postGameVideoChoices}
      onWatchVideo={openPostGameVideo}
      onClose={dismissFinishCelebration}
    />
  );
  const openMissionBadgeMap = () => {
    selectMissionMapCategory(MISSION_MAP_DEFAULT_CATEGORY_ID);
    setMenuOpen(true);
    playKidSound("boop");
  };

  if (mazeOn) {
    return (
      <div className={appShellClassName()}>
        <Starfield count={Math.round(tweaks.starDensity * quality.starScale)} />
        <MeteorSwipeCanvas />
        <MazeLevel key={mazeRun} onExit={exitMaze} />
        {renderZephyBuddy("maze")}
      </div>
    );
  }

  if (moonRoverOn) {
    return (
      <div className={appShellClassName()}>
        <MoonRoverLevel key={moonRoverRun} onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("moonRover")}
      </div>
    );
  }

  if (marsRoverOn) {
    return (
      <div className={appShellClassName()}>
        <MarsRoverLevel key={marsRoverRun} onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("marsRover")}
      </div>
    );
  }

  if (marsCityOn) {
    const MarsCityBuilderLevel = window.SpaceExplorerMarsCity;
    return (
      <div className={appShellClassName()}>
        {MarsCityBuilderLevel ? (
          <MarsCityBuilderLevel onExit={closeInteractiveSurfaces} />
        ) : (
          <div className="space-error-fallback">
            Mars City Builder is loading. Try the Mission Map again.
          </div>
        )}
        {renderZephyBuddy("marsCity")}
      </div>
    );
  }

  if (legoRoverGarageOn) {
    const LegoRoverGarageLevel = window.SpaceExplorerLegoRoverGarage;
    return (
      <div className={appShellClassName()}>
        {LegoRoverGarageLevel ? (
          <LegoRoverGarageLevel onExit={closeInteractiveSurfaces} />
        ) : (
          <div className="space-error-fallback">
            Rover Garage is loading. Try the Mission Map again.
          </div>
        )}
        {renderZephyBuddy("legoRoverGarage")}
      </div>
    );
  }

  if (pilotOn) {
    return (
      <div className={appShellClassName()}>
        <PilotControlLevel key={pilotRun} onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("pilot")}
      </div>
    );
  }

  if (treasureOn) {
    return (
      <div className={appShellClassName()}>
        <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} />
        {renderZephyBuddy("treasure")}
      </div>
    );
  }

  if (planetSortOn) {
    return (
      <div className={appShellClassName()}>
        <PlanetSortLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("planetSort")}
      </div>
    );
  }

  if (engineMatchOn) {
    return (
      <div className={appShellClassName()}>
        <EngineMatchLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("engineMatch")}
      </div>
    );
  }

  if (wireFixOn) {
    return (
      <div className={appShellClassName()}>
        <SpaceshipWireFixLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (rocketCauseOn) {
    return (
      <div className={appShellClassName()}>
        <RocketCauseLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("rocketCause")}
      </div>
    );
  }

  if (ufoBuilderOn) {
    return (
      <div className={appShellClassName()}>
        <UfoBuilderLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("ufoBuilder")}
      </div>
    );
  }

  if (mashupMakerOn) {
    return (
      <div className={appShellClassName()}>
        <MashupMakerLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("mashupMaker")}
      </div>
    );
  }

  if (connectionQuestOn) {
    return (
      <div className={appShellClassName()}>
        <ConnectionQuestLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("connectionQuest")}
      </div>
    );
  }

  if (asteroidDodgeOn) {
    return (
      <div className={appShellClassName()}>
        <AsteroidDodgeLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("asteroidDodge")}
      </div>
    );
  }

  if (missionControlOn) {
    return (
      <div className={appShellClassName()}>
        <MissionControlLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("missionControl")}
      </div>
    );
  }

  if (controlBoardMemoryOn) {
    return (
      <div className={appShellClassName()}>
        <ControlBoardMemoryLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (gravityJumpOn) {
    return (
      <div className={appShellClassName()}>
        <GravityJumpLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("gravityJump")}
      </div>
    );
  }

  if (physicsPlayOn) {
    return (
      <div className={appShellClassName()}>
        <PhysicsPlayLab onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (gravityGoldfishOn) {
    return (
      <div className={appShellClassName()}>
        <GravityGoldfishLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (cometCurlingOn) {
    return (
      <div className={appShellClassName()}>
        <CometCurlingLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (solarSailOn) {
    return (
      <div className={appShellClassName()}>
        <SolarSailSkimmerLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
      </div>
    );
  }

  if (alienLanguageOn) {
    return (
      <div className={appShellClassName()}>
        <AlienLanguageLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("alienLanguage")}
      </div>
    );
  }

  if (babyRocketsOn) {
    return (
      <div className={appShellClassName()}>
        <BabyRocketsLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("babyRockets")}
      </div>
    );
  }

  if (moonAliensOn) {
    return (
      <div className={appShellClassName()}>
        <MoonAliensPhaserLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("moonAliens")}
      </div>
    );
  }

  if (moonGardenOn) {
    return (
      <div className={appShellClassName()}>
        <MoonGardenRescuePhaserLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("moonGarden")}
      </div>
    );
  }

  if (meteorShieldOn) {
    return (
      <div className={appShellClassName()}>
        <MeteorShieldPhaserLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("meteorShield")}
      </div>
    );
  }

  if (shootingStarOn) {
    return (
      <div className={appShellClassName()}>
        <ShootingStarRidePhaserLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("shootingStar")}
      </div>
    );
  }

  if (petFeederOn) {
    return (
      <div className={appShellClassName()}>
        <PlanetPetFeederLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("petFeeder")}
      </div>
    );
  }

  if (marsSuitcaseOn) {
    return (
      <div className={appShellClassName()}>
        <MarsSuitcaseLevel onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("marsSuitcase")}
      </div>
    );
  }

  if (spaceMemoryOn) {
    return (
      <div className={appShellClassName()}>
        <SpaceMemoryLevel onExit={closeInteractiveSurfaces} />
        {renderGameFinishFireworks()}
        {renderZephyBuddy("spaceMemory")}
      </div>
    );
  }

  if (cinematicReel) {
    return (
      <div className={appShellClassName()}>
        <CinematicReel
          reel={cinematicReels[cinematicReel] || cinematicReels.mars}
          onExit={closeInteractiveSurfaces}
        />
      </div>
    );
  }

  if (spaceReadingOn) {
    return (
      <div className={appShellClassName()}>
        <SpaceReadingDeck onExit={closeInteractiveSurfaces} />
        {renderZephyBuddy("spaceReading")}
      </div>
    );
  }

  if (storybookOn) {
    return (
      <div className={appShellClassName()}>
        <StoryBookPage
          book={storybookOn}
          onExit={closeInteractiveSurfaces}
          onShelf={returnToStoryShelf}
        />
        {renderZephyBuddy("storybook")}
      </div>
    );
  }

  if (storyShelfOn) {
    return (
      <div className={appShellClassName()}>
        <StoryShelfPage
          onRead={openStoryBook}
          onExit={closeInteractiveSurfaces}
        />
        {renderZephyBuddy("storyShelf")}
      </div>
    );
  }

  if (bhOn) {
    return (
      <div className={appShellClassName()}>
        <BlackHoleLevel key={bhRun} onExit={exitBh} />
        {renderZephyBuddy("blackHole")}
      </div>
    );
  }

  if (rocketPackOn) {
    return (
      <div className={appShellClassName()}>
        <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}
        />
        {renderZephyBuddy("rocketPack")}
      </div>
    );
  }

  if (helmetPage) {
    return (
      <div className={appShellClassName()}>
        <Starfield count={Math.round(tweaks.starDensity * quality.starScale)} />
        <MeteorSwipeCanvas />
        <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={closeInteractiveSurfaces}
        />
        {renderZephyBuddy("helmetLab")}
      </div>
    );
  }

  return (
    <div
      className={appShellClassName(tweaks.showLabels ? "labels-on" : "labels-off")}
    >
      <Starfield count={Math.round(tweaks.starDensity * quality.starScale)} />
      <MeteorSwipeCanvas />

      {/* AGENT_TARGET: deuteranopia-mode — badge shown when color-friendly mode is active */}
      {deuteranopiaMode && (
        <div className="deuteranopia-badge" aria-live="polite">
          👁 Color-friendly
        </div>
      )}

      {/* AGENT_TARGET: venus-rain — acid rain overlay */}
      {venusRaining && <VenusRain onDismiss={() => setVenusRaining(false)} />}

      {/* AGENT_TARGET: galileo-eyepiece — era discovery slider overlay */}
      {galileoOn && <GalileoEyepiece onClose={() => setGalileoOn(false)} />}

      {/* AGENT_TARGET: distance-budget-tour — light-year budget explorer overlay */}
      {distanceBudgetOpen && typeof DistanceBudgetTour !== "undefined" && (
        <DistanceBudgetTour onClose={() => setDistanceBudgetOpen(false)} />
      )}

      {/* AGENT_TARGET: mission-timeline — mission history timeline overlay */}
      {missionTimelineOpen && typeof MissionTimeline !== "undefined" && (
        <MissionTimeline onClose={() => setMissionTimelineOpen(false)} />
      )}

      {/* AGENT_TARGET: body-compare — planet comparison panel overlay */}
      {bodyCompareOpen && typeof BodyComparePanel !== "undefined" && (
        <BodyComparePanel onClose={() => setBodyCompareOpen(false)} />
      )}

      {/* AGENT_TARGET: fuel-gauge-travel — fuel gauge travel mini-game overlay */}
      {fuelGameOpen && typeof FuelGaugeTravel !== "undefined" && (
        <FuelGaugeTravel onClose={() => setFuelGameOpen(false)} />
      )}

      {/* AGENT_TARGET: probe-scrapbook — spacecraft scrapbook overlay */}
      {probeScrapbookOpen && typeof ProbeScrapbook !== "undefined" && (
        <ProbeScrapbook onClose={() => setProbeScrapbookOpen(false)} />
      )}

      {/* AGENT_TARGET: moon-phase-calendar — moon phase wheel overlay */}
      {moonPhaseOpen && typeof MoonPhaseWheel !== "undefined" && (
        <MoonPhaseWheel onClose={() => setMoonPhaseOpen(false)} />
      )}

      {/* AGENT_TARGET: gravity-slingshot — drag-a-rocket orbital mechanics overlay */}
      {slingshotOpen && typeof GravitySlingshot !== "undefined" && (
        <GravitySlingshot onClose={() => setSlingshotOpen(false)} />
      )}

      {/* AGENT_TARGET: spacecraft-trajectory — Hohmann transfer arc planner overlay */}
      {trajOn && typeof SpacecraftTrajectory !== "undefined" && (
        <SpacecraftTrajectory onClose={() => setTrajOn(false)} />
      )}

      {/* AGENT_TARGET: night-sky-view — tonight's planet visibility overlay */}
      {nightSkyOpen && typeof NightSkyView !== "undefined" && (
        <NightSkyView onClose={() => setNightSkyOpen(false)} />
      )}

      {/* AGENT_TARGET: constellation-spotlight — on-launch constellation highlight */}
      {constellationSpotlightOn &&
        typeof ConstellationSpotlight !== "undefined" && (
          <ConstellationSpotlight
            onDismiss={() => {
              setConstellationSpotlightOn(false);
              try {
                sessionStorage.setItem("planets_constellation_seen", "1");
              } catch {}
            }}
          />
        )}

      {!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={
            lullabyMode
              ? 0.15
              : quietMode && orbitSpeed === 1
                ? 0.35
                : orbitSpeed
          }
          keplerMode={keplerMode}
          systemPlanets={activePlanets}
          showAsteroidBelt={!selectedSystem}
        />
      )}
      {!focused && !menuOpen && !drawingConstellation ? <HubAmbientComet /> : null}

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

      {!focused && (
        <ConstellationDrawLayer
          active={drawingConstellation}
          onFinish={(c) => {
            constellations.add({
              ...c,
              name: 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 = activePlanets.findIndex((p) => p.id === focused.id);
            setFocused(
              activePlanets[
                (i - 1 + activePlanets.length) % activePlanets.length
              ],
            );
          }}
          onNext={() => {
            const i = activePlanets.findIndex((p) => p.id === focused.id);
            setFocused(activePlanets[(i + 1) % activePlanets.length]);
          }}
          onAstronautWave={() => setAstronautWaveKey((n) => n + 1)}
          onFavoriteToggle={setFavoritePlanetId}
          onOpenMissionMap={() => {
            setFocused(null);
            selectMissionMapCategory(MISSION_MAP_DEFAULT_CATEGORY_ID);
            setMenuOpen(true);
          }}
          onOpenMarsRover={() =>
            openRunActivity(setMarsRoverRun, setMarsRoverOn, {
              narration: "game_mars_rover_start.mp3",
              sound: "whoosh",
            })
          }
        />
      )}

      {!focused && (
        <header className="header">
          <div className="title-block">
            <div className="title">
              {selectedSystem &&
              typeof EXOPLANET_SYSTEMS !== "undefined" &&
              EXOPLANET_SYSTEMS[selectedSystem]
                ? EXOPLANET_SYSTEMS[selectedSystem].name
                : "Planets"}
            </div>
            <div className="subtitle">
              {selectedSystem &&
              typeof EXOPLANET_SYSTEMS !== "undefined" &&
              EXOPLANET_SYSTEMS[selectedSystem]
                ? EXOPLANET_SYSTEMS[selectedSystem].blurb
                : "Zephy's Star Chart · map new worlds, earn badges, remember one thing"}
            </div>
            <div className="exoplanet-picker">
              {[
                { id: null, label: "Solar System" },
                { id: "trappist1", label: "TRAPPIST-1" },
                { id: "kepler90", label: "Kepler-90" },
                // AGENT_TARGET: exoplanet-system-browser — 55 Cancri system picker button
                { id: "cancri55", label: "55 Cancri" },
              ].map(({ id, label }) => (
                <button
                  key={id || "solar"}
                  className={`exoplanet-btn${selectedSystem === id ? " on" : ""}`}
                  aria-pressed={selectedSystem === id ? "true" : "false"}
                  onClick={() => {
                    setSelectedSystem(id);
                    setFocused(null);
                  }}
                >
                  {label}
                </button>
              ))}
            </div>
            <FieldJournalStrip
              worlds={activePlanets}
              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={() => {
                if (!menuOpen) {
                  selectMissionMapCategory(MISSION_MAP_DEFAULT_CATEGORY_ID);
                }
                setMenuOpen((v) => !v);
              }}
              aria-expanded={menuOpen ? "true" : "false"}
              aria-haspopup="true"
            >
              ☰ Missions
            </button>
            {menuOpen ? (
              <div className="action-menu">
                <MissionMapHeader
                  statusText={missionMapView.statusText}
                  learningCue={missionMapView.learningCue}
                />
                <MissionMapCategoryJumps
                  options={missionMapView.categoryOptions}
                  selectedId={missionMapView.selectedCategoryId}
                  onSelect={selectMissionMapCategory}
                />
                <div className="game-map-window" aria-label="Game mission map">
                  <MissionMapActivityGroups
                    groups={missionMapView.visibleGroups}
                    onLaunch={launchMissionMapActivity}
                    emptyText={missionMapView.emptyText}
                  />
                  {renderMissionLaunchPlan()}
                </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 · All sounds off</strong>
                  </button>
                  {/* AGENT_TARGET: lullaby-orbit-mode — bedtime menu button */}
                  <button
                    className={`menu-item ${lullabyMode ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setLullabyMode((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>🌙</span>
                    <strong>Lullaby Orbit · quiet visuals</strong>
                  </button>
                  {/* AGENT_TARGET: kepler-orbital-mechanics — toggle realistic elliptical orbits */}
                  <button
                    className={`menu-item ${keplerMode ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setKeplerMode((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>🪐</span>
                    <strong>Real Orbits</strong>
                  </button>
                  {/* AGENT_TARGET: galileo-eyepiece — open era discovery slider */}
                  <button
                    className={`menu-item ${galileoOn ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setGalileoOn((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>🔭</span>
                    <strong>Galileo's Eyepiece</strong>
                  </button>
                  {/* AGENT_TARGET: mission-timeline — open mission history timeline */}
                  <button
                    className={`menu-item ${missionTimelineOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setMissionTimelineOpen((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>🚀</span>
                    <strong>Mission Timeline</strong>
                  </button>
                  {/* AGENT_TARGET: body-compare — open planet comparison panel */}
                  <button
                    className={`menu-item ${bodyCompareOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setBodyCompareOpen((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>⚖</span>
                    <strong>Compare Planets</strong>
                  </button>
                  {/* AGENT_TARGET: fuel-gauge-travel — open fuel gauge travel game */}
                  <button
                    className={`menu-item ${fuelGameOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setFuelGameOpen((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>⛽</span>
                    <strong>Fuel Gauge Travel</strong>
                  </button>
                  {/* AGENT_TARGET: moon-phase-calendar — open moon phase wheel */}
                  <button
                    className={`menu-item ${moonPhaseOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setMoonPhaseOpen((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>🌙</span>
                    <strong>Moon Phase</strong>
                  </button>
                  {/* AGENT_TARGET: probe-scrapbook — open spacecraft scrapbook */}
                  <button
                    className={`menu-item ${probeScrapbookOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setProbeScrapbookOpen((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>📚</span>
                    <strong>Spacecraft Scrapbook</strong>
                  </button>
                  {/* AGENT_TARGET: gravity-slingshot — open drag-rocket orbital mechanics */}
                  <button
                    className={`menu-item ${slingshotOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setSlingshotOpen((v) => !v);
                      }, "whoosh")
                    }
                  >
                    <span>⚡</span>
                    <strong>Gravity Slingshot</strong>
                  </button>
                  {/* AGENT_TARGET: spacecraft-trajectory — open Hohmann transfer arc planner */}
                  <button
                    className={`menu-item ${trajOn ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setTrajOn((v) => !v);
                      }, "whoosh")
                    }
                  >
                    <span>🛸</span>
                    <strong>Mission Trajectory</strong>
                  </button>
                  {/* AGENT_TARGET: scavenger-checklist — toggle corner scavenger hunt */}
                  <button
                    className={`menu-item ${scavengerOn ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        const next = !scavengerOn;
                        setScavengerOn(next);
                        writeStorageFlag("planets_scavenger_on", next);
                      }, "boop")
                    }
                  >
                    <span>🔍</span>
                    <strong>Scavenger Hunt</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>
                  {/* AGENT_TARGET: deuteranopia-mode — menu toggle for orange-blue color palette */}
                  <button
                    className={`menu-item ${deuteranopiaMode ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setDeuteranopiaMode((v) => !v);
                      }, "boop")
                    }
                  >
                    <span>👁</span>
                    <strong>Color-friendly mode</strong>
                  </button>
                  {/* AGENT_TARGET: night-sky-view — open tonight's planet visibility */}
                  <button
                    className={`menu-item ${nightSkyOpen ? "on" : ""}`}
                    onClick={() =>
                      runMenuCommand(() => {
                        setNightSkyOpen((v) => !v);
                      }, "chime")
                    }
                  >
                    <span>🌌</span>
                    <strong>Tonight's Sky</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 && !drawingConstellation && !menuOpen && missionBadges.length ? (
        <MissionBadgeThread
          badges={missionBadges}
          onOpenMissionMap={openMissionBadgeMap}
          onWatchVideo={openPostGameVideo}
        />
      ) : null}

      {!focused && !drawingConstellation && !menuOpen && !kidMode
        ? renderZephyBuddy("home", {
            variant: "home",
            missionCount: missionBadges.length,
            hasBadges: Boolean(missionBadges.length),
            onOpenMissionMap: openMissionBadgeMap,
          })
        : null}

      {/* AGENT_TARGET: scavenger-checklist — corner solar system scavenger hunt */}
      {!focused &&
        !menuOpen &&
        scavengerOn &&
        typeof ScavengerChecklist !== "undefined" && (
          <ScavengerChecklist
            onCelebrate={() =>
              showKidReward("🎉", "Solar system explorer! You found them all!")
            }
          />
        )}

      {!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} />
      <MissionBadgeAlienCameo cameo={badgeAlienCameo} />
      {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>
  );
}

function CinematicReel({ reel, onExit }) {
  const videoRef = React.useRef(null);
  const [ended, setEnded] = React.useState(false);
  const [mediaFailed, setMediaFailed] = React.useState(false);
  const [retryKey, setRetryKey] = React.useState(0);
  const activeReel = reel || {
    label: "Cinematic",
    src: "data/cinematic-intro.mp4?v=mars-story-20260512",
    poster: "data/generated-assets/cinematic/mars-story-poster.png",
    aria: "Astronaut story video",
    cue: "Watch a short space movie.",
  };

  React.useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    setMediaFailed(false);
    setEnded(false);
    try {
      v.load();
    } catch (_) {}
    const tryPlay = v.play();
    if (tryPlay && typeof tryPlay.catch === "function") tryPlay.catch(() => {});
    const handleKey = (event) => {
      if (event.key === "Escape") onExit();
    };
    window.addEventListener("keydown", handleKey);
    return () => window.removeEventListener("keydown", handleKey);
  }, [activeReel.src, onExit, retryKey]);

  const retryMedia = () => {
    setMediaFailed(false);
    setEnded(false);
    setRetryKey((key) => key + 1);
  };

  const handleMediaFailure = () => {
    setEnded(false);
    setMediaFailed(true);
  };

  return (
    <div
      className="cinematic-stage"
      role="dialog"
      aria-label={`${activeReel.label} video`}
    >
      <video
        ref={videoRef}
        className="cinematic-video"
        src={activeReel.src}
        poster={activeReel.poster}
        playsInline
        autoPlay
        preload="metadata"
        controls
        aria-label={activeReel.aria}
        onEnded={() => setEnded(true)}
        onError={handleMediaFailure}
      />
      {mediaFailed && (
        <div className="cinematic-retry" role="status" aria-live="polite">
          <strong>{activeReel.label} needs another launch.</strong>
          <span>The movie did not load this time.</span>
          <div className="cinematic-retry-actions">
            <button type="button" onClick={retryMedia}>
              Tap to retry
            </button>
            <button type="button" onClick={onExit}>
              Back to missions
            </button>
          </div>
        </div>
      )}
      <div className="cinematic-caption" aria-live="polite">
        <strong>{activeReel.label}</strong>
        <span>
          {ended ? "Movie complete. Ready to head back?" : activeReel.cue}
        </span>
      </div>
      <button
        type="button"
        className="cinematic-exit"
        onClick={onExit}
        aria-label="Close cinematic"
      >
        {ended ? "Back to space" : "Skip"}
      </button>
    </div>
  );
}

function GameFinishFireworks({
  celebration,
  videoChoices = [],
  onWatchVideo,
  onClose,
}) {
  if (!celebration) return null;
  const choices = Array.isArray(videoChoices)
    ? videoChoices.filter((choice) => choice && choice.reelId)
    : [];
  const sparks = [
    [10, 18, "#ffe16f"],
    [22, 38, "#7cffb9"],
    [17, 72, "#49b1ff"],
    [36, 16, "#ff7a6a"],
    [49, 28, "#fff5a8"],
    [62, 12, "#78f0ff"],
    [78, 26, "#ffe16f"],
    [86, 54, "#7cffb9"],
    [72, 78, "#ff7a6a"],
    [53, 86, "#49b1ff"],
    [33, 82, "#fff5a8"],
    [91, 16, "#ffb0f0"],
  ];
  return (
    <div
      className="game-finish-fireworks"
      role="status"
      aria-live="polite"
      key={celebration.key}
    >
      <div className="game-finish-fireworks-sky" aria-hidden="true">
        {sparks.map(([left, top, color], index) => (
          <span
            key={index}
            style={{
              left: `${left}%`,
              top: `${top}%`,
              "--spark": color,
              animationDelay: `${(index % 4) * 0.12}s`,
            }}
          />
        ))}
      </div>
      <div className="game-finish-card">
        <span className="game-finish-emoji" aria-hidden="true">
          {celebration.emoji}
        </span>
        <div className="game-finish-copy">
          <strong>{celebration.title}</strong>
          <small>{celebration.message}</small>
        </div>
        {choices.length ? (
          <div className="game-finish-videos" aria-label="Post-game videos">
            {choices.map((choice) => (
              <button
                type="button"
                className="game-finish-video"
                key={choice.reelId}
                onClick={() => onWatchVideo?.(choice.reelId)}
              >
                <span className="game-finish-video-emoji" aria-hidden="true">
                  {choice.emoji || "▶"}
                </span>
                <span className="game-finish-video-copy">
                  <strong>{choice.label}</strong>
                  <small>{choice.cue}</small>
                </span>
              </button>
            ))}
          </div>
        ) : null}
        <button
          type="button"
          className="game-finish-dismiss"
          onClick={() => onClose?.()}
        >
          Keep playing
        </button>
      </div>
    </div>
  );
}

function MissionBadgeThread({ badges = [], onOpenMissionMap, onWatchVideo }) {
  const visibleBadges = safeArray(badges).slice(0, 6);
  if (!visibleBadges.length) return null;
  const badgeCount = safeArray(badges).length;
  const latest = visibleBadges[0];
  const milestone =
    badgeCount >= 9
      ? {
          label: "Alien dance unlocked",
          cue: "Big explorer celebration",
          reelId: "alienDance",
          emoji: "👽",
        }
      : badgeCount >= 6
        ? {
            label: "Quiet cruise unlocked",
            cue: "Float through your route",
            reelId: "quietCruise",
            emoji: "🪐",
          }
        : badgeCount >= 3
          ? {
              label: "Badge parade unlocked",
              cue: "Watch your badges shine",
              reelId: "badgeParade",
              emoji: "🏅",
            }
          : null;
  return (
    <section className="mission-badge-thread" aria-label="Mission badges earned">
      <div className="mission-badge-thread-copy">
        <span>Mission badges</span>
        <strong>{latest.title}</strong>
        <small>{badgeCount} earned on this adventure path</small>
      </div>
      <div className="mission-badge-row" aria-label="Recent mission badges">
        {visibleBadges.map((badge) => (
          <span
            key={`${badge.gameId}-${badge.earnedAt}`}
            className="mission-badge-chip"
            title={`${badge.title}: ${badge.message}`}
          >
            <SafeAssetImage
              src={GENERATED_ASSETS.missionBadge}
              alt=""
              context={`MissionBadge:${badge.gameId}`}
              fallbackText=""
            />
            <b aria-hidden="true">{badge.emoji}</b>
            <span className="sr-only">{badge.title}</span>
          </span>
        ))}
      </div>
      <button type="button" onClick={onOpenMissionMap}>
        Open missions
      </button>
      {milestone && typeof onWatchVideo === "function" ? (
        <button
          type="button"
          className="mission-badge-milestone"
          onClick={() => onWatchVideo(milestone.reelId)}
        >
          <span aria-hidden="true">{milestone.emoji}</span>
          <span>
            <strong>{milestone.label}</strong>
            <small>{milestone.cue}</small>
          </span>
        </button>
      ) : null}
    </section>
  );
}

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