import { useEffect, useRef, useState } from "react"; import { Stage, Layer, Rect, Circle, Text, Group, Line, Star } from "react-konva"; import type { KonvaEventObject } from "konva/lib/Node"; import { buildPins, clamp, type PinPos } from "../lib/worldMap"; interface Props { regions: string[]; landmarks: string[]; pins: Record; selected: string | null; onSelect: (name: string | null) => void; onPinMove: (name: string, x: number, y: number) => void; } export function WorldMap({ regions, landmarks, pins, selected, onSelect, onPinMove }: Props) { const containerRef = useRef(null); const [size, setSize] = useState({ w: 600, h: 420 }); // Measure the container so the canvas is responsive. 5:7-ish aspect. useEffect(() => { const el = containerRef.current; if (!el) return; const update = () => { const w = el.clientWidth; setSize({ w, h: Math.round(w * 0.62) }); }; update(); const ro = new ResizeObserver(update); ro.observe(el); return () => ro.disconnect(); }, []); // Build the pin list: saved positions win, else auto-layout. Pure helper so // the layout math has a runnable self-check (see src/lib/worldMap.ts). const pinList: PinPos[] = buildPins(regions, landmarks, pins); const { w, h } = size; return (
) => { // Click on empty canvas deselects. if (e.target === e.target.getStage()) onSelect(null); }} className="rounded-xl border border-[var(--color-border-glass)]" > {/* Parchment-style chart background: deep navy with a faint gold grid. */} {/* Faint grid lines */} {Array.from({ length: 9 }, (_, i) => { const gx = (i + 1) * (w / 10); const gy = (i + 1) * (h / 10); return ( ); })} {/* Vignette frame */} {/* Compass rose (top-right) */} {pinList.map((pin) => { const px = pin.x * w; const py = pin.y * h; const isRegion = pin.kind === "region"; const isSelected = pin.name === selected; return ( { e.cancelBubble = true; onSelect(pin.name); }} onTap={(e) => { e.cancelBubble = true; onSelect(pin.name); }} onDragEnd={(e) => { // ponytail: record the clamped normalized position; the React // re-render repositions the node, so no manual snap-back here // (the closure `pin` would be stale and snap it to the old spot). onPinMove(pin.name, clamp(e.target.x() / w), clamp(e.target.y() / h)); }} > {isRegion ? ( <> ) : ( )} ); })}
); }