function storedNumber(key, fallback, min, max) {
  try {
    const raw = localStorage.getItem(key);
    const value = raw === null ? fallback : Number(raw);
    if (!Number.isFinite(value)) return fallback;
    return Math.max(min, Math.min(max, value));
  } catch {
    return fallback;
  }
}

const MARS_ROVER_SIGHTINGS = [
  {
    name: "Curiosity",
    place: "Gale Crater",
    note: "Today I drilled a dusty rock and sent home a tiny chemistry clue.",
    marker: "C",
  },
  {
    name: "Perseverance",
    place: "Jezero Crater",
    note: "Today I tucked a sample tube away for future Mars explorers.",
    marker: "P",
  },
];

const PLANET_FLAG_COLORS = ["#ffd66b", "#7ee7ff", "#ff8ad6", "#94f28f", "#ff9a5b"];

function planetDistanceAu(planet) {
  const au = parseFloat(String((planet && planet.distance) || "").replace(/,/g, ""));
  return Number.isFinite(au) && au > 0 ? au : null;
}

function detailValue(value, fallback = "Unknown") {
  const text = String(value ?? "").trim();
  return text || fallback;
}

function blurbMatch(planet, pattern) {
  const match = String((planet && planet.blurb) || "").match(pattern);
  return match ? match[1] : "";
}

function planetDisplayName(planet) {
  if (!planet) return "world";
  if (String(planet.id || "").startsWith("t1") && /^[a-h]$/.test(planet.name)) {
    return `TRAPPIST-1 ${planet.name}`;
  }
  if (String(planet.id || "").startsWith("k90") && /^[a-i]$/.test(planet.name)) {
    return `Kepler-90 ${planet.name}`;
  }
  return planet.name;
}

function passportNudgeKey(planet) {
  return `planets_passport_nudge_${String((planet && planet.id) || "world")}`;
}

// AGENT_TARGET: planet-terminator — axial-tilt-aware day/night terminator, updates every 10s
const PLANET_AXIAL_TILTS = {
  mercury: 0.034, venus: 177.4, earth: 23.44, mars: 25.19,
  jupiter: 3.13,  saturn: 26.73, uranus: 97.77, neptune: 28.32,
};

function calcTerminator(planet) {
  const now = new Date();
  const utcHour = now.getUTCHours() + now.getUTCMinutes() / 60;
  const start = new Date(now.getFullYear(), 0, 0);
  const dayOfYear = Math.round((now - start) / 86400000);
  const tilt = PLANET_AXIAL_TILTS[planet.id] || 0;
  // Subsolar longitude: at 12:00 UTC the sun is overhead the prime meridian
  const subsolLon = (utcHour - 12) * 15;
  // offsetFraction (-1..1): position of terminator across the visible disk
  const offsetFraction = Math.sin((subsolLon * Math.PI) / 180);
  // Season tilt: axial tilt leans the terminator line, max at solstices
  const seasonPhase = ((dayOfYear - 81) / 365) * 2 * Math.PI;
  const tiltLean = Math.sin(seasonPhase) * tilt * 0.35;
  return { offsetFraction, tiltLean };
}

function TerminatorOverlay({ planet, radius }) {
  const [term, setTerm] = useState(() => calcTerminator(planet));
  useEffect(() => {
    setTerm(calcTerminator(planet));
    const id = setInterval(() => setTerm(calcTerminator(planet)), 10000);
    return () => clearInterval(id);
  }, [planet.id]);
  if (planet.isStar) return null;
  const { offsetFraction, tiltLean } = term;
  const pct = Math.round(50 + offsetFraction * 48);
  const gradAngle = 90 + tiltLean;
  const size = radius * 2;
  return (
    <div
      aria-hidden="true"
      style={{
        position: "absolute",
        width: size,
        height: size,
        borderRadius: "50%",
        pointerEvents: "none",
        top: "50%",
        left: "50%",
        transform: "translate(-50%, -50%)",
        background: `linear-gradient(${gradAngle}deg, rgba(0,0,20,0.5) ${pct - 3}%, transparent ${pct + 3}%)`,
      }}
    />
  );
}

// ---------- Detail/zoom view ----------
function PlanetDetail({
  planet,
  favoritePlanetId,
  onClose,
  onPrev,
  onNext,
  onAstronautWave,
  onFavoriteToggle,
  onOpenMissionMap,
  onOpenMarsRover,
}) {
  const [vp, setVp] = useState({ w: window.innerWidth, h: window.innerHeight });
  const [showLifecycle, setShowLifecycle] = useState(false);
  const [planetBoop, setPlanetBoop] = useState(0);
  const [secretPlanetTapCount, setSecretPlanetTapCount] = useState(0);
  const [secretPlanetGiggle, setSecretPlanetGiggle] = useState(0);
  const [planetFlags, setPlanetFlags] = useState([]);
  const [earthWeight, setEarthWeight] = useState(() =>
    storedNumber("planets_earth_weight_lb", 60, 20, 300),
  );
  const [earthAge, setEarthAge] = useState(() =>
    storedNumber("planets_earth_age_years", 8, 1, 120),
  );
  const [marsRoverIndex, setMarsRoverIndex] = useState(0);
  const [linkReady, setLinkReady] = useState(false);
  const secretPlanetTimer = useRef(null);
  useEffect(() => {
    const onR = () => setVp({ w: window.innerWidth, h: window.innerHeight });
    window.addEventListener("resize", onR);
    return () => window.removeEventListener("resize", onR);
  }, []);
  // Pause planet narration while the lifecycle flipbook is open so the two
  // audio sources don't fight each other.
  useEffect(() => {
    if (showLifecycle && window.__narration) window.__narration.stop();
  }, [showLifecycle]);
  useEffect(() => {
    if (!planetBoop) return;
    const t = setTimeout(() => setPlanetBoop(0), 460);
    return () => clearTimeout(t);
  }, [planetBoop]);
  useEffect(() => {
    setMarsRoverIndex(0);
    setPlanetFlags([]);
    setSecretPlanetTapCount(0);
    setSecretPlanetGiggle(0);
    window.clearTimeout(secretPlanetTimer.current);
  }, [planet.id]);
  useEffect(
    () => () => {
      window.clearTimeout(secretPlanetTimer.current);
    },
    [],
  );
  // Opening a planet should begin its explainer right away; the replay button
  // below still lets a child hear it again.
  useEffect(() => {
    const clip = `${planet.id}.mp3`;
    if (hasNarrationClip(clip)) playNarration(clip);
    return () => {
      if (window.__narration) window.__narration.stop();
    };
  }, [planet.id]);
  useEffect(() => {
    const key = passportNudgeKey(planet);
    try {
      if (localStorage.getItem(key) === "1") return undefined;
      localStorage.setItem(key, "1");
    } catch (_) {}
    const timer = window.setTimeout(() => {
      if (hasNarrationClip("passport_mission_nudge.mp3")) {
        playNarration("passport_mission_nudge.mp3");
      }
    }, 4200);
    return () => window.clearTimeout(timer);
  }, [planet.id]);
  useEffect(() => {
    if (!linkReady) return undefined;
    const timer = window.setTimeout(() => setLinkReady(false), 1800);
    return () => window.clearTimeout(timer);
  }, [linkReady]);
  useEffect(() => {
    try {
      localStorage.setItem("planets_earth_weight_lb", String(earthWeight));
    } catch {}
  }, [earthWeight]);
  useEffect(() => {
    try {
      localStorage.setItem("planets_earth_age_years", String(earthAge));
    } catch {}
  }, [earthAge]);
  const infoReserve = vp.w > 900 ? 460 : 0;
  const maxByW = (vp.w - infoReserve - 80) * 0.55;
  const maxByH = vp.h * 0.42;
  const fallbackDetailRadius = Math.max(220, (Number(planet.radius) || 12) * 18);
  const detailRadius = Number.isFinite(Number(planet.detailRadius))
    ? Number(planet.detailRadius)
    : fallbackDetailRadius;
  const radius = Math.max(120, Math.min(detailRadius, maxByW, maxByH));
  const gravityRatio = planetGravityRatio(planet);
  const planetWeight =
    gravityRatio === null ? null : Math.max(0, Math.round(earthWeight * gravityRatio));
  const jumpHeight =
    gravityRatio === null ? null : Math.max(0.1, 1 / gravityRatio).toFixed(1);
  const yearDays = planetYearInEarthDays(planet.year);
  const worldAge =
    yearDays === null ? null : Math.max(0, (earthAge * 365.25) / yearDays);
  const isFavorite = favoritePlanetId === planet.id;
  const isSolarSystemWorld = PLANETS.some((world) => world.id === planet.id);
  const blurbDistance = blurbMatch(planet, /([0-9.]+ AU)/i);
  const blurbPeriod = blurbMatch(planet, /period ([0-9.]+ days)/i);
  const distanceAu = planetDistanceAu(planet) || planetDistanceAu({ distance: blurbDistance });
  const distanceValue = detailValue(planet.distance, blurbDistance) || "Unknown";
  const yearValue =
    detailValue(planet.year, blurbPeriod) || "Unknown";
  const displayName = planetDisplayName(planet);
  const plantPlanetFlag = () => {
    setPlanetFlags((flags) => [
      ...flags.slice(-4),
      PLANET_FLAG_COLORS[flags.length % PLANET_FLAG_COLORS.length],
    ]);
    playKidSound("chime");
  };
  const tapSecretPlanet = () => {
    setPlanetBoop((n) => n + 1);
    setSecretPlanetTapCount((current) => {
      const next = current + 1;
      if (next >= 3) {
        window.clearTimeout(secretPlanetTimer.current);
        setSecretPlanetGiggle((n) => n + 1);
        playKidSound("giggle");
        secretPlanetTimer.current = window.setTimeout(() => {
          setSecretPlanetGiggle(0);
        }, 1500);
        return 0;
      }
      playKidSound("boop");
      return next;
    });
  };

  return (
    <div
      className={`detail-view ${planet.isStar ? "sun-detail-view" : ""}`}
      data-narrow={vp.w <= 900 ? "1" : "0"}
    >
      <div
        className="detail-bg"
        style={{
          background: planet.isStar
            ? "transparent"
            : `radial-gradient(circle at 30% 50%, ${planet.glowColor || planet.color}22 0%, transparent 60%)`,
        }}
      />
      <div
        className={`detail-planet-wrap ${planet.isStar ? "sun-wrap" : ""} ${planetBoop ? "boop" : ""} ${secretPlanetGiggle ? "secret-giggle" : ""}`}
        style={{ width: radius * 2.4, height: radius * 2.4 }}
        onClick={tapSecretPlanet}
      >
        <Planet3D
          planet={planet}
          detail={true}
          allowOrbit={true}
          className="planet-canvas-detail"
        />
        {planet.isStar ? (
          <button
            className="lifecycle-launch"
            onClick={() => setShowLifecycle(true)}
            title="See how the Sun ages"
          >
            ☀ Sun's life
          </button>
        ) : null}
        {/* AGENT_TARGET: planet-terminator — real-time day/night terminator */}
        {!planet.isStar && (
          <TerminatorOverlay planet={planet} radius={radius} />
        )}
        {/* AGENT_TARGET: star-spectrum — spectrum overlay button on star detail */}
        {planet.isStar && typeof StarSpectrum !== "undefined" ? (
          <StarSpectrum planet={planet} />
        ) : null}
        {planetFlags.map((color, index) => (
          <span
            key={`${color}-${index}`}
            className={`planet-detail-flag flag-${index}`}
            style={{ "--flag-color": color }}
            aria-hidden="true"
          />
        ))}
        {secretPlanetGiggle ? (
          <div className="planet-secret-sparkles" aria-hidden="true">
            {Array.from({ length: 6 }, (_, index) => (
              <SafeAssetImage
                key={`${secretPlanetGiggle}-${index}`}
                className={`planet-secret-sparkle sparkle-${index}`}
                src={GENERATED_ASSETS.sparkleStar}
                alt=""
                context={`PlanetDetail:secretSparkle:${planet.id}:${index}`}
                fallbackText=""
              />
            ))}
          </div>
        ) : null}
      </div>

      <div className="detail-info">
        <div className="detail-eyebrow">
          {detailValue(planet.type, "World").toUpperCase()}
        </div>
        <h1 className="detail-name">{displayName}</h1>
        {isFavorite ? (
          <div className="detail-favorite-badge">★ Favorite world</div>
        ) : null}
        {KID_I_SPY[planet.id] ? (
          <div className="kid-i-spy">{KID_I_SPY[planet.id]}</div>
        ) : null}
        <p className="detail-blurb">{planet.blurb}</p>
        <div className="detail-passport-card">
          <span>Passport stop</span>
          <strong>1. You studied {displayName} from your ship.</strong>
          <p>2. Play a mission. 3. Tell one thing you remember.</p>
          <button className="btn-tour" onClick={onOpenMissionMap}>
            <span className="mission-arrow" aria-hidden="true">
              ➜
            </span>
            Play a mission
          </button>
        </div>
        <div className="stat-grid">
          <Stat
            label={
              isSolarSystemWorld ? "Distance from Sun" : "Distance from star"
            }
            value={distanceValue}
          />
          <Stat label="Day length" value={detailValue(planet.day)} />
          <Stat label="Year length" value={yearValue} />
          <Stat label="Moons" value={detailValue(planet.moons)} />
          <Stat label="Surface temp" value={detailValue(planet.temp)} />
          <Stat label="Gravity" value={detailValue(planet.gravity)} />
          <Stat
            label="Sunlight trip"
            value={
              distanceAu ? sunlightTravelTime(`${distanceAu} AU`) : "Unknown"
            }
          />
        </div>
        <div className="detail-composition">
          <div className="comp-label">COMPOSITION</div>
          <div className="comp-value">
            {detailValue(planet.composition, "Still being studied.")}
          </div>
        </div>
        {!planet.isStar && distanceAu ? (
          <SunlightTripCard planet={planet} distanceAu={distanceAu} />
        ) : null}
        {planet.id === "mars" ? (
          <MarsRoverSpotter
            activeIndex={marsRoverIndex}
            onOpenMarsRover={onOpenMarsRover}
            onChoose={(index) => {
              setMarsRoverIndex(index);
              playKidSound("chime");
            }}
          />
        ) : null}
        <div
          className="gravity-play-card"
          style={{ "--planet-color": planet.color }}
        >
          <div className="gravity-play-copy">
            <div className="gravity-play-label">GRAVITY FEEL</div>
            <strong>
              {planetWeight === null
                ? "Try another world"
                : `${earthWeight} lb on Earth feels like ${planetWeight} lb on ${displayName}.`}
            </strong>
            <span>{gravityFeelingCopy(gravityRatio)}</span>
          </div>
          <label className="gravity-weight-control">
            <span>Earth weight</span>
            <input
              type="number"
              min="20"
              max="300"
              step="5"
              value={earthWeight}
              onChange={(event) => {
                const next = Number(event.target.value);
                if (!Number.isFinite(next)) return;
                setEarthWeight(Math.max(20, Math.min(300, next)));
              }}
            />
            <small>lb</small>
          </label>
          {jumpHeight ? (
            <div
              className="gravity-jump-meter"
              aria-label={`Jump feels ${jumpHeight} times Earth height`}
            >
              <span
                style={{ height: `${Math.min(100, 18 + jumpHeight * 16)}%` }}
              />
              <b>{jumpHeight}x jump</b>
            </div>
          ) : null}
        </div>
        <div className="age-play-card">
          <div className="age-play-copy">
            <div className="gravity-play-label">PLANET AGE</div>
            <strong>
              {worldAge === null
                ? `${planet.name} does not have a planet birthday.`
                : `${earthAge} Earth years is ${worldAge.toFixed(worldAge < 10 ? 1 : 0)} ${displayName} years.`}
            </strong>
          </div>
          <label className="gravity-weight-control age-control">
            <span>Earth age</span>
            <input
              type="number"
              min="1"
              max="120"
              step="1"
              value={earthAge}
              onChange={(event) => {
                const next = Number(event.target.value);
                if (!Number.isFinite(next)) return;
                setEarthAge(Math.max(1, Math.min(120, next)));
              }}
            />
            <small>yr</small>
          </label>
        </div>
        {/* AGENT_TARGET: gravity-drop-game — gravity drop mini-game card in planet detail */}
        {gravityRatio !== null && !planet.isStar && (
          <GravityDropGame planet={planet} gravityRatio={gravityRatio} />
        )}
        {/* AGENT_TARGET: gravity-jump-tester — tap-to-jump astronaut in planet detail */}
        {gravityRatio !== null && !planet.isStar && (
          <GravityJumpTester planet={planet} gravityRatio={gravityRatio} />
        )}
        {/* AGENT_TARGET: planet-thermo-card — surface temperature thermometer in planet detail */}
        {!planet.isStar && <PlanetThermoCard planet={planet} />}
        <div className="detail-actions">
          <button
            className="btn-tour"
            aria-label={`Play ${displayName} narration`}
            onClick={() =>
              window.__narration && window.__narration.play(planet.id + ".mp3")
            }
          >
            🔊 Play narration
          </button>
          {/* AGENT_TARGET: say-name-slow — pronunciation button for early readers */}
          <button
            className="btn-tour"
            aria-label={`Say ${displayName} name slowly`}
            onClick={() => speakNameSlow(displayName)}
          >
            🔤 Say my name
          </button>
          <button
            className={`btn-tour ${linkReady ? "on" : ""}`}
            aria-label={
              linkReady
                ? `${displayName} link ready`
                : `Copy ${displayName} link`
            }
            onClick={() => {
              copyPlanetLink(planet);
              setLinkReady(true);
            }}
          >
            {linkReady ? "Link ready" : "Copy link"}
          </button>
          <button
            className="btn-tour"
            aria-label="Make the astronaut wave"
            onClick={() => {
              playKidSound("giggle");
              onAstronautWave();
            }}
          >
            👋 Astronaut wave
          </button>
          <button
            className={`btn-tour ${isFavorite ? "on" : ""}`}
            aria-pressed={isFavorite ? "true" : "false"}
            aria-label={
              isFavorite
                ? `Remove ${displayName} as favorite`
                : `Set ${displayName} as favorite`
            }
            onClick={() => {
              onFavoriteToggle(isFavorite ? "" : planet.id);
              playKidSound("chime");
            }}
          >
            {isFavorite ? "★ Favorite set" : "☆ Set favorite"}
          </button>
          {!planet.isStar ? (
            <button className="btn-tour" onClick={plantPlanetFlag}>
              ⚑ Plant flag
            </button>
          ) : null}
        </div>
        <div className="detail-hint">
          tap to boop · drag to rotate · scroll to zoom
        </div>
      </div>

      <button className="detail-close" onClick={onClose}>
        <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
          <path
            d="M11 3L3 11M3 3l8 8"
            stroke="currentColor"
            strokeWidth="1.6"
            strokeLinecap="round"
          />
        </svg>
        <span>Back to system</span>
      </button>
      <button className="detail-nav prev" onClick={onPrev}>
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
          <path
            d="M12 4L6 10l6 6"
            stroke="currentColor"
            strokeWidth="1.6"
            strokeLinecap="round"
            strokeLinejoin="round"
          />
        </svg>
      </button>
      <button className="detail-nav next" onClick={onNext}>
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
          <path
            d="M8 4l6 6-6 6"
            stroke="currentColor"
            strokeWidth="1.6"
            strokeLinecap="round"
            strokeLinejoin="round"
          />
        </svg>
      </button>
      {showLifecycle ? (
        <SunLifecycleFlipbook onClose={() => setShowLifecycle(false)} />
      ) : null}
    </div>
  );
}

// AGENT_TARGET: planet-thermo-card — known mean surface temps in °C per planet id
const PLANET_SURFACE_TEMPS_C = {
  mercury: 167,
  venus: 465,
  earth: 15,
  mars: -60,
  jupiter: -110,
  saturn: -178,
  uranus: -224,
  neptune: -218,
};

function thermoComparison(tempC) {
  if (tempC > 1000) return "Hotter than volcanic lava!";
  if (tempC > 300) return "Hot enough to melt lead";
  if (tempC > 100) return "Hotter than boiling water";
  if (tempC > 37) return "Hotter than your own body";
  if (tempC > 0) return "A chilly spring day";
  if (tempC > -20) return "Colder than a deep freezer";
  if (tempC > -100) return "Colder than the South Pole";
  if (tempC > -200) return "Colder than liquid nitrogen";
  return "Near absolute zero — almost no heat at all";
}

function tempFillColor(frac) {
  const r = Math.round(30 + frac * 225);
  const g = Math.round(110 - frac * 60);
  const b = Math.round(220 - frac * 200);
  return `rgb(${r},${g},${b})`;
}

// AGENT_TARGET: planet-thermo-card — PlanetThermoCard component
function PlanetThermoCard({ planet }) {
  const TEMP_MIN = -230;
  const TEMP_MAX = 600;
  const realTemp = PLANET_SURFACE_TEMPS_C[planet.id];
  const [dragTemp, setDragTemp] = useState(null);
  const [dragging, setDragging] = useState(false);
  const tubeRef = useRef(null);

  if (realTemp === undefined) return null;

  const displayTemp = dragTemp !== null ? dragTemp : realTemp;
  const fraction = Math.max(0, Math.min(1, (displayTemp - TEMP_MIN) / (TEMP_MAX - TEMP_MIN)));
  const realFraction = Math.max(0, Math.min(1, (realTemp - TEMP_MIN) / (TEMP_MAX - TEMP_MIN)));

  function tempFromPointer(clientY) {
    if (!tubeRef.current) return realTemp;
    const rect = tubeRef.current.getBoundingClientRect();
    const frac = 1 - Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
    return Math.round(TEMP_MIN + frac * (TEMP_MAX - TEMP_MIN));
  }

  function handlePointerDown(e) {
    setDragging(true);
    setDragTemp(tempFromPointer(e.clientY));
    e.currentTarget.setPointerCapture(e.pointerId);
  }
  function handlePointerMove(e) {
    if (!dragging) return;
    setDragTemp(tempFromPointer(e.clientY));
  }
  function handlePointerUp() {
    setDragging(false);
    setDragTemp(null);
  }

  const tempLabel = displayTemp > 0 ? `+${displayTemp}` : String(displayTemp);
  const isExploring = dragTemp !== null;

  return (
    <div className="planet-thermo-card" style={{ "--planet-color": planet.color || "#5599ff" }}>
      <div className="gravity-play-label">SURFACE TEMP</div>
      <div className="planet-thermo-layout">
        <div
          ref={tubeRef}
          className="planet-thermo-tube"
          onPointerDown={handlePointerDown}
          onPointerMove={handlePointerMove}
          onPointerUp={handlePointerUp}
          onPointerCancel={handlePointerUp}
          role="slider"
          aria-valuenow={displayTemp}
          aria-valuemin={TEMP_MIN}
          aria-valuemax={TEMP_MAX}
          aria-label={`Temperature: ${displayTemp}°C`}
        >
          <div
            className="planet-thermo-fill"
            style={{
              height: `${fraction * 100}%`,
              background: `linear-gradient(to top, ${tempFillColor(0)}, ${tempFillColor(fraction)})`,
            }}
          />
          <div
            className="planet-thermo-marker"
            style={{
              bottom: `${realFraction * 100}%`,
              borderColor: tempFillColor(realFraction),
            }}
          />
        </div>
        <div className="planet-thermo-info">
          <div className="planet-thermo-temp" style={{ color: tempFillColor(fraction) }}>
            {tempLabel}°C
          </div>
          {isExploring && (
            <div className="planet-thermo-real">
              real: {realTemp > 0 ? `+${realTemp}` : realTemp}°C
            </div>
          )}
          <div className="planet-thermo-comparison">{thermoComparison(displayTemp)}</div>
          {!isExploring && (
            <div className="planet-thermo-hint">drag to explore</div>
          )}
        </div>
      </div>
    </div>
  );
}

function Stat({ label, value }) {
  return (
    <div className="stat">
      <div className="stat-label">{label}</div>
      <div className="stat-value">{value}</div>
    </div>
  );
}

function MarsRoverSpotter({ activeIndex, onChoose, onOpenMarsRover }) {
  const activeRover =
    MARS_ROVER_SIGHTINGS[activeIndex] || MARS_ROVER_SIGHTINGS[0];

  return (
    <div className="mars-rover-spotter">
      <div className="gravity-play-copy">
        <div className="gravity-play-label">WHERE'S THE ROVER?</div>
        <strong>
          {activeRover.name} is exploring {activeRover.place}.
        </strong>
        <span>{activeRover.note}</span>
      </div>
      <div className="mars-rover-map" aria-label="Mars rover map">
        {MARS_ROVER_SIGHTINGS.map((rover, index) => (
          <button
            key={rover.name}
            className={`mars-rover-pin ${index === activeIndex ? "on" : ""}`}
            type="button"
            aria-pressed={index === activeIndex ? "true" : "false"}
            aria-label={`Find ${rover.name} at ${rover.place}`}
            onClick={() => onChoose(index)}
          >
            <span>{rover.marker}</span>
            <b>{rover.name}</b>
          </button>
        ))}
      </div>
      {typeof onOpenMarsRover === "function" ? (
        <button
          className="mars-rover-drive"
          type="button"
          onClick={onOpenMarsRover}
        >
          Drive Mars Rover
        </button>
      ) : null}
    </div>
  );
}

function SunlightTripCard({ planet, distanceAu }) {
  const trip = sunlightTravelTime(`${distanceAu} AU`);
  const displayName = planetDisplayName(planet);
  const beamWidth = Math.max(12, Math.min(100, 14 + distanceAu * 12));

  return (
    <div className="sunlight-trip-card">
      <div className="gravity-play-copy">
        <div className="gravity-play-label">SUNLIGHT TRIP</div>
        <strong>
          Sunlight reaches {displayName} in {trip}.
        </strong>
        <span>Light is the fastest messenger in the solar system.</span>
      </div>
      <div className="sunlight-trip-meter" aria-hidden="true">
        <span style={{ width: `${beamWidth}%` }} />
      </div>
    </div>
  );
}

// ---------- App ----------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/ {
  tilt: 18,
  orbitOpacity: 0.7,
  showLabels: true,
  autoOrbit: true,
  starDensity: 400,
}; /*EDITMODE-END*/

function detailCenterFor() {
  const narrow = window.innerWidth <= 900;
  return {
    x: window.innerWidth * (narrow ? 0.5 : 0.32),
    y: window.innerHeight * (narrow ? 0.38 : 0.5),
    radius: Math.min(window.innerWidth, window.innerHeight) * 0.28,
  };
}
