
// ─── ICONS ──────────────────────────────────────────────
const ICONS = {
  chevronR: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M6 4l4 4-4 4"/></svg>,
  zoomOut: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5l3 3"/><path d="M5 7h4"/></svg>,
  zoomIn: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5l3 3"/><path d="M5 7h4M7 5v4"/></svg>,
  share: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M10 2l4 4-4 4M14 6H6a4 4 0 00-4 4v1"/></svg>,
  plus: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"><path d="M8 3v10M3 8h10"/></svg>,
  sparkle: <svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 1l1.5 4.5L14 7l-4.5 1.5L8 13l-1.5-4.5L2 7l4.5-1.5z"/></svg>,
  mic: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="1" width="6" height="9" rx="3"/><path d="M2 7a6 6 0 0012 0M8 13v3"/></svg>,
  target: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="8" cy="8" r="6"/><circle cx="8" cy="8" r="2.5"/></svg>,
  flag: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 14V3l9 2-9 3"/></svg>,
  calendar: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="12" height="11" rx="2"/><path d="M5 1v4M11 1v4M2 7h12"/></svg>,
  sticky: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 2h10a1 1 0 011 1v8l-4 4H3a1 1 0 01-1-1V3a1 1 0 011-1zM14 11h-3a1 1 0 00-1 1v3"/></svg>,
  link: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M6.5 9.5a4 4 0 005.66 0l2-2a4 4 0 00-5.66-5.66l-1 1M9.5 6.5a4 4 0 00-5.66 0l-2 2a4 4 0 005.66 5.66l1-1"/></svg>,
  arrow: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12L12 4M6 4h6v6"/></svg>,
  x: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>,
  copy: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="5" width="8" height="8" rx="1.5"/><path d="M11 5V3a1 1 0 00-1-1H3a1 1 0 00-1 1v7a1 1 0 001 1h2"/></svg>,
  trash: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2 4h12M5 4V2h6v2M13 4l-1 10H4L3 4"/></svg>,
  collapse: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M3 8h10M8 3l-4 5 4-5zM8 13l4-5-4 5z"/></svg>,
  expand: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M3 8h10M4 5l4-3 4 3M4 11l4 3 4-3"/></svg>,
  textA: <svg viewBox="0 0 16 16" fill="currentColor"><path d="M7.3 2.5h1.4l4.3 11h-1.6l-1-2.8H5.6l-1 2.8H3zm.7 2.3L6.1 9.3h3.8z"/></svg>,
  shapeCircle: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="8" cy="8" r="5.5"/></svg>,
  shapeRect: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><rect x="2" y="5" width="12" height="7" rx="1"/></svg>,
  shapeSquare: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><rect x="3" y="3" width="10" height="10" rx="1"/></svg>,
  shapeLine: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M3 13L13 3"/></svg>,
  shapeDiamond: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><polygon points="8,1.5 14.5,8 8,14.5 1.5,8"/></svg>,
  shapes: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="4.5" cy="11" r="2.5"/><rect x="9" y="2" width="5" height="5" rx="0.8"/><path d="M2 4.5l4 5.5"/></svg>,
  home: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2 7l6-5 6 5v7a1 1 0 01-1 1H9v-4H7v4H3a1 1 0 01-1-1z"/></svg>,
  layers: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M8 1L1 5l7 4 7-4-7-4zM1 10l7 4 7-4M1 7.5l7 4 7-4"/></svg>,
  check: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M2 8l4 5L14 3"/></svg>,
  magnet: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v5a5 5 0 0010 0V3"/><path d="M3 3h3v5a2 2 0 004 0V3h3"/></svg>,
  externalLink: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M7 3H3a1 1 0 00-1 1v9a1 1 0 001 1h9a1 1 0 001-1V9M10 2h4v4M14 2L8 8"/></svg>,
  tree: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="1" y="5" width="5" height="3.5" rx="1"/><rect x="10" y="1.5" width="5" height="3" rx="1"/><rect x="10" y="6.5" width="5" height="3" rx="1"/><rect x="10" y="11.5" width="5" height="3" rx="1"/><path d="M6 6.75h1.5V3h2M7.5 6.75V8h2M7.5 8v5h2"/></svg>,
  arrowsTool: <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M2 8h10M8.5 4.5L13 8l-4.5 3.5"/></svg>,
  arrowStraight: <svg viewBox="0 0 32 12" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2 6h24M21 2l5 4-5 4"/></svg>,
  arrowCurved: <svg viewBox="0 0 32 14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2 11 Q16 2 26 7"/><path d="M21 4l5 3-4.5 4"/></svg>,
  arrowDouble: <svg viewBox="0 0 32 12" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2 6h28M5 2L0 6l5 4M27 2l5 4-5 4"/></svg>,
  arrowDashed: <svg viewBox="0 0 32 12" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" strokeDasharray="3 2.5"><path d="M2 6h22"/></svg>,
  arrowThick: <svg viewBox="0 0 32 14" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"><path d="M2 7h20"/><polygon points="20,3 30,7 20,11" fill="currentColor" stroke="none"/></svg>,
};

// ─── MOBILE DETECTION ───────────────────────────────────
function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState(() => window.innerWidth <= 960);
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 960px)');
    const handler = e => setIsMobile(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);
  return isMobile;
}

// ─── INLINE EDIT ────────────────────────────────────────
function InlineInput({ value, onSave, style, multiline, placeholder, className }) {
  const [draft, setDraft] = React.useState(value || '');
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (ref.current) {
      ref.current.focus();
      if (ref.current.select) ref.current.select();
      if (multiline) { ref.current.style.height='auto'; ref.current.style.height=ref.current.scrollHeight+'px'; }
    }
  }, []);
  const commit = () => onSave(draft);
  const onKeyDown = (e) => {
    if (!multiline && e.key === 'Enter') { e.preventDefault(); onSave(draft); }
    if (e.key === 'Escape') { onSave(null); }
    e.stopPropagation();
  };
  const shared = {
    ref, value: draft, placeholder,
    onChange: e => { setDraft(e.target.value); if(multiline&&ref.current){ref.current.style.height='auto';ref.current.style.height=ref.current.scrollHeight+'px';} },
    onBlur: commit, onKeyDown,
    onClick: e => e.stopPropagation(),
    onMouseDown: e => e.stopPropagation(),
    'data-inline-edit': 'true',
    spellCheck: false,
    style: { display:'block', width:'100%', boxSizing:'border-box', outline:'none',
      background:'var(--insp-input-bg, rgba(255,255,255,0.07))', border:'1px solid rgba(180,150,255,0.45)',
      borderRadius:5, padding:'1px 5px', fontFamily:'inherit', resize:'none', overflow:'hidden',
      color:'var(--text)',
      ...style },
  };
  if (multiline) return <textarea rows={2} {...shared}/>;
  return <input type="text" {...shared}/>;
}

function Icon({ name, size = 14, style }) {
  const svg = ICONS[name];
  if (!svg) return <span style={{ width: size, height: size, display: 'inline-block' }}/>;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                   width: size, height: size, flexShrink: 0, ...style }}>
      {React.cloneElement(svg, { width: size, height: size, style: { display: 'block' } })}
    </span>
  );
}

// ─── LEFT RAIL ──────────────────────────────────────────
function LeftRail({ active }) {
  const items = [
    { id: 'home', icon: 'home', tip: 'Home' },
    { id: 'canvas', icon: 'layers', tip: 'Canvas' },
    { id: 'check', icon: 'check', tip: 'Tasks' },
    { id: 'calendar', icon: 'calendar', tip: 'Calendar' },
  ];
  return (
    <div style={{ width: 52, flexShrink: 0, display: 'flex', flexDirection: 'column',
                  alignItems: 'center', paddingTop: 12, gap: 4,
                  borderRight: '1px solid var(--border-l)',
                  background: 'var(--rail)' }}>
      {/* Logo */}
      <div style={{ width: 28, height: 28, borderRadius: 8, marginBottom: 14,
                    background: 'linear-gradient(135deg, #b088ff, #7fb3ff)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 13, fontWeight: 800, color: 'white', letterSpacing: '-0.05em' }}>S</div>
      {items.map(it => (
        <div key={it.id} className="strat-tip" data-tip={it.tip}>
          <button className="strat-rail-icon"
                  style={{ color: active === it.id ? 'var(--accent)' : 'var(--text-f)',
                           background: active === it.id ? 'var(--glow)' : 'transparent' }}>
            <Icon name={it.icon} size={17}/>
          </button>
        </div>
      ))}
      <div style={{ flex: 1 }}/>
      <div style={{ marginBottom: 14, width: 28, height: 28, borderRadius: '50%',
                    background: 'linear-gradient(135deg, #7fe0a8, #60a5fa)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 11, fontWeight: 700, color: '#0a1a12', cursor: 'pointer' }}>A</div>
    </div>
  );
}

// ─── PAN/ZOOM ────────────────────────────────────────────
function PanZoom({ children, initial = { x: 0, y: 0, s: 1 } }) {
  const ref = React.useRef(null);
  const stateRef = React.useRef({ x: initial.x, y: initial.y, s: initial.s });
  const dragRef = React.useRef(null);
  const pinchRef = React.useRef(null);

  const apply = () => {
    const el = ref.current; if (!el) return;
    const { x, y, s } = stateRef.current;
    el.style.transform = `translate(${x}px,${y}px) scale(${s})`;
    el.style.transformOrigin = '0 0';
    // store on host parent for toWorld
    const host = el.parentElement;
    if (host) host.__pz = { x, y, s };
  };

  React.useEffect(() => {
    apply();
    const host = ref.current?.parentElement;
    if (!host) return;

    const onWheel = (e) => {
      e.preventDefault();
      const rect = host.getBoundingClientRect();
      const { x, y, s } = stateRef.current;
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;
      const delta = e.ctrlKey || e.metaKey ? -e.deltaY * 0.008 : -e.deltaY * 0.0015;
      const ns = Math.min(3, Math.max(0.25, s + delta * s));
      stateRef.current = { x: mx - (mx - x) * (ns / s), y: my - (my - y) * (ns / s), s: ns };
      apply();
    };

    const onMouseDown = (e) => {
      if (e.target.closest('[data-no-pan]')) return;
      if (e.button !== 0) return;
      dragRef.current = { sx: e.clientX, sy: e.clientY, ox: stateRef.current.x, oy: stateRef.current.y };
      host.style.cursor = 'grabbing';
    };
    const onMouseMove = (e) => {
      if (!dragRef.current) return;
      const dx = e.clientX - dragRef.current.sx;
      const dy = e.clientY - dragRef.current.sy;
      stateRef.current.x = dragRef.current.ox + dx;
      stateRef.current.y = dragRef.current.oy + dy;
      apply();
    };
    const onMouseUp = () => { dragRef.current = null; host.style.cursor = ''; };

    // ── Touch: 1-finger pan, 2-finger pinch-zoom ──────────
    const onTouchStart = (e) => {
      // If the touch target is inside a [data-no-pan] element (buttons, cards, etc.)
      // do not preventDefault so the browser can synthesise click events for those
      // interactive elements — critical for iPad / iOS Safari.
      if (e.target.closest('[data-no-pan]')) return;
      e.preventDefault(); // prevent page scroll / browser pinch-zoom, and suppress synthetic mouse events
      if (e.touches.length === 1) {
        pinchRef.current = null;
        dragRef.current = {
          sx: e.touches[0].clientX, sy: e.touches[0].clientY,
          ox: stateRef.current.x,   oy: stateRef.current.y,
        };
      } else if (e.touches.length >= 2) {
        dragRef.current = null;
        const t0 = e.touches[0], t1 = e.touches[1];
        const rect = host.getBoundingClientRect();
        pinchRef.current = {
          startDist: Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY),
          startScale: stateRef.current.s,
          midX: (t0.clientX + t1.clientX) / 2 - rect.left,
          midY: (t0.clientY + t1.clientY) / 2 - rect.top,
          ox: stateRef.current.x,
          oy: stateRef.current.y,
        };
      }
    };

    const onTouchMove = (e) => {
      e.preventDefault(); // prevent page scroll / browser pinch-zoom
      if (!dragRef.current && !pinchRef.current) return;
      if (e.touches.length === 1 && dragRef.current) {
        stateRef.current.x = dragRef.current.ox + (e.touches[0].clientX - dragRef.current.sx);
        stateRef.current.y = dragRef.current.oy + (e.touches[0].clientY - dragRef.current.sy);
        apply();
      } else if (e.touches.length >= 2 && pinchRef.current) {
        const t0 = e.touches[0], t1 = e.touches[1];
        const dist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);
        const ns = Math.min(3, Math.max(0.25, pinchRef.current.startScale * (dist / pinchRef.current.startDist)));
        const prevS = pinchRef.current.startScale;
        stateRef.current = {
          x: pinchRef.current.midX - (pinchRef.current.midX - pinchRef.current.ox) * (ns / prevS),
          y: pinchRef.current.midY - (pinchRef.current.midY - pinchRef.current.oy) * (ns / prevS),
          s: ns,
        };
        apply();
      }
    };

    const onTouchEnd = () => { dragRef.current = null; pinchRef.current = null; };

    host.addEventListener('wheel', onWheel, { passive: false });
    host.addEventListener('mousedown', onMouseDown);
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);
    host.addEventListener('touchstart',  onTouchStart,  { passive: false });
    host.addEventListener('touchmove',   onTouchMove,   { passive: false });
    host.addEventListener('touchend',    onTouchEnd);
    host.addEventListener('touchcancel', onTouchEnd);
    return () => {
      host.removeEventListener('wheel', onWheel);
      host.removeEventListener('mousedown', onMouseDown);
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);
      host.removeEventListener('touchstart',  onTouchStart);
      host.removeEventListener('touchmove',   onTouchMove);
      host.removeEventListener('touchend',    onTouchEnd);
      host.removeEventListener('touchcancel', onTouchEnd);
    };
  }, []);

  return (
    <div ref={ref} style={{ position: 'absolute', userSelect: 'none', touchAction: 'none' }}>
      {children}
    </div>
  );
}

// ─── STARS ───────────────────────────────────────────────
function Stars() {
  const stars = React.useMemo(() => {
    const out = []; let seed = 7;
    const r = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
    for (let i = 0; i < 110; i++) out.push({ x: r()*100, y: r()*100, s: r()*1.8+0.3, o: r()*0.45+0.15 });
    return out;
  }, []);
  return (
    <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden' }}>
      {stars.map((s, i) => (
        <div key={i} style={{ position: 'absolute', left: `${s.x}%`, top: `${s.y}%`,
             width: s.s, height: s.s, borderRadius: '50%', background: 'white', opacity: s.o }}/>
      ))}
    </div>
  );
}

// ─── RING ────────────────────────────────────────────────
function Ring({ l, val, c }) {
  const R = 20, C = 2 * Math.PI * R;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
      <svg width={52} height={52}>
        <circle cx={26} cy={26} r={R} fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth={4}/>
        <circle cx={26} cy={26} r={R} fill="none" stroke={c} strokeWidth={4}
                strokeDasharray={C} strokeDashoffset={C * (1 - val/100)}
                strokeLinecap="round" transform="rotate(-90 26 26)"/>
        <text x={26} y={30} textAnchor="middle" fontSize="10" fill="rgba(220,225,235,0.8)" fontWeight="600">{val}%</text>
      </svg>
      <div style={{ fontSize: 10, color: 'rgba(200,210,225,0.65)' }}>{l}</div>
    </div>
  );
}

// ─── INSPECTOR ───────────────────────────────────────────
const CLUSTER_COLORS = ['#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#7fb3ff','#f472b6','#fb7185','#a3e635'];
const NOTE_COLORS   = ['#fde68a','#fca5a5','#bbf7d0','#bfdbfe','#e9d5ff','#fed7aa','#fecdd3','#d9f99d'];
const MAIN_COLORS   = ['#7fb3ff','#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#f472b6'];

const inspectorRow = {
  display:'flex', alignItems:'center', gap:8, padding:'6px 6px',
  borderRadius:6, background:'var(--insp-row-bg,rgba(255,255,255,0.02))', border:'1px solid var(--insp-row-bd,rgba(255,255,255,0.04))',
};
const inspectorGhostBtn = {
  display:'inline-flex', alignItems:'center', gap:5, padding:'5px 10px',
  fontSize:11, fontWeight:500, background:'var(--insp-btn-bg,rgba(255,255,255,0.04))',
  color:'var(--text)', border:'1px solid var(--border)',
  borderRadius:6, cursor:'pointer', fontFamily:'inherit',
};
const inputBase = {
  width:'100%', boxSizing:'border-box', padding:'7px 10px', fontSize:12.5,
  background:'var(--insp-input-bg,rgba(255,255,255,0.04))', border:'1px solid var(--border)',
  borderRadius:7, color:'var(--text)', outline:'none', fontFamily:'inherit',
  transition:'border-color .12s',
};

function InspectorTitleField({ value, placeholder, onChange }) {
  const [focused, setFocused] = React.useState(false);
  return (
    <input type="text" value={value||''} placeholder={placeholder} onChange={e=>onChange(e.target.value)}
      onFocus={()=>setFocused(true)} onBlur={()=>setFocused(false)} spellCheck={false}
      style={{ marginTop:4, width:'100%', boxSizing:'border-box',
        fontFamily:'Instrument Serif, serif', fontSize:22, lineHeight:1.2,
        color:'var(--text)',
        background: focused?'var(--insp-input-bg,rgba(255,255,255,0.04))':'transparent',
        border:'1px solid', borderColor: focused?'var(--accent)':'transparent',
        borderRadius:6, padding:'2px 6px', margin:'4px -6px 0', outline:'none',
        transition:'background .12s, border-color .12s' }}/>
  );
}

function InspectorSubtitleField({ value, placeholder, onChange }) {
  const ref = React.useRef(null);
  const [focused, setFocused] = React.useState(false);
  const resize = () => {
    const el = ref.current; if (!el) return;
    el.style.height = 'auto';
    if (value && value.length > 0) {
      el.style.height = el.scrollHeight + 'px';
    }
  };
  React.useLayoutEffect(resize, [value]);
  return (
    <textarea ref={ref} value={value||''} placeholder={placeholder} rows={1} spellCheck={false}
      onChange={e=>onChange(e.target.value)} onFocus={()=>setFocused(true)} onBlur={()=>setFocused(false)}
      style={{ display:'block', width:'100%', boxSizing:'border-box', margin:'6px 0 0',
        padding:'4px 6px', fontFamily:'inherit', fontSize:12.5, lineHeight:1.45,
        color:'var(--text-m)', background:focused?'var(--insp-input-bg,rgba(255,255,255,0.04))':'transparent',
        border:'1px solid', borderColor:focused?'var(--accent)':'transparent',
        borderRadius:6, outline:'none', resize:'none', overflow:'hidden',
        transition:'background .12s, border-color .12s' }}/>
  );
}

function ColorSwatches({ colors, value, onChange }) {
  return (
    <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
      {colors.map(c => (
        <button key={c} onClick={()=>onChange(c)} title={c}
          style={{ width:24, height:24, borderRadius:7, padding:0, background:c, cursor:'pointer',
            border: value===c?'2px solid var(--text)':'1px solid var(--border)',
            boxShadow: value===c?`0 0 0 2px ${c}66, 0 0 12px ${c}60`:'none',
            transition:'transform .1s' }}
          onMouseEnter={e=>e.currentTarget.style.transform='scale(1.1)'}
          onMouseLeave={e=>e.currentTarget.style.transform='scale(1)'}/>
      ))}
    </div>
  );
}

const formatDue = (due) => {
  if (!due) return '';
  if (/^\d{4}-\d{2}-\d{2}$/.test(due)) {
    const d = new Date(due + 'T00:00:00');
    return d.toLocaleDateString('en-US', { month:'short', day:'numeric' });
  }
  return due;
};

function TaskRow({ t, onChange, onDelete, isNew }) {
  const [hover, setHover] = React.useState(false);
  const textRef = React.useRef(null);
  const dateRef = React.useRef(null);
  React.useEffect(() => {
    if (isNew && textRef.current) { textRef.current.focus(); textRef.current.select(); }
  }, [isNew]);
  return (
    <div style={{...inspectorRow, opacity: t.done ? 0.6 : 1, gap:6}} onMouseEnter={()=>setHover(true)} onMouseLeave={()=>setHover(false)}>
      <input type="checkbox" checked={!!t.done} onChange={e=>onChange({done:e.target.checked})}
        style={{ accentColor:'var(--accent)', width:14, height:14, flexShrink:0, cursor:'pointer', margin:0 }}/>
      <input ref={textRef} data-task-input type="text" value={t.t||''} onChange={e=>onChange({t:e.target.value})}
        placeholder="Task name…"
        style={{ flex:1, minWidth:0, background:'transparent', border:'none',
          color:'var(--text)', fontSize:12, outline:'none', fontFamily:'inherit',
          textDecoration:t.done?'line-through':'none' }}/>
      {/* Calendar icon — triggers hidden date input */}
      <div style={{ position:'relative', width:22, height:22, flexShrink:0,
        opacity: hover||t.due ? 1 : 0, transition:'opacity .15s' }}
        title={t.due ? formatDue(t.due) : 'Set due date'}>
        <input ref={dateRef} type="date" value={t.due||''} onChange={e=>onChange({due:e.target.value})}
          style={{ position:'absolute', inset:0, opacity:0, cursor:'pointer', width:'100%', height:'100%', zIndex:2 }}/>
        <div style={{ position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center',
          borderRadius:4, color: t.due ? 'var(--accent)' : 'var(--text-f)',
          background: t.due ? 'var(--glow)' : 'transparent',
          pointerEvents:'none' }}>
          <Icon name="calendar" size={12}/>
        </div>
      </div>
      <button onClick={onDelete}
        style={{ width:18,height:18,border:'none',background:'transparent',
          color:'var(--text-f)',cursor:'pointer',borderRadius:4,
          opacity:hover?1:0,transition:'opacity .12s',
          display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0 }}>
        <Icon name="x" size={10}/>
      </button>
    </div>
  );
}

// ─── CONFIRM MODAL ───────────────────────────────────────
function ConfirmModal({ title, message, onConfirm, onCancel }) {
  React.useEffect(() => {
    const onKey = e => { if(e.key==='Escape') onCancel(); if(e.key==='Enter') onConfirm(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);
  return (
    <div onClick={onCancel} style={{ position:'fixed', inset:0, zIndex:9000,
      display:'flex', alignItems:'center', justifyContent:'center',
      background:'rgba(0,0,0,0.45)', backdropFilter:'blur(6px)',
      animation:'fadeIn .15s ease' }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background:'var(--surface-s,#0d1119)', border:'1px solid var(--border)',
        borderRadius:16, padding:'28px 28px 22px', maxWidth:340, width:'90%',
        boxShadow:'0 32px 80px rgba(0,0,0,0.5)',
        animation:'slideUp .15s ease' }}>
        <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:10 }}>
          <div style={{ width:32, height:32, borderRadius:9, background:'rgba(248,113,113,0.15)',
            display:'flex', alignItems:'center', justifyContent:'center' }}>
            <Icon name="trash" size={15} style={{ color:'#f87171' }}/>
          </div>
          <div style={{ fontFamily:'Instrument Serif, serif', fontSize:18, color:'var(--text)' }}>{title||'Delete'}</div>
        </div>
        <div style={{ fontSize:13, color:'var(--text-m)', lineHeight:1.55, marginBottom:22 }}>{message}</div>
        <div style={{ display:'flex', gap:8, justifyContent:'flex-end' }}>
          <button onClick={onCancel}
            style={{ padding:'7px 16px', fontSize:12.5, fontWeight:500, borderRadius:8,
              background:'rgba(128,128,128,0.08)', border:'1px solid var(--border)',
              color:'var(--text)', cursor:'pointer', fontFamily:'inherit' }}>
            Cancel
          </button>
          <button onClick={onConfirm}
            style={{ padding:'7px 16px', fontSize:12.5, fontWeight:600, borderRadius:8,
              background:'rgba(248,113,113,0.18)', border:'1px solid rgba(248,113,113,0.35)',
              color:'#fca5a5', cursor:'pointer', fontFamily:'inherit' }}>
            Delete
          </button>
        </div>
      </div>
    </div>
  );
}

function InspectorSection({ title, trailing, children, collapsed=false }) {
  const [open, setOpen] = React.useState(!collapsed);
  return (
    <div style={{ marginTop:14 }}>
      <div onClick={()=>setOpen(o=>!o)}
        style={{ display:'flex',alignItems:'center',gap:8,cursor:'pointer',padding:'4px 2px 8px',userSelect:'none' }}>
        <span style={{ fontSize:9.5,letterSpacing:1.2,textTransform:'uppercase',
          color:'var(--text-m)',fontWeight:600 }}>{title}</span>
        <div style={{ flex:1,height:1,background:'var(--border)' }}/>
        {trailing}
        <Icon name="chevronR" size={9}
          style={{ color:'rgba(200,210,225,0.35)',
            transform:open?'rotate(90deg)':'none',transition:'transform .15s' }}/>
      </div>
      {open && <div style={{ display:'flex',flexDirection:'column',gap:10 }}>{children}</div>}
    </div>
  );
}

function Inspector({ item, connections, itemById, onUpdate, onDelete, onDuplicate, onDisconnect, onClose, onFocus }) {
  const [confirmDel, setConfirmDel] = React.useState(false);
  const [newTaskId, setNewTaskId] = React.useState(null);
  const bodyRef = React.useRef(null);

  // Scroll to bottom when a task is added
  React.useEffect(() => {
    if (newTaskId && bodyRef.current) {
      setTimeout(() => {
        if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight + 200;
      }, 60);
    }
  }, [item?.tasks?.length, newTaskId]);
  if (!item) return (
    <div style={{ padding:'22px 20px', display:'flex', flexDirection:'column', height:'100%' }}>
      <div style={{ fontSize:9.5, letterSpacing:1.3, textTransform:'uppercase',
        color:'var(--text-f)', fontWeight:600, marginBottom:8 }}>Inspector</div>
      <div style={{ fontFamily:'Instrument Serif, serif', fontSize:22, lineHeight:1.2,
        color:'var(--text)', opacity:0.75, marginBottom:14 }}>Nothing selected</div>
      <div style={{ fontSize:12, color:'var(--text-m)', lineHeight:1.55 }}>
        Click any element on the canvas to inspect and edit it here.
      </div>
    </div>
  );

  const kindMeta = { cluster:{label:'Cluster'}, main:{label:'Objective'}, medium:{label:'Milestone'},
    deadline:{label:'Deadline'}, note:{label:'Note'}, north:{label:'North Star'}, text:{label:'Text'},
    shape:{label:'Shape'}, hyperlink:{label:'Hyperlink'}, tree:{label:'Tree'}, arrow:{label:'Arrow'} };
  const meta = kindMeta[item.kind] || { label: item.kind };
  const color = item.color || (item.kind==='deadline'?'#f87171':item.kind==='north'?'#b088ff':'#8a97ab');
  const related = connections.filter(c=>c.from===item.id||c.to===item.id);

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', minHeight:0 }}>
      {/* Header */}
      <div style={{ padding:'14px 18px 12px', borderBottom:'1px solid var(--border)',
        background:`linear-gradient(180deg, ${color}14, transparent)` }}>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <div style={{ width:22, height:22, borderRadius:6, display:'flex', alignItems:'center',
            justifyContent:'center', background:`${color}26`, color }}>
            <Icon name={item.kind==='cluster'?'layers':item.kind==='note'?'sticky':
              item.kind==='deadline'?'calendar':item.kind==='north'?'sparkle':item.kind==='hyperlink'?'link':'target'} size={12}/>
          </div>
          <div style={{ fontSize:9.5, letterSpacing:1.2, textTransform:'uppercase',
            color:'var(--text-f)', fontWeight:600 }}>{meta.label}</div>
          <div style={{ flex:1 }}/>
          <button onClick={onClose}
            style={{ width:24, height:24, border:'none', background:'transparent',
              color:'var(--text-m)', cursor:'pointer', borderRadius:5,
              display:'flex', alignItems:'center', justifyContent:'center' }}>
            <Icon name="x" size={12}/>
          </button>
        </div>
        {item.kind === 'north' ? (
          <InspectorTitleField
            value={item.label||''}
            placeholder="Vision title"
            onChange={v=>onUpdate(item.id,{label:v})}/>
        ) : item.kind === 'text' ? (
          <div style={{ fontFamily:'Instrument Serif, serif', fontSize:22, lineHeight:1.2, marginTop:6,
            color:'var(--text-m)' }}>Text element</div>
        ) : (
          <InspectorTitleField
            value={item.kind==='note'?(item.title||''):(item.label||'')}
            placeholder={item.kind==='note'?'Untitled note':item.kind==='hyperlink'?'Link name':'Untitled'}
            onChange={v=>onUpdate(item.id, item.kind==='note'?{title:v}:{label:v})}/>
        )}
        {/* Intent / body as subtitle */}
        {item.kind !== 'text' && item.kind !== 'deadline' && item.kind !== 'hyperlink' && (() => {
          const keys = { cluster:'intent', main:'body', medium:'body', note:'body', north:'body' };
          const k = keys[item.kind];
          if (!k) return null;
          const ph = { cluster:'Intent — one line on why this area matters', main:'Describe this objective',
            medium:'Describe this milestone', note:'Write a note…', north:'Your vision in one sentence' };
          return <InspectorSubtitleField key={item.id} value={item[k]||''} placeholder={ph[item.kind]}
                   onChange={v=>onUpdate(item.id,{[k]:v})}/>;
        })()}
      </div>

      {/* Body */}
      <div ref={bodyRef} style={{ flex:1, overflow:'auto', padding:'14px 16px 24px' }}>
        {item.kind==='deadline' && (
          <InspectorSection title="Content">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Date</div>
              <input type="text" value={item.date||''} onChange={e=>onUpdate(item.id,{date:e.target.value})}
                placeholder="May 15" style={{ ...inputBase, fontFamily:'ui-monospace,monospace' }}/>
            </div>
          </InspectorSection>
        )}

        {item.kind==='hyperlink' && (
          <InspectorSection title="Link">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>URL</div>
              <input type="url" value={item.url||''} onChange={e=>onUpdate(item.id,{url:e.target.value})}
                placeholder="https://example.com" style={{ ...inputBase, fontFamily:'ui-monospace,monospace', fontSize:11.5 }}
                onFocus={e=>e.target.style.borderColor='var(--accent)'}
                onBlur={e=>e.target.style.borderColor='var(--border)'}/>
            </div>
            {item.url && item.url.trim() && (
              <button onClick={()=>{ let u=item.url.trim(); if(!/^https?:\/\//i.test(u)) u='https://'+u; window.open(u,'_blank','noopener,noreferrer'); }}
                style={{ ...inspectorGhostBtn, width:'100%', justifyContent:'center', gap:6 }}>
                <Icon name="externalLink" size={12}/>
                Open link
              </button>
            )}
          </InspectorSection>
        )}

        {item.kind==='hyperlink' && (
          <InspectorSection title="Notes" collapsed={false}>
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Notes <span style={{ opacity:0.5 }}>(shown in Details view)</span></div>
              <textarea value={item.notes||''} onChange={e=>onUpdate(item.id,{notes:e.target.value})}
                placeholder="Add context or a short description…" rows={3}
                style={{ ...inputBase, resize:'vertical', lineHeight:1.5 }}
                onFocus={e=>e.target.style.borderColor='var(--accent)'}
                onBlur={e=>e.target.style.borderColor='var(--border)'}/>
            </div>
          </InspectorSection>
        )}

        {item.kind==='hyperlink' && (
          <InspectorSection title="Appearance">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Accent color</div>
              <ColorSwatches colors={CLUSTER_COLORS} value={item.color||'#7fb3ff'}
                onChange={v=>onUpdate(item.id,{color:v})}/>
            </div>
          </InspectorSection>
        )}

        {item.kind==='medium' && (
          <InspectorSection title="Due date">            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Date (optional)</div>
              <input type="text" value={item.date||''} onChange={e=>onUpdate(item.id,{date:e.target.value})}
                placeholder="e.g. May 30" style={{ ...inputBase, fontFamily:'ui-monospace,monospace' }}/>
            </div>
          </InspectorSection>
        )}

        {/* North Star properties */}
        {item.kind==='north' && (
          <InspectorSection title="Review">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:6 }}>Review rhythm</div>
              <div style={{ display:'flex', flexWrap:'wrap', gap:4 }}>
                {['Daily','Weekly','Biweekly','Monthly'].map(r=>(
                  <button key={r} onClick={()=>onUpdate(item.id,{review:r})}
                    style={{ padding:'4px 10px', fontSize:11, borderRadius:6, cursor:'pointer', fontFamily:'inherit',
                      background:(item.review||'Weekly')===r?'rgba(180,150,255,0.22)':'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                      border:(item.review||'Weekly')===r?'1px solid rgba(200,170,255,0.4)':'1px solid var(--border)',
                      color:(item.review||'Weekly')===r?'#e0d4ff':'var(--text-m)' }}>
                    {r}
                  </button>
                ))}
              </div>
            </div>
          </InspectorSection>
        )}

        {item.kind==='north' && (
          <InspectorSection title="Appearance">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Glow color</div>
              <ColorSwatches colors={CLUSTER_COLORS} value={item.color||'#b088ff'}
                onChange={v=>onUpdate(item.id,{color:v})}/>
            </div>
          </InspectorSection>
        )}

        {/* Text element */}
        {item.kind==='text' && (
          <InspectorSection title="Content">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Text</div>
              <input type="text" value={item.text||''} onChange={e=>onUpdate(item.id,{text:e.target.value})}
                placeholder="Label" style={{ ...inputBase }}/>
            </div>
            <div style={{ display:'flex', gap:8 }}>
              <div style={{ flex:1 }}>
                <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Size · {item.fontSize||18}px</div>
                <input type="range" min={10} max={72} step={1} value={item.fontSize||18}
                  onChange={e=>onUpdate(item.id,{fontSize:parseFloat(e.target.value)})}
                  style={{ width:'100%', accentColor:'var(--accent)' }}/>
              </div>
            </div>
            <div style={{ display:'flex', gap:8, marginTop:4 }}>
              {[['Bold','bold',item.bold],['Italic','italic',item.italic],['Serif','serif',item.serif]].map(([label,key,val])=>(
                <button key={key} onClick={()=>onUpdate(item.id,{[key]:!val})}
                  style={{ flex:1, padding:'5px 0', fontSize:11, borderRadius:6, cursor:'pointer',
                    background: val?'rgba(180,150,255,0.2)':'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                    border: val?'1px solid rgba(200,170,255,0.4)':'1px solid var(--border)',
                    color: val?'#e0d4ff':'var(--text-m)', fontFamily:'inherit',
                    fontWeight: key==='bold'?600:400, fontStyle: key==='italic'?'italic':'normal' }}>
                  {label}
                </button>
              ))}
            </div>
          </InspectorSection>
        )}

        {/* Appearance for text */}
        {item.kind==='text' && (
          <InspectorSection title="Color">
            <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
              {['rgba(220,225,235,0.85)','#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#7fb3ff','#f87171','#ffffff'].map(c=>(
                <button key={c} onClick={()=>onUpdate(item.id,{color:c})}
                  style={{ width:24, height:24, borderRadius:7, padding:0, background:c==='rgba(220,225,235,0.85)'?'rgba(220,225,235,0.85)':c,
                    cursor:'pointer', border: item.color===c?'2px solid rgba(255,255,255,0.9)':'1px solid rgba(255,255,255,0.12)',
                    transition:'transform .1s' }}
                  onMouseEnter={e=>e.currentTarget.style.transform='scale(1.1)'}
                  onMouseLeave={e=>e.currentTarget.style.transform='scale(1)'}/>
              ))}
            </div>
          </InspectorSection>
        )}

        {/* ── Shape inspector ── */}
        {item.kind === 'shape' && (
          <>
            <InspectorSection title="Fill">
              <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
                {SHAPE_COLORS.map(c => (
                  <button key={c} onClick={()=>onUpdate(item.id,{fill:c})} title={c==='none'?'No fill':c}
                    style={{ width:24, height:24, borderRadius:7, padding:0, cursor:'pointer',
                      background: c==='none'
                        ? 'repeating-linear-gradient(45deg,rgba(200,200,220,0.15) 0,rgba(200,200,220,0.15) 2px,transparent 0,transparent 50%) center/8px 8px'
                        : c,
                      border: (item.fill??'none')===c ? '2px solid rgba(255,255,255,0.95)' : '1px solid rgba(255,255,255,0.1)',
                      boxShadow: (item.fill??'none')===c && c!=='none' ? `0 0 0 2px ${c}66` : 'none',
                      transition:'transform .1s' }}
                    onMouseEnter={e=>e.currentTarget.style.transform='scale(1.1)'}
                    onMouseLeave={e=>e.currentTarget.style.transform='scale(1)'}/>
                ))}
              </div>
            </InspectorSection>
            <InspectorSection title="Stroke">
              <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
                {['#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#7fb3ff','#f472b6','#fb7185','rgba(220,225,235,0.6)','#ffffff','rgba(255,255,255,0.12)'].map(c=>(
                  <button key={c} onClick={()=>onUpdate(item.id,{stroke:c})} title={c}
                    style={{ width:24, height:24, borderRadius:7, padding:0, cursor:'pointer',
                      background: c,
                      border: item.stroke===c ? '2px solid rgba(255,255,255,0.95)' : '1px solid rgba(255,255,255,0.1)',
                      transition:'transform .1s' }}
                    onMouseEnter={e=>e.currentTarget.style.transform='scale(1.1)'}
                    onMouseLeave={e=>e.currentTarget.style.transform='scale(1)'}/>
                ))}
              </div>
              <div style={{ marginTop:8 }}>
                <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>
                  Stroke width · {item.strokeWidth||2}px
                </div>
                <input type="range" min={0.5} max={12} step={0.5} value={item.strokeWidth||2}
                  onChange={e=>onUpdate(item.id,{strokeWidth:parseFloat(e.target.value)})}
                  style={{ width:'100%', accentColor:'var(--accent)' }}/>
              </div>
            </InspectorSection>
            {item.shapeType !== 'line' && (
              <InspectorSection title="Text">
                <div>
                  <input type="text" value={item.text||''} placeholder="Center text"
                    onChange={e=>onUpdate(item.id,{text:e.target.value})} style={inputBase}/>
                </div>
                <div style={{ display:'flex', gap:8 }}>
                  <div style={{ flex:1 }}>
                    <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Size · {item.fontSize||14}px</div>
                    <input type="range" min={8} max={64} step={1} value={item.fontSize||14}
                      onChange={e=>onUpdate(item.id,{fontSize:parseFloat(e.target.value)})}
                      style={{ width:'100%', accentColor:'var(--accent)' }}/>
                  </div>
                </div>
                <div style={{ display:'flex', gap:4 }}>
                  {[['Bold','bold',item.bold]].map(([lbl,key,val])=>(
                    <button key={key} onClick={()=>onUpdate(item.id,{[key]:!val})}
                      style={{ padding:'5px 12px', fontSize:11, borderRadius:6, cursor:'pointer', fontFamily:'inherit',
                        background: val?'rgba(180,150,255,0.2)':'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                        border: val?'1px solid rgba(200,170,255,0.4)':'1px solid var(--border)',
                        color: val?'#e0d4ff':'var(--text-m)', fontWeight:600 }}>{lbl}</button>
                  ))}
                  <div style={{ flex:1 }}/>
                  {['rgba(220,225,235,0.9)','#b088ff','#7fe0a8','#fbbf24','#7fb3ff','#ffffff'].map(c=>(
                    <button key={c} onClick={()=>onUpdate(item.id,{textColor:c})}
                      style={{ width:22, height:22, borderRadius:6, padding:0, background:c, cursor:'pointer',
                        border: item.textColor===c?'2px solid rgba(255,255,255,0.9)':'1px solid rgba(255,255,255,0.1)',
                        transition:'transform .1s' }}
                      onMouseEnter={e=>e.currentTarget.style.transform='scale(1.1)'}
                      onMouseLeave={e=>e.currentTarget.style.transform='scale(1)'}/>
                  ))}
                </div>
              </InspectorSection>
            )}
            <InspectorSection title="Size">
              <div>
                <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Width · {Math.round(item.w||120)}px</div>
                <input type="range" min={20} max={800} step={2} value={item.w||120}
                  onChange={e=>onUpdate(item.id,{w:parseFloat(e.target.value)})}
                  style={{ width:'100%', accentColor:'var(--accent)' }}/>
              </div>
              {item.shapeType !== 'line' && (
                <div>
                  <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Height · {Math.round(item.h||80)}px</div>
                  <input type="range" min={20} max={600} step={2} value={item.h||80}
                    onChange={e=>onUpdate(item.id,{h:parseFloat(e.target.value)})}
                    style={{ width:'100%', accentColor:'var(--accent)' }}/>
                </div>
              )}
              {(item.shapeType==='rect'||item.shapeType==='square') && (
                <div>
                  <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Corner radius · {item.cornerRadius||0}px</div>
                  <input type="range" min={0} max={80} step={1} value={item.cornerRadius||0}
                    onChange={e=>onUpdate(item.id,{cornerRadius:parseFloat(e.target.value)})}
                    style={{ width:'100%', accentColor:'var(--accent)' }}/>
                </div>
              )}
              <div>
                <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Opacity · {item.opacity??100}%</div>
                <input type="range" min={10} max={100} step={5} value={item.opacity??100}
                  onChange={e=>onUpdate(item.id,{opacity:parseFloat(e.target.value)})}
                  style={{ width:'100%', accentColor:'var(--accent)' }}/>
              </div>
            </InspectorSection>
          </>
        )}

        {/* ── Tree inspector ── */}
        {item.kind === 'tree' && (
          <>
            <InspectorSection title="Root">
              <input type="text" value={item.root||''} onChange={e=>onUpdate(item.id,{root:e.target.value})}
                placeholder="Root topic" style={inputBase}
                onFocus={e=>e.target.style.borderColor='var(--accent)'}
                onBlur={e=>e.target.style.borderColor='var(--border)'}/>
            </InspectorSection>
            <InspectorSection title="Branches"
              trailing={
                <button onClick={()=>{
                  const bid='b'+Math.random().toString(36).slice(2,5);
                  onUpdate(item.id,{branches:[...(item.branches||[]),{id:bid,label:'New branch'}]});
                }} style={inspectorGhostBtn}>
                  <Icon name="plus" size={10}/> Add
                </button>
              }>
              {!(item.branches||[]).length ? (
                <div style={{fontSize:11.5,color:'var(--text-f)',padding:'6px 2px'}}>No branches yet. Add one above.</div>
              ) : (
                <div style={{display:'flex',flexDirection:'column',gap:4}}>
                  {(item.branches||[]).map((b,i)=>(
                    <div key={b.id||i} style={{...inspectorRow,gap:6}}>
                      <div style={{width:7,height:7,borderRadius:'50%',
                        background:item.color||'#b088ff',flexShrink:0,opacity:0.7}}/>
                      <input type="text" value={b.label||''} placeholder="Branch label"
                        onChange={e=>{
                          const nb=[...(item.branches||[])];
                          nb[i]={...nb[i],label:e.target.value};
                          onUpdate(item.id,{branches:nb});
                        }}
                        style={{flex:1,background:'transparent',border:'none',
                          color:'var(--text)',fontSize:12,outline:'none',fontFamily:'inherit'}}/>
                      <button onClick={()=>onUpdate(item.id,{branches:item.branches.filter((_,j)=>j!==i)})}
                        style={{width:18,height:18,border:'none',background:'transparent',
                          color:'var(--text-f)',cursor:'pointer',borderRadius:4,
                          display:'flex',alignItems:'center',justifyContent:'center'}}>
                        <Icon name="x" size={10}/>
                      </button>
                    </div>
                  ))}
                </div>
              )}
            </InspectorSection>
            <InspectorSection title="Appearance">
              <div>
                <div style={{fontSize:10.5,color:'var(--text-m)',marginBottom:4}}>Color</div>
                <ColorSwatches colors={CLUSTER_COLORS} value={item.color||'#b088ff'}
                  onChange={v=>onUpdate(item.id,{color:v})}/>
              </div>
              <div>
                <div style={{fontSize:10.5,color:'var(--text-m)',marginBottom:4}}>
                  Root width · {item.rootW||112}px
                </div>
                <input type="range" min={80} max={240} step={4} value={item.rootW||112}
                  onChange={e=>onUpdate(item.id,{rootW:parseFloat(e.target.value)})}
                  style={{width:'100%',accentColor:'var(--accent)'}}/>
              </div>
              <div>
                <div style={{fontSize:10.5,color:'var(--text-m)',marginBottom:4}}>
                  Branch width · {item.branchW||132}px
                </div>
                <input type="range" min={80} max={320} step={4} value={item.branchW||132}
                  onChange={e=>onUpdate(item.id,{branchW:parseFloat(e.target.value)})}
                  style={{width:'100%',accentColor:'var(--accent)'}}/>
              </div>
            </InspectorSection>
          </>
        )}

        {/* ── Arrow inspector ── */}
        {item.kind === 'arrow' && (
          <>
            <InspectorSection title="Style">
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:5}}>
                {[
                  {type:'straight', icon:'arrowStraight', label:'Straight'},
                  {type:'curved',   icon:'arrowCurved',   label:'Curved'},
                  {type:'double',   icon:'arrowDouble',   label:'Double'},
                  {type:'dashed',   icon:'arrowDashed',   label:'Dashed'},
                  {type:'thick',    icon:'arrowThick',    label:'Thick'},
                ].map(a=>{
                  const active=(item.arrowType||'straight')===a.type;
                  return (
                    <button key={a.type} onClick={()=>onUpdate(item.id,{arrowType:a.type})}
                      style={{display:'flex',alignItems:'center',gap:7,padding:'8px 10px',
                        borderRadius:8,border:'none',cursor:'pointer',fontFamily:'inherit',
                        background:active?'var(--glow)':'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                        outline:active?'1px solid color-mix(in oklch, var(--accent) 40%, transparent)':'1px solid var(--border)',
                        color:active?'var(--accent)':'var(--text-m)',fontSize:12,fontWeight:500}}>
                      <Icon name={a.icon} size={28} style={{flexShrink:0}}/>
                      {a.label}
                    </button>
                  );
                })}
              </div>
            </InspectorSection>
            <InspectorSection title="Appearance">
              <div>
                <div style={{fontSize:10.5,color:'var(--text-m)',marginBottom:4}}>Color</div>
                <ColorSwatches colors={CLUSTER_COLORS} value={item.color||'#b088ff'}
                  onChange={v=>onUpdate(item.id,{color:v})}/>
              </div>
              <div>
                <div style={{fontSize:10.5,color:'var(--text-m)',marginBottom:4}}>
                  Weight · {item.strokeWidth||2}px
                </div>
                <input type="range" min={1} max={12} step={0.5} value={item.strokeWidth||2}
                  onChange={e=>onUpdate(item.id,{strokeWidth:parseFloat(e.target.value)})}
                  style={{width:'100%',accentColor:'var(--accent)'}}/>
              </div>
            </InspectorSection>
            <InspectorSection title="Transform">
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:5}}>
                {[
                  {label:'↺ Rotate 90° CCW', action:()=>{
                    const cx=item.x+item.dx/2, cy=item.y+item.dy/2;
                    const ndx=item.dy, ndy=-item.dx;
                    onUpdate(item.id,{x:cx-ndx/2,y:cy-ndy/2,dx:ndx,dy:ndy});
                  }},
                  {label:'↻ Rotate 90° CW', action:()=>{
                    const cx=item.x+item.dx/2, cy=item.y+item.dy/2;
                    const ndx=-item.dy, ndy=item.dx;
                    onUpdate(item.id,{x:cx-ndx/2,y:cy-ndy/2,dx:ndx,dy:ndy});
                  }},
                  {label:'⇔ Flip H', action:()=>{
                    onUpdate(item.id,{x:item.x+item.dx,dx:-item.dx});
                  }},
                  {label:'⇕ Flip V', action:()=>{
                    onUpdate(item.id,{y:item.y+item.dy,dy:-item.dy});
                  }},
                ].map(({label,action})=>(
                  <button key={label} onClick={action}
                    style={{padding:'7px 6px',fontSize:11,borderRadius:7,cursor:'pointer',fontFamily:'inherit',
                      background:'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                      border:'1px solid var(--border)',color:'var(--text-m)',
                      transition:'background .1s,color .1s'}}
                    onMouseEnter={e=>{e.currentTarget.style.background='var(--glow)';e.currentTarget.style.color='var(--accent)';}}
                    onMouseLeave={e=>{e.currentTarget.style.background='var(--insp-btn-bg,rgba(255,255,255,0.04))';e.currentTarget.style.color='var(--text-m)';}}>
                    {label}
                  </button>
                ))}
              </div>
            </InspectorSection>
            <InspectorSection title="Label" collapsed>
              <input type="text" value={item.label||''} onChange={e=>onUpdate(item.id,{label:e.target.value})}
                placeholder="Optional midpoint label" style={inputBase}
                onFocus={e=>e.target.style.borderColor='var(--accent)'}
                onBlur={e=>e.target.style.borderColor='var(--border)'}/>
            </InspectorSection>
          </>
        )}

        {(item.kind==='cluster'||item.kind==='main'||item.kind==='note'||item.kind==='medium') && (
          <InspectorSection title="Appearance">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Color</div>
              <ColorSwatches
                colors={item.kind==='note'?NOTE_COLORS:item.kind==='medium'?['rgba(255,200,140,0.85)','#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#7fb3ff','#f472b6','#fb7185']:item.kind==='cluster'?CLUSTER_COLORS:MAIN_COLORS}
                value={item.color} onChange={v=>onUpdate(item.id,{color:v})}/>
            </div>
          </InspectorSection>
        )}
        {item.kind !== 'north' && item.kind !== 'text' && item.kind !== 'note' && item.kind !== 'shape' && item.kind !== 'tree' && item.kind !== 'arrow' && (
          <InspectorSection title="Size">
            <div>
              <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>
                Width · {item.w||(item.kind==='cluster'?250:item.kind==='main'?240:190)}px
              </div>
              <input type="range" min={120} max={500} step={10}
                value={item.w||(item.kind==='cluster'?250:item.kind==='main'?240:190)}
                onChange={e=>onUpdate(item.id,{w:parseFloat(e.target.value)})}
                style={{ width:'100%', accentColor:'var(--accent)' }}/>
            </div>
          </InspectorSection>
        )}

        {/* North Star: size, badge, color — separate from the general size section */}
        {item.kind==='north' && (
          <>
            <InspectorSection title="Size">
              <div>
                <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:4 }}>Width · {item.w||300}px</div>
                <input type="range" min={160} max={500} step={10} value={item.w||300}
                  onChange={e=>onUpdate(item.id,{w:parseFloat(e.target.value)})}
                  style={{ width:'100%', accentColor:'var(--accent)' }}/>
              </div>
            </InspectorSection>
            <InspectorSection title="Badge">
                <div>
                  <div style={{ fontSize:10.5, color:'var(--text-m)', marginBottom:6 }}>Third line</div>
                  <div style={{ display:'flex', gap:4, flexWrap:'wrap' }}>
                    {[['none','None'],['date','Date'],['text','Text'],['status','Status %']].map(([val,lbl])=>(
                      <button key={val} onClick={()=>onUpdate(item.id,{badgeType:val})}
                        style={{ padding:'4px 10px', fontSize:11, borderRadius:6, cursor:'pointer', fontFamily:'inherit',
                          background:(item.badgeType||'none')===val?'rgba(180,150,255,0.22)':'var(--insp-btn-bg,rgba(255,255,255,0.04))',
                          border:(item.badgeType||'none')===val?'1px solid rgba(200,170,255,0.4)':'1px solid var(--border)',
                          color:(item.badgeType||'none')===val?'#e0d4ff':'var(--text-m)' }}>{lbl}</button>
                    ))}
                  </div>
                  {item.badgeType==='date' && (
                    <input type="text" value={item.badgeDate||''} placeholder="e.g. Dec 31, 2026"
                      onChange={e=>onUpdate(item.id,{badgeDate:e.target.value})}
                      style={{ ...inputBase, marginTop:8 }}/>
                  )}
                  {item.badgeType==='text' && (
                    <input type="text" value={item.badgeText||''} placeholder="Short status note"
                      onChange={e=>onUpdate(item.id,{badgeText:e.target.value})}
                      style={{ ...inputBase, marginTop:8 }}/>
                  )}
                </div>
              </InspectorSection>
          </>
        )}

        {(item.kind==='main'||item.kind==='cluster') && (
          <InspectorSection key={item.id+'-tasks'} title="Tasks"
            trailing={
              <button onClick={()=>{
                const tid = 'tk'+Math.random().toString(36).slice(2,7);
                const newTasks=[...(item.tasks||[]),{_id:tid, t:'',due:''}];
                onUpdate(item.id,{tasks:newTasks});
                setNewTaskId(tid);
              }}
                style={inspectorGhostBtn}>
                <Icon name="plus" size={10}/> Add
              </button>
            }>
            <div style={{ display:'flex', flexDirection:'column', gap:4 }}>
              {!(item.tasks||[]).length ? (
                <div style={{ fontSize:11.5, color:'var(--text-f)', padding:'6px 2px' }}>No tasks yet. Click Add above.</div>
              ) : (
                (item.tasks||[])
                  .map((t,i)=>({...t,_i:i}))
                  .map((t)=>(
                  <TaskRow key={t._id||t._i} t={t}
                    isNew={t._id === newTaskId}
                    onChange={patch=>{ const n=[...item.tasks]; n[t._i]={...n[t._i],...patch}; onUpdate(item.id,{tasks:n}); }}
                    onDelete={()=>{ onUpdate(item.id,{tasks:item.tasks.filter((_,j)=>j!==t._i)}); }}/>
                ))
              )}
            </div>
          </InspectorSection>
        )}

        {item.kind !== 'note' && (
          <InspectorSection title="Connections"
            trailing={<span style={{ fontSize:10.5, color:'var(--text-f)' }}>{related.length}</span>}>
            {!related.length ? (
              <div style={{ fontSize:11.5, color:'var(--text-f)', padding:'6px 2px' }}>
                Not linked yet. Use ↗ on a card to draw a line.
              </div>
            ) : (
              <div style={{ display:'flex', flexDirection:'column', gap:3 }}>
                {related.map((c,i)=>{
                  const othId = c.from===item.id?c.to:c.from;
                  const oth = itemById[othId];
                  return (
                    <div key={i} style={inspectorRow}>
                      <span style={{ color:'var(--text-f)', fontSize:11, width:10 }}>{c.from===item.id?'→':'←'}</span>
                      <span style={{ width:6,height:6,borderRadius:'50%',background:oth?.color||'#8a97ab',flexShrink:0 }}/>
                      <span onClick={()=>onFocus(othId)}
                        style={{ flex:1,fontSize:12,cursor:'pointer',color:'var(--text)',
                          whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis' }}>
                        {oth?.label||oth?.title||(oth?.kind==='north'?'North Star':'Item')}
                      </span>
                      <button onClick={()=>onDisconnect(c.from,c.to)}
                        style={{ width:20,height:20,border:'none',background:'transparent',
                          color:'var(--text-f)',cursor:'pointer',borderRadius:4,
                          display:'flex',alignItems:'center',justifyContent:'center' }}>
                        <Icon name="x" size={10}/>
                      </button>
                    </div>
                  );
                })}
              </div>
            )}
          </InspectorSection>
        )}

        {(
          <div style={{ marginTop:14, display:'flex', gap:6 }}>
            {item.kind !== 'north' && (
              <button onClick={()=>onDuplicate(item.id)} style={inspectorGhostBtn}>
                <Icon name="copy" size={11}/> Duplicate
              </button>
            )}
            <div style={{ flex:1 }}/>
            <button onClick={()=>setConfirmDel(true)}
              style={{ ...inspectorGhostBtn, color:'#fca5a5', borderColor:'rgba(248,113,113,0.25)' }}>
              <Icon name="trash" size={11}/> Delete
            </button>
          </div>
        )}
        {confirmDel && (
          <ConfirmModal
            title={`Delete ${item.label||item.title||'element'}?`}
            message="This action can't be undone. The element and all its connections will be removed."
            onConfirm={()=>{ setConfirmDel(false); onDelete(item.id); }}
            onCancel={()=>setConfirmDel(false)}/>
        )}
      </div>
    </div>
  );
}

// ─── THEME CONTEXT ──────────────────────────────────────
const ThemeCtx = React.createContext('dark');
const useTheme = () => React.useContext(ThemeCtx);

// ─── WORKSPACE MENU ──────────────────────────────────────
function fmtRelative(iso) {
  if (!iso) return '';
  const diff = Date.now() - new Date(iso).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 2)  return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hrs = Math.floor(mins / 60);
  if (hrs < 24)  return `${hrs}h ago`;
  const days = Math.floor(hrs / 24);
  if (days === 1) return 'yesterday';
  if (days < 7)  return `${days} days ago`;
  return new Date(iso).toLocaleDateString();
}

function WorkspaceMenu({ currentName, onSwitchMap, onCreateMap }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const [open, setOpen] = React.useState(false);
  const [maps, setMaps] = React.useState([]);
  const [search, setSearch] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const handler = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    if (open) {
      document.addEventListener('mousedown', handler);
      document.addEventListener('touchstart', handler);
    }
    return () => {
      document.removeEventListener('mousedown', handler);
      document.removeEventListener('touchstart', handler);
    };
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    const p = window.MindMapPersistence;
    if (!p?.listMaps) return;
    setLoading(true);
    p.listMaps().then(data => { setMaps(data); setLoading(false); }).catch(() => setLoading(false));
  }, [open]);

  const filtered = maps.filter(m => m.name?.toLowerCase().includes(search.toLowerCase()));

  const handleCreate = async () => {
    const p = window.MindMapPersistence;
    if (!p?.createMap) return;
    const result = await p.createMap('New Map');
    if (result && onCreateMap) onCreateMap(result.state);
    setOpen(false);
    setSearch('');
  };

  return (
    <div ref={ref} style={{ position:'relative' }}>
      <button onClick={()=>setOpen(o=>!o)}
        style={{ display:'flex', alignItems:'center', gap:5, padding:'4px 8px',
          background: open ? (isDark?'rgba(255,255,255,0.08)':'rgba(0,0,0,0.07)') : 'transparent',
          border:'1px solid', borderColor: open ? 'var(--border)' : 'transparent',
          borderRadius:7, cursor:'pointer', fontFamily:'inherit',
          color:'var(--text-m)', fontSize:13, transition:'all .12s' }}>
        <svg width={14} height={14} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
          <path d="M2 8h12M2 4h12M2 12h6"/>
        </svg>
        <span style={{ color:'var(--text-m)' }}>Mind map</span>
        <svg width={10} height={10} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ opacity:0.5 }}>
          <path d="M4 6l4 4 4-4"/>
        </svg>
      </button>

      {open && (
        <div style={{ position:'absolute', top:'calc(100% + 8px)', left:0, width:280, zIndex:9900,
          background: isDark?'#0d1119':'#ffffff',
          border:'1px solid var(--border)', borderRadius:12,
          boxShadow: isDark?'0 20px 60px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06)':'0 8px 40px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.06)',
          overflow:'hidden' }}>
          {/* Search */}
          <div style={{ padding:'10px 12px 8px', borderBottom:'1px solid var(--border)' }}>
            <div style={{ display:'flex', alignItems:'center', gap:7,
              background: isDark?'rgba(255,255,255,0.06)':'rgba(0,0,0,0.05)',
              border:'1px solid var(--border)', borderRadius:7, padding:'5px 9px' }}>
              <svg width={12} height={12} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" style={{ flexShrink:0, opacity:0.45 }}>
                <circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5l3 3"/>
              </svg>
              <input
                autoFocus
                value={search}
                onChange={e => setSearch(e.target.value)}
                placeholder="Søk kart…"
                onClick={e => e.stopPropagation()}
                style={{ flex:1, background:'transparent', border:'none', outline:'none',
                  fontSize:12, fontFamily:'inherit', color:'var(--text)',
                  '::placeholder': { color:'var(--text-f)' } }}/>
            </div>
          </div>
          {/* Map list */}
          <div style={{ maxHeight:280, overflowY:'auto' }}>
            {loading && (
              <div style={{ padding:'16px 14px', textAlign:'center', color:'var(--text-f)', fontSize:12 }}>
                Laster…
              </div>
            )}
            {!loading && filtered.length === 0 && (
              <div style={{ padding:'16px 14px', textAlign:'center', color:'var(--text-f)', fontSize:12 }}>
                Ingen kart funnet
              </div>
            )}
            {!loading && filtered.map((p, i) => {
              const isCurrent = p.name === currentName;
              return (
                <div key={p.id}
                  onClick={async () => {
                    const pm = window.MindMapPersistence;
                    if (!pm?.switchMap) return;
                    const state = await pm.switchMap(p.id);
                    if (state && onSwitchMap) onSwitchMap(state);
                    setOpen(false);
                    setSearch('');
                  }}
                  style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 14px',
                    cursor:'pointer', transition:'background .1s',
                    background: isCurrent ? (isDark?'rgba(180,150,255,0.1)':'rgba(139,92,246,0.07)') : 'transparent' }}
                  onMouseEnter={e => e.currentTarget.style.background = isDark?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.04)'}
                  onMouseLeave={e => e.currentTarget.style.background = isCurrent?(isDark?'rgba(180,150,255,0.1)':'rgba(139,92,246,0.07)'):'transparent'}>
                  <div style={{ width:28, height:28, borderRadius:7, flexShrink:0,
                    background:`hsl(${i*53+200},${isDark?40:48}%,${isDark?32:72}%)`,
                    display:'flex', alignItems:'center', justifyContent:'center',
                    fontSize:11, fontWeight:700, color:'rgba(255,255,255,0.92)' }}>
                    {(p.name||'?')[0].toUpperCase()}
                  </div>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:12.5, fontWeight:500, color:'var(--text)',
                      whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{p.name||'Untitled'}</div>
                    <div style={{ fontSize:10.5, color:'var(--text-f)', marginTop:1 }}>
                      {fmtRelative(p.updated_at)}{p.is_default?' · standard':''}
                    </div>
                  </div>
                  {isCurrent && (
                    <svg width={12} height={12} viewBox="0 0 16 16" fill="none" stroke="var(--accent)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                      <path d="M2 8l4 5L14 3"/>
                    </svg>
                  )}
                </div>
              );
            })}
          </div>
          {/* Footer */}
          <div style={{ borderTop:'1px solid var(--border)', padding:'8px 14px' }}>
            <div onClick={handleCreate}
              style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 4px',
                color:'var(--text-m)', fontSize:12, cursor:'pointer', borderRadius:6,
                transition:'color .12s' }}
              onMouseEnter={e => e.currentTarget.style.color='var(--accent)'}
              onMouseLeave={e => e.currentTarget.style.color='var(--text-m)'}>
              <svg width={13} height={13} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M8 3v10M3 8h10"/></svg>
              Nytt kart
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Theme toggle
function ThemeToggle({ theme, setTheme }) {
  const isDark = theme === 'dark';
  return (
    <button onClick={()=>setTheme(isDark?'light':'dark')}
      title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
      style={{ display:'flex', alignItems:'center', gap:6, padding:'5px 10px',
        borderRadius:999, border:'1px solid var(--border)',
        background: isDark?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.05)',
        color:'var(--text-m)', cursor:'pointer', fontSize:11.5, fontWeight:500,
        fontFamily:'inherit', transition:'all .2s' }}>
      {isDark ? (
        <>
          <svg width={13} height={13} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><circle cx="8" cy="8" r="3.5"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3.1 3.1l1 1M11.9 11.9l1 1M11.9 3.1l-1 1M3.1 11.9l1-1"/></svg>
          Light
        </>
      ) : (
        <>
          <svg width={13} height={13} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M13 9.5A6 6 0 017 3.5a6 6 0 100 9 6 6 0 006-3z"/></svg>
          Dark
        </>
      )}
    </button>
  );
}


function useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart }) {
  const ref = React.useRef(null);
  const state = React.useRef(null);
  const onMouseDown = (e) => {
    if (e.button !== 0) return;
    if (e.target.closest('[data-element-action]')) return;
    if (e.target.closest('[contenteditable=true]')) return;
    if (e.target.closest('[data-inline-edit]')) return;
    e.stopPropagation();
    const isCtrl = (e.ctrlKey || e.metaKey) && !!onCtrlDragStart;
    if (isCtrl) e.preventDefault();
    document.body.style.cursor = isCtrl ? 'copy' : 'grabbing';
    const sw = toWorld(e.clientX, e.clientY);
    state.current = { sx:e.clientX, sy:e.clientY, ox:item.x, oy:item.y, swx:sw.x, swy:sw.y, moved:false, isCtrl, copyMove:null };
    const onMv = (ev) => {
      if (!state.current) return;
      const w = toWorld(ev.clientX, ev.clientY);
      if (!state.current.moved && Math.hypot(ev.clientX-state.current.sx, ev.clientY-state.current.sy) > 4)
        state.current.moved = true;
      if (state.current.moved) {
        const newX = state.current.ox + w.x - state.current.swx;
        const newY = state.current.oy + w.y - state.current.swy;
        if (state.current.isCtrl) {
          if (!state.current.copyMove) {
            state.current.copyMove = onCtrlDragStart(state.current.ox, state.current.oy) || null;
          }
          if (state.current.copyMove) state.current.copyMove(newX, newY);
        } else {
          onMove(newX, newY);
        }
      }
    };
    const onUp = (ev) => {
      document.body.style.cursor='';
      if (!state.current) return;
      const moved = state.current.moved;
      const wasCtrl = state.current.isCtrl;
      state.current = null;
      window.removeEventListener('mousemove', onMv); window.removeEventListener('mouseup', onUp);
      if (!moved) {
        if (wasCtrl && onDuplicate) { onDuplicate(); }
        else if (linking && isLinkTarget) onCompleteLink();
        else if (!linking) onOpen();
      }
    };
    window.addEventListener('mousemove', onMv); window.addEventListener('mouseup', onUp);
  };
  return { ref, onMouseDown };
}

const linkHL = (isTarget, linking, isSource) => {
  if (isSource) return { boxShadow:'0 0 0 2px rgba(200,170,255,0.8), 0 0 24px rgba(180,150,255,0.4)' };
  if (!linking) return {};
  if (isTarget) return { boxShadow:'0 0 0 2px rgba(200,170,255,0.9)', transform:'scale(1.03)' };
  return { opacity:0.55 };
};

function ElementActions({ detail, onToggle, onStartLink, linking, isLinkSource }) {
  return (
    <div data-element-action style={{ display:'flex', gap:2, alignItems:'center' }}>
      {onToggle && (
        <button onClick={e=>{e.stopPropagation();onToggle();}}
          style={{ width:20,height:20,border:'none',background:'transparent',
            color:'var(--text-m)',cursor:'pointer',borderRadius:4,
            display:'flex',alignItems:'center',justifyContent:'center' }}>
          <Icon name={detail?'collapse':'expand'} size={11}/>
        </button>
      )}
      <button onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
        style={{ width:20,height:20,border:'none',
          background:isLinkSource?'rgba(200,170,255,0.25)':'transparent',
          color:isLinkSource?'var(--accent)':'var(--text-m)',
          cursor:'pointer',borderRadius:4,
          display:'flex',alignItems:'center',justifyContent:'center' }}>
        <Icon name="arrow" size={11}/>
      </button>
    </div>
  );
}

function ClusterNode({ item:c, detail, onToggle, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme==='dark';
  const drag = useDraggable({ item:c, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [editLabel, setEditLabel] = React.useState(false);
  const [editIntent, setEditIntent] = React.useState(false);
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={onHoverEnter} onMouseLeave={onHoverLeave}
      style={{ position:'absolute', left:c.x, top:c.y, width:c.w||250,
        padding:'12px 16px', borderRadius:18,
        background: isDark
          ? `radial-gradient(circle at 30% 20%, ${c.color}33, transparent 70%), #141922`
          : `radial-gradient(circle at 30% 20%, ${c.color}22, transparent 70%), #ffffff`,
        border:`1px solid ${c.color}${isDark?'44':'33'}`,
        boxShadow: isDark
          ? `0 20px 50px ${c.color}1f, 0 1px 0 rgba(255,255,255,0.08) inset`
          : `0 4px 20px ${c.color}18, 0 1px 0 rgba(255,255,255,0.8) inset`,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'box-shadow .15s, transform .15s, opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        <div style={{ width:8,height:8,borderRadius:'50%',background:c.color,boxShadow:`0 0 12px ${c.color}`,flexShrink:0 }}/>
        {editLabel ? (
          <InlineInput value={c.label} placeholder="Cluster name"
            style={{ fontFamily:'Instrument Serif, serif', fontSize:20, color:'var(--text)', flex:1 }}
            onSave={v=>{ if(v!==null) onUpdate({label:v}); setEditLabel(false); }}/>
        ) : (
          <div onDoubleClick={e=>{e.stopPropagation();setEditLabel(true);}}
            style={{ fontFamily:'Instrument Serif, serif', fontSize:20, color:'var(--text)', flex:1, cursor:'text' }}>{c.label}</div>
        )}
        <span style={{ fontSize:10.5, color:'var(--text-m)' }}>{(c.tasks||[]).length}</span>
        <ElementActions detail={detail} onToggle={onToggle} onStartLink={onStartLink} linking={linking} isLinkSource={isLinkSource}/>
      </div>
      {detail && (
        <>
          {editIntent ? (
            <InlineInput value={c.intent} placeholder="Intent — why this area matters" multiline
              style={{ fontSize:11.5, color:'var(--text-m)', fontStyle:'italic', lineHeight:1.4, marginTop:6 }}
              onSave={v=>{ if(v!==null) onUpdate({intent:v}); setEditIntent(false); }}/>
          ) : c.intent ? (
            <div onDoubleClick={e=>{e.stopPropagation();setEditIntent(true);}}
              style={{ fontSize:11.5, color:'var(--text-m)', marginTop:6, fontStyle:'italic', lineHeight:1.4, cursor:'text' }}>
              {c.intent}
            </div>
          ) : (
            <div onDoubleClick={e=>{e.stopPropagation();setEditIntent(true);}}
              style={{ height:6, cursor:'text' }}/>
          )}
          <div style={{ marginTop:10, display:'flex', flexDirection:'column', gap:2 }}>
            {(c.tasks||[]).slice(0,4).map((t,i)=>(
              <div key={i} style={{ display:'flex',alignItems:'center',gap:8,padding:'5px 0',fontSize:12 }}>
                <div style={{ width:13,height:13,borderRadius:4,border:'1.5px solid var(--border)',flexShrink:0 }}/>
                <span style={{ flex:1 }}>{t.t}</span>
                <span style={{ fontSize:10, color:t.overdue?'#f87171':t.due==='Today'?'#60a5fa':'var(--text-f)' }}>{t.due}</span>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

function NoteEl({ item, detail, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, isLinkSource, onHoverEnter, onHoverLeave, onStartLink, onUpdate, onDuplicate, onCtrlDragStart }) {
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [editTitle, setEditTitle] = React.useState(false);
  const [editBody, setEditBody] = React.useState(false);
  const [hover, setHover] = React.useState(false);

  const onResizeStart = (e, dir) => {
    e.stopPropagation(); e.preventDefault();
    const sw = toWorld(e.clientX, e.clientY);
    const origW = item.w||190, origH = item.noteH||null;
    const estimatedH = origH || 120;
    const onMv = (ev) => {
      const w = toWorld(ev.clientX, ev.clientY);
      const patch = {};
      if (dir==='e'||dir==='se') patch.w = Math.max(120, origW + (w.x - sw.x));
      if (dir==='s'||dir==='se') patch.noteH = Math.max(70, estimatedH + (w.y - sw.y));
      onUpdate(patch);
    };
    const onUp = () => { window.removeEventListener('mousemove',onMv); window.removeEventListener('mouseup',onUp); };
    window.addEventListener('mousemove',onMv); window.addEventListener('mouseup',onUp);
  };

  const handleSz = { position:'absolute', width:10, height:10, borderRadius:3,
    background:'rgba(60,40,10,0.3)', border:'1.5px solid rgba(60,40,10,0.5)',
    zIndex:10, opacity: hover ? 1 : 0, transition:'opacity .12s' };

  return (
    <div data-no-pan className="strat-sticky" ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}}
      onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:item.x, top:item.y, width:item.w||190,
        minHeight: item.noteH || undefined,
        height: item.noteH ? item.noteH : undefined,
        overflow: item.noteH ? 'hidden' : undefined,
        background:item.color||'#fde68a', transform:'rotate(-1.5deg)',
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        color:'#3d2e10', paddingRight:26, ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <button data-element-action onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
        style={{ position:'absolute',top:6,right:6,width:20,height:20,
          border:'none',background:'transparent',color:'rgba(60,40,10,0.5)',
          cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',borderRadius:4 }}>
        <Icon name="arrow" size={11}/>
      </button>

      {/* Resize handles */}
      <div data-element-action onMouseDown={e=>onResizeStart(e,'e')}
        style={{...handleSz, right:-5, top:'50%', marginTop:-5, cursor:'e-resize'}}/>
      <div data-element-action onMouseDown={e=>onResizeStart(e,'s')}
        style={{...handleSz, bottom:-5, left:'50%', marginLeft:-5, cursor:'s-resize'}}/>
      <div data-element-action onMouseDown={e=>onResizeStart(e,'se')}
        style={{...handleSz, bottom:-5, right:-5, cursor:'se-resize', background:'rgba(60,40,10,0.5)'}}/>

      {editTitle ? (
        <InlineInput value={item.title} placeholder="Title"
          style={{ fontWeight:700, fontSize:13, color:'#2a1f08',
            background:'rgba(0,0,0,0.06)', border:'1px solid rgba(80,55,15,0.35)',
            marginBottom: item.body ? 6 : 0 }}
          onSave={v=>{ if(v!==null) onUpdate({title:v}); setEditTitle(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditTitle(true);}}
          style={{ fontWeight:700, fontSize:13, color:'#2a1f08', cursor:'text',
            paddingBottom: item.body ? 6 : 0,
            borderBottom: item.body ? '1px solid rgba(80,55,15,0.3)' : 'none',
            minHeight:18, opacity: item.title?1:0.4 }}>
          {item.title||'Title'}
        </div>
      )}
      {editBody ? (
        <InlineInput value={item.body} placeholder="Write a note…" multiline
          style={{ fontSize:12, lineHeight:1.45, color:'rgba(58,42,10,0.85)',
            marginTop:6, background:'rgba(0,0,0,0.05)', border:'1px solid rgba(80,55,15,0.3)' }}
          onSave={v=>{ if(v!==null) onUpdate({body:v}); setEditBody(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditBody(true);}}
          style={{ fontSize:12, lineHeight:1.45, color:'rgba(58,42,10,0.85)', marginTop:6,
            minHeight:16, whiteSpace:'pre-wrap', wordBreak:'break-word', cursor:'text',
            opacity: item.body?1:0.35, fontStyle: item.body?'normal':'italic' }}>
          {item.body||'Write a note…'}
        </div>
      )}
    </div>
  );
}

function MainBox({ item, detail, onToggle, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme==='dark';
  const c = item.color||'#7fb3ff';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [editLabel, setEditLabel] = React.useState(false);
  const [editBody, setEditBody] = React.useState(false);
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={onHoverEnter} onMouseLeave={onHoverLeave}
      style={{ position:'absolute', left:item.x, top:item.y, width:item.w||240,
        padding:'14px 16px', borderRadius:14,
        background: isDark ? `linear-gradient(180deg, ${c}26, #121820), #121820` : `linear-gradient(180deg, ${c}18, #fff), #fff`,
        border:`1px solid ${c}${isDark?'55':'33'}`,
        boxShadow: isDark?'0 10px 30px rgba(0,0,0,0.28)':'0 4px 20px rgba(0,0,0,0.08)',
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'box-shadow .15s, transform .15s, opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        <Icon name="target" size={14} style={{ color:c, flexShrink:0 }}/>
        {editLabel ? (
          <InlineInput value={item.label} placeholder="Objective"
            style={{ fontSize:14, fontWeight:600, color:'var(--text)', flex:1 }}
            onSave={v=>{ if(v!==null) onUpdate({label:v}); setEditLabel(false); }}/>
        ) : (
          <div onDoubleClick={e=>{e.stopPropagation();setEditLabel(true);}}
            style={{ fontSize:14, fontWeight:600, flex:1, color:'var(--text)', cursor:'text' }}>{item.label}</div>
        )}
        <ElementActions detail={detail} onToggle={onToggle} onStartLink={onStartLink} linking={linking} isLinkSource={isLinkSource}/>
      </div>
      {detail ? (
        <>
          {editBody ? (
            <InlineInput value={item.body} placeholder="Describe this objective…" multiline
              style={{ fontSize:12, color:'var(--text-m)', lineHeight:1.5, marginTop:6 }}
              onSave={v=>{ if(v!==null) onUpdate({body:v}); setEditBody(false); }}/>
          ) : item.body ? (
            <div onDoubleClick={e=>{e.stopPropagation();setEditBody(true);}}
              style={{ fontSize:12, color:'var(--text-m)', marginTop:6, lineHeight:1.5, cursor:'text' }}>
              {item.body}
            </div>
          ) : (
            <div onDoubleClick={e=>{e.stopPropagation();setEditBody(true);}}
              style={{ height:6, cursor:'text' }}/>
          )}
          {(item.tasks||[]).map((t,i)=>(
            <div key={i} style={{ display:'flex',alignItems:'center',gap:8,padding:'4px 0',fontSize:11.5 }}>
              <div style={{ width:13,height:13,borderRadius:4,border:'1.5px solid var(--border)',flexShrink:0 }}/>
              <span style={{ flex:1 }}>{t.t}</span>
              <span style={{ fontSize:10,color:'var(--text-m)' }}>{t.due}</span>
            </div>
          ))}
        </>
      ) : (
        <div style={{ fontSize:11, color:'var(--text-m)', marginTop:4 }}>{(item.tasks||[]).length} tasks</div>
      )}
    </div>
  );
}

function MediumBox({ item, detail, onToggle, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const accentColor = item.color || 'rgba(255,200,140,0.85)';
  const [editLabel, setEditLabel] = React.useState(false);
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={onHoverEnter} onMouseLeave={onHoverLeave}
      style={{ position:'absolute', left:item.x, top:item.y, width:item.w||210,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'transform .15s, opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <div style={{ display:'flex', alignItems:'flex-start', gap:0 }}>
        <div style={{ flexShrink:0, width:14, marginTop:3, marginRight:8, display:'flex', flexDirection:'column', alignItems:'center' }}>
          <div style={{ width:11, height:11, background:accentColor,
            transform:'rotate(45deg)', borderRadius:2,
            boxShadow:`0 0 8px ${accentColor}88` }}/>
          {item.date && <div style={{ width:1, flex:1, background:`${accentColor}44`, marginTop:3, minHeight:8 }}/>}
        </div>
        <div style={{ flex:1, padding:'9px 12px 9px 10px', borderRadius:10,
          background: isDark
            ? 'linear-gradient(180deg, rgba(255,255,255,0.055), rgba(255,255,255,0.025)), #14191f'
            : 'linear-gradient(180deg, rgba(255,255,255,0.9), rgba(255,255,255,0.7)), #fafaf8',
          border: `1px solid ${isDark?'rgba(255,255,255,0.1)':'rgba(0,0,0,0.08)'}`,
          borderLeft: `2px solid ${accentColor}`,
          boxShadow: isDark ? 'none' : '0 2px 10px rgba(0,0,0,0.06)' }}>
          <div style={{ display:'flex', alignItems:'center', gap:6 }}>
            {editLabel ? (
              <InlineInput value={item.label} placeholder="Milestone"
                style={{ fontSize:12.5, fontWeight:500, color:'var(--text)', flex:1 }}
                onSave={v=>{ if(v!==null) onUpdate({label:v}); setEditLabel(false); }}/>
            ) : (
              <div onDoubleClick={e=>{e.stopPropagation();setEditLabel(true);}}
                style={{ fontSize:12.5, fontWeight:500, flex:1, color:'var(--text)', cursor:'text' }}>
                {item.label||'Milestone'}
              </div>
            )}
            <ElementActions detail={detail} onToggle={onToggle} onStartLink={onStartLink} linking={linking} isLinkSource={isLinkSource}/>
          </div>
          {detail && (
            <div style={{ fontSize:11.5, color:'var(--text-m)', marginTop:4, lineHeight:1.45 }}>
              {item.body||'Describe this milestone'}
            </div>
          )}
          {item.date && (
            <div style={{ display:'flex', alignItems:'center', gap:5, marginTop:5 }}>
              <Icon name="calendar" size={10} style={{ color:accentColor, opacity:0.8 }}/>
              <span style={{ fontSize:10.5, color:'var(--text-m)', fontVariantNumeric:'tabular-nums' }}>{item.date}</span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function DeadlineBox({ item, detail, onToggle, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [editLabel, setEditLabel] = React.useState(false);
  const headerColor = isDark ? '#fca5a5' : '#b91c1c';
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={onHoverEnter} onMouseLeave={onHoverLeave}
      style={{ position:'absolute', left:item.x, top:item.y, width:item.w||170,
        padding:'10px 12px', borderRadius:11,
        background: isDark
          ? 'linear-gradient(135deg, rgba(248,113,113,0.15), #141922), #141922'
          : 'linear-gradient(135deg, rgba(248,113,113,0.10), #ffffff), #ffffff',
        border: isDark ? '1px solid rgba(248,113,113,0.3)' : '1px solid rgba(185,28,28,0.25)',
        boxShadow: isDark ? 'none' : '0 2px 10px rgba(248,113,113,0.08)',
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'box-shadow .15s, transform .15s, opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <div style={{ display:'flex', alignItems:'center', gap:6 }}>
        <Icon name="calendar" size={12} style={{ color:headerColor, flexShrink:0 }}/>
        <div style={{ fontSize:10, fontWeight:600, color:headerColor, letterSpacing:0.8, textTransform:'uppercase', flex:1 }}>{item.date}</div>
        <ElementActions detail={detail} onToggle={onToggle} onStartLink={onStartLink} linking={linking} isLinkSource={isLinkSource}/>
      </div>
      {editLabel ? (
        <InlineInput value={item.label} placeholder="Deadline name"
          style={{ fontSize:12.5, fontWeight:500, color:'var(--text)', marginTop:4 }}
          onSave={v=>{ if(v!==null) onUpdate({label:v}); setEditLabel(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditLabel(true);}}
          style={{ fontSize:12.5, fontWeight:500, marginTop:4, cursor:'text', color:'var(--text)' }}>{item.label}</div>
      )}
      {detail && <div style={{ fontSize:11.5, color:'var(--text-m)', marginTop:4, lineHeight:1.45 }}>{item.body||'Add details…'}</div>}
    </div>
  );
}

function TextEl({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, isLinkSource, onHoverEnter, onHoverLeave, onStartLink, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [hover, setHover] = React.useState(false);
  const [editing, setEditing] = React.useState(false);
  const DEFAULT_DARK = 'rgba(220,225,235,0.85)';
  const textColor = (item.color && item.color !== DEFAULT_DARK)
    ? item.color
    : (isDark ? DEFAULT_DARK : 'rgba(15,20,35,0.88)');
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}}
      onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:item.x, top:item.y,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        userSelect:'none', padding:'8px 12px', margin:'-8px -12px',
        ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <button data-element-action onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
        style={{ position:'absolute', top:-10, right:-10, width:18, height:18,
          border:'none', background:isLinkSource?'rgba(200,170,255,0.3)':'rgba(30,35,48,0.85)',
          color:isLinkSource?'#e4d9ff':'rgba(200,210,225,0.6)',
          borderRadius:4, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center',
          opacity: hover||isLinkSource ? 1 : 0, transition:'opacity .12s' }}>
        <Icon name="arrow" size={10}/>
      </button>
      {editing ? (
        <InlineInput value={item.text} placeholder="Label" multiline
          style={{ fontSize: item.fontSize||18, fontWeight: item.bold?600:400,
            color: textColor, lineHeight:1.3,
            fontFamily: item.serif?'Instrument Serif, serif':'inherit',
            fontStyle: item.italic?'italic':'normal',
            background:'rgba(255,255,255,0.07)', minWidth:60 }}
          onSave={v=>{ if(v!==null) onUpdate({text:v}); setEditing(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditing(true);}}
          style={{ fontSize: item.fontSize||18, fontWeight: item.bold ? 600 : 400,
            color: textColor, lineHeight:1.3,
            fontFamily: item.serif ? 'Instrument Serif, serif' : 'inherit',
            fontStyle: item.italic ? 'italic' : 'normal',
            textShadow: isDark ? '0 1px 8px rgba(0,0,0,0.5)' : 'none',
            outline: hover ? '1px dashed rgba(180,150,255,0.3)' : 'none',
            padding:'2px 4px', borderRadius:4, cursor:'text', minWidth:40,
            whiteSpace:'pre-wrap', wordBreak:'break-word' }}>
          {item.text || 'Label'}
        </div>
      )}
    </div>
  );
}

// ─── HYPERLINK ELEMENT ──────────────────────────────────
function HyperlinkBox({ item, detail, onToggle, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [hover, setHover] = React.useState(false);
  const accentColor = item.color || '#7fb3ff';
  const hasUrl = item.url && item.url.trim().length > 0;

  const handleNameClick = (e) => {
    e.stopPropagation();
    if (hasUrl) {
      let url = item.url.trim();
      if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
      window.open(url, '_blank', 'noopener,noreferrer');
    }
  };

  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}}
      onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:item.x, top:item.y, width:item.w||220,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'transform .15s, opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>
      <div style={{
        borderRadius:11,
        background: isDark
          ? `linear-gradient(160deg, ${accentColor}12 0%, rgba(255,255,255,0.025) 100%), #13181f`
          : `linear-gradient(160deg, ${accentColor}14 0%, rgba(255,255,255,0.8) 100%), #fff`,
        border: `1px solid ${accentColor}44`,
        boxShadow: isDark
          ? `0 0 0 1px ${accentColor}22, 0 4px 18px rgba(0,0,0,0.22)`
          : `0 0 0 1px ${accentColor}22, 0 2px 12px rgba(0,0,0,0.07)`,
        overflow:'hidden'
      }}>
        {/* Top accent bar */}
        <div style={{ height:2, background:`linear-gradient(90deg, ${accentColor}, ${accentColor}44)` }}/>

        <div style={{ padding:'10px 12px 10px' }}>
          {/* Header row */}
          <div style={{ display:'flex', alignItems:'center', gap:7 }}>
            <div style={{ width:22, height:22, borderRadius:6, flexShrink:0,
              background:`${accentColor}22`, display:'flex', alignItems:'center', justifyContent:'center' }}>
              <Icon name="link" size={11} style={{ color:accentColor }}/>
            </div>

            {/* Name — clickable if URL exists */}
            <div
              title={hasUrl ? item.url : undefined}
              onClick={handleNameClick}
              style={{
                flex:1, fontSize:13, fontWeight:600, lineHeight:1.25,
                color: hasUrl ? accentColor : 'var(--text)',
                cursor: hasUrl ? 'pointer' : 'default',
                textDecoration: hasUrl && hover ? 'underline' : 'none',
                textDecorationColor: `${accentColor}88`,
                transition:'text-decoration .1s',
                whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
              }}>
              {item.label || 'Link name'}
            </div>

            {hasUrl && (
              <Icon name="externalLink" size={10}
                style={{ color:accentColor, opacity: hover ? 0.8 : 0.4,
                  flexShrink:0, transition:'opacity .15s', pointerEvents:'none' }}/>
            )}

            <ElementActions detail={detail} onToggle={onToggle} onStartLink={onStartLink} linking={linking} isLinkSource={isLinkSource}/>
          </div>

          {/* URL preview strip — only in details mode */}
          {detail && hasUrl && (
            <div style={{ marginTop:7, padding:'4px 8px', borderRadius:6,
              background: isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)',
              border: `1px solid ${isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'}`,
              fontSize:10.5, color:'var(--text-m)',
              whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
              fontFamily:'ui-monospace, monospace', letterSpacing:'-0.01em' }}>
              {item.url}
            </div>
          )}

          {/* Notes — shown in details mode */}
          {detail && item.notes && (
            <div style={{ marginTop:8, fontSize:11.5, color:'var(--text-m)', lineHeight:1.5,
              borderTop:`1px solid ${isDark?'rgba(255,255,255,0.06)':'rgba(0,0,0,0.06)'}`, paddingTop:7 }}>
              {item.notes}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── SHAPE ELEMENT ──────────────────────────────────────
const SHAPE_COLORS = ['none','#b088ff','#7fe0a8','#fbbf24','#ff9fbf','#7fb3ff','#f472b6','#fb7185','#ffffff','rgba(220,225,235,0.25)'];

function renderShapeSVG(shapeType, w, h, fill, stroke, sw, cornerRadius) {
  const f = fill === 'none' ? 'none' : fill;
  const pad = sw / 2;
  if (shapeType === 'circle') {
    return <ellipse cx={w/2} cy={h/2} rx={Math.max(1,w/2-pad)} ry={Math.max(1,h/2-pad)} fill={f} stroke={stroke} strokeWidth={sw}/>;
  }
  if (shapeType === 'rect' || shapeType === 'square') {
    return <rect x={pad} y={pad} width={Math.max(1,w-sw)} height={Math.max(1,h-sw)} rx={cornerRadius||0} fill={f} stroke={stroke} strokeWidth={sw}/>;
  }
  if (shapeType === 'diamond') {
    const pts = `${w/2},${pad} ${w-pad},${h/2} ${w/2},${h-pad} ${pad},${h/2}`;
    return <polygon points={pts} fill={f} stroke={stroke} strokeWidth={sw}/>;
  }
  if (shapeType === 'line') {
    return <line x1={pad} y1={h/2} x2={w-pad} y2={h/2} stroke={stroke} strokeWidth={sw} strokeLinecap="round"/>;
  }
  return null;
}

function ShapeEl({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, isLinkSource, onHoverEnter, onHoverLeave, onStartLink, onUpdate, onDuplicate, onCtrlDragStart }) {
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [hover, setHover] = React.useState(false);
  const [editText, setEditText] = React.useState(false);
  const w = item.w || 120, h = item.h || 80;
  const fill = item.fill ?? 'none';
  const stroke = item.stroke || '#b088ff';
  const sw = item.strokeWidth || 2;
  const opacity = (item.opacity ?? 100) / 100;
  const isLine = item.shapeType === 'line';

  const onResizeStart = (e, corner) => {
    e.stopPropagation(); e.preventDefault();
    const origX=item.x, origY=item.y, origW=item.w||120, origH=item.h||80;
    const start=toWorld(e.clientX,e.clientY);
    const cursors={nw:'nw-resize',ne:'ne-resize',sw:'sw-resize',se:'se-resize',e:'e-resize',w:'w-resize'};
    document.body.style.cursor=cursors[corner]||'default';
    const onMv=(ev)=>{
      const cur=toWorld(ev.clientX,ev.clientY);
      const dx=cur.x-start.x, dy=cur.y-start.y;
      let nx=origX,ny=origY,nw=origW,nh=origH;
      if(corner.includes('e')) nw=Math.max(20,origW+dx);
      if(corner.includes('s')) nh=Math.max(8,origH+dy);
      if(corner.includes('w')){nx=origX+dx;nw=Math.max(20,origW-dx);}
      if(corner.includes('n')){ny=origY+dy;nh=Math.max(8,origH-dy);}
      onUpdate({x:nx,y:ny,w:nw,h:nh});
    };
    const onUp=()=>{document.body.style.cursor='';window.removeEventListener('mousemove',onMv);window.removeEventListener('mouseup',onUp);};
    window.addEventListener('mousemove',onMv);
    window.addEventListener('mouseup',onUp);
  };

  const handleSz = { position:'absolute', width:8, height:8, borderRadius:2,
    background:'#b088ff', border:'1px solid rgba(255,255,255,0.85)', zIndex:10,
    opacity: hover ? 1 : 0, transition:'opacity .12s' };

  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}}
      onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:item.x, top:item.y, width:w, height:Math.max(h,4),
        opacity, cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'opacity .15s', ...linkHL(isLinkTarget,linking,isLinkSource) }}>

      {/* Resize handles */}
      {!linking && !isLine && <>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'nw')} style={{...handleSz, left:-4, top:-4, cursor:'nw-resize'}}/>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'ne')} style={{...handleSz, right:-4, top:-4, cursor:'ne-resize'}}/>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'sw')} style={{...handleSz, left:-4, bottom:-4, cursor:'sw-resize'}}/>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'se')} style={{...handleSz, right:-4, bottom:-4, cursor:'se-resize'}}/>
      </>}
      {!linking && isLine && <>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'w')} style={{...handleSz, left:-4, top:'50%', marginTop:-4, cursor:'w-resize'}}/>
        <div data-element-action onMouseDown={e=>onResizeStart(e,'e')} style={{...handleSz, right:-4, top:'50%', marginTop:-4, cursor:'e-resize'}}/>
      </>}

      {/* Link button — right center */}
      <button data-element-action onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
        style={{ position:'absolute', right:-12, top:'50%', transform:'translateY(-50%)',
          width:20, height:20, zIndex:11,
          border:'none', background:isLinkSource?'rgba(200,170,255,0.3)':'rgba(30,35,48,0.85)',
          color:isLinkSource?'#e4d9ff':'rgba(200,210,225,0.6)',
          borderRadius:4, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center',
          opacity: hover||isLinkSource ? 1 : 0, transition:'opacity .12s' }}>
        <Icon name="arrow" size={10}/>
      </button>

      {/* Hover outline */}
      {hover && !linking && (
        <div style={{ position:'absolute', inset:-3, borderRadius:4,
          border:'1px dashed rgba(180,150,255,0.5)', pointerEvents:'none', zIndex:1 }}/>
      )}

      {/* SVG shape */}
      <svg width={w} height={Math.max(h,4)} style={{ display:'block', overflow:'visible', position:'absolute', inset:0 }}>
        {renderShapeSVG(item.shapeType, w, Math.max(h,4), fill, stroke, sw, item.cornerRadius||0)}
      </svg>

      {/* Center text */}
      {!isLine && (
        <div style={{ position:'absolute', inset:0, display:'flex', alignItems:'center',
          justifyContent:'center', zIndex:2, padding:8 }}>
          {editText ? (
            <InlineInput value={item.text||''} placeholder="Text…" multiline
              style={{ fontSize:item.fontSize||14, color:item.textColor||'rgba(220,225,235,0.9)',
                fontWeight:item.bold?600:400, textAlign:'center',
                background:'rgba(0,0,0,0.25)', border:'1px solid rgba(180,150,255,0.4)', width:'100%' }}
              onSave={v=>{ if(v!==null) onUpdate({text:v}); setEditText(false); }}/>
          ) : (
            <div onDoubleClick={e=>{e.stopPropagation();setEditText(true);}}
              style={{ fontSize:item.fontSize||14, color:item.textColor||'rgba(220,225,235,0.9)',
                fontWeight:item.bold?600:400, textAlign:'center', cursor:'text',
                lineHeight:1.3, wordBreak:'break-word', whiteSpace:'pre-wrap',
                opacity: item.text ? 1 : (hover ? 0.3 : 0) }}>
              {item.text || 'Double-click to add text'}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// Preview during drawing
function ShapePreview({ preview }) {
  const { shapeType, x, y, w, h } = preview;
  if (!w || !h) return null;
  const sw = 2;
  return (
    <div style={{ position:'absolute', left:x, top:y, width:Math.max(w,4), height:Math.max(h,4),
      pointerEvents:'none', zIndex:100 }}>
      <svg width={Math.max(w,4)} height={Math.max(h,4)} style={{ display:'block', overflow:'visible' }}>
        {renderShapeSVG(shapeType, Math.max(w,4), Math.max(h,4), 'rgba(180,150,255,0.15)', 'rgba(180,150,255,0.85)', sw, 0)}
        {/* Size label */}
        <text x={Math.max(w,4)/2} y={Math.max(h,4)+18} textAnchor="middle"
          fontSize="10" fill="rgba(180,150,255,0.8)" fontFamily="ui-monospace,monospace">
          {Math.round(w)} × {Math.round(h)}
        </text>
      </svg>
    </div>
  );
}

function NorthStarCard({ item, center, vision, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate }) {
  const [hover, setHover] = React.useState(false);
  const [editVision, setEditVision] = React.useState(false);
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking });
  const hl = isLinkSource
    ? { boxShadow:'0 0 0 2px rgba(200,170,255,0.85), 0 0 36px rgba(180,150,255,0.5)' }
    : isLinkTarget ? { boxShadow:'0 0 0 2px rgba(200,170,255,0.9)', transform:'scale(1.02)' }
    : linking ? { opacity:0.7 } : {};
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}} onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:center.x-180, top:center.y-90, width:360,
        textAlign:'center', padding:'28px 28px', borderRadius:100,
        background:`radial-gradient(circle at 50% 50%, ${(item.color||'#b088ff')}28, transparent 70%), var(--card)`,
        border:`1px solid ${(item.color||'#b088ff')}33`,
        boxShadow:`0 24px 64px ${(item.color||'#b088ff')}22`,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'box-shadow .18s, transform .18s, opacity .18s', ...hl }}>
      <div style={{ position:'absolute', top:'50%', right:16, transform:'translateY(-50%)', opacity:(hover||isLinkSource)?1:0, transition:'opacity .15s' }}>
        <button data-element-action onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
          style={{ width:22, height:22, border:'none',
            background:isLinkSource?'rgba(200,170,255,0.25)':'rgba(255,255,255,0.06)',
            color:isLinkSource?'var(--accent)':'var(--text-m)',
            cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center',
            borderRadius:6, padding:0 }}>
          <Icon name="arrow" size={11}/>
        </button>
      </div>
      <div style={{ fontSize:10.5, letterSpacing:1.5, textTransform:'uppercase',
        color:`${(item.color||'#b088ff')}cc`, marginBottom:8 }}>North Star</div>
      {editVision ? (
        <InlineInput value={vision} placeholder="Your north star…" multiline
          style={{ fontFamily:'Instrument Serif, serif', fontSize:22, lineHeight:1.3,
            color:'var(--text)', letterSpacing:'-0.005em',
            textAlign:'center', background:'var(--insp-input-bg,rgba(255,255,255,0.08))',
            border:'1px solid color-mix(in oklch, var(--accent) 45%, transparent)' }}
          onSave={v=>{ if(v!==null) onUpdate({body:v}); setEditVision(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditVision(true);}}
          style={{ fontFamily:'Instrument Serif, serif', fontSize:22, lineHeight:1.3,
            color:'var(--text)', letterSpacing:'-0.005em', cursor:'text',
            padding:'2px 4px', borderRadius:4,
            opacity: vision && vision!=='Your north star…' ? 1 : 0.55 }}>
          {vision||'Your north star…'}
        </div>
      )}
      <div style={{ marginTop:12, display:'inline-flex', alignItems:'center', gap:6,
        fontSize:10.5, color:`${(item.color||'#b088ff')}aa` }}>
        <span className="strat-pulse" style={{ width:6, height:6, borderRadius:'50%', background:item.color||'#b088ff', display:'inline-block' }}/>
        Reviewed every Sunday
      </div>
    </div>
  );
}

// North card for toolbar-added north elements
function NorthCard({ item, onOpen, onStartLink, onCompleteLink, onHoverEnter, onHoverLeave, onMove, toWorld, linking, isLinkSource, isLinkTarget, onUpdate, onDuplicate, onCtrlDragStart }) {
  const [hover, setHover] = React.useState(false);
  const [editLabel, setEditLabel] = React.useState(false);
  const [editBody, setEditBody] = React.useState(false);
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const c = item.color||'#b088ff';
  const w = item.w||300;
  const hl = isLinkSource
    ? { boxShadow:`0 0 0 2px ${c}cc, 0 0 36px ${c}66` }
    : isLinkTarget ? { boxShadow:`0 0 0 2px ${c}cc`, transform:'scale(1.02)' }
    : linking ? { opacity:0.7 } : {};
  const badgeType = item.badgeType||'none';
  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={()=>{setHover(true);onHoverEnter();}} onMouseLeave={()=>{setHover(false);onHoverLeave();}}
      style={{ position:'absolute', left:item.x, top:item.y, width:w,
        textAlign:'center', padding:'24px 22px', borderRadius:24,
        background:`radial-gradient(circle at 50% 40%, ${c}22, transparent 70%), var(--card)`,
        border:`1px solid ${c}33`,
        boxShadow:`0 16px 48px ${c}22`,
        cursor:linking?(isLinkTarget?'pointer':'default'):'grab',
        transition:'box-shadow .18s, transform .18s, opacity .18s', ...hl }}>
      <div style={{ position:'absolute', top:'50%', right:10, transform:'translateY(-50%)', display:'flex', gap:4,
        opacity:(hover||isLinkSource)?1:0, transition:'opacity .15s' }}>
        <button data-element-action onClick={e=>{e.stopPropagation();if(!linking)onStartLink();}}
          style={{ width:20,height:20,border:'none',background:`${c}22`,color:c,
            cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',borderRadius:5,padding:0 }}>
          <Icon name="arrow" size={10}/>
        </button>
      </div>
      {editLabel ? (
        <InlineInput value={item.label} placeholder="North Star label"
          style={{ fontSize:9.5, letterSpacing:1.5, textTransform:'uppercase', color:`${c}bb`,
            marginBottom:8, textAlign:'center', background:`${c}15`, border:`1px solid ${c}33` }}
          onSave={v=>{ if(v!==null) onUpdate({label:v}); setEditLabel(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditLabel(true);}}
          style={{ fontSize:9.5, letterSpacing:1.5, textTransform:'uppercase', color:`${c}bb`,
            marginBottom:8, cursor:'text' }}>
          {item.label||'North Star'}
        </div>
      )}
      {editBody ? (
        <InlineInput value={item.body} placeholder="Your vision here" multiline
          style={{ fontFamily:'Instrument Serif, serif', fontSize:18, lineHeight:1.35,
            color:'var(--text)', textAlign:'center', background:'var(--insp-input-bg,rgba(255,255,255,0.07))',
            border:'1px solid color-mix(in oklch, var(--accent) 40%, transparent)' }}
          onSave={v=>{ if(v!==null) onUpdate({body:v}); setEditBody(false); }}/>
      ) : (
        <div onDoubleClick={e=>{e.stopPropagation();setEditBody(true);}}
          style={{ fontFamily:'Instrument Serif, serif', fontSize:18, lineHeight:1.35,
            color:'var(--text)', letterSpacing:'-0.005em', cursor:'text',
            opacity: item.body?1:0.45, fontStyle: item.body?'normal':'italic' }}>
          {item.body||'Your vision here'}
        </div>
      )}
      {badgeType!=='none' && (
        <div style={{ marginTop:10, display:'inline-flex', alignItems:'center', gap:6,
          fontSize:10.5, color:`${c}99`, padding:'3px 10px', borderRadius:999,
          background:`${c}14`, border:`1px solid ${c}22` }}>
          <span className="strat-pulse" style={{ width:5,height:5,borderRadius:'50%',background:c,display:'inline-block' }}/>
          {badgeType==='date' ? (item.badgeDate||'Set a date') :
           badgeType==='text' ? (item.badgeText||'Add a note') :
           badgeType==='status' ? (() => {
             const tasks = item._connectedTasks||[];
             const done = tasks.filter(t=>t.done).length;
             return `${tasks.length?Math.round(done/tasks.length*100):0}% complete`;
           })() : null}
        </div>
      )}
    </div>
  );
}

function ToolBtn({ tool, label, desc, icon, sel, setSel }) {
  const active = sel === tool;
  return (
    <div className="strat-tip" data-tip-wide-right={`${label} · ${desc}`}>
      <button className="strat-rail-icon" onClick={e=>{e.stopPropagation();setSel(active?null:tool);}}
        style={{ width:36, height:36,
          color:active?'var(--accent)':'var(--text-m)',
          background:active?'var(--glow)':'transparent',
          border:active?'1px solid color-mix(in oklch, var(--accent) 40%, transparent)':'1px solid transparent' }}>
        <Icon name={icon} size={15}/>
      </button>
    </div>
  );
}

// ─── OVERVIEW PANEL ──────────────────────────────────────
function OverviewPanel({ clusters, extraItems }) {
  const allItems = [...clusters, ...(extraItems||[])];
  const allTasks = [];
  allItems.forEach(c => (c.tasks||[]).forEach(t => allTasks.push({ ...t, cluster:c })));

  const total = allTasks.length;
  const done = allTasks.filter(t=>t.done).length;
  const pct = total ? Math.round(done/total*100) : 0;
  const R = 28, C = 2*Math.PI*R;

  const overdue = allTasks.filter(t=>t.overdue||t.due==='Overdue');
  const upcoming = allTasks.filter(t=>!t.done&&!t.overdue&&t.due!=='Overdue'&&
    (t.due==='Today'||t.due==='Tomorrow'||t.due==='Daily'||
     (t.due&&!t.overdue&&t.due!=='Overdue')));

  const TaskLine = ({t})=>(
    <div style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 6px', borderRadius:7,
      cursor:'pointer', transition:'background .1s' }}
      onMouseEnter={e=>e.currentTarget.style.background='rgba(128,128,128,0.06)'}
      onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
      <div style={{ width:14,height:14,borderRadius:4,border:`1.5px solid ${t.overdue?'#f87171':'rgba(128,128,128,0.3)'}`,
        flexShrink:0, background: t.done?'#b088ff':'transparent',
        display:'flex',alignItems:'center',justifyContent:'center' }}>
        {t.done&&<svg width="9" height="9" viewBox="0 0 9 9" fill="none"><path d="M1 4.5l2.5 2.5L8 2" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>}
      </div>
      <span style={{ width:5,height:5,borderRadius:'50%',background:t.cluster?.color||'#8a97ab',flexShrink:0 }}/>
      <span style={{ flex:1, fontSize:12, color:'var(--text)', textDecoration:t.done?'line-through':'none', opacity:t.done?0.5:1 }}>{t.t}</span>
      <span style={{ fontSize:10.5, color:t.overdue?'#f87171':t.due==='Today'?'#60a5fa':'var(--text-f)',
        fontVariantNumeric:'tabular-nums' }}>{t.due}</span>
    </div>
  );

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', minHeight:0 }}>
      {/* Status header */}
      <div style={{ padding:'18px 18px 16px', borderBottom:'1px solid var(--border-l)' }}>
        <div style={{ display:'flex', alignItems:'center', gap:16 }}>
          {/* Status ring */}
          <div style={{ position:'relative', flexShrink:0 }}>
            <svg width={68} height={68}>
              <circle cx={34} cy={34} r={R} fill="none" stroke="rgba(128,128,128,0.1)" strokeWidth={5}/>
              <circle cx={34} cy={34} r={R} fill="none" stroke="#b088ff" strokeWidth={5}
                strokeDasharray={C} strokeDashoffset={C*(1-pct/100)}
                strokeLinecap="round" transform="rotate(-90 34 34)"
                style={{ transition:'stroke-dashoffset .5s ease' }}/>
            </svg>
            <div style={{ position:'absolute', inset:0, display:'flex', flexDirection:'column',
              alignItems:'center', justifyContent:'center' }}>
              <div style={{ fontSize:14, fontWeight:700, color:'var(--text)', lineHeight:1 }}>{pct}%</div>
              <div style={{ fontSize:8.5, color:'var(--text-f)', letterSpacing:0.3 }}>done</div>
            </div>
          </div>
          {/* KPIs */}
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:'Instrument Serif, serif', fontSize:18, color:'var(--text)', marginBottom:8 }}>Overview</div>
            <div style={{ display:'flex', gap:16 }}>
              <div>
                <div style={{ fontSize:20, fontWeight:700, color:'var(--text)', lineHeight:1 }}>{done}</div>
                <div style={{ fontSize:10, color:'var(--text-f)', marginTop:2 }}>completed</div>
              </div>
              <div style={{ width:1, background:'var(--border-l)' }}/>
              <div>
                <div style={{ fontSize:20, fontWeight:700, color:'var(--text)', lineHeight:1 }}>{total}</div>
                <div style={{ fontSize:10, color:'var(--text-f)', marginTop:2 }}>total tasks</div>
              </div>
              {overdue.length>0 && (
                <>
                  <div style={{ width:1, background:'var(--border-l)' }}/>
                  <div>
                    <div style={{ fontSize:20, fontWeight:700, color:'#f87171', lineHeight:1 }}>{overdue.length}</div>
                    <div style={{ fontSize:10, color:'var(--text-f)', marginTop:2 }}>overdue</div>
                  </div>
                </>
              )}
            </div>
          </div>
        </div>
      </div>

      {/* Task lists */}
      <div style={{ flex:1, overflow:'auto', padding:'8px 12px 20px' }}>
        {/* Overdue */}
        {overdue.length>0 && (
          <div style={{ marginBottom:16 }}>
            <div style={{ display:'flex', alignItems:'center', gap:6, padding:'10px 6px 4px',
              fontSize:10, fontWeight:700, letterSpacing:1.1, textTransform:'uppercase', color:'#f87171' }}>
              <svg width={10} height={10} viewBox="0 0 16 16" fill="none" stroke="#f87171" strokeWidth="2" strokeLinecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 5v3M8 11v.5"/></svg>
              Overdue · {overdue.length}
            </div>
            <div style={{ borderRadius:8, border:'1px solid rgba(248,113,113,0.2)', overflow:'hidden',
              background:'rgba(248,113,113,0.04)' }}>
              {overdue.map((t,i)=><TaskLine key={i} t={t}/>)}
            </div>
          </div>
        )}

        {/* Upcoming */}
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:6, padding:'10px 6px 4px',
            fontSize:10, fontWeight:700, letterSpacing:1.1, textTransform:'uppercase', color:'var(--text-f)' }}>
            <svg width={10} height={10} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"><rect x="2" y="3" width="12" height="11" rx="2"/><path d="M5 1v3M11 1v3M2 7h12"/></svg>
            Upcoming · {upcoming.length}
          </div>
          {upcoming.length===0 ? (
            <div style={{ padding:'12px 6px', fontSize:12, color:'var(--text-f)' }}>All clear — nothing upcoming.</div>
          ) : (
            <div style={{ display:'flex', flexDirection:'column', gap:1 }}>
              {upcoming.map((t,i)=><TaskLine key={i} t={t}/>)}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Estimate branch box height based on text length and available width
function estimateBranchH(label, branchW) {
  const usableW = Math.max(40, branchW - 30); // subtract dot + padding
  const charsPerLine = Math.max(1, Math.floor(usableW / 7.2));
  const lines = Math.ceil(Math.max(1, (label || 'Branch').length) / charsPerLine);
  return Math.max(36, lines * 19 + 14);
}

// ─── TREE ELEMENT ────────────────────────────────────────
function TreeEl({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, isLinkSource, onHoverEnter, onHoverLeave, onStartLink, onStartLinkWithAnchor, onUpdate, onDuplicate, onCtrlDragStart }) {
  const theme = useTheme();
  const isDark = theme === 'dark';
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const [hover, setHover] = React.useState(false);
  const [hoverAnchor, setHoverAnchor] = React.useState(null); // 'root' | 'branch:N'
  const c = item.color || '#b088ff';
  const branches = item.branches || [];

  const PAD = 14;
  const ROOT_W = item.rootW || 112;
  const ROOT_H = 40;
  const BRANCH_GAP = 8;
  const CONN_W = 48;
  const BRANCH_W = item.branchW || 132;

  const branchHeights = branches.map(b => estimateBranchH(b.label, BRANCH_W));
  const totalBranchH = branchHeights.reduce((s,h) => s+h, 0) + Math.max(0, branches.length-1) * BRANCH_GAP;
  const totalH = Math.max(totalBranchH, ROOT_H) + PAD * 2;
  const rootCY = totalH / 2;
  const totalW = ROOT_W + CONN_W + BRANCH_W + PAD + 14;

  const branchYs = branches.map((_, i) => {
    const startY = (totalH - totalBranchH) / 2;
    let y = startY;
    for (let j = 0; j < i; j++) y += branchHeights[j] + BRANCH_GAP;
    return y + branchHeights[i] / 2;
  });
  const LinkBtn = ({ anchor, right, top, transform }) => {
    const isMe = isLinkSource && hoverAnchor === anchor;
    return (
      <button data-element-action
        onClick={e => { e.stopPropagation(); if (!linking) onStartLinkWithAnchor(anchor); }}
        onMouseEnter={() => setHoverAnchor(anchor)}
        onMouseLeave={() => setHoverAnchor(null)}
        style={{ position:'absolute', right, top, transform,
          width:18, height:18, border:'none', borderRadius:4, cursor:'pointer', zIndex:10,
          background: isMe ? 'rgba(200,170,255,0.3)' : isDark ? 'rgba(20,25,34,0.92)' : 'rgba(240,242,248,0.95)',
          color: isMe ? '#e4d9ff' : 'var(--text-m)',
          display:'flex', alignItems:'center', justifyContent:'center',
          opacity: hover || isMe ? 1 : 0, transition:'opacity .12s',
          boxShadow: '0 1px 4px rgba(0,0,0,0.18)' }}>
        <Icon name="arrow" size={9}/>
      </button>
    );
  };

  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={() => { setHover(true); onHoverEnter(); }}
      onMouseLeave={() => { setHover(false); setHoverAnchor(null); onHoverLeave(); }}
      style={{ position:'absolute', left:item.x, top:item.y,
        width:totalW, height:totalH,
        cursor:linking ? (isLinkTarget ? 'pointer' : 'default') : 'grab',
        ...linkHL(isLinkTarget, linking, isLinkSource) }}>

      {/* SVG connectors */}
      <svg style={{ position:'absolute', inset:0, pointerEvents:'none', overflow:'visible' }}
        width={totalW} height={totalH}>
        {branchYs.map((bY, i) => {
          const x1 = ROOT_W, y1 = rootCY;
          const x2 = ROOT_W + CONN_W, y2 = bY;
          const cpx = (x1 + x2) / 2;
          return (
            <path key={i}
              d={`M ${x1} ${y1} C ${cpx} ${y1} ${cpx} ${y2} ${x2} ${y2}`}
              fill="none" stroke={`${c}55`} strokeWidth={1.5} strokeLinecap="round"/>
          );
        })}
        {branchYs.map((bY, i) => (
          <circle key={`d${i}`} cx={ROOT_W + CONN_W} cy={bY} r={2.5} fill={`${c}88`}/>
        ))}
      </svg>

      {/* Root box */}
      <div style={{ position:'absolute', left:0, top:rootCY - ROOT_H / 2,
        width:ROOT_W, height:ROOT_H, borderRadius:12,
        background: isDark ? `${c}1e` : `${c}14`,
        border:`1.5px solid ${c}66`,
        display:'flex', alignItems:'center', justifyContent:'center',
        padding:'0 10px',
        boxShadow: isDark ? `0 0 20px ${c}18` : `0 2px 10px ${c}20` }}>
        <div style={{ fontSize:13, fontWeight:600, color:'var(--text)',
          wordBreak:'break-word', textAlign:'center', lineHeight:1.3 }}>
          {item.root || 'Root'}
        </div>
        <LinkBtn anchor="root" right={-22} top="50%" transform="translateY(-50%)"/>
      </div>

      {/* Branch boxes */}
      {branches.map((b, i) => (
        <div key={b.id || i} style={{ position:'absolute',
          left:ROOT_W + CONN_W,
          top:branchYs[i] - branchHeights[i] / 2,
          width:BRANCH_W, minHeight:branchHeights[i],
          borderRadius:9,
          background: isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.025)',
          border:`1px solid ${isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.07)'}`,
          display:'flex', alignItems:'center', padding:'6px 28px 6px 10px',
          fontSize:12, color:'var(--text-m)', lineHeight:1.4 }}>
          <div style={{ width:5, height:5, borderRadius:'50%', background:c,
            flexShrink:0, marginRight:8, opacity:0.65, marginTop:1 }}/>
          <span style={{ wordBreak:'break-word', flex:1 }}>
            {b.label || 'Branch'}
          </span>
          <LinkBtn anchor={`branch:${i}`} right={4} top="50%" transform="translateY(-50%)"/>
        </div>
      ))}

      {hover && !linking && (
        <div style={{ position:'absolute', inset:-3, borderRadius:15,
          border:'1px dashed rgba(180,150,255,0.38)', pointerEvents:'none' }}/>
      )}
    </div>
  );
}

// ─── ARROW ELEMENT ────────────────────────────────────────
function ArrowEl({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, isLinkSource, onHoverEnter, onHoverLeave, onStartLink, onUpdate, onDuplicate, onCtrlDragStart }) {
  const [hover, setHover] = React.useState(false);
  const drag = useDraggable({ item, onOpen, onMove, toWorld, onCompleteLink, isLinkTarget, linking, onDuplicate, onCtrlDragStart });
  const c = item.color || '#b088ff';
  const sw = item.strokeWidth || 2;
  const dx = item.dx ?? 100, dy = item.dy ?? 0;
  const PAD = 22;

  const minX = Math.min(0, dx), minY = Math.min(0, dy);
  const boxW = Math.abs(dx) + PAD * 2, boxH = Math.abs(dy) + PAD * 2;
  const lx1 = -minX + PAD, ly1 = -minY + PAD;
  const lx2 = lx1 + dx, ly2 = ly1 + dy;

  const isDashed = item.arrowType === 'dashed';
  const isDouble = item.arrowType === 'double';
  const isThick = item.arrowType === 'thick';
  const isCurved = item.arrowType === 'curved';
  const effectiveSW = isThick ? Math.max(sw * 2.2, 4) : sw;
  const uid = item.id.replace(/[^a-zA-Z0-9]/g, '_');

  let pathD;
  if (isCurved) {
    const len = Math.hypot(dx, dy) || 1;
    const nx = -dy / len, ny = dx / len;
    const cpx = lx1 + dx / 2 + nx * len * 0.28;
    const cpy = ly1 + dy / 2 + ny * len * 0.28;
    pathD = `M ${lx1} ${ly1} Q ${cpx} ${cpy} ${lx2} ${ly2}`;
  } else {
    pathD = `M ${lx1} ${ly1} L ${lx2} ${ly2}`;
  }

  const mid = { x: (lx1 + lx2) / 2, y: (ly1 + ly2) / 2 };
  const mSz = isThick ? 5 : 4;

  // Endpoint drag handlers
  const onEndpointDrag = (e, which) => {
    e.stopPropagation(); e.preventDefault();
    const sw0 = toWorld(e.clientX, e.clientY);
    const origX = item.x, origY = item.y, origDx = item.dx??100, origDy = item.dy??0;
    const onMv = (ev) => {
      const w = toWorld(ev.clientX, ev.clientY);
      const ddx = w.x - sw0.x, ddy = w.y - sw0.y;
      if (which === 'start') {
        const nx = origX + ddx, ny = origY + ddy;
        onUpdate({ x: nx, y: ny, dx: origX + origDx - nx, dy: origY + origDy - ny });
      } else {
        onUpdate({ dx: origDx + ddx, dy: origDy + ddy });
      }
    };
    const onUp = () => { window.removeEventListener('mousemove',onMv); window.removeEventListener('mouseup',onUp); };
    window.addEventListener('mousemove',onMv); window.addEventListener('mouseup',onUp);
  };

  const epStyle = (cx, cy) => ({
    position:'absolute', left:cx-6, top:cy-6, width:12, height:12,
    borderRadius:'50%', background:c, border:'2px solid rgba(255,255,255,0.9)',
    cursor:'crosshair', zIndex:20, boxShadow:`0 0 6px ${c}88`,
    opacity: hover ? 1 : 0, transition:'opacity .12s'
  });

  return (
    <div data-no-pan ref={drag.ref} onMouseDown={drag.onMouseDown}
      onMouseEnter={() => { setHover(true); onHoverEnter(); }}
      onMouseLeave={() => { setHover(false); onHoverLeave(); }}
      style={{ position:'absolute',
        left: item.x + minX - PAD,
        top: item.y + minY - PAD,
        width:boxW, height:boxH,
        cursor:linking ? (isLinkTarget ? 'pointer' : 'default') : 'grab',
        ...linkHL(isLinkTarget, linking, isLinkSource) }}>
      <svg width={boxW} height={boxH} style={{ display:'block', overflow:'visible' }}>
        <defs>
          <marker id={`ah_${uid}`} viewBox="0 0 10 10" refX="9" refY="5"
            markerWidth={mSz} markerHeight={mSz} orient="auto-start-reverse">
            <path d="M0 0 L10 5 L0 10 z" fill={c}/>
          </marker>
        </defs>
        <path d={pathD} fill="none" stroke="transparent"
          strokeWidth={Math.max(18, effectiveSW + 14)} style={{ cursor:'grab' }}/>
        {hover && (
          <path d={pathD} fill="none" stroke={`${c}2a`}
            strokeWidth={effectiveSW + 8} strokeLinecap="round"/>
        )}
        <path d={pathD} fill="none" stroke={c}
          strokeWidth={effectiveSW}
          strokeDasharray={isDashed ? `${effectiveSW * 4} ${effectiveSW * 2.5}` : undefined}
          strokeLinecap="round"
          markerEnd={`url(#ah_${uid})`}
          markerStart={isDouble ? `url(#ah_${uid})` : undefined}/>
      </svg>
      {/* Endpoint handles */}
      <div data-element-action onMouseDown={e=>onEndpointDrag(e,'start')} style={epStyle(lx1, ly1)}/>
      <div data-element-action onMouseDown={e=>onEndpointDrag(e,'end')} style={epStyle(lx2, ly2)}/>
      {item.label && (
        <div style={{ position:'absolute', left:mid.x - 40, top:mid.y - 11,
          fontSize:11, color:c, fontWeight:500,
          background:'var(--bg)', padding:'1px 6px', borderRadius:4,
          border:`1px solid ${c}33`, pointerEvents:'none', whiteSpace:'nowrap' }}>
          {item.label}
        </div>
      )}
    </div>
  );
}

// Arrow preview while drawing
function ArrowDrawPreview({ preview }) {
  if (!preview) return null;
  const { x1, y1, x2, y2, arrowType } = preview;
  const dx = x2 - x1, dy = y2 - y1;
  const len = Math.hypot(dx, dy);
  if (len < 5) return null;
  const PAD = 22;
  const minX = Math.min(0, dx), minY = Math.min(0, dy);
  const boxW = Math.abs(dx) + PAD * 2, boxH = Math.abs(dy) + PAD * 2;
  const lx1 = -minX + PAD, ly1 = -minY + PAD;
  const lx2 = lx1 + dx, ly2 = ly1 + dy;
  let pathD;
  if (arrowType === 'curved') {
    const nx = -dy / len, ny = dx / len;
    const cpx = lx1 + dx / 2 + nx * len * 0.28;
    const cpy = ly1 + dy / 2 + ny * len * 0.28;
    pathD = `M ${lx1} ${ly1} Q ${cpx} ${cpy} ${lx2} ${ly2}`;
  } else {
    pathD = `M ${lx1} ${ly1} L ${lx2} ${ly2}`;
  }
  return (
    <div style={{ position:'absolute', left:x1 + minX - PAD, top:y1 + minY - PAD,
      width:boxW, height:boxH, pointerEvents:'none', zIndex:100 }}>
      <svg width={boxW} height={boxH} style={{ display:'block', overflow:'visible' }}>
        <defs>
          <marker id="prev_arrowhead" viewBox="0 0 10 10" refX="9" refY="5"
            markerWidth="4" markerHeight="4" orient="auto-start-reverse">
            <path d="M0 0 L10 5 L0 10 z" fill="rgba(180,150,255,0.85)"/>
          </marker>
        </defs>
        <path d={pathD} fill="none" stroke="rgba(180,150,255,0.85)"
          strokeWidth={2} strokeDasharray={arrowType==='dashed' ? '8 4' : undefined}
          markerEnd="url(#prev_arrowhead)" strokeLinecap="round"/>
        <text x={(lx1+lx2)/2} y={Math.min(ly1,ly2) - 6}
          textAnchor="middle" fontSize="10" fill="rgba(180,150,255,0.75)"
          fontFamily="ui-monospace,monospace">{Math.round(len)}px</text>
      </svg>
    </div>
  );
}

// ─── EDGE-AWARE CONNECTION POINT ────────────────────────
function getEdgePoint(item, target, anchor) {
  if (!item) return null;
  // For non-shapes use simple center heuristics (existing behaviour)
  if (item.kind !== 'shape') {
    if(item.kind==='north') {
      const cx=item.x+(item.w||300)/2, cy=item.y+(item.h||110)/2;
      if(!target) return {x:cx,y:cy};
      const dx=target.x-cx, dy=target.y-cy;
      const hw=(item.w||300)/2, hh=(item.h||110)/2;
      if(Math.abs(dx)<0.5&&Math.abs(dy)<0.5) return {x:cx,y:cy};
      const scaleX=hw/(Math.abs(dx)||0.001), scaleY=hh/(Math.abs(dy)||0.001);
      const sc=Math.min(scaleX,scaleY);
      return {x:cx+dx*sc, y:cy+dy*sc};
    }
    if(item.kind==='cluster')  return {x:item.x+80, y:item.y+38};
    if(item.kind==='note')     return {x:item.x+(item.w||170)/2, y:item.y+40};
    if(item.kind==='main')     return {x:item.x+(item.w||240)/2, y:item.y+50};
    if(item.kind==='medium')   return {x:item.x+100, y:item.y+32};
    if(item.kind==='deadline') return {x:item.x+80, y:item.y+36};
    if(item.kind==='hyperlink') return {x:item.x+110, y:item.y+28};
    if(item.kind==='arrow')    return {x:item.x+Math.round((item.dx||0)/2), y:item.y+Math.round((item.dy||0)/2)};
    if(item.kind==='tree') {
      const branches = item.branches||[];
      const ROOT_W = item.rootW||112, ROOT_H = 40;
      const BRANCH_GAP = 8, CONN_W = 48;
      const BRANCH_W = item.branchW||132, PAD = 14;
      const branchHeights = branches.map(b => estimateBranchH(b.label, BRANCH_W));
      const totalBranchH = branchHeights.reduce((s,h)=>s+h,0) + Math.max(0,branches.length-1)*BRANCH_GAP;
      const totalH = Math.max(totalBranchH, ROOT_H) + PAD*2;
      const rootCY = totalH/2;
      if (!anchor || anchor==='root') return {x:item.x+ROOT_W, y:item.y+rootCY};
      if (typeof anchor==='string' && anchor.startsWith('branch:')) {
        const idx = parseInt(anchor.split(':')[1]);
        const startY = (totalH-totalBranchH)/2;
        let bY = startY;
        for (let j=0; j<idx; j++) bY += branchHeights[j] + BRANCH_GAP;
        bY += branchHeights[idx]/2;
        return {x:item.x+ROOT_W+CONN_W+BRANCH_W, y:item.y+bY};
      }
      return {x:item.x+ROOT_W/2, y:item.y+rootCY};
    }
    return {x:item.x+80, y:item.y+40};
  }
  const cx = item.x + (item.w||120)/2;
  const cy = item.y + (item.h||80)/2;
  if (!target) return {x:cx, y:cy};
  const dx = target.x - cx, dy = target.y - cy;
  const hw = (item.w||120)/2, hh = Math.max((item.h||80)/2, 2);
  if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return {x:cx, y:cy};

  if (item.shapeType === 'line') {
    return dx >= 0 ? {x:item.x+(item.w||120), y:cy} : {x:item.x, y:cy};
  }
  if (item.shapeType === 'circle') {
    // True ellipse edge in the direction of (dx, dy)
    const angle = Math.atan2(dy, dx);
    return {x: cx + hw*Math.cos(angle), y: cy + hh*Math.sin(angle)};
  }
  if (item.shapeType === 'diamond') {
    // |x/hw| + |y/hh| = 1 — rhombus parametric
    const denom = Math.abs(dx)/hw + Math.abs(dy)/hh;
    if (denom < 0.001) return {x:cx, y:cy};
    const t = 1 / denom;
    return {x: cx + dx*t, y: cy + dy*t};
  }
  // rect / square — rectangle edge intersection
  const scaleX = hw / (Math.abs(dx) || 0.001);
  const scaleY = hh / (Math.abs(dy) || 0.001);
  const scale  = Math.min(scaleX, scaleY);
  return {x: cx + dx*scale, y: cy + dy*scale};
}

// ─── CONSTELLATION (MAIN APP) ─────────────────────────────
function Constellation() {
  const isMobile = useIsMobile();
  const [theme, setTheme] = React.useState(() => localStorage.getItem('strat-theme')||'dark');
  React.useEffect(()=>{ localStorage.setItem('strat-theme',theme); },[theme]);
  // Mirror the theme on <body> so page-level (mindmap-main, body safe-areas)
  // backgrounds can swap with the artboard.
  React.useEffect(() => {
    const b = document.body;
    if (!b) return;
    b.classList.toggle('mindmap-theme-light', theme === 'light');
    return () => { b.classList.remove('mindmap-theme-light'); };
  }, [theme]);
  const [globalMode, setGlobalMode] = React.useState('cluster');
  const [focused, setFocused] = React.useState(null);
  const [autoConnect, setAutoConnect] = React.useState(false);
  const [selectedTool, setSelectedTool] = React.useState(null);
  const [elementDetail, setElementDetail] = React.useState({});
  const [extraItems, setExtraItems] = React.useState([{
    id:'north-default', kind:'north', x:240, y:260,
    label:'North Star', body:'', color:'#b088ff', review:'Weekly', w:300, badgeType:'none'
  }]);
  const [connections, setConnections] = React.useState([]);
  const [linkFrom, setLinkFrom] = React.useState(null);
  const [linkFromAnchor, setLinkFromAnchor] = React.useState(null);
  const [hoverTarget, setHoverTarget] = React.useState(null);
  const [mouseWorld, setMouseWorld] = React.useState(null);
  const [rightPanel, setRightPanel] = React.useState('today'); // 'today' | 'inspector'
  const [rightPanelOpen, setRightPanelOpen] = React.useState(false);
  const [panelAutoHide, setPanelAutoHide] = React.useState(true);
  const canvasHostRef = React.useRef(null);
  const [selectedShapeType, setSelectedShapeType] = React.useState(null);
  const selectedShapeTypeRef = React.useRef(null);
  const selectShapeType = (type) => { selectedShapeTypeRef.current = type; setSelectedShapeType(type); };
  const [shapeMenuOpen, setShapeMenuOpen] = React.useState(false);
  const [drawPreview, setDrawPreview] = React.useState(null);
  const drawStartRef = React.useRef(null);
  const [hoveredConn, setHoveredConn] = React.useState(null);

  const [arrowMenuOpen, setArrowMenuOpen] = React.useState(false);
  const [selectedArrowType, setSelectedArrowType] = React.useState('straight');
  const selectedArrowTypeRef = React.useRef('straight');
  const selectArrowType = (type) => { selectedArrowTypeRef.current = type; setSelectedArrowType(type); };
  const [drawArrowPreview, setDrawArrowPreview] = React.useState(null);
  const drawArrowStartRef = React.useRef(null);

  const [clusters, setClusters] = React.useState([]);

  // ── Easylife Supabase persistence ──
  const persistence = (typeof window !== 'undefined') ? window.MindMapPersistence : null;
  const hydratedRef = React.useRef(false);
  const [workspaceName, setWorkspaceName] = React.useState('North Star — 2026');
  React.useEffect(() => {
    if (!persistence?.ready) { hydratedRef.current = true; return; }
    let cancelled = false;
    persistence.ready.then((s) => {
      if (cancelled || !s) { hydratedRef.current = true; return; }
      if (Array.isArray(s.clusters))    setClusters(s.clusters);
      if (Array.isArray(s.extraItems))  setExtraItems(s.extraItems);
      if (Array.isArray(s.connections)) setConnections(s.connections);
      if (typeof s.theme === 'string')  setTheme(s.theme);
      if (typeof s.name  === 'string')  setWorkspaceName(s.name);
      hydratedRef.current = true;
    }).catch(() => { hydratedRef.current = true; });
    return () => { cancelled = true; };
  }, []);

  React.useEffect(() => {
    if (!persistence?.save || !hydratedRef.current) return;
    persistence.save({ clusters, extraItems, connections, theme, name: workspaceName });
  }, [clusters, extraItems, connections, theme, workspaceName]);

  // ── Undo history ──
  const historyRef = React.useRef([]);
  const pushHistory = () => {
    historyRef.current = [
      ...historyRef.current.slice(-29),
      { clusters: clusters.map(c=>({...c,tasks:(c.tasks||[]).map(t=>({...t}))})),
        extraItems: extraItems.map(i=>({...i,tasks:(i.tasks||[]).map(t=>({...t}))})),
        connections: connections.map(c=>({...c})) }
    ];
  };
  const undo = () => {
    if (!historyRef.current.length) return;
    const prev = historyRef.current[historyRef.current.length - 1];
    historyRef.current = historyRef.current.slice(0, -1);
    setClusters(prev.clusters);
    setExtraItems(prev.extraItems);
    setConnections(prev.connections);
  };


  React.useEffect(()=>{
    const onKey = (e) => {
      if (e.key==='Escape') { setLinkFrom(null); setLinkFromAnchor(null); setHoverTarget(null); setMouseWorld(null); setSelectedTool(null); setShapeMenuOpen(false); setArrowMenuOpen(false); setDrawPreview(null); setDrawArrowPreview(null); drawStartRef.current=null; drawArrowStartRef.current=null; }
      if ((e.metaKey||e.ctrlKey) && e.key==='z' && !e.shiftKey) { e.preventDefault(); undo(); }
    };
    window.addEventListener('keydown', onKey);
    return ()=>window.removeEventListener('keydown', onKey);
  },[clusters, extraItems, connections]);

  const W=1700, H=1000;

  const toWorld = (clientX, clientY) => {
    const el = canvasHostRef.current; if (!el) return {x:0,y:0};
    const rect = el.getBoundingClientRect();
    const pz = el.__pz||{x:0,y:0,s:1};
    return { x:(clientX-rect.left-pz.x)/pz.s, y:(clientY-rect.top-pz.y)/pz.s };
  };

  const toggleDetail = (id) => setElementDetail(d=>({...d,[id]:!d[id]}));
  const isDetail = (id, def) => (id in elementDetail ? elementDetail[id] : def);

  const updatePos = (id, x, y) => {
    setClusters(cs=>cs.map(c=>c.id===id?{...c,x,y}:c));
    setExtraItems(list=>list.map(c=>c.id===id?{...c,x,y}:c));
  };
  const updateItem = (id, patch) => {
    setClusters(cs=>cs.map(c=>c.id===id?{...c,...patch}:c));
    setExtraItems(list=>list.map(c=>c.id===id?{...c,...patch}:c));
  };
  const deleteItem = (id) => {
    pushHistory();
    setClusters(cs=>cs.filter(c=>c.id!==id));
    setExtraItems(list=>list.filter(c=>c.id!==id));
    setConnections(cs=>cs.filter(c=>c.from!==id&&c.to!==id));
    if (focused===id) setFocused(null);
  };
  const duplicateItem = (id) => {
    const src=[...clusters,...extraItems].find(i=>i.id===id); if(!src)return;
    pushHistory();
    const newId=src.kind+'-'+Math.random().toString(36).slice(2,7);
    const copy={...src,id:newId,x:src.x+30,y:src.y+30};
    // Always add to extraItems — clusters in the `clusters` array get auto-center lines drawn,
    // so duplicates must go to extraItems to avoid phantom connections.
    setExtraItems(list=>[...list,copy]);
    setFocused(newId);
    setRightPanel('inspector');
    setRightPanelOpen(true);
  };
  const addItem = (kind, x, y) => {
    pushHistory();
    const id=kind+'-'+Math.random().toString(36).slice(2,7);
    const base={id,kind,x:x-80,y:y-30};
    let item;
    if(kind==='north') item={...base, kind:'north', label:'New vision', body:'Write your north star here', color:'#b088ff'};
    if(kind==='cluster') item={...base, kind:'cluster', label:'New cluster', intent:'What is this area about?', color:'#b088ff', w:250, tasks:[]};
    if(kind==='note') item={...base,w:190,color:'#fde68a',title:'New note',body:''};
    if(kind==='text') item={...base,text:'Label',fontSize:18,color:'rgba(220,225,235,0.85)'};
    if(kind==='main') item={...base,w:240,color:'#7fb3ff',label:'New objective',body:'',tasks:[]};
    if(kind==='medium') item={...base,label:'New milestone',body:'',date:''};
    if(kind==='deadline') item={...base,label:'New deadline',date:'May 1',body:''};
    if(kind==='hyperlink') item={...base,label:'Link name',url:'',notes:'',color:'#7fb3ff'};
    if(kind==='tree') item={...base, root:'Root topic', color:'#b088ff',
      branches:[{id:'b1',label:'First branch'},{id:'b2',label:'Second branch'},{id:'b3',label:'Third branch'}]};
    setExtraItems(list=>[...list,item]);
    // Notes and text open inspector right away
    if(kind==='note' || kind==='text' || kind==='cluster' || kind==='north' || kind==='hyperlink' || kind==='tree') { setElementDetail(d=>({...d,[id]:true})); setFocused(id); }
    setSelectedTool(null);
    if(autoConnect){
      const all=[...clusters,...extraItems]; let best=null,bestD=Infinity;
      for(const c of all){const d=Math.hypot((c.x+80)-x,(c.y+40)-y);if(d<bestD){bestD=d;best=c;}}
      if(best&&bestD<400) setConnections(cs=>[...cs,{from:best.id,to:id}]);
    }
  };

  const startLink = (id, anchor) => { setLinkFrom(id); setLinkFromAnchor(anchor||null); setHoverTarget(null); };
  const completeLink = (id) => {
    if(!linkFrom||linkFrom===id) return;
    if(!connections.find(c=>(c.from===linkFrom&&c.to===id)||(c.from===id&&c.to===linkFrom))) {
      pushHistory();
      setConnections(cs=>[...cs,{from:linkFrom,to:id,fromAnchor:linkFromAnchor||undefined}]);
    }
    setLinkFrom(null); setLinkFromAnchor(null); setHoverTarget(null); setMouseWorld(null);
  };

  const onCanvasClick = (e) => {
    if(e.target.closest('[data-no-pan]')) return;
    if(linkFrom){setLinkFrom(null);setHoverTarget(null);setMouseWorld(null);return;}
    if(selectedTool && selectedTool !== 'shape' && selectedTool !== 'arrow'){const w=toWorld(e.clientX,e.clientY);addItem(selectedTool,w.x,w.y);return;}
    // Click on empty canvas — auto-hide closes panel
    if(panelAutoHide){ setFocused(null); setRightPanelOpen(false); }
  };
  const onCanvasMouseMove = (e) => { if(linkFrom) setMouseWorld(toWorld(e.clientX,e.clientY)); };

  // ── Shape drawing ──
  const handleDrawStart = (e) => {
    if (e.button !== 0) return;
    const w = toWorld(e.clientX, e.clientY);
    drawStartRef.current = { wx: w.x, wy: w.y };
    setDrawPreview({ shapeType: selectedShapeTypeRef.current, x: w.x, y: w.y, w: 0, h: 0 });
  };
  const handleDrawMove = (e) => {
    if (!drawStartRef.current) return;
    const w = toWorld(e.clientX, e.clientY);
    const sx = drawStartRef.current.wx, sy = drawStartRef.current.wy;
    let rw = w.x - sx, rh = w.y - sy;
    const st = selectedShapeTypeRef.current || 'rect';
    const constrain = st === 'circle' || st === 'square' || st === 'diamond';
    if (constrain) { const sz = Math.max(Math.abs(rw), Math.abs(rh)); rw = Math.sign(rw||1)*sz; rh = Math.sign(rh||1)*sz; }
    const x = Math.min(sx, sx+rw), y = Math.min(sy, sy+rh);
    const pw = Math.abs(rw), ph = st==='line' ? Math.max(Math.abs(rh),4) : Math.abs(rh);
    setDrawPreview({ shapeType: st, x, y, w: pw, h: ph });
  };
  const handleDrawEnd = () => {
    if (!drawStartRef.current || !drawPreview) { drawStartRef.current=null; return; }
    drawStartRef.current = null;
    const p = drawPreview;
    setDrawPreview(null);
    if (p.w < 8 && p.h < 8) { setSelectedTool(null); return; } // tiny click = exit mode
    const id = 'shape-' + Math.random().toString(36).slice(2,7);
    const item = { id, kind:'shape', shapeType:p.shapeType||'rect',
      x:p.x, y:p.y, w:Math.max(p.w,20), h:Math.max(p.h, p.shapeType==='line'?4:20),
      fill:'none', stroke:'#b088ff', strokeWidth:2, opacity:100,
      text:'', fontSize:14, cornerRadius:0 };
    setExtraItems(list=>[...list,item]);
    setFocused(id);
  };

  // ── Arrow drawing ──
  const handleArrowDrawStart = (e) => {
    if (e.button !== 0) return;
    const w = toWorld(e.clientX, e.clientY);
    drawArrowStartRef.current = { wx: w.x, wy: w.y };
    setDrawArrowPreview({ arrowType: selectedArrowTypeRef.current, x1: w.x, y1: w.y, x2: w.x, y2: w.y });
  };
  const handleArrowDrawMove = (e) => {
    if (!drawArrowStartRef.current) return;
    const w = toWorld(e.clientX, e.clientY);
    setDrawArrowPreview({ arrowType: selectedArrowTypeRef.current,
      x1: drawArrowStartRef.current.wx, y1: drawArrowStartRef.current.wy,
      x2: w.x, y2: w.y });
  };
  const handleArrowDrawEnd = () => {
    if (!drawArrowStartRef.current || !drawArrowPreview) { drawArrowStartRef.current=null; return; }
    const p = drawArrowPreview;
    drawArrowStartRef.current = null;
    setDrawArrowPreview(null);
    const dx = p.x2 - p.x1, dy = p.y2 - p.y1;
    if (Math.hypot(dx, dy) < 10) { setSelectedTool(null); return; }
    const id = 'arrow-' + Math.random().toString(36).slice(2,7);
    const item = { id, kind:'arrow', arrowType:p.arrowType||'straight',
      x: p.x1, y: p.y1, dx, dy,
      color:'#b088ff', strokeWidth:2, label:'' };
    setExtraItems(list=>[...list,item]);
    setFocused(id);
  };

  const center = { x: 620, y: 410 };
  const allItems = [...clusters,...extraItems];
  const itemById = Object.fromEntries(allItems.map(i=>[i.id,i]));
  const centerOf = (item) => {
    if(!item) return null;
    if(item.kind==='north') return {x:item.x+(item.w||300)/2, y:item.y+(item.h||110)/2};
    if(item.kind==='cluster') return {x:item.x+80,y:item.y+38};
    if(item.kind==='note') return {x:item.x+(item.w||170)/2,y:item.y+40};
    if(item.kind==='main') return {x:item.x+(item.w||240)/2,y:item.y+50};
    if(item.kind==='medium') return {x:item.x+100,y:item.y+32};
    if(item.kind==='deadline') return {x:item.x+80,y:item.y+36};
    if(item.kind==='shape') return {x:item.x+(item.w||120)/2, y:item.y+(item.h||80)/2};
    if(item.kind==='hyperlink') return {x:item.x+110,y:item.y+28};
    return {x:item.x+80,y:item.y+40};  };

  const focusedItem = allItems.find(c=>c.id===focused);

  // Open inspector when element focused
  React.useEffect(()=>{
    if(focused){ setRightPanel('inspector'); if(panelAutoHide) setRightPanelOpen(true); }
  },[focused]);

  const elementProps = (item) => ({
    item, linking:!!linkFrom, isLinkSource:linkFrom===item.id,
    isLinkTarget:linkFrom&&hoverTarget===item.id&&linkFrom!==item.id,
    onOpen:()=>setFocused(item.id), onStartLink:()=>startLink(item.id),
    onStartLinkWithAnchor:(anchor)=>startLink(item.id,anchor),
    onCompleteLink:()=>completeLink(item.id),
    onHoverEnter:()=>linkFrom&&linkFrom!==item.id&&setHoverTarget(item.id),
    onHoverLeave:()=>setHoverTarget(h=>h===item.id?null:h),
    onMove:(x,y)=>updatePos(item.id,x,y), toWorld,
    onUpdate:(patch)=>updateItem(item.id,patch),
    onDuplicate:()=>duplicateItem(item.id),
    onCtrlDragStart:(startX, startY) => {
      const src=[...clusters,...extraItems].find(i=>i.id===item.id); if(!src) return null;
      pushHistory();
      const newId=src.kind+'-'+Math.random().toString(36).slice(2,7);
      const copy={...src,id:newId,x:startX,y:startY};
      setExtraItems(list=>[...list,copy]);
      setFocused(newId);
      setRightPanel('inspector');
      setRightPanelOpen(true);
      return (nx,ny)=>updatePos(newId,nx,ny);
    },
  });

  return (
    <ThemeCtx.Provider value={theme}>
    <div className={`strat-artboard ${theme==='light'?'theme-light':''}`}>
      <div style={{ flex:1, display:'flex', flexDirection:'column', minWidth:0 }}>
        {/* Top bar */}
        <div style={{ height:52, display:'flex', alignItems:'center', gap:12,
          padding:'0 16px', borderBottom:'1px solid var(--border-l)',
          background:'var(--topbar)', flexShrink:0, position:'relative', zIndex:100 }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, fontSize:13 }}>
            <WorkspaceMenu currentName={workspaceName}
              onSwitchMap={s => {
                if (Array.isArray(s.clusters))    setClusters(s.clusters);
                if (Array.isArray(s.extraItems))  setExtraItems(s.extraItems);
                if (Array.isArray(s.connections)) setConnections(s.connections);
                if (typeof s.theme === 'string')  setTheme(s.theme);
                if (typeof s.name  === 'string')  setWorkspaceName(s.name);
              }}
              onCreateMap={s => {
                if (Array.isArray(s.clusters))    setClusters(s.clusters);
                if (Array.isArray(s.extraItems))  setExtraItems(s.extraItems);
                if (Array.isArray(s.connections)) setConnections(s.connections);
                if (typeof s.theme === 'string')  setTheme(s.theme);
                if (typeof s.name  === 'string')  setWorkspaceName(s.name);
              }}
            />
            <Icon name="chevronR" size={11} style={{ opacity:0.3 }}/>
            <input type="text" value={workspaceName}
              onChange={e => { setWorkspaceName(e.target.value); }}
              onKeyDown={e=>{ if(e.key==='Enter') { e.preventDefault(); e.currentTarget.blur(); } }}
              style={{ fontWeight:500, fontSize:13, background:'transparent', border:'none',
                outline:'none', color:'var(--text)', fontFamily:'inherit', minWidth:80, maxWidth:220 }}/>
            {/* + New map — hidden on mobile */}
            {!isMobile && <button className="strat-btn sq" title="Nytt kart"
              style={{ width:24, height:24, borderRadius:6 }}
              onClick={async () => {
                const p = window.MindMapPersistence;
                if (!p?.createMap) return;
                const result = await p.createMap('New Map');
                if (!result) return;
                const s = result.state;
                if (Array.isArray(s.clusters))    setClusters(s.clusters);
                if (Array.isArray(s.extraItems))  setExtraItems(s.extraItems);
                if (Array.isArray(s.connections)) setConnections(s.connections);
                if (typeof s.theme === 'string')  setTheme(s.theme);
                if (typeof s.name  === 'string')  setWorkspaceName(s.name);
              }}>
              <Icon name="plus" size={12}/>
            </button>}
            {/* Auto-saved chip — hidden on mobile */}
            {!isMobile && <div className="strat-chip" style={{ marginLeft:2 }}>
              <span style={{ width:5,height:5,borderRadius:'50%',background:'#7fe0a8' }}/>
              Auto-saved
            </div>}
          </div>
          {/* Overview / Details tabs — always visible (mobile & desktop) */}
          <div className="strat-glass-light" style={{ padding:3, display:'flex', gap:2, marginLeft: isMobile ? 'auto' : 20 }}>
            {[{id:'cluster',icon:'zoomOut',l:'Overview'},{id:'task',icon:'zoomIn',l:'Details'}].map(m=>(
              <div key={m.id} className={`strat-tab ${globalMode===m.id?'active':''}`}
                onClick={()=>{setGlobalMode(m.id);setElementDetail({});}}
                style={{ display:'flex', alignItems:'center', gap:6 }}>
                <Icon name={m.icon} size={12}/> {m.l}
              </div>
            ))}
          </div>
          {/* Undo — next to overview/details */}
          {!isMobile && <button className="strat-btn sq" title="Undo (⌘Z)" onClick={undo}
            style={{ marginLeft:6 }}>
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
              <path d="M3 7H10a4 4 0 010 8H6"/><path d="M6 3L2 7l4 4"/>
            </svg>
          </button>}
          <div style={{ flex:1 }}/>
          {/* Right panel toggle — desktop only */}
          {!isMobile && <div className="strat-glass-light" style={{ padding:3, display:'flex', gap:2 }}>
            <div className={`strat-tab ${rightPanel==='today'?'active':''}`}
              onClick={()=>{setRightPanel('today');setFocused(null);setRightPanelOpen(true);}}>Overview</div>
            <div className={`strat-tab ${rightPanel==='inspector'?'active':''}`}
              onClick={()=>{setRightPanel('inspector');setRightPanelOpen(true);}}>Inspector</div>
          </div>}
          {!isMobile && <button className="strat-btn sq"
            title={panelAutoHide ? 'Auto-hide ON — click to disable' : 'Auto-hide OFF — click to enable'}
            onClick={()=>setPanelAutoHide(o=>{ const next=!o; if(next){ setRightPanelOpen(false); } else { setRightPanelOpen(true); } return next; })}
            style={{ color: panelAutoHide ? 'var(--accent)' : 'var(--text-m)', borderColor: panelAutoHide ? 'color-mix(in oklch, var(--accent) 40%, transparent)' : undefined, background: panelAutoHide ? 'var(--glow)' : undefined }}>
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="1" y="2" width="14" height="12" rx="2"/><path d="M11 2v12"/><path d={panelAutoHide ? "M13 6l-1.5 2 1.5 2" : "M13 6l1.5 2-1.5 2"}/></svg>
          </button>}
          {/* Share & theme — desktop only */}
          {!isMobile && <button className="strat-btn sq"><Icon name="share" size={13}/></button>}
          {!isMobile && <ThemeToggle theme={theme} setTheme={setTheme}/>}
        </div>

        <div style={{ flex:1, display:'flex', minHeight:0 }}>
          {/* Canvas */}
          <div ref={canvasHostRef} className={linkFrom?'linking-mode':''}
            style={{ flex:1, position:'relative', minWidth:0, overflow:'hidden', touchAction:'none',
              cursor: selectedTool==='shape' && selectedShapeType ? 'crosshair' : selectedTool==='arrow' ? 'crosshair' : selectedTool && selectedTool!=='shape' ? 'crosshair' : linkFrom ? 'crosshair' : 'default' }}
            onClick={onCanvasClick} onMouseMove={onCanvasMouseMove}>
            {/* Research Preview badge — fixed at bottom-left of canvas, lifted above the zoom hint */}
            {!isMobile && <div style={{ position:'absolute', bottom:46, left:16, zIndex:20, pointerEvents:'none',
              display:'flex', alignItems:'center', gap:5,
              padding:'4px 10px', borderRadius:999,
              background: theme==='light'
                ? 'linear-gradient(135deg,rgba(139,92,246,0.1),rgba(99,130,246,0.07))'
                : 'linear-gradient(135deg,rgba(180,140,255,0.13),rgba(120,160,255,0.09))',
              border: theme==='light' ? '1px solid rgba(139,92,246,0.2)' : '1px solid rgba(180,150,255,0.22)' }}>
              <span style={{ width:5,height:5,borderRadius:'50%',background:'var(--accent)',flexShrink:0,opacity:0.9 }}/>
              <span style={{ fontSize:10.5, fontWeight:600, letterSpacing:0.4,
                color: theme==='light' ? 'oklch(0.45 0.20 285)' : 'rgba(200,175,255,0.85)' }}>Research Preview</span>
            </div>}
            <div style={{ position:'absolute', inset:0, pointerEvents:'none',
              background: theme==='light'
                ? 'radial-gradient(circle at 50% 40%, rgba(139,92,246,0.06), transparent 55%)'
                : 'radial-gradient(circle at 50% 50%, rgba(180,150,255,0.08), transparent 55%)' }}/>
            {theme==='dark' && <Stars/>}
            <PanZoom initial={{ x:40, y:20, s:0.82 }}>
              <div style={{ width:W, height:H, position:'relative' }}>
                <svg width={W} height={H} style={{ position:'absolute', inset:0, pointerEvents:'none', overflow:'visible' }}>
                  <circle cx={center.x} cy={center.y} r={220} fill="none" stroke={theme==='light'?'rgba(0,0,0,0.06)':'rgba(255,255,255,0.06)'} strokeDasharray="2 5"/>
                  <circle cx={center.x} cy={center.y} r={330} fill="none" stroke={theme==='light'?'rgba(0,0,0,0.04)':'rgba(255,255,255,0.04)'} strokeDasharray="2 5"/>
                  {clusters.map(c=>{
                    const cx=c.x+80,cy=c.y+38,dx=cx-center.x,dy=cy-center.y,len=Math.hypot(dx,dy)||1;
                    const ux=dx/len,uy=dy/len;
                    return <line key={c.id} x1={center.x+ux*110} y1={center.y+uy*110}
                      x2={cx-ux*50} y2={cy-uy*50}
                      stroke={theme==='light'?'rgba(0,0,0,0.12)':'rgba(255,255,255,0.14)'} strokeWidth="1" strokeDasharray="4 6"/>;
                  })}
                  {connections.map((cn,i)=>{
                    const fromItem=itemById[cn.from], toItem=itemById[cn.to];
                    if(!fromItem||!toItem) return null;
                    const ac=centerOf(fromItem), bc=centerOf(toItem);
                    const a=getEdgePoint(fromItem,bc,cn.fromAnchor), b=getEdgePoint(toItem,ac,cn.toAnchor);
                    if(!a||!b) return null;
                    const mx=(a.x+b.x)/2, my=(a.y+b.y)/2;
                    const cpx=mx, cpy=my-30;
                    // Bezier midpoint at t=0.5
                    const bmpx=0.25*a.x+0.5*cpx+0.25*b.x;
                    const bmpy=0.25*a.y+0.5*cpy+0.25*b.y;
                    const hov=hoveredConn===i;
                    const lineColor=theme==='light'?'rgba(100,80,180,0.35)':'rgba(180,150,255,0.45)';
                    const lineColorHov=theme==='light'?'rgba(100,80,200,0.7)':'rgba(200,160,255,0.8)';
                    return (
                      <g key={i}
                        onMouseEnter={()=>setHoveredConn(i)}
                        onMouseLeave={()=>setHoveredConn(null)}>
                        {/* Wide transparent hit target */}
                        <path d={`M ${a.x} ${a.y} Q ${cpx} ${cpy} ${b.x} ${b.y}`}
                          fill="none" stroke="transparent" strokeWidth={18} style={{cursor:'pointer'}}/>
                        {/* Visible line */}
                        <path d={`M ${a.x} ${a.y} Q ${cpx} ${cpy} ${b.x} ${b.y}`}
                          fill="none" stroke={hov?lineColorHov:lineColor}
                          strokeWidth={hov?2:1.5}
                          style={{transition:'stroke .12s, stroke-width .12s'}}/>
                        <circle cx={b.x} cy={b.y} r="3"
                          fill={theme==='light'?'rgba(100,80,180,0.6)':'rgba(180,150,255,0.8)'}/>
                        {/* Hover delete button at bezier midpoint */}
                        {hov && (
                          <g style={{cursor:'pointer'}}
                            onClick={()=>setConnections(cs=>cs.filter((_,j)=>j!==i))}>
                            <circle cx={bmpx} cy={bmpy} r={11}
                              fill="rgba(12,15,24,0.97)"
                              stroke="rgba(248,113,113,0.5)" strokeWidth={1}/>
                            <line x1={bmpx-4} y1={bmpy-4} x2={bmpx+4} y2={bmpy+4}
                              stroke="#fca5a5" strokeWidth={1.6} strokeLinecap="round"/>
                            <line x1={bmpx+4} y1={bmpy-4} x2={bmpx-4} y2={bmpy+4}
                              stroke="#fca5a5" strokeWidth={1.6} strokeLinecap="round"/>
                          </g>
                        )}
                      </g>
                    );
                  })}
                  {/* Live link preview */}
                  {linkFrom && mouseWorld && (()=>{
                    const fromItem=itemById[linkFrom]; if(!fromItem) return null;
                    const fromCenter=centerOf(fromItem);
                    const targetPt=hoverTarget?centerOf(itemById[hoverTarget]):mouseWorld;
                    const a=getEdgePoint(fromItem,targetPt,linkFromAnchor);
                    const b=hoverTarget?getEdgePoint(itemById[hoverTarget],fromCenter):mouseWorld;
                    if(!a||!b) return null;
                    const mx=(a.x+b.x)/2,my=(a.y+b.y)/2;
                    return (
                      <g>
                        <path d={`M ${a.x} ${a.y} Q ${mx} ${my-30} ${b.x} ${b.y}`}
                          fill="none" stroke="rgba(200,170,255,0.85)" strokeWidth="2"
                          strokeDasharray={hoverTarget?'0':'5 6'}/>
                        <circle cx={b.x} cy={b.y} r="4" fill="rgba(200,170,255,0.9)"/>
                      </g>
                    );
                  })()}
                </svg>

                {clusters.map(c=>(
                  <ClusterNode key={c.id} {...elementProps(c)}
                    detail={isDetail(c.id,globalMode==='task')}
                    onToggle={()=>toggleDetail(c.id)}/>
                ))}
                {extraItems.map(item=>{
                  const detail=isDetail(item.id,globalMode==='task');
                  const ep=elementProps(item);
                  if(item.kind==='note') return <NoteEl key={item.id} {...ep} detail={detail}/>;
                  if(item.kind==='main') return <MainBox key={item.id} {...ep} detail={detail} onToggle={()=>toggleDetail(item.id)}/>;
                  if(item.kind==='medium') return <MediumBox key={item.id} {...ep} detail={detail} onToggle={()=>toggleDetail(item.id)}/>;
                  if(item.kind==='deadline') return <DeadlineBox key={item.id} {...ep} detail={detail} onToggle={()=>toggleDetail(item.id)}/>;
                  if(item.kind==='hyperlink') return <HyperlinkBox key={item.id} {...ep} detail={detail} onToggle={()=>toggleDetail(item.id)}/>;
                  if(item.kind==='text') return <TextEl key={item.id} {...ep}/>;
                  if(item.kind==='cluster') return <ClusterNode key={item.id} {...ep}
                    detail={isDetail(item.id,globalMode==='task')} onToggle={()=>toggleDetail(item.id)}/>;
                  if(item.kind==='north') return <NorthCard key={item.id} {...ep}/>;
                  if(item.kind==='shape') return <ShapeEl key={item.id} {...ep}/>;
                  if(item.kind==='tree') return <TreeEl key={item.id} {...ep}/>;
                  if(item.kind==='arrow') return <ArrowEl key={item.id} {...ep}/>;
                  return null;
                })}
                {/* Draw preview */}
                {drawPreview && drawPreview.w > 4 && <ShapePreview preview={drawPreview}/>}
                {/* Arrow draw preview */}
                {drawArrowPreview && <ArrowDrawPreview preview={drawArrowPreview}/>}
              </div>
            </PanZoom>

            {/* Shape draw overlay — sits on top when shape tool active */}
            {selectedTool === 'shape' && selectedShapeType && (
              <div data-no-pan
                style={{ position:'absolute', inset:0, zIndex:50, cursor:'crosshair' }}
                onMouseDown={handleDrawStart}
                onMouseMove={handleDrawMove}
                onMouseUp={handleDrawEnd}
                onMouseLeave={()=>{ if(drawStartRef.current) handleDrawEnd(); }}/>
            )}

            {/* Arrow draw overlay */}
            {selectedTool === 'arrow' && (
              <div data-no-pan
                style={{ position:'absolute', inset:0, zIndex:50, cursor:'crosshair' }}
                onMouseDown={handleArrowDrawStart}
                onMouseMove={handleArrowDrawMove}
                onMouseUp={handleArrowDrawEnd}
                onMouseLeave={()=>{ if(drawArrowStartRef.current) handleArrowDrawEnd(); }}/>
            )}

            {/* Left toolbar */}
            {!isMobile && (
            <div data-no-pan className="strat-glass-light"
              style={{ position:'absolute', left:16, top:'50%', transform:'translateY(-50%)',
                padding:5, display:'flex', flexDirection:'column', gap:3, zIndex:10 }}>
              <ToolBtn tool="north" label="North Star" desc="Core vision card" icon="sparkle" sel={selectedTool} setSel={setSelectedTool}/>
              <div style={{ height:1, background:'var(--border)', margin:'4px 2px' }}/>
              <ToolBtn tool="cluster" label="Cluster" desc="Group related goals" icon="layers" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="main" label="Objective" desc="Big objective" icon="target" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="medium" label="Milestone" desc="A step along the way" icon="flag" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="deadline" label="Deadline" desc="Fixed date that matters" icon="calendar" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="note" label="Note" desc="Sticky thought" icon="sticky" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="text" label="Text" desc="Free-floating label" icon="textA" sel={selectedTool} setSel={setSelectedTool}/>
              <ToolBtn tool="hyperlink" label="Hyperlink" desc="Clickable link card" icon="link" sel={selectedTool} setSel={setSelectedTool}/>
              {/* Tree tool */}
              <ToolBtn tool="tree" label="Tree" desc="Branching node structure" icon="tree" sel={selectedTool} setSel={setSelectedTool}/>
              {/* Shapes tool with submenu */}
              <div style={{ position:'relative' }}>
                <div className="strat-tip" data-tip-right="Shapes">
                  <button className="strat-rail-icon" onClick={e=>{e.stopPropagation();setShapeMenuOpen(o=>!o);if(selectedTool==='shape')setSelectedTool(null);}}
                    style={{ width:36,height:36,
                      color: selectedTool==='shape'?'var(--accent)':'var(--text-m)',
                      background: selectedTool==='shape'?'var(--glow)':'transparent',
                      border: selectedTool==='shape'?'1px solid color-mix(in oklch, var(--accent) 40%, transparent)':'1px solid transparent' }}>
                    <Icon name="shapes" size={15}/>
                  </button>
                </div>
                {shapeMenuOpen && (
                  <div data-no-pan
                    style={{ position:'absolute', left:'calc(100% + 8px)', top:'50%', transform:'translateY(-50%)',
                      background:'var(--panel)', border:'1px solid var(--border)',
                      borderRadius:12, padding:6, display:'flex', flexDirection:'column', gap:2,
                      boxShadow:'0 8px 32px rgba(0,0,0,0.22)', backdropFilter:'blur(20px)', zIndex:100, width:130 }}>
                    {[
                      {type:'rect',    icon:'shapeRect',    label:'Rectangle'},
                      {type:'square',  icon:'shapeSquare',  label:'Square'},
                      {type:'circle',  icon:'shapeCircle',  label:'Circle'},
                      {type:'diamond', icon:'shapeDiamond', label:'Diamond'},
                      {type:'line',    icon:'shapeLine',    label:'Line'},
                    ].map(s=>(
                      <button key={s.type}
                        onClick={e=>{e.stopPropagation();selectShapeType(s.type);setSelectedTool('shape');setShapeMenuOpen(false);}}
                        style={{ display:'flex', alignItems:'center', gap:8, padding:'7px 10px',
                          borderRadius:8, border:'none', cursor:'pointer', fontFamily:'inherit',
                          background: selectedShapeType===s.type ? 'var(--glow)' : 'transparent',
                          color: selectedShapeType===s.type ? 'var(--accent)' : 'var(--text-m)',
                          fontSize:12, fontWeight:500, textAlign:'left', width:'100%',
                          transition:'background .1s, color .1s' }}
                        onMouseEnter={e=>{ if(selectedShapeType!==s.type){ e.currentTarget.style.background='var(--insp-btn-bg,rgba(255,255,255,0.07))'; e.currentTarget.style.color='var(--text)'; }}}
                        onMouseLeave={e=>{ if(selectedShapeType!==s.type){ e.currentTarget.style.background='transparent'; e.currentTarget.style.color='var(--text-m)'; }}}>
                        <Icon name={s.icon} size={14} style={{ flexShrink:0, opacity:0.85 }}/>
                        {s.label}
                      </button>
                    ))}

                  </div>
                )}
              </div>
              {/* Arrows tool with submenu */}
              <div style={{ position:'relative' }}>
                <div className="strat-tip" data-tip-right="Arrows">
                  <button className="strat-rail-icon" onClick={e=>{e.stopPropagation();setArrowMenuOpen(o=>!o);if(selectedTool==='arrow')setSelectedTool(null);}}
                    style={{ width:36,height:36,
                      color: selectedTool==='arrow'?'var(--accent)':'var(--text-m)',
                      background: selectedTool==='arrow'?'var(--glow)':'transparent',
                      border: selectedTool==='arrow'?'1px solid color-mix(in oklch, var(--accent) 40%, transparent)':'1px solid transparent' }}>
                    <Icon name="arrowsTool" size={15}/>
                  </button>
                </div>
                {arrowMenuOpen && (
                  <div data-no-pan
                    style={{ position:'absolute', left:'calc(100% + 8px)', top:'50%', transform:'translateY(-50%)',
                      background:'var(--panel)', border:'1px solid var(--border)',
                      borderRadius:12, padding:6, display:'flex', flexDirection:'column', gap:2,
                      boxShadow:'0 8px 32px rgba(0,0,0,0.22)', backdropFilter:'blur(20px)', zIndex:100, width:148 }}>
                    {[
                      {type:'straight', icon:'arrowStraight', label:'Straight'},
                      {type:'curved',   icon:'arrowCurved',   label:'Curved'},
                      {type:'double',   icon:'arrowDouble',   label:'Double-headed'},
                      {type:'dashed',   icon:'arrowDashed',   label:'Dashed'},
                      {type:'thick',    icon:'arrowThick',    label:'Thick'},
                    ].map(a=>(
                      <button key={a.type}
                        onClick={e=>{e.stopPropagation();selectArrowType(a.type);setSelectedTool('arrow');setArrowMenuOpen(false);}}
                        style={{ display:'flex', alignItems:'center', gap:8, padding:'7px 10px',
                          borderRadius:8, border:'none', cursor:'pointer', fontFamily:'inherit',
                          background: selectedArrowType===a.type&&selectedTool==='arrow' ? 'var(--glow)' : 'transparent',
                          color: selectedArrowType===a.type&&selectedTool==='arrow' ? 'var(--accent)' : 'var(--text-m)',
                          fontSize:12, fontWeight:500, textAlign:'left', width:'100%',
                          transition:'background .1s, color .1s' }}
                        onMouseEnter={e=>{ if(!(selectedArrowType===a.type&&selectedTool==='arrow')){ e.currentTarget.style.background='var(--insp-btn-bg,rgba(255,255,255,0.07))'; e.currentTarget.style.color='var(--text)'; }}}
                        onMouseLeave={e=>{ if(!(selectedArrowType===a.type&&selectedTool==='arrow')){ e.currentTarget.style.background='transparent'; e.currentTarget.style.color='var(--text-m)'; }}}>
                        <Icon name={a.icon} size={28} style={{ flexShrink:0, opacity:0.85 }}/>
                        {a.label}
                      </button>
                    ))}
                  </div>
                )}
              </div>
              <div className="strat-tip" data-tip-wide-right="Auto-connect new elements to nearest cluster">
                <button className="strat-rail-icon" onClick={()=>setAutoConnect(v=>!v)}
                  style={{ width:36,height:36,
                    color:autoConnect?'var(--accent)':'var(--text-m)',
                    background:autoConnect?'rgba(180,150,255,0.15)':'transparent' }}>
                  <Icon name="magnet" size={15}/>
                </button>
              </div>
            </div>
            )}

            {/* Mode hint */}
            {!isMobile && (selectedTool||linkFrom) && !(selectedTool==='shape'&&!selectedShapeType) && (
              <div data-no-pan style={{ position:'absolute', top:16, left:'50%', transform:'translateX(-50%)',
                padding:'8px 14px', borderRadius:999,
                background:'rgba(180,150,255,0.18)', border:'1px solid rgba(200,170,255,0.35)',
                color:'#e4d9ff', fontSize:12, fontWeight:500, zIndex:20,
                display:'flex', alignItems:'center', gap:8 }}>
                {linkFrom ? <><Icon name="arrow" size={12}/> Click a target to connect · Esc to cancel</>
                          : selectedTool === 'shape'
                            ? <><Icon name="shapes" size={12}/> Drag to draw {selectedShapeType} · Esc to cancel</>
                            : selectedTool === 'arrow'
                              ? <><Icon name="arrowsTool" size={12}/> Drag to draw {selectedArrowType} arrow · Esc to cancel</>
                              : <><Icon name="plus" size={12}/> Click canvas to place · Esc to cancel</>}
              </div>
            )}

            {!isMobile && <div data-no-pan style={{ position:'absolute', bottom:16, left:16,
              fontSize:11, color:'var(--text-f)', display:'flex', alignItems:'center', gap:6 }}>
              <span className="strat-chip" style={{ padding:'2px 6px', fontSize:10 }}>⌘+scroll</span>
              zoom · drag to pan · drag element to move
            </div>}

          </div>

          {/* Right panel — desktop only */}
          {!isMobile && <div style={{ display:'flex', flexShrink:0 }}>
            {/* Manual-mode collapse strip (only when auto-hide is OFF) */}
            {!panelAutoHide && (
              <div style={{ width:36, flexShrink:0, borderLeft:'1px solid var(--border-l)',
                background:'var(--rail)', display:'flex', flexDirection:'column',
                alignItems:'center', paddingTop:10, gap:4 }}>
                <div className="strat-tip" data-tip={rightPanelOpen ? 'Collapse panel' : 'Expand panel'}>
                  <button className="strat-rail-icon"
                    onClick={()=>setRightPanelOpen(o=>!o)}
                    style={{ color: rightPanelOpen ? 'var(--text-m)' : 'var(--accent)',
                             background: rightPanelOpen ? 'transparent' : 'var(--glow)',
                             transform: rightPanelOpen ? 'rotate(180deg)' : 'rotate(0deg)',
                             transition:'transform .2s, color .15s, background .15s' }}>
                    <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                      <path d="M10 4l-4 4 4 4"/>
                    </svg>
                  </button>
                </div>
              </div>
            )}
            {/* Panel content */}
            <div style={{ width: rightPanelOpen ? 340 : 0, flexShrink:0,
              borderLeft: rightPanelOpen ? '1px solid var(--border-l)' : 'none',
              background:'var(--panel)', backdropFilter:'blur(14px)',
              display:'flex', flexDirection:'column', overflow:'hidden',
              transition:'width .22s cubic-bezier(0.4,0,0.2,1)' }}>
            {rightPanel === 'today' ? (
              <OverviewPanel clusters={clusters} extraItems={extraItems}/>
            ) : (
              <Inspector
                item={focusedItem}
                connections={connections}
                itemById={itemById}
                onUpdate={updateItem}
                onDelete={deleteItem}
                onDuplicate={duplicateItem}
                onDisconnect={(from,to)=>setConnections(cs=>cs.filter(c=>!(c.from===from&&c.to===to)&&!(c.from===to&&c.to===from)))}
                onClose={()=>setFocused(null)}
                onFocus={setFocused}/>
            )}
          </div>
          </div>}
        </div>
      </div>
    </div>
    </ThemeCtx.Provider>
  );
}

const __mmRoot = document.getElementById('mindmap-root');
if (__mmRoot) ReactDOM.createRoot(__mmRoot).render(<Constellation/>);
