// world.jsx — the open-world park. One big translatable container with
// zones, scenery, hidden things. Pan via drag-on-empty, arrows/WASD,
// or mouse near edges.

const { useState: wUseState, useEffect: wUseEffect, useRef: wUseRef, useMemo: wUseMemo, useCallback: wUseCallback } = React;

const WORLD = { w: 3400, h: 2000 };
const ZONES = {
  slide:   { x: 220,  y: 460,  w: 900,  h: 1000, label: "01 / the slide" },
  sandbox: { x: 1280, y: 720,  w: 940,  h: 760,  label: "02 / the sandbox" },
  swings:  { x: 2360, y: 320,  w: 940,  h: 1180, label: "03 / the swings" },
};

// scenery placements (world coords)
const SCENERY = [
  { id:"sun",      kind:"sun",     x: 280,  y: 220, easter:"sun" },
  { id:"cloud-1",  kind:"cloud",   x: 1100, y: 160 },
  { id:"cloud-2",  kind:"cloud",   x: 2200, y: 90 },
  { id:"cloud-3",  kind:"cloud",   x: 3000, y: 220 },
  { id:"tree-1",   kind:"tree",    x: 1180, y: 1620 },
  { id:"tree-2",   kind:"tree",    x: 2240, y: 1620 },
  { id:"bench",    kind:"bench",   x: 580,  y: 1640, easter:"bench" },
  { id:"flag",     kind:"flag",    x: 2080, y: 1620, easter:"flag" },
  { id:"stamp00",  kind:"stamp",   x: 3120, y: 1660, easter:"stamp" },
];

function World({
  wild, onEgg, balloons, fireworkSeed,
}) {
  const wrapRef = wUseRef(null);
  const containerRef = wUseRef(null);

  // ── pure imperative camera (NO React state — never overwritten on re-render)
  // start centered on wordmark with a hint of each zone around the edges
  const initialCam = wUseMemo(() => {
    const vw = window.innerWidth, vh = window.innerHeight;
    return { x: -(1300 - vw/2), y: -(420 - vh/2) };
  }, []);
  const camRef = wUseRef({ ...initialCam });
  const velRef = wUseRef({ x: 0, y: 0 });          // momentum velocity
  const animatingRef = wUseRef(false);             // true during goto-animation
  const interactingRef = wUseRef(false);           // true while user is panning
  const panRef = wUseRef({ panning: false, sx: 0, sy: 0, cx: 0, cy: 0, lastX: 0, lastY: 0, lastT: 0, vx: 0, vy: 0 });
  const edgeRefs = { l: wUseRef(null), r: wUseRef(null), t: wUseRef(null), b: wUseRef(null) };

  const clampCam = (c) => {
    const vw = window.innerWidth, vh = window.innerHeight;
    return {
      x: clamp(c.x, vw - WORLD.w - 40, 40),
      y: clamp(c.y, vh - WORLD.h - 40, 40),
    };
  };

  const writeTransform = () => {
    const el = containerRef.current;
    if (!el) return;
    const c = camRef.current;
    el.style.transform = `translate3d(${c.x}px, ${c.y}px, 0)`;
  };

  // initial paint (runs before browser paint so no flash)
  React.useLayoutEffect(() => { writeTransform(); }, []);

  // ── main physics loop: momentum + smooth follow + key/edge input
  wUseEffect(() => {
    const keys = new Set();
    let mx = -1, my = -1;
    const onKeyDown = (e) => {
      if (e.target && e.target.matches && e.target.matches("input,textarea,select")) return;
      const k = e.key;
      if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","w","a","s","d","W","A","S","D"].includes(k)) {
        keys.add(k.toLowerCase());
      }
    };
    const onKeyUp = (e) => keys.delete(e.key.toLowerCase());
    const onMouseMove = (e) => { mx = e.clientX; my = e.clientY; };
    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("keyup", onKeyUp);
    window.addEventListener("mousemove", onMouseMove);

    let raf;
    const tick = () => {
      // accumulate input forces (only when not actively dragging or animating)
      let fx = 0, fy = 0;
      const KS = 1.6; // key force per frame
      if (!interactingRef.current && !animatingRef.current) {
        if (keys.has("arrowleft")  || keys.has("a")) fx += KS;
        if (keys.has("arrowright") || keys.has("d")) fx -= KS;
        if (keys.has("arrowup")    || keys.has("w")) fy += KS;
        if (keys.has("arrowdown")  || keys.has("s")) fy -= KS;
        // edge pan
        const vw = window.innerWidth, vh = window.innerHeight;
        const margin = 60, ESp = 1.2;
        let l=false,r=false,t=false,b=false;
        if (mx >= 0 && mx < margin)   { fx += ESp * (1 - mx/margin); l = true; }
        if (mx > vw - margin)         { fx -= ESp * (1 - (vw-mx)/margin); r = true; }
        if (my >= 0 && my < margin)   { fy += ESp * (1 - my/margin); t = true; }
        if (my > vh - margin)         { fy -= ESp * (1 - (vh-my)/margin); b = true; }
        const setOn = (ref, on) => {
          const el = ref.current; if (!el) return;
          if (on) el.classList.add("on"); else el.classList.remove("on");
        };
        setOn(edgeRefs.l, l); setOn(edgeRefs.r, r); setOn(edgeRefs.t, t); setOn(edgeRefs.b, b);
      }

      // integrate (input force + inertia)
      if (!interactingRef.current && !animatingRef.current) {
        const v = velRef.current;
        v.x = v.x * 0.90 + fx;
        v.y = v.y * 0.90 + fy;
        // cap top speed
        v.x = clamp(v.x, -42, 42);
        v.y = clamp(v.y, -42, 42);
        if (Math.abs(v.x) > 0.04 || Math.abs(v.y) > 0.04) {
          camRef.current = clampCam({ x: camRef.current.x + v.x, y: camRef.current.y + v.y });
          writeTransform();
        } else {
          v.x = 0; v.y = 0;
        }
      }

      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("keyup", onKeyUp);
      window.removeEventListener("mousemove", onMouseMove);
      cancelAnimationFrame(raf);
    };
  }, []);

  // ── pan via mouse drag (with momentum sampling)
  wUseEffect(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;

    const onDown = (e) => {
      if (e.button !== 0) return;
      if (e.target.closest("[data-cursor]")) return;
      if (e.target.closest("[data-no-pan]")) return;
      interactingRef.current = true;
      animatingRef.current = false;
      const now = performance.now();
      panRef.current = {
        panning: true,
        sx: e.clientX, sy: e.clientY,
        cx: camRef.current.x, cy: camRef.current.y,
        lastX: e.clientX, lastY: e.clientY, lastT: now,
        vx: 0, vy: 0,
      };
      velRef.current.x = 0; velRef.current.y = 0;
      document.getElementById("cursor")?.setAttribute("data-state", "pan");
    };
    const onMove = (e) => {
      const p = panRef.current;
      if (!p.panning) return;
      const now = performance.now();
      const dt = Math.max(1, now - p.lastT);
      p.vx = (e.clientX - p.lastX) / dt * 16; // px per frame
      p.vy = (e.clientY - p.lastY) / dt * 16;
      p.lastX = e.clientX; p.lastY = e.clientY; p.lastT = now;
      camRef.current = clampCam({
        x: p.cx + (e.clientX - p.sx),
        y: p.cy + (e.clientY - p.sy),
      });
      writeTransform();
    };
    const onUp = () => {
      const p = panRef.current;
      if (!p.panning) return;
      p.panning = false;
      interactingRef.current = false;
      // hand off to inertia
      velRef.current.x = clamp(p.vx, -90, 90);
      velRef.current.y = clamp(p.vy, -90, 90);
      document.getElementById("cursor")?.setAttribute("data-state", "");
    };
    wrap.addEventListener("mousedown", onDown);
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    return () => {
      wrap.removeEventListener("mousedown", onDown);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
    };
  }, []);

  // ── wheel scroll pans (vertical wheel = vertical pan; shift = horizontal)
  wUseEffect(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;
    const onWheel = (e) => {
      // skip if event is over an interactive element that wants its own wheel
      if (e.target.closest("[data-piece-id]")) return; // sandbox piece wants wheel
      if (e.target.closest("[data-no-pan]")) return;
      e.preventDefault();
      const dx = e.shiftKey ? e.deltaY : e.deltaX;
      const dy = e.shiftKey ? 0 : e.deltaY;
      // injection: add to velocity for nonlinear feel
      velRef.current.x += -dx * 0.08;
      velRef.current.y += -dy * 0.08;
      animatingRef.current = false;
    };
    wrap.addEventListener("wheel", onWheel, { passive: false });
    return () => wrap.removeEventListener("wheel", onWheel);
  }, []);

  // ── animation: go-to / go-to-xy / balloon-translate
  const animateCam = (target) => {
    const start = { ...camRef.current };
    const c = clampCam(target);
    const t0 = performance.now();
    const dur = 700;
    animatingRef.current = true;
    velRef.current = { x: 0, y: 0 };
    const step = (now) => {
      const k = clamp((now - t0) / dur, 0, 1);
      const e = 1 - Math.pow(1 - k, 3);
      camRef.current = { x: lerp(start.x, c.x, e), y: lerp(start.y, c.y, e) };
      writeTransform();
      if (k < 1) requestAnimationFrame(step);
      else animatingRef.current = false;
    };
    requestAnimationFrame(step);
  };

  wUseEffect(() => {
    const onZone = (e) => {
      const { zone } = e.detail || {};
      const z = ZONES[zone];
      if (!z) return;
      animateCam({
        x: -(z.x + z.w/2 - window.innerWidth/2),
        y: -(z.y + z.h/2 - window.innerHeight/2),
      });
    };
    const onXY = (e) => animateCam(e.detail || {});
    const onBalloon = (e) => {
      const { x, y } = e.detail || {};
      const wx = x - camRef.current.x;
      const wy = y - camRef.current.y;
      window.dispatchEvent(new CustomEvent("frgmt:balloon-world", { detail: { x: wx, y: wy } }));
    };
    window.addEventListener("frgmt:goto", onZone);
    window.addEventListener("frgmt:goto-xy", onXY);
    window.addEventListener("frgmt:balloon", onBalloon);
    return () => {
      window.removeEventListener("frgmt:goto", onZone);
      window.removeEventListener("frgmt:goto-xy", onXY);
      window.removeEventListener("frgmt:balloon", onBalloon);
    };
  }, []);

  return (
    <>
      <div ref={wrapRef} style={{ position:"absolute", inset:0, overflow:"hidden" }}>
        <div ref={containerRef} className="world" style={{
          width: WORLD.w, height: WORLD.h,
          willChange: "transform",
        }}>
          {/* horizon line — drawn at world Y=1300 */}
          <div style={{
            position:"absolute", left: 0, right: 0, top: 1300,
            height: 1, background: "var(--ink)", opacity: .35,
          }} />
          {/* grid in the ground */}
          <div style={{
            position:"absolute", left:0, right:0, top:1300, bottom:0,
            backgroundImage:
              "linear-gradient(transparent 0, transparent calc(100% - 1px), rgba(14,14,14,.08) 100%),"+
              "linear-gradient(90deg, transparent 0, transparent calc(100% - 1px), rgba(14,14,14,.08) 100%)",
            backgroundSize:"40px 40px",
            maskImage:"linear-gradient(to bottom, rgba(0,0,0,.85), rgba(0,0,0,0) 80%)",
            WebkitMaskImage:"linear-gradient(to bottom, rgba(0,0,0,.85), rgba(0,0,0,0) 80%)",
            pointerEvents:"none",
          }} />

          {/* dotted hand-drawn paths between attractions */}
          <ParkPaths />

          {/* drifting confetti shards (world layer) */}
          <DriftShards />

          {/* the BIG fragmented wordmark — at upper center of world */}
          <Wordmark fireworkSeed={fireworkSeed} />

          {/* tagline (serif italic) */}
          <div className="flashin" style={{
            position:"absolute", left: 800, top: 660, width: 1000, textAlign:"center",
            fontFamily:"var(--serif)", fontStyle:"italic",
            fontSize: 32, lineHeight: 1.2, color:"var(--ink-2)",
            pointerEvents:"none",
          }}>
            a tiny park for restless hands —<br/>
            three rides, no queues, all toys live.
          </div>

          {/* zone wrappers — each a card-bordered playable area */}
          {Object.entries(ZONES).map(([id, z]) => (
            <div key={id} style={{
              position:"absolute",
              left: z.x, top: z.y, width: z.w, height: z.h,
              background: "var(--paper)",
              border: "1.5px solid var(--ink)",
              borderRadius: 22,
              boxShadow: "8px 8px 0 var(--ink)",
              overflow:"hidden",
            }}>
              {id === "slide"   && <SlideZone   wild={wild} />}
              {id === "sandbox" && <SandboxZone wild={wild} />}
              {id === "swings"  && <SwingsZone  wild={wild} />}
              {/* a corner stamp tag */}
              <div style={{
                position:"absolute", left:-10, top:-10,
                padding:"4px 10px", background:"var(--ink)", color:"var(--paper)",
                fontFamily:"var(--mono)", fontSize:11, letterSpacing:".1em", textTransform:"uppercase",
                borderRadius:99, transform:"rotate(-3deg)",
                pointerEvents:"none", zIndex:50,
              }}>{z.label}</div>
            </div>
          ))}

          {/* scenery */}
          {SCENERY.map((s) => (
            <Scenery key={s.id} item={s} onEgg={onEgg} />
          ))}

          {/* bird — orbits at idle */}
          <Bird />

          {/* little hidden zen garden top right — easter egg "garden" */}
          <ZenGarden onEgg={() => onEgg("garden")} />

          {/* balloons — released from right-clicks; float up forever */}
          {balloons.map((b) => (
            <BalloonRising key={b.id} b={b} />
          ))}

          {/* welcome stamp — placed near tagline so it's in initial view */}
          <div style={{
            position:"absolute", left: 1080, top: 970,
            padding:"8px 14px", border:"1.5px dashed var(--ink)", borderRadius:99,
            fontFamily:"var(--mono)", fontSize:11, letterSpacing:".1em", textTransform:"uppercase",
            color:"var(--ink)", background:"rgba(242,235,220,.6)",
            transform:"rotate(-3deg)", pointerEvents:"none",
          }}>
            ↳ drag the ground · arrows to walk
          </div>
        </div>
      </div>

      {/* edge pan glow (viewport-fixed) — driven via ref classes by physics loop */}
      <div ref={edgeRefs.l} className="edge left" />
      <div ref={edgeRefs.r} className="edge right" />
      <div ref={edgeRefs.t} className="edge top" />
      <div ref={edgeRefs.b} className="edge bottom" />

      {/* mini-map (viewport-fixed) — reads camRef directly via internal RAF */}
      <Minimap cameraRef={camRef} />
    </>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Wordmark — fragmented, mouse-parallax
// ─────────────────────────────────────────────────────────────────────────
function Wordmark({ fireworkSeed }) {
  const letters = ["f","r","g","m","t"];
  const colors  = ["var(--ink)","var(--cherry)","var(--ink)","var(--sky)","var(--ink)"];
  const refs = letters.map(() => wUseRef(null));
  // local mousemove → write transform via ref (no re-render)
  wUseEffect(() => {
    const onMove = (e) => {
      const px = (e.clientX / innerWidth - 0.5) * 2;
      const py = (e.clientY / innerHeight - 0.5) * 2;
      refs.forEach((ref, i) => {
        const el = ref.current; if (!el) return;
        const drift = (i - 2) * 0.6;
        const tx = px * (10 + i*3) - drift*4;
        const ty = py * (6 + i);
        const rot = (i - 2) * -1.2 + py * 1.8;
        el.style.transform = `translate(${tx}px, ${ty}px) rotate(${rot}deg)`;
      });
    };
    window.addEventListener("mousemove", onMove);
    return () => window.removeEventListener("mousemove", onMove);
  }, []);

  // explode after fireworkSeed bumps
  const [exp, setExp] = wUseState(0);
  wUseEffect(() => {
    if (!fireworkSeed) return;
    setExp(1);
    refs.forEach((ref) => {
      const el = ref.current; if (!el) return;
      const tx = rand(-180, 180), ty = rand(-140, 140), rot = rand(-30, 30);
      el.style.transition = "transform .9s cubic-bezier(.25,1.6,.5,1)";
      el.style.transform = `translate(${tx}px, ${ty}px) rotate(${rot}deg)`;
    });
    const t = setTimeout(() => {
      setExp(0);
      refs.forEach((ref) => {
        const el = ref.current; if (!el) return;
        el.style.transition = "";
        el.style.transform = "";
      });
    }, 900);
    return () => clearTimeout(t);
  }, [fireworkSeed]);

  return (
    <div style={{
      position:"absolute", left: 800, top: 200, width: 1000,
      display:"flex", alignItems:"center", justifyContent:"center", gap: 12,
      fontFamily:"var(--display)", fontWeight:900,
      fontVariationSettings:'"wdth" 80',
      fontSize: 320, lineHeight: .9, letterSpacing: "-.04em",
      pointerEvents:"none", userSelect:"none",
    }}>
      {letters.map((L, i) => (
        <span key={i} ref={refs[i]} style={{
          display:"inline-block",
          color: colors[i],
          transition: "color .3s, transform .15s linear",
          position:"relative",
          willChange:"transform",
        }}>
          {L}
          {i === 1 && (<span style={{
            position:"absolute", left:"30%", top:"40%", width:"40%", height:6,
            background:"var(--paper)", transform:"rotate(-12deg)",
          }} />)}
          {i === 3 && (<span style={{
            position:"absolute", left:"20%", top:"60%", width:"60%", height:8,
            background:"var(--paper)", transform:"rotate(10deg)",
          }} />)}
          {(i === 0 || i === 4) && (
            <span aria-hidden="true" style={{
              position:"absolute", inset:0,
              color: i === 0 ? "var(--gum)" : "var(--grass)",
              transform:"translate(10px, 10px)",
              clipPath:"polygon(0 60%, 100% 60%, 100% 100%, 0 100%)",
              opacity:.85,
            }}>{L}</span>
          )}
        </span>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   ParkPaths — dotted SVG paths in world coords
// ─────────────────────────────────────────────────────────────────────────
function ParkPaths() {
  return (
    <svg style={{ position:"absolute", left:0, top:0, width: WORLD.w, height: WORLD.h, pointerEvents:"none" }}>
      <defs>
        <pattern id="dots" patternUnits="userSpaceOnUse" width="10" height="10">
          <circle cx="2" cy="2" r="1.6" fill="var(--ink)" opacity=".35" />
        </pattern>
      </defs>
      {/* slide → sandbox */}
      <path d="M 670 1450 Q 1000 1530, 1380 1500" fill="none" stroke="url(#dots)" strokeWidth="8" />
      {/* sandbox → swings */}
      <path d="M 1700 1500 Q 2100 1430, 2480 1450" fill="none" stroke="url(#dots)" strokeWidth="8" />
      {/* loop near welcome to sandbox */}
      <path d="M 880 1480 Q 950 1620, 1300 1620" fill="none" stroke="url(#dots)" strokeWidth="6" />
    </svg>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Drifting confetti shards — world layer
// ─────────────────────────────────────────────────────────────────────────
function DriftShards() {
  const tick = useTick(1);
  const items = wUseMemo(() => Array.from({ length: 36 }).map((_, i) => ({
    id: i,
    x: Math.random() * WORLD.w,
    y: 100 + Math.random() * 600,
    s: 5 + Math.random() * 14,
    rot: Math.random() * 360,
    speed: 0.04 + Math.random() * 0.14,
    color: ["var(--cherry)","var(--sun)","var(--sky)","var(--grass)","var(--gum)","var(--plum)"][i % 6],
    shape: i % 3,
  })), []);
  return (
    <div style={{ position:"absolute", inset:0, pointerEvents:"none" }}>
      {items.map(it => {
        const x = ((it.x + tick * it.speed * 60) % (WORLD.w + 200) - 100);
        const y = it.y + Math.sin(tick + it.id) * 8;
        const r = it.rot + tick * (it.speed*60);
        return (
          <div key={it.id} style={{
            position:"absolute", left: x, top: y,
            width: it.s, height: it.s,
            transform: `rotate(${r}deg)`,
            background: it.shape === 1 ? "transparent" : it.color,
            border: it.shape === 1 ? `2px solid ${it.color}` : "none",
            borderRadius: it.shape === 2 ? "50%" : 0,
            opacity: .85,
          }} />
        );
      })}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Scenery — sun, clouds, trees, bench, flag, stamp
// ─────────────────────────────────────────────────────────────────────────
function Scenery({ item, onEgg }) {
  const tick = useTick(1);
  const { x, y, kind, easter } = item;
  const interactive = !!easter;

  const cursor = easter === "bench" ? "sit" : easter ? "hot" : undefined;
  const handle = (e) => {
    if (!easter) return;
    e.stopPropagation();
    onEgg(easter);
  };
  const bob = Math.sin(tick * 0.6 + (kind.length + x*0.001)) * (kind === "cloud" ? 8 : 2);

  let body = null;
  switch (kind) {
    case "sun":   body = <SunIcon size={120} />; break;
    case "cloud": body = <CloudIcon size={140 + (item.id?.length || 0) * 6} />; break;
    case "tree":  body = <TreeIcon size={140} />; break;
    case "bench": body = <BenchIcon size={120} />; break;
    case "flag":  body = <FlagIcon size={70} />; break;
    case "stamp":
      body = (
        <div style={{
          padding:"10px 18px", border:"2px solid var(--ink)", borderRadius:16,
          background:"transparent", color:"var(--ink)",
          fontFamily:"var(--mono)", fontWeight:700, fontSize:32, letterSpacing:".06em",
          transform:"rotate(-6deg)",
        }}>00 / index</div>
      );
      break;
    default: body = null;
  }

  return (
    <div data-cursor={cursor}
      onClick={handle} onMouseDown={(e) => interactive && e.stopPropagation()}
      style={{
        position:"absolute", left: x, top: y,
        transform: `translateY(${bob}px)`,
        transition: "transform .6s ease-in-out",
      }}>
      {body}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Bird — orbits across the sky every so often
// ─────────────────────────────────────────────────────────────────────────
function Bird() {
  const tick = useTick(1);
  const t = (tick * 0.06) % 1;
  const x = lerp(-80, WORLD.w + 80, t);
  const y = 150 + Math.sin(tick * 0.6) * 30;
  const dir = 1;
  const flap = Math.sin(tick * 8) * 0.6;
  return (
    <div style={{
      position:"absolute", left: x, top: y,
      transform:`scaleY(${1 + flap*0.3}) scaleX(${dir})`,
    }}>
      <BirdIcon size={28} color="var(--ink)" />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Hidden zen garden — top-right of world. Click pebbles to chime.
// ─────────────────────────────────────────────────────────────────────────
function ZenGarden({ onEgg }) {
  const cellsRef = wUseRef(null);
  const [pebbles, setPebbles] = wUseState(() =>
    Array.from({ length: 7 }).map((_, i) => ({
      id: i,
      x: 30 + i * 18 + (i%2) * 8,
      y: 30 + (i%3) * 16,
      hit: false,
    }))
  );
  const allHit = pebbles.every(p => p.hit);
  wUseEffect(() => {
    if (allHit) onEgg();
  }, [allHit]);
  return (
    <div style={{
      position:"absolute", left: WORLD.w - 360, top: 140, width: 280, height: 140,
      border:"1.5px dashed rgba(14,14,14,.3)", borderRadius:18,
      background:"rgba(242,235,220,.4)",
    }}>
      <div style={{
        position:"absolute", left:10, top:8,
        fontFamily:"var(--mono)", fontSize:10, letterSpacing:".08em",
        textTransform:"uppercase", opacity:.45,
      }}>// pebbles</div>
      {/* sand swirls */}
      <svg style={{ position:"absolute", inset:0 }}>
        <path d="M 12 70 Q 90 50, 130 80 T 260 70" fill="none" stroke="rgba(14,14,14,.15)" strokeWidth="1.5" />
        <path d="M 12 100 Q 90 80, 130 110 T 260 100" fill="none" stroke="rgba(14,14,14,.12)" strokeWidth="1.5" />
      </svg>
      {pebbles.map(p => (
        <button key={p.id} data-cursor="hot"
          onClick={(e) => {
            e.stopPropagation();
            setPebbles(arr => arr.map(x => x.id === p.id ? { ...x, hit: true } : x));
          }}
          onMouseDown={(e) => e.stopPropagation()}
          style={{
            position:"absolute", left: 110 + p.x, top: 30 + p.y,
            width: 18, height: 14, borderRadius: "50%",
            background: p.hit ? "var(--cherry)" : "var(--ink)",
            border:"1.5px solid var(--ink)",
            transition:"background .3s, transform .3s",
            transform: p.hit ? "scale(1.1)" : "scale(1)",
          }}/>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Balloon — rises forever from spawn point in world coords
// ─────────────────────────────────────────────────────────────────────────
function BalloonRising({ b }) {
  const tick = useTick(1);
  const dy = (performance.now() - b.born) / 12;
  const sway = Math.sin((tick + b.id*0.3) * 1.2) * 14;
  return (
    <div style={{
      position:"absolute", left: b.x + sway, top: b.y - dy,
      pointerEvents:"none",
    }}>
      <BalloonIcon size={30} color={b.color} />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
//   Minimap (viewport-fixed)
// ─────────────────────────────────────────────────────────────────────────
function Minimap({ cameraRef }) {
  const vpRef = wUseRef(null);
  // self-RAF: read camRef and write inline style on the viewport rect
  wUseEffect(() => {
    let raf;
    const tick = () => {
      const el = vpRef.current;
      const c = cameraRef.current;
      if (el && c) {
        const sx = 140 / WORLD.w, sy = 80 / WORLD.h;
        el.style.left = (-c.x * sx) + "px";
        el.style.top  = (-c.y * sy) + "px";
        el.style.width  = (window.innerWidth * sx) + "px";
        el.style.height = (window.innerHeight * sy) + "px";
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [cameraRef]);

  const sx = 140 / WORLD.w, sy = 80 / WORLD.h;
  return (
    <div className="minimap" data-cursor="hot" onMouseDown={(e) => e.stopPropagation()}
      onClick={(e) => {
        e.stopPropagation();
        const r = e.currentTarget.getBoundingClientRect();
        const px = (e.clientX - r.left) / r.width;
        const py = (e.clientY - r.top) / r.height;
        const targetX = -(px * WORLD.w - window.innerWidth/2);
        const targetY = -(py * WORLD.h - window.innerHeight/2);
        window.dispatchEvent(new CustomEvent("frgmt:goto-xy", { detail: { x: targetX, y: targetY }}));
      }}>
      {Object.entries(ZONES).map(([id, z]) => (
        <span key={id} className="minimap__poi" style={{
          left: (z.x + z.w/2) * sx - 3,
          top:  (z.y + z.h/2) * sy - 3,
          background: id === "slide" ? "var(--cherry)" : id === "sandbox" ? "var(--sun)" : "var(--sky)",
        }} />
      ))}
      <span ref={vpRef} className="minimap__viewport" />
    </div>
  );
}

Object.assign(window, { World, ZONES, WORLD });
