/* Brainstorm X — Name Nebula. A living 2.5D semantic naming space.
   Gravity (votes → mass/position), keyword wells, constellations, animated
   waves, drag-to-evolve, fuse, simulated presence, sprint mode. */
(function () {
  const { useState, useRef, useEffect, useMemo } = React;
  const DS = window.BrainstormXDesignSystem_f2a458;
  const { Button, NameCard, KeywordOrb, Badge, Avatar } = DS;
  const I = window.BXIcons;
  const N = window.BXNebula;
  const { clamp, hash, deriveVoters, relatedIds, FireFX, FrostFX, Stars, VoterAvatars,
    Header, ZoomControl, Welcome, ActivityFeed, WaveBanner, SprintBar, Portals,
    InviteModal, AddCustomModal, DetailPanel } = N;

  const MASS = { on_fire: 0.92, hot: 0.82, warm: 0.75, neutral: 0.7, cold: 0.64, frozen: 0.56 };
  const HEAT_C = { on_fire: 'var(--heat-fire)', hot: 'var(--heat-hot)', warm: 'var(--heat-warm)', neutral: 'var(--neutral-state)', cold: 'var(--cold-cool)', frozen: 'var(--cold-frozen)' };
  const HEAT_EMOJI = { on_fire: '🔥', hot: '🔥', cold: '❄️', frozen: '❄️' };
  const heatFromVotes = window.BXheatFromVotes;
  const scoreOf = (c, sl) => (c.up || 0) * 2 - (c.down || 0) * 2 + (sl ? 5 : 0);

  const KW_POOL = ['cosy', 'vibrant', 'bold', 'handmade', 'colourful', 'cheeky', 'premium', 'local', 'bright', 'snug'];
  const SEED_COUNT = (window.BXseedNames || []).length;
  const NAME_POOL = [
    { id: 'pawza', name: 'Pawza', style: 'brandable', pronunciation: 'paw-zuh', reason: 'Built from your boosted "paws" + playful tone.', keywords: ['paws', 'playful'] },
    { id: 'trinkett', name: 'Trinkett', style: 'real_word', pronunciation: 'trin-ket', reason: 'Leans into quirky, collectible accessories.', keywords: ['quirky', 'cute'] },
    { id: 'bumblepaw', name: 'Bumblepaw', style: 'compound', pronunciation: 'bum-buhl-paw', reason: 'Warm and characterful — from top-voted directions.', keywords: ['playful', 'paws'] },
    { id: 'yipster', name: 'Yipster', style: 'brandable', pronunciation: 'yip-ster', reason: 'Energetic and modern, social-handle friendly.', keywords: ['fun', 'bright'] },
    { id: 'cosypaw', name: 'Cosypaw', style: 'compound', pronunciation: 'koh-zee-paw', reason: 'Soft and snug — echoes the team’s cosy keywords.', keywords: ['cosy', 'cute'] },
    { id: 'fuzzly', name: 'Fuzzly', style: 'alternate_spelling', pronunciation: 'fuhz-lee', reason: 'Friendly and tactile, easy to say.', keywords: ['cute', 'snug'] },
  ];
  const EVOLVE = {
    shorter: { lbl: 'shorter', mk: (s) => [s.slice(0, Math.max(3, s.length - 2)), s.replace(/[aeiou]([^aeiou]*)$/i, '$1') || s + 'o'] },
    premium: { lbl: 'more premium', mk: (s) => [s + 'o', s.slice(0, 1).toUpperCase() + s.slice(1) + ' & Co'] },
    playful: { lbl: 'more playful', mk: (s) => [s + 'zo', 'Boop' + s.slice(0, 3)] },
    unusual: { lbl: 'more unusual', mk: (s) => [s.replace(/s$/, 'x') + 'i', 'Qy' + s.slice(1)] },
    british: { lbl: 'more British', mk: (s) => [s + 'shire', s + ' & Sons'] },
    like: { lbl: 'more like this', mk: (s) => [s + 'a', s + 'io'] },
  };

  // ---- a single floating name card (position owned by the rAF loop) -----
  function SceneCard({ n, motion, selected, onVote, onOpen, onDelete, onCardDown }) {
    const voters = deriveVoters(n);
    const mass = MASS[n.heat] || 0.84;
    const [hover, setHover] = useState(false);
    const expanded = hover || selected;
    const heatC = HEAT_C[n.heat] || 'var(--neutral-state)';
    const emoji = HEAT_EMOJI[n.heat] || '';
    const sc = mass * (hover ? 1.04 : 1);

    // tiny vote control for the compact node
    const Mini = ({ dir }) => (
      <button onClick={(e) => { e.stopPropagation(); onVote(dir); }} aria-label={dir === 'up' ? 'Upvote' : 'Downvote'}
        style={{ display: 'flex', alignItems: 'center', gap: 2, height: 22, padding: '0 6px', borderRadius: 999, cursor: 'pointer',
          border: '1px solid var(--line-2)', background: 'var(--surface-2)', color: dir === 'up' ? 'var(--vote-up)' : 'var(--vote-down)',
          fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700 }}>
        {dir === 'up' ? <I.ChevronUp size={13} /> : <I.ChevronDown size={13} />}{(n.counts && n.counts[dir]) || 0}
      </button>
    );

    if (!expanded) {
      // ---- compact nebula node ----
      return (
        <div className="bx-nc-mount" data-name-id={n.id} onMouseDown={onCardDown} onClick={onOpen}
          onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
          style={{ position: 'relative', transform: `scale(${sc})`, transformOrigin: 'center', transition: 'transform var(--dur-base) var(--ease-spring)', cursor: 'grab' }}>
          {n.heat === 'on_fire' && <FireFX />}
          {n.heat === 'frozen' && <FrostFX />}
          <div style={{
            position: 'relative', width: 150, padding: '11px 13px 10px', borderRadius: 'var(--radius-lg)',
            background: 'var(--surface-glass-strong)', border: `1px solid color-mix(in srgb, ${heatC} 55%, var(--line-2))`,
            boxShadow: `0 0 18px color-mix(in srgb, ${heatC} 30%, transparent), var(--shadow-md)`,
            backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 5 }}>
              <span style={{ width: 9, height: 9, borderRadius: '50%', background: heatC, boxShadow: `0 0 8px ${heatC}` }} />
              <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700, color: heatC }}>{n.score > 0 ? '+' + n.score : n.score}</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
              <span style={{ fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 18, color: 'var(--text-1)', letterSpacing: '-0.01em', lineHeight: 1.05 }}>{n.name}</span>
              {emoji && <span style={{ fontSize: 12 }}>{emoji}</span>}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginTop: 9 }}>
              <Mini dir="up" /><Mini dir="down" />
              <span style={{ flex: 1 }} />
              <button onClick={(e) => { e.stopPropagation(); onVote('star'); }} aria-label="Shortlist"
                style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 22, borderRadius: 7, cursor: 'pointer',
                  border: `1px solid ${n.shortlisted ? 'var(--vote-star)' : 'var(--line-2)'}`, background: n.shortlisted ? 'color-mix(in srgb, var(--vote-star) 22%, var(--surface-2))' : 'var(--surface-2)',
                  color: n.shortlisted ? 'var(--vote-star)' : 'var(--text-3)' }}>
                <I.Star size={13} fill={n.shortlisted ? 'var(--vote-star)' : 'none'} />
              </button>
            </div>
            {voters.length > 0 && (
              <div onClick={(e) => e.stopPropagation()} style={{ position: 'absolute', top: -10, right: -8, zIndex: 6 }}>
                <VoterAvatars voters={voters} size={18} popUp />
              </div>
            )}
          </div>
        </div>
      );
    }

    // ---- expanded full card (hover / selected) ----
    return (
      <div className="bx-nc-mount" data-name-id={n.id} onMouseDown={onCardDown} onClick={onOpen}
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{ position: 'relative', transform: `scale(${sc})`, transformOrigin: 'center', transition: 'transform var(--dur-base) var(--ease-spring)', cursor: 'grab', zIndex: 40 }}>
        {n.heat === 'on_fire' && <FireFX />}
        {n.heat === 'frozen' && <FrostFX />}
        <NameCard {...n} scale={1} floating={false} selected={selected} onVote={onVote} />
        {voters.length > 0 && (
          <div onClick={(e) => e.stopPropagation()} style={{ position: 'absolute', left: 116, bottom: 75, zIndex: 6 }}>
            <VoterAvatars voters={voters} size={18} popUp />
          </div>
        )}
        <button onClick={(e) => { e.stopPropagation(); onVote('star'); }} aria-label="Shortlist" data-overlay="1"
          title={n.shortlisted ? 'Shortlisted' : 'Add to shortlist'} style={{
            position: 'absolute', bottom: 15, right: 54, zIndex: 6, width: 34, height: 34, borderRadius: '50%',
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
            border: `1px solid ${n.shortlisted ? 'var(--vote-star)' : 'var(--line-2)'}`,
            background: n.shortlisted ? 'color-mix(in srgb, var(--vote-star) 22%, var(--surface-2))' : 'var(--surface-glass)',
            color: n.shortlisted ? 'var(--vote-star)' : 'var(--text-2)',
            boxShadow: n.shortlisted ? '0 0 14px color-mix(in srgb, var(--vote-star) 45%, transparent)' : 'none',
            backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
          }}>
          <I.Star size={17} fill={n.shortlisted ? 'var(--vote-star)' : 'none'} />
        </button>
        <button onClick={(e) => { e.stopPropagation(); onDelete(); }} aria-label="Delete name" title="Remove this name"
          onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--danger)'; e.currentTarget.style.borderColor = 'var(--danger)'; }}
          onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--text-3)'; e.currentTarget.style.borderColor = 'var(--line-2)'; }}
          style={{
            position: 'absolute', bottom: 15, right: 14, zIndex: 6, width: 34, height: 34, borderRadius: '50%',
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
            border: '1px solid var(--line-2)', background: 'var(--surface-glass)', color: 'var(--text-3)',
            backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
          }}>
          <I.Trash2 size={16} />
        </button>
      </div>
    );
  }

  // ---- keyword gravity well (lives in the scene) ------------------------
  function KeywordWell({ k, onBoost, onCool, onDelete }) {
    const [hover, setHover] = useState(false);
    return (
      <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
        <KeywordOrb {...k} onBoost={onBoost} floating={false} />
        <div style={{
          display: 'flex', gap: 5, marginTop: 6, opacity: hover ? 1 : 0, transition: 'opacity var(--dur-fast)',
          pointerEvents: hover ? 'auto' : 'none',
        }}>
          <button onClick={(e) => { e.stopPropagation(); onCool(); }} aria-label={`Cool ${k.label}`} title="Cool"
            style={{ width: 22, height: 20, borderRadius: 6, cursor: 'pointer', border: '1px solid var(--line-2)', background: 'var(--surface-2)', color: 'var(--vote-down)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><I.ChevronDown size={13} /></button>
          <button onClick={(e) => { e.stopPropagation(); onDelete(); }} aria-label={`Delete keyword ${k.label}`} title="Remove"
            style={{ width: 22, height: 20, borderRadius: 6, cursor: 'pointer', border: '1px solid color-mix(in srgb, var(--danger) 60%, transparent)', background: 'color-mix(in srgb, var(--danger) 22%, var(--surface-2))', color: 'var(--danger)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><I.X size={12} /></button>
        </div>
      </div>
    );
  }

  function Arena({ names, setNames, keywords, setKeywords, wave, setWave, goto, project = 'Pickle Paws', motion = true, density = 8 }) {
    const [openId, setOpenId] = useState(null);
    const [generating, setGenerating] = useState(false);
    const [inviteOpen, setInviteOpen] = useState(false);
    const [addOpen, setAddOpen] = useState(false);
    const [zoom, setZoom] = useState(0.85);
    const [pan, setPan] = useState({ x: 0, y: 0 });
    const [size, setSize] = useState({ w: 1000, h: 640 });
    const [hoverId, setHoverId] = useState(null);
    const [dragging, setDragging] = useState(false);
    const [hoverPortal, setHoverPortal] = useState(null);
    const [waveText, setWaveText] = useState(null);
    const [sprint, setSprint] = useState(false);
    const [sprintLeft, setSprintLeft] = useState(60);
    const [sparks, setSparks] = useState([]); // fuse spark fx
    const [events, setEvents] = useState([
      { k: 'e1', who: 'Sam Lee', what: 'boosted "quirky"' },
      { k: 'e2', who: 'Alex Roy', what: 'shortlisted "Pawlio"' },
      { k: 'e3', who: 'Priya N', what: 'upvoted "Snugglo"' },
    ]);

    const sceneRef = useRef(null);
    const posRef = useRef(new Map());
    const tgtRef = useRef(new Map());
    const elsRef = useRef(new Map());
    const linesRef = useRef(new Map());
    const presEls = useRef(new Map());
    const spotEls = useRef(new Map());
    const dataRef = useRef({});
    const panRef = useRef(pan); panRef.current = pan;
    const zoomRef = useRef(zoom); zoomRef.current = zoom;
    const dragRef = useRef(null);       // pan drag
    const cardDragRef = useRef(null);   // card drag
    const suppressClickRef = useRef(false);
    const evK = useRef(4);

    const visibleNames = useMemo(() => names.slice(0, density + Math.max(0, names.length - SEED_COUNT)), [names, density]);

    // presence (simulated teammates)
    const presence = useRef((window.BXpeople || []).slice(0, 3).map((p, i) => ({
      id: 'pres-' + i, name: p.name, colour: ['#2DE2E6', '#FF3DA6', '#A8FF4D'][i % 3],
      x: Math.cos((i / 3) * Math.PI * 2) * 230, y: Math.sin((i / 3) * Math.PI * 2) * 150 - 30,
      ox: (Math.random() - 0.5) * 120, oy: 60 + Math.random() * 40, targetId: null,
    }))).current;

    const pushEvent = (who, what) => setEvents((ev) => [{ k: 'k' + (evK.current++), who, what }, ...ev].slice(0, 12));

    // ---- layout: keyword wells around a ring; names by gravity ----------
    const kwPos = useMemo(() => {
      const m = new Map();
      const Rkx = size.w / 2 - 90, Rky = size.h / 2 - 70;
      keywords.forEach((k, i) => {
        const a = (i / Math.max(1, keywords.length)) * Math.PI * 2 - Math.PI / 2;
        m.set(k.id, { x: Math.cos(a) * Rkx, y: Math.sin(a) * Rky });
      });
      return m;
    }, [keywords, size]);

    useEffect(() => {
      const Rx = size.w / 2 - 150, Ry = size.h / 2 - 120;
      const GA = Math.PI * (3 - Math.sqrt(5));
      const tgt = tgtRef.current;
      const ids = new Set();
      const order = [...visibleNames].sort((a, b) => hash(a.id) - hash(b.id));
      const N = Math.max(1, order.length);
      order.forEach((n, i) => {
        ids.add(n.id);
        const a = i * GA + (hash(n.id) % 24) / 24;
        const norm = clamp((n.score + 16) / 42, 0, 1);
        // score-driven radius: liked names sit near the centre, rejected drift to the edge
        const rad = 0.30 + 0.62 * (1 - norm) + ((hash(n.id) % 5) / 5) * 0.06;
        let tx = Math.cos(a) * Rx * rad, ty = Math.sin(a) * Ry * rad;
        let ax = 0, ay = 0, aw = 0;
        (n.keywords || []).forEach((kw) => {
          const k = keywords.find((kk) => kk.label === kw);
          if (k && (k.state === 'boosted' || k.state === 'favourite' || (k.weight || 1) > 1)) {
            const p = kwPos.get(k.id); if (p) { ax += p.x * k.weight; ay += p.y * k.weight; aw += k.weight; }
          }
        });
        if (aw > 0) { const b = clamp(aw * 0.12, 0, 0.5); tx = tx * (1 - b) + (ax / aw) * b; ty = ty * (1 - b) + (ay / aw) * b; }
        tgt.set(n.id, { x: tx, y: ty });
        // seed position + paint immediately so layout is correct even if rAF is throttled
        if (!posRef.current.has(n.id)) {
          posRef.current.set(n.id, { x: tx, y: ty });
          const el = elsRef.current.get(n.id);
          const norm0 = clamp((n.score + 16) / 42, 0, 1);
          if (el) el.style.transform = `translate(-50%,-50%) translate3d(${tx}px, ${ty}px, ${norm0 * 55 - 8}px)`;
        }
      });
      [...tgt.keys()].forEach((id) => { if (!ids.has(id)) tgt.delete(id); });
    }, [visibleNames, keywords, kwPos, size]);

    // measure scene
    useEffect(() => {
      const el = sceneRef.current; if (!el) return;
      const measure = () => setSize({ w: el.clientWidth, h: el.clientHeight });
      measure();
      const ro = new ResizeObserver(measure); ro.observe(el);
      return () => ro.disconnect();
    }, [openId]);

    // keep latest data for the rAF loop
    const related = useMemo(() => relatedIds(names.find((n) => n.id === hoverId), visibleNames), [hoverId, visibleNames]);
    dataRef.current = { names: visibleNames, motion, hoverId, related, openId, presence, dragId: cardDragRef.current && cardDragRef.current.id };

    // ---- the physics / animation loop -----------------------------------
    useEffect(() => {
      let raf;
      const tick = () => {
        const now = performance.now();
        const d = dataRef.current;
        const pos = posRef.current, tgt = tgtRef.current;
        window.__bx = { t: ((window.__bx && window.__bx.t) || 0) + 1, names: d.names ? d.names.length : -1, tgt: tgt.size, els: elsRef.current.size, sample: tgt.get('drabwell') };
        d.names.forEach((n) => {
          let p = pos.get(n.id);
          const t = tgt.get(n.id) || { x: 0, y: 0 };
          if (!p) { p = { x: (Math.random() - 0.5) * 40, y: (Math.random() - 0.5) * 40 }; pos.set(n.id, p); }
          const drag = cardDragRef.current;
          if (drag && drag.id === n.id && drag.pos) { p.x = drag.pos.x; p.y = drag.pos.y; }
          else { p.x += (t.x - p.x) * 0.07; p.y += (t.y - p.y) * 0.07; }
          const el = elsRef.current.get(n.id);
          if (el) {
            const phase = (hash(n.id) % 1000) / 1000 * Math.PI * 2;
            const fl = d.motion ? Math.sin(now / 1100 + phase) * 4 : 0;
            const norm = clamp((n.score + 16) / 42, 0, 1);
            const z = norm * 55 - 8;
            el.style.transform = `translate(-50%,-50%) translate3d(${p.x}px, ${(p.y + fl).toFixed(2)}px, ${z}px)`;
            const dim = d.hoverId && d.hoverId !== n.id && !d.related.has(n.id);
            el.style.opacity = dim ? 0.3 : 1;
            el.style.zIndex = d.openId === n.id ? 50 : (n.heat === 'on_fire' ? 22 : 14);
          }
        });
        // presence markers + spotlights
        d.presence.forEach((u) => {
          const tp = u.targetId && pos.get(u.targetId);
          const tx = tp ? tp.x + u.ox : u.x, ty = tp ? tp.y + u.oy : u.y;
          u.x += (tx - u.x) * 0.045; u.y += (ty - u.y) * 0.045;
          const el = presEls.current.get(u.id);
          if (el) el.style.transform = `translate(-50%,-50%) translate(${u.x.toFixed(1)}px, ${u.y.toFixed(1)}px)`;
          const sp = spotEls.current.get(u.id);
          if (sp) {
            if (tp) { sp.style.opacity = 0.5; sp.style.transform = `translate(-50%,-50%) translate(${tp.x.toFixed(1)}px, ${tp.y.toFixed(1)}px)`; }
            else sp.style.opacity = 0;
          }
        });
        // constellation lines
        if (d.hoverId) {
          const hp = pos.get(d.hoverId);
          linesRef.current.forEach((ln, id) => {
            const mp = pos.get(id);
            if (hp && mp) { ln.setAttribute('x1', hp.x.toFixed(1)); ln.setAttribute('y1', hp.y.toFixed(1)); ln.setAttribute('x2', mp.x.toFixed(1)); ln.setAttribute('y2', mp.y.toFixed(1)); }
          });
        }
        raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, []);

    // ---- presence behaviour: wander + vote ------------------------------
    useEffect(() => {
      const pick = setInterval(() => {
        const ns = dataRef.current.names;
        if (!ns.length) return;
        presence.forEach((u) => { u.targetId = ns[Math.floor(Math.random() * ns.length)].id; u.ox = (Math.random() - 0.5) * 140; u.oy = 50 + Math.random() * 50; });
      }, 2600);
      return () => clearInterval(pick);
    }, []);

    useEffect(() => {
      const ms = sprint ? 1200 : 4200;
      const t = setInterval(() => {
        const ns = dataRef.current.names;
        if (!ns.length) return;
        const u = presence[Math.floor(Math.random() * presence.length)];
        const target = (u.targetId && ns.find((n) => n.id === u.targetId)) || ns[Math.floor(Math.random() * ns.length)];
        const type = Math.random() < 0.72 ? 'up' : 'down';
        applyVote(target.id, type, u.name);
      }, ms);
      return () => clearInterval(t);
    }, [sprint]);

    // sprint countdown
    useEffect(() => {
      if (!sprint) return;
      setSprintLeft(60);
      const t = setInterval(() => setSprintLeft((s) => { if (s <= 1) { clearInterval(t); setSprint(false); return 60; } return s - 1; }), 1000);
      return () => clearInterval(t);
    }, [sprint]);

    // ---- interactions ---------------------------------------------------
    useEffect(() => {
      const el = sceneRef.current; if (!el) return;
      const onWheel = (e) => { e.preventDefault(); setZoom((z) => clamp(z - e.deltaY * 0.0014, 0.3, 1.7)); };
      el.addEventListener('wheel', onWheel, { passive: false });
      return () => el.removeEventListener('wheel', onWheel);
    }, []);

    useEffect(() => {
      const onKey = (e) => {
        const tag = document.activeElement && document.activeElement.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA') return;
        const step = 60; let dx = 0, dy = 0;
        if (e.key === 'ArrowLeft') dx = step; else if (e.key === 'ArrowRight') dx = -step;
        else if (e.key === 'ArrowUp') dy = step; else if (e.key === 'ArrowDown') dy = -step;
        else if (e.key === 'Escape') { setOpenId(null); return; } else return;
        e.preventDefault(); setPan((p) => ({ x: p.x + dx, y: p.y + dy }));
      };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, []);

    const toScene = (clientX, clientY) => {
      const r = sceneRef.current.getBoundingClientRect();
      return { x: (clientX - r.left - r.width / 2 - panRef.current.x) / zoomRef.current, y: (clientY - r.top - r.height / 2 - panRef.current.y) / zoomRef.current };
    };

    useEffect(() => {
      const onMove = (e) => {
        if (cardDragRef.current) {
          const d = cardDragRef.current;
          if (Math.abs(e.clientX - d.sx) > 4 || Math.abs(e.clientY - d.sy) > 4) d.moved = true;
          d.pos = toScene(e.clientX, e.clientY);
          // portal hover detection
          const elBelow = document.elementFromPoint(e.clientX, e.clientY);
          const portal = elBelow && elBelow.closest && elBelow.closest('[data-portal]');
          setHoverPortal(portal ? portal.getAttribute('data-portal') : null);
          return;
        }
        if (dragRef.current) setPan({ x: dragRef.current.px + (e.clientX - dragRef.current.x), y: dragRef.current.py + (e.clientY - dragRef.current.y) });
      };
      const onUp = (e) => {
        if (cardDragRef.current) {
          const d = cardDragRef.current;
          if (d.moved) {
            suppressClickRef.current = true;
            const below = document.elementFromPoint(e.clientX, e.clientY);
            const portal = below && below.closest && below.closest('[data-portal]');
            const card = below && below.closest && below.closest('[data-name-id]');
            if (portal) evolveName(d.id, portal.getAttribute('data-portal'));
            else if (card && card.getAttribute('data-name-id') !== d.id) fuseNames(d.id, card.getAttribute('data-name-id'), e.clientX, e.clientY);
          }
          cardDragRef.current = null; setDragging(false); setHoverPortal(null); document.body.style.cursor = '';
        }
        if (dragRef.current) { dragRef.current = null; document.body.style.cursor = ''; }
      };
      window.addEventListener('mousemove', onMove);
      window.addEventListener('mouseup', onUp);
      return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
    });

    const onCardDown = (e, n) => {
      if (e.button !== 0 || (e.target.closest && e.target.closest('button'))) return;
      e.stopPropagation();
      const p = posRef.current.get(n.id) || { x: 0, y: 0 };
      cardDragRef.current = { id: n.id, sx: e.clientX, sy: e.clientY, moved: false, pos: { ...p } };
      setDragging(true); document.body.style.cursor = 'grabbing';
    };
    const onSceneDown = (e) => {
      const onCard = e.target.closest && e.target.closest('.bx-nc-mount');
      if (e.button === 1 || (e.button === 0 && !onCard)) { dragRef.current = { x: e.clientX, y: e.clientY, px: pan.x, py: pan.y }; document.body.style.cursor = 'grabbing'; e.preventDefault(); }
    };

    // ---- mutations ------------------------------------------------------
    function applyVote(id, type, who) {
      setNames((ns) => ns.map((n) => {
        if (n.id !== id) return n;
        if (type === 'star') { const sl = !n.shortlisted; const sc = scoreOf(n.counts, sl); return { ...n, shortlisted: sl, score: sc, heat: heatFromVotes(n.counts.up || 0, n.counts.down || 0) }; }
        if (type !== 'up' && type !== 'down') return n;
        const counts = { ...n.counts, [type]: (n.counts[type] || 0) + 1 };
        return { ...n, counts, score: scoreOf(counts, n.shortlisted), heat: heatFromVotes(counts.up || 0, counts.down || 0) };
      }));
      const nm = dataRef.current.names.find((n) => n.id === id);
      const lbl = { up: 'upvoted', down: 'downvoted', star: 'shortlisted' }[type];
      pushEvent(who || 'You', `${lbl} "${nm ? nm.name : ''}"`);
    }
    const vote = (id, type) => applyVote(id, type, 'You');

    const adjustKw = (id, dir) => {
      setKeywords((ks) => ks.map((k) => {
        if (k.id !== id) return k;
        if (dir > 0) return { ...k, weight: (k.weight || 1) + 1, count: (k.count || 0) + 1, state: k.state === 'avoid' ? 'avoid' : 'boosted' };
        const weight = Math.max(0, (k.weight || 1) - 1);
        return { ...k, weight, count: Math.max(0, (k.count || 0) - 1) || null, state: weight <= 1 ? 'cooling' : k.state };
      }));
      const kw = keywords.find((k) => k.id === id);
      pushEvent('You', `${dir > 0 ? 'boosted' : 'cooled'} "${kw ? kw.label : ''}"`);
    };
    const deleteKw = (id) => { const kw = keywords.find((k) => k.id === id); setKeywords((ks) => ks.filter((k) => k.id !== id)); pushEvent('You', `removed keyword "${kw ? kw.label : ''}"`); };
    const moreKeywords = () => {
      setKeywords((ks) => { const have = new Set(ks.map((k) => k.id)); const pick = KW_POOL.filter((w) => !have.has(w)).slice(0, 2); return pick.length ? [...ks, ...pick.map((w) => ({ id: w, label: w, state: 'normal', weight: 1, count: null }))] : ks; });
      pushEvent('Brainstorm X', 'suggested new keywords');
    };

    const spawn = (cards) => setNames((ns) => { cards.forEach((c) => posRef.current.delete(c.id)); return [...ns, ...cards]; });

    const moreNames = () => {
      const have = new Set(names.map((n) => n.id));
      const pick = NAME_POOL.find((p) => !have.has(p.id)); if (!pick) return;
      const up = 3 + Math.floor(Math.random() * 3);
      spawn([{ ...pick, confidence: 62 + Math.floor(Math.random() * 24), counts: { up, down: 1 }, score: up * 2 - 2, heat: heatFromVotes(up, 1), wave }]);
      pushEvent('Brainstorm X', `generated "${pick.name}" from your votes`);
    };

    const deleteName = (id) => { const nm = names.find((n) => n.id === id); if (openId === id) setOpenId(null); posRef.current.delete(id); setNames((ns) => ns.filter((n) => n.id !== id)); pushEvent('You', `removed "${nm ? nm.name : ''}"`); };

    const addCustomName = (name, styl) => {
      const id = 'custom-' + name.toLowerCase().replace(/[^a-z0-9]/g, '') + '-' + Date.now().toString(36);
      spawn([{ id, name, style: styl || 'brandable', pronunciation: '', reason: 'Added by you — vote it up or down with the team.', keywords: [], confidence: null, counts: { up: 0, down: 0 }, score: 0, heat: 'neutral', custom: true, wave }]);
      pushEvent('You', `added custom name "${name}"`);
    };

    const evolveName = (id, dir) => {
      const parent = names.find((n) => n.id === id); if (!parent) return;
      const cfg = EVOLVE[dir] || EVOLVE.like;
      const base = parent.name.replace(/[^A-Za-z]/g, '');
      const kids = cfg.mk(base).slice(0, 2).map((nm, i) => {
        const up = 2 + Math.floor(Math.random() * 3);
        return { id: `evo-${id}-${dir}-${i}-${Date.now().toString(36)}`, name: nm.charAt(0).toUpperCase() + nm.slice(1), style: parent.style, pronunciation: '', reason: `Evolved from “${parent.name}” → ${cfg.lbl}.`, keywords: parent.keywords, confidence: 60 + Math.floor(Math.random() * 22), counts: { up, down: 0 }, score: up * 2, heat: heatFromVotes(up, 0), parentNameIds: [id], wave };
      });
      spawn(kids);
      setWaveText(`Evolved “${parent.name}” → ${cfg.lbl} · ${kids.map((k) => k.name).join(', ')}`);
      pushEvent('Brainstorm X', `evolved "${parent.name}" → ${cfg.lbl}`);
      clearWaveText();
    };

    const fuseNames = (aId, bId, cx, cy) => {
      const a = names.find((n) => n.id === aId), b = names.find((n) => n.id === bId); if (!a || !b) return;
      const A = a.name.replace(/[^A-Za-z]/g, ''), B = b.name.replace(/[^A-Za-z]/g, '');
      const blends = [A.slice(0, Math.ceil(A.length / 2)) + B.slice(Math.floor(B.length / 2)), B.slice(0, Math.ceil(B.length / 2)) + A.slice(Math.floor(A.length / 2)), A.slice(0, 3) + B.slice(0, 3)];
      const kids = [...new Set(blends)].slice(0, 3).map((nm, i) => {
        const up = 2 + Math.floor(Math.random() * 3);
        return { id: `fuse-${i}-${Date.now().toString(36)}`, name: nm.charAt(0).toUpperCase() + nm.slice(1).toLowerCase(), style: 'compound', pronunciation: '', reason: `Fused from “${a.name}” + “${b.name}”.`, keywords: [...new Set([...(a.keywords || []), ...(b.keywords || [])])].slice(0, 3), confidence: 58 + Math.floor(Math.random() * 24), counts: { up, down: 0 }, score: up * 2, heat: heatFromVotes(up, 0), parentNameIds: [aId, bId], wave };
      });
      spawn(kids);
      const sk = toScene(cx, cy);
      const sid = 's' + Date.now();
      setSparks((s) => [...s, { id: sid, x: sk.x, y: sk.y }]);
      setTimeout(() => setSparks((s) => s.filter((x) => x.id !== sid)), 700);
      setWaveText(`Fused “${a.name}” + “${b.name}” → ${kids.map((k) => k.name).join(', ')}`);
      pushEvent('Brainstorm X', `fused "${a.name}" + "${b.name}"`);
      clearWaveText();
    };

    const waveTimer = useRef(null);
    const clearWaveText = () => { clearTimeout(waveTimer.current); waveTimer.current = setTimeout(() => setWaveText(null), 6000); };

    const generate = () => {
      setGenerating(true);
      const boosted = keywords.filter((k) => k.state === 'boosted' || k.state === 'favourite' || (k.weight || 1) > 1).map((k) => k.label);
      const from = (boosted.length ? boosted : ['playful', 'warm', 'short']).slice(0, 4).join(', ');
      setTimeout(() => {
        const batch = [
          { id: 'pip-' + wave, name: 'Pippa & Co', style: 'short_phrase', pronunciation: 'pip-uh', reason: 'Playful, personable and clearly British in tone.', keywords: ['quirky', 'warm'], confidence: 77, score: 10, counts: { up: 5, down: 0 }, heat: heatFromVotes(5, 0), wave: wave + 1 },
          { id: 'wag-' + wave, name: 'Waggle', style: 'real_word', pronunciation: 'wag-uhl', reason: 'Fun, energetic and easy to remember for a pet brand.', keywords: ['playful', 'fun'], confidence: 73, score: 6, counts: { up: 4, down: 1 }, heat: heatFromVotes(4, 1), wave: wave + 1 },
          { id: 'mlo-' + wave, name: 'Marlo Pets', style: 'compound', pronunciation: 'mar-loh', reason: 'Warmer, more premium feel from the boosted keywords.', keywords: ['premium', 'warm'], confidence: 70, score: 4, counts: { up: 3, down: 1 }, heat: heatFromVotes(3, 1), wave: wave + 1 },
        ];
        spawn(batch);
        setWave((w) => w + 1);
        setWaveText(`Wave ${wave + 1} · generated from: ${from}`);
        pushEvent('Brainstorm X', `generated Wave ${wave + 1}`);
        clearWaveText();
        setGenerating(false);
      }, 1200);
    };

    const open = names.find((n) => n.id === openId);
    const sceneRight = open ? 360 : 40;
    const cx = size.w / 2, cy = size.h / 2;

    return (
      <div className="bx-nebula-bg" style={{ position: 'relative', height: '100vh', overflow: 'hidden' }}>
        <Stars n={80} />
        <Header wave={wave} project={project} onGenerate={generate} generating={generating} goto={goto}
          onInvite={() => setInviteOpen(true)} onMoreNames={moreNames} onAddCustom={() => setAddOpen(true)}
          sprint={sprint} onToggleSprint={() => setSprint((s) => !s)} />
        <ZoomControl zoom={zoom} setZoom={setZoom} onReset={() => { setZoom(0.85); setPan({ x: 0, y: 0 }); }} />
        <WaveBanner text={waveText} />
        {sprint && <SprintBar seconds={sprintLeft} />}
        <Portals active={dragging} hoverId={hoverPortal} />

        {/* scene */}
        <div ref={sceneRef} onMouseDown={onSceneDown} style={{ position: 'absolute', inset: `70px ${sceneRight}px 0 0`, overflow: 'hidden', cursor: 'grab', perspective: '1400px', transition: 'inset var(--dur-base) var(--ease-out)' }}>
          <div style={{ position: 'absolute', inset: 0, transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, transformStyle: 'preserve-3d' }}>
            {/* constellation lines */}
            {hoverId && (
              <svg width={size.w} height={size.h} style={{ position: 'absolute', left: 0, top: 0, pointerEvents: 'none', zIndex: 8, overflow: 'visible' }}>
                <defs>
                  <linearGradient id="bxLine" x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0" stopColor="var(--neon-cyan)" stopOpacity="0.9" />
                    <stop offset="1" stopColor="var(--neon-magenta)" stopOpacity="0.5" />
                  </linearGradient>
                </defs>
                <g transform={`translate(${cx}, ${cy})`}>
                  {[...related].map((id) => (
                    <line key={id} ref={(el) => { if (el) linesRef.current.set(id, el); else linesRef.current.delete(id); }}
                      stroke="url(#bxLine)" strokeWidth="1.5" strokeLinecap="round" style={{ filter: 'drop-shadow(0 0 4px rgba(45,226,230,0.6))' }} />
                  ))}
                </g>
              </svg>
            )}

            {/* keyword gravity wells */}
            {keywords.map((k) => {
              const p = kwPos.get(k.id) || { x: 0, y: 0 };
              return (
                <div key={'w' + k.id} style={{ position: 'absolute', left: `calc(50% + ${p.x}px)`, top: `calc(50% + ${p.y}px)`, transform: 'translate(-50%,-50%)', zIndex: 9 }}>
                  <KeywordWell k={k} onBoost={() => adjustKw(k.id, +1)} onCool={() => adjustKw(k.id, -1)} onDelete={() => deleteKw(k.id)} />
                </div>
              );
            })}

            {/* presence spotlights (behind cards) */}
            {presence.map((u) => (
              <div key={'sp' + u.id} ref={(el) => { if (el) spotEls.current.set(u.id, el); }} style={{ position: 'absolute', left: '50%', top: '50%', width: 260, height: 180, borderRadius: '50%', pointerEvents: 'none', opacity: 0, zIndex: 7, background: `radial-gradient(circle, color-mix(in srgb, ${u.colour} 22%, transparent), transparent 70%)`, transition: 'opacity var(--dur-base)' }} />
            ))}

            {/* name cards */}
            {visibleNames.map((n) => (
              <div key={n.id} data-name-id={n.id}
                ref={(el) => { if (el) { elsRef.current.set(n.id, el); const p = posRef.current.get(n.id) || tgtRef.current.get(n.id); el.style.transform = `translate(-50%,-50%) translate3d(${p ? p.x : 0}px, ${p ? p.y : 0}px, 0)`; } else elsRef.current.delete(n.id); }}
                onMouseEnter={() => setHoverId(n.id)} onMouseLeave={() => setHoverId((h) => (h === n.id ? null : h))}
                style={{ position: 'absolute', left: '50%', top: '50%', willChange: 'transform' }}>
                <SceneCard n={n} motion={motion} selected={openId === n.id}
                  onVote={(t) => vote(n.id, t)} onCardDown={(e) => onCardDown(e, n)}
                  onOpen={() => { if (suppressClickRef.current) { suppressClickRef.current = false; return; } setOpenId(n.id); }}
                  onDelete={() => deleteName(n.id)} />
              </div>
            ))}

            {/* presence markers */}
            {presence.map((u) => (
              <div key={u.id} ref={(el) => { if (el) { presEls.current.set(u.id, el); el.style.transform = `translate(-50%,-50%) translate(${u.x}px, ${u.y}px)`; } }} style={{ position: 'absolute', left: '50%', top: '50%', zIndex: 30, pointerEvents: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
                <Avatar name={u.name} size={26} colour={u.colour} style={{ border: `2px solid ${u.colour}`, boxShadow: `0 0 12px ${u.colour}` }} />
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-1)', background: 'rgba(10,10,24,0.7)', padding: '2px 7px', borderRadius: 999, whiteSpace: 'nowrap' }}>{u.name.split(' ')[0]}</span>
              </div>
            ))}

            {/* fuse sparks */}
            {sparks.map((s) => (
              <div key={s.id} style={{ position: 'absolute', left: `calc(50% + ${s.x}px)`, top: `calc(50% + ${s.y}px)`, zIndex: 48, pointerEvents: 'none' }}>
                <div style={{ position: 'absolute', width: 60, height: 60, borderRadius: '50%', background: 'radial-gradient(circle, #fff, var(--neon-magenta) 40%, transparent 70%)', animation: 'bxSpark 0.7s var(--ease-out) forwards' }} />
              </div>
            ))}

            {/* generation portal flash */}
            {generating && (
              <div style={{ position: 'absolute', left: '50%', top: '50%', zIndex: 6, pointerEvents: 'none' }}>
                <div style={{ position: 'absolute', width: 120, height: 120, borderRadius: '50%', background: 'radial-gradient(circle, color-mix(in srgb, var(--neon-cyan) 50%, transparent), transparent 70%)', animation: 'bxPortalCore 1.2s var(--ease-out) infinite' }} />
              </div>
            )}
          </div>
        </div>

        <ActivityFeed events={events} />
        <Welcome />
        {open && <DetailPanel name={open} names={names} onClose={() => setOpenId(null)} onEvolve={evolveName} onOpen={(id) => setOpenId(id)} onVote={vote} />}
        {inviteOpen && <InviteModal onClose={() => setInviteOpen(false)} />}
        {addOpen && <AddCustomModal onAdd={addCustomName} onClose={() => setAddOpen(false)} />}
      </div>
    );
  }

  window.BXArena = Arena;
})();
