// 1Radar — Canvas Image Editor (Canva-like)
// Design editor for creating images with text, shapes, drawings and photos.

const CANVAS_PRESETS = [
  { label: 'Square',    w: 1080, h: 1080 },
  { label: 'Wide',      w: 1920, h: 1080 },
  { label: 'Story',     w: 1080, h: 1920 },
  { label: 'A4',        w: 794,  h: 1123 },
  { label: 'Landscape', w: 1280, h: 720  },
  { label: 'Banner',    w: 1200, h: 628  },
];

const EDITOR_PALETTE = [
  '#0E121B', '#FFFFFF', '#374151', '#6B7280',
  '#335CFF', '#7B5BFF', '#10B981', '#F59E0B',
  '#EF4444', '#FF8A00', '#EC4899', '#FFCB5C',
  '#A5D8FF', '#FFB59A', '#B7F0AD', '#D7B4FF',
];

const FONT_FAMILIES = [
  'Inter', 'Georgia', 'Arial', 'Helvetica Neue',
  'Times New Roman', 'JetBrains Mono', 'Courier New',
];

const SHAPE_OPTIONS = [
  { id: 'rect',     label: 'Rectangle',  shortcut: 'R' },
  { id: 'circle',   label: 'Circle',     shortcut: 'C' },
  { id: 'triangle', label: 'Triangle',   shortcut: '' },
];

const EDITOR_SNAP_PX = 6;
const TOOLBAR_W = 172;
const LIBRARY_W = 268;
const CANVAS_PROJECTS_KEY = 'canvas-projects';

function ieUid() {
  return Math.random().toString(36).slice(2, 9);
}

function ieLoadProjects() {
  try { return JSON.parse(localStorage.getItem(CANVAS_PROJECTS_KEY) || '[]'); } catch { return []; }
}

function ieSaveProjects(projects) {
  try { localStorage.setItem(CANVAS_PROJECTS_KEY, JSON.stringify(projects)); } catch {}
}

function ieRoundRect(ctx, x, y, w, h, r) {
  if (typeof ctx.roundRect === 'function') {
    ctx.roundRect(x, y, w, h, r);
  } else {
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r);
    ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
    ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r);
    ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r);
    ctx.closePath();
  }
}

async function ieGenerateThumbnail(canvasSize, canvasBg, elements) {
  try {
    const THUMB_MAX = 400;
    const ratio = canvasSize.w / canvasSize.h;
    const tw = ratio >= 1 ? THUMB_MAX : Math.round(THUMB_MAX * ratio);
    const th = ratio >= 1 ? Math.round(THUMB_MAX / ratio) : THUMB_MAX;
    const scale = tw / canvasSize.w;

    // Pre-load all image elements before drawing
    const imageMap = {};
    await Promise.all(
      elements.filter(el => el.type === 'image' && el.src).map(el =>
        new Promise(resolve => {
          const img = new window.Image();
          img.onload = () => { imageMap[el.id] = img; resolve(); };
          img.onerror = resolve;
          img.src = el.src;
        })
      )
    );

    const cvs = document.createElement('canvas');
    cvs.width = tw; cvs.height = th;
    const ctx = cvs.getContext('2d');
    if (!ctx) return null;

    ctx.fillStyle = canvasBg || '#ffffff';
    ctx.fillRect(0, 0, tw, th);

    for (const el of elements) {
      try {
        ctx.save();
        ctx.globalAlpha = el.opacity ?? 1;
        const x = el.x * scale, y = el.y * scale, w = el.w * scale, h = el.h * scale;

        if (el.type === 'rect') {
          ctx.fillStyle = el.fill || '#335CFF';
          const r = Math.min((el.borderRadius || 0) * scale, w / 2, h / 2);
          ctx.beginPath();
          if (r > 0) { ieRoundRect(ctx, x, y, w, h, r); } else { ctx.rect(x, y, w, h); }
          ctx.fill();
          if (el.stroke && el.strokeWidth) {
            ctx.strokeStyle = el.stroke; ctx.lineWidth = el.strokeWidth * scale; ctx.stroke();
          }
        } else if (el.type === 'circle') {
          ctx.save();
          ctx.fillStyle = el.fill || '#335CFF';
          ctx.translate(x + w/2, y + h/2); ctx.scale(w/2, h/2);
          ctx.beginPath(); ctx.arc(0, 0, 1, 0, Math.PI * 2); ctx.fill();
          ctx.restore();
        } else if (el.type === 'triangle') {
          ctx.fillStyle = el.fill || '#335CFF';
          ctx.beginPath(); ctx.moveTo(x + w/2, y); ctx.lineTo(x + w, y + h); ctx.lineTo(x, y + h); ctx.closePath(); ctx.fill();
        } else if (el.type === 'text') {
          const fs = (el.fontSize || 32) * scale;
          ctx.font = `${el.fontWeight || 700} ${fs}px ${el.fontFamily || 'Inter'}`;
          ctx.fillStyle = el.color || '#0E121B';
          ctx.fillText(el.text || '', x, y + fs);
        } else if (el.type === 'image' && imageMap[el.id]) {
          ctx.drawImage(imageMap[el.id], x, y, w, h);
        } else if (el.type === 'pen' && el.points && el.points.length > 1) {
          ctx.strokeStyle = el.color || '#0E121B'; ctx.lineWidth = (el.strokeWidth || 3) * scale;
          ctx.lineCap = 'round'; ctx.lineJoin = 'round';
          ctx.beginPath(); ctx.moveTo(el.points[0].x * scale, el.points[0].y * scale);
          for (let i = 1; i < el.points.length; i++) ctx.lineTo(el.points[i].x * scale, el.points[i].y * scale);
          ctx.stroke();
        }
        ctx.restore();
      } catch {}
    }
    return cvs.toDataURL('image/jpeg', 0.75);
  } catch { return null; }
}

// ── Canvas Projects Home ─────────────────────────────────────────────────────
function CanvasProjectsHome({ projects, onOpen, onNew, onDelete }) {
  const [hoveredId, setHoveredId] = React.useState(null);

  const fmtDate = (ts) => {
    if (!ts) return '';
    const d = new Date(ts);
    const now = new Date();
    const diffDays = Math.floor((now - d) / 86400000);
    if (diffDays === 0) return 'Today';
    if (diffDays === 1) return 'Yesterday';
    if (diffDays < 7) return `${diffDays} days ago`;
    return d.toLocaleDateString('en-US', { day: 'numeric', month: 'short' });
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg)', overflow: 'auto' }} className="scroll">
      {/* Header */}
      <div style={{ padding: '32px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--text)', marginBottom: 4 }}>Canvas</div>
        <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>Your designs and creations</div>
      </div>

      {/* Grid */}
      <div style={{ padding: '28px 40px 40px', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 20 }}>

        {/* New project card */}
        <div
          onClick={onNew}
          onMouseEnter={() => setHoveredId('__new__')}
          onMouseLeave={() => setHoveredId(null)}
          style={{ cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{
            aspectRatio: '4/3', borderRadius: 10, border: `2px dashed ${hoveredId === '__new__' ? 'var(--accent)' : 'var(--border)'}`,
            background: hoveredId === '__new__' ? 'var(--accent-soft)' : 'var(--bg-soft)',
            display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8,
            transition: 'border-color 150ms, background 150ms',
          }}>
            <div style={{ width: 36, height: 36, borderRadius: '50%', background: hoveredId === '__new__' ? 'var(--accent)' : 'var(--bg)', border: `1.5px solid ${hoveredId === '__new__' ? 'var(--accent)' : 'var(--border)'}`, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 150ms, border-color 150ms' }}>
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={hoveredId === '__new__' ? '#fff' : 'var(--text-muted)'} strokeWidth="2" strokeLinecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
            </div>
            <span style={{ fontSize: 12, fontWeight: 600, color: hoveredId === '__new__' ? 'var(--accent)' : 'var(--text-muted)', transition: 'color 150ms' }}>New project</span>
          </div>
        </div>

        {/* Existing projects */}
        {[...projects].sort((a, b) => (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0)).map(proj => (
          <div key={proj.id}
            onMouseEnter={() => setHoveredId(proj.id)}
            onMouseLeave={() => setHoveredId(null)}
            style={{ cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div
              onClick={() => onOpen(proj.id)}
              style={{
                aspectRatio: '4/3', borderRadius: 10, overflow: 'hidden', position: 'relative',
                border: `1.5px solid ${hoveredId === proj.id ? 'var(--accent)' : 'var(--border)'}`,
                background: proj.canvasBg || 'var(--bg-soft)',
                boxShadow: hoveredId === proj.id ? '0 6px 20px rgba(0,0,0,0.15)' : '0 2px 8px rgba(0,0,0,0.06)',
                transform: hoveredId === proj.id ? 'translateY(-2px)' : 'none',
                transition: 'border-color 150ms, box-shadow 150ms, transform 150ms',
              }}>
              {proj.thumbnail ? (
                <img src={proj.thumbnail} style={{ width: '100%', height: '100%', objectFit: 'contain', background: proj.canvasBg || '#fff', display: 'block' }} alt={proj.name}/>
              ) : (
                <div style={{ width: '100%', height: '100%', background: `linear-gradient(135deg, ${proj.canvasBg || '#f3f4f6'} 0%, color-mix(in srgb, ${proj.canvasBg || '#f3f4f6'} 70%, #000 30%) 100%)` }}/>
              )}
              {/* Delete button on hover */}
              {hoveredId === proj.id && (
                <button
                  onClick={e => { e.stopPropagation(); onDelete(proj.id); }}
                  style={{ position: 'absolute', top: 6, right: 6, width: 24, height: 24, borderRadius: 6, border: 'none', background: 'rgba(0,0,0,0.55)', color: '#fff', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)' }}
                  title="Delete">
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                </button>
              )}
            </div>
            <div style={{ padding: '0 2px' }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{proj.name || 'Untitled'}</div>
              <div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 2 }}>{fmtDate(proj.updatedAt)} · {proj.canvasSize?.w || 1080}×{proj.canvasSize?.h || 1080}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Shape icon helper ────────────────────────────────────────────────────────
function ShapeIcon({ type, size = 15 }) {
  const props = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round' };
  if (type === 'circle') return <svg {...props}><circle cx="12" cy="12" r="9"/></svg>;
  if (type === 'triangle') return <svg {...props}><path d="M12 4 22 20 2 20Z"/></svg>;
  return <Icon name="shape-rect" size={size}/>;
}

// ── Image library panel ──────────────────────────────────────────────────────
function IeImageLibrary({ onUpload, onAddFromAd, uploadedImages = [], onAddUploaded }) {
  const folders = (window.SAVE_FOLDERS_V3 || []).filter(f => !f.system);
  const ads = window.ADS_V3 || [];
  const [openFolder, setOpenFolder] = React.useState(null);
  const [search, setSearch] = React.useState('');

  const folderAds = openFolder
    ? ads.filter((_, i) => i % (folders.findIndex(f => f.id === openFolder) + 2) < 3).slice(0, 12)
    : [];

  const ADS_COLORS = ['#335CFF','#7B5BFF','#10B981','#F59E0B','#EF4444','#FF8A00','#EC4899','#6B7280'];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* Header */}
      <div style={{ padding: '12px 12px 8px', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', marginBottom: 8 }}>Images</div>
        {/* Upload button */}
        <label style={{ display: 'block', cursor: 'pointer' }}>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 7,
            padding: '7px 10px', borderRadius: 7,
            border: '1.5px dashed var(--border)',
            color: 'var(--text-muted)', fontSize: 12, fontWeight: 500,
            transition: 'border-color 120ms, color 120ms',
          }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--text-muted)'; }}>
            <Icon name="upload" size={14}/>
            Upload from device
          </div>
          <input type="file" accept="image/*" style={{ display: 'none' }}
            onChange={e => { if (e.target.files[0]) { onUpload(e.target.files[0]); e.target.value = ''; } }}/>
        </label>
      </div>

      <div style={{ flex: 1, overflow: 'auto' }} className="scroll">
        {/* Search */}
        <div style={{ padding: '8px 10px' }}>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 6,
            height: 30, padding: '0 9px',
            background: 'var(--bg-soft)', border: '1px solid var(--border)', borderRadius: 7,
          }}>
            <Icon name="search" size={12} style={{ color: 'var(--text-faint)', flexShrink: 0 }}/>
            <input value={search} onChange={e => setSearch(e.target.value)}
              placeholder="Search folders…"
              style={{ flex: 1, background: 'none', border: 'none', outline: 'none', fontSize: 12, color: 'var(--text)', minWidth: 0 }}/>
          </div>
        </div>

        {/* Uploaded images section */}
        {uploadedImages.length > 0 && (
          <div style={{ padding: '0 6px 4px' }}>
            <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.06em', padding: '4px 6px 6px' }}>
              Uploads
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
              {uploadedImages.map(img => (
                <button
                  key={img.id}
                  onClick={() => onAddUploaded && onAddUploaded(img)}
                  title={img.name || 'Add to canvas'}
                  style={{
                    aspectRatio: '1', borderRadius: 6, border: '1px solid var(--border)',
                    background: 'var(--bg-soft)', cursor: 'pointer', overflow: 'hidden', padding: 0,
                    position: 'relative', transition: 'transform 100ms, box-shadow 100ms',
                  }}
                  onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.04)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.18)'; }}
                  onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none'; }}>
                  <img src={img.src} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', pointerEvents: 'none' }}/>
                  <div style={{
                    position: 'absolute', bottom: 0, left: 0, right: 0,
                    background: 'linear-gradient(transparent, rgba(0,0,0,0.55))',
                    padding: '10px 4px 3px',
                  }}>
                    <div style={{ fontSize: 9, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }}>
                      {img.name || 'image'}
                    </div>
                  </div>
                </button>
              ))}
            </div>
          </div>
        )}

        {/* Folder list */}
        <div style={{ padding: '0 6px' }}>
          <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.06em', padding: '4px 6px 6px' }}>
            Your boards
          </div>

          {folders
            .filter(f => !f.parentId)
            .filter(f => !search || f.name.toLowerCase().includes(search.toLowerCase()))
            .map(folder => {
              const isOpen = openFolder === folder.id;
              const sub = folders.filter(f => f.parentId === folder.id);
              return (
                <div key={folder.id}>
                  <button
                    onClick={() => setOpenFolder(isOpen ? null : folder.id)}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 7,
                      width: '100%', padding: '6px 8px', borderRadius: 7,
                      border: 'none', cursor: 'pointer', textAlign: 'left',
                      background: isOpen ? 'var(--accent-soft)' : 'transparent',
                      color: isOpen ? 'var(--accent)' : 'var(--text)',
                      transition: 'background 100ms',
                    }}
                    onMouseEnter={e => { if (!isOpen) e.currentTarget.style.background = 'var(--bg-soft)'; }}
                    onMouseLeave={e => { if (!isOpen) e.currentTarget.style.background = 'transparent'; }}>
                    <span style={{ color: folder.color || 'var(--text-faint)', flexShrink: 0 }}>
                      <Icon name={folder.icon || 'folder'} size={14}/>
                    </span>
                    <span style={{ flex: 1, fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {folder.name}
                    </span>
                    <span style={{ fontSize: 11, color: 'var(--text-faint)', marginRight: 2 }}>{folder.count}</span>
                    <Icon name={isOpen ? 'chevron-down' : 'chevron-right'} size={11} style={{ color: 'var(--text-faint)', flexShrink: 0 }}/>
                  </button>

                  {/* Subfolder rows */}
                  {isOpen && sub.length > 0 && sub.map(sf => (
                    <button key={sf.id}
                      onClick={() => setOpenFolder(openFolder === sf.id ? folder.id : sf.id)}
                      style={{
                        display: 'flex', alignItems: 'center', gap: 7,
                        width: '100%', padding: '5px 8px 5px 28px', borderRadius: 6,
                        border: 'none', cursor: 'pointer', textAlign: 'left',
                        background: openFolder === sf.id ? 'var(--accent-soft)' : 'transparent',
                        color: openFolder === sf.id ? 'var(--accent)' : 'var(--text-muted)',
                      }}
                      onMouseEnter={e => { if (openFolder !== sf.id) e.currentTarget.style.background = 'var(--bg-soft)'; }}
                      onMouseLeave={e => { if (openFolder !== sf.id) e.currentTarget.style.background = 'transparent'; }}>
                      <span style={{ color: sf.color || 'var(--text-faint)', flexShrink: 0 }}><Icon name={sf.icon || 'folder'} size={12}/></span>
                      <span style={{ flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{sf.name}</span>
                      <span style={{ fontSize: 10, color: 'var(--text-faint)' }}>{sf.count}</span>
                    </button>
                  ))}

                  {/* Thumbnails grid when folder is open */}
                  {isOpen && (
                    <div style={{ padding: '8px 4px 8px 8px' }}>
                      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
                        {/* Mock ad cards — colored placeholders */}
                        {Array.from({ length: Math.min(folder.count || 6, 9) }, (_, i) => {
                          const ad = ads[i % Math.max(ads.length, 1)];
                          const color = ADS_COLORS[i % ADS_COLORS.length];
                          const seed = (folder.name || '').charCodeAt(0) + i;
                          return (
                            <button
                              key={i}
                              onClick={() => ad && onAddFromAd && onAddFromAd(ad, color)}
                              title={ad ? `${ad.brand || 'Ad'} — Add to canvas` : 'Add to canvas'}
                              style={{
                                aspectRatio: '1', borderRadius: 6, border: '1px solid var(--border)',
                                background: `linear-gradient(135deg, ${color}22, ${color}66)`,
                                cursor: 'pointer', overflow: 'hidden', padding: 0,
                                position: 'relative',
                                transition: 'transform 100ms, box-shadow 100ms',
                              }}
                              onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.04)'; e.currentTarget.style.boxShadow = `0 4px 12px ${color}44`; }}
                              onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none'; }}>
                              {ad && ad.thumbnail ? (
                                <img src={ad.thumbnail} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} onError={e => { e.target.style.display = 'none'; }}/>
                              ) : (
                                <div style={{
                                  position: 'absolute', inset: 0,
                                  display: 'flex', flexDirection: 'column',
                                  alignItems: 'center', justifyContent: 'center', gap: 2,
                                }}>
                                  <div style={{ width: 24, height: 24, borderRadius: 4, background: color + '88' }}/>
                                  <div style={{ width: 32, height: 3, borderRadius: 2, background: color + '66' }}/>
                                  <div style={{ width: 22, height: 3, borderRadius: 2, background: color + '44' }}/>
                                </div>
                              )}
                              <div style={{
                                position: 'absolute', inset: 0,
                                background: 'transparent',
                                border: '1.5px solid transparent',
                                borderRadius: 5,
                                transition: 'border-color 100ms',
                              }}
                              onMouseEnter={e => e.currentTarget.style.borderColor = color}
                              onMouseLeave={e => e.currentTarget.style.borderColor = 'transparent'}/>
                            </button>
                          );
                        })}
                      </div>
                      <div style={{ marginTop: 8, fontSize: 11, color: 'var(--text-faint)', textAlign: 'center' }}>
                        {folder.count} saved ads · click to add
                      </div>
                    </div>
                  )}
                </div>
              );
            })}

          {folders.filter(f => !f.parentId).length === 0 && (
            <div style={{ padding: '16px 8px', fontSize: 12, color: 'var(--text-faint)', textAlign: 'center' }}>
              No boards yet. Save ads from the Explore tab.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ── Stock photo library ───────────────────────────────────────────────────────
const PIXABAY_KEY = '55824294-0d0d56a18702d4aaa0f6ec9ca';
const PEXELS_KEY  = 'aXDQLagdGaYRr8WWHxFAWIpS7MFK87R9WuX3kmO38fOJN3Paibjer7du';

function IeStockLibrary({ onAddImage }) {
  const [query, setQuery]              = React.useState('');
  const [debouncedQuery, setDebounced] = React.useState('');
  const [photos, setPhotos]           = React.useState([]);
  const [loading, setLoading]         = React.useState(false);
  const [loadingMore, setLoadingMore] = React.useState(false);
  const fetchingRef                   = React.useRef(false);
  const hasMoreRef                    = React.useRef(false);
  const pageRef                       = React.useRef(1);
  const sentinelRef                   = React.useRef(null);
  const scrollRef                     = React.useRef(null);
  const loadMoreRef                   = React.useRef(null);

  const PER_PAGE = 30;

  const interleave = (a, b) => {
    const out = [], len = Math.max(a.length, b.length);
    for (let i = 0; i < len; i++) {
      if (i < a.length) out.push(a[i]);
      if (i < b.length) out.push(b[i]);
    }
    return out;
  };

  const fetchPage = (q, page) => Promise.all([
    fetch(`https://pixabay.com/api/?${new URLSearchParams({ key: PIXABAY_KEY, q, image_type: 'photo', per_page: PER_PAGE, safesearch: 'true', page })}`)
      .then(r => r.json()).then(d => (d.hits || []).map(h => ({ id: `px-${h.id}`, thumb: h.webformatURL, full: h.largeImageURL, author: h.user }))).catch(() => []),
    fetch(`https://api.pexels.com/v1/search?query=${encodeURIComponent(q)}&per_page=${PER_PAGE}&page=${page}`, { headers: { Authorization: PEXELS_KEY } })
      .then(r => r.json()).then(d => (d.photos || []).map(p => ({ id: `pe-${p.id}`, thumb: p.src.medium, full: p.src.large2x || p.src.large, author: p.photographer }))).catch(() => []),
  ]);

  React.useEffect(() => {
    const t = setTimeout(() => setDebounced(query), 400);
    return () => clearTimeout(t);
  }, [query]);

  const STOCK_DEFAULT = 'nature';

  React.useEffect(() => {
    const q = debouncedQuery.trim() || STOCK_DEFAULT;
    setLoading(true); setPhotos([]); fetchingRef.current = true; pageRef.current = 1;
    fetchPage(q, 1).then(([px, pe]) => {
      setPhotos(interleave(px, pe));
      pageRef.current = 2;
      hasMoreRef.current = px.length === PER_PAGE || pe.length === PER_PAGE;
    }).finally(() => { setLoading(false); fetchingRef.current = false; });
  }, [debouncedQuery]);

  loadMoreRef.current = () => {
    if (fetchingRef.current || !hasMoreRef.current) return;
    const q = debouncedQuery.trim() || STOCK_DEFAULT;
    fetchingRef.current = true; setLoadingMore(true);
    fetchPage(q, pageRef.current).then(([px, pe]) => {
      const merged = interleave(px, pe);
      setPhotos(prev => [...prev, ...merged]);
      pageRef.current += 1;
      hasMoreRef.current = px.length === PER_PAGE || pe.length === PER_PAGE;
    }).finally(() => { setLoadingMore(false); fetchingRef.current = false; });
  };

  React.useEffect(() => { if (!loading) loadMoreRef.current?.(); }, [loading]);

  React.useEffect(() => {
    const sentinel = sentinelRef.current, scroller = scrollRef.current;
    if (!sentinel || !scroller) return;
    const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) loadMoreRef.current?.(); }, { root: scroller, rootMargin: '120px' });
    obs.observe(sentinel);
    return () => obs.disconnect();
  }, []);

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', overflow:'hidden' }}>
      <div style={{ padding:'10px 10px 8px', borderBottom:'1px solid var(--border)', flexShrink:0 }}>
        <div style={{ fontSize:12, fontWeight:600, color:'var(--text-muted)', marginBottom:6 }}>Stock Photos</div>
        <div style={{ position:'relative' }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"
            style={{ position:'absolute', left:7, top:'50%', transform:'translateY(-50%)', color:'var(--text-faint)', pointerEvents:'none' }}>
            <circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>
          </svg>
          <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search free photos…"
            style={{ width:'100%', padding:'6px 8px 6px 26px', borderRadius:6, border:'1px solid var(--border)', background:'var(--bg)', color:'var(--text)', fontSize:12, outline:'none', boxSizing:'border-box' }}/>
        </div>
      </div>
      <div ref={scrollRef} style={{ flex:1, overflowY:'auto', padding:6 }}>
        {loading && <div style={{ textAlign:'center', padding:24, color:'var(--text-faint)', fontSize:12 }}>Loading…</div>}
        {!loading && photos.length === 0 && (
          <div style={{ textAlign:'center', padding:'32px 12px', color:'var(--text-faint)', fontSize:12, lineHeight:1.6 }}>
            No photos found.
          </div>
        )}
        {photos.length > 0 && (
          <div style={{ display:'grid', gridTemplateColumns:'repeat(2, 1fr)', gap:4 }}>
            {photos.map(photo => (
              <button key={photo.id} onClick={() => onAddImage(photo.full)}
                style={{ aspectRatio:'4/3', borderRadius:6, border:'1px solid var(--border)', background:'var(--bg-soft)', cursor:'pointer', overflow:'hidden', padding:0, position:'relative' }}
                onMouseEnter={e => { const o = e.currentTarget.querySelector('.spl-ov'); if(o) o.style.opacity='1'; }}
                onMouseLeave={e => { const o = e.currentTarget.querySelector('.spl-ov'); if(o) o.style.opacity='0'; }}>
                <img src={photo.thumb} style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }} loading="lazy"/>
                <div className="spl-ov" style={{ position:'absolute', inset:0, background:'rgba(0,0,0,0.32)', opacity:0, transition:'opacity 150ms', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:2 }}>
                  <span style={{ color:'#fff', fontSize:11, fontWeight:600 }}>Add to canvas</span>
                  {photo.author && <span style={{ color:'rgba(255,255,255,0.7)', fontSize:9 }}>by {photo.author}</span>}
                </div>
              </button>
            ))}
          </div>
        )}
        <div ref={sentinelRef} style={{ height:1 }}/>
        {loadingMore && <div style={{ textAlign:'center', padding:'8px 0', color:'var(--text-faint)', fontSize:11 }}>Loading more…</div>}
      </div>
    </div>
  );
}

// ── Icon library panel ───────────────────────────────────────────────────────
const ICON_PAGE = 999;

function IeIconLibrary({ onAddIcon }) {
  const [query, setQuery]              = React.useState('');
  const [debouncedQuery, setDebounced] = React.useState('');
  const [icons, setIcons]              = React.useState([]);
  const [loading, setLoading]          = React.useState(false);
  const [loadingMore, setLoadingMore]  = React.useState(false);
  const [total, setTotal]              = React.useState(0);
  const fetchingRef                    = React.useRef(false);
  const sentinelRef                    = React.useRef(null);
  const scrollRef                      = React.useRef(null);
  const loadMoreRef                    = React.useRef(null);

  React.useEffect(() => {
    const t = setTimeout(() => setDebounced(query), 350);
    return () => clearTimeout(t);
  }, [query]);

  // Default view: load Lucide collection on open or when query is cleared
  React.useEffect(() => {
    if (debouncedQuery.trim()) return;
    setIcons([]); setTotal(0); setLoading(true); fetchingRef.current = true;
    fetch('https://api.iconify.design/collection?prefix=lucide')
      .then(r => r.json())
      .then(d => {
        const list = [...(d.icons || []), ...Object.keys(d.aliases || {})].map(n => `lucide:${n}`);
        setIcons(list); setTotal(list.length);
      })
      .catch(() => {})
      .finally(() => { setLoading(false); fetchingRef.current = false; });
  }, [debouncedQuery]);

  // Search when query is non-empty
  React.useEffect(() => {
    if (!debouncedQuery.trim()) return;
    setIcons([]); setTotal(0); setLoading(true); fetchingRef.current = true;
    const qs = new URLSearchParams({ query: debouncedQuery, limit: ICON_PAGE });
    fetch(`https://api.iconify.design/search?${qs}`)
      .then(r => r.json())
      .then(d => { setIcons(d.icons || []); setTotal(d.total || 0); })
      .catch(() => {})
      .finally(() => { setLoading(false); fetchingRef.current = false; });
  }, [debouncedQuery]);

  // Always-fresh load-more (pagination only for search results)
  loadMoreRef.current = () => {
    if (fetchingRef.current || icons.length >= total || !debouncedQuery.trim()) return;
    fetchingRef.current = true; setLoadingMore(true);
    const qs = new URLSearchParams({ query: debouncedQuery, limit: ICON_PAGE, start: icons.length });
    fetch(`https://api.iconify.design/search?${qs}`)
      .then(r => r.json())
      .then(d => { setIcons(prev => [...prev, ...(d.icons || [])]); })
      .catch(() => {})
      .finally(() => { setLoadingMore(false); fetchingRef.current = false; });
  };

  React.useEffect(() => { if (!loading) loadMoreRef.current?.(); }, [loading]);

  React.useEffect(() => {
    const sentinel = sentinelRef.current, scroller = scrollRef.current;
    if (!sentinel || !scroller) return;
    const obs = new IntersectionObserver(
      ([e]) => { if (e.isIntersecting) loadMoreRef.current?.(); },
      { root: scroller, rootMargin: '120px' }
    );
    obs.observe(sentinel);
    return () => obs.disconnect();
  }, []);

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', overflow:'hidden' }}>
      <div style={{ padding:'10px 10px 8px', borderBottom:'1px solid var(--border)', flexShrink:0 }}>
        <div style={{ fontSize:12, fontWeight:600, color:'var(--text-muted)', marginBottom:6 }}>Icons</div>
        <div style={{ position:'relative' }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"
            style={{ position:'absolute', left:7, top:'50%', transform:'translateY(-50%)', color:'var(--text-faint)', pointerEvents:'none' }}>
            <circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>
          </svg>
          <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search icons…"
            style={{ width:'100%', padding:'6px 8px 6px 26px', borderRadius:6, border:'1px solid var(--border)', background:'var(--bg)', color:'var(--text)', fontSize:12, outline:'none', boxSizing:'border-box' }}/>
        </div>
      </div>
      <div ref={scrollRef} style={{ flex:1, overflowY:'auto', padding:6 }}>
        {loading && <div style={{ textAlign:'center', padding:24, color:'var(--text-faint)', fontSize:12 }}>Loading…</div>}
        {!loading && icons.length === 0 && debouncedQuery && (
          <div style={{ textAlign:'center', padding:'32px 12px', color:'var(--text-faint)', fontSize:12 }}>No icons found.</div>
        )}
        {icons.length > 0 && (
          <div style={{ display:'grid', gridTemplateColumns:'repeat(5, 1fr)', gap:4 }}>
            {icons.map(iconId => {
              const [prefix, name] = iconId.split(':');
              return (
                <button key={iconId} title={name} onClick={() => onAddIcon(iconId)}
                  style={{ aspectRatio:'1', borderRadius:6, border:'1px solid var(--border)', background:'var(--bg)', cursor:'pointer', padding:7, display:'flex', alignItems:'center', justifyContent:'center' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-soft)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'var(--bg)'}>
                  <img src={`https://api.iconify.design/${prefix}/${name}.svg`} style={{ width:'100%', height:'100%', objectFit:'contain' }} loading="lazy"/>
                </button>
              );
            })}
          </div>
        )}
        <div ref={sentinelRef} style={{ height:1 }}/>
        {loadingMore && <div style={{ textAlign:'center', padding:'8px 0', color:'var(--text-faint)', fontSize:11 }}>Loading more…</div>}
      </div>
    </div>
  );
}

// ── Custom slider ─────────────────────────────────────────────────────────────
function IeSlider({ min, max, value, onChange, onCommit, formatValue }) {
  const trackRef = React.useRef(null);
  const [active, setActive] = React.useState(false);
  const lastValRef = React.useRef(value);

  const pct = Math.max(0, Math.min(1, (value - min) / (max - min)));
  const label = formatValue ? formatValue(value) : String(value);

  const seek = (ev) => {
    const rect = trackRef.current.getBoundingClientRect();
    const ratio = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));
    const v = Math.round(min + ratio * (max - min));
    lastValRef.current = v;
    onChange(v);
  };

  const onMouseDown = (e) => {
    if (e.button !== 0) return;
    e.preventDefault();
    setActive(true);
    seek(e);
    const move = (ev) => seek(ev);
    const up = () => { setActive(false); if (onCommit) onCommit(lastValRef.current); window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };

  return (
    <div style={{ paddingBottom:4, userSelect:'none' }}
      onMouseEnter={() => setActive(true)} onMouseLeave={() => setActive(false)}>
      {/* Track */}
      <div ref={trackRef} onMouseDown={onMouseDown}
        style={{ position:'relative', height:4, borderRadius:2, background:'var(--border)', cursor:'pointer' }}>
        <div style={{ position:'absolute', left:0, top:0, height:'100%', width:`${pct*100}%`, background:'var(--accent)', borderRadius:2, pointerEvents:'none' }}/>
        <div style={{ position:'absolute', top:'50%', left:`${pct*100}%`, transform:'translate(-50%,-50%)', width:14, height:14, borderRadius:'50%', background:'var(--accent)', boxShadow:'0 0 0 3px rgba(51,92,255,0.18)', pointerEvents:'none' }}/>
        {/* Tooltip — inside track so bottom:100% = just above the track */}
        <div style={{ position:'absolute', bottom:'calc(100% + 8px)', left:`${pct*100}%`, transform:'translateX(-50%)', pointerEvents:'none', opacity: active ? 1 : 0, transition:'opacity 120ms' }}>
          <div style={{ background:'#0E121B', color:'#fff', fontSize:11, fontWeight:700, padding:'3px 8px', borderRadius:5, whiteSpace:'nowrap', letterSpacing:'0.01em', position:'relative' }}>
            {label}
            <div style={{ position:'absolute', top:'100%', left:'50%', transform:'translateX(-50%)', width:0, height:0, borderLeft:'4px solid transparent', borderRight:'4px solid transparent', borderTop:'4px solid #0E121B' }}/>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────

function ImageEditorV1() {
  // ── Project management ────────────────────────────────────────────────────
  const [projects, setProjects] = React.useState(() => ieLoadProjects());
  const [activeProjectId, setActiveProjectId] = React.useState(null); // null = home screen

  // ── Canvas config ─────────────────────────────────────────────────────────
  const [canvasSize, setCanvasSize] = React.useState({ w: 1080, h: 1080 });
  const [canvasBg, setCanvasBg] = React.useState('#FFFFFF');
  const [canvasName, setCanvasName] = React.useState('Untitled');
  const [editingName, setEditingName] = React.useState(false);
  const [showPresets, setShowPresets] = React.useState(false);

  // ── Elements ───────────────────────────────────────────────────────────────
  const [elements, setElements] = React.useState([]);
  const [selectedIds, setSelectedIds] = React.useState(new Set());
  // Tracks active drag listeners so stale ones are always cleaned before a new drag starts
  const activeDragRef = React.useRef(null);
  const [editingTextId, setEditingTextId] = React.useState(null);

  // ── Context menu ──────────────────────────────────────────────────────────
  const [ctxMenu, setCtxMenu] = React.useState(null); // { x, y, elId } | null

  const openCtxMenu = (e, el) => {
    e.preventDefault();
    e.stopPropagation();
    if (!selectedIds.has(el.id)) setSelectedIds(new Set([el.id]));
    setCtxMenu({ x: e.clientX, y: e.clientY, elId: el.id });
  };
  const closeCtxMenu = () => setCtxMenu(null);

  // ── Uploaded images library ───────────────────────────────────────────────
  const [uploadedImages, setUploadedImages] = React.useState([]);
  const [removingBgIds, setRemovingBgIds] = React.useState(new Set());
  const [bgRemovedIds, setBgRemovedIds] = React.useState(new Set());
  const bgCacheRef = React.useRef({}); // { elId: { originalSrc, maskedSrc } }



  // ── Tool & panel state ────────────────────────────────────────────────────
  const [tool, setTool] = React.useState('select');
  const [activePanel, setActivePanel] = React.useState(null); // 'images' | null
  const [shapeDropOpen, setShapeDropOpen] = React.useState(false);
  const [lastShape, setLastShape] = React.useState('rect'); // last used shape type
  const [penPoints, setPenPoints] = React.useState(null);
  const [shapePreview, setShapePreview] = React.useState(null);

  // ── Pen / shape defaults ───────────────────────────────────────────────────
  const [penColor, setPenColor] = React.useState('#0E121B');
  const [penWidth, setPenWidth] = React.useState(3);
  const [fillColor, setFillColor] = React.useState('#335CFF');
  const [shapeStrokeColor, setShapeStrokeColor] = React.useState('#0E121B');
  const [shapeStrokeWidth, setShapeStrokeWidth] = React.useState(0);
  const [shapeRadius, setShapeRadius] = React.useState(0);
  const [textFontFamily, setTextFontFamily] = React.useState('Inter');
  const [textFontSize, setTextFontSize] = React.useState(32);
  const [textFontWeight, setTextFontWeight] = React.useState('700');
  const [textAlign, setTextAlign] = React.useState('left');
  const [textColor, setTextColor] = React.useState('#0E121B');

  // ── Viewport ───────────────────────────────────────────────────────────────
  const [zoom, setZoom] = React.useState(0.5);
  const [pan, setPan] = React.useState({ x: 0, y: 0 });
  const [panning, setPanning] = React.useState(false);
  const [spacePan, setSpacePan] = React.useState(false);
  const spaceHeldRef = React.useRef(false);

  // ── Snap guides ───────────────────────────────────────────────────────────
  const [guides, setGuides] = React.useState({ x: null, y: null });
  const [marquee, setMarquee] = React.useState(null); // { x, y, w, h } in canvas coords

  // ── History ────────────────────────────────────────────────────────────────
  const historyRef = React.useRef([]);
  const historyIdxRef = React.useRef(-1);
  const viewportRef = React.useRef(null);
  const canvasAreaRef = React.useRef(null);

  // ── History helpers ────────────────────────────────────────────────────────
  const pushHistory = (newEls) => {
    const snap = JSON.parse(JSON.stringify(newEls));
    const truncated = historyRef.current.slice(0, historyIdxRef.current + 1);
    truncated.push(snap);
    if (truncated.length > 100) truncated.shift();
    historyRef.current = truncated;
    historyIdxRef.current = truncated.length - 1;
  };

  const undo = React.useCallback(() => {
    if (historyIdxRef.current <= 0) return;
    historyIdxRef.current--;
    setElements(JSON.parse(JSON.stringify(historyRef.current[historyIdxRef.current])));
    setSelectedIds(new Set());
  }, []);

  const redo = React.useCallback(() => {
    if (historyIdxRef.current >= historyRef.current.length - 1) return;
    historyIdxRef.current++;
    setElements(JSON.parse(JSON.stringify(historyRef.current[historyIdxRef.current])));
    setSelectedIds(new Set());
  }, []);

  React.useEffect(() => { pushHistory([]); }, []);

  // ── Coordinate conversion ─────────────────────────────────────────────────
  const screenToCanvas = (clientX, clientY) => {
    const vp = viewportRef.current;
    if (!vp) return { x: clientX, y: clientY };
    const rect = vp.getBoundingClientRect();
    return {
      x: (clientX - rect.left - rect.width / 2 - pan.x) / zoom + canvasSize.w / 2,
      y: (clientY - rect.top - rect.height / 2 - pan.y) / zoom + canvasSize.h / 2,
    };
  };

  // ── Snap ──────────────────────────────────────────────────────────────────
  const computeSnap = (px, py, w, h, others) => {
    const me = { l: px, r: px + w, cx: px + w / 2, t: py, b: py + h, cy: py + h / 2 };
    let bestDX = EDITOR_SNAP_PX + 1, bestDY = EDITOR_SNAP_PX + 1;
    let sx = px, sy = py, gx = null, gy = null;
    const targets = [...others, { l: 0, r: canvasSize.w, cx: canvasSize.w / 2, t: 0, b: canvasSize.h, cy: canvasSize.h / 2 }];
    for (const o of targets) {
      for (const mk of ['l', 'r', 'cx']) {
        for (const ok of ['l', 'r', 'cx']) {
          const d = o[ok] - me[mk];
          if (Math.abs(d) < bestDX) { bestDX = Math.abs(d); sx = px + d; gx = o[ok]; }
        }
      }
      for (const mk of ['t', 'b', 'cy']) {
        for (const ok of ['t', 'b', 'cy']) {
          const d = o[ok] - me[mk];
          if (Math.abs(d) < bestDY) { bestDY = Math.abs(d); sy = py + d; gy = o[ok]; }
        }
      }
    }
    return { x: bestDX <= EDITOR_SNAP_PX ? sx : px, y: bestDY <= EDITOR_SNAP_PX ? sy : py, gx: bestDX <= EDITOR_SNAP_PX ? gx : null, gy: bestDY <= EDITOR_SNAP_PX ? gy : null };
  };

  // ── Element operations ─────────────────────────────────────────────────────
  const updateEl = (id, patch) =>
    setElements(els => els.map(e => e.id === id ? { ...e, ...patch } : e));

  const addElement = (el) => {
    setElements(prev => {
      const newEls = [...prev, el];
      pushHistory(newEls);
      return newEls;
    });
    setSelectedIds(new Set([el.id]));
  };

  const deleteSelected = React.useCallback(() => {
    if (selectedIds.size === 0) return;
    setElements(els => {
      const newEls = els.filter(e => !selectedIds.has(e.id));
      pushHistory(newEls);
      return newEls;
    });
    setSelectedIds(new Set());
    setEditingTextId(null);
  }, [selectedIds]);

  const duplicateSelected = React.useCallback(() => {
    if (selectedIds.size === 0) return;
    setElements(els => {
      const clones = els
        .filter(e => selectedIds.has(e.id))
        .map(e => ({ ...JSON.parse(JSON.stringify(e)), id: ieUid(), x: (e.x || 0) + 20, y: (e.y || 0) + 20 }));
      const newEls = [...els, ...clones];
      pushHistory(newEls);
      setSelectedIds(new Set(clones.map(c => c.id)));
      return newEls;
    });
  }, [selectedIds]);

  const bringForward = (id) => setElements(els => { const i = els.findIndex(e => e.id === id); if (i >= els.length - 1) return els; const a = [...els]; [a[i], a[i+1]] = [a[i+1], a[i]]; pushHistory(a); return a; });
  const sendBackward = (id) => setElements(els => { const i = els.findIndex(e => e.id === id); if (i <= 0) return els; const a = [...els]; [a[i], a[i-1]] = [a[i-1], a[i]]; pushHistory(a); return a; });
  const bringToFront = (id) => setElements(els => { const el = els.find(e => e.id === id); if (!el) return els; const a = [...els.filter(e => e.id !== id), el]; pushHistory(a); return a; });
  const sendToBack = (id) => setElements(els => { const el = els.find(e => e.id === id); if (!el) return els; const a = [el, ...els.filter(e => e.id !== id)]; pushHistory(a); return a; });

  const bringToFrontMulti = (ids) => setElements(els => { const s = new Set(ids); const a = [...els.filter(e => !s.has(e.id)), ...els.filter(e => s.has(e.id))]; pushHistory(a); return a; });
  const sendToBackMulti = (ids) => setElements(els => { const s = new Set(ids); const a = [...els.filter(e => s.has(e.id)), ...els.filter(e => !s.has(e.id))]; pushHistory(a); return a; });
  const bringForwardMulti = (ids) => setElements(els => {
    const s = new Set(ids); const a = [...els];
    const sel = a.reduce((acc, e, i) => { if (s.has(e.id)) acc.push(i); return acc; }, []);
    if (!sel.length) return els;
    const topIdx = sel[sel.length - 1];
    let swapWith = -1;
    for (let i = topIdx + 1; i < a.length; i++) { if (!s.has(a[i].id)) { swapWith = i; break; } }
    if (swapWith === -1) return els;
    const [el] = a.splice(swapWith, 1);
    a.splice(sel[0], 0, el);
    pushHistory(a); return a;
  });
  const sendBackwardMulti = (ids) => setElements(els => {
    const s = new Set(ids); const a = [...els];
    const sel = a.reduce((acc, e, i) => { if (s.has(e.id)) acc.push(i); return acc; }, []);
    if (!sel.length) return els;
    const botIdx = sel[0];
    let swapWith = -1;
    for (let i = botIdx - 1; i >= 0; i--) { if (!s.has(a[i].id)) { swapWith = i; break; } }
    if (swapWith === -1) return els;
    const [el] = a.splice(swapWith, 1);
    a.splice(sel[sel.length - 1], 0, el);
    pushHistory(a); return a;
  });

  // ── Element drag ──────────────────────────────────────────────────────────
  const onElementMouseDown = (e, el) => {
    if (e.button !== 0) return;
    if (tool !== 'select') return; // let the canvas handler place the new element
    e.stopPropagation();
    e.preventDefault();
    setEditingTextId(null);
    setShapeDropOpen(false);

    if (e.shiftKey || e.ctrlKey) {
      setSelectedIds(s => { const n = new Set(s); n.has(el.id) ? n.delete(el.id) : n.add(el.id); return n; });
      return;
    }

    let activeIds;
    if (selectedIds.has(el.id) && selectedIds.size > 1) activeIds = [...selectedIds];
    else { setSelectedIds(new Set([el.id])); activeIds = [el.id]; }

    const startX = e.clientX, startY = e.clientY;
    const origPosSync = new Map();
    elements.forEach(elem => {
      if (!activeIds.includes(elem.id)) return;
      if (elem.type === 'pen') origPosSync.set(elem.id, { points: elem.points.map(p => ({ ...p })) });
      else origPosSync.set(elem.id, { x: elem.x ?? 0, y: elem.y ?? 0 });
    });

    const snapOthers = elements.filter(x => !activeIds.includes(x.id) && x.type !== 'pen')
      .map(x => ({ l: x.x||0, r:(x.x||0)+(x.w||0), cx:(x.x||0)+(x.w||0)/2, t:x.y||0, b:(x.y||0)+(x.h||0), cy:(x.y||0)+(x.h||0)/2 }));
    const snapEls = activeIds
      .map(id => ({ origPos: origPosSync.get(id), el: elements.find(e => e.id === id) }))
      .filter(({origPos, el}) => origPos && el && el.type !== 'pen');
    const origBBoxX = snapEls.length > 0 ? Math.min(...snapEls.map(({origPos}) => origPos.x)) : 0;
    const origBBoxY = snapEls.length > 0 ? Math.min(...snapEls.map(({origPos}) => origPos.y)) : 0;
    const origBBoxW = snapEls.length > 0 ? Math.max(...snapEls.map(({origPos, el}) => origPos.x + (el.w??0))) - origBBoxX : 0;
    const origBBoxH = snapEls.length > 0 ? Math.max(...snapEls.map(({origPos, el}) => origPos.y + (el.h??0))) - origBBoxY : 0;

    let moved = false, fdx = 0, fdy = 0;
    const onMove = (ev) => {
      let dx = (ev.clientX - startX) / zoom, dy = (ev.clientY - startY) / zoom;
      if (snapEls.length > 0) {
        const snap = computeSnap(origBBoxX + dx, origBBoxY + dy, origBBoxW, origBBoxH, snapOthers);
        dx = snap.x - origBBoxX; dy = snap.y - origBBoxY;
        setGuides({ x: snap.gx, y: snap.gy });
      }
      fdx = dx; fdy = dy; moved = true;
      setElements(els => els.map(e => {
        const o = origPosSync.get(e.id);
        if (!o) return e;
        if (e.type === 'pen') return { ...e, points: o.points.map(p => ({ x: p.x + dx, y: p.y + dy })) };
        return { ...e, x: o.x + dx, y: o.y + dy };
      }));
    };
    const onUp = () => {
      activeDragRef.current = null;
      if (moved) setElements(els => { pushHistory(els); return els; });
      setGuides({ x: null, y: null });
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
    if (activeDragRef.current) {
      window.removeEventListener('mousemove', activeDragRef.current.move);
      window.removeEventListener('mouseup', activeDragRef.current.up);
    }
    activeDragRef.current = { move: onMove, up: onUp };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  };

  // ── Resize handles ─────────────────────────────────────────────────────────
  const onResizeHandle = (e, el, handle) => {
    e.stopPropagation(); e.preventDefault();
    const startX = e.clientX, startY = e.clientY;
    const orig = {
      x: el.x??0, y: el.y??0, w: el.w??100, h: el.h??100, fontSize: el.fontSize??24,
      cropX: el.cropX ?? 0, cropY: el.cropY ?? 0,
      cropScale: el.cropScale ?? (el.naturalW ? Math.max((el.w??100) / el.naturalW, (el.h??100) / el.naturalH) : null),
    };
    const MIN = 20, isCorner = handle.length === 2;
    const snapOthersResize = elements
      .filter(el2 => el2.id !== el.id && el2.type !== 'pen')
      .map(el2 => ({ l: el2.x||0, r:(el2.x||0)+(el2.w||0), cx:(el2.x||0)+(el2.w||0)/2, t:el2.y||0, b:(el2.y||0)+(el2.h||0), cy:(el2.y||0)+(el2.h||0)/2 }));
    const allSnapX = [...snapOthersResize.flatMap(o => [o.l, o.r, o.cx]), 0, canvasSize.w, canvasSize.w/2];
    const allSnapY = [...snapOthersResize.flatMap(o => [o.t, o.b, o.cy]), 0, canvasSize.h, canvasSize.h/2];
    let finalPatch = null;
    const onMove = (ev) => {
      const dx = (ev.clientX - startX) / zoom, dy = (ev.clientY - startY) / zoom;
      let { x, y, w, h } = orig;
      if (handle.includes('e')) w = Math.max(MIN, orig.w + dx);
      if (handle.includes('s')) h = Math.max(MIN, orig.h + dy);
      if (handle.includes('w')) { const nw = Math.max(MIN, orig.w - dx); x = orig.x + (orig.w - nw); w = nw; }
      if (handle.includes('n')) { const nh = Math.max(MIN, orig.h - dy); y = orig.y + (orig.h - nh); h = nh; }

      // Corner + no Shift on images: lock to current box ratio, no crop change
      if (isCorner && el.type === 'image' && !ev.shiftKey) {
        const ar = orig.w / orig.h;
        const scale = (w / orig.w + h / orig.h) / 2;
        const newW = Math.max(MIN, orig.w * scale);
        const newH = newW / ar;
        if (handle.includes('n')) y = orig.y + orig.h - newH;
        if (handle.includes('w')) x = orig.x + orig.w - newW;
        w = newW;
        h = newH;
      }

      // Snap active edges to alignment guides
      let snapGx = null, snapGy = null;
      const nearX = (edge) => allSnapX.reduce((b, t) => Math.abs(t-edge) < Math.abs(b-edge) ? t : b);
      const nearY = (edge) => allSnapY.reduce((b, t) => Math.abs(t-edge) < Math.abs(b-edge) ? t : b);
      if (isCorner && el.type === 'image' && !ev.shiftKey) {
        // AR-locked corner: pick the closer snap axis, derive the other to keep ratio intact
        const ar = orig.w / orig.h;
        let tx = null, txDist = Infinity, ty = null, tyDist = Infinity;
        if (handle.includes('e')) { const t = nearX(x+w); const d = Math.abs(t-(x+w)); if (d <= EDITOR_SNAP_PX) { tx = t; txDist = d; } }
        if (handle.includes('w')) { const t = nearX(x);   const d = Math.abs(t-x);     if (d <= EDITOR_SNAP_PX) { tx = t; txDist = d; } }
        if (handle.includes('s')) { const t = nearY(y+h); const d = Math.abs(t-(y+h)); if (d <= EDITOR_SNAP_PX) { ty = t; tyDist = d; } }
        if (handle.includes('n')) { const t = nearY(y);   const d = Math.abs(t-y);     if (d <= EDITOR_SNAP_PX) { ty = t; tyDist = d; } }
        if (tx != null && (txDist <= tyDist || ty == null)) {
          if (handle.includes('e')) w = tx - x;
          if (handle.includes('w')) { w = orig.x + orig.w - tx; x = tx; }
          h = w / ar;
          if (handle.includes('n')) y = orig.y + orig.h - h;
          snapGx = tx;
        } else if (ty != null) {
          if (handle.includes('s')) h = ty - y;
          if (handle.includes('n')) { h = orig.y + orig.h - ty; y = ty; }
          w = h * ar;
          if (handle.includes('w')) x = orig.x + orig.w - w;
          snapGy = ty;
        }
      } else {
        if (handle.includes('e')) { const t = nearX(x+w); if (Math.abs(t-(x+w)) <= EDITOR_SNAP_PX) { w = t-x; snapGx = t; } }
        if (handle.includes('w')) { const t = nearX(x);   if (Math.abs(t-x)     <= EDITOR_SNAP_PX) { w += x-t; x = t; snapGx = t; } }
        if (handle.includes('s')) { const t = nearY(y+h); if (Math.abs(t-(y+h)) <= EDITOR_SNAP_PX) { h = t-y; snapGy = t; } }
        if (handle.includes('n')) { const t = nearY(y);   if (Math.abs(t-y)     <= EDITOR_SNAP_PX) { h += y-t; y = t; snapGy = t; } }
      }
      setGuides({ x: snapGx, y: snapGy });

      const patch = { x, y, w, h };

      if (el.type === 'image') {
        if (el.naturalW && el.naturalH && orig.cropScale != null) {
          // ── Full pixel-based crop system ──────────────────────────────────────
          // cropScale: how many canvas px per natural image px (render scale)
          // cropX/cropY: rendered-space offset (how many px cropped from left/top)
          // This ensures the image NEVER "resets" its zoom when box ratio changes.
          const natW = el.naturalW, natH = el.naturalH;
          let newCropScale, newCropX = orig.cropX, newCropY = orig.cropY;

          if (isCorner && !ev.shiftKey) {
            // AR-locked corner: scale image uniformly with the box — same content, different size
            const sf = w / orig.w; // both axes share same factor since AR is locked
            newCropScale = orig.cropScale * sf;
            newCropX = orig.cropX * sf;
            newCropY = orig.cropY * sf;
          } else {
            // Side handle OR corner+Shift: crop from the dragged side(s)
            // Scale only increases if the box grows beyond the currently rendered image.
            const minFill = Math.max(w / natW, h / natH);
            newCropScale = Math.max(orig.cropScale, minFill);
            if (handle.includes('e')) newCropX = 0;                             // anchor left
            if (handle.includes('w')) newCropX = natW * newCropScale - w;       // anchor right
            if (handle.includes('s')) newCropY = 0;                             // anchor top
            if (handle.includes('n')) newCropY = natH * newCropScale - h;       // anchor bottom
          }

          patch.cropScale = newCropScale;
          patch.cropX = newCropX;
          patch.cropY = newCropY;
        } else {
          // ── Legacy fallback (no naturalW stored): CSS object-position ─────────
          if (!isCorner || ev.shiftKey) {
            if (handle.includes('e')) patch.cropX = 0;
            if (handle.includes('w')) patch.cropX = 100;
            if (handle.includes('s')) patch.cropY = 0;
            if (handle.includes('n')) patch.cropY = 100;
          }
        }
      }

      if (isCorner && el.type === 'text') patch.fontSize = Math.max(8, Math.min(200, Math.round(orig.fontSize * (w/orig.w + h/orig.h) / 2)));
      finalPatch = patch;
      updateEl(el.id, patch);
    };
    const onUp = () => {
      activeDragRef.current = null;
      if (finalPatch) setElements(els => { const u = els.map(e => e.id === el.id ? { ...e, ...finalPatch } : e); pushHistory(u); return u; });
      setGuides({ x: null, y: null });
      window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp);
    };
    if (activeDragRef.current) {
      window.removeEventListener('mousemove', activeDragRef.current.move);
      window.removeEventListener('mouseup', activeDragRef.current.up);
    }
    activeDragRef.current = { move: onMove, up: onUp };
    window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
  };

  const ResizeHandles = ({ el }) => {
    const cs = { nw:'nwse-resize', n:'ns-resize', ne:'nesw-resize', e:'ew-resize', w:'ew-resize', sw:'nesw-resize', s:'ns-resize', se:'nwse-resize' };
    const dot = (cx, cy, h) => ({ position:'absolute', left:cx-5, top:cy-5, width:10, height:10, borderRadius:3, background:'var(--bg-surface)', border:'1.5px solid var(--accent)', cursor:cs[h], zIndex:20 });
    const x = el.x??0, y = el.y??0, w = el.w??0, h = el.h??0;
    return (<>
      <span style={dot(x,       y,       'nw')} onMouseDown={ev => onResizeHandle(ev, el, 'nw')}/>
      <span style={dot(x+w/2,   y,       'n' )} onMouseDown={ev => onResizeHandle(ev, el, 'n' )}/>
      <span style={dot(x+w,     y,       'ne')} onMouseDown={ev => onResizeHandle(ev, el, 'ne')}/>
      <span style={dot(x+w,     y+h/2,   'e' )} onMouseDown={ev => onResizeHandle(ev, el, 'e' )}/>
      <span style={dot(x+w,     y+h,     'se')} onMouseDown={ev => onResizeHandle(ev, el, 'se')}/>
      <span style={dot(x+w/2,   y+h,     's' )} onMouseDown={ev => onResizeHandle(ev, el, 's' )}/>
      <span style={dot(x,       y+h,     'sw')} onMouseDown={ev => onResizeHandle(ev, el, 'sw')}/>
      <span style={dot(x,       y+h/2,   'w' )} onMouseDown={ev => onResizeHandle(ev, el, 'w' )}/>
    </>);
  };

  const onMultiResizeHandle = (e, handle) => {
    e.stopPropagation(); e.preventDefault();
    const selEls = elements.filter(el => selectedIds.has(el.id) && el.type !== 'pen');
    if (selEls.length === 0) return;
    const bx1 = Math.min(...selEls.map(el => el.x??0));
    const by1 = Math.min(...selEls.map(el => el.y??0));
    const bx2 = Math.max(...selEls.map(el => (el.x??0)+(el.w??0)));
    const by2 = Math.max(...selEls.map(el => (el.y??0)+(el.h??0)));
    const origBox = { x:bx1, y:by1, w:bx2-bx1, h:by2-by1 };
    const boxAR = origBox.w / origBox.h;
    const isCorner = handle.length === 2;
    const origEls = selEls.map(el => ({
      id: el.id,
      rx: ((el.x??0)-bx1) / origBox.w,
      ry: ((el.y??0)-by1) / origBox.h,
      rw: (el.w??0) / origBox.w,
      rh: (el.h??0) / origBox.h,
      origW: el.w??0, origH: el.h??0,
      type: el.type,
      naturalW: el.naturalW, naturalH: el.naturalH,
      cropScale: el.cropScale, cropX: el.cropX??0, cropY: el.cropY??0,
    }));
    const startX = e.clientX, startY = e.clientY;
    const MIN = 10;
    const snapOthersR = elements.filter(el => !selectedIds.has(el.id) && el.type !== 'pen')
      .map(el => ({ l: el.x||0, r:(el.x||0)+(el.w||0), cx:(el.x||0)+(el.w||0)/2, t:el.y||0, b:(el.y||0)+(el.h||0), cy:(el.y||0)+(el.h||0)/2 }));
    const allSnapX = [...snapOthersR.flatMap(o => [o.l, o.r, o.cx]), 0, canvasSize.w, canvasSize.w/2];
    const allSnapY = [...snapOthersR.flatMap(o => [o.t, o.b, o.cy]), 0, canvasSize.h, canvasSize.h/2];
    const onMove = (ev) => {
      const dx = (ev.clientX-startX)/zoom, dy = (ev.clientY-startY)/zoom;
      let { x, y, w, h } = origBox;
      if (handle.includes('e')) w = Math.max(MIN, origBox.w+dx);
      if (handle.includes('s')) h = Math.max(MIN, origBox.h+dy);
      if (handle.includes('w')) { const nw = Math.max(MIN, origBox.w-dx); x = origBox.x+(origBox.w-nw); w = nw; }
      if (handle.includes('n')) { const nh = Math.max(MIN, origBox.h-dy); y = origBox.y+(origBox.h-nh); h = nh; }
      const nearX = (edge) => allSnapX.reduce((b, t) => Math.abs(t-edge) < Math.abs(b-edge) ? t : b);
      const nearY = (edge) => allSnapY.reduce((b, t) => Math.abs(t-edge) < Math.abs(b-edge) ? t : b);
      let snapGx = null, snapGy = null;
      if (isCorner) {
        // First compute the AR-locked position (diagonal projection) — this is the actual element position
        const rawDx = handle.includes('e') ? dx : -dx;
        const rawDy = handle.includes('s') ? dy : -dy;
        const diagSq = origBox.w * origBox.w + origBox.h * origBox.h;
        const proj = diagSq > 0 ? (origBox.w * rawDx + origBox.h * rawDy) / diagSq : 0;
        const scale = Math.max(MIN / origBox.w, 1 + proj);
        w = origBox.w * scale; h = origBox.h * scale;
        x = origBox.x; y = origBox.y;
        if (handle.includes('w')) x = origBox.x + origBox.w - w;
        if (handle.includes('n')) y = origBox.y + origBox.h - h;
        // Then check snap on the actual AR-locked edges
        let tx = null, txDist = Infinity, ty = null, tyDist = Infinity;
        if (handle.includes('e')) { const t = nearX(x+w); const d = Math.abs(t-(x+w)); if (d <= EDITOR_SNAP_PX) { tx = t; txDist = d; } }
        if (handle.includes('w')) { const t = nearX(x);   const d = Math.abs(t-x);     if (d <= EDITOR_SNAP_PX) { tx = t; txDist = d; } }
        if (handle.includes('s')) { const t = nearY(y+h); const d = Math.abs(t-(y+h)); if (d <= EDITOR_SNAP_PX) { ty = t; tyDist = d; } }
        if (handle.includes('n')) { const t = nearY(y);   const d = Math.abs(t-y);     if (d <= EDITOR_SNAP_PX) { ty = t; tyDist = d; } }
        if (tx != null && (txDist <= tyDist || ty == null)) {
          if (handle.includes('e')) w = tx - x;
          if (handle.includes('w')) { w = origBox.x+origBox.w-tx; x = tx; }
          h = w / boxAR; if (handle.includes('n')) y = origBox.y+origBox.h-h;
          snapGx = tx;
        } else if (ty != null) {
          if (handle.includes('s')) h = ty - y;
          if (handle.includes('n')) { h = origBox.y+origBox.h-ty; y = ty; }
          w = h * boxAR; if (handle.includes('w')) x = origBox.x+origBox.w-w;
          snapGy = ty;
        }
      } else {
        if (handle.includes('e')) { const t = nearX(x+w); if (Math.abs(t-(x+w)) <= EDITOR_SNAP_PX) { w = t-x; snapGx = t; } }
        if (handle.includes('w')) { const t = nearX(x);   if (Math.abs(t-x)     <= EDITOR_SNAP_PX) { w = origBox.x+origBox.w-t; x = t; snapGx = t; } }
        if (handle.includes('s')) { const t = nearY(y+h); if (Math.abs(t-(y+h)) <= EDITOR_SNAP_PX) { h = t-y; snapGy = t; } }
        if (handle.includes('n')) { const t = nearY(y);   if (Math.abs(t-y)     <= EDITOR_SNAP_PX) { h = origBox.y+origBox.h-t; y = t; snapGy = t; } }
      }
      setGuides({ x: snapGx, y: snapGy });
      const scaleX = w / origBox.w, scaleY = h / origBox.h;
      setElements(els => els.map(el => {
        const o = origEls.find(oe => oe.id === el.id);
        if (!o) return el;
        const newW = Math.max(1, o.rw * w);
        const newH = Math.max(1, o.rh * h);
        const patch = { x: x+o.rx*w, y: y+o.ry*h, w: newW, h: newH };
        if (el.type === 'image' && o.naturalW && o.naturalH && o.cropScale != null) {
          if (isCorner) {
            // Uniform scale — same image content, different size
            const sf = isCorner ? w/origBox.w : 1;
            patch.cropScale = o.cropScale * sf;
            patch.cropX = o.cropX * sf;
            patch.cropY = o.cropY * sf;
          } else {
            // Side handle: ensure image still fills the new box
            const newCropScale = Math.max(o.cropScale, Math.max(newW/o.naturalW, newH/o.naturalH));
            let newCropX = o.cropX * (newCropScale / o.cropScale);
            let newCropY = o.cropY * (newCropScale / o.cropScale);
            if (handle.includes('e')) newCropX = 0;
            if (handle.includes('w')) newCropX = o.naturalW * newCropScale - newW;
            if (handle.includes('s')) newCropY = 0;
            if (handle.includes('n')) newCropY = o.naturalH * newCropScale - newH;
            patch.cropScale = newCropScale;
            patch.cropX = Math.max(0, newCropX);
            patch.cropY = Math.max(0, newCropY);
          }
        }
        return { ...el, ...patch };
      }));
    };
    const onUp = () => {
      activeDragRef.current = null;
      setElements(els => { pushHistory(els); return els; });
      setGuides({ x: null, y: null });
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
    if (activeDragRef.current) {
      window.removeEventListener('mousemove', activeDragRef.current.move);
      window.removeEventListener('mouseup', activeDragRef.current.up);
    }
    activeDragRef.current = { move: onMove, up: onUp };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  };

  const MultiResizeHandles = () => {
    const selEls = elements.filter(el => selectedIds.has(el.id) && el.type !== 'pen');
    if (selEls.length < 2) return null;
    const x = Math.min(...selEls.map(el => el.x??0));
    const y = Math.min(...selEls.map(el => el.y??0));
    const x2 = Math.max(...selEls.map(el => (el.x??0)+(el.w??0)));
    const y2 = Math.max(...selEls.map(el => (el.y??0)+(el.h??0)));
    const w = x2-x, h = y2-y;
    const cs = { nw:'nwse-resize', n:'ns-resize', ne:'nesw-resize', e:'ew-resize', w:'ew-resize', sw:'nesw-resize', s:'ns-resize', se:'nwse-resize' };
    const dot = (cx, cy, hd) => ({ position:'absolute', left:cx-5, top:cy-5, width:10, height:10, borderRadius:3, background:'var(--bg-surface)', border:'1.5px solid var(--accent)', cursor:cs[hd], zIndex:20 });
    return (<>
      <div style={{ position:'absolute', left:x, top:y, width:w, height:h, border:'1.5px dashed var(--accent)', pointerEvents:'none', zIndex:19 }}/>
      <span style={dot(x,     y,     'nw')} onMouseDown={ev => onMultiResizeHandle(ev,'nw')}/>
      <span style={dot(x+w/2, y,     'n' )} onMouseDown={ev => onMultiResizeHandle(ev,'n' )}/>
      <span style={dot(x+w,   y,     'ne')} onMouseDown={ev => onMultiResizeHandle(ev,'ne')}/>
      <span style={dot(x+w,   y+h/2, 'e' )} onMouseDown={ev => onMultiResizeHandle(ev,'e' )}/>
      <span style={dot(x+w,   y+h,   'se')} onMouseDown={ev => onMultiResizeHandle(ev,'se')}/>
      <span style={dot(x+w/2, y+h,   's' )} onMouseDown={ev => onMultiResizeHandle(ev,'s' )}/>
      <span style={dot(x,     y+h,   'sw')} onMouseDown={ev => onMultiResizeHandle(ev,'sw')}/>
      <span style={dot(x,     y+h/2, 'w' )} onMouseDown={ev => onMultiResizeHandle(ev,'w' )}/>
    </>);
  };

  // ── Canvas mouse down (tool actions) ──────────────────────────────────────
  const onCanvasMouseDown = (e) => {
    if (e.button !== 0) return;
    setShapeDropOpen(false);

    if (tool === 'select') {
      setSelectedIds(new Set()); setEditingTextId(null);
      const startPos = screenToCanvas(e.clientX, e.clientY);
      const onMove = (ev) => {
        const cur = screenToCanvas(ev.clientX, ev.clientY);
        setMarquee({ x: Math.min(startPos.x, cur.x), y: Math.min(startPos.y, cur.y), w: Math.abs(cur.x - startPos.x), h: Math.abs(cur.y - startPos.y) });
      };
      const onUp = () => {
        setMarquee(prev => {
          if (prev && prev.w > 4 && prev.h > 4) {
            const mx1 = prev.x, my1 = prev.y, mx2 = prev.x + prev.w, my2 = prev.y + prev.h;
            const hit = elements.filter(el => {
              if (el.type === 'pen') {
                const pts = el.points || [];
                const bx1 = Math.min(...pts.map(p => p.x)), by1 = Math.min(...pts.map(p => p.y));
                const bx2 = Math.max(...pts.map(p => p.x)), by2 = Math.max(...pts.map(p => p.y));
                return bx1 < mx2 && bx2 > mx1 && by1 < my2 && by2 > my1;
              }
              const ex1 = el.x ?? 0, ey1 = el.y ?? 0, ex2 = ex1 + (el.w ?? 0), ey2 = ey1 + (el.h ?? 0);
              return ex1 < mx2 && ex2 > mx1 && ey1 < my2 && ey2 > my1;
            });
            setSelectedIds(new Set(hit.map(el => el.id)));
          }
          return null;
        });
        activeDragRef.current = null;
        window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp);
      };
      if (activeDragRef.current) {
        window.removeEventListener('mousemove', activeDragRef.current.move);
        window.removeEventListener('mouseup', activeDragRef.current.up);
      }
      activeDragRef.current = { move: onMove, up: onUp };
      window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
      return;
    }

    const pos = screenToCanvas(e.clientX, e.clientY);

    if (tool === 'text') {
      const id = ieUid();
      addElement({ id, type:'text', x:pos.x, y:pos.y, w:300, h:60, text:'Add text here', fontSize:textFontSize, fontWeight:textFontWeight, fontFamily:textFontFamily, color:textColor, textAlign:textAlign, opacity:1 });
      setTimeout(() => setEditingTextId(id), 50);
      return;
    }

    if (tool === 'rect' || tool === 'circle' || tool === 'triangle') {
      const startPos = { x: pos.x, y: pos.y };
      let currentPreview = { x:pos.x, y:pos.y, w:0, h:0 };
      setShapePreview(currentPreview);
      const onMove = (ev) => {
        const cur = screenToCanvas(ev.clientX, ev.clientY);
        currentPreview = { x:Math.min(startPos.x,cur.x), y:Math.min(startPos.y,cur.y), w:Math.abs(cur.x-startPos.x), h:Math.abs(cur.y-startPos.y) };
        setShapePreview(currentPreview);
      };
      const onUp = () => {
        if (currentPreview.w > 5 && currentPreview.h > 5) {
          addElement({ id:ieUid(), type:tool, x:currentPreview.x, y:currentPreview.y, w:currentPreview.w, h:currentPreview.h, fill:fillColor, stroke:shapeStrokeWidth>0?shapeStrokeColor:'transparent', strokeWidth:shapeStrokeWidth, opacity:1, borderRadius:tool==='rect'?shapeRadius:0 });
          _ieAddCustomColor(fillColor);
        }
        setShapePreview(null);
        window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp);
      };
      window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
      return;
    }

    if (tool === 'pen') {
      const startP = screenToCanvas(e.clientX, e.clientY);
      let pts = [startP];
      setPenPoints([startP]);
      const onMove = (ev) => { const p = screenToCanvas(ev.clientX, ev.clientY); pts = [...pts, p]; setPenPoints([...pts]); };
      const onUp = () => {
        if (pts.length > 1) { addElement({ id:ieUid(), type:'pen', points:pts, stroke:penColor, strokeWidth:penWidth, opacity:1 }); _ieAddCustomColor(penColor); }
        setPenPoints(null);
        window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp);
      };
      window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
      return;
    }
  };

  // ── Viewport wheel ────────────────────────────────────────────────────────
  React.useEffect(() => {
    const vp = viewportRef.current;
    if (!vp) return;
    const onWheel = (e) => {
      e.preventDefault();
      if (e.ctrlKey || e.metaKey) {
        const factor = e.deltaY < 0 ? 1.1 : 0.91;
        setZoom(z => Math.min(5, Math.max(0.05, z * factor)));
      } else {
        setPan(p => ({ x: p.x - e.deltaX, y: p.y - e.deltaY }));
      }
    };
    vp.addEventListener('wheel', onWheel, { passive: false });
    return () => vp.removeEventListener('wheel', onWheel);
  }, []);

  // ── Keyboard shortcuts ────────────────────────────────────────────────────
  React.useEffect(() => {
    const onKeyDown = (e) => {
      const tag = e.target.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA') return;
      if (e.key === ' ') { e.preventDefault(); spaceHeldRef.current = true; setSpacePan(true); }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') { e.preventDefault(); e.shiftKey ? redo() : undo(); }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'y') { e.preventDefault(); redo(); }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'd') { e.preventDefault(); duplicateSelected(); }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'a') { e.preventDefault(); setSelectedIds(new Set(elements.map(el => el.id))); }
      if (e.key === 'Delete' || e.key === 'Backspace') { if (editingTextId) return; deleteSelected(); }
      if (e.key === 'Escape') { setSelectedIds(new Set()); setEditingTextId(null); setTool('select'); setShapeDropOpen(false); setActivePanel(null); }
      if (e.key.toLowerCase() === 'v') setTool('select');
      if (e.key.toLowerCase() === 't') setTool('text');
      if (e.key.toLowerCase() === 'r') { setTool('rect'); setLastShape('rect'); }
      if (e.key.toLowerCase() === 'c') { e.preventDefault(); setTool('circle'); setLastShape('circle'); }
      if (e.key.toLowerCase() === 'p') setTool('pen');
      if (e.key.toLowerCase() === 'i') document.getElementById('ie-img-upload')?.click();
      if (['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(e.key)) {
        if (selectedIds.size === 0) return;
        e.preventDefault();
        const delta = e.shiftKey ? 10 : 1;
        const dx = e.key === 'ArrowLeft' ? -delta : e.key === 'ArrowRight' ? delta : 0;
        const dy = e.key === 'ArrowUp' ? -delta : e.key === 'ArrowDown' ? delta : 0;
        setElements(els => { const u = els.map(el => { if (!selectedIds.has(el.id)) return el; if (el.type === 'pen') return { ...el, points: el.points.map(p => ({ x:p.x+dx, y:p.y+dy })) }; return { ...el, x:(el.x||0)+dx, y:(el.y||0)+dy }; }); pushHistory(u); return u; });
      }
    };
    const onKeyUp = (e) => { if (e.key === ' ') { spaceHeldRef.current = false; setSpacePan(false); } };
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); };
  }, [undo, redo, deleteSelected, duplicateSelected, editingTextId, elements, selectedIds]);

  // ── Image upload ─────────────────────────────────────────────────────────
  const handleReplaceImage = (file, elId, currentEl) => {
    const reader = new FileReader();
    reader.onload = (ev) => {
      const src = ev.target.result;
      const img = new Image();
      img.onload = () => {
        const cropScale = Math.max(currentEl.w / img.width, currentEl.h / img.height);
        setElements(els => { const u = els.map(e => e.id === elId ? { ...e, src, naturalW: img.width, naturalH: img.height, cropScale, cropX: 0, cropY: 0 } : e); pushHistory(u); return u; });
        setUploadedImages(prev => [{ id: ieUid(), src, name: file.name, w: img.width, h: img.height }, ...prev]);
      };
      img.src = src;
    };
    reader.readAsDataURL(file);
  };



  const handleRemoveBg = async (elId, src) => {
    setRemovingBgIds(prev => new Set([...prev, elId]));
    try {
      while (!window.__imglyRemoveBg) await new Promise(r => setTimeout(r, 100));
      const blob = await window.__imglyRemoveBg(src);
      const maskedSrc = URL.createObjectURL(blob);
      bgCacheRef.current[elId] = { originalSrc: src, maskedSrc };
      setElements(els => { const u = els.map(e => e.id === elId ? { ...e, src: maskedSrc } : e); pushHistory(u); return u; });
      setBgRemovedIds(prev => new Set([...prev, elId]));
    } catch(err) {
      console.error('Background removal failed:', err);
      alert('Background removal failed: ' + err.message);
    } finally {
      setRemovingBgIds(prev => { const s = new Set(prev); s.delete(elId); return s; });
    }
  };

  const handleRestoreBg = (elId) => {
    const cache = bgCacheRef.current[elId];
    if (!cache) return;
    setElements(els => els.map(e => e.id === elId ? { ...e, src: cache.originalSrc } : e));
    setBgRemovedIds(prev => { const s = new Set(prev); s.delete(elId); return s; });
  };

  const handleReRemoveBg = (elId) => {
    const cache = bgCacheRef.current[elId];
    if (!cache) return;
    setElements(els => els.map(e => e.id === elId ? { ...e, src: cache.maskedSrc } : e));
    setBgRemovedIds(prev => new Set([...prev, elId]));
  };

  const handleImageUpload = (file) => {
    const reader = new FileReader();
    reader.onload = (ev) => {
      const src = ev.target.result;
      const img = new Image();
      img.onload = () => {
        const maxW = Math.min(img.width, canvasSize.w * 0.7);
        const scale = maxW / img.width;
        const w = Math.round(img.width * scale), h = Math.round(img.height * scale);
        addElement({ id:ieUid(), type:'image', x:Math.round(canvasSize.w/2-w/2), y:Math.round(canvasSize.h/2-h/2), w, h, src, opacity:1, borderRadius:0, naturalW: img.width, naturalH: img.height, cropScale: scale, cropX: 0, cropY: 0 });
        setTool('select');
        setActivePanel(null);
      };
      img.src = src;
      // Save to library (newest first)
      setUploadedImages(prev => [{ id: ieUid(), src, name: file.name, w: 0, h: 0 }, ...prev]);
    };
    reader.readAsDataURL(file);
  };

  const handleAddUploaded = (img) => {
    const el = new Image();
    el.onload = () => {
      const maxW = Math.min(el.width, canvasSize.w * 0.7);
      const scale = maxW / el.width;
      const w = Math.round(el.width * scale), h = Math.round(el.height * scale);
      addElement({ id:ieUid(), type:'image', x:Math.round(canvasSize.w/2-w/2), y:Math.round(canvasSize.h/2-h/2), w, h, src:img.src, opacity:1, borderRadius:0 });
      setTool('select');
    };
    el.src = img.src;
  };

  // ── Fit to screen ─────────────────────────────────────────────────────────
  const fitToScreen = React.useCallback(() => {
    const vp = viewportRef.current;
    if (!vp) return;
    const rect = vp.getBoundingClientRect();
    setZoom(Math.min((rect.width - 80) / canvasSize.w, (rect.height - 80) / canvasSize.h, 1));
    setPan({ x: 0, y: 0 });
  }, [canvasSize]);
  React.useEffect(() => { setTimeout(fitToScreen, 80); }, [canvasSize]);

  // ── SVG pen path ──────────────────────────────────────────────────────────
  const penPath = (pts) => {
    if (!pts || pts.length < 2) return '';
    return `M ${pts[0].x} ${pts[0].y}` + pts.slice(1).map(p => ` L ${p.x} ${p.y}`).join('');
  };

  // ── Render element ────────────────────────────────────────────────────────
  const elCursor = panning||spacePan ? 'grabbing' : tool==='select' ? 'move' : tool==='text' ? 'text' : (tool==='pen'||tool==='rect'||tool==='circle'||tool==='triangle') ? 'crosshair' : 'default';

  const renderElement = (el) => {
    const sel = selectedIds.has(el.id), editing = editingTextId === el.id, mv = tool === 'select';
    const onMD = (e) => onElementMouseDown(e, el);
    const onDbl = (e) => { e.stopPropagation(); if (el.type === 'text') setEditingTextId(el.id); };
    const onCM = (e) => openCtxMenu(e, el);
    const selStyle = { outline: sel && !editing ? '2px solid var(--accent)' : 'none', outlineOffset: 1 };

    if (el.type === 'text') return (
      <div key={el.id} onMouseDown={onMD} onDoubleClick={onDbl} onContextMenu={onCM}
        style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, opacity:el.opacity??1, cursor:elCursor, boxSizing:'border-box', userSelect:'none', overflow:'hidden', ...selStyle }}>
        {editing ? (
          <textarea autoFocus defaultValue={el.text}
            onBlur={ev => { setElements(els => { const u = els.map(x => x.id === el.id ? { ...x, text:ev.target.value } : x); pushHistory(u); return u; }); setEditingTextId(null); }}
            onChange={ev => updateEl(el.id, { text: ev.target.value })}
            onKeyDown={ev => { ev.stopPropagation(); if (ev.key === 'Escape') ev.target.blur(); }}
            onMouseDown={ev => ev.stopPropagation()}
            style={{ width:'100%', height:'100%', background:'transparent', border:'none', outline:'none', resize:'none', padding:0, margin:0, fontSize:el.fontSize??32, fontWeight:el.fontWeight??'700', fontFamily:el.fontFamily??'Inter', color:el.color??'#0E121B', textAlign:el.textAlign??'left', lineHeight:1.25 }}/>
        ) : (
          <div style={{ width:'100%', height:'100%', fontSize:el.fontSize??32, fontWeight:el.fontWeight??'700', fontFamily:el.fontFamily??'Inter', color:el.color??'#0E121B', textAlign:el.textAlign??'left', lineHeight:1.25, whiteSpace:'pre-wrap', wordBreak:'break-word', overflow:'hidden' }}>
            {el.text || 'Add text'}
          </div>
        )}
      </div>
    );

    if (el.type === 'image') {
      const hasCropSystem = el.naturalW && el.naturalH && el.cropScale != null;
      return (
        <div key={el.id} onMouseDown={onMD} onContextMenu={onCM}
          style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, opacity:el.opacity??1, cursor:elCursor, userSelect:'none', borderRadius:el.borderRadius??0, overflow:'hidden', ...selStyle }}>
          {hasCropSystem ? (
            <img draggable={false} src={el.src} style={{
              position: 'absolute',
              width: el.naturalW * el.cropScale,
              height: el.naturalH * el.cropScale,
              left: -(el.cropX ?? 0),
              top: -(el.cropY ?? 0),
              display: 'block',
              pointerEvents: 'none',
            }}/>
          ) : (
            <img draggable={false} src={el.src} style={{ width:'100%', height:'100%', objectFit:'cover', objectPosition:`${el.cropX ?? 50}% ${el.cropY ?? 50}%`, display:'block', pointerEvents:'none' }}/>
          )}
        </div>
      );
    }

    if (el.type === 'rect') return (
      <div key={el.id} onMouseDown={onMD} onContextMenu={onCM}
        style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, background:el.fill??'#335CFF', borderRadius:el.borderRadius??0, border:el.strokeWidth?`${el.strokeWidth}px solid ${el.stroke}`:'none', opacity:el.opacity??1, cursor:elCursor, userSelect:'none', ...selStyle }}/>
    );

    if (el.type === 'circle') return (
      <div key={el.id} onMouseDown={onMD} onContextMenu={onCM}
        style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, background:el.fill??'#335CFF', borderRadius:'50%', border:el.strokeWidth?`${el.strokeWidth}px solid ${el.stroke}`:'none', opacity:el.opacity??1, cursor:elCursor, userSelect:'none', ...selStyle }}/>
    );

    if (el.type === 'triangle') return (
      <div key={el.id} onMouseDown={onMD} onContextMenu={onCM}
        style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, opacity:el.opacity??1, cursor:elCursor, userSelect:'none', ...selStyle }}>
        <svg width={el.w} height={el.h} style={{ display:'block', pointerEvents:'none' }}>
          <polygon points={`${el.w/2},0 ${el.w},${el.h} 0,${el.h}`} fill={el.fill??'#335CFF'} stroke={el.strokeWidth?el.stroke:'none'} strokeWidth={el.strokeWidth??0}/>
        </svg>
      </div>
    );

    if (el.type === 'pen') {
      const pts = el.points || [];
      if (pts.length < 2) return null;
      const xs = pts.map(p => p.x), ys = pts.map(p => p.y);
      const x0 = Math.min(...xs), y0 = Math.min(...ys), x1 = Math.max(...xs), y1 = Math.max(...ys);
      const pad = (el.strokeWidth??3) + 6;
      const svgW = x1 - x0 + pad * 2, svgH = y1 - y0 + pad * 2;
      const d = penPath(pts.map(p => ({ x: p.x - x0 + pad, y: p.y - y0 + pad })));
      return (
        <div key={el.id} onMouseDown={ev => onElementMouseDown(ev, el)} onContextMenu={onCM}
          style={{ position:'absolute', left:x0-pad, top:y0-pad, width:svgW, height:svgH, opacity:el.opacity??1, cursor:elCursor, outline:sel?'1px dashed var(--accent)':'none', outlineOffset:3 }}>
          <svg width={svgW} height={svgH} style={{ display:'block' }}>
            <path d={d} stroke={el.stroke??'#0E121B'} strokeWidth={el.strokeWidth??3} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </div>
      );
    }

    if (el.type === 'icon') {
      const [prefix, name] = (el.iconId || 'mdi:circle').split(':');
      const colorParam = encodeURIComponent(el.color || '#0E121B');
      return (
        <div key={el.id} onMouseDown={onMD} onContextMenu={onCM}
          style={{ position:'absolute', left:el.x, top:el.y, width:el.w, height:el.h, opacity:el.opacity??1, cursor:elCursor, userSelect:'none', ...selStyle }}>
          <img draggable={false} src={`https://api.iconify.design/${prefix}/${name}.svg?color=${colorParam}`}
            style={{ width:'100%', height:'100%', objectFit:'contain', display:'block', pointerEvents:'none' }}/>
        </div>
      );
    }

    return null;
  };

  // ── Properties panel ──────────────────────────────────────────────────────
  const selectedEl = selectedIds.size === 1 ? elements.find(e => selectedIds.has(e.id)) : null;

  const commitEl = (id, patch) => setElements(els => { const u = els.map(e => e.id === id ? { ...e, ...patch } : e); pushHistory(u); return u; });

  const renderProperties = () => {
    if (selectedIds.size > 1) return (
      <div style={{ padding: '16px 12px' }}>
        <div style={pLabelSt}>{selectedIds.size} elements</div>
        <button onClick={duplicateSelected} style={pBtnSt}>Duplicate all</button>
        <button onClick={deleteSelected} style={{ ...pBtnSt, marginTop:6, color:'#EF4444' }}>Delete all</button>
      </div>
    );
    if (!selectedEl) return (
      <div style={{ padding:'20px 14px', color:'var(--text-faint)', fontSize:13, lineHeight:1.5 }}>
        <div style={{ fontWeight:600, color:'var(--text-muted)', marginBottom:8 }}>No selection</div>
        Select an element to edit its properties.
      </div>
    );
    const el = selectedEl;
    const up = (patch) => updateEl(el.id, patch);
    const cm = (patch) => commitEl(el.id, patch);
    const typeLabel = { text:'Text', image:'Image', rect:'Rectangle', circle:'Circle', triangle:'Triangle', pen:'Drawing', icon:'Icon' }[el.type] || el.type;

    return (
      <div>
        <div style={{ padding:'10px 12px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span style={pLabelSt}>{typeLabel}</span>
          <span style={{ fontSize:10, color:'var(--text-faint)' }}>Right-click for actions</span>
        </div>
        {el.type !== 'pen' && (
          <div style={pSectionSt}>
            <div style={pLabelSt}>Position & Size</div>
            <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:6 }}>
              {[['X','x'],['Y','y'],['W','w'],['H','h']].map(([lbl,key]) => (
                <label key={key} style={{ display:'flex', flexDirection:'column', gap:3 }}>
                  <span style={{ fontSize:10, color:'var(--text-faint)', fontWeight:600 }}>{lbl}</span>
                  <input type="number" value={Math.round(el[key]??0)} onChange={e=>up({[key]:Number(e.target.value)})} onBlur={e=>cm({[key]:Number(e.target.value)})} style={numInpSt}/>
                </label>
              ))}
            </div>
          </div>
        )}
        <div style={pSectionSt}>
          <div style={pLabelSt}>Opacity</div>
          <IeSlider min={0} max={100} value={Math.round((el.opacity??1)*100)} onChange={v=>up({opacity:v/100})} onCommit={v=>cm({opacity:v/100})} formatValue={v=>`${v}%`}/>
        </div>
        {el.type === 'text' && (
          <div style={pSectionSt}>
            <div style={pLabelSt}>Typography</div>
            <select value={el.fontFamily??'Inter'} onChange={e=>cm({fontFamily:e.target.value})} style={selSt}>
              {FONT_FAMILIES.map(f => <option key={f} value={f}>{f}</option>)}
            </select>
            <div style={{ display:'flex', gap:6, marginTop:6 }}>
              <div style={{ flex:1 }}>
                <div style={{ fontSize:10, color:'var(--text-faint)', fontWeight:600, marginBottom:2 }}>Size</div>
                <input type="number" min={6} max={400} value={el.fontSize??32} onChange={e=>up({fontSize:Number(e.target.value)})} onBlur={e=>cm({fontSize:Number(e.target.value)})} style={numInpSt}/>
              </div>
              <div style={{ flex:1 }}>
                <div style={{ fontSize:10, color:'var(--text-faint)', fontWeight:600, marginBottom:2 }}>Weight</div>
                <select value={el.fontWeight??'700'} onChange={e=>cm({fontWeight:e.target.value})} style={selSt}>
                  <option value="400">Regular</option><option value="500">Medium</option>
                  <option value="600">Semibold</option><option value="700">Bold</option><option value="800">Extra Bold</option>
                </select>
              </div>
            </div>
            <div style={{ display:'flex', gap:4, marginTop:8 }}>
              {['left','center','right'].map(v => (
                <button key={v} onClick={()=>cm({textAlign:v})} style={{ flex:1, height:28, borderRadius:5, border:'none', cursor:'pointer', fontSize:11, fontWeight:500, background:(el.textAlign??'left')===v?'var(--accent-soft)':'var(--bg-soft)', color:(el.textAlign??'left')===v?'var(--accent)':'var(--text-muted)' }}>
                  {v.charAt(0).toUpperCase()+v.slice(1)}
                </button>
              ))}
            </div>
            <div style={{ marginTop:10 }}>
              <div style={pLabelSt}>Color</div>
              <IeColorPicker value={el.color??'#0E121B'} onChange={c=>up({color:c})} onCommit={c=>cm({color:c})}/>
            </div>
          </div>
        )}
        {(el.type==='rect'||el.type==='circle'||el.type==='triangle') && (
          <>
            <div style={pSectionSt}>
              <div style={pLabelSt}>Fill</div>
              <IeColorPicker value={el.fill??'#335CFF'} onChange={c=>up({fill:c})} onCommit={c=>cm({fill:c})}/>
            </div>
            {el.type==='rect' && (
              <div style={pSectionSt}>
                <div style={pLabelSt}>Radius</div>
                <IeSlider min={0} max={Math.min(el.w,el.h)/2|0} value={el.borderRadius??0} onChange={v=>up({borderRadius:v})} onCommit={v=>cm({borderRadius:v})} formatValue={v=>`${v}px`}/>
              </div>
            )}
            <div style={pSectionSt}>
              <div style={pLabelSt}>Border color</div>
              <IeColorPicker value={el.stroke&&el.stroke!=='transparent'?el.stroke:'#000000'} onChange={c=>up({stroke:c})} onCommit={c=>cm({stroke:c})}/>
            </div>
            <div style={pSectionSt}>
              <div style={pLabelSt}>Border width</div>
              <IeSlider min={0} max={40} value={el.strokeWidth??0} onChange={v=>up({stroke: v>0?(el.stroke&&el.stroke!=='transparent'?el.stroke:'#000000'):'transparent', strokeWidth:v})} onCommit={v=>cm({stroke: v>0?(el.stroke&&el.stroke!=='transparent'?el.stroke:'#000000'):'transparent', strokeWidth:v})} formatValue={v=>v===0?'None':`${v}px`}/>
            </div>
          </>
        )}
        {el.type==='pen' && (
          <div style={pSectionSt}>
            <div style={pLabelSt}>Stroke</div>
            <IeColorPicker value={el.stroke??'#0E121B'} onChange={c=>up({stroke:c})} onCommit={c=>cm({stroke:c})}/>
            <div style={{ marginTop:10 }}>
              <div style={pLabelSt}>Thickness</div>
              <IeSlider min={1} max={60} value={el.strokeWidth??3} onChange={v=>up({strokeWidth:v})} onCommit={v=>cm({strokeWidth:v})} formatValue={v=>`${v}px`}/>
              <div style={{ marginTop:10, borderRadius:8, background:'var(--bg)', border:'1px solid var(--border)', height:56, overflow:'hidden' }}>
                {(() => { const amp = Math.max(0, (56 - (el.strokeWidth??3)) / 4 - 2); return (
                  <svg width="100%" height="100%" viewBox="0 0 196 56" preserveAspectRatio="xMidYMid meet">
                    <path d={`M 0 28 Q 49 ${28-amp} 98 28 Q 147 ${28+amp} 196 28`} stroke={el.stroke??'#0E121B'} strokeWidth={el.strokeWidth??3} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
                  </svg>
                ); })()}
              </div>
            </div>
          </div>
        )}
        {el.type==='image' && (
          <div style={pSectionSt}>
            <div style={pLabelSt}>Radius</div>
            <IeSlider min={0} max={Math.min(el.w,el.h)/2|0} value={el.borderRadius??0} onChange={v=>up({borderRadius:v})} onCommit={v=>cm({borderRadius:v})} formatValue={v=>`${v}px`}/>
            <label style={{ display:'block', cursor:'pointer', marginTop:6 }}>
              <div style={pBtnSt}>Replace image…</div>
              <input type="file" accept="image/*" style={{ display:'none' }} onChange={e=>{ if(e.target.files[0]) handleReplaceImage(e.target.files[0], el.id, el); }}/>
            </label>
            <button
              onClick={() => {
                if (removingBgIds.has(el.id)) return;
                if (bgRemovedIds.has(el.id)) handleRestoreBg(el.id);
                else if (bgCacheRef.current[el.id]) handleReRemoveBg(el.id);
                else handleRemoveBg(el.id, el.src);
              }}
              disabled={removingBgIds.has(el.id)}
              style={{ ...pBtnSt, marginTop:8, display:'flex', alignItems:'center', justifyContent:'center', gap:6, opacity: removingBgIds.has(el.id) ? 0.6 : 1, cursor: removingBgIds.has(el.id) ? 'default' : 'pointer' }}>
              {removingBgIds.has(el.id) ? (
                <>
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
                    <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83">
                      <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/>
                    </path>
                  </svg>
                  Analysing depth…
                </>
              ) : bgRemovedIds.has(el.id) ? 'Restore background' : 'Remove background'}
            </button>
          </div>
        )}
        {el.type==='icon' && (
          <div style={pSectionSt}>
            <div style={pLabelSt}>Color</div>
            <IeColorPicker value={el.color??'#0E121B'} onChange={c=>up({color:c})} onCommit={c=>cm({color:c})}/>
            <div style={{ marginTop:10, fontSize:10, color:'var(--text-faint)', wordBreak:'break-all' }}>{el.iconId}</div>
          </div>
        )}
      </div>
    );
  };

  // ── Viewport cursor ───────────────────────────────────────────────────────
  const vpCursor = panning||spacePan ? 'grabbing' : (tool==='pen'||tool==='rect'||tool==='circle'||tool==='triangle') ? 'crosshair' : tool==='text' ? 'text' : 'default';

  // ── Shape dropdown handlers ───────────────────────────────────────────────
  const selectShape = (shapeId) => {
    setTool(shapeId);
    setLastShape(shapeId);
    setShapeDropOpen(false);
  };

  const isShapeActive = (tool === 'rect' || tool === 'circle' || tool === 'triangle') && !activePanel;
  const shapeLabel = SHAPE_OPTIONS.find(s => s.id === lastShape)?.label || 'Shapes';

  // ── Images panel toggle ───────────────────────────────────────────────────
  const toggleImages = () => {
    setActivePanel(p => p === 'images' ? null : 'images');
    setShapeDropOpen(false);
  };

  // ── Stock photos panel toggle + add ──────────────────────────────────────
  const toggleStock = () => {
    setActivePanel(p => p === 'stock' ? null : 'stock');
    setShapeDropOpen(false);
  };

  const handleAddImageFromUrl = (url) => {
    const img = new Image();
    img.onload = () => {
      const maxW = Math.min(img.naturalWidth, canvasSize.w * 0.7);
      const scale = maxW / img.naturalWidth;
      const w = Math.round(img.naturalWidth * scale), h = Math.round(img.naturalHeight * scale);
      addElement({ id: ieUid(), type: 'image', x: Math.round(canvasSize.w/2-w/2), y: Math.round(canvasSize.h/2-h/2), w, h, src: url, opacity: 1, borderRadius: 0, naturalW: img.naturalWidth, naturalH: img.naturalHeight, cropScale: scale, cropX: 0, cropY: 0 });
      setTool('select');
    };
    img.src = url;
  };

  // ── Icons panel toggle + add ──────────────────────────────────────────────
  const toggleIcons = () => {
    setActivePanel(p => p === 'icons' ? null : 'icons');
    setShapeDropOpen(false);
  };

  const handleAddIcon = (iconId) => {
    const size = Math.round(Math.min(canvasSize.w, canvasSize.h) * 0.15);
    addElement({ id: ieUid(), type: 'icon', x: Math.round(canvasSize.w/2 - size/2), y: Math.round(canvasSize.h/2 - size/2), w: size, h: size, iconId, color: '#0E121B', opacity: 1 });
    setTool('select');
  };

  // ── Project helpers ───────────────────────────────────────────────────────

  // Load an image for canvas use: for http(s) URLs fetch as blob (avoids canvas taint),
  // for data URLs load directly.
  const loadImgForCanvas = (src) => new Promise(resolve => {
    if (!src) return resolve(null);
    const finish = (blobUrl) => {
      const img = new window.Image();
      img.onload = () => { if (blobUrl) URL.revokeObjectURL(blobUrl); resolve(img); };
      img.onerror = () => { if (blobUrl) URL.revokeObjectURL(blobUrl); resolve(null); };
      img.src = blobUrl || src;
    };
    if (src.startsWith('data:')) {
      finish(null);
    } else {
      fetch(src, { mode: 'cors' })
        .then(r => r.blob())
        .then(b => finish(URL.createObjectURL(b)))
        .catch(() => finish(null));
    }
  });

  const buildThumbnail = async (snapshotElements, snapshotBg, snapshotSize) => {
    try {
      const THUMB_MAX = 400;
      const scale = THUMB_MAX / Math.max(snapshotSize.w, snapshotSize.h);
      const tw = Math.round(snapshotSize.w * scale);
      const th = Math.round(snapshotSize.h * scale);

      // Load all images + icons via blob URLs (same-origin → no canvas taint)
      const imageMap = {};
      await Promise.all(
        snapshotElements.filter(el => el.type === 'image' || el.type === 'icon').map(async el => {
          const src = el.type === 'icon' && el.iconId
            ? `https://api.iconify.design/${el.iconId.split(':')[0]}/${el.iconId.split(':')[1]}.svg?color=${encodeURIComponent(el.color || '#0E121B')}`
            : el.src;
          if (!src) return;
          const img = await loadImgForCanvas(src);
          if (img) imageMap[el.id] = img;
        })
      );

      const cvs = document.createElement('canvas');
      cvs.width = tw; cvs.height = th;
      const ctx = cvs.getContext('2d');
      if (!ctx) return null;
      ctx.fillStyle = snapshotBg || '#ffffff';
      ctx.fillRect(0, 0, tw, th);

      for (const el of snapshotElements) {
        try {
          ctx.save();
          ctx.globalAlpha = el.opacity ?? 1;
          const x = el.x * scale, y = el.y * scale, w = el.w * scale, h = el.h * scale;
          console.log('[thumb el] START', el.type, {x,y,w,h}, 'fill:', el.fill, 'opacity:', el.opacity);
          if (el.type === 'rect') {
            ctx.fillStyle = el.fill || '#335CFF';
            const r = Math.min((el.borderRadius || 0) * scale, w / 2, h / 2);
            ctx.beginPath();
            if (r > 0) { ieRoundRect(ctx, x, y, w, h, r); } else { ctx.rect(x, y, w, h); }
            ctx.fill();
            if (el.stroke && el.strokeWidth) { ctx.strokeStyle = el.stroke; ctx.lineWidth = el.strokeWidth * scale; ctx.stroke(); }
            console.log('[thumb el] rect drawn');
          } else if (el.type === 'circle') {
            const cx = x + w/2, cy = y + h/2, rx = w/2, ry = h/2;
            const K = 0.5522847498;
            ctx.fillStyle = el.fill || '#335CFF';
            ctx.beginPath();
            ctx.moveTo(cx + rx, cy);
            ctx.bezierCurveTo(cx + rx, cy - ry*K, cx + rx*K, cy - ry, cx, cy - ry);
            ctx.bezierCurveTo(cx - rx*K, cy - ry, cx - rx, cy - ry*K, cx - rx, cy);
            ctx.bezierCurveTo(cx - rx, cy + ry*K, cx - rx*K, cy + ry, cx, cy + ry);
            ctx.bezierCurveTo(cx + rx*K, cy + ry, cx + rx, cy + ry*K, cx + rx, cy);
            ctx.closePath();
            ctx.fill();
            if (el.stroke && el.strokeWidth && el.stroke !== 'transparent') { ctx.strokeStyle = el.stroke; ctx.lineWidth = el.strokeWidth * scale; ctx.stroke(); }
            const px = ctx.getImageData(Math.round(cx), Math.round(cy), 1, 1).data;
            console.log('[thumb el] circle drawn, center pixel RGBA:', px[0], px[1], px[2], px[3]);
          } else if (el.type === 'triangle') {
            ctx.fillStyle = el.fill || '#335CFF';
            ctx.beginPath(); ctx.moveTo(x + w/2, y); ctx.lineTo(x + w, y + h); ctx.lineTo(x, y + h); ctx.closePath(); ctx.fill();
            if (el.stroke && el.strokeWidth) { ctx.strokeStyle = el.stroke; ctx.lineWidth = el.strokeWidth * scale; ctx.stroke(); }
          } else if (el.type === 'text') {
            const fs = (el.fontSize || 32) * scale;
            ctx.font = `${el.fontWeight || 700} ${fs}px ${el.fontFamily || 'Inter'}`;
            ctx.fillStyle = el.color || '#0E121B';
            ctx.fillText(el.text || '', x, y + fs);
          } else if ((el.type === 'image' || el.type === 'icon') && imageMap[el.id]) {
            ctx.drawImage(imageMap[el.id], x, y, w, h);
          } else if (el.type === 'pen' && el.points && el.points.length > 1) {
            ctx.strokeStyle = el.stroke || el.color || '#0E121B'; ctx.lineWidth = (el.strokeWidth || 3) * scale;
            ctx.lineCap = 'round'; ctx.lineJoin = 'round';
            ctx.beginPath(); ctx.moveTo(el.points[0].x * scale, el.points[0].y * scale);
            for (let i = 1; i < el.points.length; i++) ctx.lineTo(el.points[i].x * scale, el.points[i].y * scale);
            ctx.stroke();
          }
          ctx.restore();
        } catch(e) { console.warn('[thumb el] ERROR', el.type, e); }
      }
      return cvs.toDataURL('image/jpeg', 0.82);
    } catch(e) { console.warn('[thumb] outer ERROR', e); return null; }
  };

  const handleBackToHome = async () => {
    // Snapshot current state before any setState resets it
    const snapElements = elements;
    const snapBg = canvasBg;
    const snapSize = canvasSize;
    const snapName = canvasName;

    // Navigate home immediately (don't wait for thumbnail)
    setActiveProjectId(null);
    setElements([]);
    setSelectedIds(new Set());
    setCanvasName('Untitled');
    setCanvasSize({ w: 1080, h: 1080 });
    setCanvasBg('#FFFFFF');

    // Generate thumbnail in background and update the project once done
    const now = Date.now();
    const isExisting = activeProjectId && activeProjectId !== '__new__';
    const thumbnail = await buildThumbnail(snapElements, snapBg, snapSize);
    const projectId = isExisting ? activeProjectId : ieUid();
    setProjects(prev => {
      const exists = isExisting && prev.some(p => p.id === activeProjectId);
      const updated = exists
        ? prev.map(p => p.id === activeProjectId ? { ...p, name: snapName, canvasSize: snapSize, canvasBg: snapBg, elements: snapElements, thumbnail, updatedAt: now } : p)
        : [...prev, { id: projectId, name: snapName, canvasSize: snapSize, canvasBg: snapBg, elements: snapElements, thumbnail, updatedAt: now, createdAt: now }];
      ieSaveProjects(updated);
      return updated;
    });
  };

  const handleNewProject = () => {
    setActiveProjectId('__new__');
    setElements([]);
    setSelectedIds(new Set());
    setCanvasName('Untitled');
    setCanvasSize({ w: 1080, h: 1080 });
    setCanvasBg('#FFFFFF');
    historyRef.current = [[]];
    historyIdxRef.current = 0;
  };

  const handleOpenProject = (id) => {
    const proj = projects.find(p => p.id === id);
    if (!proj) return;
    setActiveProjectId(id);
    setCanvasName(proj.name || 'Untitled');
    setCanvasSize(proj.canvasSize || { w: 1080, h: 1080 });
    setCanvasBg(proj.canvasBg || '#FFFFFF');
    const els = proj.elements || [];
    setElements(els);
    setSelectedIds(new Set());
    historyRef.current = [JSON.parse(JSON.stringify(els))];
    historyIdxRef.current = 0;
  };

  const handleDeleteProject = (id) => {
    setProjects(prev => {
      const updated = prev.filter(p => p.id !== id);
      ieSaveProjects(updated);
      return updated;
    });
  };

  // ── Home screen ───────────────────────────────────────────────────────────
  if (activeProjectId === null) {
    return (
      <CanvasProjectsHome
        projects={projects}
        onNew={handleNewProject}
        onOpen={handleOpenProject}
        onDelete={handleDeleteProject}
      />
    );
  }

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', overflow:'hidden', background:'var(--bg)', userSelect:'none' }}>

      {/* ══ Top bar ═══════════════════════════════════════════════════════════ */}
      <div style={{ height:50, flexShrink:0, display:'flex', alignItems:'center', gap:6, padding:'0 10px', borderBottom:'1px solid var(--border)', background:'var(--bg-surface)', zIndex:10 }}>
        {/* Back to home */}
        <button onClick={handleBackToHome} title="Back to projects" style={{ display:'flex', alignItems:'center', gap:5, padding:'4px 8px', borderRadius:6, border:'none', background:'transparent', cursor:'pointer', color:'var(--text-muted)', fontSize:12, fontWeight:500, flexShrink:0 }}
          onMouseEnter={e=>{e.currentTarget.style.background='var(--bg-soft)';e.currentTarget.style.color='var(--text)';}}
          onMouseLeave={e=>{e.currentTarget.style.background='transparent';e.currentTarget.style.color='var(--text-muted)';}}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
          Canvas
        </button>
        <div style={{ width:1, height:20, background:'var(--border)', margin:'0 2px' }}/>
        {editingName ? (
          <input autoFocus value={canvasName} onChange={e=>setCanvasName(e.target.value)} onBlur={()=>setEditingName(false)} onKeyDown={e=>{if(e.key==='Enter'||e.key==='Escape')setEditingName(false);}} style={{ fontSize:14, fontWeight:600, background:'transparent', border:'1px solid var(--accent)', borderRadius:5, padding:'3px 6px', color:'var(--text)', outline:'none', minWidth:120 }}/>
        ) : (
          <span onClick={()=>setEditingName(true)} style={{ fontSize:14, fontWeight:600, color:'var(--text)', cursor:'text', padding:'3px 6px', borderRadius:5, minWidth:60 }}>{canvasName}</span>
        )}
        <div style={{ width:1, height:20, background:'var(--border)', margin:'0 2px' }}/>
        <div style={{ position:'relative' }}>
          <button onClick={()=>setShowPresets(s=>!s)} style={{ display:'flex', alignItems:'center', gap:4, padding:'4px 8px', borderRadius:6, border:'1px solid var(--border)', background:showPresets?'var(--bg-soft)':'transparent', cursor:'pointer', fontSize:12, color:'var(--text-muted)' }}>
            <span style={{ fontVariantNumeric:'tabular-nums' }}>{canvasSize.w} × {canvasSize.h}</span>
            <Icon name="chevron-down" size={10}/>
          </button>
          {showPresets && (
            <>
              <div style={{ position:'fixed', inset:0, zIndex:19 }} onClick={()=>setShowPresets(false)}/>
              <div style={{ position:'absolute', top:'100%', left:0, marginTop:4, background:'var(--bg-surface)', border:'1px solid var(--border)', borderRadius:8, padding:6, zIndex:20, minWidth:180, boxShadow:'0 8px 24px rgba(0,0,0,0.12)' }}>
                {CANVAS_PRESETS.map(p => (
                  <button key={p.label} onClick={()=>{setCanvasSize({w:p.w,h:p.h});setShowPresets(false);}} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', width:'100%', padding:'6px 8px', borderRadius:5, border:'none', background:canvasSize.w===p.w&&canvasSize.h===p.h?'var(--accent-soft)':'transparent', color:canvasSize.w===p.w&&canvasSize.h===p.h?'var(--accent)':'var(--text)', cursor:'pointer', fontSize:13 }}>
                    <span>{p.label}</span>
                    <span style={{ color:'var(--text-faint)', fontSize:11, fontVariantNumeric:'tabular-nums' }}>{p.w}×{p.h}</span>
                  </button>
                ))}
              </div>
            </>
          )}
        </div>
        <label title="Canvas background" style={{ display:'flex', alignItems:'center', gap:5, cursor:'pointer', padding:'3px 6px', borderRadius:6, border:'1px solid var(--border)' }}>
          <div style={{ width:16, height:16, borderRadius:3, background:canvasBg, border:'1px solid rgba(0,0,0,0.12)', flexShrink:0 }}/>
          <span style={{ fontSize:12, color:'var(--text-muted)' }}>BG</span>
          <input type="color" value={canvasBg} onChange={e=>setCanvasBg(e.target.value)} style={{ width:0, height:0, opacity:0, position:'absolute', pointerEvents:'none' }}/>
        </label>
        <div style={{ flex:1 }}/>
        <button onClick={undo} style={topBtnSt} title="Undo (Ctrl+Z)"><Icon name="undo" size={15}/></button>
        <button onClick={redo} style={topBtnSt} title="Redo (Ctrl+Y)"><Icon name="redo" size={15}/></button>
        <div style={{ width:1, height:20, background:'var(--border)' }}/>
        <button onClick={()=>setZoom(z=>Math.max(0.05,+(z*0.8).toFixed(3)))} style={topBtnSt}><Icon name="minus" size={14}/></button>
        <button onClick={fitToScreen} style={{ ...topBtnSt, fontSize:12, fontWeight:600, minWidth:54, padding:'0 6px', color:'var(--text-muted)' }} title="Fit to screen">{Math.round(zoom*100)}%</button>
        <button onClick={()=>setZoom(z=>Math.min(5,+(z*1.25).toFixed(3)))} style={topBtnSt}><Icon name="plus" size={14}/></button>
        <div style={{ width:1, height:20, background:'var(--border)' }}/>
        <button style={{ display:'flex', alignItems:'center', gap:6, padding:'5px 14px', borderRadius:7, border:'none', background:'var(--accent)', color:'#fff', cursor:'pointer', fontSize:13, fontWeight:600 }}>
          <Icon name="download" size={13}/>Export
        </button>
      </div>

      <div style={{ flex:1, display:'flex', minHeight:0, overflow:'hidden' }}>

        {/* ══ Left toolbar — wide labeled ════════════════════════════════════ */}
        <div style={{ width:TOOLBAR_W, flexShrink:0, display:'flex', flexDirection:'column', padding:'8px 8px', gap:1, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', zIndex:5, overflowY:'auto' }}>

          {/* Select */}
          <IeToolRow icon={<Icon name="cursor" size={15}/>} label="Select" shortcut="V" active={tool==='select'&&!activePanel} onClick={()=>{setTool('select');setActivePanel(null);}}/>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Text */}
          <IeToolRow icon={<Icon name="text-tool" size={15}/>} label="Text" shortcut="T" active={tool==='text'&&!activePanel} onClick={()=>{setTool('text');setActivePanel(null);}}/>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Shapes — button + dropdown */}
          <div style={{ position:'relative' }}>
            <div style={{ display:'flex', alignItems:'center', borderRadius:7, background:isShapeActive||shapeDropOpen?'var(--accent-soft)':'transparent', overflow:'hidden' }}>
              {/* Main button — activates last used shape */}
              <button
                onClick={()=>{setTool(lastShape);setActivePanel(null);setShapeDropOpen(false);}}
                style={{ flex:1, display:'flex', alignItems:'center', gap:9, padding:'7px 10px', border:'none', background:'transparent', cursor:'pointer', color:isShapeActive?'var(--accent)':'var(--text-muted)', textAlign:'left' }}>
                <span style={{ flexShrink:0 }}><ShapeIcon type={lastShape} size={15}/></span>
                <span style={{ fontSize:13, fontWeight:500 }}>{shapeLabel}</span>
              </button>
              {/* Chevron to open dropdown */}
              <button
                onClick={e=>{e.stopPropagation();setShapeDropOpen(o=>!o);}}
                style={{ width:28, height:32, border:'none', background:'transparent', cursor:'pointer', color:isShapeActive||shapeDropOpen?'var(--accent)':'var(--text-faint)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
                <Icon name={shapeDropOpen?'chevron-up':'chevron-down'} size={11}/>
              </button>
            </div>
            {/* Shape dropdown */}
            {shapeDropOpen && (
              <>
                <div style={{ position:'fixed', inset:0, zIndex:29 }} onClick={()=>setShapeDropOpen(false)}/>
                <div style={{ position:'absolute', top:'100%', left:0, right:0, marginTop:3, background:'var(--bg-surface)', border:'1px solid var(--border)', borderRadius:8, padding:4, zIndex:30, boxShadow:'0 8px 20px rgba(0,0,0,0.12)' }}>
                  {SHAPE_OPTIONS.map(s => (
                    <button key={s.id} onClick={()=>selectShape(s.id)} style={{ display:'flex', alignItems:'center', gap:9, width:'100%', padding:'7px 10px', borderRadius:6, border:'none', cursor:'pointer', background:lastShape===s.id?'var(--accent-soft)':'transparent', color:lastShape===s.id?'var(--accent)':'var(--text)' }}
                      onMouseEnter={e=>{if(lastShape!==s.id)e.currentTarget.style.background='var(--bg-soft)';}}
                      onMouseLeave={e=>{if(lastShape!==s.id)e.currentTarget.style.background='transparent';}}>
                      <ShapeIcon type={s.id} size={14}/>
                      <span style={{ fontSize:13, fontWeight:500, flex:1 }}>{s.label}</span>
                      {s.shortcut && <span style={{ fontSize:11, color:'var(--text-faint)', fontFamily:'monospace' }}>{s.shortcut}</span>}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Images */}
          <IeToolRow icon={<Icon name="image" size={15}/>} label="Images" shortcut="I" active={activePanel==='images'} onClick={toggleImages}/>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Stock photos */}
          <IeToolRow icon={<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>} label="Photos" shortcut="" active={activePanel==='stock'} onClick={toggleStock}/>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Icons */}
          <IeToolRow icon={<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>} label="Icons" shortcut="" active={activePanel==='icons'} onClick={toggleIcons}/>

          <div style={{ height:1, background:'var(--border)', margin:'4px 4px' }}/>

          {/* Draw */}
          <IeToolRow icon={<Icon name="pen" size={15}/>} label="Draw" shortcut="P" active={tool==='pen'&&!activePanel} onClick={()=>{setTool('pen');setActivePanel(null);}}/>

          <div style={{ flex:1 }}/>
        </div>

        {/* ══ Image library panel ════════════════════════════════════════════ */}
        {activePanel === 'images' && (
          <div style={{ width:LIBRARY_W, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <IeImageLibrary
              onUpload={handleImageUpload}
              uploadedImages={uploadedImages}
              onAddUploaded={handleAddUploaded}
              onAddFromAd={(ad, color) => {
                const w = Math.round(canvasSize.w * 0.4), h = Math.round(w * 0.75);
                addElement({ id:ieUid(), type:'rect', x:Math.round(canvasSize.w/2-w/2), y:Math.round(canvasSize.h/2-h/2), w, h, fill:color, stroke:'transparent', strokeWidth:0, opacity:1, borderRadius:8 });
                setActivePanel(null);
                setTool('select');
              }}/>
          </div>
        )}

        {/* ══ Stock photos panel ══════════════════════════════════════════════ */}
        {activePanel === 'stock' && (
          <div style={{ width:LIBRARY_W, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <IeStockLibrary onAddImage={handleAddImageFromUrl}/>
          </div>
        )}

        {/* ══ Icons library panel ═════════════════════════════════════════════ */}
        {activePanel === 'icons' && (
          <div style={{ width:LIBRARY_W, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <IeIconLibrary onAddIcon={handleAddIcon}/>
          </div>
        )}

        {/* ══ Shape tool options panel ═════════════════════════════════════════ */}
        {isShapeActive && (
          <div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <div style={{ padding:'10px 12px', borderBottom:'1px solid var(--border)', flexShrink:0 }}>
              <div style={{ fontSize:13, fontWeight:600, color:'var(--text)' }}>{SHAPE_OPTIONS.find(s => s.id === tool)?.label ?? 'Shape'}</div>
            </div>
            <div style={{ padding:'14px 12px', display:'flex', flexDirection:'column', gap:16, overflowY:'auto' }}>
              <div>
                <div style={pLabelSt}>Fill</div>
                <IeColorPicker value={fillColor} onChange={setFillColor} onCommit={setFillColor}/>
              </div>
              {tool === 'rect' && (
                <div>
                  <div style={pLabelSt}>Radius</div>
                  <IeSlider min={0} max={200} value={shapeRadius} onChange={setShapeRadius} onCommit={setShapeRadius} formatValue={v=>v===0?'None':`${v}px`}/>
                </div>
              )}
              <div>
                <div style={pLabelSt}>Border color</div>
                <IeColorPicker value={shapeStrokeColor} onChange={setShapeStrokeColor} onCommit={setShapeStrokeColor}/>
              </div>
              <div>
                <div style={pLabelSt}>Border width</div>
                <IeSlider min={0} max={40} value={shapeStrokeWidth} onChange={setShapeStrokeWidth} onCommit={setShapeStrokeWidth} formatValue={v=>v===0?'None':`${v}px`}/>
              </div>
            </div>
          </div>
        )}

        {/* ══ Pen tool options panel ════════════════════════════════════════════ */}
        {tool === 'pen' && !activePanel && (
          <div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <div style={{ padding:'10px 12px', borderBottom:'1px solid var(--border)', flexShrink:0 }}>
              <div style={{ fontSize:13, fontWeight:600, color:'var(--text)' }}>Draw</div>
            </div>
            <div style={{ padding:'14px 12px', display:'flex', flexDirection:'column', gap:16 }}>
              <div>
                <div style={pLabelSt}>Color</div>
                <IeColorPicker value={penColor} onChange={setPenColor} onCommit={setPenColor}/>
              </div>
              <div>
                <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:2 }}>
                  <div style={pLabelSt}>Thickness</div>
                  <span style={{ fontSize:11, color:'var(--text-faint)', fontVariantNumeric:'tabular-nums' }}>{penWidth}px</span>
                </div>
                <IeSlider min={1} max={60} value={penWidth} onChange={setPenWidth} formatValue={v => `${v}px`}/>
                <div style={{ marginTop:12 }}/>
                {(() => {
                  const amp = Math.max(0, (56 - penWidth) / 4 - 2);
                  return (
                    <div style={{ borderRadius:8, background:'var(--bg)', border:'1px solid var(--border)', height:56, overflow:'hidden' }}>
                      <svg width="100%" height="100%" viewBox="0 0 196 56" preserveAspectRatio="xMidYMid meet">
                        <path d={`M 0 28 Q 49 ${28 - amp} 98 28 Q 147 ${28 + amp} 196 28`} stroke={penColor} strokeWidth={penWidth} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
                      </svg>
                    </div>
                  );
                })()}
              </div>
            </div>
          </div>
        )}

        {/* ══ Text tool options panel ══════════════════════════════════════════ */}
        {tool === 'text' && !activePanel && (
          <div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', background:'var(--bg-surface)', display:'flex', flexDirection:'column', zIndex:4 }}>
            <div style={{ padding:'10px 12px', borderBottom:'1px solid var(--border)', flexShrink:0 }}>
              <div style={{ fontSize:13, fontWeight:600, color:'var(--text)' }}>Text</div>
            </div>
            <div style={{ padding:'14px 12px', display:'flex', flexDirection:'column', gap:16, overflowY:'auto' }}>
              <div>
                <div style={pLabelSt}>Typography</div>
                <select value={textFontFamily} onChange={e=>setTextFontFamily(e.target.value)} style={sidebarSelSt}>
                  {FONT_FAMILIES.map(f => <option key={f} value={f}>{f}</option>)}
                </select>
                <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:6, marginTop:6 }}>
                  <div>
                    <div style={{ fontSize:10, color:'var(--text-faint)', fontWeight:600, marginBottom:3 }}>Size</div>
                    <input type="number" min={6} max={400} value={textFontSize} onChange={e=>setTextFontSize(Number(e.target.value))} style={sidebarNumSt}/>
                  </div>
                  <div>
                    <div style={{ fontSize:10, color:'var(--text-faint)', fontWeight:600, marginBottom:3 }}>Weight</div>
                    <select value={textFontWeight} onChange={e=>setTextFontWeight(e.target.value)} style={sidebarSelSt}>
                      <option value="400">Regular</option>
                      <option value="500">Medium</option>
                      <option value="600">Semibold</option>
                      <option value="700">Bold</option>
                      <option value="800">Extra Bold</option>
                    </select>
                  </div>
                </div>
                <div style={{ display:'flex', gap:4, marginTop:8 }}>
                  {['left','center','right'].map(v => (
                    <button key={v} onClick={()=>setTextAlign(v)} style={{ flex:1, height:28, borderRadius:5, border:'none', cursor:'pointer', fontSize:11, fontWeight:500, background:textAlign===v?'var(--accent-soft)':'var(--bg-soft)', color:textAlign===v?'var(--accent)':'var(--text-muted)' }}>
                      {v.charAt(0).toUpperCase()+v.slice(1)}
                    </button>
                  ))}
                </div>
              </div>
              <div>
                <div style={pLabelSt}>Color</div>
                <IeColorPicker value={textColor} onChange={setTextColor} onCommit={setTextColor}/>
              </div>
            </div>
          </div>
        )}

        {/* ══ Viewport ════════════════════════════════════════════════════════ */}
        <div ref={viewportRef} style={{ flex:1, position:'relative', overflow:'hidden', background:'var(--bg)', cursor:vpCursor }}
          onContextMenu={e=>{ e.preventDefault(); setTool('select'); setActivePanel(null); }}
          onMouseDown={e=>{
            setShapeDropOpen(false);
            if(spaceHeldRef.current||e.button===1){
              e.preventDefault();
              const sx=e.clientX,sy=e.clientY,op={...pan};
              setPanning(true);
              const mm=ev=>setPan({x:op.x+(ev.clientX-sx),y:op.y+(ev.clientY-sy)});
              const mu=()=>{setPanning(false);window.removeEventListener('mousemove',mm);window.removeEventListener('mouseup',mu);};
              window.addEventListener('mousemove',mm);window.addEventListener('mouseup',mu);
              return;
            }
            onCanvasMouseDown(e);
          }}>

          <div style={{ position:'absolute', left:'50%', top:'50%', transform:`translate(-50%,-50%) translate(${pan.x}px,${pan.y}px) scale(${zoom})`, transformOrigin:'center center' }}>
            {/* Canvas */}
            <div ref={canvasAreaRef} style={{ width:canvasSize.w, height:canvasSize.h, background:canvasBg, position:'relative', boxShadow:'0 12px 48px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.06)', overflow:'hidden' }}>
              {elements.map(renderElement)}
              {penPoints && penPoints.length > 1 && (
                <svg style={{ position:'absolute', inset:0, width:'100%', height:'100%', pointerEvents:'none' }}>
                  <path d={penPath(penPoints)} stroke={penColor} strokeWidth={penWidth} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              )}
              {shapePreview && tool==='rect' && shapePreview.w>0 && <div style={{ position:'absolute', left:shapePreview.x, top:shapePreview.y, width:shapePreview.w, height:shapePreview.h, background:fillColor+'40', border:'2px dashed #335CFF', boxShadow:'0 0 0 1px rgba(255,255,255,0.6)', pointerEvents:'none' }}/>}
              {shapePreview && tool==='circle' && shapePreview.w>0 && <div style={{ position:'absolute', left:shapePreview.x, top:shapePreview.y, width:shapePreview.w, height:shapePreview.h, background:fillColor+'40', border:'2px dashed #335CFF', boxShadow:'0 0 0 1px rgba(255,255,255,0.6)', borderRadius:'50%', pointerEvents:'none' }}/>}
              {shapePreview && tool==='triangle' && shapePreview.w>0 && <div style={{ position:'absolute', left:shapePreview.x, top:shapePreview.y, width:shapePreview.w, height:shapePreview.h, pointerEvents:'none' }}><svg width={shapePreview.w} height={shapePreview.h}><polygon points={`${shapePreview.w/2},0 ${shapePreview.w},${shapePreview.h} 0,${shapePreview.h}`} fill={fillColor+'40'} stroke='#335CFF' strokeWidth={2} strokeDasharray="6,3"/></svg></div>}
            </div>

            {/* Resize handles */}
            {tool==='select'&&selectedIds.size===1&&(()=>{const el=elements.find(e=>selectedIds.has(e.id));if(!el||el.type==='pen'||editingTextId===el.id)return null;return <ResizeHandles el={el}/>;})()}
            {tool==='select'&&selectedIds.size>1&&<MultiResizeHandles/>}

            {/* Guides */}
            {guides.x!=null&&<div style={{ position:'absolute', left:guides.x-0.5, top:-canvasSize.h, width:1, height:canvasSize.h*3, background:'var(--accent)', opacity:0.7, pointerEvents:'none', zIndex:99 }}/>}
            {guides.y!=null&&<div style={{ position:'absolute', top:guides.y-0.5, left:-canvasSize.w, height:1, width:canvasSize.w*3, background:'var(--accent)', opacity:0.7, pointerEvents:'none', zIndex:99 }}/>}

            {/* Marquee selection */}
            {marquee&&marquee.w>1&&marquee.h>1&&<div style={{ position:'absolute', left:marquee.x, top:marquee.y, width:marquee.w, height:marquee.h, border:'1.5px solid var(--accent)', background:'rgba(51,92,255,0.07)', pointerEvents:'none', zIndex:98 }}/>}
          </div>

          {/* Zoom badge */}
          <div style={{ position:'absolute', bottom:10, left:'50%', transform:'translateX(-50%)', background:'rgba(14,18,27,0.55)', color:'#fff', fontSize:11, fontWeight:600, padding:'3px 9px', borderRadius:20, pointerEvents:'none', backdropFilter:'blur(4px)' }}>
            {Math.round(zoom*100)}%
          </div>
          {/* Tool hint */}
          {tool!=='select'&&(
            <div style={{ position:'absolute', bottom:10, right:14, background:'rgba(14,18,27,0.55)', color:'#fff', fontSize:11, fontWeight:500, padding:'3px 9px', borderRadius:20, pointerEvents:'none', backdropFilter:'blur(4px)' }}>
              {tool==='text'?'Click to place text':tool==='pen'?'Drag to draw':'Drag to draw shape'} · Esc
            </div>
          )}
        </div>

        {/* ══ Properties panel ════════════════════════════════════════════════ */}
        <div style={{ width:230, flexShrink:0, borderLeft:'1px solid var(--border)', background:'var(--bg-surface)', overflowY:'auto', display:'flex', flexDirection:'column' }}>
          {renderProperties()}
        </div>
      </div>

      <input id="ie-img-upload" type="file" accept="image/*" style={{ display:'none' }}
        onChange={e=>{ if(e.target.files[0]){handleImageUpload(e.target.files[0]);e.target.value='';} }}/>

      {/* ══ Context menu ════════════════════════════════════════════════════ */}
      {ctxMenu && (() => {
        const ctxEl = elements.find(e => e.id === ctxMenu.elId);
        if (!ctxEl) return null;
        const typeLabel = { text:'Text', image:'Image', rect:'Rectangle', circle:'Circle', triangle:'Triangle', pen:'Drawing' }[ctxEl.type] || ctxEl.type;
        const isMulti = selectedIds.size > 1;
        const selIds = isMulti ? [...selectedIds] : [ctxEl.id];
        const selIndices = selIds.map(id => elements.findIndex(e => e.id === id)).filter(i => i >= 0);
        const canGoUp = isMulti ? Math.max(...selIndices) < elements.length - 1 : elements.findIndex(e => e.id === ctxEl.id) < elements.length - 1;
        const canGoDown = isMulti ? Math.min(...selIndices) > 0 : elements.findIndex(e => e.id === ctxEl.id) > 0;

        const menuW = 200;
        const menuH = 290;
        const vw = window.innerWidth, vh = window.innerHeight;
        const mx = ctxMenu.x + menuW > vw ? ctxMenu.x - menuW : ctxMenu.x;
        const my = ctxMenu.y + menuH > vh ? ctxMenu.y - menuH : ctxMenu.y;

        const itemSt = (danger) => ({
          display:'flex', alignItems:'center', gap:10,
          width:'100%', padding:'7px 12px', border:'none', background:'transparent',
          cursor:'pointer', textAlign:'left', fontSize:13, fontWeight:500,
          color: danger ? '#EF4444' : 'var(--text)',
          borderRadius:5,
        });
        const Item = ({ icon, label, onClick, danger, disabled }) => (
          <button
            style={{ ...itemSt(danger), opacity: disabled ? 0.35 : 1, cursor: disabled ? 'default' : 'pointer' }}
            onClick={disabled ? null : () => { onClick(); closeCtxMenu(); }}
            onMouseEnter={e => { if (!disabled) e.currentTarget.style.background = danger ? 'rgba(239,68,68,0.08)' : 'var(--bg-soft)'; }}
            onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}>
            <span style={{ width:15, display:'flex', flexShrink:0 }}><Icon name={icon} size={13}/></span>
            {label}
          </button>
        );

        return (
          <>
            <div style={{ position:'fixed', inset:0, zIndex:999 }} onClick={closeCtxMenu} onContextMenu={e=>{e.preventDefault();closeCtxMenu();}}/>
            <div style={{
              position:'fixed', left:mx, top:my, zIndex:1000,
              background:'var(--bg-surface)', border:'1px solid var(--border)',
              borderRadius:10, padding:'6px', minWidth:menuW,
              boxShadow:'0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.08)',
            }}>
              {/* Header */}
              <div style={{ padding:'4px 12px 6px', borderBottom:'1px solid var(--border)', marginBottom:4 }}>
                <div style={{ fontSize:11, fontWeight:700, color:'var(--text-faint)', textTransform:'uppercase', letterSpacing:'0.06em' }}>{typeLabel}</div>
              </div>

              {/* Layer */}
              <Item icon="arrow-up" label="Bring to front" disabled={!canGoUp} onClick={()=>isMulti ? bringToFrontMulti(selIds) : bringToFront(ctxEl.id)}/>
              <Item icon="arrow-up" label="Bring forward" disabled={!canGoUp} onClick={()=>isMulti ? bringForwardMulti(selIds) : bringForward(ctxEl.id)}/>
              <Item icon="arrow-down" label="Send backward" disabled={!canGoDown} onClick={()=>isMulti ? sendBackwardMulti(selIds) : sendBackward(ctxEl.id)}/>
              <Item icon="arrow-down" label="Send to back" disabled={!canGoDown} onClick={()=>isMulti ? sendToBackMulti(selIds) : sendToBack(ctxEl.id)}/>

              <div style={{ height:1, background:'var(--border)', margin:'4px 0' }}/>

              {/* Edit */}
              {ctxEl.type === 'text' && (
                <Item icon="text-tool" label="Edit text" onClick={()=>{ setEditingTextId(ctxEl.id); setTool('select'); }}/>
              )}
              {ctxEl.type === 'image' && (
                <Item icon="image"
                  label={removingBgIds.has(ctxEl.id) ? 'Analysing depth…' : bgRemovedIds.has(ctxEl.id) ? 'Restore background' : 'Remove background'}
                  disabled={removingBgIds.has(ctxEl.id)}
                  onClick={() => {
                    if (bgRemovedIds.has(ctxEl.id)) handleRestoreBg(ctxEl.id);
                    else if (bgCacheRef.current[ctxEl.id]) handleReRemoveBg(ctxEl.id);
                    else handleRemoveBg(ctxEl.id, ctxEl.src);
                  }}/>
              )}
              <Item icon="copy" label="Duplicate" onClick={duplicateSelected}/>

              <div style={{ height:1, background:'var(--border)', margin:'4px 0' }}/>

              {/* Danger */}
              <Item icon="trash" label="Delete" danger onClick={deleteSelected}/>
            </div>
          </>
        );
      })()}
    </div>
  );
}

// ── Style constants ───────────────────────────────────────────────────────────
const topBtnSt = { width:30, height:30, display:'flex', alignItems:'center', justifyContent:'center', background:'transparent', border:'none', borderRadius:6, cursor:'pointer', color:'var(--text-muted)' };
const pSectionSt = { padding:'12px 12px', borderBottom:'1px solid var(--border)' };
const pLabelSt = { fontSize:11, fontWeight:600, color:'var(--text-faint)', textTransform:'uppercase', letterSpacing:'0.05em', marginBottom:6 };
const pBtnSt = { display:'block', width:'100%', padding:'6px 10px', textAlign:'center', background:'var(--bg-soft)', border:'1px solid var(--border)', borderRadius:6, cursor:'pointer', fontSize:12, color:'var(--text)' };
const iconBtnSt = { width:26, height:26, display:'inline-flex', alignItems:'center', justifyContent:'center', background:'var(--bg-soft)', border:'1px solid var(--border)', borderRadius:5, cursor:'pointer', color:'var(--text-muted)' };
const numInpSt = { width:'100%', padding:'4px 6px', fontSize:12, background:'var(--bg)', border:'1px solid var(--border)', borderRadius:5, color:'var(--text)', outline:'none', boxSizing:'border-box', fontFamily:'inherit', height:28 };
const selSt = { width:'100%', padding:'4px 6px', fontSize:12, background:'var(--bg)', border:'1px solid var(--border)', borderRadius:5, color:'var(--text)', outline:'none', boxSizing:'border-box', cursor:'pointer', height:28 };
const sidebarNumSt = { width:'100%', padding:'4px 6px', fontSize:12, background:'var(--bg)', border:'1px solid var(--border)', borderRadius:5, color:'var(--text)', outline:'none', boxSizing:'border-box', fontFamily:'inherit', height:28 };
const sidebarSelSt = { width:'100%', padding:'4px 6px', fontSize:12, background:'var(--bg)', border:'1px solid var(--border)', borderRadius:5, color:'var(--text)', outline:'none', boxSizing:'border-box', cursor:'pointer', height:28 };

// ── Sub-components ────────────────────────────────────────────────────────────

function IeToolRow({ icon, label, shortcut, active, onClick }) {
  return (
    <button onClick={onClick} style={{
      display:'flex', alignItems:'center', gap:9,
      width:'100%', padding:'7px 10px', borderRadius:7, border:'none',
      background: active ? 'var(--accent-soft)' : 'transparent',
      color: active ? 'var(--accent)' : 'var(--text-muted)',
      cursor:'pointer', textAlign:'left',
      transition:'background 120ms, color 120ms',
    }}
    onMouseEnter={e=>{ if(!active){ e.currentTarget.style.background='var(--bg-soft)'; e.currentTarget.style.color='var(--text)'; } }}
    onMouseLeave={e=>{ if(!active){ e.currentTarget.style.background='transparent'; e.currentTarget.style.color='var(--text-muted)'; } }}>
      <span style={{ flexShrink:0, display:'flex' }}>{icon}</span>
      <span style={{ flex:1, fontSize:13, fontWeight:500 }}>{label}</span>
      {shortcut && <span style={{ fontSize:10, color: active ? 'var(--accent)' : 'var(--text-faint)', fontFamily:'monospace', fontWeight:600 }}>{shortcut}</span>}
    </button>
  );
}

// ── Custom color history (shared across all picker instances, persisted) ───────
let _ieCustomColors = (() => { try { return JSON.parse(localStorage.getItem('ie-custom-colors') || '[]'); } catch { return []; } })();
const _ieCustomListeners = new Set();
function _ieAddCustomColor(hex) {
  if (!hex || !/^#[0-9a-fA-F]{6}$/i.test(hex)) return;
  if (EDITOR_PALETTE.some(c => c.toLowerCase() === hex.toLowerCase())) return;
  const filtered = _ieCustomColors.filter(c => c.toLowerCase() !== hex.toLowerCase());
  _ieCustomColors = [hex, ...filtered].slice(0, 20);
  try { localStorage.setItem('ie-custom-colors', JSON.stringify(_ieCustomColors)); } catch {}
  _ieCustomListeners.forEach(fn => fn(_ieCustomColors));
}

function _hsv2hex(h, s, v) {
  const f = n => { const k = (n + h / 60) % 6; return v - v * s * Math.max(0, Math.min(k, 4 - k, 1)); };
  return '#' + [f(5), f(3), f(1)].map(c => Math.round(c * 255).toString(16).padStart(2, '0')).join('');
}
function _hex2hsv(hex) {
  const r = parseInt(hex.slice(1,3),16)/255, g = parseInt(hex.slice(3,5),16)/255, b = parseInt(hex.slice(5,7),16)/255;
  const max = Math.max(r,g,b), min = Math.min(r,g,b), d = max - min;
  let h = 0;
  if (d > 0) { if (max===r) h=((g-b)/d+6)%6; else if (max===g) h=(b-r)/d+2; else h=(r-g)/d+4; h*=60; }
  return { h, s: max > 0 ? d/max : 0, v: max };
}

function IeColorPicker({ value, onChange, onCommit }) {
  const safeHex = v => /^#[0-9a-fA-F]{6}$/.test(v) ? v : '#000000';
  const [hsv, setHsv] = React.useState(() => _hex2hsv(safeHex(value)));
  const [hexInput, setHexInput] = React.useState(safeHex(value));
  const [open, setOpen] = React.useState(false);
  const [customColors, setCustomColors] = React.useState(_ieCustomColors);
  const hsvRef = React.useRef(hsv);
  const lastRef = React.useRef(value);
  const pickerRef = React.useRef(null);
  const hueRef = React.useRef(null);

  React.useEffect(() => {
    _ieCustomListeners.add(setCustomColors);
    return () => _ieCustomListeners.delete(setCustomColors);
  }, []);

  const prevOpenRef = React.useRef(false);
  React.useEffect(() => {
    if (prevOpenRef.current && !open) {
      _ieAddCustomColor(_hsv2hex(hsvRef.current.h, hsvRef.current.s, hsvRef.current.v));
    }
    prevOpenRef.current = open;
  }, [open]);

  React.useEffect(() => {
    if (value && value !== lastRef.current && /^#[0-9a-fA-F]{6}$/.test(value)) {
      lastRef.current = value;
      const h = _hex2hsv(value);
      hsvRef.current = h; setHsv(h); setHexInput(value);
    }
  }, [value]);

  const emit = (h, s, v, commit = false) => {
    const hex = _hsv2hex(h, s, v);
    hsvRef.current = { h, s, v }; setHsv({ h, s, v });
    lastRef.current = hex; setHexInput(hex);
    onChange(hex); if (commit) onCommit(hex);
  };

  const makeDrag = (ref, onMove) => e => {
    if (e.button !== 0) return; e.preventDefault();
    const move = ev => onMove(ev, ref.current.getBoundingClientRect());
    const up = () => {
      onCommit(_hsv2hex(hsvRef.current.h, hsvRef.current.s, hsvRef.current.v));
      window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move); window.addEventListener('mouseup', up); move(e);
  };

  const onPickerDown = makeDrag(pickerRef, (ev, rect) => {
    const s = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));
    const v = 1 - Math.max(0, Math.min(1, (ev.clientY - rect.top) / rect.height));
    emit(hsvRef.current.h, s, v);
  });

  const onHueDown = makeDrag(hueRef, (ev, rect) => {
    const h = Math.max(0, Math.min(360, (ev.clientX - rect.left) / rect.width * 360));
    emit(h, hsvRef.current.s, hsvRef.current.v);
  });

  const hueOnly = _hsv2hex(hsv.h, 1, 1);
  const currentHex = _hsv2hex(hsv.h, hsv.s, hsv.v);
  const SWATCH = 28;
  const [hoveredSwatch, setHoveredSwatch] = React.useState(null);

  return (
    <div>
      {/* Swatch row — rainbow circle + palette circles */}
      <div style={{ display:'flex', flexWrap:'wrap', gap:6, marginBottom: open ? 12 : 0 }}>

        {/* Custom color circles — most recent first */}
        {customColors.map(c => {
          const sel = currentHex.toLowerCase() === c.toLowerCase();
          const hov = hoveredSwatch === c;
          return (
            <button key={c}
              onClick={() => { if (sel) { setOpen(o => !o); } else { const h = _hex2hsv(c); emit(h.h, h.s, h.v, true); setOpen(false); } }}
              onMouseEnter={() => setHoveredSwatch(c)} onMouseLeave={() => setHoveredSwatch(null)}
              style={{ position:'relative', width:SWATCH, height:SWATCH, borderRadius:'50%', padding:0, border:'none', cursor:'pointer', flexShrink:0, background:c, overflow:'hidden',
                boxShadow: sel ? '0 0 0 2px var(--bg-surface), 0 0 0 4px var(--accent), inset 0 0 0 1px rgba(0,0,0,0.1)' : 'inset 0 0 0 1px rgba(0,0,0,0.12)' }}>
              {sel && (
                <div style={{ position:'absolute', inset:0, borderRadius:'50%', background:'rgba(0,0,0,0.38)', display:'flex', alignItems:'center', justifyContent:'center', opacity: hov ? 1 : 0, transition:'opacity 120ms' }}>
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                    <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
                  </svg>
                </div>
              )}
            </button>
          );
        })}

        {/* Rainbow "custom" circle */}
        <button onClick={() => setOpen(o => !o)} title="Custom color"
          style={{ width:SWATCH, height:SWATCH, borderRadius:'50%', padding:0, border:'none', cursor:'pointer', flexShrink:0, position:'relative',
            background:'conic-gradient(#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)',
            boxShadow: open ? '0 0 0 2px var(--bg-surface), 0 0 0 4px var(--accent)' : 'none' }}>
          <div style={{ position:'absolute', inset:3, borderRadius:'50%', background:'var(--bg-surface)', display:'flex', alignItems:'center', justifyContent:'center' }}>
            <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
              <path d="M5 2v6M2 5h6"/>
            </svg>
          </div>
        </button>

        {/* Palette circles */}
        {EDITOR_PALETTE.map(c => {
          const sel = currentHex.toLowerCase() === c.toLowerCase();
          const hov = hoveredSwatch === c;
          return (
            <button key={c}
              onClick={() => {
                if (sel) { setOpen(o => !o); }
                else { const h = _hex2hsv(c); emit(h.h, h.s, h.v, true); setOpen(false); }
              }}
              onMouseEnter={() => setHoveredSwatch(c)}
              onMouseLeave={() => setHoveredSwatch(null)}
              style={{ position:'relative', width:SWATCH, height:SWATCH, borderRadius:'50%', padding:0, border:'none', cursor:'pointer', flexShrink:0, background:c, overflow:'hidden',
                boxShadow: sel
                  ? '0 0 0 2px var(--bg-surface), 0 0 0 4px var(--accent), inset 0 0 0 1px rgba(0,0,0,0.1)'
                  : 'inset 0 0 0 1px rgba(0,0,0,0.12)' }}>
              {/* Edit overlay — visible on hover of selected swatch */}
              {sel && (
                <div style={{ position:'absolute', inset:0, borderRadius:'50%', background:'rgba(0,0,0,0.38)', display:'flex', alignItems:'center', justifyContent:'center',
                  opacity: hov ? 1 : 0, transition:'opacity 120ms' }}>
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                    <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
                  </svg>
                </div>
              )}
            </button>
          );
        })}
      </div>

      {/* Full picker — only when open */}
      {open && (
        <div>
          {/* Saturation / Brightness */}
          <div ref={pickerRef} onMouseDown={onPickerDown}
            style={{ position:'relative', width:'100%', height:150, borderRadius:8, cursor:'crosshair', overflow:'hidden', marginBottom:10, userSelect:'none' }}>
            <div style={{ position:'absolute', inset:0, background:hueOnly }}/>
            <div style={{ position:'absolute', inset:0, background:'linear-gradient(to right, #fff, transparent)' }}/>
            <div style={{ position:'absolute', inset:0, background:'linear-gradient(to bottom, transparent, #000)' }}/>
            <div style={{ position:'absolute', left:`${hsv.s*100}%`, top:`${(1-hsv.v)*100}%`, transform:'translate(-50%,-50%)',
              width:14, height:14, borderRadius:'50%', border:'2px solid #fff', boxShadow:'0 0 0 1.5px rgba(0,0,0,0.4)', pointerEvents:'none' }}/>
          </div>

          {/* Hue slider */}
          <div ref={hueRef} onMouseDown={onHueDown}
            style={{ position:'relative', height:14, borderRadius:7, cursor:'pointer', marginBottom:10, userSelect:'none',
              background:'linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)' }}>
            <div style={{ position:'absolute', top:'50%', left:`${hsv.h/360*100}%`, transform:'translate(-50%,-50%)',
              width:18, height:18, borderRadius:'50%', background:hueOnly,
              border:'2.5px solid #fff', boxShadow:'0 0 0 1.5px rgba(0,0,0,0.25)', pointerEvents:'none' }}/>
          </div>

          {/* Hex input */}
          <div style={{ display:'flex', alignItems:'center', gap:8 }}>
            <div style={{ width:28, height:28, borderRadius:'50%', background:currentHex, boxShadow:'inset 0 0 0 1px rgba(0,0,0,0.12)', flexShrink:0 }}/>
            <input value={hexInput}
              onChange={e => { setHexInput(e.target.value); if (/^#[0-9a-fA-F]{6}$/.test(e.target.value)) { const h = _hex2hsv(e.target.value); emit(h.h, h.s, h.v); } }}
              onBlur={e => { if (/^#[0-9a-fA-F]{6}$/.test(e.target.value)) onCommit(e.target.value); else setHexInput(currentHex); }}
              style={{ flex:1, padding:'5px 8px', borderRadius:6, border:'1px solid var(--border)', background:'var(--bg)', color:'var(--text)', fontSize:12, fontFamily:'monospace', outline:'none', boxSizing:'border-box' }}/>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { ImageEditorV1 });
