// ============================================================
// Husky Paths — WorldMap (paper atlas)
// FitScan editorial style: cream canvas, soft tonal landmasses,
// coral flight arcs from Seattle, concentric scan-mark pins.
// ============================================================

const { useState, useEffect, useRef, useMemo, useCallback } = React;

const MAP_STYLES = {
  huskies: {
    // UW Husky Purple as primary, Metallic Gold accents on lavender-pearl canvas.
    name: "Husky Purple",
    bg: "#F4F1F5",
    landFill: "#EAE3EF",
    landStroke: "#C4B8CE",
    graticule: "rgba(75, 46, 131, 0.07)",
    seaLabel: "rgba(75, 46, 131, 0.45)",
    arcColor: "#7A5AA8",       // muted UW purple
    arcActive: "#4B2E83",      // UW Husky Purple
    pinRing: "#4B2E83",
    pinDot: "#2A1A4E",
    pinActive: "#B7A57A",      // UW Metallic Gold
    originColor: "#4B2E83",
  },
  paper: {
    // Soft paper-atlas variant — all purple, no coral
    name: "Lavender Paper",
    bg: "#F4F1F5",
    landFill: "#EBE6EF",
    landStroke: "#C9BFD2",
    graticule: "rgba(75, 46, 131, 0.06)",
    seaLabel: "rgba(75, 46, 131, 0.4)",
    arcColor: "#8C6EBA",
    arcActive: "#4B2E83",
    pinRing: "#6E4FA8",
    pinDot: "#2A1A4E",
    pinActive: "#B7A57A",
    originColor: "#4B2E83",
  },
  minimal: {
    name: "Minimal",
    bg: "#F4F1F5",
    landFill: "#FFFFFF",
    landStroke: "#DDD4E2",
    graticule: "rgba(75, 46, 131, 0.05)",
    seaLabel: "rgba(75, 46, 131, 0.35)",
    arcColor: "#6E4FA8",
    arcActive: "#2A1A4E",
    pinRing: "#4B2E83",
    pinDot: "#2A1A4E",
    pinActive: "#B7A57A",
    originColor: "#2A1A4E",
  },
  warm: {
    // Deeper aubergine canvas, gold accents
    name: "Aubergine",
    bg: "#E8E1ED",
    landFill: "#F2EDF5",
    landStroke: "#B8A8C6",
    graticule: "rgba(75, 46, 131, 0.08)",
    seaLabel: "rgba(75, 46, 131, 0.5)",
    arcColor: "#5C3D8C",
    arcActive: "#2A1A4E",
    pinRing: "#4B2E83",
    pinDot: "#1C1024",
    pinActive: "#B7A57A",
    originColor: "#4B2E83",
  },
};

// Public CORS-friendly world geojson
const WORLD_GEOJSON = "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson";

const WorldMap = ({
  filteredProfiles,
  selectedCity,
  hoveredCity,
  onCityHover,
  onCityClick,
  onBackgroundClick,
  tweaks,
  setTweak,
  activeYear,
  setActiveYear,
  allYears,
  settled,
}) => {
  const svgRef = useRef(null);
  const gRef = useRef(null);          // 缩放/平移图层 — 直接改它的 transform，不走 React state
  const zoomRef = useRef(null);
  const lastTransform = useRef(null); // 记住当前缩放，重渲染后恢复
  const [geographies, setGeographies] = useState([]);
  const [geoErr, setGeoErr] = useState(false);

  // ── 球面(地球仪)状态 ──────────────────────────────
  const [rotation, setRotation] = useState([122, -30]);   // 初始对准西雅图、略俯视
  const rotationRef = useRef(rotation);
  useEffect(() => { rotationRef.current = rotation; }, [rotation]);
  const [globeScale, setGlobeScale] = useState(1);
  const [yearMenuOpen, setYearMenuOpen] = useState(false);   // 「更早」年份下拉
  useEffect(() => {
    if (!yearMenuOpen) return;
    const close = () => setYearMenuOpen(false);
    const id = setTimeout(() => document.addEventListener("click", close), 0);  // 延后挂载，避免开菜单那一下立刻关掉
    return () => { clearTimeout(id); document.removeEventListener("click", close); };
  }, [yearMenuOpen]);
  const globeScaleRef = useRef(globeScale);
  useEffect(() => { globeScaleRef.current = globeScale; }, [globeScale]);
  const dragStateRef = useRef(null);     // 拖拽旋转状态
  const justDraggedRef = useRef(false);  // 刚拖过 → 抑制背景点击(避免误取消选中)

  const style = MAP_STYLES[tweaks.mapStyle] || MAP_STYLES.paper;
  const isGlobe = tweaks.projection === "globe";

  // Fixed canvas (we scale via SVG viewBox)
  const W = 1600, H = 900;

  // 投影:flat = Natural Earth(平面);globe = Orthographic(可旋转地球)。
  // flat 模式下 rotation/globeScale 不变 → memo 实际稳定 → countryPaths 仍走缓存(性能不受影响)。
  const projection = useMemo(() => {
    if (isGlobe) {
      return d3.geoOrthographic()
        .scale(Math.min(W, H) * 0.42 * globeScale)
        .translate([W / 2, H / 2 + 10])
        .rotate(rotation)
        .clipAngle(90);
    }
    return d3.geoNaturalEarth1()
      .scale(280)
      .translate([W / 2, H / 2 + 30]);
  }, [isGlobe, rotation, globeScale]);

  const pathGen = useMemo(() => d3.geoPath(projection), [projection]);
  const graticule = useMemo(() => d3.geoGraticule().step([20, 20])(), []);

  // Load geojson
  useEffect(() => {
    let abort = false;
    fetch(WORLD_GEOJSON)
      .then((r) => r.json())
      .then((d) => { if (!abort) setGeographies(d.features); })
      .catch(() => { if (!abort) setGeoErr(true); });
    return () => { abort = true; };
  }, []);

  // d3 zoom — 关键性能点：缩放/平移只改 <g> 的 transform 属性（GPU 合成），
  // 不再 setState，因此不会触发整张地图的 React 重渲染。
  useEffect(() => {
    if (!svgRef.current) return;
    const sel = d3.select(svgRef.current);
    sel.on(".zoom", null);
    if (isGlobe) {
      // 球面模式:不在 <g> 上做缩放/平移变换(由 projection 的 rotate/scale 负责),
      // 清掉平面模式残留的 transform。
      lastTransform.current = null;
      if (gRef.current) {
        gRef.current.classList.remove("hp-g-animating");
        gRef.current.setAttribute("transform", "");
      }
      return;
    }
    const z = d3.zoom()
      .scaleExtent([1, 8])
      .on("zoom", (e) => {
        const t = e.transform.toString();
        lastTransform.current = t;
        if (gRef.current) {
          // 程序化缩放（聚焦/按钮，sourceEvent 为空）→ 开 CSS 过渡做平滑动画；
          // 用户滚轮/拖动（有 sourceEvent）→ 关过渡，即时跟手不卡。
          if (e.sourceEvent) gRef.current.classList.remove("hp-g-animating");
          else gRef.current.classList.add("hp-g-animating");
          gRef.current.setAttribute("transform", t);
        }
      });
    zoomRef.current = z;
    sel.call(z);
    return () => { sel.on(".zoom", null); };
  }, [isGlobe]);

  // 每次重渲染后把缩放视图复原（filter/选中态变化会重渲染 <g>，避免视图被重置）
  React.useLayoutEffect(() => {
    if (!gRef.current) return;
    if (isGlobe) { gRef.current.setAttribute("transform", ""); return; }
    if (lastTransform.current) gRef.current.setAttribute("transform", lastTransform.current);
  });

  // ── 球面辅助:数值补间(本项目 d3.transition 失效 → rAF 自定义补间)──
  const animateScaleTo = useCallback((target, dur = 600) => {
    const from = globeScaleRef.current;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / dur);
      const e = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
      setGlobeScale(from + (target - from) * e);
      if (t < 1) requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
  }, []);

  const animateRotationTo = useCallback((target, dur = 900) => {
    const from = rotationRef.current;
    const to = [...target];
    const dLon = to[0] - from[0];
    if (dLon > 180) to[0] -= 360;
    if (dLon < -180) to[0] += 360;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / dur);
      const e = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
      setRotation([from[0] + (to[0] - from[0]) * e, from[1] + (to[1] - from[1]) * e]);
      if (t < 1) requestAnimationFrame(tick);
    };
    requestAnimationFrame(tick);
  }, []);

  // 球面拖拽旋转(指针事件覆盖触摸+鼠标;move/up 挂 window 以免抢走子元素点击;rAF 节流防抖)
  const onSvgPointerDown = useCallback((e) => {
    if (!isGlobe || e.button !== 0) return;
    const svgEl = svgRef.current;
    if (!svgEl) return;
    const rect = svgEl.getBoundingClientRect();
    const scaleX = (W / rect.width) * 0.35;
    const scaleY = (H / rect.height) * 0.35;
    const s = { startX: e.clientX, startY: e.clientY, startRot: [...rotationRef.current], pending: null, raf: 0, dragged: false, scaleX, scaleY };
    dragStateRef.current = s;
    const onMove = (ev) => {
      const st = dragStateRef.current;
      if (!st) return;
      const dx = ev.clientX - st.startX, dy = ev.clientY - st.startY;
      if (!st.dragged && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) { st.dragged = true; svgEl.style.cursor = "grabbing"; }
      if (!st.dragged) return;  // 微小移动忽略 → 点击仍能命中图钉
      st.pending = [st.startRot[0] + dx * st.scaleX, Math.max(-85, Math.min(85, st.startRot[1] - dy * st.scaleY))];
      if (!st.raf) {
        st.raf = requestAnimationFrame(() => {
          if (dragStateRef.current && dragStateRef.current.pending) { setRotation(dragStateRef.current.pending); dragStateRef.current.raf = 0; }
        });
      }
    };
    const onUp = () => {
      const st = dragStateRef.current;
      if (st) { if (st.raf) cancelAnimationFrame(st.raf); if (st.dragged) justDraggedRef.current = true; }
      dragStateRef.current = null;
      if (svgEl) svgEl.style.cursor = "grab";
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
      window.removeEventListener("pointercancel", onUp);
    };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
    window.addEventListener("pointercancel", onUp);
  }, [isGlobe]);

  // 背景点击:刚拖完地球则忽略(避免旋转后误取消选中)
  const onSvgClick = useCallback(() => {
    if (justDraggedRef.current) { justDraggedRef.current = false; return; }
    onBackgroundClick();
  }, [onBackgroundClick]);

  // 选中城市 → 平滑聚焦。flat:即时 zoom.transform(GPU transform 动画);globe:旋转地球。
  // ⚠️ 依赖里不放 projection — 否则球面每帧旋转都重触发本 effect、把地球拽回原位 → 抖动。
  useEffect(() => {
    if (!svgRef.current) return;
    if (!selectedCity) {
      if (isGlobe) animateRotationTo([122, -30], 700);
      else if (zoomRef.current) d3.select(svgRef.current).call(zoomRef.current.transform, d3.zoomIdentity);
      return;
    }
    const city = CITIES.find((c) => c.name === selectedCity);
    if (!city) return;
    if (isGlobe) {
      const [lon, lat] = city.coords;
      animateRotationTo([-lon + 20, -lat], 900);  // +20° 把城市偏左,给右侧 Spotlight 留位
      return;
    }
    if (!zoomRef.current) return;
    const target = projection(city.coords);
    if (!target) return;
    const focusX = W * 0.38, focusY = H * 0.5;
    const kk = selectedCity === "Seattle" ? 1.25 : 1.4;   // Seattle 1.25 / 其他城市 1.4
    const next = d3.zoomIdentity.translate(focusX - target[0] * kk, focusY - target[1] * kk).scale(kk);
    d3.select(svgRef.current).call(zoomRef.current.transform, next);
  }, [selectedCity, isGlobe, animateRotationTo]);

  // 即时缩放（d3 transition 在本项目环境失效，改即时 + CSS 过渡做动画）；球面则缩放球体/旋转复位
  const zoomIn  = () => {
    if (isGlobe) { animateScaleTo(Math.min(2.6, globeScaleRef.current * 1.3), 300); return; }
    d3.select(svgRef.current).call(zoomRef.current.scaleBy, 1.5);
  };
  const zoomOut = () => {
    if (isGlobe) { animateScaleTo(Math.max(0.75, globeScaleRef.current / 1.3), 300); return; }
    d3.select(svgRef.current).call(zoomRef.current.scaleBy, 1 / 1.5);
  };
  const zoomReset = () => {
    if (isGlobe) { animateRotationTo([122, -30], 700); animateScaleTo(1, 400); return; }
    d3.select(svgRef.current).call(zoomRef.current.transform, d3.zoomIdentity);
  };

  // Group profiles by city
  const cityGroups = useMemo(() => {
    const groups = {};
    for (const p of filteredProfiles) {
      if (!groups[p.city]) {
        const ci = CITIES.find((c) => c.name === p.city);
        if (!ci) continue;
        groups[p.city] = { ...ci, count: 0, profiles: [] };
      }
      if (groups[p.city]) {
        groups[p.city].count++;
        groups[p.city].profiles.push(p);
      }
    }
    // ensure Seattle is always present (origin marker even if no filtered profiles)
    if (!groups["Seattle"]) {
      const ci = CITIES.find((c) => c.name === "Seattle");
      groups["Seattle"] = { ...ci, count: 0, profiles: [] };
    }
    return Object.values(groups);
  }, [filteredProfiles]);

  // 缓存每个国家的 path d（投影固定，加载后只算一次），避免重渲染时重复跑 ~200 次 geoPath
  const countryPaths = useMemo(
    () => geographies.map((geo, i) => ({ key: geo.id || i, d: pathGen(geo) })),
    [geographies, pathGen]
  );

  const originCoords = projection(SEATTLE_COORDS);
  // 尺寸基准固定为 1：缩放统一由 <g> 的 transform 处理（元素随地图一起缩放），
  // 避免每帧重算每个元素的线宽/半径/字号。
  const k = 1;

  // 球面背面剔除:隐藏背对视角那半球的图钉/弧线。可视半球中心 = rotation 取负;
  // d3.geoDistance 返回弧度,> 90° 即在背面。flat 模式恒为 true(不剔除)。
  const globeCenter = [-rotation[0], -rotation[1]];
  const onNearSide = (coords) =>
    !isGlobe || d3.geoDistance(coords, globeCenter) < (Math.PI / 2) - 0.02;

  // Animate arcs: stroke-dashoffset
  const arcDashLen = 8;

  return (
    <div className="hp-map-wrap" style={{ background: style.bg }}>
      {/* Subtle paper grain — radial gradient noise */}
      <div className="hp-map-grain" />

      <svg
        ref={svgRef}
        viewBox={`0 0 ${W} ${H}`}
        preserveAspectRatio="xMidYMid slice"
        className={`hp-map-svg ${isGlobe ? "is-globe" : ""}`}
        onClick={onSvgClick}
        onPointerDown={onSvgPointerDown}
      >
        <defs>
          {/* 阴影/模糊滤镜已移除：对 200 个国家整体实时算 drop-shadow 是手机端最大的性能杀手，
              改用大陆描边补偿视觉层次。 */}

          {/* Animated dash for active arc */}
          <style>{`
            .hp-arc-active { animation: hp-arc-dash 1.6s linear infinite; }
            @keyframes hp-arc-dash {
              from { stroke-dashoffset: 24; }
              to   { stroke-dashoffset: 0; }
            }
          `}</style>
        </defs>

        <g ref={gRef}>
          {/* Globe: 球体海洋底 + 边缘高光（仅球面模式;单层圆,无滤镜以保性能） */}
          {isGlobe && (() => {
            const sphereR = Math.min(W, H) * 0.42 * globeScale;
            const cx = W / 2, cy = H / 2 + 10;
            return (
              <g pointerEvents="none">
                <circle cx={cx} cy={cy} r={sphereR + 5} fill="none" stroke={style.landStroke} strokeWidth="1.2" opacity="0.5" />
                <circle cx={cx} cy={cy} r={sphereR} fill="#ECE7F0" />
                <circle cx={cx} cy={cy} r={sphereR} fill="none" stroke="#4B2E83" strokeWidth="1" opacity="0.22" />
              </g>
            );
          })()}

          {/* Graticule */}
          <path d={pathGen(graticule)} fill="none" stroke={style.graticule} strokeWidth={1 / k} className="hp-map-graticule" />

          {/* Pacific Ocean editorial label */}
          {(() => {
            const p = projection([-160, 25]);
            if (!p) return null;
            if (isGlobe && !onNearSide([-160, 25])) return null;
            const fs = 14 / k;
            return (
              <g transform={`translate(${p[0]}, ${p[1]})`} className="hp-pacific-label" pointerEvents="none">
                <text textAnchor="middle" fill={style.seaLabel} fontFamily="Lora, serif" fontStyle="italic" fontSize={fs} letterSpacing={fs * 0.05}>
                  Pacific Ocean
                </text>
                <text y={fs * 1.3} textAnchor="middle" fill={style.seaLabel} fontFamily="Lora, serif" fontStyle="italic" fontSize={fs * 0.7} letterSpacing={fs * 0.04}>
                  太平洋
                </text>
              </g>
            );
          })()}

          {/* Continents（滤镜已移除以提升移动端性能） */}
          <g>
            {countryPaths.map((c) => (
              <path
                key={c.key}
                d={c.d}
                fill={style.landFill}
                stroke={style.landStroke}
                strokeWidth={0.8 / k}
                strokeLinejoin="round"
              />
            ))}
          </g>

          {/* Flight arcs from Seattle — only to cities that have visible pins */}
          {originCoords && cityGroups.map((city, i) => {
            if (city.name === "Seattle") return null;
            if (city.count === 0) return null;
            const dest = projection(city.coords);
            if (!dest) return null;
            // 球面:跨到背面半球的弧线不画
            if (isGlobe && (!onNearSide(city.coords) || !onNearSide(SEATTLE_COORDS))) return null;

            const dx = dest[0] - originCoords[0];
            const dy = dest[1] - originCoords[1];
            const dr = Math.sqrt(dx * dx + dy * dy) * 1.3;
            const curve = `M ${originCoords[0]},${originCoords[1]} A ${dr},${dr} 0 0,1 ${dest[0]},${dest[1]}`;

            const isActive = selectedCity === city.name || hoveredCity === city.name;
            const isDimmed = !!selectedCity && !isActive;   // 选中某城市时，其它弧线压暗，突出选中
            const useDash = tweaks.arcStyle === "dashed" && !isActive;
            // Offset so arcs draw AFTER the intro splash fades (~1.9s).
            // Once the page is "settled" (>3.5s after mount), filter changes
            // re-render arcs with no delay.
            const baseOffset = settled ? 0 : 1.9;
            const drawDelay = baseOffset + Math.min(i, 12) * 0.045;

            // Different entrance motion per style:
            // - Solid arcs: draw-in via stroke-dashoffset (pathLength=1 normalized)
            // - Dashed arcs: fade-in (the dash pattern is its own motion)
            return (
              <g key={`arc-${city.name}`} pointerEvents="none">
                <path
                  d={curve}
                  fill="none"
                  stroke={isActive ? style.arcActive : style.arcColor}
                  strokeWidth={(isActive ? 1.8 : 1.2) / k}
                  strokeLinecap="round"
                  opacity={isActive ? 1 : isDimmed ? 0.12 : 0.65}
                  {...(useDash
                    ? {
                        strokeDasharray: `${5/k} ${4/k}`,
                        className: "hp-arc hp-arc-fade",
                        style: { animationDelay: `${drawDelay}s` },
                      }
                    : {
                        pathLength: "1",
                        strokeDasharray: "1 1",
                        className: "hp-arc hp-arc-draw",
                        style: { animationDelay: `${drawDelay}s` },
                      }
                  )}
                />
              </g>
            );
          })}

          {/* Pins — destinations first */}
          {cityGroups.filter((c) => c.name !== "Seattle").map((city) => {
            const coords = projection(city.coords);
            if (!coords) return null;
            if (isGlobe && !onNearSide(city.coords)) return null;   // 球面:背面图钉不画
            const isSelected = selectedCity === city.name;
            const isHovered = hoveredCity === city.name;
            const isActive = isSelected || isHovered;
            if (city.count === 0) return null;

            const base = 3 + Math.min(5, city.count * 0.6);   // 调小 pin（原 4 + min(7, *0.8)）
            const r = base / k;
            const ringR = r * 1.9;                              // 原 2.2
            const haloR = r * 3;                                // 原 3.4
            // Always-comfortable hit target (24px diameter min, scaled to zoom)
            const hitR = Math.max(ringR * 1.6, 14 / k);

            const ringColor = isActive ? style.pinActive : style.pinRing;
            const dotColor = style.pinDot;

            const showLabel = tweaks.showLabels || isActive;
            const labelFs = Math.max(9, 13 / k);
            const labelDy = -ringR - 4 / k;

            return (
              <g
                key={`pin-${city.name}`}
                transform={`translate(${coords[0]}, ${coords[1]})`}
                className={`hp-pin ${isActive ? "is-active" : ""}`}
                onMouseEnter={() => onCityHover(city.name)}
                onMouseLeave={() => onCityHover(null)}
                onClick={(e) => { e.stopPropagation(); onCityClick(city.name); }}
                style={{ cursor: "pointer" }}
              >
                {/* Invisible larger hit target for easier clicking */}
                <circle r={hitR} fill="transparent" />

                {/* Visual group — scales on hover via CSS */}
                <g className="hp-pin-visual">
                  {/* soft filled halo (static — no animation flicker) */}
                  <circle r={ringR * 1.9} fill={ringColor} opacity={isActive ? 0.16 : 0} />
                  {/* main ring */}
                  <circle
                    r={ringR}
                    fill="none"
                    stroke={ringColor}
                    strokeWidth={(isActive ? 1.8 : 1.4) / k}
                    opacity={isActive ? 1 : 0.55}
                  />
                  {/* inner dot */}
                  <circle r={r} fill={isActive ? ringColor : dotColor} stroke={style.bg} strokeWidth={1.4 / k} />
                  {city.count >= 2 && tweaks.pinCount && (
                    <text textAnchor="middle" dy={r * 0.35} fontFamily="Poppins, sans-serif" fontWeight="700" fontSize={r * 1.1} fill={style.bg} pointerEvents="none">
                      {city.count}
                    </text>
                  )}
                </g>

                {/* Hover hint badge — fades in below the pin, hides once selected
                    (the spotlight panel takes over the info display). */}
                <g
                  transform={`translate(0, ${ringR + 18/k})`}
                  pointerEvents="none"
                  className="hp-pin-hint"
                  style={{ opacity: (isHovered && !isSelected) ? 1 : 0 }}
                >
                  <rect
                    x={-44/k} y={-9/k}
                    width={88/k} height={18/k}
                    rx={9/k} ry={9/k}
                    fill={style.bg}
                    stroke={ringColor}
                    strokeWidth={1 / k}
                  />
                  <text
                    textAnchor="middle"
                    dy={3.5/k}
                    fontFamily="Poppins, sans-serif" fontWeight="600"
                    fontSize={9.5 / k}
                    fill={ringColor}
                    letterSpacing={0.4 / k}
                  >
                    {city.count} {city.count === 1 ? "PATH" : "PATHS"} →
                  </text>
                </g>

                {showLabel && (
                  <g pointerEvents="none">
                    <text
                      textAnchor="middle"
                      dy={labelDy}
                      fontFamily="Poppins, sans-serif"
                      fontWeight={isActive ? 700 : 600}
                      fontSize={labelFs}
                      fill="#0F0E0E"
                      style={{ paintOrder: "stroke", stroke: style.bg, strokeWidth: 3 / k, strokeLinejoin: "round" }}
                    >
                      {window.__HP_LANG === "zh" && city.zh ? city.zh : city.name}
                    </text>
                  </g>
                )}
              </g>
            );
          })}

          {/* ── Origin pin (Seattle / UW) — distinct, always on top ── */}
          {(() => {
            if (!originCoords) return null;
            if (isGlobe && !onNearSide(SEATTLE_COORDS)) return null;   // 球面:西雅图转到背面则不画
            const isActive = selectedCity === "Seattle" || hoveredCity === "Seattle";

            // UW-themed accent: purple #4B2E83 + metallic gold #B7A57A
            // (one-off override for the origin only; rest of map stays FitScan-warm)
            const uwPurple = "#4B2E83";
            const uwGold   = "#B7A57A";

            // Sizes (in unscaled px at k=1, divided by k for zoom consistency)
            const badgeR  = 14 / k;       // filled badge radius (the "coin")
            const ringR1  = 22 / k;       // first concentric ring
            const ringR2  = 30 / k;       // outer ring
            const haloR1  = 40 / k;       // pulsing halo
            const haloR2  = 52 / k;       // outer pulsing halo
            const tickR   = 24 / k;       // crosshair tick start
            const tickEnd = 32 / k;

            // Stylized block-W glyph path (clean geometric W — sized for the badge)
            // Drawn in a 24×24 viewport then scaled to fit badge.
            const wScale = (badgeR * 1.55) / 24;
            const wPath = "M -10 -8 L -6 -8 L -3 5 L 0 -3 L 3 5 L 6 -8 L 10 -8 L 5 9 L 1 9 L 0 4 L -1 9 L -5 9 Z";

            const flagH   = 22 / k;
            const flagY   = -ringR2 - 14 / k;
            const labelFs = Math.max(11, 14 / k);

            return (
              <g
                key="pin-Seattle"
                transform={`translate(${originCoords[0]}, ${originCoords[1]})`}
                className="hp-pin hp-pin-origin"
                onMouseEnter={() => onCityHover("Seattle")}
                onMouseLeave={() => onCityHover(null)}
                onClick={(e) => { e.stopPropagation(); onCityClick("Seattle"); }}
                style={{ cursor: "pointer" }}
              >
                {/* Pulsing halos */}
                <circle r={haloR1} fill="none" stroke={uwPurple} strokeWidth={1 / k} opacity="0.45" className="hp-pin-halo" />
                <circle r={haloR2} fill="none" stroke={uwPurple} strokeWidth={0.7 / k} opacity="0.28" className="hp-pin-halo" style={{ animationDelay: "1.2s" }} />

                {/* Outer rings (concentric scan mark) */}
                <circle r={ringR2} fill="none" stroke={uwPurple} strokeWidth={1.2 / k} opacity="0.55" />
                <circle r={ringR1} fill="none" stroke={uwGold}   strokeWidth={1.8 / k} opacity="0.85" />

                {/* Crosshair compass ticks (N/S/E/W) */}
                {[
                  { x1:-tickEnd, y1:0,   x2:-tickR, y2:0   },
                  { x1: tickR,   y1:0,   x2: tickEnd, y2:0 },
                  { x1: 0, y1:-tickEnd, x2: 0, y2:-tickR   },
                  { x1: 0, y1: tickR,   x2: 0, y2: tickEnd },
                ].map((l, i) => (
                  <line key={i} x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2}
                        stroke={uwPurple} strokeWidth={1.2 / k} strokeLinecap="round" opacity="0.75" />
                ))}

                {/* Cardinal letters around the rim — gold compass feel */}
                {[
                  { l: "N", x: 0, y: -ringR2 - 6/k },
                  { l: "S", x: 0, y:  ringR2 + 10/k },
                  { l: "E", x:  ringR2 + 8/k, y: 3/k },
                  { l: "W", x: -ringR2 - 8/k, y: 3/k },
                ].map((p, i) => (
                  <text key={i} x={p.x} y={p.y} textAnchor="middle"
                        fontFamily="Poppins, sans-serif" fontWeight="700"
                        fontSize={9 / k} letterSpacing={0.4 / k}
                        fill={uwGold} opacity="0.7" pointerEvents="none">
                    {p.l}
                  </text>
                ))}

                {/* Solid filled badge (the "coin") with cream rim */}
                <circle r={badgeR + 2/k} fill={style.bg} />
                <circle r={badgeR} fill={uwPurple} />
                {/* Inner gold rim */}
                <circle r={badgeR - 2/k} fill="none" stroke={uwGold} strokeWidth={0.8 / k} opacity="0.6" />

                {/* Stylized W mark — gold on purple */}
                <g transform={`scale(${wScale})`} pointerEvents="none">
                  <path d={wPath} fill={uwGold} />
                </g>

                {/* Origin pill label above pin */}
                <g transform={`translate(0, ${flagY})`} pointerEvents="none">
                  <line x1="0" y1={flagH * 0.5} x2="0" y2={ringR2 + 2/k} stroke={uwPurple} strokeWidth={1.4 / k} opacity="0.6"/>
                  <rect
                    x={-110/k} y={-flagH * 0.5}
                    width={220/k} height={flagH}
                    rx={flagH * 0.5} ry={flagH * 0.5}
                    fill={uwPurple}
                  />
                  <text textAnchor="middle" dy={flagH * 0.12}
                    fontFamily="Poppins, sans-serif" fontWeight="700"
                    fontSize={10.5 / k} letterSpacing={1.2 / k}
                    fill={uwGold}>
                    UNIVERSITY OF WASHINGTON
                  </text>
                </g>

                {/* Editorial gloss below pin */}
                <text
                  textAnchor="middle"
                  dy={ringR2 + 22/k}
                  fontFamily="Lora, serif"
                  fontStyle="italic"
                  fontWeight="500"
                  fontSize={labelFs * 0.95}
                  fill={uwPurple}
                  pointerEvents="none"
                  style={{ paintOrder: "stroke", stroke: style.bg, strokeWidth: 4 / k, strokeLinejoin: "round" }}
                >
                  {t("Seattle", "西雅图")}
                </text>
                <text
                  textAnchor="middle"
                  dy={ringR2 + 36/k}
                  fontFamily="Poppins, sans-serif"
                  fontWeight="700"
                  fontSize={labelFs * 0.65}
                  fill={uwGold}
                  letterSpacing={1.5 / k}
                  pointerEvents="none"
                  style={{ paintOrder: "stroke", stroke: style.bg, strokeWidth: 3 / k, strokeLinejoin: "round" }}
                >
                  {t("WHERE IT BEGINS", "一切的起点")}
                </text>
              </g>
            );
          })()}
        </g>
      </svg>

      {/* Map error fallback */}
      {geoErr && (
        <div className="hp-map-err">
          <div className="hp-overline" style={{ color: "var(--accent-coral)" }}>NETWORK · 网络错误</div>
          <div className="hp-card-h">Couldn't load the world atlas.</div>
          <p className="hp-italic" style={{ marginTop: 8 }}>
            Showing pins only. Try refreshing — the geometry is fetched from a public mirror.
          </p>
        </div>
      )}

      {/* Loading state for landmasses */}
      {!geoErr && geographies.length === 0 && (
        <div className="hp-map-loading">
          <div className="hp-overline">LOADING · 加载中</div>
          <div className="hp-loading-mark">
            <div className="hp-loading-ring" />
            <div className="hp-loading-ring hp-loading-ring-2" />
            <div className="hp-loading-dot" />
          </div>
        </div>
      )}

      {/* Zoom controls — bottom right of map */}
      <div className="hp-zoom-controls">
        <button
          className={`hp-zoom-btn hp-view-toggle ${isGlobe ? "is-on" : ""}`}
          onClick={() => setTweak && setTweak("projection", isGlobe ? "flat" : "globe")}
          title={isGlobe ? "Switch to flat map · 切换平面地图" : "Spin the globe · 切换地球仪"}
          aria-pressed={isGlobe}
        >
          <HPIcons.Globe />
          <span>{isGlobe ? t("Flat view", "平面") : t("Globe view", "球面")}</span>
        </button>
        <button className="hp-zoom-btn" onClick={zoomIn} title="Zoom in">
          <HPIcons.Plus />
        </button>
        <button className="hp-zoom-btn" onClick={zoomOut} title="Zoom out">
          <HPIcons.Minus />
        </button>
        <button className="hp-zoom-btn hp-zoom-reset" onClick={zoomReset} title="Reset view">
          <span style={{ fontSize: 14, fontFamily: "Poppins", fontWeight: 700, letterSpacing: 0.5 }}>⊕</span>
        </button>
      </div>

      {/* Year scrubber — 近届药丸 + 「更早 ▾」折叠 */}
      {allYears && allYears.length > 0 && (() => {
        const yearsDesc = [...allYears].sort((a, b) => b - a);   // 新 → 旧
        const recent = yearsDesc.slice(0, 5);
        const older = yearsDesc.slice(5);
        const olderActive = activeYear != null && older.includes(activeYear);
        return (
          <div className="hp-year-scrub">
            <div className="hp-year-pills">
              <button
                className={`hp-year-pill ${!activeYear ? "is-active" : ""}`}
                onClick={() => { setActiveYear(null); setYearMenuOpen(false); }}
              >
                {t("All", "全部")}
              </button>
              {recent.map((y) => (
                <button
                  key={y}
                  className={`hp-year-pill ${activeYear === y ? "is-active" : ""}`}
                  onClick={() => { setActiveYear(activeYear === y ? null : y); setYearMenuOpen(false); }}
                >
                  '{String(y).slice(-2)}
                </button>
              ))}
              {older.length > 0 && (
                <div className="hp-year-more">
                  <button
                    className={`hp-year-pill ${olderActive ? "is-active" : ""}`}
                    onClick={(e) => { e.stopPropagation(); setYearMenuOpen((v) => !v); }}
                  >
                    {olderActive ? `'${String(activeYear).slice(-2)}` : t("Earlier", "更早")} ▾
                  </button>
                  {yearMenuOpen && (
                    <div className="hp-year-menu">
                      {older.map((y) => (
                        <button
                          key={y}
                          className={`hp-year-menu-item ${activeYear === y ? "is-active" : ""}`}
                          onClick={() => { setActiveYear(activeYear === y ? null : y); setYearMenuOpen(false); }}
                        >
                          {t(`Class of '${String(y).slice(-2)}`, `${y} 届`)}
                        </button>
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        );
      })()}

      {/* Decorative compass rose — bottom-left editorial detail */}
      <div className="hp-compass" aria-hidden>
        <svg viewBox="0 0 80 80" width="64" height="64">
          {/* outer ring */}
          <circle cx="40" cy="40" r="36" fill="none" stroke="#4B2E83" strokeWidth="0.6" opacity="0.35"/>
          <circle cx="40" cy="40" r="28" fill="none" stroke="#4B2E83" strokeWidth="0.5" opacity="0.25"/>
          <circle cx="40" cy="40" r="18" fill="none" stroke="#B7A57A" strokeWidth="0.7" opacity="0.55"/>
          {/* Cardinal star */}
          <polygon points="40,4 44,40 40,76 36,40" fill="#4B2E83" opacity="0.85"/>
          <polygon points="4,40 40,44 76,40 40,36" fill="#B7A57A" opacity="0.9"/>
          {/* Diagonal short ticks */}
          {[45, 135, 225, 315].map((deg) => {
            const r1 = 36, r2 = 30;
            const rad = (deg * Math.PI) / 180;
            return (
              <line key={deg}
                x1={40 + Math.cos(rad) * r1} y1={40 + Math.sin(rad) * r1}
                x2={40 + Math.cos(rad) * r2} y2={40 + Math.sin(rad) * r2}
                stroke="#4B2E83" strokeWidth="0.7" opacity="0.55"/>
            );
          })}
          {/* center dot */}
          <circle cx="40" cy="40" r="2.4" fill="#4B2E83"/>
          {/* N label */}
          <text x="40" y="3.5" textAnchor="middle" fontFamily="Poppins, sans-serif" fontWeight="700" fontSize="6" letterSpacing="0.6" fill="#4B2E83">N</text>
        </svg>
        <div className="hp-compass-meta">
          <div className="hp-overline" style={{ color: "var(--accent-coral)", fontSize: 9 }}>
            {t("HUSKY PATHS", "HUSKY PATHS · 校友足迹")}
          </div>
          <div className="hp-italic" style={{ fontSize: 10.5, marginTop: 2 }}>
            {t("Plotted from", "起点")} {SEATTLE_COORDS[1].toFixed(2)}° N, {Math.abs(SEATTLE_COORDS[0]).toFixed(2)}° W
          </div>
        </div>
      </div>

      {/* Hover tooltip */}
      {hoveredCity && (() => {
        const c = cityGroups.find((g) => g.name === hoveredCity);
        if (!c) return null;
        return (
          <div className="hp-hover-toast">
            <div className="hp-hover-pin">
              <HPIcons.ScanMark size={14} color={c.name === "Seattle" ? style.originColor : style.pinRing} />
            </div>
            <div>
              <div className="hp-hover-name">
                {window.__HP_LANG === "zh" && c.zh ? c.zh : c.name}
              </div>
              <div className="hp-hover-meta">
                {c.name === "Seattle" ? "Origin · 起点" : `${c.count} ${c.count === 1 ? "Husky" : "Huskies"}`}
                {c.country && <span> · {c.country}</span>}
              </div>
            </div>
          </div>
        );
      })()}
    </div>
  );
};

window.WorldMap = WorldMap;
window.MAP_STYLES = MAP_STYLES;
