/* App — Cozy Vesak Lantern Builder
   Dark Vesak-night theme. Buddhist flag colors (blue/yellow/red/white/orange) as accents.
*/

const { useState, useRef, useEffect, useMemo, useCallback } = React;

// Helper to downscale image for the localStorage gallery thumbnail (keeps it under 5MB quota)
function shrinkImage(dataUrl, maxDim = 200) {
  return new Promise((resolve) => {
    if (!dataUrl) return resolve(null);
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      let w = img.width;
      let h = img.height;
      if (w > h) {
        if (w > maxDim) { h = Math.round(h * maxDim / w); w = maxDim; }
      } else {
        if (h > maxDim) { w = Math.round(w * maxDim / h); h = maxDim; }
      }
      canvas.width = w;
      canvas.height = h;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, w, h);
      resolve(canvas.toDataURL('image/jpeg', 0.85)); // JPEG is much smaller than PNG for thumbs
    };
    img.onerror = () => resolve(null);
    img.src = dataUrl;
  });
}

const api = {
  async save(data) {
    await new Promise(r => setTimeout(r, 500));
    const id = 'lantern_' + Date.now().toString(36);
    
    // Create a shrunk version of the image to prevent QuotaExceededError
    let thumbnail = null;
    if (data.image) {
      try {
        thumbnail = await shrinkImage(data.image, 200);
      } catch (e) {
        console.error("Failed to shrink thumbnail image", e);
      }
    }

    const entry = { 
      id, 
      ...data, 
      image: thumbnail, 
      savedAt: new Date().toISOString() 
    };

    let all = [];
    try {
      all = JSON.parse(localStorage.getItem('vesak_lanterns') || '[]');
    } catch (e) {
      console.warn("Failed to read from localStorage", e);
    }

    all.push(entry);
    
    try {
      localStorage.setItem('vesak_lanterns', JSON.stringify(all));
    } catch (e) {
      console.warn("localStorage quota exceeded, saving entry without thumbnail", e);
      // Fallback: try saving without the thumbnail at all
      try {
        entry.image = null;
        all[all.length - 1] = entry;
        localStorage.setItem('vesak_lanterns', JSON.stringify(all));
      } catch (err) {
        console.error("Failed to save even without thumbnail", err);
      }
    }
    return entry;
  },
};

// ============ Synthesized sounds ============
let audioCtx = null;
function ensureAudio() {
  if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  return audioCtx;
}
function playTock(freq = 320, dur = 0.18, type = 'sine') {
  try {
    const ctx = ensureAudio();
    const osc = ctx.createOscillator();
    const g = ctx.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    osc.connect(g);
    g.connect(ctx.destination);
    const t0 = ctx.currentTime;
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(0.18, t0 + 0.01);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    osc.start(t0);
    osc.stop(t0 + dur + 0.05);
  } catch (e) {}
}
function playChime() {
  [523, 659, 784].forEach((f, i) => setTimeout(() => playTock(f, 0.6, 'triangle'), i * 120));
}

// ============ Icons ============
const Icon = ({ name, className = "w-5 h-5" }) => {
  const paths = {
    save:     <><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></>,
    bulb:     <><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7c.6.5 1 1.2 1 2V18h6v-1.3c0-.8.4-1.5 1-2A7 7 0 0 0 12 2z"/></>,
    sparkles: <><path d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z"/></>,
    rotate:   <><path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v6h-6"/></>,
    undo:     <><path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-4"/></>,
    check:    <><polyline points="20 6 9 17 4 12"/></>,
    grip:     <><circle cx="9" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="18" r="1"/></>,
    book:     <><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></>,
    flame:    <><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></>,
    moon:     <><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></>,
  };
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"
         strokeLinecap="round" strokeLinejoin="round" className={className}>
      {paths[name]}
    </svg>
  );
};

// ============ Stick graphic (drag ghost + rack) ============
const StickGraphic = ({ size = 14 }) => (
  <svg viewBox="0 0 14 80" width={size} height={size * 5.7}>
    <defs>
      <linearGradient id="stick-grad" x1="0" y1="0" x2="1" y2="0">
        <stop offset="0" stopColor="#9c7349"/>
        <stop offset="0.5" stopColor="#714e2f"/>
        <stop offset="1" stopColor="#3a2614"/>
      </linearGradient>
    </defs>
    <rect x="2" y="3" width="10" height="74" rx="5" fill="url(#stick-grad)" />
    <ellipse cx="7" cy="4" rx="5" ry="2" fill="#5a3e25"/>
    <ellipse cx="7" cy="76" rx="5" ry="2" fill="#2c1d10"/>
    <ellipse cx="7" cy="34" rx="2" ry="1.2" fill="#3a2614" opacity="0.6"/>
    <line x1="7" y1="10" x2="7" y2="72" stroke="#3a2614" strokeWidth="0.4" opacity="0.5"/>
  </svg>
);

// ============ Blueprint thumbnail SVGs ============
const BlueprintIcon = ({ bpKey, active }) => {
  const stroke = active ? '#FFE9A8' : '#A89F87';
  const sw = active ? 1.8 : 1.4;
  const fill = active ? 'rgba(244,196,48,0.15)' : 'transparent';
  switch (bpKey) {
    case 'box': return (
      <svg viewBox="0 0 40 40" className="w-full h-full">
        <rect x="12" y="6" width="16" height="28" fill={fill} stroke={stroke} strokeWidth={sw}/>
        <line x1="12" y1="14" x2="28" y2="14" stroke={stroke} strokeWidth={sw} opacity="0.6"/>
        <line x1="12" y1="26" x2="28" y2="26" stroke={stroke} strokeWidth={sw} opacity="0.6"/>
      </svg>
    );
    case 'bucket': return (
      <svg viewBox="0 0 40 40" className="w-full h-full">
        <polygon points="20,4 34,20 20,36 6,20" fill={fill} stroke={stroke} strokeWidth={sw} strokeLinejoin="round"/>
        <line x1="20" y1="4" x2="20" y2="36" stroke={stroke} strokeWidth={sw} opacity="0.5"/>
        <line x1="6" y1="20" x2="34" y2="20" stroke={stroke} strokeWidth={sw} opacity="0.5"/>
      </svg>
    );
    case 'atapattama': return (
      <svg viewBox="0 0 40 40" className="w-full h-full">
        <polygon points="20,4 32,12 32,28 20,36 8,28 8,12" fill={fill} stroke={stroke} strokeWidth={sw} strokeLinejoin="round"/>
        <polygon points="20,12 28,18 20,24 12,18" fill="none" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="20" y1="4" x2="20" y2="12" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="20" y1="24" x2="20" y2="36" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="8" y1="12" x2="12" y2="18" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="32" y1="12" x2="28" y2="18" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="8" y1="28" x2="12" y2="18" stroke={stroke} strokeWidth={sw*0.7}/>
        <line x1="32" y1="28" x2="28" y2="18" stroke={stroke} strokeWidth={sw*0.7}/>
      </svg>
    );
  }
};

// ============ Top bar ============
function TopBar({ bpKey, onPickBp, lit, onToggleLight, onReset, onUndo, canUndo }) {
  const order = window.BLUEPRINT_ORDER;
  return (
    <div className="relative z-30 px-3 sm:px-7 pt-2 sm:pt-5 pb-1.5 sm:pb-3 flex-shrink-0">
      <div className="flag-stripe mb-1.5 sm:mb-4" />
      <div className="flex items-start justify-between gap-3 sm:gap-6">
        <div className="flex items-center gap-2 sm:gap-4">
          <div className="relative w-10 h-10 sm:w-14 sm:h-14">
            <div className="halo-spin absolute inset-0 rounded-full"
                 style={{
                   background: 'conic-gradient(from 0deg, #1E5BC6, #F4C430, #DA1A32, #FFFFFF, #F47C2C, #1E5BC6)',
                   filter: 'blur(6px)',
                   opacity: 0.7,
                 }} />
            <div className="absolute inset-1 rounded-full flex items-center justify-center"
                 style={{
                   background: 'radial-gradient(circle, #FFE9A8 0%, #F4C430 50%, #DA1A32 100%)',
                   boxShadow: '0 0 26px rgba(244,196,48,0.7), inset 0 -3px 8px rgba(0,0,0,0.3)',
                 }}>
              <Icon name="flame" className="w-4 h-4 sm:w-5 sm:h-5 text-[#3a1408]" />
            </div>
          </div>
          <div>
            <h1 className="font-display text-[1.1rem] sm:text-[2rem] leading-none font-bold tracking-[0.14em] sm:tracking-[0.18em] title-glow"
                style={{
                  background: 'linear-gradient(90deg, #F4C430 0%, #FFFFFF 50%, #F47C2C 100%)',
                  WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
                }}>
              VESAK LANTERN
            </h1>
            <div className="hidden sm:block text-[10px] tracking-[0.42em] uppercase text-[#A89F87] mt-1.5 font-accent">
              A night of light · craft your lantern
            </div>
          </div>
        </div>

        <div className="flex items-center gap-2">
          <PillBtn onClick={onUndo} disabled={!canUndo} icon="undo">Undo</PillBtn>
          <PillBtn onClick={onReset} icon="rotate">Reset</PillBtn>
          <button onClick={onToggleLight} aria-label={lit ? 'Turn light off' : 'Turn light on'}
                  className="h-8 sm:h-9 w-8 sm:w-auto px-0 sm:px-4 rounded-full text-[10px] uppercase tracking-[0.22em] font-bold flex items-center justify-center gap-1.5 transition-all"
                  style={lit ? {
                    background: 'linear-gradient(180deg, #FFE9A8 0%, #F47C2C 100%)',
                    border: '1px solid #F4C430',
                    color: '#3a1408',
                    boxShadow: '0 0 22px rgba(244,124,44,0.65), inset 0 1px 0 rgba(255,255,255,0.4)',
                  } : {
                    background: 'linear-gradient(180deg, rgba(27,36,71,0.85), rgba(11,17,36,0.9))',
                    border: '1px solid rgba(244,196,48,0.3)',
                    color: '#E6DDC4',
                  }}>
            <Icon name="bulb" className="w-3.5 h-3.5" />
            <span className="hidden sm:inline">{lit ? 'Lit' : 'Light'}</span>
          </button>
        </div>
      </div>

      <div className="mt-3 sm:mt-5 flex items-center gap-2 flex-wrap">
        <span className="hidden sm:flex text-[10px] uppercase tracking-[0.32em] font-bold text-[#A89F87] mr-2 items-center gap-1.5 font-accent">
          <Icon name="book" className="w-3.5 h-3.5" /> Blueprint
        </span>
        {order.map(k => {
          const bp = window.BLUEPRINTS[k];
          const active = k === bpKey;
          return (
            <button key={k} onClick={() => onPickBp(k)}
                    className={`flex items-center gap-1 sm:gap-2 pl-1 sm:pl-1.5 pr-2 sm:pr-3.5 py-1 sm:py-1.5 rounded-full transition-all ${active ? 'pill-active' : 'pill-idle'}`}>
              <span className="w-5 h-5 sm:w-7 sm:h-7 flex items-center justify-center"><BlueprintIcon bpKey={k} active={active} /></span>
              <span className="text-[10px] sm:text-[11px] font-bold tracking-[0.12em] sm:tracking-[0.14em] font-ui uppercase">
                {bp.name}
              </span>
              <span className="hidden sm:inline text-[10px] font-medium opacity-70 font-ui tabular-nums">
                {bp.edges.length}
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function PillBtn({ onClick, disabled, icon, children }) {
  return (
    <button onClick={onClick} disabled={disabled} aria-label={typeof children === 'string' ? children : undefined}
            className="h-8 sm:h-9 w-8 sm:w-auto px-0 sm:px-4 rounded-full text-[10px] uppercase tracking-[0.22em] font-bold flex items-center justify-center gap-1.5 transition-all disabled:opacity-40"
            style={{
              background: 'linear-gradient(180deg, rgba(27,36,71,0.85), rgba(11,17,36,0.9))',
              border: '1px solid rgba(244,196,48,0.3)',
              color: '#E6DDC4',
            }}>
      <Icon name={icon} className="w-3.5 h-3.5" />
      <span className="hidden sm:inline">{children}</span>
    </button>
  );
}

// ============ Stick rack ============
function StickRack({ onDragStart, isMobile }) {
  const handlePD = (e) => { e.preventDefault(); onDragStart({ x: e.clientX, y: e.clientY }); };

  // mobile: horizontal strip at top-left of canvas, big touch targets
  if (isMobile) {
    return (
      <div className="absolute top-2 left-2 z-20 select-none">
        <div className="px-2 py-1.5 rounded-xl flag-border-strong panel-shadow flex items-center gap-1"
             style={{
               background: 'linear-gradient(180deg, rgba(35,46,88,0.92) 0%, rgba(11,17,36,0.95) 100%)',
               backdropFilter: 'blur(10px)',
             }}>
          <span className="text-[8px] uppercase tracking-[0.18em] font-bold text-[#F4C430] mr-1 font-accent whitespace-nowrap">
            Sticks ›
          </span>
          {[0,1,2,3].map(i => (
            <div key={i}
                 className="drag-source cursor-grab active:cursor-grabbing"
                 style={{
                   width: 24, height: 38,
                   display: 'flex', alignItems: 'center', justifyContent: 'center',
                   transform: `rotate(${(i - 1.5) * 6}deg)`,
                   filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.4))',
                 }}
                 onPointerDown={handlePD}>
              <StickGraphic size={10} />
            </div>
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className="absolute top-1/2 left-6 -translate-y-1/2 z-20 select-none">
      <div className="p-3 rounded-2xl flag-border-strong panel-shadow"
           style={{
             background: 'linear-gradient(180deg, rgba(35,46,88,0.92) 0%, rgba(11,17,36,0.95) 100%)',
             width: 110,
             backdropFilter: 'blur(10px)',
           }}>
        <div className="text-center text-[9px] uppercase tracking-[0.28em] font-bold text-[#F4C430] mb-2 font-accent">
          Stick Rack
        </div>
        <div className="relative h-[260px]">
          {[0,1,2,3,4,5].map(i => (
            <div key={i}
                 className="drag-source absolute left-1/2 -translate-x-1/2 cursor-grab active:cursor-grabbing"
                 style={{
                   top: i * 14 + 6,
                   transform: `translate(-50%, 0) rotate(${(i - 2.5) * 1.5}deg)`,
                   transformOrigin: 'center bottom',
                   transition: 'transform 0.18s ease',
                   filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.4))',
                 }}
                 onPointerDown={handlePD}
                 onMouseEnter={(e) => e.currentTarget.style.transform = `translate(-50%, -3px) rotate(${(i - 2.5) * 2.5}deg)`}
                 onMouseLeave={(e) => e.currentTarget.style.transform = `translate(-50%, 0) rotate(${(i - 2.5) * 1.5}deg)`}>
              <StickGraphic size={16} />
            </div>
          ))}
        </div>
        <div className="text-center text-[8px] text-[#A89F87] mt-1 leading-tight font-ui uppercase tracking-[0.15em]">
          Drag onto a<br/>glowing edge
        </div>
      </div>
    </div>
  );
}

// ============ Progress bar ============
function ProgressBar({ placedSticks, totalSticks, sticksComplete, placedPapers, totalPapers, papersComplete, isMobile }) {
  const showPapers = sticksComplete;
  const placed = showPapers ? placedPapers : placedSticks;
  const total = showPapers ? totalPapers : totalSticks;
  const complete = showPapers ? papersComplete : sticksComplete;
  const pct = total === 0 ? 0 : (placed / total) * 100;
  const label = showPapers ? (papersComplete ? "Complete" : "Wrapping Paper") : (sticksComplete ? "Frame Complete" : "Assembling Frame");
  const subLabel = showPapers ? "panels" : "sticks";

  return (
    <div
      className="fixed left-1/2 -translate-x-1/2 z-30 w-[calc(100%-1rem)] sm:w-auto max-w-md px-2 sm:px-0"
      style={{ bottom: 'calc(0.75rem + env(safe-area-inset-bottom))' }}
    >
      <div className="px-3 sm:px-5 py-1.5 sm:py-3 rounded-2xl flag-border-strong panel-shadow surface-card flex items-center gap-3 sm:gap-4"
           style={{ minWidth: isMobile ? 0 : 340 }}>
        <div className="flex items-center gap-2.5">
          <div className="w-9 h-9 rounded-full flex items-center justify-center"
               style={{
                 background: complete
                   ? 'radial-gradient(circle, #FFE9A8 0%, #F4C430 50%, #DA1A32 100%)'
                   : 'linear-gradient(180deg, rgba(244,196,48,0.18) 0%, rgba(218,26,50,0.12) 100%)',
                 border: complete ? '1px solid #F4C430' : '1px solid rgba(244,196,48,0.4)',
                 boxShadow: complete ? '0 0 22px rgba(244,124,44,0.7)' : 'none',
               }}>
            {complete
              ? <Icon name="check" className="w-4 h-4 text-[#3a1408]" />
              : <Icon name="flame" className="w-4 h-4 text-[#F4C430]" />}
          </div>
          <div>
            <div className="text-[9px] uppercase tracking-[0.28em] font-bold text-[#A89F87] font-accent">
              {label}
            </div>
            <div className="text-[14px] font-bold font-display tracking-wider text-[#F5EFE0] tabular-nums">
              {placed} <span className="text-[#A89F87] font-normal">/ {total} {subLabel}</span>
            </div>
          </div>
        </div>
        <div className="flex-1 h-2.5 rounded-full overflow-hidden"
             style={{ background: 'rgba(0,0,0,0.4)', boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.5)' }}>
          <div className="h-full rounded-full transition-all duration-300"
               style={{
                 width: pct + '%',
                 background: complete
                   ? 'linear-gradient(90deg, #F4C430 0%, #F47C2C 50%, #DA1A32 100%)'
                   : 'linear-gradient(90deg, #1E5BC6 0%, #F4C430 100%)',
                 boxShadow: complete
                   ? '0 0 18px rgba(244,124,44,0.8)'
                   : '0 0 8px rgba(244,196,48,0.4)',
               }} />
        </div>
      </div>
    </div>
  );
}

// ============ Paper palette ============
const PAPERS = [
  { id: 'p-blue-mand',  color: '#1E5BC6', pattern: 'mandala', label: 'Nila Mandala' },
  { id: 'p-yel-mand',   color: '#F4C430', pattern: 'mandala', label: 'Pita Mandala' },
  { id: 'p-red-mand',   color: '#DA1A32', pattern: 'mandala', label: 'Lohitha Mandala' },
  { id: 'p-org-mand',   color: '#F47C2C', pattern: 'mandala', label: 'Manjista Mandala' },
  { id: 'p-blue-star',  color: '#1E5BC6', pattern: 'star',    label: 'Blue Star' },
  { id: 'p-yel-star',   color: '#F4C430', pattern: 'star',    label: 'Yellow Star' },
  { id: 'p-red-lotus',  color: '#DA1A32', pattern: 'lotus',   label: 'Red Lotus' },
  { id: 'p-org-lotus',  color: '#F47C2C', pattern: 'lotus',   label: 'Orange Lotus' },
  { id: 'p-blue-leaf',  color: '#1E5BC6', pattern: 'leaf',    label: 'Blue Leaves' },
  { id: 'p-yel-leaf',   color: '#F4C430', pattern: 'leaf',    label: 'Yellow Leaves' },
  { id: 'p-red-lat',    color: '#DA1A32', pattern: 'lattice', label: 'Red Lattice' },
  { id: 'p-white-plain',color: '#F5EFE0', pattern: 'lotus',   label: 'Odata White' },
];

const PaperSwatch = ({ paper, size = 44 }) => (
  <svg viewBox="0 0 64 64" width={size} height={size}>
    <rect x="4" y="4" width="56" height="56" fill={paper.color} stroke="#F4C430" strokeWidth="2" rx="2"/>
    {paper.pattern === 'mandala' && (
      <g>
        {[...Array(8)].map((_, i) => (
          <ellipse key={i} cx="32" cy="20" rx="3" ry="10" transform={`rotate(${i*45} 32 32)`} fill="#FDF6E3" opacity="0.92"/>
        ))}
        <circle cx="32" cy="32" r="4" fill="#FDF6E3"/>
      </g>
    )}
    {paper.pattern === 'lotus' && [...Array(8)].map((_, i) => (
      <path key={i} d="M32 32 Q36 22 32 12 Q28 22 32 32 Z" transform={`rotate(${i*45} 32 32)`} fill="#FDF6E3"/>
    ))}
    {paper.pattern === 'star' && (
      <polygon points="32,8 36,26 54,26 39,37 45,55 32,44 19,55 25,37 10,26 28,26"
               fill="none" stroke="#FDF6E3" strokeWidth="2"/>
    )}
    {paper.pattern === 'lattice' && (
      <g stroke="#FDF6E3" strokeWidth="1.5" fill="none">
        <line x1="10" y1="20" x2="54" y2="32"/>
        <line x1="10" y1="32" x2="54" y2="44"/>
        <line x1="10" y1="44" x2="54" y2="20"/>
        <line x1="10" y1="20" x2="54" y2="44"/>
        <line x1="10" y1="32" x2="54" y2="20"/>
        <line x1="10" y1="44" x2="54" y2="32"/>
      </g>
    )}
    {paper.pattern === 'leaf' && [...Array(6)].map((_, i) => (
      <path key={i} d="M32 32 Q42 22 32 8 Q22 22 32 32 Z" transform={`rotate(${i*60} 32 32)`}
            fill="none" stroke="#FDF6E3" strokeWidth="1.5"/>
    ))}
  </svg>
);

function PaperPalette({ onPaperDragStart, onClear, isMobile }) {
  const wrapperPos = isMobile
    ? "absolute top-2 right-2 z-20 select-none"
    : "absolute top-4 right-6 z-20 select-none";
  const wrapperWidth = isMobile ? 168 : 296;
  const gridCols = isMobile ? "grid-cols-4" : "grid-cols-6";
  return (
    <div className={wrapperPos}>
      <div className="p-2 sm:p-3 rounded-2xl flag-border-strong panel-shadow surface-card-elev toast-in" style={{ width: wrapperWidth }}>
        <div className="flex items-center justify-between mb-2">
          <div className="flex items-center gap-1.5">
            <Icon name="sparkles" className="w-3.5 h-3.5 text-[#F4C430]" />
            <h3 className="font-display text-[0.85rem] sm:text-[0.95rem] font-bold text-[#F4C430] tracking-[0.16em]">
              PAPER
            </h3>
          </div>
          <button onClick={onClear}
                  className="text-[8px] uppercase tracking-[0.22em] font-bold text-[#A89F87] hover:text-[#F4C430] transition">
            Clear
          </button>
        </div>
        <div className={`grid ${gridCols} gap-1.5`}>
          {PAPERS.map(p => (
            <button key={p.id}
                    onPointerDown={(e) => { e.preventDefault(); onPaperDragStart(p, { x: e.clientX, y: e.clientY }); }}
                    className="card-item drag-source p-1" title={p.label}>
              <div className="w-full aspect-square overflow-hidden rounded">
                <PaperSwatch paper={p} size={36} />
              </div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ============ Glow & Save panel ============
function DecoratePanel({
  glowColor, onGlowChange, tasselCount, onTassels, onSave, saving, saved, isMobile,
  showTassels = true, showRibbons = false, ribbonColor = 0xFFFFFF, onRibbonColorChange,
}) {
  const glows = [
    { name: 'Pita',     hex: 0xF4C430, sw: '#F4C430' },
    { name: 'Manjista', hex: 0xF47C2C, sw: '#F47C2C' },
    { name: 'Lohitha',  hex: 0xDA1A32, sw: '#DA1A32' },
    { name: 'Odata',    hex: 0xFFFFFF, sw: '#FFFFFF' },
    { name: 'Nila',     hex: 0x1E5BC6, sw: '#1E5BC6' },
    { name: 'Warm',     hex: 0xFFD27F, sw: '#FFD27F' },
  ];
  const ribbonColors = [
    { name: 'Odata',    hex: 0xFFFFFF, sw: '#FFFFFF' },
    { name: 'Pita',     hex: 0xF4C430, sw: '#F4C430' },
    { name: 'Manjista', hex: 0xF47C2C, sw: '#F47C2C' },
    { name: 'Lohitha',  hex: 0xDA1A32, sw: '#DA1A32' },
    { name: 'Nila',     hex: 0x1E5BC6, sw: '#1E5BC6' },
    { name: 'Warm',     hex: 0xFFD27F, sw: '#FFD27F' },
  ];
  // mobile: viewport-anchored (fixed) so it stays above the progress bar even when the layout is squeezed
  const wrapPos = isMobile
    ? "fixed right-2 z-30 toast-in"
    : "absolute bottom-6 right-6 z-20 toast-in";
  const wrapStyle = isMobile
    ? { bottom: 'calc(68px + env(safe-area-inset-bottom))' }
    : {};
  const wrapW = isMobile ? "w-44" : "w-72";
  return (
    <div className={wrapPos} style={wrapStyle}>
      <div className={`p-2 sm:p-4 rounded-2xl flag-border-strong panel-shadow surface-card-elev ${wrapW} max-h-[58vh] overflow-y-auto scroll-warm`}>
        <div className="flex items-center gap-2 mb-2 sm:mb-3">
          <Icon name="flame" className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-[#F47C2C]" />
          <h3 className="font-display text-[0.8rem] sm:text-[1rem] font-bold tracking-[0.14em] sm:tracking-[0.16em] text-[#F4C430]">
            GLOW &amp; SAVE
          </h3>
        </div>

        <div className="text-[9px] uppercase tracking-[0.28em] font-bold text-[#A89F87] mb-1.5 sm:mb-2 font-accent">
          Candle Glow
        </div>
        <div className="grid grid-cols-6 gap-1 sm:gap-1.5 mb-2 sm:mb-3">
          {glows.map(g => (
            <button key={g.name} onClick={() => onGlowChange(g.hex)}
                    className="aspect-square rounded-md sm:rounded-lg transition-all relative overflow-hidden"
                    title={g.name}
                    style={{
                      background: g.sw,
                      border: glowColor === g.hex ? '2px solid #FFE9A8' : '1px solid rgba(244,196,48,0.4)',
                      boxShadow: glowColor === g.hex
                        ? `0 0 14px ${g.sw}, inset 0 1px 0 rgba(255,255,255,0.4)`
                        : 'inset 0 1px 0 rgba(255,255,255,0.15)',
                    }} />
          ))}
        </div>

        {showRibbons && (
          <>
            <div className="text-[9px] uppercase tracking-[0.28em] font-bold text-[#A89F87] mb-1.5 sm:mb-2 font-accent">
              Ribbon Color
            </div>
            <div className="grid grid-cols-6 gap-1 sm:gap-1.5 mb-2 sm:mb-3">
              {ribbonColors.map(rc => (
                <button key={rc.name} onClick={() => onRibbonColorChange(rc.hex)}
                        className="aspect-square rounded-md sm:rounded-lg transition-all relative overflow-hidden"
                        title={rc.name}
                        style={{
                          background: rc.sw,
                          border: ribbonColor === rc.hex ? '2px solid #FFE9A8' : '1px solid rgba(244,196,48,0.4)',
                          boxShadow: ribbonColor === rc.hex
                            ? `0 0 14px ${rc.sw}, inset 0 1px 0 rgba(255,255,255,0.4)`
                            : 'inset 0 1px 0 rgba(255,255,255,0.15)',
                        }} />
              ))}
            </div>
          </>
        )}

        {showTassels && (
          <>
            <div className="text-[9px] uppercase tracking-[0.28em] font-bold text-[#A89F87] mb-1.5 sm:mb-2 font-accent">
              Tassels
            </div>
            <div className="grid grid-cols-4 gap-1 sm:gap-2 mb-2 sm:mb-4">
              {[0,1,2,3].map(n => (
                <button key={n} onClick={() => onTassels(n)}
                        className="h-7 sm:h-10 rounded-md sm:rounded-lg text-[11px] sm:text-[13px] font-bold transition-all font-display tracking-wider"
                        style={{
                          background: tasselCount === n
                            ? 'linear-gradient(180deg, rgba(244,196,48,0.3) 0%, rgba(244,124,44,0.2) 100%)'
                            : 'linear-gradient(180deg, rgba(27,36,71,0.7) 0%, rgba(11,17,36,0.8) 100%)',
                          border: tasselCount === n ? '1.5px solid #F4C430' : '1px solid rgba(244,196,48,0.25)',
                          color: tasselCount === n ? '#FFE9A8' : '#A89F87',
                          boxShadow: tasselCount === n ? '0 0 12px rgba(244,196,48,0.3)' : 'none',
                        }}>
                  {n === 0 ? '—' : '×' + n}
                </button>
              ))}
            </div>
          </>
        )}

        <button onClick={onSave} disabled={saving}
                className="w-full py-2 sm:py-2.5 rounded-lg sm:rounded-xl text-[10px] sm:text-[11px] uppercase tracking-[0.18em] sm:tracking-[0.22em] font-bold transition-all flex items-center justify-center gap-2"
                style={{
                  background: saved
                    ? 'linear-gradient(180deg, #6CCB60 0%, #2B7A2E 100%)'
                    : 'linear-gradient(180deg, #F4C430 0%, #F47C2C 50%, #DA1A32 100%)',
                  border: '1px solid #F4C430',
                  color: saved ? '#0F2A12' : '#1A0805',
                  boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.3), 0 4px 14px rgba(244,124,44,0.4)',
                }}>
          {saved ? <Icon name="check" className="w-4 h-4" /> : <Icon name="save" className="w-4 h-4" />}
          {saving ? 'Saving…' : saved ? 'Saved' : 'Save to Gallery'}
        </button>
      </div>
    </div>
  );
}

// ============ Confetti ============
function Confetti({ shown }) {
  if (!shown) return null;
  const items = useMemo(() => Array.from({ length: 32 }, (_, i) => ({
    id: i,
    left: Math.random() * 100,
    delay: Math.random() * 0.6,
    dur: 1.6 + Math.random() * 1.4,
    color: ['#1E5BC6','#F4C430','#DA1A32','#FFFFFF','#F47C2C','#F4C430'][i % 6],
    rot: Math.random() * 360,
    w: 6 + Math.random() * 6,
    h: 12 + Math.random() * 14,
  })), [shown]);
  return (
    <div className="absolute inset-0 pointer-events-none overflow-hidden z-40">
      {items.map(it => (
        <div key={it.id}
             style={{
               position: 'absolute',
               left: it.left + '%',
               top: -20,
               width: it.w, height: it.h,
               background: it.color,
               transform: `rotate(${it.rot}deg)`,
               animation: `fall ${it.dur}s ${it.delay}s ease-out forwards`,
               opacity: 0.95,
               borderRadius: 2,
               boxShadow: `0 0 12px ${it.color}`,
             }}/>
      ))}
      <style>{`
        @keyframes fall {
          0%   { transform: translateY(0) rotate(0deg); opacity: 0; }
          10%  { opacity: 1; }
          100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
        }
      `}</style>
    </div>
  );
}

// ============ Toast ============
function Toast({ msg }) {
  if (!msg) return null;
  return (
    <div className="toast-in fixed bottom-24 left-1/2 -translate-x-1/2 z-50"
         style={{
           background: 'linear-gradient(180deg, rgba(35,46,88,0.95) 0%, rgba(11,17,36,0.98) 100%)',
           color: '#FFE9A8',
           padding: '10px 22px',
           borderRadius: 999,
           fontSize: 11,
           letterSpacing: '0.18em',
           fontWeight: 700,
           textTransform: 'uppercase',
           boxShadow: '0 12px 32px rgba(0,0,0,0.6), 0 0 22px rgba(244,196,48,0.18)',
           border: '1px solid rgba(244,196,48,0.55)',
         }}>
      {msg}
    </div>
  );
}

// ============ Main App ============
function App() {
  const [bpKey, setBpKey] = useState('atapattama');
  const bp = window.BLUEPRINTS[bpKey];

  const [placedEdges, setPlacedEdges] = useState(() => new Set());
  const [order, setOrder] = useState([]);
  const [lit, setLit] = useState(true);
  const [glowColor, setGlowColor] = useState(0xFFD27F);
  const [tasselCount, setTasselCount] = useState(0);
  const [panels, setPanels] = useState({});
  const [paperDrag, setPaperDrag] = useState(null);
  const [ribbonColor, setRibbonColor] = useState(0xFFFFFF);

  const [drag, setDrag] = useState(null);
  const [toast, setToast] = useState(null);
  const [saving, setSaving] = useState(false);
  const [saved, setSaved] = useState(false);
  const [showConfetti, setShowConfetti] = useState(false);

  // mobile detection — re-evaluates on resize so flipping the phone reflows the layout
  const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.innerWidth < 640);
  useEffect(() => {
    const onResize = () => setIsMobile(window.innerWidth < 640);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  const complete = bp && !bp.freeform && placedEdges.size === bp.edges.length;

  const switchBp = (k) => {
    setBpKey(k);
    setPlacedEdges(new Set());
    setOrder([]);
    setLit(true);
    setTasselCount(0);
    setPanels({});
    setSaved(false);
    setRibbonColor(0xFFFFFF);
    showToast(`Blueprint: ${window.BLUEPRINTS[k].name}`);
  };

  useEffect(() => {
    if (complete) {
      setLit(true);
      setShowConfetti(true);
      playChime();
      showToast('Lantern complete · decorate & save');
      const t = setTimeout(() => setShowConfetti(false), 3200);
      return () => clearTimeout(t);
    }
  }, [complete]);

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(t => t === msg ? null : t), 2000);
  };

  const handleDragStart = (pos) => { setDrag({ x: pos.x, y: pos.y }); ensureAudio(); };
  useEffect(() => {
    if (!drag) return;
    const onMove = (e) => setDrag({ x: e.clientX, y: e.clientY });
    const onUp = () => setDrag(null);
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
  }, [drag]);

  const handlePaperDragStart = (paper, pos) => { setPaperDrag({ ...paper, x: pos.x, y: pos.y }); ensureAudio(); };
  useEffect(() => {
    if (!paperDrag) return;
    const onMove = (e) => setPaperDrag(p => p ? { ...p, x: e.clientX, y: e.clientY } : p);
    const onUp = () => setPaperDrag(null);
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
  }, [paperDrag]);

  const handlePaperSnap = useCallback((faceIdx) => {
    if (faceIdx >= 0 && paperDrag) {
      setPanels(p => ({ ...p, [faceIdx]: { color: paperDrag.color, pattern: paperDrag.pattern } }));
      playTock(420 + Math.random() * 40, 0.16, 'triangle');
    } else if (faceIdx === -1) {
      playTock(140, 0.1, 'sine');
    }
  }, [paperDrag]);

  const handleSnap = useCallback((edgeIdx) => {
    if (edgeIdx >= 0 && !placedEdges.has(edgeIdx)) {
      const next = new Set(placedEdges);
      next.add(edgeIdx);
      setPlacedEdges(next);
      setOrder(o => [...o, edgeIdx]);
      const base = 280 + (next.size * 15);
      playTock(base, 0.22);
    } else if (edgeIdx === -1) {
      playTock(140, 0.12, 'sine');
    }
  }, [placedEdges]);

  const handleUndo = () => {
    if (!order.length) return;
    const last = order[order.length - 1];
    const next = new Set(placedEdges);
    next.delete(last);
    setPlacedEdges(next);
    setOrder(order.slice(0, -1));
    playTock(180, 0.14);
  };

  const handleReset = () => {
    setPlacedEdges(new Set());
    setOrder([]);
    setLit(true);
    setTasselCount(0);
    setPanels({});
    setSaved(false);
    setRibbonColor(0xFFFFFF);
    showToast('Cleared');
    playTock(220, 0.18);
  };

  const handleSave = async () => {
    setSaving(true);

    const entryId = 'lantern_' + Date.now().toString(36);

    // 1) snapshot the 3D lantern as a PNG
    let imageDataUrl = null;
    try {
      if (window.__captureLantern) imageDataUrl = window.__captureLantern();
    } catch (e) { /* capture failed — config still saves below */ }

    // 2) trigger a download of the PNG BEFORE any async operations so the browser doesn't block it
    if (imageDataUrl) {
      try {
        const link = document.createElement('a');
        link.href = imageDataUrl;
        link.download = `vesak-lantern-${entryId}.png`;
        link.rel = 'noopener';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      } catch (e) {
        try {
          // fallback: open in a new tab (works even when iframe blocks downloads)
          window.open(imageDataUrl, '_blank');
        } catch (err) {
          console.error("Image download / open in tab failed", err);
        }
      }
    }

    // 3) persist the config (localStorage; survives reloads on itch.io too)
    let entry = { id: entryId };
    try {
      entry = await api.save({
        blueprint: bpKey,
        placedEdges: [...placedEdges],
        panels,
        lit, glowColor, tasselCount, ribbonColor,
        image: imageDataUrl, // small PNG kept alongside the config so the gallery has a thumbnail
      });
    } catch (e) {
      console.error("api.save failed", e);
    }

    setSaving(false);
    setSaved(true);
    showToast(imageDataUrl ? `Image saved · ${entry.id}` : `Saved as ${entry.id}`);
    playChime();
    setTimeout(() => setSaved(false), 3000);
  };

  return (
    <div className="h-full w-full flex flex-col font-ui relative overflow-hidden surface-night">
      <div className={`bokeh ${lit ? 'lit' : ''}`} style={{ position: 'absolute', inset: 0, zIndex: 0 }}></div>

      <TopBar
        bpKey={bpKey}
        onPickBp={switchBp}
        lit={lit}
        onToggleLight={() => setLit(v => !v)}
        onReset={handleReset}
        onUndo={handleUndo}
        canUndo={order.length > 0}
      />

      <div className="flex-1 relative px-2 sm:px-7 pb-2 sm:pb-7">
        <div className={`relative w-full h-full rounded-xl sm:rounded-3xl overflow-hidden flag-border-strong panel-shadow ${complete && lit ? 'canvas-lit-glow' : ''}`}
             style={{
               background: 'radial-gradient(ellipse at center, #131A36 0%, #0B1124 60%, #050913 100%)',
             }}>
          <window.Builder3D
            blueprintKey={bpKey}
            placedEdges={placedEdges}
            onSnap={handleSnap}
            dragging={!!drag}
            lit={lit}
            glowColor={glowColor}
            tasselCount={tasselCount}
            panels={panels}
            paperDrag={paperDrag}
            onPaperSnap={handlePaperSnap}
            ribbonColor={ribbonColor}
            showRibbons={complete && (Object.keys(panels).length === (bp ? bp.faces.filter(f => !f.noPaper).length : 0))}
          />

          <StickRack onDragStart={handleDragStart} isMobile={isMobile} />

          {complete && (
            <PaperPalette
              onPaperDragStart={handlePaperDragStart}
              onClear={() => { setPanels({}); showToast('Papers cleared'); }}
              isMobile={isMobile}
            />
          )}

          <ProgressBar
            placedSticks={placedEdges.size}
            totalSticks={bp ? bp.edges.length : 0}
            sticksComplete={complete}
            placedPapers={Object.keys(panels).length}
            totalPapers={bp ? bp.faces.filter(f => !f.noPaper).length : 0}
            papersComplete={complete && (Object.keys(panels).length === (bp ? bp.faces.filter(f => !f.noPaper).length : 0))}
            isMobile={isMobile}
          />

          {complete && (
            <DecoratePanel
              glowColor={glowColor}
              onGlowChange={setGlowColor}
              tasselCount={tasselCount}
              onTassels={setTasselCount}
              onSave={handleSave}
              saving={saving}
              saved={saved}
              isMobile={isMobile}
              showTassels={bpKey === 'bucket'}
              showRibbons={bpKey === 'atapattama' && (Object.keys(panels).length === (bp ? bp.faces.filter(f => !f.noPaper).length : 0))}
              ribbonColor={ribbonColor}
              onRibbonColorChange={setRibbonColor}
            />
          )}

          <Confetti shown={showConfetti} />

          {placedEdges.size === 0 && !drag && (
            <div
              className="fixed left-1/2 -translate-x-1/2 z-30 text-center pointer-events-none"
              style={{ bottom: isMobile
                ? 'calc(82px + env(safe-area-inset-bottom))'  // mobile: just above progress bar
                : 'calc(112px + env(safe-area-inset-bottom))' // desktop: above progress bar, with extra gap
              }}>
              <div className="px-4 py-2 rounded-full inline-flex items-center gap-2"
                   style={{
                     background: 'rgba(11,17,36,0.65)',
                     backdropFilter: 'blur(8px)',
                     border: '1px dashed rgba(244,196,48,0.5)',
                     color: '#A89F87',
                     fontSize: 10,
                     letterSpacing: '0.28em',
                     textTransform: 'uppercase',
                     fontWeight: 700,
                     fontFamily: 'Manrope',
                   }}>
                <Icon name="grip" className="w-3.5 h-3.5" />
                Drag a stick onto a glowing edge
              </div>
            </div>
          )}

          {complete && Object.keys(panels).length === 0 && !paperDrag && (
            <div
              className="fixed left-1/2 -translate-x-1/2 z-30 text-center pointer-events-none toast-in"
              style={{ bottom: isMobile
                ? 'calc(82px + env(safe-area-inset-bottom))'
                : 'calc(112px + env(safe-area-inset-bottom))'
              }}>
              <div className="px-4 py-2 rounded-full inline-flex items-center gap-2"
                   style={{
                     background: 'rgba(11,17,36,0.72)',
                     backdropFilter: 'blur(8px)',
                     border: '1px dashed rgba(244,196,48,0.6)',
                     color: '#FFE9A8',
                     fontSize: 10,
                     letterSpacing: '0.28em',
                     textTransform: 'uppercase',
                     fontWeight: 700,
                     fontFamily: 'Manrope',
                   }}>
                <Icon name="sparkles" className="w-3.5 h-3.5" />
                Drag paper onto each side to decorate
              </div>
            </div>
          )}
        </div>
      </div>

      {drag && (
        <div className="drag-ghost" style={{ left: drag.x, top: drag.y, width: 50, height: 100 }}>
          <div style={{
            width: '100%', height: '100%',
            transform: 'rotate(-15deg)',
            filter: 'drop-shadow(0 6px 18px rgba(244,196,48,0.55))',
          }}>
            <StickGraphic size={18} />
          </div>
        </div>
      )}

      {paperDrag && (
        <div className="drag-ghost" style={{
          left: paperDrag.x, top: paperDrag.y, width: 64, height: 64,
          transform: 'translate(-50%, -50%) rotate(-6deg)',
          filter: `drop-shadow(0 8px 22px ${paperDrag.color}aa)`,
        }}>
          <PaperSwatch paper={paperDrag} size={64} />
        </div>
      )}

      <Toast msg={toast} />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
