function Planet3D({ planet, detail, allowOrbit, className, style }) {
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current) return;
    const dispose = renderPlanet3D(ref.current, planet, {
      detail,
      allowOrbit,
      autoRotate: true,
    });
    return dispose;
  }, [planet.id, detail, allowOrbit]);
  return <div ref={ref} className={className} style={style} />;
}

// ---------- Stars background (DOM stars + nebula) ----------
function Starfield({ count = 400 }) {
  const stars = useRef(null);
  if (!stars.current) {
    const compactCount =
      typeof window !== "undefined" && window.innerWidth < 700
        ? Math.min(count, 260)
        : count;
    const arr = [];
    for (let i = 0; i < compactCount; i++) {
      arr.push({
        x: Math.random() * 100,
        y: Math.random() * 100,
        size: Math.random() * 1.6 + 0.3,
        opacity: Math.random() * 0.7 + 0.3,
        twinkle: Math.random() * 4 + 2,
        delay: Math.random() * 4,
        color:
          Math.random() > 0.92
            ? "#FFE08A"
            : Math.random() > 0.85
              ? "#A8C8FF"
              : "#fff",
      });
    }
    arr.nebulae = [
      { x: 12, y: 18, color: "#3a1a5a", size: 380, opacity: 0.18 },
      { x: 78, y: 72, color: "#1a3a5a", size: 460, opacity: 0.22 },
      { x: 88, y: 12, color: "#5a1a3a", size: 280, opacity: 0.14 },
    ];
    stars.current = arr;
  }
  return (
    <div className="starfield">
      {stars.current.nebulae.map((n, i) => (
        <div
          key={"n" + i}
          className="nebula"
          style={{
            left: `${n.x}%`,
            top: `${n.y}%`,
            width: n.size,
            height: n.size,
            background: `radial-gradient(circle, ${n.color} 0%, transparent 70%)`,
            opacity: n.opacity,
          }}
        />
      ))}
      {stars.current.map((s, i) => (
        <div
          key={i}
          className="star"
          style={{
            left: `${s.x}%`,
            top: `${s.y}%`,
            width: s.size,
            height: s.size,
            background: s.color,
            opacity: s.opacity,
            animationDuration: `${s.twinkle}s`,
            animationDelay: `${s.delay}s`,
            boxShadow: s.size > 1.2 ? `0 0 ${s.size * 2}px ${s.color}` : "none",
          }}
        />
      ))}
    </div>
  );
}

// ---------- Overview ----------
function SolarOverview({
  onSelect,
  paused,
  tilt,
  orbitOpacity,
  scale,
  panX,
  panY,
  onPan,
  onZoom,
  rocketOrbiting,
  rocketColor,
  onResetRocket,
  planetDance,
  orbitSpeed,
}) {
  const dragRef = useRef({ active: false, sx: 0, sy: 0, ox: 0, oy: 0 });
  const [rocketControlled, setRocketControlled] = useState(false);
  const [controlledRocket, setControlledRocket] = useState({
    x: 40,
    y: -90,
    angle: 28,
  });
  // Pinch-zoom: track multiple active pointers and the initial pinch distance.
  const pointersRef = useRef(new Map());
  const pinchRef = useRef({ active: false, startDist: 0, startScale: 1 });

  const flyControlledRocket = (turnDegrees, thrust) => {
    setControlledRocket((current) => {
      const angle = current.angle + turnDegrees;
      const radians = (angle * Math.PI) / 180;
      const nextX = Math.max(
        -260,
        Math.min(260, current.x + Math.sin(radians) * thrust),
      );
      const nextY = Math.max(
        -260,
        Math.min(360, current.y - Math.cos(radians) * thrust),
      );
      return { x: nextX, y: nextY, angle };
    });
  };

  useEffect(() => {
    if (!rocketOrbiting) {
      setRocketControlled(false);
      return undefined;
    }
    if (!rocketControlled) return undefined;
    const onKey = (event) => {
      const turn = event.repeat ? 8 : 12;
      const thrust = event.repeat ? 16 : 23;
      const arcThrust = event.repeat ? 8 : 11;
      if (event.key === "ArrowLeft") {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation?.();
        flyControlledRocket(-turn, arcThrust);
      } else if (event.key === "ArrowRight") {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation?.();
        flyControlledRocket(turn, arcThrust);
      } else if (event.key === "ArrowUp") {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation?.();
        flyControlledRocket(0, thrust);
      } else if (event.key === "ArrowDown") {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation?.();
        flyControlledRocket(0, -Math.round(thrust * 0.55));
      }
    };
    window.addEventListener("keydown", onKey, true);
    return () => window.removeEventListener("keydown", onKey, true);
  }, [rocketOrbiting, rocketControlled]);

  useEffect(() => {
    document.body.classList.toggle(
      "rocket-control-active",
      rocketOrbiting && rocketControlled,
    );
    return () => document.body.classList.remove("rocket-control-active");
  }, [rocketOrbiting, rocketControlled]);

  const handlePointerDown = (e) => {
    if (e.target.closest("[data-planet]")) return;
    pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
    if (pointersRef.current.size === 1) {
      dragRef.current = {
        active: true,
        sx: e.clientX,
        sy: e.clientY,
        ox: panX,
        oy: panY,
      };
      try {
        e.currentTarget.setPointerCapture(e.pointerId);
      } catch {}
    } else if (pointersRef.current.size === 2) {
      // Second finger down: start a pinch, suspend drag.
      dragRef.current.active = false;
      const pts = [...pointersRef.current.values()];
      const dx = pts[0].x - pts[1].x;
      const dy = pts[0].y - pts[1].y;
      pinchRef.current = {
        active: true,
        startDist: Math.hypot(dx, dy) || 1,
        startScale: scale,
      };
    }
  };
  const handlePointerMove = (e) => {
    if (pointersRef.current.has(e.pointerId)) {
      pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
    }
    if (pinchRef.current.active && pointersRef.current.size === 2) {
      const pts = [...pointersRef.current.values()];
      const dx = pts[0].x - pts[1].x;
      const dy = pts[0].y - pts[1].y;
      const d = Math.hypot(dx, dy);
      if (d > 0)
        onZoom(pinchRef.current.startScale * (d / pinchRef.current.startDist));
      return;
    }
    if (!dragRef.current.active) return;
    onPan(
      dragRef.current.ox + (e.clientX - dragRef.current.sx),
      dragRef.current.oy + (e.clientY - dragRef.current.sy),
    );
  };
  const handlePointerUp = (e) => {
    pointersRef.current.delete(e.pointerId);
    if (pointersRef.current.size < 2) pinchRef.current.active = false;
    if (pointersRef.current.size === 0) dragRef.current.active = false;
    try {
      e.currentTarget.releasePointerCapture(e.pointerId);
    } catch {}
  };
  const handleWheel = (e) => {
    e.preventDefault();
    onZoom(scale * (1 - e.deltaY * 0.001));
  };

  return (
    <div
      className="overview-canvas"
      onPointerDown={handlePointerDown}
      onPointerMove={handlePointerMove}
      onPointerUp={handlePointerUp}
      onPointerCancel={handlePointerUp}
      onWheel={handleWheel}
      style={{ touchAction: "none" }}
    >
      <div
        className="overview-stage"
        style={{
          transform: `translate(${panX}px, ${panY}px) scale(${scale}) rotateX(${tilt}deg)`,
        }}
      >
        <svg
          className="orbits"
          width="1400"
          height="1400"
          viewBox="-700 -700 1400 1400"
        >
          {PLANETS.filter((p) => p.orbitRadius > 0).map((p) => (
            <ellipse
              key={p.id}
              cx="0"
              cy="0"
              rx={p.orbitRadius}
              ry={p.orbitRadius}
              fill="none"
              stroke="rgba(180,200,255,0.18)"
              strokeWidth="0.6"
              opacity={orbitOpacity}
            />
          ))}
        </svg>
        {PLANETS.map((p) => (
          <OrbitalPlanet
            key={p.id}
            planet={p}
            paused={paused}
            dancing={planetDance}
            orbitSpeed={orbitSpeed}
            onClick={(coords) => onSelect(p, coords)}
          />
        ))}
        {rocketOrbiting && !rocketControlled ? (
          <SolarRocketOrbit paused={paused} color={rocketColor} />
        ) : null}
        {rocketOrbiting && rocketControlled ? (
          <ControlledSolarRocket
            color={rocketColor}
            x={controlledRocket.x}
            y={controlledRocket.y}
            angle={controlledRocket.angle}
          />
        ) : null}
      </div>
      {rocketOrbiting ? (
        <div className="solar-rocket-actions">
          <button
            className="solar-rocket-control"
            aria-pressed={rocketControlled}
            onPointerDown={(event) => {
              event.preventDefault();
              event.stopPropagation();
            }}
            onClick={(event) => {
              event.preventDefault();
              event.stopPropagation();
              setRocketControlled((current) => {
                if (!current) setControlledRocket({ x: 40, y: -90, angle: 28 });
                return !current;
              });
            }}
          >
            {rocketControlled ? "Orbit rocket" : "Control rocket"}
          </button>
          {rocketControlled ? (
            <span className="solar-rocket-key-hint">Arrow keys fly</span>
          ) : null}
          <button
            className="solar-rocket-reset"
            onPointerDown={(event) => {
              event.preventDefault();
              event.stopPropagation();
            }}
            onClick={(event) => {
              event.preventDefault();
              event.stopPropagation();
              try {
                localStorage.setItem("planets_solar_rocket_orbiting", "0");
              } catch {}
              onResetRocket();
            }}
          >
            Reset rocket
          </button>
        </div>
      ) : null}
    </div>
  );
}

function RocketSpriteImages() {
  return (
    <>
      <SafeAssetImage
        className="solar-rocket-booster"
        src={GENERATED_ASSETS.rocketBooster}
        alt=""
        context="SolarRocket:booster"
        fallbackText=""
      />
      <SafeAssetImage
        className="solar-rocket-nose"
        src={GENERATED_ASSETS.rocketNose}
        alt=""
        context="SolarRocket:nose"
        fallbackText=""
      />
      <SafeAssetImage
        className="solar-rocket-body"
        src={GENERATED_ASSETS.rocketBody}
        alt=""
        context="SolarRocket:body"
        fallbackText=""
      />
      <SafeAssetImage
        className="solar-rocket-fins"
        src={GENERATED_ASSETS.rocketFins}
        alt=""
        context="SolarRocket:fins"
        fallbackText=""
      />
      <SafeAssetImage
        className="solar-rocket-fire"
        src={GENERATED_ASSETS.rocketFire}
        alt=""
        context="SolarRocket:fire"
        fallbackText=""
      />
    </>
  );
}

function SolarRocketOrbit({ paused, color }) {
  return (
    <div
      className={`solar-rocket-orbit color-${color || "red"}`}
      style={{ animationPlayState: paused ? "paused" : "running" }}
      aria-hidden="true"
    >
      <div className="solar-rocket-sprite">
        <RocketSpriteImages />
      </div>
    </div>
  );
}

function ControlledSolarRocket({ color, x, y, angle }) {
  return (
    <div
      className={`controlled-solar-rocket color-${color || "red"}`}
      style={{
        transform: `translate(${x}px, ${y}px) rotate(${angle}deg)`,
      }}
      aria-hidden="true"
    >
      <div className="solar-rocket-sprite">
        <RocketSpriteImages />
      </div>
    </div>
  );
}

function OrbitalPlanet({ planet, paused, dancing, orbitSpeed, onClick }) {
  const pressRef = useRef(null);
  const [pressed, setPressed] = useState(false);
  const beginPress = (e) => {
    e.stopPropagation();
    const r = e.currentTarget.getBoundingClientRect();
    pressRef.current = {
      x: e.clientX,
      y: e.clientY,
      moved: false,
      center: { x: r.left + r.width / 2, y: r.top + r.height / 2 },
    };
    setPressed(true);
  };
  const movePress = (e) => {
    if (!pressRef.current) return;
    const dx = e.clientX - pressRef.current.x;
    const dy = e.clientY - pressRef.current.y;
    if (Math.hypot(dx, dy) > 8) pressRef.current.moved = true;
  };
  const endPress = () => {
    setPressed(false);
    pressRef.current = null;
  };
  const choosePlanet = (e) => {
    e.stopPropagation();
    if (pressRef.current && pressRef.current.moved) {
      pressRef.current = null;
      return;
    }
    const r = e.currentTarget.getBoundingClientRect();
    const coords = pressRef.current
      ? pressRef.current.center
      : { x: r.left + r.width / 2, y: r.top + r.height / 2 };
    pressRef.current = null;
    onClick(coords);
  };
  const orbitStyle = {
    width: planet.orbitRadius * 2,
    height: planet.orbitRadius * 2,
    marginLeft: -planet.orbitRadius,
    marginTop: -planet.orbitRadius,
    animationDuration:
      planet.orbitRadius === 0
        ? "0s"
        : `${(planet.period * 2) / Math.max(0.2, orbitSpeed || 1)}s`,
    animationPlayState: paused || pressed ? "paused" : "running",
  };
  const planetWrap = {
    width: planet.radius * 2,
    height: planet.radius * 2,
    marginLeft: -planet.radius,
    marginTop: -planet.radius,
  };

  if (planet.orbitRadius === 0) {
    return (
      <div className="sun-anchor">
        <div
          className={`planet-marker ${pressed ? "planet-pressed" : ""}`}
          data-planet={planet.id}
          onPointerDown={beginPress}
          onPointerMove={movePress}
          onPointerUp={endPress}
          onPointerCancel={endPress}
          onClick={choosePlanet}
          style={{ width: planet.radius * 2, height: planet.radius * 2 }}
        >
          <Planet3D
            planet={planet}
            detail={false}
            allowOrbit={false}
            className={`planet-canvas ${dancing ? "planet-dance-canvas" : ""}`}
          />
          <div className="planet-label">{planet.name}</div>
        </div>
      </div>
    );
  }

  return (
    <div className="orbit-ring" style={orbitStyle}>
      <div className="orbit-anchor" style={planetWrap}>
        <div
          className={`planet-marker ${pressed ? "planet-pressed" : ""}`}
          data-planet={planet.id}
          onPointerDown={beginPress}
          onPointerMove={movePress}
          onPointerUp={endPress}
          onPointerCancel={endPress}
          onClick={choosePlanet}
          style={{ width: planet.radius * 2, height: planet.radius * 2 }}
        >
          <Planet3D
            planet={planet}
            detail={false}
            allowOrbit={false}
            className={`planet-canvas ${dancing ? "planet-dance-canvas" : ""}`}
          />
          <div className="planet-label">{planet.name}</div>
        </div>
      </div>
    </div>
  );
}

// ---------- Astronaut flight ----------
// 12-frame sprite (4 cols × 3 rows, 126×90 each), three phases:
//   "flying"    — lerp from click point to the detail-planet center
//   "orbiting"  — elliptical orbit around the detail-planet center
//   "departing" — keep orbiting while fading out
function DraggableAstronaut({
  position,
  onPositionChange,
  onPlanetDrop,
  helmetSticker,
  stickerPlacement,
  suitColor,
  astronautName,
}) {
  const [dragging, setDragging] = useState(false);
  const [keyboardFlying, setKeyboardFlying] = useState(false);
  const [visual, setVisual] = useState(position);
  const [angle, setAngle] = useState(-14);
  const [idleFrame, setIdleFrame] = useState(0);
  const [overPlanet, setOverPlanet] = useState(null);
  const dragRef = useRef({
    dx: 0,
    dy: 0,
    moved: false,
    target: position,
    visual: position,
    last: position,
  });
  const selectedSticker =
    HELMET_STICKERS.find((s) => s.id === helmetSticker) || HELMET_STICKERS[0];
  const suit = currentSuit(suitColor);

  useEffect(() => {
    dragRef.current.target = position;
  }, [position.x, position.y]);

  useEffect(() => {
    const keys = new Set();
    let raf;
    let last = performance.now();
    const step = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000 || 0.016);
      last = now;
      if (keys.size) {
        let dx = 0;
        let dy = 0;
        if (keys.has("arrowleft")) dx -= 1;
        if (keys.has("arrowright")) dx += 1;
        if (keys.has("arrowup")) dy -= 1;
        if (keys.has("arrowdown")) dy += 1;
        const len = Math.max(1, Math.hypot(dx, dy));
        const next = clamp(
          dragRef.current.target.x + (dx / len) * 230 * dt,
          dragRef.current.target.y + (dy / len) * 230 * dt,
        );
        dragRef.current.target = next;
        onPositionChange(next);
        const hovered = document
          .elementsFromPoint(next.x, next.y)
          .find((el) => el.dataset && el.dataset.planet);
        setOverPlanet(hovered ? hovered.dataset.planet : null);
      }
      raf = requestAnimationFrame(step);
    };
    const onDown = (event) => {
      if (
        !["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)
      )
        return;
      if (document.querySelector(".detail-view")) return;
      event.preventDefault();
      keys.add(event.key.toLowerCase());
      setKeyboardFlying(true);
    };
    const onUp = (event) => {
      keys.delete(event.key.toLowerCase());
      if (!keys.size) {
        setKeyboardFlying(false);
        setOverPlanet(null);
      }
    };
    window.addEventListener("keydown", onDown);
    window.addEventListener("keyup", onUp);
    raf = requestAnimationFrame(step);
    return () => {
      window.removeEventListener("keydown", onDown);
      window.removeEventListener("keyup", onUp);
      cancelAnimationFrame(raf);
    };
  }, [onPositionChange]);

  useEffect(() => {
    let raf;
    const born = performance.now();
    const tick = () => {
      const now = performance.now();
      if (!dragging) {
        const floatTarget = {
          x: position.x + Math.sin((now - born) / 2200) * 10,
          y: position.y + Math.cos((now - born) / 2700) * 8,
        };
        dragRef.current.target = floatTarget;
        setIdleFrame(Math.floor(now / 620) % 4);
      }
      const current = dragRef.current.visual;
      const target = dragRef.current.target;
      const ease = dragging || keyboardFlying ? 0.34 : 0.2;
      const next = {
        x: current.x + (target.x - current.x) * ease,
        y: current.y + (target.y - current.y) * ease,
      };
      const dx = next.x - dragRef.current.last.x;
      const dy = next.y - dragRef.current.last.y;
      if (Math.hypot(dx, dy) > 0.15) {
        setAngle((Math.atan2(dy, dx) * 180) / Math.PI);
      }
      dragRef.current.visual = next;
      dragRef.current.last = next;
      setVisual(next);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [dragging, position.x, position.y]);

  const clamp = (x, y) => ({
    x: Math.max(58, Math.min(window.innerWidth - 58, x)),
    y: Math.max(88, Math.min(window.innerHeight - 58, y)),
  });

  const moveTo = (clientX, clientY) => {
    const next = clamp(
      clientX - dragRef.current.dx,
      clientY - dragRef.current.dy,
    );
    dragRef.current.target = next;
    onPositionChange(next);
    const hovered = document
      .elementsFromPoint(clientX, clientY)
      .find((el) => el.dataset && el.dataset.planet);
    setOverPlanet(hovered ? hovered.dataset.planet : null);
  };

  const begin = (event) => {
    const rect = event.currentTarget.getBoundingClientRect();
    dragRef.current = {
      dx: event.clientX - rect.left - rect.width / 2,
      dy: event.clientY - rect.top - rect.height / 2,
      moved: false,
      target: position,
      visual: dragRef.current.visual || position,
      last: dragRef.current.last || position,
    };
    setDragging(true);
    event.preventDefault();
    try {
      event.currentTarget.setPointerCapture(event.pointerId);
    } catch {}
  };

  const move = (event) => {
    if (!dragging) return;
    dragRef.current.moved = true;
    moveTo(event.clientX, event.clientY);
  };

  const end = (event) => {
    if (!dragging) return;
    setDragging(false);
    setOverPlanet(null);
    try {
      event.currentTarget.releasePointerCapture(event.pointerId);
    } catch {}
    const droppedOnPlanet = document
      .elementsFromPoint(event.clientX, event.clientY)
      .find((el) => el.dataset && el.dataset.planet);
    if (droppedOnPlanet && onPlanetDrop) {
      const planet = PLANETS.find(
        (p) => p.id === droppedOnPlanet.dataset.planet,
      );
      const r = droppedOnPlanet.getBoundingClientRect();
      if (planet)
        onPlanetDrop(planet, {
          x: r.left + r.width / 2,
          y: r.top + r.height / 2,
        });
    }
  };

  return (
    <button
      className={`home-astronaut-dragger ${dragging || keyboardFlying ? "dragging" : ""} ${overPlanet ? "over-planet" : ""}`}
      style={{
        left: `${visual.x}px`,
        top: `${visual.y}px`,
        "--fly-rotate": `${angle}deg`,
        "--idle-x": `${idleFrame * -126}px`,
        "--suit-accent": suit.accent,
      }}
      onPointerDown={begin}
      onPointerMove={move}
      onPointerUp={end}
      onPointerCancel={() => {
        setDragging(false);
        setOverPlanet(null);
      }}
      aria-label="Drag astronaut to a planet"
      title="Drag me to a planet"
    >
      <span className="home-astronaut-sprite" />
      {selectedSticker.glyph || selectedSticker.asset ? (
        <span
          className={`astronaut-sticker-overlay ${stickerPlacement === "backpack" ? "backpack" : "helmet"}`}
        >
          {selectedSticker.asset ? (
            <SafeAssetImage
              src={selectedSticker.asset}
              alt=""
              context="Astronaut:sticker"
              fallbackText=""
            />
          ) : (
            selectedSticker.glyph
          )}
        </span>
      ) : null}
      {astronautName ? (
        <span className="astronaut-name-tag">{astronautName}</span>
      ) : null}
      <span className="home-astronaut-hint">Drag me</span>
    </button>
  );
}

function AstronautFlight({
  astronaut,
  onFlyComplete,
  helmetSticker,
  stickerPlacement,
  suitColor,
  waveKey,
  astronautName,
}) {
  const ref = useRef(null);
  const stickerRef = useRef(null);
  const phaseStartRef = useRef(performance.now());
  const lastPhaseRef = useRef(null);
  const [waving, setWaving] = useState(false);
  const selectedSticker =
    HELMET_STICKERS.find((s) => s.id === helmetSticker) || HELMET_STICKERS[0];
  const suit = currentSuit(suitColor);

  useEffect(() => {
    if (!astronaut) return;
    if (lastPhaseRef.current !== astronaut.phase) {
      phaseStartRef.current = performance.now();
      lastPhaseRef.current = astronaut.phase;
    }
  }, [astronaut && astronaut.phase]);

  useEffect(() => {
    if (!waveKey) return;
    setWaving(true);
    const t = setTimeout(() => setWaving(false), 900);
    return () => clearTimeout(t);
  }, [waveKey]);

  useEffect(() => {
    if (!ref.current || !astronaut) return;
    const FRAME_W = 126,
      FRAME_H = 90;
    // Frame index for "smooth flight" — fist forward, no cycling.
    const HERO_FRAME = 0;
    // Slow cycle while orbiting (just enough to feel alive).
    const ORBIT_CYCLE = [0, 1, 2, 3];
    const ORBIT_FRAME_MS = 320;
    let raf;
    let armedComplete = false;

    function tick(now) {
      const a = astronaut;
      const elapsed = now - phaseStartRef.current;
      let x = 0,
        y = 0,
        angle = 0,
        opacity = 1,
        scale = 1;

      if (a.phase === "flying") {
        const dur = 1200;
        const t = Math.min(1, elapsed / dur);
        const e = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
        x = a.from.x + (a.to.x - a.from.x) * e;
        y = a.from.y + (a.to.y - a.from.y) * e;
        const dx = a.to.x - a.from.x;
        const dy = a.to.y - a.from.y;
        angle = (Math.atan2(dy, dx) * 180) / Math.PI;
        scale = 0.85 + 0.4 * e;
        if (t >= 1 && !armedComplete) {
          armedComplete = true;
          if (onFlyComplete) onFlyComplete();
        }
      } else if (a.phase === "orbiting" || a.phase === "departing") {
        // Elliptical orbit around the planet detail center
        const omega = 0.0014; // rad/ms — one lap per ~75s
        const ang = elapsed * omega;
        x = a.center.x + Math.cos(ang) * a.radius;
        // Squash vertically so the orbit reads as 3D
        y = a.center.y + Math.sin(ang) * a.radius * 0.55;
        // Tangent direction (derivative): (-sin, 0.55*cos)
        angle =
          (Math.atan2(Math.cos(ang) * 0.55, -Math.sin(ang)) * 180) / Math.PI;
        scale = 1.0;
        if (a.phase === "departing") {
          const t = Math.min(1, elapsed / 800);
          opacity = 1 - t;
          scale = 1.0 - 0.25 * t;
        }
      }

      // Smooth flight = single hero frame; orbit = slow 4-frame cycle.
      let frame;
      if (a.phase === "flying") {
        frame = HERO_FRAME;
      } else {
        const i = Math.floor(now / ORBIT_FRAME_MS) % ORBIT_CYCLE.length;
        frame = ORBIT_CYCLE[i];
      }
      const col = frame % 4;
      const row = Math.floor(frame / 4);
      const el = ref.current;
      if (el) {
        el.style.left = x - FRAME_W / 2 + "px";
        el.style.top = y - FRAME_H / 2 + "px";
        el.style.transform = `rotate(${angle}deg) scale(${scale})`;
        el.style.backgroundPosition = `-${col * FRAME_W}px -${row * FRAME_H}px`;
        el.style.opacity = String(opacity);
      }
      raf = requestAnimationFrame(tick);
    }
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [astronaut, onFlyComplete]);

  if (!astronaut) return null;
  return (
    <div
      ref={ref}
      className={`astronaut-flight ${waving ? "waving" : ""}`}
      style={{ opacity: 0, "--suit-accent": suit.accent }}
    >
      <span className="suit-color-glow" />
      {selectedSticker.glyph || selectedSticker.asset ? (
        <span
          ref={stickerRef}
          className={`astronaut-sticker-overlay ${stickerPlacement === "backpack" ? "backpack" : "helmet"}`}
        >
          {selectedSticker.asset ? (
            <SafeAssetImage
              src={selectedSticker.asset}
              alt=""
              context="Astronaut:sticker"
              fallbackText=""
            />
          ) : (
            selectedSticker.glyph
          )}
        </span>
      ) : null}
      {astronautName ? (
        <span className="astronaut-name-tag flight">{astronautName}</span>
      ) : null}
    </div>
  );
}
