function HelmetLab({
  helmetSticker,
  onHelmetChange,
  stickerPlacement,
  onPlacementChange,
  helmetSide,
  onHelmetSideChange,
  stickerSide,
  onStickerSideChange,
  stickerPos,
  onStickerPosChange,
  suitColor,
  onSuitChange,
  onClose,
}) {
  const sticker =
    HELMET_STICKERS.find((s) => s.id === helmetSticker) || HELMET_STICKERS[0];
  const suit = currentSuit(suitColor);
  const helmetSurfaceRef = useRef(null);
  const backpackRef = useRef(null);
  const flyTimer = useRef(null);
  const choiceRefs = useRef({});
  const helmetRotateDragRef = useRef(null);
  const dragAbortRef = useRef(null);
  useEffect(
    () => () => {
      if (dragAbortRef.current) {
        dragAbortRef.current.abort();
        dragAbortRef.current = null;
      }
    },
    [],
  );
  const [flyingSticker, setFlyingSticker] = useState(null);
  const [placedStickers, setPlacedStickers] = useHelmetDecorations();
  const [selectedPlacedId, setSelectedPlacedId] = useState(null);
  const [draggingPlacedId, setDraggingPlacedId] = useState(null);
  const sideIndex = Math.max(
    0,
    HELMET_SIDES.findIndex((s) => s.id === helmetSide),
  );
  const side = HELMET_SIDES[sideIndex] || HELMET_SIDES[0];
  const visibleStickers = placedStickers.filter(
    (item) => item.side === side.id,
  );
  const selectedPlacedSticker = placedStickers.find(
    (item) => item.uid === selectedPlacedId,
  );

  const pointOnHelmetSurface = (event, opts = {}) => {
    const rect =
      helmetSurfaceRef.current &&
      helmetSurfaceRef.current.getBoundingClientRect();
    if (!rect) return null;
    const pad = opts.edgeClamp ? 160 : 24;
    if (
      event.clientX < rect.left - pad ||
      event.clientX > rect.right + pad ||
      event.clientY < rect.top - pad ||
      event.clientY > rect.bottom + pad
    ) {
      return null;
    }
    return {
      x: Math.max(
        12,
        Math.min(88, ((event.clientX - rect.left) / rect.width) * 100),
      ),
      y: Math.max(
        10,
        Math.min(88, ((event.clientY - rect.top) / rect.height) * 100),
      ),
    };
  };

  const updatePlacedStickerPosition = (uid, point) => {
    if (!uid || !point) return;
    setPlacedStickers((current) =>
      current.map((item) =>
        item.uid === uid
          ? { ...item, side: side.id, x: point.x, y: point.y }
          : item,
      ),
    );
  };

  const moveStickerOnSurface = (event) => {
    if (stickerPlacement !== "helmet") return;
    const point = pointOnHelmetSurface(event, { edgeClamp: true });
    const targetUid =
      selectedPlacedSticker && selectedPlacedSticker.side === side.id
        ? selectedPlacedSticker.uid
        : visibleStickers[visibleStickers.length - 1] &&
          visibleStickers[visibleStickers.length - 1].uid;
    if (!targetUid || !point) return;
    updatePlacedStickerPosition(targetUid, point);
    setSelectedPlacedId(targetUid);
    playKidSound("boop");
  };

  const startHelmetSurfaceDrag = (event) => {
    if (stickerPlacement !== "helmet") return;
    event.preventDefault();
    try {
      event.currentTarget.setPointerCapture(event.pointerId);
    } catch {}
    helmetRotateDragRef.current = {
      id: event.pointerId,
      x: event.clientX,
      y: event.clientY,
      sideIndex,
      lastIndex: sideIndex,
      lastStep: 0,
      lastDx: 0,
      moved: false,
    };
  };

  const continueSurfaceDrag = (event) => {
    if (stickerPlacement !== "helmet") return;
    const drag = helmetRotateDragRef.current;
    if (!drag || drag.id !== event.pointerId) return;
    if (event.buttons !== 1 && event.pointerType !== "touch") return;
    event.preventDefault();
    const dx = event.clientX - drag.x;
    const dy = event.clientY - drag.y;
    drag.lastDx = dx;
    if (Math.abs(dx) < 24 || Math.abs(dx) < Math.abs(dy) * 1.15) return;
    drag.moved = true;
    const step = Math.trunc(dx / 68);
    const nextIndex =
      (drag.sideIndex + step + HELMET_SIDES.length * 8) % HELMET_SIDES.length;
    if (step && nextIndex !== drag.lastIndex) {
      const next = HELMET_SIDES[nextIndex];
      onHelmetSideChange(next.id);
      const nextSticker = placedStickers.find((item) => item.side === next.id);
      setSelectedPlacedId(nextSticker ? nextSticker.uid : null);
      drag.lastIndex = nextIndex;
      drag.lastStep = step;
      playKidSound("boop");
    }
  };

  const stopSurfaceDrag = (event) => {
    const drag = helmetRotateDragRef.current;
    if (drag && drag.id === event.pointerId) {
      window.setTimeout(
        () => {
          helmetRotateDragRef.current = null;
        },
        drag.moved ? 140 : 0,
      );
    }
  };

  const maybeMoveStickerOnSurface = (event) => {
    if (helmetRotateDragRef.current?.moved) {
      event.preventDefault();
      return;
    }
    moveStickerOnSurface(event);
  };

  const startPlacedStickerDrag = (event, placed, placedSticker) => {
    event.preventDefault();
    event.stopPropagation();
    setSelectedPlacedId(placed.uid);
    onHelmetChange(placedSticker.id);
    setDraggingPlacedId(placed.uid);
    updatePlacedStickerPosition(placed.uid, pointOnHelmetSurface(event));
    playKidSound("boop");

    const move = (moveEvent) => {
      moveEvent.preventDefault();
      updatePlacedStickerPosition(
        placed.uid,
        pointOnHelmetSurface(moveEvent, { edgeClamp: true }),
      );
    };
    if (dragAbortRef.current) dragAbortRef.current.abort();
    const ac = new AbortController();
    dragAbortRef.current = ac;
    const stop = (upEvent) => {
      updatePlacedStickerPosition(
        placed.uid,
        pointOnHelmetSurface(upEvent, { edgeClamp: true }),
      );
      setDraggingPlacedId(null);
      ac.abort();
      if (dragAbortRef.current === ac) dragAbortRef.current = null;
    };
    window.addEventListener("pointermove", move, { signal: ac.signal });
    window.addEventListener("pointerup", stop, { signal: ac.signal });
    window.addEventListener("pointercancel", stop, { signal: ac.signal });
  };

  const startChoiceStickerDrag = (event, s) => {
    if (s.id === "none") {
      event.preventDefault();
      onHelmetChange(s.id);
      playKidSound("boop");
      return;
    }
    event.preventDefault();
    event.stopPropagation();
    clearTimeout(flyTimer.current);
    onHelmetChange(s.id);
    const start = { x: event.clientX, y: event.clientY };
    let uid = null;
    let didDrag = false;

    const move = (moveEvent) => {
      moveEvent.preventDefault();
      const distance = Math.hypot(
        moveEvent.clientX - start.x,
        moveEvent.clientY - start.y,
      );
      if (!didDrag && distance < 9) return;
      if (stickerPlacement === "backpack") return;
      if (!didDrag) {
        didDrag = true;
        uid = `${s.id}-${side.id}-${Date.now()}`;
        const fallback = {
          x: 50 + ((visibleStickers.length % 3) - 1) * 14,
          y: 48 + (Math.floor(visibleStickers.length / 3) % 2) * 16,
        };
        const startPoint = pointOnHelmetSurface(moveEvent) || fallback;
        setPlacedStickers((current) => [
          ...current,
          {
            uid,
            stickerId: s.id,
            side: side.id,
            x: startPoint.x,
            y: startPoint.y,
          },
        ]);
        setSelectedPlacedId(uid);
        setDraggingPlacedId(uid);
        onStickerSideChange(side.id);
        playKidSound("chime");
      }
      updatePlacedStickerPosition(
        uid,
        pointOnHelmetSurface(moveEvent, { edgeClamp: true }),
      );
    };
    if (dragAbortRef.current) dragAbortRef.current.abort();
    const ac = new AbortController();
    dragAbortRef.current = ac;
    const stop = (upEvent) => {
      if (didDrag && uid) {
        updatePlacedStickerPosition(
          uid,
          pointOnHelmetSurface(upEvent, { edgeClamp: true }),
        );
      } else {
        pickSticker(s);
      }
      setDraggingPlacedId(null);
      ac.abort();
      if (dragAbortRef.current === ac) dragAbortRef.current = null;
    };
    window.addEventListener("pointermove", move, { signal: ac.signal });
    window.addEventListener("pointerup", stop, { signal: ac.signal });
    window.addEventListener("pointercancel", stop, { signal: ac.signal });
  };

  const rotateHelmet = (step) => {
    const next =
      HELMET_SIDES[
        (sideIndex + step + HELMET_SIDES.length) % HELMET_SIDES.length
      ];
    onHelmetSideChange(next.id);
    const nextSticker = placedStickers.find((item) => item.side === next.id);
    setSelectedPlacedId(nextSticker ? nextSticker.uid : null);
    playKidSound("boop");
    if (window.__narration) window.__narration.play("game_helmet_rotate.mp3");
  };

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

  const pickSticker = (s) => {
    clearTimeout(flyTimer.current);
    const target =
      stickerPlacement === "backpack"
        ? backpackRef.current
        : helmetSurfaceRef.current;
    if (s.id === "none" || !target || !choiceRefs.current[s.id]) {
      onHelmetChange(s.id);
      playKidSound("boop");
      return;
    }
    const from = choiceRefs.current[s.id].getBoundingClientRect();
    const to = target.getBoundingClientRect();
    const fromX = from.left + from.width / 2;
    const fromY = from.top + from.height / 2;
    const toX = to.left + to.width / 2;
    const toY = to.top + to.height / 2;
    setFlyingSticker({
      id: s.id + "-" + Date.now(),
      glyph: s.glyph,
      asset: s.asset,
      fromX,
      fromY,
      dx: toX - fromX,
      dy: toY - fromY,
    });
    playKidSound("chime");
    flyTimer.current = setTimeout(() => {
      onHelmetChange(s.id);
      if (stickerPlacement === "helmet") {
        const uid = `${s.id}-${side.id}-${Date.now()}`;
        setPlacedStickers([
          ...placedStickers,
          {
            uid,
            stickerId: s.id,
            side: side.id,
            x: 50 + ((visibleStickers.length % 3) - 1) * 14,
            y: 48 + (Math.floor(visibleStickers.length / 3) % 2) * 16,
          },
        ]);
        setSelectedPlacedId(uid);
        onStickerSideChange(side.id);
      }
      setFlyingSticker(null);
      playKidSound("boop");
      if (s.id !== "none" && window.__narration) {
        window.__narration.play("game_helmet_sticker_placed.mp3");
      }
    }, 720);
  };

  const removeSelectedSticker = () => {
    if (!selectedPlacedId) return;
    setPlacedStickers(
      placedStickers.filter((item) => item.uid !== selectedPlacedId),
    );
    setSelectedPlacedId(null);
    playKidSound("boop");
  };

  const clearCurrentSide = () => {
    if (!visibleStickers.length) return;
    setPlacedStickers(placedStickers.filter((item) => item.side !== side.id));
    setSelectedPlacedId(null);
    playKidSound("boop");
  };

  const activateStickerChoice = (s) => {
    if (s.id === "none") {
      onHelmetChange(s.id);
      playKidSound("boop");
      return;
    }
    onHelmetChange(s.id);
    pickSticker(s);
  };

  return (
    <div className="detail-view helmet-lab-view">
      <div className="detail-bg helmet-lab-bg" />
      <div
        className="helmet-lab-preview"
        aria-label="Decorated astronaut helmet"
      >
        <button
          className="helmet-rotate-btn prev"
          onClick={() => rotateHelmet(-1)}
          aria-label="Rotate helmet left"
        >
          ‹
        </button>
        <button
          className="helmet-rotate-btn next"
          onClick={() => rotateHelmet(1)}
          aria-label="Rotate helmet right"
        >
          ›
        </button>
        <div className="helmet-side-label">{side.label}</div>
        <div
          className={`helmet-lab-suit side-${side.id} placement-${stickerPlacement}`}
          style={{ "--suit-color": suit.color, "--suit-accent": suit.accent }}
        >
          <SafeAssetImage
            className="helmet-lab-base"
            src={side.asset}
            alt=""
            context="HelmetLab:suitBase"
            fallbackText=""
          />
          <div className="helmet-lab-neck" />
          <div
            className="helmet-lab-shell"
            ref={helmetSurfaceRef}
            onClick={maybeMoveStickerOnSurface}
            onPointerDown={startHelmetSurfaceDrag}
            onPointerMove={continueSurfaceDrag}
            onPointerUp={stopSurfaceDrag}
            onPointerCancel={stopSurfaceDrag}
            title="Drag the helmet to rotate. Drag stickers to place them."
          >
            <div className="helmet-lab-visor" aria-hidden="true">
              {visibleStickers.map((placed) => {
                const placedSticker =
                  HELMET_STICKERS.find((s) => s.id === placed.stickerId) ||
                  HELMET_STICKERS[0];
                const selected = selectedPlacedId === placed.uid;
                const dragging = draggingPlacedId === placed.uid;
                return (
                  <button
                    key={placed.uid}
                    type="button"
                    className={`helmet-lab-sticker placed ${selected ? "selected" : ""} ${dragging ? "dragging" : ""} ${placedSticker.asset ? "asset" : ""}`}
                    style={{ left: `${placed.x}%`, top: `${placed.y}%` }}
                    onPointerDown={(event) =>
                      startPlacedStickerDrag(event, placed, placedSticker)
                    }
                    onClick={(event) => {
                      event.stopPropagation();
                      setSelectedPlacedId(placed.uid);
                      onHelmetChange(placedSticker.id);
                      playKidSound("boop");
                    }}
                    aria-label={`${placedSticker.label} sticker on ${side.label}`}
                    title="Drag this sticker or tap the helmet to move it"
                  >
                    <StickerArt sticker={placedSticker} />
                  </button>
                );
              })}
            </div>
            <div className="helmet-lab-ear left" />
            <div className="helmet-lab-ear right" />
          </div>
          <div className="helmet-lab-backpack" ref={backpackRef}>
            <span className="helmet-lab-backpack-strap" />
            {stickerPlacement === "backpack" && sticker.id !== "none" ? (
              <StickerArt
                sticker={sticker}
                className={`helmet-lab-backpack-sticker ${sticker.asset ? "" : "glyph"}`}
              />
            ) : null}
          </div>
        </div>
      </div>
      <div className="detail-info helmet-lab-info">
        <div className="detail-eyebrow">ASTRONAUT CLOSET</div>
        <h1 className="detail-name">Helmet Lab</h1>
        <div className="kid-i-spy">Pick a sticker and a suit color.</div>
        <div className="helmet-lab-section">
          <div className="helmet-lab-label">Put it on</div>
          <div className="placement-row">
            <button
              className={`placement-choice ${stickerPlacement === "helmet" ? "on" : ""}`}
              onClick={() => {
                onPlacementChange("helmet");
                onStickerSideChange(side.id);
                playKidSound("boop");
                if (window.__narration)
                  window.__narration.play("game_helmet_helmet.mp3");
              }}
              aria-pressed={stickerPlacement === "helmet" ? "true" : "false"}
            >
              Helmet
            </button>
            <button
              className={`placement-choice ${stickerPlacement === "backpack" ? "on" : ""}`}
              onClick={() => {
                onPlacementChange("backpack");
                playKidSound("boop");
                if (window.__narration)
                  window.__narration.play("game_helmet_backpack.mp3");
              }}
              aria-pressed={stickerPlacement === "backpack" ? "true" : "false"}
            >
              Backpack
            </button>
          </div>
        </div>
        <div className="helmet-lab-section">
          <div className="helmet-lab-label">Helmet sticker</div>
          <div className="helmet-lab-note">
            Pick. Drag a sticker to place it. Drag the helmet to spin.
          </div>
          <div className="helmet-side-status" aria-live="polite">
            <span>{side.label}</span>
            <strong>
              {visibleStickers.length}{" "}
              {visibleStickers.length === 1 ? "sticker" : "stickers"}
            </strong>
          </div>
          <div className="helmet-side-tools">
            <button
              className="placement-choice"
              onClick={removeSelectedSticker}
              disabled={!selectedPlacedId}
            >
              Remove selected
            </button>
            <button
              className="placement-choice"
              onClick={clearCurrentSide}
              disabled={!visibleStickers.length}
            >
              Clear {side.label}
            </button>
          </div>
          <div className="helmet-lab-grid">
            {HELMET_STICKERS.map((s) => (
              <button
                key={s.id}
                ref={(el) => {
                  if (el) choiceRefs.current[s.id] = el;
                }}
                className={`helmet-lab-choice ${helmetSticker === s.id ? "on" : ""}`}
                onPointerDown={(event) => startChoiceStickerDrag(event, s)}
                onClick={(event) => {
                  event.preventDefault();
                  if (event.detail === 0) activateStickerChoice(s);
                }}
                onKeyDown={(event) => {
                  if (event.key === "Enter" || event.key === " ") {
                    event.preventDefault();
                    activateStickerChoice(s);
                  }
                }}
                aria-pressed={helmetSticker === s.id ? "true" : "false"}
                title={s.label}
              >
                <span>
                  <StickerArt sticker={s} />
                </span>
                <small>{s.label}</small>
              </button>
            ))}
          </div>
        </div>
        <div className="helmet-lab-section">
          <div className="helmet-lab-label">Suit color</div>
          <div className="suit-color-row">
            {SUIT_COLORS.map((s) => (
              <button
                key={s.id}
                className={`suit-color-choice ${suitColor === s.id ? "on" : ""}`}
                onClick={() => {
                  onSuitChange(s.id);
                  playKidSound("boop");
                }}
                aria-pressed={suitColor === s.id ? "true" : "false"}
                title={s.label}
                style={{ "--suit-color": s.color, "--suit-accent": s.accent }}
              />
            ))}
          </div>
        </div>
      </div>
      {flyingSticker ? (
        <div
          key={flyingSticker.id}
          className="helmet-lab-flying-sticker"
          style={{
            left: flyingSticker.fromX,
            top: flyingSticker.fromY,
            "--fly-x": `${flyingSticker.dx}px`,
            "--fly-y": `${flyingSticker.dy}px`,
          }}
          aria-hidden="true"
        >
          <StickerArt sticker={flyingSticker} />
        </div>
      ) : null}
      <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>
    </div>
  );
}

function SurpriseComet({ active, comet, onTap }) {
  if (!active || !comet) return null;
  return (
    <button
      key={comet.id}
      className="surprise-comet"
      onClick={onTap}
      aria-label="Tap the surprise comet"
      title="Tap the comet"
    >
      <SafeAssetImage
        src={GENERATED_ASSETS.comet}
        alt=""
        context="SurpriseComet"
        fallbackText=""
      />
    </button>
  );
}

function TreasureHuntLevel({
  collected,
  score,
  comet,
  pickupFlight,
  onTreasure,
  onPickupComplete,
  onComet,
  onReset,
  onExit,
}) {
  const remaining = KID_TREASURES.length - collected.length;
  const nextTreasure = KID_TREASURES.find(
    (treasure) => !collected.includes(treasure.id),
  );
  const nextTreasureIndex = nextTreasure
    ? KID_TREASURES.findIndex((treasure) => treasure.id === nextTreasure.id)
    : -1;
  const nextTreasureSpot =
    nextTreasureIndex >= 0
      ? KID_TREASURE_SPOTS[nextTreasureIndex % KID_TREASURE_SPOTS.length]
      : null;
  return (
    <div className="treasure-level">
      <Starfield count={420} />
      <KidStarSurprises
        active={true}
        collected={collected}
        collectingId={pickupFlight && pickupFlight.treasure.id}
        nextTreasureId={nextTreasure && nextTreasure.id}
        busy={Boolean(pickupFlight)}
        onTap={onTreasure}
      />
      <SurpriseComet active={true} comet={comet} onTap={onComet} />
      <TreasureAstronautPickup
        flight={pickupFlight}
        onComplete={onPickupComplete}
      />
      {remaining > 0 && nextTreasureSpot && !pickupFlight ? (
        <div className="treasure-tap-helper" style={nextTreasureSpot}>
          <span>Tap this!</span>
        </div>
      ) : null}
      <div className="treasure-hud">
        <button className="treasure-back" onClick={onExit}>
          ← Back to planets
        </button>
        <div className="treasure-title-block">
          <div className="treasure-title">Treasure Hunt</div>
          <div className="treasure-subtitle">
            {pickupFlight
              ? "Astronaut flying!"
              : "Tap any treasure and Adam will fly over to grab it"}
          </div>
        </div>
        <div className="treasure-count">
          <strong>{score}</strong>
          <span>{score === 1 ? "treasure" : "treasures"} found</span>
        </div>
      </div>
      {remaining > 0 && nextTreasure ? (
        <div className="treasure-next-card" role="status" aria-live="polite">
          <span>{pickupFlight ? "Flying" : "Tap one"}</span>
          <SafeAssetImage
            src={nextTreasure.asset}
            alt=""
            context={`TreasureHunt:next:${nextTreasure.id}`}
            fallbackClassName="treasure-image-fallback"
            fallbackText="Treasure is loading."
          />
          <strong>
            {pickupFlight ? "Adam is grabbing it" : nextTreasure.label}
          </strong>
        </div>
      ) : null}
      <div className="treasure-tray">
        {KID_TREASURES.map((treasure) => (
          <span
            key={treasure.id}
            className={`treasure-tray-item ${
              collected.includes(treasure.id) ? "found" : ""
            }`}
            title={treasure.label}
          >
            <SafeAssetImage
              src={treasure.asset}
              alt=""
              context={`TreasureHunt:tray:${treasure.id}`}
              fallbackText=""
            />
          </span>
        ))}
      </div>
      {remaining > 0 ? (
        <div className="treasure-progress" aria-hidden="true">
          {KID_TREASURES.map((treasure) => (
            <span
              key={treasure.id}
              className={collected.includes(treasure.id) ? "done" : ""}
            />
          ))}
        </div>
      ) : null}
      {remaining === 0 ? (
        <div className="treasure-complete">
          <div className="treasure-complete-sparkles" aria-hidden="true">
            <span />
            <span />
            <span />
            <span />
            <span />
          </div>
          <SafeAssetImage
            src={GENERATED_ASSETS.missionBadge}
            alt=""
            context="TreasureHunt:missionBadge"
            fallbackText=""
          />
          <strong>All treasures found!</strong>
          <p>You helped the astronaut collect the whole set.</p>
          <div className="treasure-complete-actions">
            <button onClick={onReset}>Play again</button>
            <button onClick={onExit}>Back to planets</button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

function RocketPackLevel({
  builtParts,
  color,
  message,
  launched,
  mode,
  onMode,
  onPart,
  onColor,
  onLaunch,
  onReset,
  onExit,
}) {
  const partSet =
    mode === "engineering"
      ? ROCKET_PARTS
      : ROCKET_PARTS.filter((part) => SIMPLE_ROCKET_PART_IDS.includes(part.id));
  const done = partSet.every((part) => builtParts.includes(part.id));
  const nextPart = partSet.find((part) => !builtParts.includes(part.id));
  const builtCount = partSet.filter((part) => builtParts.includes(part.id)).length;
  const nextPartIndex = nextPart ? partSet.findIndex((part) => part.id === nextPart.id) : -1;
  const colorInfo =
    ROCKET_COLORS.find((entry) => entry.id === color) || ROCKET_COLORS[0];
  const padRef = useRef(null);
  const [draggingPart, setDraggingPart] = useState(null);
  const [dragPoint, setDragPoint] = useState(null);
  const [dragStatus, setDragStatus] = useState(null);
  const dragStartRef = useRef(null);
  const ignoreNextClickRef = useRef(false);
  const isDraggingNextPart = Boolean(draggingPart);
  const currentDragPart = draggingPart
    ? ROCKET_PARTS.find((part) => part.id === draggingPart)
    : null;
  const describeNextPart = () =>
    nextPart
      ? `Next piece ${builtCount + 1} of ${partSet.length}: ${nextPart.label}`
      : "Rocket is ready.";
  const markDragMoved = (clientX, clientY) => {
    if (!dragStartRef.current) return false;
    const dx = clientX - dragStartRef.current.x;
    const dy = clientY - dragStartRef.current.y;
    if (Math.hypot(dx, dy) > 8) dragStartRef.current.moved = true;
    return dragStartRef.current.moved;
  };
  const dropPointInBuildPad = (clientX, clientY) => {
    const rect = padRef.current && padRef.current.getBoundingClientRect();
    const dropForgiveness = 72;
    return Boolean(
      rect &&
        clientX >= rect.left - dropForgiveness &&
        clientX <= rect.right + dropForgiveness &&
        clientY >= rect.top - dropForgiveness &&
        clientY <= rect.bottom + dropForgiveness,
    );
  };
  const finishDragPart = useCallback(
    (clientX, clientY) => {
      if (!draggingPart) return;
      const inPad = dropPointInBuildPad(clientX, clientY);
      const part = ROCKET_PARTS.find((entry) => entry.id === draggingPart);
      const wasMoved = Boolean(
        dragStartRef.current && dragStartRef.current.moved,
      );
      setDraggingPart(null);
      setDragPoint(null);
      dragStartRef.current = null;
      if (wasMoved) ignoreNextClickRef.current = true;
      if (inPad && part && part.id === (nextPart && nextPart.id)) {
        ignoreNextClickRef.current = true;
        setDragStatus({ tone: "good", text: `${part.label} snapped on!` });
        onPart(part);
      } else if (wasMoved && part) {
        setDragStatus({
          tone: "try",
          text: `Move ${part.label} onto the rocket.`,
        });
      }
    },
    [draggingPart, nextPart, onPart],
  );
  const cancelDragPart = () => {
    setDraggingPart(null);
    setDragPoint(null);
    dragStartRef.current = null;
  };
  useEffect(() => {
    if (!draggingPart) return;
    const onPointerMove = (event) => {
      markDragMoved(event.clientX, event.clientY);
      setDragPoint({ x: event.clientX, y: event.clientY });
    };
    const onPointerUp = (event) => finishDragPart(event.clientX, event.clientY);
    const onTouchMove = (event) => {
      const touch = event.touches && event.touches[0];
      if (touch) {
        event.preventDefault();
        markDragMoved(touch.clientX, touch.clientY);
        setDragPoint({ x: touch.clientX, y: touch.clientY });
      }
    };
    const onTouchEnd = (event) => {
      const touch = event.changedTouches && event.changedTouches[0];
      if (touch) finishDragPart(touch.clientX, touch.clientY);
    };
    window.addEventListener("pointermove", onPointerMove);
    window.addEventListener("pointerup", onPointerUp);
    window.addEventListener("pointercancel", cancelDragPart);
    window.addEventListener("touchmove", onTouchMove, { passive: false });
    window.addEventListener("touchend", onTouchEnd);
    window.addEventListener("touchcancel", cancelDragPart);
    return () => {
      window.removeEventListener("pointermove", onPointerMove);
      window.removeEventListener("pointerup", onPointerUp);
      window.removeEventListener("pointercancel", cancelDragPart);
      window.removeEventListener("touchmove", onTouchMove);
      window.removeEventListener("touchend", onTouchEnd);
      window.removeEventListener("touchcancel", cancelDragPart);
    };
  }, [draggingPart, finishDragPart]);
  const beginDragPart = (event, part) => {
    if (part.id !== (nextPart && nextPart.id)) return;
    event.preventDefault();
    setDragStatus({ tone: "ready", text: `Drag ${part.label} to the rocket.` });
    try {
      event.currentTarget.setPointerCapture(event.pointerId);
    } catch {}
    setDraggingPart(part.id);
    setDragPoint({ x: event.clientX, y: event.clientY });
    dragStartRef.current = { x: event.clientX, y: event.clientY, moved: false };
  };
  const moveDragPart = (event) => {
    if (!draggingPart) return;
    markDragMoved(event.clientX, event.clientY);
    setDragPoint({ x: event.clientX, y: event.clientY });
  };
  const endDragPart = (event, part) => {
    if (!draggingPart) return;
    try {
      event.currentTarget.releasePointerCapture(event.pointerId);
    } catch {}
    finishDragPart(event.clientX, event.clientY);
  };
  const tapNextPart = () => {
    if (ignoreNextClickRef.current) {
      ignoreNextClickRef.current = false;
      return;
    }
    if (dragStartRef.current && dragStartRef.current.moved) return;
    if (nextPart) {
      setDragStatus({ tone: "good", text: `${nextPart.label} snapped on!` });
      onPart(nextPart);
    }
  };
  return (
    <div className="rocket-pack-level">
      <Starfield count={260} />
      <button className="rocket-pack-back" onClick={onExit}>
        ← Back to planets
      </button>
      <div
        className={`rocket-builder-stage color-${colorInfo.id} ${launched ? "launching" : ""} ${isDraggingNextPart ? "dragging" : ""}`}
        style={{ "--rocket-color": colorInfo.value }}
      >
        <div
          className="rocket-build-pad"
          role="button"
          tabIndex={nextPart && !launched ? 0 : -1}
          aria-label={
            nextPart && !launched
              ? `Add ${nextPart.label} to the rocket`
              : "Rocket build area"
          }
          aria-describedby="rocket-build-status"
          ref={padRef}
          onClick={() => {
            if (nextPart && !launched && !draggingPart) tapNextPart();
          }}
          onKeyDown={(event) => {
            if (!nextPart || launched || draggingPart) return;
            if (event.key === " " || event.key === "Enter") {
              event.preventDefault();
              tapNextPart();
            }
          }}
        >
          {nextPart && !launched ? (
            <div className="rocket-drop-hint">
              {isDraggingNextPart
                ? "Release to snap it on"
                : `Drop ${nextPart.label} here`}
            </div>
          ) : null}
          <div className="rocket-build-ship" aria-hidden="true">
            <img
              className={`rocket-part-img rocket-nose-img ${builtParts.includes("nose") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketNose}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-body-img ${builtParts.includes("body") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketBody}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-fins-img ${builtParts.includes("fins") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketFins}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-window-img no-tint ${builtParts.includes("window") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketWindow}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-engine-img no-tint ${builtParts.includes("engine") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketEngine}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-fuel-tank-img no-tint ${builtParts.includes("fuelTank") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketFuelTank}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-fuel-pipes-img no-tint ${builtParts.includes("fuelPipes") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketFuelPipes}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-igniter-img no-tint ${builtParts.includes("igniter") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketIgniter}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-booster-img ${builtParts.includes("booster") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketBooster}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-antenna-img no-tint ${builtParts.includes("antenna") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketAntenna}
              alt=""
            />
            <img
              className={`rocket-part-img rocket-badge-img no-tint ${builtParts.includes("badge") ? "on" : ""}`}
              src={GENERATED_ASSETS.rocketBadge}
              alt=""
            />
            <img
              className="rocket-flame"
              src={GENERATED_ASSETS.rocketFire}
              alt=""
            />
          </div>
        </div>
        <div className="rocket-pack-copy">
          <span>Build Rocket</span>
          <h1>Build Adam's rocket.</h1>
          <p>
            {mode === "engineering"
              ? "Engineer the engine system piece by piece."
              : "Build the big rocket first, then level up."}
          </p>
          <div className="rocket-mode-toggle" aria-label="Rocket builder mode">
            <button
              className={mode === "simple" ? "on" : ""}
              onClick={() => onMode("simple")}
              aria-pressed={mode === "simple" ? "true" : "false"}
            >
              Simple
            </button>
            <button
              className={mode === "engineering" ? "on" : ""}
              onClick={() => onMode("engineering")}
              aria-pressed={mode === "engineering" ? "true" : "false"}
            >
              Engineering
            </button>
          </div>
          <div className="rocket-pack-slots" aria-label="Packed items">
            {partSet.map((part) => {
              const isBuilt = builtParts.includes(part.id);
              const isNext = nextPart && part.id === nextPart.id;
              return (
                <span
                  key={part.id}
                  className={`${isBuilt ? "packed" : ""} ${isNext ? "next" : ""}`}
                  title={part.label}
                >
                  {isBuilt ? (
                    <SafeAssetImage
                      src={part.asset}
                      alt=""
                      context="RocketPack:builtPart"
                      fallbackText=""
                    />
                  ) : isNext ? (
                    nextPartIndex + 1
                  ) : (
                    "?"
                  )}
                </span>
              );
            })}
          </div>
          <div className="rocket-color-row" aria-label="Rocket color">
            {ROCKET_COLORS.map((entry) => (
              <button
                key={entry.id}
                className={entry.id === color ? "on" : ""}
                style={{ "--swatch": entry.value }}
                onClick={() => onColor(entry.id)}
                aria-label={`${entry.label} rocket`}
              />
            ))}
          </div>
          {nextPart ? (
            <button
              className="pack-next-choice"
              onClick={tapNextPart}
              onPointerDown={(event) => beginDragPart(event, nextPart)}
              onPointerMove={moveDragPart}
              onPointerUp={(event) => endDragPart(event, nextPart)}
              onPointerCancel={cancelDragPart}
              aria-label={`Add ${nextPart.label}`}
            >
              <SafeAssetImage
                src={nextPart.asset}
                alt=""
                context="RocketPack:nextPart"
                fallbackText=""
              />
              <span>
                Next piece {builtCount + 1} of {partSet.length}
              </span>
              <strong>{nextPart.label}</strong>
              <em>Tap here or tap the big rocket.</em>
            </button>
          ) : null}
          {currentDragPart && dragPoint ? (
            <img
              className="rocket-drag-ghost"
              src={currentDragPart.asset}
              alt=""
              style={{
                left: `${dragPoint.x}px`,
                top: `${dragPoint.y}px`,
              }}
            />
          ) : null}
          {message ? (
            <div className="rocket-pack-fact good" role="status">
              <strong>{message.title}</strong>
              <span>{message.fact}</span>
            </div>
          ) : (
            <div className="rocket-pack-fact" role="status">
              <strong>Try one.</strong>
              <span id="rocket-build-status">
                {dragStatus ? dragStatus.text : describeNextPart()}
              </span>
            </div>
          )}
          {done ? (
            <div className="rocket-pack-done" role="status">
              <strong>{launched ? "Whoosh!" : "Ready for launch!"}</strong>
              <button onClick={launched ? onReset : onLaunch}>
                {launched ? "Build again" : "3 2 1 Launch"}
              </button>
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
}
