// LELUSA — Canvas Editor v3 (Konva imperative, no react-konva)
const CANVAS_STORE_KEY = 'lelusa.canvas.v3';

// ─── Constants ────────────────────────────────────────────────────
const C_FONTS = [
  'Cormorant Garamond','Playfair Display','Dancing Script','Montserrat','DM Sans',
  'Georgia','Arial','Helvetica','Times New Roman','Trebuchet MS',
];
const C_BLENDS = ['normal','multiply','screen','overlay','darken','lighten','soft-light','hard-light','difference','exclusion','color-dodge','color-burn'];
const C_FILTERS = [
  { id:'none', label:'Sin filtro' },
  { id:'grayscale', label:'Gris', css:'grayscale(1)' },
  { id:'sepia', label:'Sépia', css:'sepia(0.8)' },
  { id:'warm', label:'Cálido', css:'sepia(0.3) saturate(1.4) brightness(1.05)' },
  { id:'cool', label:'Frío', css:'hue-rotate(180deg) saturate(0.7)' },
  { id:'vintage', label:'Vintage', css:'sepia(0.5) contrast(0.9) brightness(1.1) saturate(0.8)' },
  { id:'dreamy', label:'Dreamy', css:'blur(0.5px) brightness(1.1) saturate(1.2)' },
  { id:'matte', label:'Mate', css:'contrast(0.85) brightness(1.08) saturate(0.85)' },
  { id:'dark', label:'Dark', css:'brightness(0.75) contrast(1.2)' },
  { id:'vivid', label:'Vívido', css:'saturate(1.8) contrast(1.1)' },
];
const C_SHAPES = [
  {id:'rect',n:'Rect'},{id:'circle',n:'Círculo'},{id:'arch',n:'Arco'},{id:'oval',n:'Óvalo'},
  {id:'triangle',n:'Triángulo'},{id:'diamond',n:'Diamante'},{id:'hexagon',n:'Hexágono'},
  {id:'heart',n:'Corazón'},{id:'star',n:'Estrella'},{id:'crown',n:'Corona'},
  {id:'bow',n:'Moño'},{id:'banner',n:'Pancarta'},{id:'frame',n:'Marco'},
  {id:'cloud',n:'Nube'},{id:'cross',n:'Cruz'},{id:'garland',n:'Guirnalda'},
  {id:'florals',n:'Flores'},{id:'podium',n:'Podio'},{id:'table',n:'Mesa'},
];
const C_TEMPLATES = [
  {id:'wedding',name:'Wedding',bg:'#FAF0E8',text:'Our Wedding',accent:'#C8A87A'},
  {id:'baby',name:'Baby Shower',bg:'#F6E9EF',text:'Oh Baby',accent:'#C4899A'},
  {id:'gender',name:'Gender Reveal',bg:'#EAF2F6',text:'Boy or Girl?',accent:'#A9C4D4'},
  {id:'xv',name:'Quinceañera',bg:'#F4EAEC',text:'Mis XV',accent:'#9E6B7C'},
  {id:'bday',name:'Birthday',bg:'#FFF8EA',text:'Happy Birthday',accent:'#C8A87A'},
  {id:'grad',name:'Graduation',bg:'#F7F2EC',text:'Congrats',accent:'#3E6378'},
  {id:'proposal',name:'Proposal',bg:'#FAF0E8',text:'Marry Me',accent:'#C4899A'},
  {id:'corp',name:'Corporate',bg:'#F4EEE0',text:'Brand Moment',accent:'#7E9896'},
];
const LAYER_DEF = {
  rotation:0,opacity:1,visible:true,locked:false,zIndex:1,
  color:'#C4899A',stroke:'#C8A87A',strokeWidth:3,borderRadius:0,
  shadow:false,shadowX:8,shadowY:8,shadowBlur:20,shadowColor:'rgba(58,42,32,0.3)',shadowOpacity:0.6,
  fontWeight:'bold',fontStyle:'normal',textDecoration:'none',lineHeight:1.2,letterSpacing:0,
  blendMode:'normal',flipX:false,flipY:false,filter:'none',filterIntensity:1,
  adjBrightness:100,adjContrast:100,adjSaturation:100,adjBlur:0,
};

// ─── Shape paths ──────────────────────────────────────────────────
function shapePath(id, w, h) {
  switch(id) {
    case 'triangle': return `M${w/2} 0L${w} ${h}L0 ${h}Z`;
    case 'diamond': return `M${w/2} 0L${w} ${h/2}L${w/2} ${h}L0 ${h/2}Z`;
    case 'hexagon': {
      const pts=Array.from({length:6},(_,i)=>{const a=Math.PI/3*i-Math.PI/6;return`${w/2+w/2*Math.cos(a)},${h/2+h/2*Math.sin(a)}`;});
      return`M${pts[0]}L${pts.slice(1).join('L')}Z`;
    }
    case 'heart': return`M${w*.5} ${h*.85}C${w*.1} ${h*.55},0 ${h*.35},${w*.08} ${h*.22}C${w*.15} ${h*.08},${w*.3} 0,${w*.5} ${h*.18}C${w*.7} 0,${w*.85} ${h*.08},${w*.92} ${h*.22}C${w} ${h*.35},${w*.9} ${h*.55},${w*.5} ${h*.85}Z`;
    case 'star': {
      const pts=[];const r1=Math.min(w,h)*.48,r2=r1*.42,cx=w/2,cy=h/2;
      for(let i=0;i<10;i++){const a=Math.PI*2*i/10-Math.PI/2,r=i%2?r2:r1;pts.push(`${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`);}
      return`M${pts[0]}L${pts.slice(1).join('L')}Z`;
    }
    case 'crown': return`M0 ${h}L0 ${h*.55}L${w*.2} ${h*.2}L${w*.35} ${h*.55}L${w*.5} 0L${w*.65} ${h*.55}L${w*.8} ${h*.2}L${w} ${h*.55}L${w} ${h}Z`;
    case 'banner': return`M0 0L${w} 0L${w} ${h*.7}L${w*.5} ${h}L0 ${h*.7}Z`;
    case 'cloud': return`M${w*.18} ${h*.85}Q0 ${h*.85},0 ${h*.65}Q0 ${h*.45},${w*.15} ${h*.42}Q${w*.12} ${h*.15},${w*.35} ${h*.18}Q${w*.38} 0,${w*.55} 0Q${w*.72} 0,${w*.75} ${h*.18}Q${w*.9} ${h*.08},${w} ${h*.3}Q${w} ${h*.55},${w*.82} ${h*.6}Q${w*.88} ${h*.85},${w*.7} ${h*.85}Z`;
    case 'cross': return`M${w*.35} 0L${w*.65} 0L${w*.65} ${h*.35}L${w} ${h*.35}L${w} ${h*.65}L${w*.65} ${h*.65}L${w*.65} ${h}L${w*.35} ${h}L${w*.35} ${h*.65}L0 ${h*.65}L0 ${h*.35}L${w*.35} ${h*.35}Z`;
    case 'frame': return`M0 0L${w} 0L${w} ${h}L0 ${h}ZM${w*.08} ${h*.06}L${w*.08} ${h*.94}L${w*.92} ${h*.94}L${w*.92} ${h*.06}Z`;
    case 'bow': return`M${w*.5} ${h*.42}L${w*.08} 0L${w*.3} ${h*.5}L${w*.08} ${h}L${w*.5} ${h*.58}L${w*.92} ${h}L${w*.7} ${h*.5}L${w*.92} 0Z`;
    case 'arch': return`M0 ${h}L0 ${h*.5}Q0 0,${w/2} 0Q${w} 0,${w} ${h*.5}L${w} ${h}Z`;
    case 'podium': return`M${w*.1} ${h}L${w*.1} ${h*.35}Q${w*.1} 0,${w*.5} 0Q${w*.9} 0,${w*.9} ${h*.35}L${w*.9} ${h}Z`;
    default: return null;
  }
}

// ─── Storage ──────────────────────────────────────────────────────
function loadProjects() {
  try { const r=JSON.parse(localStorage.getItem(CANVAS_STORE_KEY)||'[]'); return Array.isArray(r)?r:[]; } catch{return [];}
}
function saveProjects(ps) { try{localStorage.setItem(CANVAS_STORE_KEY,JSON.stringify(ps));}catch{} }
function mkLayer(p={}) {
  return { ...LAYER_DEF, id:`l_${Date.now()}_${(Math.random()*9999|0)}`,
    name:p.name||'Capa', type:p.type||'shape', x:p.x??400, y:p.y??300,
    width:p.width??500, height:p.height??400, zIndex:p.zIndex??1, ...p };
}
function mkProject(name='Nuevo diseño') {
  return { id:`p_${Date.now()}`, name, updatedAt:new Date().toISOString(),
    canvas:{ width:3000, height:2000, bg:'#FAF0E8' }, layers:[] };
}
function mkTemplate(t) {
  const p=mkProject(t.name);
  p.canvas.bg=t.bg;
  p.layers=[
    mkLayer({name:'Backdrop',type:'shape',shape:'circle',x:870,y:170,width:1260,height:1260,color:'#FFFCF9',stroke:t.accent,strokeWidth:8,zIndex:1}),
    mkLayer({name:'Garland',type:'shape',shape:'garland',x:520,y:210,width:900,height:1080,color:t.accent,stroke:'#FAF0E8',zIndex:2}),
    mkLayer({name:'Florals',type:'shape',shape:'florals',x:530,y:960,width:620,height:430,color:'#C4899A',stroke:'#A8BAB8',zIndex:3}),
    mkLayer({name:'Podio',type:'shape',shape:'podium',x:1240,y:1140,width:540,height:470,color:'#FFFFFF',stroke:'#C8A87A',zIndex:4}),
    mkLayer({name:'Título',type:'text',text:t.text,x:1030,y:700,width:940,height:180,color:'#9E6B7C',fontSize:130,fontFamily:'Cormorant Garamond',zIndex:5}),
  ];
  return p;
}
// ─── Canvas Assets (Supabase Storage) ────────────────────────────
async function uploadCanvasAsset(file) {
  const { url, anonKey } = window.LELUSA_SUPABASE;
  const filename = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
  const uploadRes = await fetch(
    `${url}/storage/v1/object/canvas-assets/${filename}`,
    { method: 'POST', headers: { Authorization: `Bearer ${anonKey}`, 'Content-Type': file.type }, body: file }
  );
  if (!uploadRes.ok) throw new Error('Error subiendo imagen');
  const publicUrl = `${url}/storage/v1/object/public/canvas-assets/${filename}`;
  const dims = await new Promise(resolve => {
    const img = new window.Image();
    img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
    img.onerror = () => resolve({ width: null, height: null });
    img.src = publicUrl;
  });
  const insertRes = await fetch(
    `${url}/rest/v1/canvas_assets`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${anonKey}`,
        apikey: anonKey,
        'Content-Type': 'application/json',
        Prefer: 'return=representation',
      },
      body: JSON.stringify({
        name: file.name.replace(/\.[^.]+$/, ''),
        url: publicUrl,
        width: dims.width,
        height: dims.height,
        size_bytes: file.size,
        mime_type: file.type,
      }),
    }
  );
  if (!insertRes.ok) throw new Error('Error guardando metadatos');
  const [row] = await insertRes.json();
  return row;
}

async function fetchCanvasAssets() {
  const { url, anonKey } = window.LELUSA_SUPABASE;
  const res = await fetch(
    `${url}/rest/v1/canvas_assets?order=created_at.desc`,
    { headers: { Authorization: `Bearer ${anonKey}`, apikey: anonKey } }
  );
  if (!res.ok) return [];
  return res.json();
}

async function deleteCanvasAsset(asset) {
  const { url, anonKey } = window.LELUSA_SUPABASE;
  const filename = asset.url.split('/canvas-assets/').pop();
  const storageRes = await fetch(
    `${url}/storage/v1/object/canvas-assets/${filename}`,
    { method: 'DELETE', headers: { Authorization: `Bearer ${anonKey}` } }
  );
  if (!storageRes.ok) throw new Error('Error eliminando archivo');
  const dbRes = await fetch(
    `${url}/rest/v1/canvas_assets?id=eq.${asset.id}`,
    { method: 'DELETE', headers: { Authorization: `Bearer ${anonKey}`, apikey: anonKey } }
  );
  if (!dbRes.ok) throw new Error('Error eliminando metadatos');
}

function sortedLayers(ls=[]) { return [...ls].sort((a,b)=>(a.zIndex||0)-(b.zIndex||0)); }

function getCanvasProjectIdParam() {
  try {
    return new URLSearchParams(window.location.search || '').get('projectId') || '';
  } catch {
    return '';
  }
}

function projectTime(p) {
  const value = Date.parse(p?.updatedAt || '');
  return Number.isFinite(value) ? value : 0;
}

function mergeCanvasProjects(existing=[], incoming=[]) {
  const byId = new Map();
  [...existing, ...incoming].filter(Boolean).forEach((project) => {
    const current = byId.get(String(project.id));
    if (!current || projectTime(project) >= projectTime(current)) byId.set(String(project.id), project);
  });
  return [...byId.values()].sort((a,b)=>projectTime(b)-projectTime(a));
}

// ─── CSS tokens ───────────────────────────────────────────────────
const T = {
  bg:'var(--bg)',card:'var(--card)',text:'var(--text)',muted:'var(--muted)',
  border:'var(--border)',primary:'var(--primary)',accent:'var(--accent-light)',
  panelBg:'rgba(250,240,232,0.97)',panelShadow:'0 8px 40px rgba(58,42,32,0.14)',
  radius:'12px',radiusLg:'18px',radiusSm:'8px',
  inputBg:'rgba(255,252,249,0.9)',
};

// ─── Tiny shared UI ───────────────────────────────────────────────
const IS = { // input style
  width:'100%',border:'1px solid var(--border)',borderRadius:8,padding:'6px 10px',
  fontSize:13,color:'var(--text)',background:T.inputBg,outline:'none',
  fontFamily:"'DM Sans',sans-serif",transition:'border-color .15s',
};

function Sep() {
  return <div style={{height:1,background:'var(--border)',margin:'6px 0',opacity:.7}} />;
}

function Lbl({children, sub}) {
  return (
    <div style={{marginBottom:sub?2:5}}>
      <span style={{fontSize:sub?10:11,fontWeight:700,color:'var(--muted)',textTransform:'uppercase',letterSpacing:'0.07em'}}>{children}</span>
    </div>
  );
}

function Row({children,gap=6}) {
  return <div style={{display:'flex',alignItems:'center',gap}}>{children}</div>;
}

function Grid2({children,gap=8}) {
  return <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap}}>{children}</div>;
}

function IField({label,children}) {
  return (
    <div style={{display:'grid',gap:4}}>
      {label && <Lbl sub>{label}</Lbl>}
      {children}
    </div>
  );
}

function NumIn({value,onChange,min,max,step=1,unit}) {
  return (
    <div style={{display:'flex',alignItems:'center',gap:0,border:'1px solid var(--border)',borderRadius:8,overflow:'hidden',background:T.inputBg}}>
      <input type="number" value={value??''} min={min} max={max} step={step}
        onChange={e=>onChange(Number(e.target.value))}
        style={{border:'none',background:'transparent',padding:'5px 8px',fontSize:13,color:'var(--text)',width:'100%',outline:'none',fontFamily:"'DM Sans',sans-serif"}} />
      {unit && <span style={{padding:'0 8px 0 0',fontSize:11,color:'var(--muted)',whiteSpace:'nowrap'}}>{unit}</span>}
    </div>
  );
}

function RangeRow({label,value,onChange,min=0,max=1,step=0.01,fmt}) {
  const disp = fmt ? fmt(value) : (typeof value==='number' ? (step<1?value.toFixed(2):String(value)) : '');
  return (
    <IField label={label}>
      <div style={{display:'flex',alignItems:'center',gap:8}}>
        <input type="range" min={min} max={max} step={step} value={value??min}
          onChange={e=>onChange(Number(e.target.value))}
          style={{flex:1,accentColor:'var(--primary)',height:4,cursor:'pointer'}} />
        <span style={{fontSize:11,fontWeight:700,color:'var(--muted)',minWidth:36,textAlign:'right'}}>{disp}</span>
      </div>
    </IField>
  );
}

function ColorRow({label,value,onChange}) {
  return (
    <IField label={label}>
      <div style={{display:'flex',gap:7,alignItems:'center'}}>
        <div style={{position:'relative',flexShrink:0}}>
          <div style={{width:32,height:32,borderRadius:8,background:value||'#ccc',border:'2px solid var(--border)',cursor:'pointer',overflow:'hidden'}}>
            <input type="color" value={value||'#C4899A'} onChange={e=>onChange(e.target.value)}
              style={{opacity:0,position:'absolute',inset:0,cursor:'pointer',width:'100%',height:'100%'}} />
          </div>
        </div>
        <input value={value||''} onChange={e=>onChange(e.target.value)} placeholder="#000000"
          style={{...IS,flex:1,fontFamily:'monospace',fontSize:12,padding:'5px 8px'}} />
      </div>
    </IField>
  );
}

function ToggleGroup({options,value,onChange}) {
  return (
    <div style={{display:'flex',background:'rgba(0,0,0,0.05)',borderRadius:8,padding:2,gap:1}}>
      {options.map(o=>(
        <button key={o.v} type="button" title={o.title||o.l} onClick={()=>onChange(o.v)}
          style={{flex:1,border:'none',borderRadius:6,padding:'5px 4px',fontSize:12,fontWeight:700,cursor:'pointer',
            background:value===o.v?'var(--card)':'transparent',
            color:value===o.v?'var(--primary)':'var(--muted)',
            boxShadow:value===o.v?'0 1px 4px rgba(0,0,0,0.1)':'none',transition:'all .12s'}}>
          {o.l}
        </button>
      ))}
    </div>
  );
}

function TBtn({icon,label,title,active,disabled,onClick,danger,style:sx}) {
  return (
    <button type="button" title={title||label} disabled={disabled} onClick={onClick}
      style={{display:'flex',alignItems:'center',justifyContent:'center',gap:5,
        height:32,padding:label?'0 10px':'0 8px',borderRadius:8,
        border:`1px solid ${active?'var(--primary)':danger?'#FCA5A5':'var(--border)'}`,
        background:active?'var(--accent-light)':danger?'#FEF2F2':'var(--card)',
        color:active?'var(--primary)':danger?'#DC2626':'var(--text)',
        fontSize:12,fontWeight:700,cursor:disabled?'not-allowed':'pointer',
        opacity:disabled?.4:1,whiteSpace:'nowrap',fontFamily:"'DM Sans',sans-serif",
        transition:'all .12s',...sx}}>
      {icon && <Icon name={icon} size={13} color={active?'var(--primary)':danger?'#DC2626':'var(--muted)'} />}
      {label}
    </button>
  );
}

// ─── Konva Stage ──────────────────────────────────────────────────
function KStage({cw,ch,cbg,layers,selectedIds,zoom,pos,snap,grid,guides,
  onSelect,onChange,onZoom,onPos,onContextMenu}) {

  const containerRef=React.useRef(null);
  const stage=React.useRef(null);
  const bgLyr=React.useRef(null);
  const mainLyr=React.useRef(null);
  const uiLyr=React.useRef(null);
  const guideLyr=React.useRef(null);
  const tr=React.useRef(null);
  const imgCache=React.useRef({});
  const lastDist=React.useRef(0);
  const isPanning=React.useRef(false);
  const panStart=React.useRef({x:0,y:0});
  const spaceDown=React.useRef(false);

  // init once
  React.useEffect(()=>{
    if(!containerRef.current||stage.current) return;
    const K=window.Konva;
    const el=containerRef.current;
    const w=el.offsetWidth||900,h=el.offsetHeight||600;

    const s=new K.Stage({container:el,width:w,height:h});
    stage.current=s;

    const bl=new K.Layer({listening:false}); bgLyr.current=bl; s.add(bl);
    const ml=new K.Layer(); mainLyr.current=ml; s.add(ml);
    const gl=new K.Layer({listening:false}); guideLyr.current=gl; s.add(gl);
    const ul=new K.Layer(); uiLyr.current=ul; s.add(ul);

    const transformer=new K.Transformer({
      rotateEnabled:true,keepRatio:false,
      anchorSize:11,anchorCornerRadius:6,
      anchorStroke:'#C4899A',anchorFill:'#fff',anchorStrokeWidth:2,
      borderStroke:'#C4899A',borderStrokeWidth:1.5,borderDash:[5,3],
      rotateAnchorOffset:22,
      boundBoxFunc:(old,next)=>(next.width<10||next.height<10?old:next),
    });
    tr.current=transformer; ul.add(transformer);

    // stage events
    s.on('click tap',e=>{ if(e.target===s){onSelect([]);} });
    s.on('contextmenu',e=>{
      e.evt.preventDefault();
      const pos2=s.getPointerPosition();
      if(e.target!==s && e.target._layerDataId) {
        onContextMenu({x:e.evt.clientX,y:e.evt.clientY,layerId:e.target._layerDataId});
      }
    });

    // wheel zoom
    s.on('wheel',e=>{
      e.evt.preventDefault();
      if(spaceDown.current) return;
      const oldScale=s.scaleX();
      const pointer=s.getPointerPosition();
      const to={x:(pointer.x-s.x())/oldScale,y:(pointer.y-s.y())/oldScale};
      const delta=e.evt.deltaY<0?1.1:1/1.1;
      const newScale=Math.min(6,Math.max(0.04,oldScale*delta));
      s.scale({x:newScale,y:newScale});
      s.position({x:pointer.x-to.x*newScale,y:pointer.y-to.y*newScale});
      s.batchDraw();
      onZoom(newScale); onPos({x:s.x(),y:s.y()});
    });

    // pan with space+drag or middle mouse
    s.on('mousedown',e=>{
      if(e.evt.button===1||spaceDown.current){
        isPanning.current=true;
        panStart.current={x:e.evt.clientX-s.x(),y:e.evt.clientY-s.y()};
        el.style.cursor='grabbing';
      }
    });
    window.addEventListener('mousemove',onMouseMove);
    window.addEventListener('mouseup',onMouseUp);
    function onMouseMove(e){
      if(!isPanning.current) return;
      const nx=e.clientX-panStart.current.x, ny=e.clientY-panStart.current.y;
      s.position({x:nx,y:ny}); s.batchDraw(); onPos({x:nx,y:ny});
    }
    function onMouseUp(){
      if(isPanning.current){ isPanning.current=false; el.style.cursor=spaceDown.current?'grab':'default'; }
    }

    // pinch
    s.on('touchmove',e=>{
      if(e.evt.touches.length!==2) return;
      e.evt.preventDefault();
      const t0=e.evt.touches[0],t1=e.evt.touches[1];
      const dist=Math.hypot(t0.clientX-t1.clientX,t0.clientY-t1.clientY);
      if(!lastDist.current){lastDist.current=dist;return;}
      const factor=dist/lastDist.current; lastDist.current=dist;
      const old=s.scaleX();
      const nsc=Math.min(6,Math.max(0.04,old*factor));
      const cx=(t0.clientX+t1.clientX)/2-el.getBoundingClientRect().left;
      const cy=(t0.clientY+t1.clientY)/2-el.getBoundingClientRect().top;
      const to={x:(cx-s.x())/old,y:(cy-s.y())/old};
      s.scale({x:nsc,y:nsc}); s.position({x:cx-to.x*nsc,y:cy-to.y*nsc});
      s.batchDraw(); onZoom(nsc); onPos({x:s.x(),y:s.y()});
    });
    s.on('touchend',()=>{lastDist.current=0;});

    // keyboard pan
    const onKey=e=>{if(e.code==='Space'){spaceDown.current=true;el.style.cursor='grab';}};
    const offKey=e=>{if(e.code==='Space'){spaceDown.current=false;el.style.cursor='default';}};
    window.addEventListener('keydown',onKey);
    window.addEventListener('keyup',offKey);

    // resize
    const ro=new ResizeObserver(([en])=>{s.width(en.contentRect.width);s.height(en.contentRect.height);s.batchDraw();});
    ro.observe(el);

    return ()=>{
      ro.disconnect();
      window.removeEventListener('mousemove',onMouseMove);
      window.removeEventListener('mouseup',onMouseUp);
      window.removeEventListener('keydown',onKey);
      window.removeEventListener('keyup',offKey);
      s.destroy(); stage.current=null;
    };
  },[]);// eslint-disable-line

  // sync zoom+pos
  React.useEffect(()=>{
    const s=stage.current; if(!s) return;
    s.scale({x:zoom,y:zoom}); s.position(pos); s.batchDraw();
  },[zoom,pos]);

  // draw background
  React.useEffect(()=>{
    const K=window.Konva,l=bgLyr.current; if(!l||!K) return;
    l.destroyChildren();
    l.add(new K.Rect({x:0,y:0,width:cw,height:ch,fill:cbg||'#FAF0E8',
      shadowColor:'rgba(0,0,0,0.18)',shadowBlur:60,shadowOffsetY:16}));
    if(grid){
      for(let x=0;x<=cw;x+=100) l.add(new K.Line({points:[x,0,x,ch],stroke:'rgba(196,137,154,0.12)',strokeWidth:1}));
      for(let y=0;y<=ch;y+=100) l.add(new K.Line({points:[0,y,cw,y],stroke:'rgba(196,137,154,0.12)',strokeWidth:1}));
    }
    if(snap){
      for(let x=0;x<=cw;x+=25) l.add(new K.Line({points:[x,0,x,ch],stroke:'rgba(196,137,154,0.05)',strokeWidth:.5}));
      for(let y=0;y<=ch;y+=25) l.add(new K.Line({points:[0,y,cw,y],stroke:'rgba(196,137,154,0.05)',strokeWidth:.5}));
    }
    l.batchDraw();
  },[cw,ch,cbg,grid,snap]);

  // draw guide lines
  React.useEffect(()=>{
    const K=window.Konva,l=guideLyr.current; if(!l||!K) return;
    l.destroyChildren();
    (guides||[]).forEach(g=>{
      if(g.type==='v') l.add(new K.Line({points:[g.x,0,g.x,ch],stroke:'#3B82F6',strokeWidth:1,dash:[4,4]}));
      if(g.type==='h') l.add(new K.Line({points:[0,g.y,cw,g.y],stroke:'#3B82F6',strokeWidth:1,dash:[4,4]}));
      if(g.type==='snap') l.add(new K.Line({points:g.points,stroke:'#F43F5E',strokeWidth:1,dash:[3,3]}));
    });
    l.batchDraw();
  },[guides,cw,ch]);

  // draw nodes
  React.useEffect(()=>{
    const K=window.Konva,ml=mainLyr.current,transformer=tr.current; if(!ml||!transformer) return;
    ml.destroyChildren();
    const nodeMap={};

    sortedLayers(layers).filter(l=>l.visible!==false).forEach(ld=>{
      let node=null;
      const com={
        x:ld.x,y:ld.y,width:ld.width,height:ld.height,
        rotation:ld.rotation||0,opacity:ld.opacity??1,
        scaleX:ld.flipX?-1:1,scaleY:ld.flipY?-1:1,
        offsetX:ld.flipX?ld.width:0,offsetY:ld.flipY?ld.height:0,
        draggable:!ld.locked,
        globalCompositeOperation:ld.blendMode||'source-over',
      };
      const shd=ld.shadow?{shadowColor:ld.shadowColor||'rgba(58,42,32,0.3)',
        shadowOffsetX:ld.shadowX??8,shadowOffsetY:ld.shadowY??8,
        shadowBlur:ld.shadowBlur??20,shadowOpacity:ld.shadowOpacity??0.6}:{};
      const str={stroke:ld.stroke||'#C8A87A',strokeWidth:ld.strokeWidth??3};

      if(ld.type==='text'){
        const fs=[ld.fontStyle==='italic'?'italic':'',ld.fontWeight==='bold'?'bold':''].join(' ').trim()||'normal';
        node=new K.Text({...com,...shd,text:ld.text||'Texto',fontSize:ld.fontSize||96,
          fontFamily:`'${ld.fontFamily||'Cormorant Garamond'}'`,fontStyle:fs,
          textDecoration:ld.textDecoration||'none',fill:ld.color||'#9E6B7C',
          align:ld.textAlign||'center',lineHeight:ld.lineHeight||1.2,
          letterSpacing:ld.letterSpacing||0,wrap:'word',perfectDrawEnabled:false});
      } else if(ld.type==='image'){
        const src=ld.src;
        if(src){
          if(imgCache.current[src]){
            node=new K.Image({...com,...shd,image:imgCache.current[src],perfectDrawEnabled:false});
          } else {
            node=new K.Rect({...com,fill:'#F4EAEC'});
            const img=new window.Image(); img.crossOrigin='anonymous';
            img.onload=()=>{
              imgCache.current[src]=img;
              const ki=new K.Image({...com,...shd,image:img,perfectDrawEnabled:false});
              attach(ki,ld); nodeMap[ld.id]=ki;
              node&&node.destroy();
              mainLyr.current&&mainLyr.current.add(ki)&&mainLyr.current.batchDraw();
            }; img.src=src;
          }
        } else node=new K.Rect({...com,fill:'#F4EAEC'});
      } else {
        const sh=ld.shape||'rect';
        if(sh==='circle'||sh==='oval'){
          node=new K.Ellipse({...com,...shd,...str,
            radiusX:ld.width/2,radiusY:ld.height/2,
            x:ld.x+ld.width/2,y:ld.y+ld.height/2,
            fill:ld.color||'#C4899A',perfectDrawEnabled:false});
        } else if(sh==='garland'){
          const g=new K.Group({...com});
          for(let i=0;i<14;i++){
            const sz=55+(i%4)*22,px=ld.width*.05+(i/13)*ld.width*.9;
            const py=ld.height*.12+Math.sin((i/13)*Math.PI)*ld.height*.38;
            const col=i%3===0?'#FAF0E8':i%3===1?ld.color:ld.stroke;
            g.add(new K.Circle({x:px,y:py,radius:sz/2,fill:col,stroke:'rgba(255,255,255,.45)',strokeWidth:2}));
          } node=g;
        } else if(sh==='florals'){
          const g=new K.Group({...com});
          for(let i=0;i<10;i++){
            const x=ld.width*(0.08+(i*.13)%0.78),y=ld.height*(0.08+(i*.19)%0.72);
            const r=ld.width*(0.08+(i%4)*.02);
            g.add(new K.Circle({x,y,radius:r,fill:i%2===0?ld.color:'#F1C6D0',stroke:i%3===0?ld.stroke:'rgba(255,255,255,.55)',strokeWidth:2}));
          } node=g;
        } else if(sh==='table'){
          const g=new K.Group({...com});
          g.add(new K.Rect({x:0,y:ld.height*.08,width:ld.width,height:ld.height*.38,fill:ld.color,...str,cornerRadius:18}));
          g.add(new K.Rect({x:ld.width*.1,y:ld.height*.48,width:ld.width*.8,height:ld.height*.52,fill:ld.color,...str,cornerRadius:14}));
          node=g;
        } else {
          const p=shapePath(sh,ld.width,ld.height);
          node=p
            ?new K.Path({...com,...shd,...str,data:p,fill:ld.color||'#C4899A',perfectDrawEnabled:false})
            :new K.Rect({...com,...shd,...str,fill:ld.color||'#C4899A',cornerRadius:ld.borderRadius||0,perfectDrawEnabled:false});
        }
      }
      if(node){ attach(node,ld); nodeMap[ld.id]=node; ml.add(node); }
    });

    const selNodes=selectedIds.map(id=>nodeMap[id]).filter(Boolean);
    transformer.nodes(selNodes);
    ml.batchDraw(); uiLyr.current&&uiLyr.current.batchDraw();

    function attach(node,ld){
      node._layerDataId=ld.id;
      node.on('click tap',e=>{
        e.cancelBubble=true;
        if(ld.groupId){
          onSelect(layers.filter(l=>l.groupId===ld.groupId).map(l=>l.id));
        } else onSelect([ld.id]);
      });
      node.on('contextmenu',e=>{
        e.evt.preventDefault();
        onContextMenu({x:e.evt.clientX,y:e.evt.clientY,layerId:ld.id});
      });
      node.on('dragend',e=>{
        if(ld.locked) return;
        onChange(ld.id,{x:Math.round(e.target.x()),y:Math.round(e.target.y())});
      });
      node.on('transformend',e=>{
        const n=e.target,sx=n.scaleX(),sy=n.scaleY();
        n.scaleX(1);n.scaleY(1);
        onChange(ld.id,{
          x:Math.round(n.x()),y:Math.round(n.y()),
          width:Math.round(Math.max(10,n.width()*Math.abs(sx))),
          height:Math.round(Math.max(10,n.height()*Math.abs(sy))),
          rotation:Math.round(n.rotation()),
        });
      });
    }
    if(stage.current) stage.current._ml=ml;
  },[layers,selectedIds]);// eslint-disable-line

  React.useEffect(()=>{
    if(stage.current) window._kStage=stage.current;
    return()=>{delete window._kStage;};
  },[]);

  return (
    <div ref={containerRef} style={{
      flex:1,width:'100%',height:'100%',cursor:'default',
      background:'#d8d4cf',
      backgroundImage:[
        'linear-gradient(45deg,#ccc9c4 25%,transparent 25%)',
        'linear-gradient(-45deg,#ccc9c4 25%,transparent 25%)',
        'linear-gradient(45deg,transparent 75%,#ccc9c4 75%)',
        'linear-gradient(-45deg,transparent 75%,#ccc9c4 75%)',
      ].join(','),
      backgroundSize:'24px 24px',
      backgroundPosition:'0 0,0 12px,12px -12px,-12px 0',
    }}/>
  );
}

// ─── Canvas Settings Modal ────────────────────────────────────────
function CanvasSettingsModal({canvas,onUpdate,onClose}) {
  const [local,setLocal]=React.useState({...canvas});
  const set=p=>setLocal(c=>({...c,...p}));
  return (
    <div style={{position:'fixed',inset:0,zIndex:9999,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(58,42,32,0.45)',backdropFilter:'blur(8px)'}}>
      <div style={{background:'var(--card)',borderRadius:20,width:380,maxWidth:'94vw',boxShadow:'0 24px 80px rgba(0,0,0,0.22)',overflow:'hidden'}}>
        <div style={{padding:'18px 22px',borderBottom:'1px solid var(--border)',display:'flex',alignItems:'center',justifyContent:'space-between'}}>
          <span style={{fontSize:16,fontWeight:800,color:'var(--text)',fontFamily:"'DM Sans',sans-serif"}}>Ajustes del Canvas</span>
          <button type="button" onClick={onClose} style={{border:'none',background:'none',cursor:'pointer',padding:4}}><Icon name="minus" size={18} color="var(--muted)"/></button>
        </div>
        <div style={{padding:22,display:'grid',gap:16}}>
          <Grid2>
            <IField label="Ancho (px)"><NumIn value={local.width} onChange={v=>set({width:v})} min={100} max={10000} /></IField>
            <IField label="Alto (px)"><NumIn value={local.height} onChange={v=>set({height:v})} min={100} max={10000} /></IField>
          </Grid2>
          <ColorRow label="Color de fondo" value={local.bg} onChange={v=>set({bg:v})} />
          <IField label="Formato de canvas">
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:6}}>
              {[{l:'A4',w:2480,h:3508},{l:'16:9',w:3840,h:2160},{l:'Square',w:3000,h:3000},{l:'Story',w:2160,h:3840},{l:'Banner',w:3000,h:1000},{l:'Custom',w:local.width,h:local.height}].map(f=>(
                <button key={f.l} type="button" onClick={()=>set({width:f.w,height:f.h})}
                  style={{border:'1px solid var(--border)',borderRadius:8,padding:'6px 4px',cursor:'pointer',fontSize:11,fontWeight:700,
                    background:local.width===f.w&&local.height===f.h?'var(--accent-light)':'var(--card)',
                    color:local.width===f.w&&local.height===f.h?'var(--primary)':'var(--muted)'}}>
                  {f.l}
                </button>
              ))}
            </div>
          </IField>
        </div>
        <div style={{padding:'12px 22px',borderTop:'1px solid var(--border)',display:'flex',justifyContent:'flex-end',gap:8}}>
          <TBtn label="Cancelar" onClick={onClose} />
          <Btn onClick={()=>{onUpdate(local);onClose();}}>Aplicar</Btn>
        </div>
      </div>
    </div>
  );
}

// ─── Context Menu ──────────────────────────────────────────────────
function CtxMenu({ctx,layers,selectedIds,onDup,onDel,onUp,onDown,onTop,onBottom,onLock,onHide,onGroup,onUngroup,onAddGuide,onClose}) {
  if(!ctx) return null;
  const layer=ctx.layerId?layers.find(l=>l.id===ctx.layerId):null;
  const isGroup=selectedIds.length>1;
  const menuItems=[
    layer&&{icon:'plus',label:'Duplicar',action:onDup},
    layer&&{sep:true},
    layer&&{icon:'chevRight',label:'Traer adelante',action:onUp},
    layer&&{icon:'chevLeft',label:'Enviar atrás',action:onDown},
    layer&&{icon:'arrowLeft',label:'Traer al frente',action:onTop,kbd:'⌘]'},
    layer&&{icon:'arrowLeft',label:'Enviar al fondo',action:onBottom,kbd:'⌘['},
    layer&&{sep:true},
    isGroup&&{icon:'grid',label:'Agrupar selección',action:onGroup},
    layer?.groupId&&{icon:'grid',label:'Desagrupar',action:onUngroup},
    layer&&{icon:'settings',label:layer.locked?'Desbloquear':'Bloquear',action:onLock},
    layer&&{icon:'grid',label:layer.visible===false?'Mostrar capa':'Ocultar capa',action:onHide},
    {sep:true},
    {icon:'minus',label:'Añadir guía vertical',action:()=>onAddGuide('v',ctx)},
    {icon:'minus',label:'Añadir guía horizontal',action:()=>onAddGuide('h',ctx)},
    layer&&{sep:true},
    layer&&{icon:'minus',label:'Eliminar',action:onDel,danger:true,kbd:'Del'},
  ].filter(Boolean);

  return (
    <div style={{position:'fixed',left:ctx.x,top:ctx.y,zIndex:99999,minWidth:200,
      background:'rgba(255,252,249,0.98)',border:'1px solid var(--border)',
      borderRadius:14,boxShadow:'0 16px 56px rgba(58,42,32,0.2)',padding:'5px 0',
      backdropFilter:'blur(20px)'}} onClick={e=>e.stopPropagation()}>
      {layer&&(
        <div style={{padding:'7px 14px 5px',borderBottom:'1px solid var(--border)',marginBottom:4}}>
          <div style={{fontSize:11,fontWeight:800,color:'var(--primary)',textTransform:'uppercase',letterSpacing:'0.06em'}}>{layer.name}</div>
          <div style={{fontSize:10,color:'var(--muted)',marginTop:1}}>{layer.type} · {layer.width}×{layer.height}px</div>
        </div>
      )}
      {menuItems.map((item,i)=>item.sep
        ?<div key={i} style={{height:1,background:'var(--border)',margin:'4px 8px'}}/>
        :(
          <button key={i} type="button" onClick={()=>{item.action();onClose();}}
            style={{display:'flex',alignItems:'center',justifyContent:'space-between',
              width:'100%',padding:'8px 14px',border:'none',background:'none',
              cursor:'pointer',textAlign:'left',borderRadius:0,
              fontFamily:"'DM Sans',sans-serif",fontSize:13,fontWeight:600,
              color:item.danger?'#DC2626':'var(--text)'}}
            onMouseEnter={e=>e.currentTarget.style.background=item.danger?'#FEF2F2':'var(--accent-light)'}
            onMouseLeave={e=>e.currentTarget.style.background='none'}>
            <Row gap={9}>
              <Icon name={item.icon} size={14} color={item.danger?'#DC2626':'var(--primary)'}/>
              {item.label}
            </Row>
            {item.kbd&&<span style={{fontSize:10,color:'var(--muted)',fontFamily:'monospace'}}>{item.kbd}</span>}
          </button>
        )
      )}
    </div>
  );
}

// ─── Inspector Panels ─────────────────────────────────────────────
function InspSection({title,children,defaultOpen=true}) {
  const [open,setOpen]=React.useState(defaultOpen);
  return (
    <div style={{borderBottom:'1px solid var(--border)'}}>
      <button type="button" onClick={()=>setOpen(o=>!o)}
        style={{width:'100%',display:'flex',alignItems:'center',justifyContent:'space-between',
          padding:'10px 14px',border:'none',background:'none',cursor:'pointer',
          fontFamily:"'DM Sans',sans-serif",fontSize:12,fontWeight:800,
          color:'var(--text)',textTransform:'uppercase',letterSpacing:'0.06em',textAlign:'left'}}>
        {title}
        <span style={{fontSize:10,color:'var(--muted)',transform:open?'rotate(180deg)':'none',transition:'transform .2s'}}>▾</span>
      </button>
      {open&&<div style={{padding:'0 14px 14px',display:'grid',gap:10}}>{children}</div>}
    </div>
  );
}

function InspTransform({layer,up}) {
  return (
    <InspSection title="Transformación">
      <Grid2>
        <IField label="X"><NumIn value={layer.x} onChange={v=>up({x:v})} unit="px"/></IField>
        <IField label="Y"><NumIn value={layer.y} onChange={v=>up({y:v})} unit="px"/></IField>
        <IField label="Ancho"><NumIn value={layer.width} min={1} onChange={v=>up({width:v})} unit="px"/></IField>
        <IField label="Alto"><NumIn value={layer.height} min={1} onChange={v=>up({height:v})} unit="px"/></IField>
      </Grid2>
      <IField label="Rotación">
        <NumIn value={layer.rotation||0} min={-360} max={360} onChange={v=>up({rotation:v})} unit="°"/>
      </IField>
      <IField label="Voltear">
        <Row>
          <TBtn label="↔ H" title="Flip horizontal" active={layer.flipX} onClick={()=>up({flipX:!layer.flipX})} sx={{flex:1}}/>
          <TBtn label="↕ V" title="Flip vertical" active={layer.flipY} onClick={()=>up({flipY:!layer.flipY})} sx={{flex:1}}/>
        </Row>
      </IField>
    </InspSection>
  );
}

function InspAppearance({layer,up}) {
  return (
    <InspSection title="Apariencia">
      <ColorRow label="Color de relleno" value={layer.color} onChange={v=>up({color:v})}/>
      {layer.type!=='text'&&<>
        <Row gap={8}>
          <div style={{flex:1}}><ColorRow label="Borde" value={layer.stroke} onChange={v=>up({stroke:v})}/></div>
          <IField label="Grosor"><NumIn value={layer.strokeWidth??3} min={0} max={40} onChange={v=>up({strokeWidth:v})} unit="px"/></IField>
        </Row>
        {(!layer.shape||layer.shape==='rect')&&
          <RangeRow label="Radio esquinas" value={layer.borderRadius||0} min={0} max={200} step={1} fmt={v=>`${v}px`} onChange={v=>up({borderRadius:v})}/>}
      </>}
      <RangeRow label="Opacidad" value={layer.opacity??1} fmt={v=>`${Math.round(v*100)}%`} onChange={v=>up({opacity:v})}/>
      <IField label="Modo de fusión">
        <select value={layer.blendMode||'normal'} onChange={e=>up({blendMode:e.target.value})}
          style={{...IS,fontSize:12,padding:'5px 8px'}}>
          {C_BLENDS.map(b=><option key={b} value={b}>{b}</option>)}
        </select>
      </IField>
    </InspSection>
  );
}

function InspShadow({layer,up}) {
  return (
    <InspSection title="Sombra" defaultOpen={false}>
      <label style={{display:'flex',alignItems:'center',gap:8,cursor:'pointer',userSelect:'none'}}>
        <div onClick={()=>up({shadow:!layer.shadow})}
          style={{width:36,height:20,borderRadius:99,background:layer.shadow?'var(--primary)':'var(--border)',
            position:'relative',transition:'background .2s',cursor:'pointer'}}>
          <div style={{width:16,height:16,borderRadius:'50%',background:'#fff',position:'absolute',
            top:2,left:layer.shadow?18:2,transition:'left .2s',boxShadow:'0 1px 4px rgba(0,0,0,.2)'}}/>
        </div>
        <span style={{fontSize:12,fontWeight:600,color:'var(--text)'}}>Activar sombra</span>
      </label>
      {layer.shadow&&<>
        <ColorRow label="Color" value={layer.shadowColor||'#3A2A20'} onChange={v=>up({shadowColor:v})}/>
        <Grid2>
          <IField label="Offset X"><NumIn value={layer.shadowX??8} onChange={v=>up({shadowX:v})} unit="px"/></IField>
          <IField label="Offset Y"><NumIn value={layer.shadowY??8} onChange={v=>up({shadowY:v})} unit="px"/></IField>
        </Grid2>
        <RangeRow label="Desenfoque" value={layer.shadowBlur??20} min={0} max={80} step={1} fmt={v=>`${v}px`} onChange={v=>up({shadowBlur:v})}/>
        <RangeRow label="Opacidad" value={layer.shadowOpacity??0.6} fmt={v=>`${Math.round(v*100)}%`} onChange={v=>up({shadowOpacity:v})}/>
      </>}
    </InspSection>
  );
}

function buildCssFilter(layer) {
  const parts = [];
  const preset = C_FILTERS.find(f => f.id === layer.filter);
  if (preset && preset.css) parts.push(preset.css);
  if ((layer.adjBrightness ?? 100) !== 100) parts.push(`brightness(${layer.adjBrightness}%)`);
  if ((layer.adjContrast ?? 100) !== 100) parts.push(`contrast(${layer.adjContrast}%)`);
  if ((layer.adjSaturation ?? 100) !== 100) parts.push(`saturate(${layer.adjSaturation}%)`);
  if ((layer.adjBlur ?? 0) > 0) parts.push(`blur(${layer.adjBlur}px)`);
  return parts.join(' ') || 'none';
}

function InspFilters({layer,up}) {
  return (
    <InspSection title="Filtros y ajustes" defaultOpen={false}>
      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:6,marginBottom:8}}>
        {C_FILTERS.map(f=>(
          <button key={f.id} type="button" onClick={()=>up({filter:f.id})}
            style={{border:`1.5px solid ${layer.filter===f.id?'var(--primary)':'var(--border)'}`,
              borderRadius:8,padding:'6px 8px',cursor:'pointer',fontSize:11,fontWeight:700,
              background:layer.filter===f.id?'var(--accent-light)':'var(--card)',
              color:layer.filter===f.id?'var(--primary)':'var(--text)',textAlign:'left'}}>
            {f.label}
          </button>
        ))}
      </div>
      {layer.filter&&layer.filter!=='none'&&
        <RangeRow label="Intensidad preset" value={layer.filterIntensity??1} fmt={v=>`${Math.round(v*100)}%`} onChange={v=>up({filterIntensity:v})}/>
      }
      <Sep/>
      <Lbl>Ajustes manuales</Lbl>
      <RangeRow label="Brillo" value={layer.adjBrightness??100} min={0} max={200} step={1} fmt={v=>`${v}%`} onChange={v=>up({adjBrightness:v})}/>
      <RangeRow label="Contraste" value={layer.adjContrast??100} min={0} max={200} step={1} fmt={v=>`${v}%`} onChange={v=>up({adjContrast:v})}/>
      <RangeRow label="Saturación" value={layer.adjSaturation??100} min={0} max={200} step={1} fmt={v=>`${v}%`} onChange={v=>up({adjSaturation:v})}/>
      <RangeRow label="Desenfoque" value={layer.adjBlur??0} min={0} max={10} step={0.5} fmt={v=>`${v}px`} onChange={v=>up({adjBlur:v})}/>
      {((layer.adjBrightness??100)!==100||(layer.adjContrast??100)!==100||(layer.adjSaturation??100)!==100||(layer.adjBlur??0)>0)&&(
        <button type="button"
          onClick={()=>up({adjBrightness:100,adjContrast:100,adjSaturation:100,adjBlur:0})}
          style={{display:'flex',alignItems:'center',gap:4,border:'none',background:'none',
            cursor:'pointer',fontSize:10,fontWeight:700,color:'var(--muted)',padding:'2px 0',marginTop:4,
            fontFamily:"'DM Sans',sans-serif"}}>
          ↺ Restablecer ajustes
        </button>
      )}
    </InspSection>
  );
}

function InspText({layer,up}) {
  if(layer.type!=='text') return null;
  return (
    <InspSection title="Texto">
      <IField label="Contenido">
        <textarea value={layer.text||''} rows={3} onChange={e=>up({text:e.target.value})}
          style={{...IS,resize:'vertical',lineHeight:1.45,fontSize:13}}/>
      </IField>
      <IField label="Fuente">
        <select value={layer.fontFamily||'Cormorant Garamond'} onChange={e=>up({fontFamily:e.target.value})} style={{...IS}}>
          {C_FONTS.map(f=><option key={f} value={f}>{f}</option>)}
        </select>
      </IField>
      <Grid2>
        <IField label="Tamaño"><NumIn value={layer.fontSize||96} min={8} max={600} onChange={v=>up({fontSize:v})} unit="px"/></IField>
        <IField label="Espaciado"><NumIn value={layer.letterSpacing||0} min={-10} max={40} step={0.5} onChange={v=>up({letterSpacing:v})} unit="px"/></IField>
      </Grid2>
      <ColorRow label="Color" value={layer.color} onChange={v=>up({color:v})}/>
      <IField label="Estilo">
        <ToggleGroup value={layer.fontWeight==='bold'?'bold':'normal'}
          onChange={v=>up({fontWeight:v})}
          options={[{v:'normal',l:'Regular'},{v:'bold',l:'Bold'}]}/>
      </IField>
      <IField label="Alineación">
        <ToggleGroup value={layer.textAlign||'center'}
          onChange={v=>up({textAlign:v})}
          options={[{v:'left',l:'←'},{v:'center',l:'↔'},{v:'right',l:'→'}]}/>
      </IField>
      <RangeRow label="Interlineado" value={layer.lineHeight??1.2} min={0.8} max={4} step={0.05} fmt={v=>v.toFixed(2)} onChange={v=>up({lineHeight:v})}/>
    </InspSection>
  );
}

function InspLayers({layers,selectedIds,onSelect,onUpdate,onReorder}) {
  return (
    <div style={{padding:'8px 10px',display:'grid',gap:3}}>
      {[...layers].reverse().map((l,i)=>{
        const sel=selectedIds.includes(l.id);
        return (
          <div key={l.id} onClick={()=>onSelect([l.id])}
            style={{display:'flex',alignItems:'center',gap:7,padding:'7px 9px',borderRadius:10,cursor:'pointer',
              background:sel?'var(--accent-light)':'transparent',
              border:`1px solid ${sel?'var(--primary)':'transparent'}`,transition:'all .12s'}}>
            <div style={{width:24,height:24,borderRadius:6,background:l.color||'var(--accent-light)',border:'1px solid var(--border)',flexShrink:0}}/>
            <span style={{flex:1,fontSize:12,fontWeight:700,color:'var(--text)',minWidth:0,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{l.name}</span>
            <button type="button" title={l.visible!==false?'Ocultar':'Mostrar'} onClick={e=>{e.stopPropagation();onUpdate(l.id,{visible:l.visible===false});}}
              style={{border:'none',background:'none',cursor:'pointer',padding:2,opacity:l.visible===false?.3:1}}>
              <Icon name="grid" size={13} color="var(--muted)"/>
            </button>
            <button type="button" title={l.locked?'Desbloquear':'Bloquear'} onClick={e=>{e.stopPropagation();onUpdate(l.id,{locked:!l.locked});}}
              style={{border:'none',background:'none',cursor:'pointer',padding:2}}>
              <Icon name="settings" size={13} color={l.locked?'var(--primary)':'var(--muted)'}/>
            </button>
          </div>
        );
      })}
    </div>
  );
}

function applyCrop(imgEl, cropRect) {
  const offscreen = document.createElement('canvas');
  offscreen.width = Math.round(cropRect.w);
  offscreen.height = Math.round(cropRect.h);
  const ctx = offscreen.getContext('2d');
  ctx.drawImage(imgEl, cropRect.x, cropRect.y, cropRect.w, cropRect.h, 0, 0, cropRect.w, cropRect.h);
  return { dataUrl: offscreen.toDataURL('image/png'), width: Math.round(cropRect.w), height: Math.round(cropRect.h) };
}

function CropOverlay({ layer, onApply, onCancel }) {
  const [imgEl, setImgEl] = React.useState(null);
  const [imgSize, setImgSize] = React.useState({ w: 0, h: 0 });
  const [displaySize, setDisplaySize] = React.useState({ w: 0, h: 0 });
  const [cropRect, setCropRect] = React.useState(null);
  const [dragState, setDragState] = React.useState(null);
  const [loadError, setLoadError] = React.useState('');

  React.useEffect(() => {
    const img = new window.Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      const maxW = Math.min(window.innerWidth * 0.8, 900);
      const maxH = window.innerHeight * 0.7;
      const scale = Math.min(maxW / img.naturalWidth, maxH / img.naturalHeight, 1);
      const dw = Math.round(img.naturalWidth * scale);
      const dh = Math.round(img.naturalHeight * scale);
      setImgEl(img);
      setImgSize({ w: img.naturalWidth, h: img.naturalHeight });
      setDisplaySize({ w: dw, h: dh });
      setCropRect({ x: 0, y: 0, w: img.naturalWidth, h: img.naturalHeight });
    };
    img.onerror = () => setLoadError('No se puede recortar esta imagen (restricción CORS). Descárgala y vuelve a subirla.');
    img.src = layer.src;
  }, [layer.src]);

  const scale = imgSize.w > 0 ? displaySize.w / imgSize.w : 1;

  const dispCrop = cropRect ? {
    x: cropRect.x * scale,
    y: cropRect.y * scale,
    w: cropRect.w * scale,
    h: cropRect.h * scale,
  } : null;

  const handleMouseDown = (e, handle) => {
    e.preventDefault();
    e.stopPropagation();
    setDragState({ handle, startX: e.clientX, startY: e.clientY, startRect: { ...cropRect } });
  };

  React.useEffect(() => {
    if (!dragState) return;
    const onMove = (e) => {
      const dx = (e.clientX - dragState.startX) / scale;
      const dy = (e.clientY - dragState.startY) / scale;
      const r = { ...dragState.startRect };
      const min = 20;
      if (dragState.handle === 'tl') {
        r.x = Math.min(r.x + dx, r.x + r.w - min); r.w = dragState.startRect.w - dx; r.y = Math.min(r.y + dy, r.y + r.h - min); r.h = dragState.startRect.h - dy;
      } else if (dragState.handle === 'tr') {
        r.w = Math.max(min, dragState.startRect.w + dx); r.y = Math.min(r.y + dy, r.y + r.h - min); r.h = dragState.startRect.h - dy;
      } else if (dragState.handle === 'bl') {
        r.x = Math.min(r.x + dx, r.x + r.w - min); r.w = dragState.startRect.w - dx; r.h = Math.max(min, dragState.startRect.h + dy);
      } else if (dragState.handle === 'br') {
        r.w = Math.max(min, dragState.startRect.w + dx); r.h = Math.max(min, dragState.startRect.h + dy);
      }
      r.x = Math.max(0, Math.min(r.x, imgSize.w - min));
      r.y = Math.max(0, Math.min(r.y, imgSize.h - min));
      r.w = Math.max(min, Math.min(r.w, imgSize.w - r.x));
      r.h = Math.max(min, Math.min(r.h, imgSize.h - r.y));
      setCropRect(r);
    };
    const onUp = () => setDragState(null);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
  }, [dragState, scale, imgSize]);

  const handleApply = () => {
    if (!imgEl || !cropRect) return;
    const result = applyCrop(imgEl, cropRect);
    onApply(result.dataUrl, result.width, result.height);
  };

  const handleStyle = { position: 'absolute', width: 14, height: 14, background: '#fff', border: '2px solid var(--primary)', borderRadius: 3, cursor: 'nwse-resize', zIndex: 10 };

  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 700, background: 'rgba(0,0,0,0.75)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16 }}>
      {loadError ? (
        <div style={{ background: 'var(--card)', borderRadius: 16, padding: 28, maxWidth: 360, textAlign: 'center' }}>
          <div style={{ fontSize: 14, color: 'var(--text)', marginBottom: 16 }}>{loadError}</div>
          <button type="button" onClick={onCancel} style={{ padding: '8px 24px', borderRadius: 10, border: 'none', background: 'var(--primary)', color: '#fff', fontWeight: 700, cursor: 'pointer' }}>Cerrar</button>
        </div>
      ) : imgEl && dispCrop ? (
        <>
          <div style={{ position: 'relative', width: displaySize.w, height: displaySize.h, flexShrink: 0 }}>
            <img src={layer.src} alt="" crossOrigin="anonymous"
              style={{ width: displaySize.w, height: displaySize.h, display: 'block', borderRadius: 4, objectFit: 'fill' }}/>
            <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
              <div style={{ position: 'absolute', left: dispCrop.x, top: dispCrop.y, width: dispCrop.w, height: dispCrop.h, boxShadow: '0 0 0 9999px rgba(0,0,0,0.5)', borderRadius: 2 }}>
                <div style={{ position: 'absolute', left: '33.33%', top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.3)' }}/>
                <div style={{ position: 'absolute', left: '66.66%', top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.3)' }}/>
                <div style={{ position: 'absolute', top: '33.33%', left: 0, right: 0, height: 1, background: 'rgba(255,255,255,0.3)' }}/>
                <div style={{ position: 'absolute', top: '66.66%', left: 0, right: 0, height: 1, background: 'rgba(255,255,255,0.3)' }}/>
                <div style={{ position: 'absolute', inset: 0, border: '2px solid var(--primary)', borderRadius: 2 }}/>
                <div style={{ ...handleStyle, top: -7, left: -7, cursor: 'nw-resize', pointerEvents: 'all' }} onMouseDown={e => handleMouseDown(e, 'tl')}/>
                <div style={{ ...handleStyle, top: -7, right: -7, cursor: 'ne-resize', pointerEvents: 'all' }} onMouseDown={e => handleMouseDown(e, 'tr')}/>
                <div style={{ ...handleStyle, bottom: -7, left: -7, cursor: 'sw-resize', pointerEvents: 'all' }} onMouseDown={e => handleMouseDown(e, 'bl')}/>
                <div style={{ ...handleStyle, bottom: -7, right: -7, cursor: 'se-resize', pointerEvents: 'all' }} onMouseDown={e => handleMouseDown(e, 'br')}/>
              </div>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 10 }}>
            <button type="button" onClick={onCancel}
              style={{ padding: '9px 22px', borderRadius: 10, border: '1px solid rgba(255,255,255,0.3)', background: 'transparent', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer' }}>
              Cancelar
            </button>
            <button type="button" onClick={handleApply}
              style={{ padding: '9px 22px', borderRadius: 10, border: 'none', background: 'var(--primary)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer' }}>
              Aplicar recorte
            </button>
          </div>
        </>
      ) : (
        <div style={{ color: '#fff', fontSize: 14 }}>Cargando imagen...</div>
      )}
    </div>
  );
}

async function removeBgFromLayer(layer, up, setRmBgLoading, setRmBgError) {
  const apiKey = localStorage.getItem('lelusa.removebg.apikey');
  if (!apiKey) {
    setRmBgError('Configura tu API key en Ajustes > Integraciones');
    setTimeout(() => setRmBgError(''), 4000);
    return;
  }
  setRmBgLoading(true);
  setRmBgError('');
  try {
    const formData = new FormData();
    if (layer.src && layer.src.startsWith('data:')) {
      const res = await fetch(layer.src);
      const blob = await res.blob();
      formData.append('image_file', blob, 'image.png');
    } else if (layer.src) {
      formData.append('image_url', layer.src);
    } else {
      throw new Error('Esta capa no tiene imagen');
    }
    formData.append('size', 'auto');
    const response = await fetch('https://api.remove.bg/v1.0/removebg', {
      method: 'POST',
      headers: { 'X-Api-Key': apiKey },
      body: formData,
    });
    if (!response.ok) {
      const err = await response.json().catch(() => ({}));
      throw new Error(err?.errors?.[0]?.title || `Error ${response.status}`);
    }
    const blob = await response.blob();
    await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => { up({ src: reader.result }); resolve(); };
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  } catch (err) {
    setRmBgError(err.message || 'Error al eliminar fondo');
    setTimeout(() => setRmBgError(''), 4000);
  } finally {
    setRmBgLoading(false);
  }
}

function Inspector({layers,selectedIds,onSelect,onUpdate,onDelete,guides,onAddGuide,onRemoveGuide,isMobile,inventoryAssets,onCrop}) {
  const [inspTab,setInspTab]=React.useState('design');
  const sel=selectedIds.length===1?layers.find(l=>l.id===selectedIds[0]):null;
  const up=p=>{if(sel)onUpdate(sel.id,p);};
  const [rmBgLoading, setRmBgLoading] = React.useState(false);
  const [rmBgError, setRmBgError] = React.useState('');

  return (
    <div style={{width:isMobile?'100%':248,flexShrink:0,display:'flex',flexDirection:'column',
      background:'var(--card)',borderLeft:isMobile?'none':'1px solid var(--border)',overflow:'hidden'}}>

      {/* Tabs header — solo desktop */}
      {!isMobile&&(
        <div style={{display:'flex',borderBottom:'1px solid var(--border)',flexShrink:0}}>
          {[['design','Diseño'],['layers','Capas'],['guides','Guías']].map(([id,lbl])=>(
            <button key={id} type="button" onClick={()=>setInspTab(id)}
              style={{flex:1,border:'none',background:'none',padding:'10px 4px',
                fontSize:10.5,fontWeight:inspTab===id?800:600,
                color:inspTab===id?'var(--primary)':'var(--muted)',
                borderBottom:inspTab===id?'2px solid var(--primary)':'2px solid transparent',
                cursor:'pointer',fontFamily:"'DM Sans',sans-serif",transition:'color .12s'}}>
              {lbl}
            </button>
          ))}
        </div>
      )}

      <div style={{flex:1,overflowY:'auto',padding:isMobile?8:10}}>

        {/* TAB DISEÑO: contenido de propiedades */}
        {(isMobile||inspTab==='design')&&(
          <div>
            {sel?(
              <div>
                {/* Layer name */}
                <div style={{padding:'10px 14px 6px',borderBottom:'1px solid var(--border)'}}>
                  <input value={sel.name} onChange={e=>up({name:e.target.value})}
                    style={{...IS,fontWeight:700,fontSize:13}} placeholder="Nombre de capa"/>
                </div>
                <InspTransform layer={sel} up={up}/>
                <InspAppearance layer={sel} up={up}/>
                {sel.type==='text'&&<InspText layer={sel} up={up}/>}
                <InspShadow layer={sel} up={up}/>
                {/* Botón Agregar al inventario — solo para capas imagen sin vínculo */}
                {sel.type==='image'&&!sel.inventoryAssetId&&(
                  <div style={{padding:'6px 14px'}}>
                    <button type="button"
                      onClick={()=>window.LelusaNav?.('admin-inventory',{action:'new',prefill:{name:sel.name,image:sel.src}})}
                      style={{display:'flex',alignItems:'center',gap:6,width:'100%',
                        padding:'8px 12px',border:'1px solid var(--border)',borderRadius:10,
                        background:'var(--card)',cursor:'pointer',
                        fontSize:11,fontWeight:700,color:'var(--text)',
                        fontFamily:"'DM Sans',sans-serif",transition:'background .1s'}}>
                      <Icon name="plus" size={12} color="var(--primary)"/>
                      Agregar al inventario →
                    </button>
                  </div>
                )}
                {/* Remove background */}
                {sel.type==='image'&&(
                  <div style={{padding:'4px 14px 8px'}}>
                    <button type="button"
                      disabled={rmBgLoading}
                      onClick={()=>removeBgFromLayer(sel,up,setRmBgLoading,setRmBgError)}
                      style={{display:'flex',alignItems:'center',gap:6,width:'100%',
                        padding:'8px 12px',border:'1px solid var(--border)',borderRadius:10,
                        background:rmBgLoading?'var(--bg)':'var(--card)',cursor:rmBgLoading?'not-allowed':'pointer',
                        fontSize:11,fontWeight:700,color:'var(--text)',
                        fontFamily:"'DM Sans',sans-serif",transition:'background .1s',
                        opacity:rmBgLoading?0.6:1}}>
                      <span style={{fontSize:13}}>✦</span>
                      {rmBgLoading?'Procesando...':'Eliminar fondo'}
                    </button>
                    {rmBgError&&(
                      <div style={{fontSize:10,color:'#C83A3A',marginTop:4,padding:'0 2px',lineHeight:1.4}}>
                        {rmBgError}
                      </div>
                    )}
                    <button type="button"
                      onClick={()=>onCrop(sel.id)}
                      style={{display:'flex',alignItems:'center',gap:6,width:'100%',
                        padding:'8px 12px',border:'1px solid var(--border)',borderRadius:10,
                        background:'var(--card)',cursor:'pointer',marginTop:6,
                        fontSize:11,fontWeight:700,color:'var(--text)',
                        fontFamily:"'DM Sans',sans-serif",transition:'background .1s'}}>
                      <span style={{fontSize:13}}>✂</span>
                      Recortar
                    </button>
                  </div>
                )}
                <InspFilters layer={sel} up={up}/>
                {inventoryAssets&&inventoryAssets.length>0&&(
                  <InspInventoryLink layer={sel} up={up} inventoryAssets={inventoryAssets}/>
                )}
                {/* Delete */}
                <div style={{padding:14}}>
                  {sel.inventoryAssetId&&(
                    <div style={{background:'var(--accent-light)',borderRadius:10,padding:'8px 12px',marginBottom:8,display:'flex',alignItems:'center',gap:6}}>
                      <Icon name="sparkles" size={13} color="var(--primary)"/>
                      <span style={{fontSize:11,fontWeight:700,color:'var(--primary)'}}>Vinculado al inventario</span>
                    </div>
                  )}
                  <TBtn label="Eliminar capa" icon="minus" danger onClick={onDelete} sx={{width:'100%',justifyContent:'center',height:36}}/>
                </div>
              </div>
            ):(
              <div style={{padding:20,textAlign:'center'}}>
                <div style={{width:56,height:56,borderRadius:16,background:'var(--accent-light)',display:'grid',placeItems:'center',margin:'0 auto 12px'}}>
                  <Icon name="sparkles" size={24} color="var(--primary)"/>
                </div>
                <div style={{fontSize:13,fontWeight:700,color:'var(--text)',marginBottom:4}}>Sin selección</div>
                <div style={{fontSize:11,color:'var(--muted)'}}>Haz clic en un elemento del canvas para editar sus propiedades</div>
              </div>
            )}
          </div>
        )}

        {/* TAB CAPAS */}
        {(!isMobile&&inspTab==='layers')&&(
          <InspLayers layers={layers} selectedIds={selectedIds} onSelect={onSelect} onUpdate={onUpdate}/>
        )}

        {/* TAB GUÍAS */}
        {(!isMobile&&inspTab==='guides')&&(
          <div style={{display:'grid',gap:8}}>
            <div style={{display:'flex',gap:6}}>
              <TBtn label="+ Guía V" onClick={()=>onAddGuide('v')} sx={{flex:1,justifyContent:'center'}}/>
              <TBtn label="+ Guía H" onClick={()=>onAddGuide('h')} sx={{flex:1,justifyContent:'center'}}/>
            </div>
            {guides.length===0&&(
              <div style={{fontSize:11,color:'var(--muted)',textAlign:'center',padding:'12px 0',lineHeight:1.5}}>
                Sin guías. Añade guías para alinear elementos precisamente.
              </div>
            )}
            {guides.map((g,i)=>(
              <div key={i} style={{display:'flex',alignItems:'center',gap:7,
                border:'1px solid var(--border)',borderRadius:9,padding:'7px 10px',background:'var(--card)'}}>
                <span style={{fontSize:11,fontWeight:700,color:'var(--muted)',width:14}}>{g.type==='v'?'V':'H'}</span>
                <NumIn value={g.type==='v'?g.x:g.y} onChange={v=>onRemoveGuide(i,g.type==='v'?{...g,x:v}:{...g,y:v})} unit="px"/>
                <button type="button" onClick={()=>onRemoveGuide(i)}
                  style={{border:'none',background:'none',cursor:'pointer',padding:4,color:'var(--muted)',fontSize:14,lineHeight:1}}>×</button>
              </div>
            ))}
          </div>
        )}

      </div>
    </div>
  );
}

// ─── Left Panel ───────────────────────────────────────────────────
function LeftPanel({tab,setTab,onAddShape,onAddText,onAddAsset,onUpload,onTemplate,projects,activeId,onNewProject,onSwitch,isMobile,inventoryAssets,canvasAssets,canvasAssetsLoading,onUploadCanvasAsset,onDeleteCanvasAsset}) {
  const [search,setSearch]=React.useState('');
  const [assetsTab,setAssetsTab]=React.useState('inventario');

  const invList=(inventoryAssets||[]).map(a=>({...a,cat:a.category||'Inventario'}));
  const hasInventory=invList.length>0;
  const effectiveTab=hasInventory?assetsTab:'mis-imagenes';
  const sourceAssets=effectiveTab==='inventario'?invList:(canvasAssets||[]);
  const filtered=search
    ?sourceAssets.filter(a=>(a.name||'').toLowerCase().includes(search.toLowerCase())||(a.cat||a.category||'General').toLowerCase().includes(search.toLowerCase()))
    :sourceAssets;
  const cats=effectiveTab==='inventario'?[...new Set(filtered.map(a=>a.cat||a.category||'General'))]:['Mis imágenes'];

  const sidebarTabs=[
    {id:'assets',icon:'✦',label:'Assets'},
    {id:'shapes',icon:'◆',label:'Formas'},
    {id:'tmpl',icon:'⬚',label:'Tmpl'},
    {id:'proj',icon:'📁',label:'Proy.'},
  ];

  const iconBtnStyle=(id)=>({
    width:56,height:54,borderRadius:10,border:'none',
    background:tab===id?'var(--accent-light)':'transparent',
    cursor:'pointer',display:'flex',flexDirection:'column',
    alignItems:'center',justifyContent:'center',gap:3,
    fontSize:7.5,fontWeight:700,
    color:tab===id?'var(--primary)':'var(--muted)',
    transition:'background .12s, color .12s',
    fontFamily:"'DM Sans',sans-serif",
  });

  const panelExpanded=(
    <div style={{width:isMobile?'100%':240,background:'var(--card)',
      borderRight:isMobile?'none':'1px solid var(--border)',
      display:'flex',flexDirection:'column',overflow:'hidden',flexShrink:0}}>

      {/* Panel header */}
      <div style={{padding:'12px 14px 8px',display:'flex',alignItems:'center',justifyContent:'space-between',
        borderBottom:'1px solid var(--border)',flexShrink:0}}>
        <span style={{fontSize:13,fontWeight:800,color:'var(--text)',fontFamily:"'DM Sans',sans-serif"}}>
          {sidebarTabs.find(t=>t.id===tab)?.label||''}
        </span>
        {tab==='assets'&&(
          <button type="button" onClick={onAddText}
            style={{height:26,padding:'0 10px',borderRadius:7,border:'1px solid var(--border)',
              background:'var(--card)',fontSize:11,fontWeight:700,color:'var(--text)',cursor:'pointer',
              display:'flex',alignItems:'center',gap:4,fontFamily:"'DM Sans',sans-serif"}}>
            <Icon name="edit" size={11} color="var(--primary)"/>T
          </button>
        )}
      </div>

      <div style={{flex:1,overflowY:'auto',padding:10}}>

        {/* ASSETS TAB */}
        {tab==='assets'&&(
          <div style={{display:'grid',gap:8}}>
            <button type="button" onClick={onUpload}
              style={{display:'flex',alignItems:'center',justifyContent:'center',gap:6,
                padding:'8px 10px',border:'1.5px dashed var(--border)',borderRadius:10,
                cursor:'pointer',background:'transparent',
                fontSize:11,fontWeight:700,color:'var(--primary)',fontFamily:"'DM Sans',sans-serif",
                width:'100%'}}>
              <Icon name="plus" size={13} color="var(--primary)"/>Subir imagen
            </button>

            <ToggleGroup value={effectiveTab} onChange={setAssetsTab}
              options={[
                {v:'inventario',l:hasInventory?`Inv. (${invList.length})`:'Inventario'},
                {v:'mis-imagenes',l:'Mis imágenes'},
              ]}/>

            {!hasInventory&&effectiveTab==='inventario'&&(
              <div style={{background:'var(--bg)',border:'1px solid var(--border)',borderRadius:8,
                padding:'8px 10px',fontSize:11,color:'var(--muted)',textAlign:'center',lineHeight:1.5}}>
                Sin items en inventario.<br/>
                <span style={{color:'var(--primary)',fontWeight:700,cursor:'pointer'}}
                  onClick={()=>window.LelusaNav?.('admin-inventory')}>Ir a Inventario</span>
              </div>
            )}

            <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="Buscar…"
              style={{...IS,fontSize:12,padding:'7px 10px'}}/>

            {effectiveTab==='inventario' && cats.map(cat=>{
              const items=filtered.filter(a=>(a.cat||a.category||'General')===cat);
              if(!items.length) return null;
              return (
                <div key={cat}>
                  <div style={{fontSize:9,fontWeight:800,color:'var(--muted)',textTransform:'uppercase',
                    letterSpacing:'0.07em',margin:'6px 0 5px'}}>{cat}</div>
                  {items.map(a=>(
                    <button key={a.id} type="button" onClick={()=>onAddAsset(a)}
                      style={{display:'flex',alignItems:'center',gap:9,width:'100%',
                        padding:'8px 10px',border:'1px solid transparent',borderRadius:10,
                        cursor:'pointer',background:'transparent',marginBottom:3,textAlign:'left',
                        transition:'background .1s, border-color .1s'}}
                      onMouseEnter={e=>{e.currentTarget.style.background='var(--bg)';e.currentTarget.style.borderColor='var(--border)';}}
                      onMouseLeave={e=>{e.currentTarget.style.background='transparent';e.currentTarget.style.borderColor='transparent';}}>
                      {(a.image||a.src)
                        ?<img src={a.image||a.src} alt="" style={{width:32,height:32,borderRadius:7,objectFit:'cover',border:'1px solid var(--border)',flexShrink:0}}/>
                        :<div style={{width:32,height:32,borderRadius:7,background:a.color||'var(--accent-light)',border:'1px solid var(--border)',flexShrink:0}}/>
                      }
                      <div style={{minWidth:0}}>
                        <div style={{fontSize:12,fontWeight:700,color:'var(--text)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{a.name}</div>
                        <div style={{fontSize:10,color:'var(--muted)'}}>
                          {a.cat||a.category}
                          {a.quantity!=null?` · Stock: ${a.quantity}`:''}
                          {a.basePrice?` · $${a.basePrice}`:''}
                          {a.status&&a.status!=='disponible'?<span style={{color:a.status==='reservado'?'#F59E0B':'#DC2626'}}> · {a.status}</span>:null}
                        </div>
                      </div>
                    </button>
                  ))}
                </div>
              );
            })}

            {effectiveTab==='mis-imagenes' && (
              <div>
                <input type="file" accept="image/*" style={{display:'none'}}
                  id="canvas-asset-file-input" title="Subir imagen al canvas"
                  onChange={e=>{const f=e.target.files?.[0];if(f){onUploadCanvasAsset(f);e.target.value='';}}}/>
                <button type="button"
                  onClick={()=>document.getElementById('canvas-asset-file-input').click()}
                  style={{display:'flex',alignItems:'center',justifyContent:'center',gap:6,
                    padding:'8px 10px',border:'1.5px dashed var(--border)',borderRadius:10,
                    cursor:'pointer',background:'transparent',
                    fontSize:11,fontWeight:700,color:'var(--primary)',fontFamily:"'DM Sans',sans-serif",
                    width:'100%',marginBottom:8}}>
                  <Icon name="plus" size={13} color="var(--primary)"/>Subir imagen
                </button>
                {canvasAssetsLoading&&(
                  <div style={{fontSize:11,color:'var(--muted)',textAlign:'center',padding:12}}>Cargando...</div>
                )}
                {!canvasAssetsLoading&&(!canvasAssets||canvasAssets.length===0)&&(
                  <div style={{fontSize:11,color:'var(--muted)',textAlign:'center',padding:'16px 8px',lineHeight:1.6}}>
                    Sube imágenes para reutilizarlas en tus diseños
                  </div>
                )}
                {!canvasAssetsLoading&&canvasAssets&&canvasAssets.length>0&&(
                  <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:6}}>
                    {canvasAssets.map(a=>(
                      <div key={a.id} style={{position:'relative',cursor:'pointer'}}
                        onClick={()=>onAddAsset({...a,src:a.url})}
                        onMouseEnter={e=>{const btn=e.currentTarget.querySelector('.ca-del-btn');if(btn)btn.style.display='flex';}}
                        onMouseLeave={e=>{const btn=e.currentTarget.querySelector('.ca-del-btn');if(btn)btn.style.display='none';}}>
                        <img src={a.url} alt={a.name}
                          style={{width:'100%',height:80,objectFit:'cover',borderRadius:8,
                            border:'1px solid var(--border)',display:'block'}}/>
                        <div style={{fontSize:10,color:'var(--muted)',textAlign:'center',
                          overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',
                          padding:'3px 2px',fontFamily:"'DM Sans',sans-serif"}}>{a.name}</div>
                        <button type="button"
                          onClick={e=>{e.stopPropagation();onDeleteCanvasAsset(a);}}
                          className="ca-del-btn"
                          style={{position:'absolute',top:4,right:4,width:20,height:20,
                            border:'none',borderRadius:4,background:'rgba(0,0,0,0.55)',
                            color:'#fff',fontSize:11,cursor:'pointer',display:'none',
                            alignItems:'center',justifyContent:'center',lineHeight:1}}>✕</button>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* SHAPES TAB */}
        {tab==='shapes'&&(
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:7}}>
            {C_SHAPES.map(s=>(
              <button key={s.id} type="button" title={s.n} onClick={()=>onAddShape(s.id)}
                style={{border:'1px solid var(--border)',borderRadius:12,padding:'10px 6px',
                  background:'var(--card)',cursor:'pointer',display:'flex',flexDirection:'column',
                  alignItems:'center',gap:6,transition:'background .1s, border-color .1s'}}
                onMouseEnter={e=>{e.currentTarget.style.background='var(--bg)';e.currentTarget.style.borderColor='var(--primary)';}}
                onMouseLeave={e=>{e.currentTarget.style.background='var(--card)';e.currentTarget.style.borderColor='var(--border)';}}>
                <svg width="40" height="40" viewBox="0 0 44 44">
                  {s.id==='circle'||s.id==='oval'
                    ?<ellipse cx="22" cy="22" rx={s.id==='oval'?20:16} ry={s.id==='oval'?11:16} fill="var(--accent-light)" stroke="var(--primary)" strokeWidth="1.5"/>
                    :s.id==='rect'
                    ?<rect x="4" y="9" width="36" height="26" rx="4" fill="var(--accent-light)" stroke="var(--primary)" strokeWidth="1.5"/>
                    :(()=>{const p=shapePath(s.id,40,40);return p?<path d={p} transform="translate(2,2)" fill="var(--accent-light)" stroke="var(--primary)" strokeWidth="1.5" fillRule="evenodd"/>:<rect x="4" y="4" width="36" height="36" fill="var(--accent-light)" stroke="var(--primary)" strokeWidth="1.5"/>;})()
                  }
                </svg>
                <span style={{fontSize:9.5,fontWeight:700,color:'var(--text)',textAlign:'center'}}>{s.n}</span>
              </button>
            ))}
          </div>
        )}

        {/* TEMPLATES TAB */}
        {tab==='tmpl'&&(
          <div style={{display:'grid',gap:8}}>
            {C_TEMPLATES.map(t=>(
              <button key={t.id} type="button" onClick={()=>onTemplate(t)}
                style={{border:'1px solid var(--border)',background:t.bg,borderRadius:14,
                  padding:'12px 14px',cursor:'pointer',textAlign:'left',
                  position:'relative',overflow:'hidden',transition:'box-shadow .12s'}}
                onMouseEnter={e=>e.currentTarget.style.boxShadow='0 4px 16px rgba(0,0,0,.1)'}
                onMouseLeave={e=>e.currentTarget.style.boxShadow='none'}>
                <div style={{position:'absolute',top:0,left:0,right:0,height:3,background:t.accent}}/>
                <div style={{fontSize:13,fontWeight:800,color:'#3A2A20',marginTop:4}}>{t.name}</div>
                <div style={{fontSize:10,color:'rgba(58,42,32,0.6)',marginTop:2}}>{t.text}</div>
              </button>
            ))}
          </div>
        )}

        {/* PROJECTS TAB */}
        {tab==='proj'&&(
          <div style={{display:'grid',gap:8}}>
            <Btn onClick={onNewProject} style={{justifyContent:'center'}}>
              <Icon name="plus" size={14} color="#fff"/>Nuevo proyecto
            </Btn>
            {projects.map(p=>(
              <button key={p.id} type="button" onClick={()=>onSwitch(p.id)}
                style={{border:'1px solid var(--border)',borderRadius:12,
                  background:p.id===activeId?'var(--accent-light)':'var(--card)',
                  padding:'10px 12px',textAlign:'left',cursor:'pointer',
                  transition:'background .1s, border-color .1s'}}
                onMouseEnter={e=>{if(p.id!==activeId){e.currentTarget.style.background='var(--bg)';e.currentTarget.style.borderColor='var(--primary)';}}}
                onMouseLeave={e=>{if(p.id!==activeId){e.currentTarget.style.background='var(--card)';e.currentTarget.style.borderColor='var(--border)';}}}>
                <div style={{fontSize:13,fontWeight:800,color:'var(--text)'}}>{p.name}</div>
                <div style={{fontSize:10,color:'var(--muted)',marginTop:2}}>{(p.layers||[]).length} capas · {new Date(p.updatedAt).toLocaleDateString('es')}</div>
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );

  if(isMobile) return panelExpanded;

  return (
    <div style={{display:'flex',flexShrink:0,height:'100%'}}>
      {/* Sidebar de iconos */}
      <div style={{width:64,background:'#faf9f8',borderRight:'1px solid var(--border)',
        display:'flex',flexDirection:'column',alignItems:'center',padding:'8px 0',gap:2,flexShrink:0}}>
        {sidebarTabs.map(t=>(
          <button key={t.id} type="button" title={t.label} onClick={()=>setTab(tab===t.id?null:t.id)}
            style={iconBtnStyle(t.id)}
            onMouseEnter={e=>{if(tab!==t.id){e.currentTarget.style.background='var(--bg)';e.currentTarget.style.color='var(--text)';}}}
            onMouseLeave={e=>{if(tab!==t.id){e.currentTarget.style.background='transparent';e.currentTarget.style.color='var(--muted)';}}}>
            <span style={{fontSize:17,lineHeight:1}}>{t.icon}</span>
            <span>{t.label}</span>
          </button>
        ))}
        <div style={{flex:1}}/>
        {/* Upload atajo directo */}
        <button type="button" title="Subir imagen" onClick={onUpload}
          style={{...iconBtnStyle(null),color:'var(--muted)'}}
          onMouseEnter={e=>{e.currentTarget.style.background='var(--bg)';}}
          onMouseLeave={e=>{e.currentTarget.style.background='transparent';}}>
          <span style={{fontSize:17,lineHeight:1}}>🖼</span>
          <span>Imagen</span>
        </button>
        {/* Añadir texto directo */}
        <button type="button" title="Añadir texto" onClick={onAddText}
          style={{...iconBtnStyle(null),color:'var(--muted)'}}
          onMouseEnter={e=>{e.currentTarget.style.background='var(--bg)';}}
          onMouseLeave={e=>{e.currentTarget.style.background='transparent';}}>
          <span style={{fontSize:17,lineHeight:1,fontFamily:'Georgia,serif',fontWeight:800}}>T</span>
          <span>Texto</span>
        </button>
      </div>
      {/* Panel expandido — solo si hay tab activo */}
      {tab&&panelExpanded}
    </div>
  );
}

// ─── Topbar ───────────────────────────────────────────────────────
function Topbar({project,layers,selectedIds,history,future,zoom,snap,grid,isMobile,
  onBack,onNameChange,onUndo,onRedo,onDup,onDelete,onAlignLeft,onAlignCenterH,onAlignRight,
  onAlignTop,onAlignCenterV,onAlignBottom,onGroup,onUngroup,onOrderUp,onOrderDown,
  onZoom,onToggleSnap,onToggleGrid,onSettings,onExportPng,onExportPdf,onExportCanvas,
  onPublish,onQuote,hasLinkedDesign}) {

  const sel1=selectedIds.length===1?layers.find(l=>l.id===selectedIds[0]):null;
  const multi=selectedIds.length>1;
  const [exportOpen,setExportOpen]=React.useState(false);

  React.useEffect(()=>{
    if(!exportOpen) return;
    const close=(e)=>{ e.stopPropagation(); setExportOpen(false); };
    window.addEventListener('click',close,{capture:true,once:true});
    return()=>window.removeEventListener('click',close,{capture:true});
  },[exportOpen]);

  const pillSep=<div style={{width:1,height:20,background:'var(--border)',margin:'0 2px',flexShrink:0}}/>;

  return (
    <div style={{height:56,flexShrink:0,display:'flex',alignItems:'center',gap:8,padding:'0 16px',
      borderBottom:'1px solid var(--border)',background:'#ffffff',
      position:'relative',zIndex:200,overflow:'visible'}}>

      {/* Logo + nombre */}
      <div style={{display:'flex',alignItems:'center',gap:9,flex:'0 0 auto',maxWidth:isMobile?160:300,minWidth:0}}>
        <button type="button" title="Volver al dashboard" onClick={onBack}
          style={{width:34,height:34,borderRadius:9,border:'1px solid var(--border)',background:'var(--card)',
            cursor:'pointer',display:'grid',placeItems:'center',flexShrink:0,transition:'background .12s'}}
          onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
          onMouseLeave={e=>e.currentTarget.style.background='var(--card)'}>
          <Icon name="arrowLeft" size={14} color="var(--muted)"/>
        </button>
        <div style={{minWidth:0}}>
          <input value={project?.name||''} onChange={e=>onNameChange(e.target.value)}
            style={{border:'none',background:'transparent',outline:'none',fontWeight:800,
              fontFamily:"'Cormorant Garamond',serif",fontSize:isMobile?15:19,color:'var(--text)',
              width:'100%',minWidth:60,maxWidth:isMobile?110:240,display:'block'}}/>
          {!isMobile&&(
            <div style={{fontSize:9.5,color:'var(--muted)',fontFamily:"'DM Sans',sans-serif",whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
              {layers.length} capas
            </div>
          )}
        </div>
      </div>

      {/* Center pill toolbar */}
      {!isMobile&&(
        <div style={{flex:1,display:'flex',alignItems:'center',justifyContent:'center',minWidth:0}}>
          <div style={{display:'flex',alignItems:'center',background:'#f5f2ef',borderRadius:11,padding:'3px',gap:1,flexWrap:'nowrap',overflow:'hidden'}}>
            {/* Undo/Redo */}
            <button type="button" title="Deshacer (⌘Z)" disabled={!history.length} onClick={onUndo}
              style={{height:30,padding:'0 9px',borderRadius:8,border:'none',background:'transparent',
                fontSize:11,fontWeight:700,color:history.length?'var(--text)':'var(--muted)',
                cursor:history.length?'pointer':'not-allowed',opacity:history.length?1:.4,display:'flex',alignItems:'center',gap:3}}>
              <Icon name="chevLeft" size={12} color="var(--muted)"/>
            </button>
            <button type="button" title="Rehacer (⌘⇧Z)" disabled={!future.length} onClick={onRedo}
              style={{height:30,padding:'0 9px',borderRadius:8,border:'none',background:'transparent',
                fontSize:11,fontWeight:700,color:future.length?'var(--text)':'var(--muted)',
                cursor:future.length?'pointer':'not-allowed',opacity:future.length?1:.4,display:'flex',alignItems:'center',gap:3}}>
              <Icon name="chevRight" size={12} color="var(--muted)"/>
            </button>

            {/* Align — solo si hay selección */}
            {(sel1||multi)&&<>{pillSep}
              {[
                {title:'Alinear izquierda',label:'⬡L',action:onAlignLeft},
                {title:'Centrar H',label:'⬡C',action:onAlignCenterH},
                {title:'Alinear derecha',label:'⬡R',action:onAlignRight},
                {title:'Alinear arriba',label:'⬡T',action:onAlignTop},
                {title:'Centrar V',label:'⬡M',action:onAlignCenterV},
                {title:'Alinear abajo',label:'⬡B',action:onAlignBottom},
              ].map(b=>(
                <button key={b.label} type="button" title={b.title} onClick={b.action}
                  style={{height:30,padding:'0 7px',borderRadius:8,border:'none',background:'transparent',
                    fontSize:11,fontWeight:700,color:'var(--muted)',cursor:'pointer',transition:'background .1s'}}
                  onMouseEnter={e=>e.currentTarget.style.background='rgba(255,255,255,.7)'}
                  onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                  {b.label}
                </button>
              ))}
              {pillSep}
              <button type="button" title="Subir orden [" onClick={onOrderUp}
                style={{height:30,padding:'0 7px',borderRadius:8,border:'none',background:'transparent',fontSize:11,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}>↑</button>
              <button type="button" title="Bajar orden ]" onClick={onOrderDown}
                style={{height:30,padding:'0 7px',borderRadius:8,border:'none',background:'transparent',fontSize:11,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}>↓</button>
              {multi&&<button type="button" title="Agrupar (⌘G)" onClick={onGroup}
                style={{height:30,padding:'0 8px',borderRadius:8,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}>Agrupar</button>}
              {sel1?.groupId&&<button type="button" title="Desagrupar (⌘⇧G)" onClick={onUngroup}
                style={{height:30,padding:'0 8px',borderRadius:8,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}>Desagrupar</button>}
              {pillSep}
              <button type="button" title="Duplicar (⌘D)" onClick={onDup}
                style={{height:30,padding:'0 8px',borderRadius:8,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}>⊕ Dup</button>
              <button type="button" title="Eliminar (Del)" onClick={onDelete}
                style={{height:30,padding:'0 8px',borderRadius:8,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'#dc2626',cursor:'pointer'}}>🗑</button>
            </>}

            {/* View controls */}
            {pillSep}
            <button type="button" title="Grid" onClick={onToggleGrid}
              style={{height:30,padding:'0 9px',borderRadius:8,border:'none',
                background:grid?'#fff':'transparent',
                color:grid?'var(--primary)':'var(--muted)',
                boxShadow:grid?'0 1px 4px rgba(0,0,0,.1)':'none',
                fontSize:10,fontWeight:700,cursor:'pointer',transition:'all .1s'}}>
              ⊞ Grid
            </button>
            <button type="button" title="Snap" onClick={onToggleSnap}
              style={{height:30,padding:'0 9px',borderRadius:8,border:'none',
                background:snap?'#fff':'transparent',
                color:snap?'var(--primary)':'var(--muted)',
                boxShadow:snap?'0 1px 4px rgba(0,0,0,.1)':'none',
                fontSize:10,fontWeight:700,cursor:'pointer',transition:'all .1s'}}>
              ⊹ Snap
            </button>
            {pillSep}
            {/* Zoom inline */}
            <button type="button" onClick={()=>onZoom(-0.1)}
              style={{height:30,padding:'0 7px',borderRadius:8,border:'none',background:'transparent',cursor:'pointer'}}>
              <Icon name="minus" size={11} color="var(--muted)"/>
            </button>
            <span style={{fontSize:11,fontWeight:800,color:'var(--muted)',minWidth:38,textAlign:'center',fontFamily:'monospace',userSelect:'none'}}>
              {Math.round(zoom*100)}%
            </span>
            <button type="button" onClick={()=>onZoom(+0.1)}
              style={{height:30,padding:'0 7px',borderRadius:8,border:'none',background:'transparent',cursor:'pointer'}}>
              <Icon name="plus" size={11} color="var(--muted)"/>
            </button>
            <button type="button" onClick={()=>onZoom('fit')}
              style={{height:30,padding:'0 8px',borderRadius:8,border:'none',background:'transparent',fontSize:9,fontWeight:800,color:'var(--muted)',cursor:'pointer'}}>
              FIT
            </button>
          </div>
        </div>
      )}

      {/* Right actions */}
      <div style={{display:'flex',gap:6,flexShrink:0,alignItems:'center'}}>
        {!isMobile&&(
          <button type="button" title="Ajustes del canvas" onClick={onSettings}
            style={{width:34,height:34,borderRadius:9,border:'1px solid var(--border)',background:'var(--card)',
              cursor:'pointer',display:'grid',placeItems:'center',transition:'background .12s'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
            onMouseLeave={e=>e.currentTarget.style.background='var(--card)'}>
            <Icon name="settings" size={14} color="var(--muted)"/>
          </button>
        )}
        {!isMobile&&hasLinkedDesign&&(
          <button type="button" onClick={onQuote}
            style={{height:34,padding:'0 14px',borderRadius:9,border:'1px solid var(--border)',
              background:'var(--card)',color:'var(--text)',fontSize:12,fontWeight:700,
              cursor:'pointer',fontFamily:"'DM Sans',sans-serif",transition:'background .12s'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
            onMouseLeave={e=>e.currentTarget.style.background='var(--card)'}>
            Cotizar
          </button>
        )}
        {!isMobile&&(
          <button type="button" onClick={onPublish}
            style={{height:34,padding:'0 14px',borderRadius:9,
              border:'1.5px solid var(--primary)',
              background:'var(--accent-light)',color:'var(--primary)',
              fontSize:12,fontWeight:700,cursor:'pointer',
              fontFamily:"'DM Sans',sans-serif",transition:'opacity .12s'}}
            onMouseEnter={e=>e.currentTarget.style.opacity='.8'}
            onMouseLeave={e=>e.currentTarget.style.opacity='1'}>
            {hasLinkedDesign?'Actualizar':'Publicar'}
          </button>
        )}
        {/* Export button — prominent */}
        <div style={{position:'relative'}}>
          <button type="button" onClick={()=>setExportOpen(o=>!o)}
            style={{height:34,padding:'0 16px',borderRadius:9,border:'none',
              background:'linear-gradient(135deg,var(--primary),var(--primary-dark,#9e6b7c))',
              color:'#fff',fontSize:12,fontWeight:700,cursor:'pointer',
              fontFamily:"'DM Sans',sans-serif",display:'flex',alignItems:'center',gap:5,
              boxShadow:'0 2px 8px rgba(196,137,154,.35)',transition:'opacity .12s'}}
            onMouseEnter={e=>e.currentTarget.style.opacity='.88'}
            onMouseLeave={e=>e.currentTarget.style.opacity='1'}>
            ↑ Exportar
          </button>
          {exportOpen&&(
            <div onClick={e=>e.stopPropagation()}
              style={{position:'absolute',right:0,top:'calc(100% + 8px)',
                background:'var(--card)',border:'1px solid var(--border)',
                borderRadius:14,minWidth:170,
                boxShadow:'0 16px 48px rgba(58,42,32,.18)',zIndex:9999,overflow:'hidden',padding:'4px 0'}}>
              {[['PNG','png'],['JPG','jpg'],['PDF','pdf'],['Archivo .canvas','canvas']].map(([lbl,id])=>(
                <button key={id} type="button"
                  onClick={()=>{setExportOpen(false);id==='canvas'?onExportCanvas():id==='pdf'?onExportPdf():onExportPng(id);}}
                  style={{display:'flex',alignItems:'center',gap:10,width:'100%',padding:'10px 16px',
                    border:'none',background:'none',cursor:'pointer',fontSize:13,fontWeight:600,
                    color:'var(--text)',fontFamily:"'DM Sans',sans-serif",textAlign:'left'}}
                  onMouseEnter={e=>e.currentTarget.style.background='var(--accent-light)'}
                  onMouseLeave={e=>e.currentTarget.style.background='none'}>
                  {lbl}
                </button>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Mobile Bottom Bar ─────────────────────────────────────────────
function MobileBar({onAssets,onShapes,onLayers,onInspector,onText,snap,onSnap,onUndo,onRedo,history,future}) {
  const items=[
    {l:'Assets',i:'sparkles',a:onAssets},{l:'Formas',i:'grid',a:onShapes},
    {l:'Capas',i:'fileText',a:onLayers},{l:'Inspector',i:'settings',a:onInspector},
    {l:'Texto',i:'edit',a:onText},{l:'Snap',i:'grid',a:onSnap,act:snap},
    {l:'Undo',i:'chevLeft',a:onUndo,dis:!history.length},
    {l:'Redo',i:'chevRight',a:onRedo,dis:!future.length},
  ];
  return (
    <div style={{position:'fixed',left:10,right:10,bottom:'calc(10px + env(safe-area-inset-bottom))',
      background:'rgba(255,252,249,0.97)',border:'1px solid var(--border)',borderRadius:999,
      padding:'6px 8px',display:'flex',justifyContent:'space-around',
      boxShadow:'0 8px 32px rgba(58,42,32,0.14)',backdropFilter:'blur(14px)',zIndex:800}}>
      {items.map(it=>(
        <button key={it.l} type="button" title={it.l} disabled={it.dis} onClick={it.dis?undefined:it.a}
          style={{display:'flex',flexDirection:'column',alignItems:'center',gap:2,border:'none',
            background:'none',cursor:it.dis?'not-allowed':'pointer',padding:'3px 6px',
            opacity:it.dis?.35:1,borderRadius:10}}>
          <Icon name={it.i} size={19} color={it.act?'var(--primary)':'var(--muted)'}/>
          <span style={{fontSize:8,fontWeight:800,color:it.act?'var(--primary)':'var(--muted)',textTransform:'uppercase'}}>{it.l}</span>
        </button>
      ))}
    </div>
  );
}

// ─── Publish Design Modal ─────────────────────────────────────────
function PublishDesignModal({proj,layers,onClose,onPublished,navigate}) {
  const DESIGN_CATS=['baby-shower','boda','quinceanera','cumpleanos','graduacion','propuesta','corporativo','cena-romantica','postre'];
  const [form,setForm]=React.useState({
    name:proj?.name||'',
    category:proj?.linkedDesign?.category||'baby-shower',
    description:'',
    price:0,
    status:'draft',
    ...( proj?.linkedDesign||{} ),
  });
  const [saving,setSaving]=React.useState(false);
  const [err,setErr]=React.useState('');
  const set=p=>setForm(f=>({...f,...p}));

  // derive materials from layers that have inventoryAssetId
  const materials=layers.filter(l=>l.inventoryAssetId).map(l=>({
    assetId:l.inventoryAssetId,quantity:l.inventoryQty||1,
    role:l.name,variant:'',notes:'',required:true,sortOrder:l.zIndex||0,
  }));
  const scene_assets=layers.map((l,i)=>({
    assetId:l.inventoryAssetId||null,
    x:l.x,y:l.y,scale:1,z:l.zIndex||i,delay_ms:i*80,
  }));

  const save=async()=>{
    if(!form.name.trim()){setErr('El nombre es obligatorio.');return;}
    setSaving(true);setErr('');
    try {
      // capture preview
      let previewUri=null;
      if(window._kStage){
        const s=window._kStage;
        const pr=Math.min(1,(1000/Math.max(s.width()/s.scaleX(),1)));
        previewUri=s.toDataURL({pixelRatio:pr,mimeType:'image/png'});
      }
      const payload={
        ...form,
        image:previewUri||form.image||'',
        canvasPreview:previewUri,
        canvasData:{layers,canvas:proj?.canvas},
        materials,
        scene_assets,
        tags:[form.category],
        includes:[],
        options:proj?.linkedDesign?.options||[],
        extras:proj?.linkedDesign?.extras||[],
      };
      let result;
      if(proj?.linkedDesign?.id){
        result=await window.LelusaStore.updateDesign(proj.linkedDesign.id,payload);
      } else {
        result=await window.LelusaStore.addDesign(payload);
      }
      onPublished(result||{...payload,id:Date.now()});
    } catch(e){setErr(String(e?.message||e||'Error al guardar'));}
    setSaving(false);
  };

  return (
    <div style={{position:'fixed',inset:0,zIndex:9999,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(58,42,32,0.5)',backdropFilter:'blur(10px)'}}>
      <div style={{background:'var(--card)',borderRadius:22,width:440,maxWidth:'96vw',maxHeight:'92vh',overflow:'hidden',display:'flex',flexDirection:'column',boxShadow:'0 32px 100px rgba(0,0,0,0.25)'}}>
        {/* header */}
        <div style={{padding:'18px 22px',borderBottom:'1px solid var(--border)',display:'flex',alignItems:'center',justifyContent:'space-between',flexShrink:0}}>
          <div>
            <div style={{fontSize:16,fontWeight:800,color:'var(--text)',fontFamily:"'DM Sans',sans-serif"}}>Publicar como Diseño</div>
            <div style={{fontSize:11,color:'var(--muted)',marginTop:2}}>{materials.length} materiales vinculados · {layers.length} capas</div>
          </div>
          <button type="button" onClick={onClose} style={{border:'none',background:'none',cursor:'pointer',padding:4,borderRadius:8}}><Icon name="minus" size={18} color="var(--muted)"/></button>
        </div>
        {/* body */}
        <div style={{flex:1,overflowY:'auto',padding:22,display:'grid',gap:14}}>
          <IField label="Nombre del diseño">
            <input value={form.name} onChange={e=>set({name:e.target.value})} style={{...IS,fontWeight:700}} placeholder="Ej: Arco Rosa Champagne"/>
          </IField>
          <Grid2>
            <IField label="Categoría">
              <select value={form.category} onChange={e=>set({category:e.target.value})} style={{...IS}}>
                {DESIGN_CATS.map(c=><option key={c} value={c}>{c}</option>)}
              </select>
            </IField>
            <IField label="Precio base (MXN)">
              <NumIn value={form.price} min={0} onChange={v=>set({price:v})} unit="$"/>
            </IField>
          </Grid2>
          <IField label="Descripción">
            <textarea value={form.description} onChange={e=>set({description:e.target.value})} rows={3}
              placeholder="Describe el diseño para el catálogo público..." style={{...IS,resize:'vertical',lineHeight:1.45}}/>
          </IField>
          <IField label="Estado">
            <ToggleGroup value={form.status} onChange={v=>set({status:v})}
              options={[{v:'draft',l:'Borrador'},{v:'published',l:'Publicado'},{v:'archived',l:'Archivado'}]}/>
          </IField>
          {materials.length>0&&(
            <div style={{background:'var(--accent-light)',borderRadius:12,padding:'10px 14px'}}>
              <div style={{fontSize:11,fontWeight:800,color:'var(--primary)',marginBottom:6,textTransform:'uppercase',letterSpacing:'0.06em'}}>Materiales del inventario ({materials.length})</div>
              {materials.map((m,i)=>(
                <div key={i} style={{fontSize:12,color:'var(--text)',padding:'3px 0',borderTop:i?'1px solid rgba(196,137,154,0.2)':'none'}}>
                  {m.role} · qty {m.quantity}
                </div>
              ))}
            </div>
          )}
          {err&&<div style={{background:'#FEF2F2',border:'1px solid #FECACA',borderRadius:10,padding:'10px 14px',fontSize:12,color:'#DC2626'}}>{err}</div>}
        </div>
        {/* footer */}
        <div style={{padding:'12px 22px',borderTop:'1px solid var(--border)',display:'flex',gap:8,justifyContent:'flex-end',flexShrink:0}}>
          <TBtn label="Cancelar" onClick={onClose}/>
          <TBtn label="Ver catálogo" onClick={()=>{onClose();navigate('admin-designs');}} sx={{}}/>
          <Btn disabled={saving} onClick={save} style={{minWidth:120,justifyContent:'center'}}>
            {saving?'Guardando…':(proj?.linkedDesign?.id?'Actualizar diseño':'Publicar diseño')}
          </Btn>
        </div>
      </div>
    </div>
  );
}

// ─── Quote From Canvas Modal ──────────────────────────────────────
function QuoteFromCanvasModal({proj,layers,designId,onClose,navigate}) {
  const [form,setForm]=React.useState({
    clientName:'',phone:'',email:'',eventDate:'',eventPlace:'',eventType:proj?.linkedDesign?.category||'otro',notes:'',guests:50,
  });
  const [saving,setSaving]=React.useState(false);
  const [err,setErr]=React.useState('');
  const set=p=>setForm(f=>({...f,...p}));

  const save=async()=>{
    if(!form.clientName.trim()||!form.eventDate){setErr('Nombre y fecha son obligatorios.');return;}
    setSaving(true);setErr('');
    try {
      // addQuote generates the COT-YYYY-NNN id internally
      await window.LelusaStore.addQuote({
        clientName:form.clientName,
        phone:form.phone,
        email:form.email,
        eventDate:form.eventDate,
        eventPlace:form.eventPlace,
        eventType:form.eventType,
        guests:Number(form.guests)||0,
        notes:form.notes,
        designId:designId||null,
        designName:proj?.linkedDesign?.name||proj?.name||'Canvas diseño',
        basePrice:proj?.linkedDesign?.price||0,
        extrasPrice:0,
        total:proj?.linkedDesign?.price||0,
        selectedOptions:{},
        selectedExtras:[],
        status:'pendiente',
        createdAt:new Date().toISOString().slice(0,10),
        language:'es',
        contactPreference:'WhatsApp',
        decorationDetails:{theme:proj?.name,colors:'',focus:'',mustHave:'',budgetRange:'',customBrief:''},
      });
      onClose();
      navigate('admin-quotes');
    } catch(e){setErr(String(e?.message||e||'Error al crear cotización'));}
    setSaving(false);
  };

  return (
    <div style={{position:'fixed',inset:0,zIndex:9999,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(58,42,32,0.5)',backdropFilter:'blur(10px)'}}>
      <div style={{background:'var(--card)',borderRadius:22,width:420,maxWidth:'96vw',maxHeight:'90vh',overflow:'hidden',display:'flex',flexDirection:'column',boxShadow:'0 32px 100px rgba(0,0,0,0.25)'}}>
        <div style={{padding:'18px 22px',borderBottom:'1px solid var(--border)',display:'flex',alignItems:'center',justifyContent:'space-between',flexShrink:0}}>
          <div style={{fontSize:16,fontWeight:800,color:'var(--text)',fontFamily:"'DM Sans',sans-serif"}}>Nueva Cotización</div>
          <button type="button" onClick={onClose} style={{border:'none',background:'none',cursor:'pointer',padding:4}}><Icon name="minus" size={18} color="var(--muted)"/></button>
        </div>
        <div style={{flex:1,overflowY:'auto',padding:22,display:'grid',gap:12}}>
          <IField label="Nombre del cliente">
            <input value={form.clientName} onChange={e=>set({clientName:e.target.value})} style={{...IS,fontWeight:700}} placeholder="Ej: María García"/>
          </IField>
          <Grid2>
            <IField label="Teléfono"><input value={form.phone} onChange={e=>set({phone:e.target.value})} style={IS} placeholder="+52 55 0000 0000"/></IField>
            <IField label="Email"><input value={form.email} type="email" onChange={e=>set({email:e.target.value})} style={IS} placeholder="cliente@email.com"/></IField>
          </Grid2>
          <Grid2>
            <IField label="Fecha del evento"><input value={form.eventDate} type="date" onChange={e=>set({eventDate:e.target.value})} style={IS}/></IField>
            <IField label="Invitados"><NumIn value={form.guests} min={1} onChange={v=>set({guests:v})}/></IField>
          </Grid2>
          <IField label="Lugar del evento">
            <input value={form.eventPlace} onChange={e=>set({eventPlace:e.target.value})} style={IS} placeholder="Salón, ciudad..."/>
          </IField>
          <IField label="Tipo de evento">
            <select value={form.eventType} onChange={e=>set({eventType:e.target.value})} style={IS}>
              {['baby-shower','boda','quinceanera','cumpleanos','graduacion','corporativo','otro'].map(c=><option key={c} value={c}>{c}</option>)}
            </select>
          </IField>
          <IField label="Notas">
            <textarea value={form.notes} onChange={e=>set({notes:e.target.value})} rows={3} style={{...IS,resize:'vertical',lineHeight:1.45}} placeholder="Detalles, colores preferidos, solicitudes especiales..."/>
          </IField>
          {proj?.linkedDesign?.price>0&&(
            <div style={{background:'var(--accent-light)',borderRadius:10,padding:'10px 14px',display:'flex',justifyContent:'space-between',alignItems:'center'}}>
              <span style={{fontSize:12,fontWeight:700,color:'var(--text)'}}>Precio base diseño</span>
              <span style={{fontSize:16,fontWeight:800,color:'var(--primary)',fontFamily:"'Cormorant Garamond',serif"}}>${(proj.linkedDesign.price||0).toLocaleString()} MXN</span>
            </div>
          )}
          {err&&<div style={{background:'#FEF2F2',border:'1px solid #FECACA',borderRadius:10,padding:'10px 14px',fontSize:12,color:'#DC2626'}}>{err}</div>}
        </div>
        <div style={{padding:'12px 22px',borderTop:'1px solid var(--border)',display:'flex',gap:8,justifyContent:'flex-end',flexShrink:0}}>
          <TBtn label="Cancelar" onClick={onClose}/>
          <Btn disabled={saving} onClick={save} style={{minWidth:120,justifyContent:'center'}}>
            {saving?'Creando…':'Crear cotización'}
          </Btn>
        </div>
      </div>
    </div>
  );
}

// ─── Asset Linker in Inspector ────────────────────────────────────
function InspInventoryLink({layer,up,inventoryAssets}) {
  const [search,setSearch]=React.useState('');
  const linked=layer.inventoryAssetId?inventoryAssets.find(a=>String(a.id)===String(layer.inventoryAssetId)):null;
  const filtered=search
    ?(inventoryAssets||[]).filter(a=>(a.name||'').toLowerCase().includes(search.toLowerCase())||(a.category||'').toLowerCase().includes(search.toLowerCase()))
    :(inventoryAssets||[]).slice(0,12);

  return (
    <InspSection title="Inventario Lelusa" defaultOpen={false}>
      {linked?(
        <div style={{background:'var(--accent-light)',borderRadius:10,padding:'10px 12px'}}>
          <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:8}}>
            <div style={{display:'flex',alignItems:'center',gap:8}}>
              {linked.image&&<img src={linked.image} alt="" style={{width:32,height:32,borderRadius:6,objectFit:'cover',border:'1px solid var(--border)'}}/>}
              <div>
                <div style={{fontSize:12,fontWeight:800,color:'var(--primary)'}}>{linked.name}</div>
                <div style={{fontSize:10,color:'var(--muted)'}}>{linked.category} · Stock: {linked.quantity}</div>
              </div>
            </div>
            <TBtn label="Desvincular" onClick={()=>up({inventoryAssetId:null,inventoryQty:null})} sx={{fontSize:10}}/>
          </div>
          <div style={{marginTop:10}}>
            <RangeRow label="Cantidad usada" value={layer.inventoryQty||1} min={1} max={linked.quantity||50} step={1}
              fmt={v=>`${v} uds`} onChange={v=>up({inventoryQty:v})}/>
          </div>
        </div>
      ):(
        <div style={{display:'grid',gap:8}}>
          <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="Buscar en inventario..."
            style={{...IS,fontSize:12,padding:'6px 10px'}}/>
          {filtered.length===0&&<div style={{fontSize:11,color:'var(--muted)',textAlign:'center',padding:8}}>Sin resultados</div>}
          {filtered.map(a=>(
            <button key={a.id} type="button" onClick={()=>up({inventoryAssetId:a.id,inventoryQty:1,name:layer.name===mkLayer().name?a.name:layer.name})}
              style={{display:'flex',alignItems:'center',gap:8,padding:'7px 10px',border:'1px solid var(--border)',borderRadius:9,cursor:'pointer',background:'var(--card)',textAlign:'left',width:'100%'}}>
              {a.image&&<img src={a.image} alt="" style={{width:28,height:28,borderRadius:5,objectFit:'cover',flexShrink:0}}/>}
              {!a.image&&<div style={{width:28,height:28,borderRadius:5,background:a.color||'var(--accent-light)',flexShrink:0,border:'1px solid var(--border)'}}/>}
              <div style={{minWidth:0}}>
                <div style={{fontSize:11,fontWeight:700,color:'var(--text)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{a.name}</div>
                <div style={{fontSize:9.5,color:'var(--muted)'}}>Stock: {a.quantity} · ${a.basePrice||0}</div>
              </div>
            </button>
          ))}
        </div>
      )}
    </InspSection>
  );
}

// ─── Main Page ────────────────────────────────────────────────────
function AdminCanvasPage() {
  const {navigate}=React.useContext(window.AppContext);
  const isMobile=window.LelusaGestures.useIsMobile();
  React.useEffect(()=>{ window.LelusaNav=navigate; return()=>{delete window.LelusaNav;}; },[navigate]);
  const fileRef=React.useRef(null);
  const importRef=React.useRef(null);
  const clipboard=React.useRef(null);
  const remoteSyncReady=React.useRef(false);
  const requestedProjectId=React.useMemo(()=>getCanvasProjectIdParam(),[]);

  // ── Lelusa Store data ──
  const storeData=window.LelusaStore.useBusinessData();
  const inventoryAssets=storeData?.assets||[];
  const storeDesigns=storeData?.designs||[];

  const [projects,setProjects]=React.useState(()=>{
    const saved=loadProjects();
    return saved.length?saved:[mkTemplate(C_TEMPLATES[0])];
  });
  const [activeId,setActiveId]=React.useState(null);
  React.useEffect(()=>{if(!activeId&&projects.length)setActiveId(projects[0].id);},[projects,activeId]);

  const [selectedIds,setSelectedIds]=React.useState([]);
  const [zoom,setZoom]=React.useState(isMobile?.15:.22);
  const [pos,setPos]=React.useState({x:20,y:20});
  const [snap,setSnap]=React.useState(true);
  const [grid,setGrid]=React.useState(false);
  const [guides,setGuides]=React.useState([]);
  const [history,setHistory]=React.useState([]);
  const [future,setFuture]=React.useState([]);
  const [ctxMenu,setCtxMenu]=React.useState(null);
  const [settingsOpen,setSettingsOpen]=React.useState(false);
  const [publishOpen,setPublishOpen]=React.useState(false);
  const [quoteOpen,setQuoteOpen]=React.useState(false);
  const [leftTab,setLeftTab]=React.useState('assets');
  const [mobileSheet,setMobileSheet]=React.useState(null);
  const [statusMsg,setStatusMsg]=React.useState('');
  const [canvasAssets,setCanvasAssets]=React.useState([]);
  const [canvasAssetsLoading,setCanvasAssetsLoading]=React.useState(false);
  const [cropLayerId, setCropLayerId] = React.useState(null);

  const loadCanvasAssets=React.useCallback(async()=>{
    if(!window.LELUSA_SUPABASE?.url) return;
    setCanvasAssetsLoading(true);
    try { setCanvasAssets(await fetchCanvasAssets()); } catch{}
    setCanvasAssetsLoading(false);
  },[]);

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

  const handleUploadCanvasAsset=React.useCallback(async(file)=>{
    try {
      const asset=await uploadCanvasAsset(file);
      await loadCanvasAssets();
      addLayer({ name:asset.name, type:'image', src:asset.url, width:asset.width||760, height:asset.height||520 });
    } catch(err) {
      setStatusMsg('Error al subir imagen: '+err.message);
      setTimeout(()=>setStatusMsg(''),4000);
    }
  },[loadCanvasAssets,addLayer]);

  const handleDeleteCanvasAsset=React.useCallback(async(asset)=>{
    try {
      await deleteCanvasAsset(asset);
      await loadCanvasAssets();
    } catch(err) {
      setStatusMsg('Error al eliminar imagen: '+err.message);
      setTimeout(()=>setStatusMsg(''),4000);
    }
  },[loadCanvasAssets]);

  React.useEffect(()=>{
    let cancelled=false;
    fetch('/api/canvas/projects')
      .then(r=>r.ok?r.json():null)
      .then(data=>{
        if(cancelled) return;
        const remote=Array.isArray(data?.projects)?data.projects:[];
        if(remote.length){
          setProjects(cur=>mergeCanvasProjects(cur,remote));
          setActiveId(id=>{
            if(requestedProjectId&&remote.some(p=>p.id===requestedProjectId)) return requestedProjectId;
            if(id&&remote.some(p=>p.id===id)) return id;
            return remote[0].id;
          });
        }
        remoteSyncReady.current=true;
      })
      .catch(()=>{remoteSyncReady.current=true;});
    return()=>{cancelled=true;};
  },[]);

  React.useEffect(()=>{
    saveProjects(projects);
    if(!remoteSyncReady.current) return;
    const timer=setTimeout(()=>{
      fetch('/api/canvas/projects',{
        method:'PUT',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify({projects}),
      }).catch(()=>{});
    },350);
    return()=>clearTimeout(timer);
  },[projects]);

  const proj=projects.find(p=>p.id===activeId)||projects[0]||mkProject();
  const layers=sortedLayers(proj?.layers||[]);
  const canvas=proj?.canvas||{width:3000,height:2000,bg:'#FAF0E8'};

  // commit helper
  const commit=React.useCallback((updater)=>{
    setProjects(cur=>{
      const before=cur.find(p=>p.id===proj?.id);
      if(before){setHistory(h=>[...h.slice(-49),JSON.stringify(before)]);setFuture([]);}
      return cur.map(p=>{
        if(p.id!==proj?.id) return p;
        const next=typeof updater==='function'?updater(p):updater;
        return {...next,updatedAt:new Date().toISOString()};
      });
    });
  },[proj?.id]);

  const updLayers=React.useCallback((fn)=>commit(p=>({...p,layers:fn(p.layers||[])})),[commit]);
  const updLayer=React.useCallback((id,patch)=>updLayers(ls=>ls.map(l=>l.id===id?{...l,...patch}:l)),[updLayers]);
  const addLayer=React.useCallback((patch)=>{
    const z=(layers.reduce((m,l)=>Math.max(m,l.zIndex||0),0))+1;
    const l=mkLayer({zIndex:z,...patch});
    updLayers(ls=>[...ls,l]); setSelectedIds([l.id]); return l.id;
  },[layers,updLayers]);
  const delLayer=React.useCallback((id)=>{updLayers(ls=>ls.filter(l=>l.id!==id));},[updLayers]);

  const undo=React.useCallback(()=>{
    if(!history.length) return;
    const prev=JSON.parse(history[history.length-1]);
    setFuture(f=>[JSON.stringify(proj),...f.slice(0,49)]);
    setHistory(h=>h.slice(0,-1));
    setProjects(ps=>ps.map(p=>p.id===proj?.id?prev:p));
    setSelectedIds([]);
  },[history,proj]);
  const redo=React.useCallback(()=>{
    if(!future.length) return;
    const next=JSON.parse(future[0]);
    setHistory(h=>[...h.slice(-49),JSON.stringify(proj)]);
    setFuture(f=>f.slice(1));
    setProjects(ps=>ps.map(p=>p.id===proj?.id?next:p));
    setSelectedIds([]);
  },[future,proj]);

  const dupSelected=React.useCallback(()=>{
    selectedIds.forEach(id=>{const l=layers.find(x=>x.id===id);if(l)addLayer({...l,id:undefined,name:l.name+' copia',x:l.x+50,y:l.y+50});});
  },[selectedIds,layers,addLayer]);
  const delSelected=React.useCallback(()=>{
    selectedIds.forEach(id=>{const l=layers.find(x=>x.id===id);if(l&&!l.locked)delLayer(id);});
    setSelectedIds([]);
  },[selectedIds,layers,delLayer]);

  const align=React.useCallback((mode)=>{
    if(!selectedIds.length) return;
    const sel=layers.filter(l=>selectedIds.includes(l.id));
    const bounds={minX:Math.min(...sel.map(l=>l.x)),maxX:Math.max(...sel.map(l=>l.x+l.width)),
      minY:Math.min(...sel.map(l=>l.y)),maxY:Math.max(...sel.map(l=>l.y+l.height))};
    const patches={};
    sel.forEach(l=>{
      if(mode==='left') patches[l.id]={x:bounds.minX};
      else if(mode==='right') patches[l.id]={x:bounds.maxX-l.width};
      else if(mode==='centerH') patches[l.id]={x:(bounds.minX+bounds.maxX)/2-l.width/2};
      else if(mode==='top') patches[l.id]={y:bounds.minY};
      else if(mode==='bottom') patches[l.id]={y:bounds.maxY-l.height};
      else if(mode==='centerV') patches[l.id]={y:(bounds.minY+bounds.maxY)/2-l.height/2};
    });
    updLayers(ls=>ls.map(l=>patches[l.id]?{...l,...patches[l.id]}:l));
  },[selectedIds,layers,updLayers]);

  const reorder=React.useCallback((dir)=>{
    if(selectedIds.length!==1) return;
    const sorted2=sortedLayers(layers);
    const idx=sorted2.findIndex(l=>l.id===selectedIds[0]);
    const si=dir==='up'?idx+1:idx-1;
    if(si<0||si>=sorted2.length) return;
    const next=[...sorted2];[next[idx],next[si]]=[next[si],next[idx]];
    updLayers(()=>next.map((l,i)=>({...l,zIndex:i+1})));
  },[selectedIds,layers,updLayers]);

  const group=React.useCallback(()=>{
    if(selectedIds.length<2) return;
    const gid=`g_${Date.now()}`;
    updLayers(ls=>ls.map(l=>selectedIds.includes(l.id)?{...l,groupId:gid}:l));
  },[selectedIds,updLayers]);
  const ungroup=React.useCallback(()=>{
    if(!selectedIds.length) return;
    const l=layers.find(x=>x.id===selectedIds[0]); if(!l?.groupId) return;
    const gid=l.groupId;
    updLayers(ls=>ls.map(l2=>l2.groupId===gid?{...l2,groupId:''}:l2));
  },[selectedIds,layers,updLayers]);

  const addAsset=React.useCallback((a)=>{
    // a puede ser asset del inventario (tiene a.id numérico) o asset de la librería
    const isInv=typeof a.id==='number'||/^\d+$/.test(String(a.id));
    addLayer({
      name:a.name,
      type:(a.image||a.src)?'image':'shape',
      shape:a.shape||'rect',
      color:a.color,stroke:a.stroke,
      src:a.image||a.src||null,
      width:620,height:520,
      ...(isInv?{inventoryAssetId:a.id,inventoryQty:1}:{}),
    });
    setMobileSheet(null);
  },[addLayer]);
  const addShape=React.useCallback((id)=>{
    addLayer({name:C_SHAPES.find(s=>s.id===id)?.n||id,type:'shape',shape:id,width:480,height:480});
    setMobileSheet(null);
  },[addLayer]);
  const addText=React.useCallback(()=>{
    addLayer({name:'Texto',type:'text',text:'Escribe aquí',color:'#9E6B7C',fontSize:100,width:800,height:180,fontFamily:'Cormorant Garamond',fontWeight:'bold'});
    setMobileSheet(null);
  },[addLayer]);

  const addGuide=React.useCallback((type,ctx2)=>{
    if(type==='v') setGuides(g=>[...g,{type:'v',x:ctx2?.x?Math.round(canvas.width/2):Math.round(canvas.width/2)}]);
    else setGuides(g=>[...g,{type:'h',y:Math.round(canvas.height/2)}]);
  },[canvas]);
  const removeGuide=React.useCallback((i,updated)=>{
    if(updated) setGuides(g=>g.map((gg,gi)=>gi===i?updated:gg));
    else setGuides(g=>g.filter((_,gi)=>gi!==i));
  },[]);

  const handleZoom=React.useCallback((delta)=>{
    if(delta==='fit'){
      const s=window._kStage; if(!s) return;
      const scaleX=s.width()/canvas.width, scaleY=s.height()/canvas.height;
      const newZ=Math.min(scaleX,scaleY)*0.9;
      const nx=(s.width()-canvas.width*newZ)/2, ny=(s.height()-canvas.height*newZ)/2;
      setZoom(newZ); setPos({x:nx,y:ny});
    } else {
      setZoom(v=>Math.min(6,Math.max(0.04,Math.round((v+delta)*100)/100)));
    }
  },[canvas]);

  const exportImg=React.useCallback(async(type='png')=>{
    const s=window._kStage; if(!s) return;
    setSelectedIds([]);
    await new Promise(r=>setTimeout(r,100));
    const pr=canvas.width/s.width()*s.scaleX();
    // eslint-disable-next-line no-undef
    const uri=s.toDataURL({pixelRatio:pr,mimeType:type==='jpg'?'image/jpeg':'image/png'});
    const a=document.createElement('a');a.href=uri;a.download=`${proj?.name||'canvas'}.${type}`;a.click();
    return uri;
  },[canvas,proj?.name]);

  const exportPdf=React.useCallback(async()=>{
    const uri=await exportImg('png'); if(!uri) return;
    const w=window.open('','_blank'); if(!w) return;
    w.document.write(`<html><head><title>${proj?.name}</title><style>body{margin:0}img{width:100%;display:block}</style></head><body><img src="${uri}" onload="window.print()"/></body></html>`);
    w.document.close();
  },[exportImg,proj?.name]);

  const exportCanvas=React.useCallback(()=>{
    const blob=new Blob([JSON.stringify({name:proj?.name,canvas,layers},null,2)],{type:'application/json'});
    const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`${proj?.name||'design'}.canvas`;a.click();
  },[proj?.name,canvas,layers]);

  // keyboard shortcuts
  React.useEffect(()=>{
    const kd=e=>{
      const tag=e.target?.tagName;
      if(tag==='INPUT'||tag==='TEXTAREA'||tag==='SELECT') return;
      const ctrl=e.ctrlKey||e.metaKey;
      if(ctrl&&!e.shiftKey&&e.key.toLowerCase()==='z'){e.preventDefault();undo();}
      if(ctrl&&(e.shiftKey&&e.key.toLowerCase()==='z'||e.key.toLowerCase()==='y')){e.preventDefault();redo();}
      if(ctrl&&e.key.toLowerCase()==='d'){e.preventDefault();dupSelected();}
      if(ctrl&&e.key.toLowerCase()==='a'){e.preventDefault();setSelectedIds(layers.map(l=>l.id));}
      if(ctrl&&e.key.toLowerCase()==='g'&&!e.shiftKey){e.preventDefault();group();}
      if(ctrl&&e.key.toLowerCase()==='g'&&e.shiftKey){e.preventDefault();ungroup();}
      if(ctrl&&e.key.toLowerCase()==='c'&&selectedIds.length===1) clipboard.current=layers.find(l=>l.id===selectedIds[0]);
      if(ctrl&&e.key.toLowerCase()==='v'&&clipboard.current) addLayer({...clipboard.current,id:undefined,name:clipboard.current.name+' copia',x:clipboard.current.x+40,y:clipboard.current.y+40});
      if(e.key==='Delete'||e.key==='Backspace'){e.preventDefault();delSelected();}
      if(e.key==='Escape'){setSelectedIds([]);setCtxMenu(null);}
      if(e.key===']') reorder('up');
      if(e.key==='[') reorder('down');
      if(e.key==='+') handleZoom(0.1);
      if(e.key==='-') handleZoom(-0.1);
      if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(e.key)){
        e.preventDefault();
        const step=e.shiftKey?10:1;
        selectedIds.forEach(id=>{
          const l=layers.find(x=>x.id===id); if(!l) return;
          updLayer(id,{x:l.x+(e.key==='ArrowLeft'?-step:e.key==='ArrowRight'?step:0),
            y:l.y+(e.key==='ArrowUp'?-step:e.key==='ArrowDown'?step:0)});
        });
      }
    };
    window.addEventListener('keydown',kd);
    return()=>window.removeEventListener('keydown',kd);
  },[undo,redo,dupSelected,group,ungroup,delSelected,reorder,selectedIds,layers,addLayer,updLayer,handleZoom]);

  return (
    <div style={{height:'100vh',display:'flex',flexDirection:'column',overflow:'hidden',fontFamily:"'DM Sans',sans-serif"}}
      onClick={()=>{setCtxMenu(null);}}
      onDrop={e=>{e.preventDefault();const files=e.dataTransfer.files;Array.from(files).filter(f=>f.type.startsWith('image/')).forEach(f=>{const r=new FileReader();r.onload=()=>addLayer({name:f.name.split('.')[0],type:'image',src:r.result,width:760,height:520});r.readAsDataURL(f);});}}
      onDragOver={e=>e.preventDefault()}>

      <Topbar
        project={proj} layers={layers} selectedIds={selectedIds} history={history} future={future}
        zoom={zoom} snap={snap} grid={grid} isMobile={isMobile}
        onBack={()=>navigate('admin-dashboard')}
        onNameChange={n=>commit(p=>({...p,name:n}))}
        onUndo={undo} onRedo={redo} onDup={dupSelected} onDelete={delSelected}
        onAlignLeft={()=>align('left')} onAlignCenterH={()=>align('centerH')} onAlignRight={()=>align('right')}
        onAlignTop={()=>align('top')} onAlignCenterV={()=>align('centerV')} onAlignBottom={()=>align('bottom')}
        onGroup={group} onUngroup={ungroup}
        onOrderUp={()=>reorder('up')} onOrderDown={()=>reorder('down')}
        onZoom={handleZoom} onToggleSnap={()=>setSnap(v=>!v)} onToggleGrid={()=>setGrid(v=>!v)}
        onSettings={()=>setSettingsOpen(true)}
        onExportPng={exportImg} onExportPdf={exportPdf} onExportCanvas={exportCanvas}
        onPublish={()=>setPublishOpen(true)} onQuote={()=>setQuoteOpen(true)}
        hasLinkedDesign={Boolean(proj?.linkedDesign?.id)}
      />

      <div style={{flex:1,minHeight:0,display:'flex',overflow:'hidden'}}>
        {!isMobile&&(
          <LeftPanel tab={leftTab} setTab={setLeftTab}
            onAddShape={addShape} onAddText={addText} onAddAsset={addAsset}
            onUpload={()=>fileRef.current?.click()} onTemplate={t=>{const np=mkTemplate(t);np.id=proj?.id;np.name=proj?.name;commit(np);setSelectedIds([]);}}
            projects={projects} activeId={activeId}
            onNewProject={()=>{const np=mkProject();setProjects(ps=>[np,...ps]);setActiveId(np.id);setSelectedIds([]);}}
            onSwitch={id=>{setActiveId(id);setSelectedIds([]);}}
            inventoryAssets={inventoryAssets}
            canvasAssets={canvasAssets}
            canvasAssetsLoading={canvasAssetsLoading}
            onUploadCanvasAsset={handleUploadCanvasAsset}
            onDeleteCanvasAsset={handleDeleteCanvasAsset}
          />
        )}

        <div style={{flex:1,position:'relative',display:'flex',overflow:'hidden'}}>
          <KStage
            cw={canvas.width} ch={canvas.height} cbg={canvas.bg}
            layers={layers} selectedIds={selectedIds}
            zoom={zoom} pos={pos} snap={snap} grid={grid} guides={guides}
            onSelect={setSelectedIds}
            onChange={updLayer}
            onZoom={setZoom} onPos={setPos}
            onContextMenu={ctx=>{
              setCtxMenu(ctx);
              if(ctx.layerId&&!selectedIds.includes(ctx.layerId)) setSelectedIds([ctx.layerId]);
            }}
          />

          {/* Toolbar flotante de contexto — aparece al seleccionar */}
          {!isMobile&&selectedIds.length>0&&(
            <div style={{position:'absolute',top:12,left:'50%',transform:'translateX(-50%)',
              background:'rgba(255,252,249,0.97)',border:'1px solid var(--border)',
              borderRadius:11,padding:'3px 5px',display:'flex',alignItems:'center',gap:2,
              boxShadow:'0 4px 20px rgba(58,42,32,.13)',backdropFilter:'blur(12px)',
              zIndex:500,pointerEvents:'all'}}>
              {selectedIds.length>1&&<>
                {[
                  {title:'Alinear izq.',label:'⬡L',action:()=>align('left')},
                  {title:'Centrar H',label:'⬡C',action:()=>align('centerH')},
                  {title:'Alinear der.',label:'⬡R',action:()=>align('right')},
                ].map(b=>(
                  <button key={b.label} type="button" title={b.title} onClick={b.action}
                    style={{height:26,padding:'0 6px',borderRadius:7,border:'none',background:'transparent',
                      fontSize:10,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}
                    onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
                    onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                    {b.label}
                  </button>
                ))}
                <div style={{width:1,height:16,background:'var(--border)',margin:'0 2px'}}/>
              </>}
              <button type="button" title="Subir orden [" onClick={()=>reorder('up')}
                style={{height:26,padding:'0 7px',borderRadius:7,border:'none',background:'transparent',fontSize:12,color:'var(--muted)',cursor:'pointer'}}
                onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>↑</button>
              <button type="button" title="Bajar orden ]" onClick={()=>reorder('down')}
                style={{height:26,padding:'0 7px',borderRadius:7,border:'none',background:'transparent',fontSize:12,color:'var(--muted)',cursor:'pointer'}}
                onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>↓</button>
              <div style={{width:1,height:16,background:'var(--border)',margin:'0 2px'}}/>
              <button type="button" title="Duplicar (⌘D)" onClick={dupSelected}
                style={{height:26,padding:'0 8px',borderRadius:7,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'var(--muted)',cursor:'pointer'}}
                onMouseEnter={e=>e.currentTarget.style.background='var(--bg)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>⊕ Dup</button>
              <button type="button" title="Eliminar (Del)" onClick={delSelected}
                style={{height:26,padding:'0 8px',borderRadius:7,border:'none',background:'transparent',fontSize:10,fontWeight:700,color:'#dc2626',cursor:'pointer'}}
                onMouseEnter={e=>e.currentTarget.style.background='#fef2f2'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>🗑</button>
            </div>
          )}

          {/* HUD de zoom — esquina inferior izquierda */}
          {!isMobile&&(
            <div style={{position:'absolute',bottom:12,left:12,
              background:'rgba(255,252,249,0.95)',border:'1px solid var(--border)',
              borderRadius:9,padding:'4px 8px',display:'flex',alignItems:'center',gap:4,
              boxShadow:'0 2px 8px rgba(0,0,0,.08)',zIndex:400}}>
              <button type="button" onClick={()=>handleZoom(-0.1)}
                style={{border:'none',background:'none',cursor:'pointer',padding:'0 2px',
                  lineHeight:1,display:'flex',alignItems:'center'}}>
                <Icon name="minus" size={12} color="var(--muted)"/>
              </button>
              <span style={{fontSize:11,fontWeight:800,color:'var(--muted)',minWidth:38,textAlign:'center',fontFamily:'monospace',userSelect:'none'}}>
                {Math.round(zoom*100)}%
              </span>
              <button type="button" onClick={()=>handleZoom(+0.1)}
                style={{border:'none',background:'none',cursor:'pointer',padding:'0 2px',
                  lineHeight:1,display:'flex',alignItems:'center'}}>
                <Icon name="plus" size={12} color="var(--muted)"/>
              </button>
              <div style={{width:1,height:14,background:'var(--border)',margin:'0 2px'}}/>
              <button type="button" onClick={()=>handleZoom('fit')}
                style={{border:'none',background:'none',cursor:'pointer',fontSize:9,fontWeight:800,color:'var(--muted)'}}>
                FIT
              </button>
            </div>
          )}
        </div>

        {!isMobile&&(
          <Inspector
            layers={layers} selectedIds={selectedIds}
            onSelect={setSelectedIds} onUpdate={updLayer} onDelete={delSelected}
            guides={guides} onAddGuide={addGuide} onRemoveGuide={removeGuide}
            inventoryAssets={inventoryAssets}
            onCrop={id=>setCropLayerId(id)}
          />
        )}
      </div>

      {/* Mobile bottom bar */}
      {isMobile&&(
        <MobileBar
          onAssets={()=>setMobileSheet('assets')} onShapes={()=>setMobileSheet('shapes')}
          onLayers={()=>setMobileSheet('layers')} onInspector={()=>setMobileSheet('inspector')}
          onText={addText} snap={snap} onSnap={()=>setSnap(v=>!v)}
          history={history} future={future} onUndo={undo} onRedo={redo}
        />
      )}

      {/* Mobile sheets */}
      {isMobile&&mobileSheet&&(
        <BottomSheet open title={
          {assets:'Assets',shapes:'Formas',layers:'Capas',inspector:'Inspector'}[mobileSheet]||''
        } onClose={()=>setMobileSheet(null)}>
          <div style={{maxHeight:'70vh',overflowY:'auto'}}>
            {mobileSheet==='assets'&&<LeftPanel tab="assets" setTab={()=>{}} onAddShape={addShape} onAddText={addText} onAddAsset={addAsset} onUpload={()=>fileRef.current?.click()} onTemplate={t=>{const np=mkTemplate(t);np.id=proj?.id;np.name=proj?.name;commit(np);setSelectedIds([]);setMobileSheet(null);}} projects={projects} activeId={activeId} onNewProject={()=>{const np=mkProject();setProjects(ps=>[np,...ps]);setActiveId(np.id);}} onSwitch={id=>{setActiveId(id);setMobileSheet(null);}} isMobile inventoryAssets={inventoryAssets} canvasAssets={canvasAssets} canvasAssetsLoading={canvasAssetsLoading} onUploadCanvasAsset={handleUploadCanvasAsset} onDeleteCanvasAsset={handleDeleteCanvasAsset}/>}
            {mobileSheet==='shapes'&&<LeftPanel tab="shapes" setTab={()=>{}} onAddShape={s=>{addShape(s);setMobileSheet(null);}} onAddText={()=>{addText();setMobileSheet(null);}} onAddAsset={a=>{addAsset(a);setMobileSheet(null);}} onUpload={()=>fileRef.current?.click()} onTemplate={()=>{}} projects={[]} activeId="" onNewProject={()=>{}} onSwitch={()=>{}} isMobile canvasAssets={canvasAssets} canvasAssetsLoading={canvasAssetsLoading} onUploadCanvasAsset={handleUploadCanvasAsset} onDeleteCanvasAsset={handleDeleteCanvasAsset}/>}
            {mobileSheet==='layers'&&<InspLayers layers={layers} selectedIds={selectedIds} onSelect={ids=>{setSelectedIds(ids);setMobileSheet('inspector');}} onUpdate={updLayer}/>}
            {mobileSheet==='inspector'&&<Inspector layers={layers} selectedIds={selectedIds} onSelect={setSelectedIds} onUpdate={updLayer} onDelete={delSelected} guides={guides} onAddGuide={addGuide} onRemoveGuide={removeGuide} isMobile inventoryAssets={inventoryAssets}/>}
          </div>
        </BottomSheet>
      )}

      {/* Context menu */}
      <CtxMenu ctx={ctxMenu} layers={layers} selectedIds={selectedIds}
        onDup={dupSelected} onDel={delSelected}
        onUp={()=>reorder('up')} onDown={()=>reorder('down')}
        onTop={()=>{if(selectedIds.length===1)updLayer(selectedIds[0],{zIndex:layers.length+1});}}
        onBottom={()=>{if(selectedIds.length===1)updLayer(selectedIds[0],{zIndex:0});}}
        onLock={()=>ctxMenu?.layerId&&(()=>{const l=layers.find(x=>x.id===ctxMenu.layerId);if(l)updLayer(l.id,{locked:!l.locked});})()}
        onHide={()=>ctxMenu?.layerId&&(()=>{const l=layers.find(x=>x.id===ctxMenu.layerId);if(l)updLayer(l.id,{visible:l.visible===false});})()}
        onGroup={group} onUngroup={ungroup}
        onAddGuide={addGuide}
        onClose={()=>setCtxMenu(null)}
      />

      {/* Settings modal */}
      {settingsOpen&&(
        <CanvasSettingsModal
          canvas={canvas}
          onUpdate={c=>commit(p=>({...p,canvas:{...p.canvas,...c}}))}
          onClose={()=>setSettingsOpen(false)}
        />
      )}

      {/* Publish design modal */}
      {publishOpen&&(
        <PublishDesignModal
          proj={proj} layers={layers}
          onClose={()=>setPublishOpen(false)}
          navigate={navigate}
          onPublished={result=>{
            commit(p=>({...p,linkedDesign:{...(p.linkedDesign||{}),...result,id:result.id||p.linkedDesign?.id}}));
            setPublishOpen(false);
            setStatusMsg(`✓ Diseño "${result.name||proj?.name}" guardado en catálogo`);
            setTimeout(()=>setStatusMsg(''),4000);
          }}
        />
      )}

      {/* Quote from canvas modal */}
      {quoteOpen&&(
        <QuoteFromCanvasModal
          proj={proj} layers={layers} designId={proj?.linkedDesign?.id}
          onClose={()=>setQuoteOpen(false)}
          navigate={navigate}
        />
      )}

      {/* Status toast */}
      {statusMsg&&(
        <div style={{position:'fixed',bottom:28,left:'50%',transform:'translateX(-50%)',
          background:'#1C1C1E',color:'#fff',borderRadius:999,padding:'10px 20px',
          fontSize:13,fontWeight:700,zIndex:99999,
          boxShadow:'0 8px 32px rgba(0,0,0,0.25)',
          fontFamily:"'DM Sans',sans-serif",whiteSpace:'nowrap',pointerEvents:'none'}}>
          {statusMsg}
        </div>
      )}

      {/* Mobile zoom FAB */}
      {isMobile&&(
        <div style={{position:'fixed',right:12,bottom:'calc(70px + env(safe-area-inset-bottom))',display:'flex',flexDirection:'column',gap:5,zIndex:820}}>
          <button type="button" onClick={()=>handleZoom(0.1)} style={{width:40,height:40,borderRadius:'50%',border:'1px solid var(--border)',background:'rgba(255,252,249,0.97)',cursor:'pointer',display:'grid',placeItems:'center',boxShadow:'0 4px 16px rgba(0,0,0,.1)'}}>
            <Icon name="plus" size={17} color="var(--primary)"/>
          </button>
          <div style={{width:40,height:28,borderRadius:10,background:'rgba(255,252,249,0.97)',border:'1px solid var(--border)',display:'grid',placeItems:'center',fontSize:9,fontWeight:800,color:'var(--muted)',fontFamily:'monospace'}}>{Math.round(zoom*100)}%</div>
          <button type="button" onClick={()=>handleZoom(-0.1)} style={{width:40,height:40,borderRadius:'50%',border:'1px solid var(--border)',background:'rgba(255,252,249,0.97)',cursor:'pointer',display:'grid',placeItems:'center',boxShadow:'0 4px 16px rgba(0,0,0,.1)'}}>
            <Icon name="minus" size={17} color="var(--primary)"/>
          </button>
        </div>
      )}

      <input ref={fileRef} type="file" accept="image/*" multiple hidden onChange={e=>{
        Array.from(e.target.files||[]).filter(f=>f.type.startsWith('image/')).forEach(f=>{
          const r=new FileReader();r.onload=()=>addLayer({name:f.name.split('.')[0],type:'image',src:r.result,width:760,height:520});r.readAsDataURL(f);
        });
      }}/>
      <input ref={importRef} type="file" accept=".canvas,.json" hidden onChange={e=>{
        const f=e.target.files?.[0]; if(!f) return;
        const r=new FileReader(); r.onload=()=>{
          try{const d=JSON.parse(r.result);
            const np={...mkProject(d.name||f.name),canvas:{...canvas,...d.canvas},layers:(d.layers||[]).map(l=>mkLayer(l))};
            setProjects(ps=>[np,...ps]); setActiveId(np.id); setSelectedIds([]);
          }catch{}
        }; r.readAsText(f);
      }}/>
      {cropLayerId&&(()=>{
        const cropLayer=layers.find(l=>l.id===cropLayerId);
        if(!cropLayer) return null;
        return (
          <CropOverlay
            layer={cropLayer}
            onApply={(dataUrl,w,h)=>{
              updLayer(cropLayerId,{src:dataUrl,width:w,height:h});
              setCropLayerId(null);
            }}
            onCancel={()=>setCropLayerId(null)}
          />
        );
      })()}
    </div>
  );
}

Object.assign(window,{AdminCanvasPage});
