// ─── Animation catalogs ──────────────────────────────────────────
const CANVAS_ASPECT = 62.5;

const ANIM_BY_CATEGORY = {
  'Globos': 'animRiseUp', 'Flores': 'animBounceIn',
  'Mesas': 'animSlideRight', 'Manteles': 'animSlideRight',
  'Pedestales': 'animDropDown', 'Backdrops': 'animFadeIn',
  'Accesorios': 'animFloatIn', 'Letras luminosas': 'animFlipInY', 'Luces': 'animFadeIn',
};
const ANIM_OUT_BY_CATEGORY = {
  'Globos': 'animFlyUp', 'Flores': 'animZoomOut',
  'Mesas': 'animSlideOutLeft', 'Manteles': 'animSlideOutLeft',
  'Pedestales': 'animSinkDown', 'Backdrops': 'animFadeOut',
  'Accesorios': 'animFadeOut', 'Letras luminosas': 'animFlipOutY', 'Luces': 'animFadeOut',
};
const ANIMS_IN = [
  { id: 'animFadeIn',     label: 'Fade',      icon: '◎', color: '#B89A7E' },
  { id: 'animRiseUp',     label: 'Sube',      icon: '↑', color: '#7EB8D4' },
  { id: 'animDropDown',   label: 'Cae',       icon: '↓', color: '#9AB8D4' },
  { id: 'animSlideLeft',  label: '← Slide',  icon: '←', color: '#8BA888' },
  { id: 'animSlideRight', label: 'Slide →',  icon: '→', color: '#7AAF78' },
  { id: 'animZoomIn',     label: 'Zoom',      icon: '⊕', color: '#C4899A' },
  { id: 'animZoomInSoft', label: 'Z.Suave',  icon: '⊙', color: '#D4A0B0' },
  { id: 'animBounceIn',   label: 'Rebote',    icon: '⬆', color: '#E8A870' },
  { id: 'animSpinIn',     label: 'Giro',      icon: '↻', color: '#B88ABE' },
  { id: 'animFlipInX',    label: 'Flip X',    icon: '↕', color: '#8AB4BE' },
  { id: 'animFlipInY',    label: 'Flip Y',    icon: '↔', color: '#7EB8A8' },
  { id: 'animSwingIn',    label: 'Columpio',  icon: '〜', color: '#D4C060' },
  { id: 'animRollIn',     label: 'Rueda',     icon: '◐', color: '#A88870' },
  { id: 'animElastic',    label: 'Elástico',  icon: '⟡', color: '#D488A8' },
  { id: 'animHeartbeat',  label: 'Latido',    icon: '♥', color: '#E87878' },
  { id: 'animFloatIn',    label: 'Flota',     icon: '✦', color: '#9088D4' },
];
const ANIMS_OUT = [
  { id: 'none',              label: 'Ninguna',   icon: '—', color: '#6B5048' },
  { id: 'animFadeOut',       label: 'Fade',      icon: '◎', color: '#B89A7E' },
  { id: 'animFlyUp',         label: 'Vuela ↑',   icon: '↑', color: '#7EB8D4' },
  { id: 'animSinkDown',      label: 'Hunde ↓',   icon: '↓', color: '#9AB8D4' },
  { id: 'animSlideOutLeft',  label: 'Sale ←',    icon: '←', color: '#8BA888' },
  { id: 'animSlideOutRight', label: 'Sale →',    icon: '→', color: '#7AAF78' },
  { id: 'animZoomOut',       label: 'Zoom',      icon: '⊖', color: '#C4899A' },
  { id: 'animZoomOutSoft',   label: 'Z.Suave',   icon: '⊙', color: '#D4A0B0' },
  { id: 'animSpinOut',       label: 'Giro',      icon: '↺', color: '#B88ABE' },
  { id: 'animFlipOutX',      label: 'Flip X',    icon: '↕', color: '#8AB4BE' },
  { id: 'animFlipOutY',      label: 'Flip Y',    icon: '↔', color: '#7EB8A8' },
  { id: 'animShrink',        label: 'Encoge',    icon: '⟡', color: '#D488A8' },
];

// CSS filter presets
const FILTER_PRESETS = [
  { id: 'none',        label: 'Normal',    css: 'none' },
  { id: 'vivid',       label: 'Vívido',    css: 'saturate(1.8) contrast(1.1)' },
  { id: 'soft',        label: 'Suave',     css: 'brightness(1.1) saturate(0.8)' },
  { id: 'warm',        label: 'Cálido',    css: 'sepia(0.4) saturate(1.3) brightness(1.05)' },
  { id: 'cool',        label: 'Frío',      css: 'hue-rotate(200deg) saturate(1.2)' },
  { id: 'mono',        label: 'B&N',       css: 'grayscale(1)' },
  { id: 'vintage',     label: 'Vintage',   css: 'sepia(0.7) contrast(0.9) brightness(0.95)' },
  { id: 'neon',        label: 'Neón',      css: 'saturate(3) contrast(1.3) brightness(0.9)' },
  { id: 'dreamy',      label: 'Dreamy',    css: 'blur(0.5px) brightness(1.15) saturate(1.2)' },
  { id: 'dark',        label: 'Oscuro',    css: 'brightness(0.6) contrast(1.2)' },
];

// ─── Remove BG with canvas sweep ────────────────────────────────
function removeBackground(imgEl, threshold = 30) {
  return new Promise((resolve) => {
    const canvas = document.createElement('canvas');
    const w = imgEl.naturalWidth || imgEl.width;
    const h = imgEl.naturalHeight || imgEl.height;
    canvas.width = w; canvas.height = h;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(imgEl, 0, 0, w, h);
    const data = ctx.getImageData(0, 0, w, h);
    const d = data.data;
    // Sample corners to detect background color
    const corners = [0, (w - 1) * 4, (h - 1) * w * 4, ((h - 1) * w + w - 1) * 4];
    let bgR = 0, bgG = 0, bgB = 0;
    corners.forEach(i => { bgR += d[i]; bgG += d[i + 1]; bgB += d[i + 2]; });
    bgR = Math.round(bgR / 4); bgG = Math.round(bgG / 4); bgB = Math.round(bgB / 4);
    // Remove pixels close to bg color
    for (let i = 0; i < d.length; i += 4) {
      const dr = Math.abs(d[i] - bgR), dg = Math.abs(d[i+1] - bgG), db = Math.abs(d[i+2] - bgB);
      if (dr + dg + db < threshold * 3) d[i + 3] = 0;
    }
    ctx.putImageData(data, 0, 0);
    // Use PNG to preserve transparency, but cap dimensions to reduce storage size
    const MAX = 512;
    if (w > MAX || h > MAX) {
      const scale = Math.min(MAX / w, MAX / h);
      const small = document.createElement('canvas');
      small.width = Math.round(w * scale);
      small.height = Math.round(h * scale);
      small.getContext('2d').drawImage(canvas, 0, 0, small.width, small.height);
      resolve(small.toDataURL('image/png'));
    } else {
      resolve(canvas.toDataURL('image/png'));
    }
  });
}

// ─── Context Menu component ──────────────────────────────────────
function ContextMenu({ x, y, items, onClose }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    setTimeout(() => document.addEventListener('mousedown', close), 0);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  // Clamp to viewport
  const [pos, setPos] = React.useState({ left: x, top: y });
  React.useEffect(() => {
    if (!ref.current) return;
    const rect = ref.current.getBoundingClientRect();
    const vw = window.innerWidth, vh = window.innerHeight;
    setPos({
      left: Math.min(x, vw - rect.width - 8),
      top:  Math.min(y, vh - rect.height - 8),
    });
  }, [x, y]);

  return (
    <div ref={ref} style={{
      position: 'fixed', left: pos.left, top: pos.top,
      background: '#1E1818', border: '1px solid #4A3028',
      borderRadius: 10, padding: '4px 0', zIndex: 9999,
      minWidth: 160, boxShadow: '0 8px 32px rgba(0,0,0,0.7)',
      backdropFilter: 'blur(10px)',
      animation: 'animZoomInSoft 0.12s ease both',
    }}>
      {items.map((item, i) =>
        item === 'divider'
          ? <div key={i} style={{ height: 1, background: '#3D2C24', margin: '3px 0' }} />
          : (
            <button key={i} onClick={() => { item.action(); onClose(); }} style={{
              width: '100%', textAlign: 'left', background: 'none', border: 'none',
              padding: '8px 14px', cursor: 'pointer', fontSize: 12, color: item.danger ? '#E08080' : '#C8B8B0',
              display: 'flex', alignItems: 'center', gap: 8, fontFamily: "'DM Sans', sans-serif",
              transition: 'background 0.1s',
            }}
              onMouseEnter={e => e.currentTarget.style.background = '#3D2C24'}
              onMouseLeave={e => e.currentTarget.style.background = 'none'}
            >
              <span style={{ width: 16, textAlign: 'center', fontSize: 13 }}>{item.icon}</span>
              {item.label}
            </button>
          )
      )}
    </div>
  );
}

// ─── BG helpers ──────────────────────────────────────────────────
// sceneBg is stored as an object: { src, opacity, brightness, contrast, saturate, blur, size, posX, posY }
// Legacy: if it arrives as a string, normalise to object on first read
const normBg = (raw) => {
  if (!raw) return null;
  if (typeof raw === 'string') return { src: raw, opacity: 100, brightness: 100, contrast: 100, saturate: 100, blur: 0, size: 'cover', posX: 50, posY: 50 };
  return raw;
};
const BG_DEFAULTS = { opacity: 100, brightness: 100, contrast: 100, saturate: 100, blur: 0, size: 'cover', posX: 50, posY: 50 };

// ─── Main SceneEditor ────────────────────────────────────────────
function SceneEditor({ design = {}, assets = [], sceneAssets = [], onChange, sceneBg: sceneBgRaw, onChangeBg }) {
  const sceneBg = normBg(sceneBgRaw);

  const [selected, setSelected]   = React.useState(null);
  const [playing, setPlaying]     = React.useState(false);
  const [playKey, setPlayKey]     = React.useState(0);
  const [panelTab, setPanelTab]   = React.useState('assets');
  const [propTab, setPropTab]     = React.useState('transform'); // 'transform'|'anim'|'fx'
  const [mobileSheet, setMobileSheet] = React.useState(null);   // null|'left'|'right'
  const [ctxMenu, setCtxMenu]     = React.useState(null);       // {x,y,index}
  const [removingBg, setRemovingBg] = React.useState(false);
  const [sweepPct, setSweepPct]   = React.useState(0);
  const [bgExpanded, setBgExpanded] = React.useState(false);
  const [storageWarn, setStorageWarn] = React.useState(false);
  const canvasRef  = React.useRef(null);

  React.useEffect(() => {
    const handler = () => { setStorageWarn(true); setTimeout(() => setStorageWarn(false), 6000); };
    window.addEventListener('lelusa:quota-exceeded', handler);
    return () => window.removeEventListener('lelusa:quota-exceeded', handler);
  }, []);
  const dragging   = React.useRef(null);
  const resizing   = React.useRef(null);
  const longPress  = React.useRef(null);
  const bgFileRef  = React.useRef(null);

  const updateBg = (patch) => onChangeBg && onChangeBg({ ...(sceneBg || BG_DEFAULTS), ...patch });
  const resetBg  = () => onChangeBg && onChangeBg(null);

  const isMobile = () => window.innerWidth < 768;

  const designMaterials = React.useMemo(() => {
    if (Array.isArray(design.materials) && design.materials.length) return design.materials;
    return (design.assetIds || []).map((assetId, index) => ({
      assetId,
      quantity: 1,
      required: true,
      sortOrder: index,
    }));
  }, [design.materials, design.assetIds]);

  const designAssets = React.useMemo(() => {
    return designMaterials
      .map((material, index) => {
        const asset = assets.find(a => String(a.id) === String(material.assetId));
        return asset ? { ...asset, material: { ...material, sortOrder: material.sortOrder ?? index } } : null;
      })
      .filter(Boolean)
      .sort((a, b) => (a.material.sortOrder || 0) - (b.material.sortOrder || 0));
  }, [assets, designMaterials]);

  const grouped = React.useMemo(() => {
    const g = {};
    designAssets.forEach(a => {
      if (!g[a.category]) g[a.category] = [];
      g[a.category].push(a);
    });
    return g;
  }, [designAssets]);

  const resolvedItems = sceneAssets.map(sa => ({
    ...sa,
    asset: assets.find(a => String(a.id) === String(sa.assetId)),
  })).filter(sa => sa.asset);

  const sel = selected !== null ? sceneAssets[selected] : null;
  const selResolved = selected !== null ? resolvedItems[selected] : null;

  // ── Dismiss on outside click ─────────────────────────────────
  React.useEffect(() => {
    const handler = () => setCtxMenu(null);
    if (ctxMenu) document.addEventListener('click', handler, { once: true });
    return () => document.removeEventListener('click', handler);
  }, [ctxMenu]);

  // ── Drop from panel ──────────────────────────────────────────
  const handlePanelDragStart = (e, asset) => {
    e.dataTransfer.setData('assetId', String(asset.id));
  };

  const handleCanvasDrop = (e) => {
    e.preventDefault();
    const assetId = Number(e.dataTransfer.getData('assetId'));
    if (!assetId) return;
    const rect = canvasRef.current.getBoundingClientRect();
    const x = Math.round(((e.clientX - rect.left) / rect.width) * 100);
    const y = Math.round(((e.clientY - rect.top) / rect.height) * 100);
    const existingZ = sceneAssets.reduce((max, sa) => Math.max(max, sa.z || 0), -1);
    const z = Math.min(existingZ + 1, 4);
    const droppedAsset = assets.find(a => String(a.id) === String(assetId));
    const cat = droppedAsset ? droppedAsset.category : '';
    const animIn = ANIM_BY_CATEGORY[cat] || 'animFadeIn';
    const animOut = ANIM_OUT_BY_CATEGORY[cat] || 'none';
    const newItem = { assetId, x, y, scale: 1, z, delay: z * 120, delayManual: false, animIn, animOut, flipped: false, filter: 'none', opacity: 100 };
    const next = [...sceneAssets, newItem];
    onChange(next);
    setSelected(next.length - 1);
  };
  const handleCanvasDragOver = (e) => e.preventDefault();

  // ── Move drag ────────────────────────────────────────────────
  const startMoveDrag = (clientX, clientY, index) => {
    const rect = canvasRef.current.getBoundingClientRect();
    dragging.current = {
      index, rect,
      startX: clientX, startY: clientY,
      startPctX: sceneAssets[index].x || 0,
      startPctY: sceneAssets[index].y || 0,
    };
    const onMove = (mv) => {
      if (!dragging.current) return;
      if (mv.touches && mv.cancelable) mv.preventDefault(); // keep the page from scrolling while dragging an asset
      const { index: idx, startX: sx, startY: sy, startPctX: spx, startPctY: spy, rect: r } = dragging.current;
      const cx = mv.clientX !== undefined ? mv.clientX : mv.touches[0].clientX;
      const cy = mv.clientY !== undefined ? mv.clientY : mv.touches[0].clientY;
      const dx = ((cx - sx) / r.width) * 100;
      const dy = ((cy - sy) / r.height) * 100;
      onChange(sceneAssets.map((sa, i) => i === idx ? { ...sa, x: Math.round(Math.min(100, Math.max(0, spx + dx))), y: Math.round(Math.min(100, Math.max(0, spy + dy))) } : sa));
    };
    const onUp = () => {
      dragging.current = null;
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onUp);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    window.addEventListener('touchmove', onMove, { passive: false });
    window.addEventListener('touchend', onUp);
  };

  const handleItemMouseDown = (e, index) => {
    if (e.button !== 0) return;
    e.preventDefault(); e.stopPropagation();
    setSelected(index);
    setCtxMenu(null);
    startMoveDrag(e.clientX, e.clientY, index);
  };

  const handleItemTouchStart = (e, index) => {
    e.stopPropagation();
    setSelected(index);
    const touch = e.touches[0];
    // Long press → context menu
    longPress.current = setTimeout(() => {
      setCtxMenu({ x: touch.clientX, y: touch.clientY, index });
    }, 500);
    startMoveDrag(touch.clientX, touch.clientY, index);
  };

  const handleItemTouchEnd = () => {
    clearTimeout(longPress.current);
  };

  // ── Resize drag (corner handle) ──────────────────────────────
  const startResize = (e, index) => {
    e.preventDefault(); e.stopPropagation();
    const rect = canvasRef.current.getBoundingClientRect();
    const startScale = sceneAssets[index].scale || 1;
    const startX = e.clientX;
    resizing.current = { index, startScale, startX, rect };
    const onMove = (mv) => {
      if (!resizing.current) return;
      const { index: idx, startScale: ss, startX: sx, rect: r } = resizing.current;
      const dx = (mv.clientX - sx) / r.width;
      const newScale = Math.max(0.1, Math.min(10, ss + dx * 10));
      onChange(sceneAssets.map((sa, i) => i === idx ? { ...sa, scale: Math.round(newScale * 10) / 10 } : sa));
    };
    const onUp = () => {
      resizing.current = null;
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  };

  // ── Property update ──────────────────────────────────────────
  const updateSelected = (patch) => {
    if (selected === null) return;
    onChange(sceneAssets.map((sa, i) => {
      if (i !== selected) return sa;
      const updated = { ...sa, ...patch };
      if ('z' in patch && !('delay' in patch) && !updated.delayManual) updated.delay = patch.z * 120;
      if ('delay' in patch) updated.delayManual = true;
      return updated;
    }));
  };

  const updateAt = (index, patch) => {
    onChange(sceneAssets.map((sa, i) => {
      if (i !== index) return sa;
      const updated = { ...sa, ...patch };
      if ('z' in patch && !('delay' in patch) && !updated.delayManual) updated.delay = patch.z * 120;
      return updated;
    }));
  };

  const duplicateItem = (index) => {
    const src = sceneAssets[index];
    const copy = { ...src, x: Math.min(100, (src.x || 0) + 5), y: Math.min(100, (src.y || 0) + 5) };
    const next = [...sceneAssets, copy];
    onChange(next);
    setSelected(next.length - 1);
  };

  const removeItem = (index) => {
    onChange(sceneAssets.filter((_, i) => i !== index));
    if (selected === index) setSelected(null);
    else if (selected > index) setSelected(selected - 1);
  };

  const removeSelected = () => { if (selected !== null) removeItem(selected); };
  const clearAll = () => { onChange([]); setSelected(null); };
  const handlePlay = () => { setPlaying(false); setTimeout(() => { setPlaying(true); setPlayKey(k => k + 1); }, 50); };

  // ── Remove BG with sweep ─────────────────────────────────────
  const doRemoveBg = async () => {
    if (!selResolved || !selResolved.asset.image) return;
    setRemovingBg(true);
    setSweepPct(0);
    // Animate sweep
    let pct = 0;
    const interval = setInterval(() => {
      pct += 3;
      setSweepPct(pct);
      if (pct >= 100) clearInterval(interval);
    }, 30);
    try {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.src = selResolved.asset.image;
      await new Promise((res, rej) => { img.onload = res; img.onerror = rej; });
      const newSrc = await removeBackground(img, 40);
      updateSelected({ removedBgSrc: newSrc });
    } catch (err) {
      console.error('Remove BG error:', err);
    }
    clearInterval(interval);
    setSweepPct(100);
    setTimeout(() => { setRemovingBg(false); setSweepPct(0); }, 400);
  };

  // ── Background upload (compressed to reduce localStorage size) ──
  const handleBgFile = (e) => {
    const file = e.target.files[0];
    if (!file) return;
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      URL.revokeObjectURL(url);
      const MAX = 800;
      const scale = Math.min(1, MAX / Math.max(img.width, img.height));
      const w = Math.round(img.width * scale);
      const h = Math.round(img.height * scale);
      const cvs = document.createElement('canvas');
      cvs.width = w; cvs.height = h;
      cvs.getContext('2d').drawImage(img, 0, 0, w, h);
      const compressed = cvs.toDataURL('image/jpeg', 0.75);
      updateBg({ src: compressed });
    };
    img.src = url;
  };

  // ── Context menu items ───────────────────────────────────────
  const buildCtxItems = (index) => {
    const sa = sceneAssets[index];
    return [
      { icon: '⬆', label: 'Subir capa', action: () => updateAt(index, { z: Math.min(4, (sa.z || 0) + 1) }) },
      { icon: '⬇', label: 'Bajar capa', action: () => updateAt(index, { z: Math.max(0, (sa.z || 0) - 1) }) },
      'divider',
      { icon: '⊕', label: 'Duplicar', action: () => duplicateItem(index) },
      { icon: '↔', label: sa.flipped ? 'Flip: quitar' : 'Flip horizontal', action: () => updateAt(index, { flipped: !sa.flipped }) },
      'divider',
      { icon: '🎨', label: 'Propiedades', action: () => { setSelected(index); if (isMobile()) setMobileSheet('right'); } },
      'divider',
      { icon: '✕', label: 'Eliminar', danger: true, action: () => removeItem(index) },
    ];
  };

  const handleItemContextMenu = (e, index) => {
    e.preventDefault(); e.stopPropagation();
    setSelected(index);
    setCtxMenu({ x: e.clientX, y: e.clientY, index });
  };

  // ─────────────────────────────────────────────────────────────
  // Render helpers
  // ─────────────────────────────────────────────────────────────
  const P = '#C4899A';
  const DK = '#1E1818';
  const MD = '#2A1F1A';
  const BRD = '#3D2C24';
  const MUT = '#6B5048';
  const DIM = '#A07868';
  const LT = '#E8D8CC';

  const tabBtn = (active, color = P) => ({
    flex: 1, padding: '6px 0', fontSize: 10, fontWeight: active ? 700 : 500,
    background: active ? color + '22' : 'transparent',
    color: active ? color : MUT, border: 'none', cursor: 'pointer',
    borderRadius: 6, transition: 'all 0.15s',
    borderBottom: active ? `2px solid ${color}` : '2px solid transparent',
  });

  const sectionLabel = { fontSize: 9, fontWeight: 700, color: MUT, letterSpacing: 0.8, marginBottom: 8 };
  const sectionWrap = { marginBottom: 14 };
  const sliderRow = { fontSize: 10, color: MUT, marginBottom: 3, display: 'flex', justifyContent: 'space-between', alignItems: 'center' };
  const valBadge = (c = P) => ({ background: DK, padding: '1px 6px', borderRadius: 4, color: c, fontWeight: 700, fontSize: 9 });
  const iconBtn = (col = MUT, bg = 'transparent') => ({
    background: bg, border: `1px solid ${BRD}`, color: col,
    borderRadius: 6, width: 22, height: 22, cursor: 'pointer',
    fontSize: 11, display: 'flex', alignItems: 'center', justifyContent: 'center',
    transition: 'all 0.1s', flexShrink: 0,
  });

  // ── Left panel ────────────────────────────────────────────────
  // Elemento JSX (no componente) para identidad estable y evitar remontar
  // los inputs del panel al re-renderizar el editor.
  const leftPanel = (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: MD }}>
      {/* Header */}
      <div style={{ padding: '12px 12px 8px', borderBottom: `1px solid ${BRD}`, flexShrink: 0 }}>
        <div style={{ fontSize: 9, fontWeight: 800, color: DIM, letterSpacing: 1.2, marginBottom: 8 }}>ELEMENTOS</div>
        <div style={{ display: 'flex', gap: 3, background: DK, borderRadius: 8, padding: 3 }}>
          {['assets', 'capas'].map(t => (
            <button key={t} style={tabBtn(panelTab === t)} onClick={() => setPanelTab(t)}>
              {t === 'assets' ? 'Assets' : 'Capas'}
            </button>
          ))}
        </div>
      </div>

      {/* Background section — header fixed, adjustments scroll with list */}
      <div style={{ borderBottom: `1px solid ${BRD}`, flexShrink: 0 }}>
        {/* Header row */}
        <div style={{ padding: '8px 10px 6px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div style={{ fontSize: 9, fontWeight: 700, color: MUT, letterSpacing: 0.8 }}>FONDO DEL CANVAS</div>
          {sceneBg && (
            <button onClick={() => setBgExpanded(x => !x)} style={{
              background: 'none', border: 'none', color: bgExpanded ? P : MUT, fontSize: 10, cursor: 'pointer', padding: '0 2px',
            }}>{bgExpanded ? '▴' : '▾'} Ajustes</button>
          )}
        </div>

        {/* Upload row */}
        <div style={{ padding: '0 10px 8px', display: 'flex', gap: 4, alignItems: 'center' }}>
          <div style={{
            width: 36, height: 36, borderRadius: 6, border: `1px solid ${BRD}`,
            background: sceneBg ? `url(${sceneBg.src}) center/cover` : DK,
            flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            {!sceneBg && <span style={{ fontSize: 14 }}>🖼</span>}
          </div>
          <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 3 }}>
            <button onClick={() => bgFileRef.current && bgFileRef.current.click()}
              style={{ background: BRD, border: 'none', color: LT, borderRadius: 5, padding: '3px 7px', fontSize: 9, cursor: 'pointer', fontWeight: 600, textAlign: 'left' }}>
              ⬆ Subir imagen
            </button>
            {sceneBg && (
              <button onClick={resetBg}
                style={{ background: 'transparent', border: 'none', color: MUT, borderRadius: 5, padding: '1px 0', fontSize: 9, cursor: 'pointer', textAlign: 'left' }}>
                ✕ Quitar fondo
              </button>
            )}
          </div>
          <input ref={bgFileRef} type="file" accept="image/*" style={{ display: 'none' }} onChange={handleBgFile} />
        </div>

        {/* URL input */}
        <div style={{ padding: '0 10px 8px' }}>
          <input type="text" placeholder="URL o ruta..."
            value={sceneBg && sceneBg.src && sceneBg.src.startsWith('http') ? sceneBg.src : ''}
            onChange={e => e.target.value ? updateBg({ src: e.target.value }) : resetBg()}
            style={{
              width: '100%', background: DK, border: `1px solid ${BRD}`,
              borderRadius: 5, padding: '3px 7px', fontSize: 9, color: LT,
              outline: 'none', boxSizing: 'border-box', fontFamily: "'DM Sans', sans-serif",
            }}
          />
        </div>
      </div>

      {/* Assets / Capas list — also contains bg adjustments when expanded */}
      <div style={{ flex: 1, overflowY: 'auto', padding: '6px 8px 10px' }}>

        {/* BG adjustments (scrollable, visible only when expanded) */}
        {sceneBg && bgExpanded && (
          <div style={{ background: DK, border: `1px solid ${BRD}`, borderRadius: 8, padding: '8px 10px', marginBottom: 10 }}>
            <div style={{ fontSize: 8, fontWeight: 700, color: MUT, letterSpacing: 0.8, marginBottom: 8 }}>AJUSTES DE FONDO</div>

            {/* Fit mode */}
            <div style={{ marginBottom: 9 }}>
              <div style={{ fontSize: 8, color: MUT, marginBottom: 4 }}>ENCUADRE</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 3 }}>
                {[
                  { val: 'cover',     label: 'Cubrir'    },
                  { val: 'contain',   label: 'Contener'  },
                  { val: '100% 100%', label: 'Estirar'   },
                ].map(({ val, label }) => (
                  <button key={val} onClick={() => updateBg({ size: val })} style={{
                    padding: '4px 2px', fontSize: 8, borderRadius: 5, cursor: 'pointer', border: 'none',
                    background: (sceneBg.size || 'cover') === val ? P + '33' : '#2A1F1A',
                    color: (sceneBg.size || 'cover') === val ? P : MUT,
                    fontWeight: (sceneBg.size || 'cover') === val ? 700 : 400,
                  }}>{label}</button>
                ))}
              </div>
            </div>

            {/* Position X/Y */}
            {[
              { key: 'posX', label: 'Posición X', min: 0, max: 100, unit: '%', def: 50, color: P },
              { key: 'posY', label: 'Posición Y', min: 0, max: 100, unit: '%', def: 50, color: P },
            ].map(({ key, label, min, max, unit, def, color }) => (
              <div key={key} style={{ marginBottom: 7 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 8, color: MUT, marginBottom: 2 }}>
                  <span>{label}</span>
                  <span style={{ color, fontWeight: 700 }}>{sceneBg[key] ?? def}{unit}</span>
                </div>
                <input type="range" title={label} min={min} max={max} value={sceneBg[key] ?? def}
                  onChange={e => updateBg({ [key]: Number(e.target.value) })}
                  style={{ width: '100%', accentColor: color, height: 3 }} />
              </div>
            ))}

            <div style={{ height: 1, background: '#3D2C24', margin: '6px 0' }} />

            {/* Visual filters */}
            {[
              { key: 'opacity',    label: 'Opacidad',   min: 0,  max: 100, unit: '%',  def: 100 },
              { key: 'brightness', label: 'Brillo',     min: 0,  max: 200, unit: '%',  def: 100 },
              { key: 'contrast',   label: 'Contraste',  min: 0,  max: 200, unit: '%',  def: 100 },
              { key: 'saturate',   label: 'Saturación', min: 0,  max: 300, unit: '%',  def: 100 },
              { key: 'blur',       label: 'Desenfoque', min: 0,  max: 20,  unit: 'px', def: 0   },
            ].map(({ key, label, min, max, unit, def }) => {
              const val = sceneBg[key] ?? def;
              const changed = val !== def;
              return (
                <div key={key} style={{ marginBottom: 7 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 8, color: MUT, marginBottom: 2 }}>
                    <span>{label}</span>
                    <span style={{ color: changed ? '#7EB8D4' : MUT, fontWeight: 700 }}>{val}{unit}</span>
                  </div>
                  <input type="range" title={label} min={min} max={max} value={val}
                    onChange={e => updateBg({ [key]: Number(e.target.value) })}
                    style={{ width: '100%', accentColor: '#7EB8D4', height: 3 }} />
                </div>
              );
            })}

            <button onClick={() => updateBg({ ...BG_DEFAULTS })} style={{
              width: '100%', background: 'transparent', border: `1px solid #3D2C24`,
              color: MUT, borderRadius: 5, padding: '4px 0', fontSize: 8, cursor: 'pointer', marginTop: 2,
            }}>↺ Restaurar ajustes</button>
          </div>
        )}
        {panelTab === 'assets' && (
          designAssets.length === 0 ? (
            <div style={{ padding: '24px 8px', textAlign: 'center' }}>
              <div style={{ fontSize: 24, marginBottom: 8 }}>📦</div>
              <div style={{ fontSize: 10, color: MUT, lineHeight: 1.6 }}>Sin materiales asignados.<br />Editá la decoración para agregar.</div>
            </div>
          ) : Object.entries(grouped).map(([cat, catAssets]) => (
            <div key={cat} style={{ marginBottom: 10 }}>
              <div style={{ fontSize: 8, fontWeight: 700, color: MUT, letterSpacing: 1, marginBottom: 4, marginTop: 6, paddingLeft: 2 }}>
                {cat.toUpperCase()}
              </div>
              {catAssets.map(asset => {
                const animName = ANIM_BY_CATEGORY[asset.category] || 'animFadeIn';
                const animDef = ANIMS_IN.find(a => a.id === animName) || ANIMS_IN[0];
                return (
                  <div
                    key={asset.id}
                    draggable
                    onDragStart={e => handlePanelDragStart(e, asset)}
                    style={{
                      background: '#2E2320', border: `1px solid ${BRD}`, borderRadius: 8,
                      padding: '5px 7px', marginBottom: 4, cursor: 'grab',
                      display: 'flex', alignItems: 'center', gap: 7, transition: 'all 0.1s',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.background = BRD; e.currentTarget.style.borderColor = P + '44'; }}
                    onMouseLeave={e => { e.currentTarget.style.background = '#2E2320'; e.currentTarget.style.borderColor = BRD; }}
                  >
                    <div style={{ width: 32, height: 32, borderRadius: 6, background: DK, flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                      {asset.image ? <img src={asset.image} style={{ width: 28, height: 28, objectFit: 'contain' }} /> : '📦'}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 10, fontWeight: 600, color: LT, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{asset.name}</div>
                      <div style={{ fontSize: 8, fontWeight: 700, color: animDef.color, marginTop: 1 }}>{animDef.icon} {animDef.label}</div>
                      <div style={{ fontSize: 8, color: MUT, marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        x{asset.material.quantity || 1}
                        {asset.material.role ? ` · ${asset.material.role}` : ''}
                        {asset.material.variant ? ` · ${asset.material.variant}` : ''}
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>
          ))
        )}

        {panelTab === 'capas' && (
          resolvedItems.length === 0 ? (
            <div style={{ padding: 20, textAlign: 'center', fontSize: 10, color: MUT }}>Canvas vacío</div>
          ) : [...resolvedItems].reverse().map((sa, ri) => {
            const realIdx = resolvedItems.length - 1 - ri;
            const isSel = selected === realIdx;
            const animDef = ANIMS_IN.find(a => a.id === (sa.animIn || 'animFadeIn')) || ANIMS_IN[0];
            return (
              <div key={realIdx} onClick={() => setSelected(realIdx)} style={{
                padding: '6px 8px', marginBottom: 3, borderRadius: 7,
                background: isSel ? P + '22' : 'transparent',
                border: `1px solid ${isSel ? P + '44' : 'transparent'}`,
                cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 7, transition: 'all 0.1s',
              }}>
                <div style={{ width: 5, height: 5, borderRadius: '50%', background: animDef.color, flexShrink: 0 }} />
                <div style={{ width: 26, height: 26, borderRadius: 5, background: DK, flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  {sa.asset.image ? <img src={sa.asset.image} style={{ width: 22, height: 22, objectFit: 'contain' }} /> : '📦'}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 10, fontWeight: 600, color: isSel ? LT : DIM, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{sa.asset.name}</div>
                  <div style={{ fontSize: 8, color: MUT }}>Capa {sa.z} · {sa.delay}ms</div>
                </div>
              </div>
            );
          })
        )}
      </div>

      <div style={{ padding: '6px 10px', borderTop: `1px solid ${BRD}`, fontSize: 9, color: MUT, display: 'flex', justifyContent: 'space-between' }}>
        <span>{designAssets.length} materiales</span>
        <span>{resolvedItems.length} en escena</span>
      </div>
    </div>
  );

  // ── Right panel ───────────────────────────────────────────────
  const rightPanel = (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: MD }}>
      <div style={{ padding: '12px 12px 8px', borderBottom: `1px solid ${BRD}`, flexShrink: 0 }}>
        <div style={{ fontSize: 9, fontWeight: 800, color: DIM, letterSpacing: 1.2, marginBottom: 8 }}>PROPIEDADES</div>
        {sel && selResolved && (
          <div style={{ display: 'flex', gap: 3, background: DK, borderRadius: 8, padding: 3 }}>
            {[['transform', '⊹ Pos'], ['anim', '▶ Anim'], ['fx', '✦ FX']].map(([id, lbl]) => (
              <button key={id} style={tabBtn(propTab === id, P)} onClick={() => setPropTab(id)}>{lbl}</button>
            ))}
          </div>
        )}
      </div>

      {sel && selResolved ? (
        <div style={{ flex: 1, overflowY: 'auto', padding: '10px 10px 16px' }}>
          {/* Asset card */}
          <div style={{
            background: DK, border: `1px solid ${BRD}`, borderRadius: 9,
            padding: '7px 9px', marginBottom: 12, display: 'flex', alignItems: 'center', gap: 7,
          }}>
            <div style={{ width: 30, height: 30, borderRadius: 6, background: '#151010', flexShrink: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              {selResolved.asset.image ? <img src={selResolved.asset.image} style={{ width: 26, height: 26, objectFit: 'contain' }} /> : '📦'}
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 10, fontWeight: 700, color: LT, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{selResolved.asset.name}</div>
              <div style={{ fontSize: 8, color: MUT }}>{selResolved.asset.category || '—'}</div>
            </div>
            {/* Quick action buttons */}
            <div style={{ display: 'flex', gap: 3 }}>
              <button style={iconBtn('#7EB8D4')} title="Duplicar" onClick={() => duplicateItem(selected)}>⊕</button>
              <button style={iconBtn('#D4C060')} title="Flip" onClick={() => updateSelected({ flipped: !sel.flipped })}>↔</button>
              <button style={iconBtn('#E08080')} title="Eliminar" onClick={removeSelected}>✕</button>
            </div>
          </div>

          {/* ── TRANSFORM tab ─────────────────────────────── */}
          {propTab === 'transform' && <>
            <div style={sectionWrap}>
              <div style={sectionLabel}>POSICIÓN</div>
              {['x', 'y'].map(axis => (
                <div key={axis} style={{ marginBottom: 7 }}>
                  <div style={sliderRow}>
                    <span style={{ color: DIM, fontWeight: 600 }}>{axis === 'x' ? 'Horizontal' : 'Vertical'}</span>
                    <span style={valBadge()}>{sel[axis] || 0}%</span>
                  </div>
                  <input type="range" min={0} max={100} value={sel[axis] || 0}
                    onChange={e => updateSelected({ [axis]: Number(e.target.value) })}
                    style={{ width: '100%', accentColor: P, height: 3 }} />
                </div>
              ))}
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>TAMAÑO</div>
              <div style={sliderRow}>
                <span style={{ color: DIM, fontWeight: 600 }}>Escala</span>
                <span style={valBadge()}>{(sel.scale || 1).toFixed(1)}×</span>
              </div>
              <input type="range" min={1} max={80} value={Math.round((sel.scale || 1) * 10)}
                onChange={e => updateSelected({ scale: Number(e.target.value) / 10 })}
                style={{ width: '100%', accentColor: P, height: 3 }} />
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>CAPA (Z-INDEX)</div>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 3 }}>
                {[0, 1, 2, 3, 4].map(z => (
                  <button key={z} onClick={() => updateSelected({ z })} style={{
                    padding: '5px 0', fontSize: 10, border: 'none', borderRadius: 5, cursor: 'pointer',
                    background: (sel.z || 0) === z ? P : DK,
                    color: (sel.z || 0) === z ? '#fff' : MUT,
                    fontWeight: (sel.z || 0) === z ? 800 : 500, transition: 'all 0.12s',
                    boxShadow: (sel.z || 0) === z ? `0 2px 6px ${P}44` : 'none',
                  }}>{z}</button>
                ))}
              </div>
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>DELAY</div>
              <div style={sliderRow}>
                <span style={{ color: DIM, fontWeight: 600 }}>Espera</span>
                <span style={valBadge(sel.delayManual ? '#7EB8D4' : '#8BA888')}>{sel.delay || 0}ms</span>
              </div>
              <input type="range" min={0} max={2000} step={50} value={sel.delay || 0}
                onChange={e => updateSelected({ delay: Number(e.target.value) })}
                style={{ width: '100%', accentColor: sel.delayManual ? '#7EB8D4' : '#8BA888', height: 3 }} />
              <div style={{ fontSize: 8, color: '#4A3028', marginTop: 2, textAlign: 'right' }}>
                {sel.delayManual ? '(manual)' : '(auto por capa)'}
              </div>
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>OPACIDAD</div>
              <div style={sliderRow}>
                <span style={{ color: DIM, fontWeight: 600 }}>Transparencia</span>
                <span style={valBadge()}>{sel.opacity ?? 100}%</span>
              </div>
              <input type="range" min={0} max={100} value={sel.opacity ?? 100}
                onChange={e => updateSelected({ opacity: Number(e.target.value) })}
                style={{ width: '100%', accentColor: P, height: 3 }} />
            </div>

            <div style={{ height: 1, background: BRD, margin: '6px 0 10px' }} />
            <button onClick={removeSelected} style={{
              width: '100%', background: '#3D1A1A', color: '#E08080',
              border: '1px solid #5D2A2A', borderRadius: 7, padding: '7px 0',
              fontSize: 10, cursor: 'pointer', fontWeight: 700, transition: 'all 0.1s',
            }}
              onMouseEnter={e => { e.currentTarget.style.background = '#5D2020'; }}
              onMouseLeave={e => { e.currentTarget.style.background = '#3D1A1A'; }}
            >✕ Quitar del canvas</button>
          </>}

          {/* ── ANIM tab ──────────────────────────────────── */}
          {propTab === 'anim' && <>
            <div style={sectionWrap}>
              <div style={sectionLabel}>ANIMACIÓN DE ENTRADA</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
                {ANIMS_IN.map(anim => {
                  const active = (sel.animIn || ANIM_BY_CATEGORY[selResolved?.asset?.category] || 'animFadeIn') === anim.id;
                  return (
                    <button key={anim.id} onClick={() => updateSelected({ animIn: anim.id })} title={anim.label}
                      style={{
                        padding: '4px 4px', fontSize: 9, cursor: 'pointer', borderRadius: 5,
                        border: `1px solid ${active ? anim.color + '88' : BRD}`,
                        background: active ? anim.color + '22' : DK, color: active ? anim.color : MUT,
                        fontWeight: active ? 700 : 400, transition: 'all 0.1s',
                        display: 'flex', alignItems: 'center', gap: 3, overflow: 'hidden',
                      }}>
                      <span style={{ flexShrink: 0 }}>{anim.icon}</span>
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{anim.label}</span>
                    </button>
                  );
                })}
              </div>
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>ANIMACIÓN DE SALIDA</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
                {ANIMS_OUT.map(anim => {
                  const active = (sel.animOut || 'none') === anim.id;
                  return (
                    <button key={anim.id} onClick={() => updateSelected({ animOut: anim.id })} title={anim.label}
                      style={{
                        padding: '4px 4px', fontSize: 9, cursor: 'pointer', borderRadius: 5,
                        border: `1px solid ${active ? anim.color + '88' : BRD}`,
                        background: active ? anim.color + '22' : DK, color: active ? anim.color : MUT,
                        fontWeight: active ? 700 : 400, transition: 'all 0.1s',
                        display: 'flex', alignItems: 'center', gap: 3, overflow: 'hidden',
                      }}>
                      <span style={{ flexShrink: 0 }}>{anim.icon}</span>
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{anim.label}</span>
                    </button>
                  );
                })}
              </div>
            </div>
          </>}

          {/* ── FX tab ────────────────────────────────────── */}
          {propTab === 'fx' && <>
            <div style={sectionWrap}>
              <div style={sectionLabel}>FILTROS CSS</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
                {FILTER_PRESETS.map(fp => {
                  const active = (sel.filter || 'none') === fp.id;
                  return (
                    <button key={fp.id} onClick={() => updateSelected({ filter: fp.id })}
                      style={{
                        padding: '5px 4px', fontSize: 9, borderRadius: 5, cursor: 'pointer',
                        border: `1px solid ${active ? P + '88' : BRD}`,
                        background: active ? P + '22' : DK, color: active ? P : MUT,
                        fontWeight: active ? 700 : 400, transition: 'all 0.1s', textAlign: 'center',
                      }}>{fp.label}</button>
                  );
                })}
              </div>
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>AJUSTES MANUALES</div>
              {[
                { key: 'brightness', label: 'Brillo', min: 0, max: 200, def: 100, unit: '%' },
                { key: 'contrast',   label: 'Contraste', min: 0, max: 200, def: 100, unit: '%' },
                { key: 'saturate',   label: 'Saturación', min: 0, max: 300, def: 100, unit: '%' },
                { key: 'blur',       label: 'Blur', min: 0, max: 10, def: 0, unit: 'px' },
              ].map(({ key, label, min, max, def, unit }) => (
                <div key={key} style={{ marginBottom: 7 }}>
                  <div style={sliderRow}>
                    <span style={{ color: DIM, fontWeight: 600 }}>{label}</span>
                    <span style={valBadge()}>{sel[key] ?? def}{unit}</span>
                  </div>
                  <input type="range" min={min} max={max} value={sel[key] ?? def}
                    onChange={e => updateSelected({ [key]: Number(e.target.value) })}
                    style={{ width: '100%', accentColor: P, height: 3 }} />
                </div>
              ))}
            </div>

            <div style={sectionWrap}>
              <div style={sectionLabel}>HERRAMIENTAS</div>

              {/* Remove BG */}
              <div style={{ background: DK, borderRadius: 8, border: `1px solid ${BRD}`, overflow: 'hidden', marginBottom: 8 }}>
                <div style={{ padding: '8px 10px' }}>
                  <div style={{ fontSize: 10, fontWeight: 700, color: LT, marginBottom: 3 }}>Eliminar fondo</div>
                  <div style={{ fontSize: 9, color: MUT, lineHeight: 1.5, marginBottom: 8 }}>
                    Detecta el color de fondo y lo elimina con un barrido progresivo.
                  </div>
                  <button
                    onClick={doRemoveBg}
                    disabled={removingBg}
                    style={{
                      width: '100%', background: removingBg ? BRD : '#2A3D2A',
                      border: `1px solid ${removingBg ? BRD : '#4A6A4A'}`,
                      color: removingBg ? MUT : '#8BA888', borderRadius: 6,
                      padding: '6px 0', fontSize: 10, cursor: removingBg ? 'default' : 'pointer',
                      fontWeight: 700, transition: 'all 0.15s', position: 'relative', overflow: 'hidden',
                    }}
                  >
                    {removingBg && (
                      <div style={{
                        position: 'absolute', left: 0, top: 0, bottom: 0,
                        width: `${sweepPct}%`, background: '#4A6A4A44',
                        transition: 'width 0.03s linear',
                      }} />
                    )}
                    <span style={{ position: 'relative' }}>
                      {removingBg ? `✂ Procesando ${sweepPct}%…` : '✂ Eliminar fondo'}
                    </span>
                  </button>
                  {sel.removedBgSrc && (
                    <button
                      onClick={() => updateSelected({ removedBgSrc: null })}
                      style={{ width: '100%', background: 'transparent', border: 'none', color: MUT, fontSize: 9, cursor: 'pointer', marginTop: 4, padding: '2px 0' }}
                    >
                      ↩ Restaurar fondo original
                    </button>
                  )}
                </div>
              </div>

              {/* Flip */}
              <button onClick={() => updateSelected({ flipped: !sel.flipped })} style={{
                width: '100%', background: DK, border: `1px solid ${BRD}`, color: sel.flipped ? P : MUT,
                borderRadius: 6, padding: '6px 0', fontSize: 10, cursor: 'pointer', fontWeight: 600, marginBottom: 4,
              }}>
                ↔ {sel.flipped ? 'Flip activo — quitar' : 'Flip horizontal'}
              </button>

              {/* Blend mode */}
              <div style={{ marginBottom: 4 }}>
                <div style={{ fontSize: 8, color: MUT, marginBottom: 3 }}>BLEND MODE</div>
                <select
                  value={sel.blendMode || 'normal'}
                  onChange={e => updateSelected({ blendMode: e.target.value })}
                  style={{
                    width: '100%', background: DK, border: `1px solid ${BRD}`, color: LT,
                    borderRadius: 5, padding: '4px 6px', fontSize: 9, outline: 'none',
                    fontFamily: "'DM Sans', sans-serif",
                  }}
                >
                  {['normal','multiply','screen','overlay','darken','lighten','color-dodge','color-burn','hard-light','soft-light','difference','exclusion','hue','saturation','color','luminosity'].map(m => (
                    <option key={m} value={m}>{m}</option>
                  ))}
                </select>
              </div>
            </div>
          </>}
        </div>
      ) : (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 20, gap: 8 }}>
          <div style={{ width: 44, height: 44, borderRadius: '50%', background: DK, border: `1px solid ${BRD}`, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>✦</div>
          <div style={{ fontSize: 10, color: '#4A3028', textAlign: 'center', lineHeight: 1.6 }}>Seleccioná un elemento del canvas para editar sus propiedades</div>
        </div>
      )}
    </div>
  );

  // ── Canvas items ──────────────────────────────────────────────
  const canvasItems = resolvedItems.map((sa, i) => {
    const isSel = selected === i;
    const animName = sa.animIn || ANIM_BY_CATEGORY[sa.asset.category] || 'animFadeIn';
    const delayS = ((sa.delay || 0) / 1000).toFixed(2);
    const imgSrc = sa.removedBgSrc || sa.asset.image;
    const filterPreset = FILTER_PRESETS.find(f => f.id === (sa.filter || 'none'));
    const filterCss = (() => {
      let f = filterPreset ? filterPreset.css : 'none';
      const parts = [];
      if (sa.brightness != null && sa.brightness !== 100) parts.push(`brightness(${sa.brightness}%)`);
      if (sa.contrast   != null && sa.contrast !== 100)   parts.push(`contrast(${sa.contrast}%)`);
      if (sa.saturate   != null && sa.saturate !== 100)   parts.push(`saturate(${sa.saturate}%)`);
      if (sa.blur       != null && sa.blur !== 0)         parts.push(`blur(${sa.blur}px)`);
      if (parts.length) f = (f === 'none' ? '' : f + ' ') + parts.join(' ');
      return f || 'none';
    })();

    return (
      <div
        key={`${sa.assetId}-${i}`}
        onMouseDown={e => handleItemMouseDown(e, i)}
        onTouchStart={e => handleItemTouchStart(e, i)}
        onTouchEnd={handleItemTouchEnd}
        onContextMenu={e => handleItemContextMenu(e, i)}
        style={{
          position: 'absolute',
          left: `${sa.x || 0}%`, top: `${sa.y || 0}%`,
          width: `${(sa.scale || 1) * 80}px`,
          zIndex: (sa.z || 0) + 1,
          cursor: 'grab',
          outline: isSel ? `2px solid ${P}` : '1px solid transparent',
          outlineOffset: 3, borderRadius: 4,
          opacity: (sa.opacity ?? 100) / 100,
          transform: sa.flipped ? 'scaleX(-1)' : 'none',
          mixBlendMode: sa.blendMode || 'normal',
          boxShadow: isSel ? `0 0 0 4px ${P}28, 0 4px 16px rgba(0,0,0,0.5)` : '0 2px 8px rgba(0,0,0,0.3)',
          animation: playing ? `${animName} 480ms cubic-bezier(0.34,1.3,0.64,1) ${delayS}s both` : 'none',
          userSelect: 'none', WebkitUserDrag: 'none', touchAction: 'none',
          transition: 'box-shadow 0.1s, outline-color 0.1s',
        }}
      >
        <img
          src={imgSrc} alt={sa.asset.name}
          style={{ width: '100%', height: 'auto', display: 'block', objectFit: 'contain', filter: filterCss }}
          draggable={false}
        />

        {/* Corner handles — only when selected */}
        {isSel && !playing && (
          <>
            {/* Info badge (top center) */}
            <div style={{
              position: 'absolute', bottom: '100%', left: '50%', transform: 'translateX(-50%)',
              marginBottom: 5, background: P, color: '#fff',
              fontSize: 8, padding: '2px 7px', borderRadius: 5, fontWeight: 700,
              whiteSpace: 'nowrap', pointerEvents: 'none', boxShadow: '0 2px 6px rgba(0,0,0,0.4)',
            }}>
              {sa.asset.name} · z{sa.z} · {sa.delay}ms
            </div>

            {/* Top-left: Flip */}
            <div
              onMouseDown={e => { e.stopPropagation(); updateSelected({ flipped: !sel.flipped }); }}
              title="Flip horizontal"
              style={{
                position: 'absolute', top: -10, left: -10,
                width: 20, height: 20, borderRadius: '50%',
                background: '#2A3D5A', border: `1.5px solid #7EB8D4`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', fontSize: 10, color: '#7EB8D4',
                boxShadow: '0 2px 6px rgba(0,0,0,0.5)', zIndex: 10,
              }}
            >↔</div>

            {/* Top-right: Duplicate */}
            <div
              onMouseDown={e => { e.stopPropagation(); duplicateItem(i); }}
              title="Duplicar"
              style={{
                position: 'absolute', top: -10, right: -10,
                width: 20, height: 20, borderRadius: '50%',
                background: '#3D2A5A', border: `1.5px solid #B88ABE`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', fontSize: 11, color: '#B88ABE',
                boxShadow: '0 2px 6px rgba(0,0,0,0.5)', zIndex: 10,
              }}
            >⊕</div>

            {/* Bottom-left: Delete */}
            <div
              onMouseDown={e => { e.stopPropagation(); removeItem(i); }}
              title="Eliminar"
              style={{
                position: 'absolute', bottom: -10, left: -10,
                width: 20, height: 20, borderRadius: '50%',
                background: '#4A1A1A', border: `1.5px solid #E08080`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', fontSize: 10, color: '#E08080',
                boxShadow: '0 2px 6px rgba(0,0,0,0.5)', zIndex: 10,
              }}
            >✕</div>

            {/* Bottom-right: Resize drag handle */}
            <div
              onMouseDown={e => startResize(e, i)}
              title="Redimensionar"
              style={{
                position: 'absolute', bottom: -10, right: -10,
                width: 20, height: 20, borderRadius: '50%',
                background: '#2A3D2A', border: `1.5px solid #8BA888`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'nwse-resize', fontSize: 10, color: '#8BA888',
                boxShadow: '0 2px 6px rgba(0,0,0,0.5)', zIndex: 10,
              }}
            >⤡</div>
          </>
        )}
      </div>
    );
  });

  // ─────────────────────────────────────────────────────────────
  // Main layout — responsive
  // ─────────────────────────────────────────────────────────────
  return (
    <div style={{
      display: 'flex', height: '100%', flex: 1,
      background: DK, fontFamily: "'DM Sans', sans-serif",
      overflow: 'hidden', position: 'relative',
    }}>

      {/* ── Desktop: side panels ─────────────────────── */}
      <div className="se-left-panel" style={{ width: 195, flexShrink: 0, borderRight: `1px solid ${BRD}` }}>
        {leftPanel}
      </div>

      {/* ── Center canvas ─────────────────────────────── */}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, background: DK }}>
        {/* Canvas toolbar */}
        <div style={{
          height: 42, background: MD, borderBottom: `1px solid ${BRD}`,
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: '0 12px', flexShrink: 0, gap: 8,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            {/* Mobile: panel toggles */}
            <button className="se-mobile-only" onClick={() => setMobileSheet(mobileSheet === 'left' ? null : 'left')}
              style={{ background: MD, border: `1px solid ${BRD}`, color: DIM, borderRadius: 6, padding: '3px 8px', fontSize: 11, cursor: 'pointer' }}>
              ☰
            </button>
            <span style={{ fontSize: 9, fontWeight: 800, color: MUT, letterSpacing: 1.2 }}>CANVAS</span>
            {sel && selResolved && (
              <div style={{ background: P + '22', border: `1px solid ${P}44`, borderRadius: 20, padding: '1px 8px', display: 'flex', alignItems: 'center', gap: 4 }}>
                <div style={{ width: 4, height: 4, borderRadius: '50%', background: P }} />
                <span style={{ fontSize: 9, fontWeight: 600, color: P }}>{selResolved.asset.name}</span>
              </div>
            )}
          </div>
          <div style={{ display: 'flex', gap: 5, alignItems: 'center' }}>
            {resolvedItems.length > 0 && (
              <button onClick={clearAll} style={{
                background: 'transparent', border: `1px solid ${BRD}`, color: MUT,
                borderRadius: 5, padding: '2px 7px', fontSize: 9, cursor: 'pointer', fontWeight: 600,
              }}
                onMouseEnter={e => { e.currentTarget.style.color = '#E08080'; e.currentTarget.style.borderColor = '#E0808044'; }}
                onMouseLeave={e => { e.currentTarget.style.color = MUT; e.currentTarget.style.borderColor = BRD; }}
              >✕ Limpiar</button>
            )}
            <button onClick={handlePlay} style={{
              background: P, color: '#fff', border: 'none', borderRadius: 7,
              padding: '4px 12px', fontSize: 11, cursor: 'pointer', fontWeight: 700,
              display: 'flex', alignItems: 'center', gap: 4, boxShadow: `0 2px 8px ${P}44`,
            }}>
              <span style={{ fontSize: 9 }}>▶</span> Preview
            </button>
            {/* Mobile: props toggle */}
            <button className="se-mobile-only" onClick={() => setMobileSheet(mobileSheet === 'right' ? null : 'right')}
              style={{ background: sel ? P + '22' : MD, border: `1px solid ${sel ? P + '44' : BRD}`, color: sel ? P : DIM, borderRadius: 6, padding: '3px 8px', fontSize: 11, cursor: 'pointer' }}>
              ✦
            </button>
          </div>
        </div>

        {/* Canvas */}
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16, overflow: 'hidden' }}>
          <div style={{ width: '100%', maxWidth: 800, position: 'relative' }}>
            <div style={{ borderRadius: 10, overflow: 'hidden', boxShadow: '0 8px 40px rgba(0,0,0,0.6), 0 0 0 1px #3D2C24' }}>
              <div
                ref={canvasRef}
                onDrop={handleCanvasDrop}
                onDragOver={handleCanvasDragOver}
                onClick={e => { if (e.target === canvasRef.current || e.target.tagName === 'DIV') { if (!e.target.closest('[data-handle]')) setSelected(null); } }}
                key={playKey}
                style={{
                  position: 'relative', width: '100%', paddingTop: `${CANVAS_ASPECT}%`,
                  background: '#151010',
                  backgroundImage: !sceneBg ? [
                    'linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px)',
                    'linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px)',
                  ].join(',') : undefined,
                  backgroundSize: !sceneBg ? '5% 5%' : undefined,
                  cursor: 'default', userSelect: 'none',
                }}
              >
                {/* Background layer with filters & opacity */}
                {sceneBg && (() => {
                  const bgFilter = [
                    sceneBg.brightness !== undefined && sceneBg.brightness !== 100 ? `brightness(${sceneBg.brightness}%)` : '',
                    sceneBg.contrast   !== undefined && sceneBg.contrast   !== 100 ? `contrast(${sceneBg.contrast}%)`     : '',
                    sceneBg.saturate   !== undefined && sceneBg.saturate   !== 100 ? `saturate(${sceneBg.saturate}%)`     : '',
                    sceneBg.blur       !== undefined && sceneBg.blur       !== 0   ? `blur(${sceneBg.blur}px)`            : '',
                  ].filter(Boolean).join(' ') || 'none';
                  return (
                    <div style={{
                      position: 'absolute', inset: 0, zIndex: 0,
                      backgroundImage: `url(${sceneBg.src})`,
                      backgroundSize: sceneBg.size || 'cover',
                      backgroundPosition: `${sceneBg.posX ?? 50}% ${sceneBg.posY ?? 50}%`,
                      backgroundRepeat: 'no-repeat',
                      opacity: (sceneBg.opacity ?? 100) / 100,
                      filter: bgFilter,
                      pointerEvents: 'none',
                    }} />
                  );
                })()}

                {/* Drop hint */}
                {resolvedItems.length === 0 && !sceneBg && (
                  <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none', gap: 8 }}>
                    <div style={{ width: 90, height: 90, borderRadius: '50%', border: `2px dashed ${BRD}`, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 24 }}>🎨</div>
                    <div style={{ fontSize: 12, color: MUT, fontWeight: 600 }}>Arrastrá un asset aquí</div>
                    <div style={{ fontSize: 10, color: '#4A3028' }}>Los elementos entran en secuencia según su capa</div>
                  </div>
                )}

                {canvasItems}
              </div>
            </div>
            <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12, fontSize: 9, color: '#4A3028' }}>
              <span>⬡ Arrastrá para mover</span>
              <span style={{ color: BRD }}>·</span>
              <span>⊞ Click para seleccionar</span>
              <span style={{ color: BRD }}>·</span>
              <span>Clic derecho para opciones</span>
              {resolvedItems.length > 0 && <><span style={{ color: BRD }}>·</span><span style={{ color: MUT }}>{resolvedItems.length} elemento{resolvedItems.length !== 1 ? 's' : ''}</span></>}
            </div>
          </div>
        </div>
      </div>

      {/* ── Desktop: right panel ──────────────────────── */}
      <div className="se-right-panel" style={{ width: 195, flexShrink: 0, borderLeft: `1px solid ${BRD}` }}>
        {rightPanel}
      </div>

      {/* ── Mobile: bottom sheets ─────────────────────── */}
      {mobileSheet && (
        <div style={{
          position: 'absolute', inset: 0, zIndex: 100,
          background: 'rgba(0,0,0,0.5)',
        }} onClick={() => setMobileSheet(null)}>
          <div
            onClick={e => e.stopPropagation()}
            style={{
              position: 'absolute', bottom: 0, left: 0, right: 0,
              height: '72vh', background: MD, borderRadius: '16px 16px 0 0',
              overflow: 'hidden', animation: 'animRiseUp 0.25s ease both',
              boxShadow: '0 -8px 40px rgba(0,0,0,0.5)',
            }}
          >
            <div style={{ display: 'flex', justifyContent: 'center', padding: '10px 0 0' }}>
              <div style={{ width: 36, height: 4, borderRadius: 2, background: BRD }} />
            </div>
            <div style={{ height: 'calc(72vh - 24px)', overflowY: 'auto' }}>
              {mobileSheet === 'left' ? leftPanel : rightPanel}
            </div>
          </div>
        </div>
      )}

      {/* ── Context menu ──────────────────────────────── */}
      {ctxMenu && (
        <ContextMenu
          x={ctxMenu.x} y={ctxMenu.y}
          items={buildCtxItems(ctxMenu.index)}
          onClose={() => setCtxMenu(null)}
        />
      )}

      {/* ── Storage warning toast ─────────────────────── */}
      {storageWarn && (
        <div style={{
          position: 'absolute', bottom: 20, left: '50%', transform: 'translateX(-50%)',
          background: '#3D1A00', border: '1px solid #8B4500', borderRadius: 10,
          padding: '10px 16px', zIndex: 9999, maxWidth: 320,
          boxShadow: '0 4px 20px rgba(0,0,0,0.6)', animation: 'animRiseUp 0.2s ease both',
          display: 'flex', alignItems: 'flex-start', gap: 10,
        }}>
          <span style={{ fontSize: 16, flexShrink: 0 }}>⚠️</span>
          <div>
            <div style={{ fontSize: 11, fontWeight: 700, color: '#FFB060', marginBottom: 3 }}>
              Almacenamiento lleno
            </div>
            <div style={{ fontSize: 10, color: '#C89060', lineHeight: 1.5 }}>
              Las imágenes subidas ocupan demasiado espacio. Usa URLs externas o imágenes más pequeñas. Los cambios no se guardaron.
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

window.SceneEditor = SceneEditor;
