// ============ SCREENS ============

const { useState: useStateS, useEffect: useEffectS, useMemo: useMemoS } = React;

// ---------- HUB ----------
function HubScreen({ items, onNav, tweaks, setTweak, ownerMode, onToggleOwner }) {
  const { play } = useSound();
  const total = items.length;
  const completed = items.filter(i => i.completed).length;
  const inProg = total - completed;
  const avgPct = total ? Math.round(items.reduce((s, i) => s + pctOf(i), 0) / total) : 0;
  const totalBacked = items.reduce((s, i) => s + (i.donors || []).reduce((a, d) => a + d.amount, 0), 0);
  const menu = ownerMode ? [
    { id: "grid",   label: "Browse Wishes",  sub: `${total} items archived`, ico: "▶" },
    { id: "add",    label: "Make a Wish",    sub: "Insert a new dream",       ico: "✎" },
    { id: "import", label: "Import from Web", sub: "Scan product URLs",       ico: "⊕" },
    { id: "stats",  label: "Lifetime Stats", sub: `${avgPct}% avg progress`,  ico: "◇" },
    { id: "trophy", label: "Trophy Room",    sub: `${completed} unlocked`,    ico: "✦" },
  ] : [
    { id: "grid",   label: "Browse Wishes",  sub: `${total} items archived`, ico: "▶" },
    { id: "stats",  label: "View Progress",  sub: `${avgPct}% avg complete`,  ico: "◇" },
    { id: "trophy", label: "Trophy Room",    sub: `${completed} unlocked`,    ico: "✦" },
  ];

  const cycleBg = () => {
    const order = ["stars", "dvd", "none"];
    const next = order[(order.indexOf(tweaks.background) + 1) % order.length];
    play("tick");
    setTweak("background", next);
  };
  const bgLabel = { stars: "STARS", dvd: "DVD BOUNCE", none: "OFF" }[tweaks.background];

  return (
    <div className="screen">
      <LockBadge ownerMode={ownerMode} onClick={onToggleOwner} />
      <div className="hub">
        <div className="hub-left">
          <div className="hub-tagline">▮ {ownerMode ? "Owner Edition" : "Visitor Mode"} · Disc 1 of 1</div>
          <h1 className="hub-title">Dream<span className="ampersand">&amp;</span><br/>Track</h1>
          <div className="hub-tagline" style={{color:"var(--chrome-2)"}}>{ownerMode ? "Press a button to begin" : "Browse · Back a wish · Cheer them on"}</div>
          <div className="hub-stats">
            <div className="hub-stat"><div className="num">{total.toString().padStart(2,"0")}</div><div className="label">Total Wishes</div></div>
            <div className="hub-stat"><div className="num">{inProg.toString().padStart(2,"0")}</div><div className="label">In Progress</div></div>
            <div className="hub-stat"><div className="num">{completed.toString().padStart(2,"0")}</div><div className="label">Granted</div></div>
          </div>
          {totalBacked > 0 && (
            <div className="hub-backed">
              ▮ ${totalBacked.toLocaleString()} contributed by backers
            </div>
          )}
        </div>
        <div className="menu-list">
          {menu.map((m) => (
            <div key={m.id}
              className="menu-item"
              onMouseEnter={() => play("hover")}
              onClick={() => { play("select"); onNav(m.id); }}
            >
              <span className="arrow">▶</span>
              <span className="ico">{m.ico}</span>
              <span className="menu-label">{m.label}</span>
              <span className="sub">{m.sub}</span>
            </div>
          ))}
        </div>
      </div>

      <div className="hub-toggles">
        <div className="hub-toggle"
          onMouseEnter={()=>play("hover")}
          onClick={()=>{play("tick"); setTweak("sound", !tweaks.sound);}}>
          <span className="hl">♪ SOUND</span>
          <span className={`hv ${tweaks.sound?"on":"off"}`}>{tweaks.sound ? "ON" : "OFF"}</span>
        </div>
        <div className="hub-toggle"
          onMouseEnter={()=>play("hover")}
          onClick={()=>{play("tick"); setTweak("scanlines", !tweaks.scanlines);}}>
          <span className="hl">▮ SCANLINES</span>
          <span className={`hv ${tweaks.scanlines?"on":"off"}`}>{tweaks.scanlines ? "ON" : "OFF"}</span>
        </div>
        <div className="hub-toggle"
          onMouseEnter={()=>play("hover")}
          onClick={cycleBg}>
          <span className="hl">✦ BG</span>
          <span className={`hv ${tweaks.background==="none"?"off":"on"}`}>{bgLabel}</span>
        </div>
      </div>
    </div>
  );
}

// ---------- GRID ----------
function GridScreen({ items, onOpen, onAdd, onHome, onStats, filter, setFilter, search, setSearch, sort, setSort, ownerMode }) {
  const { play } = useSound();
  let filtered = filter === "all" ? items
              : filter === "complete" ? items.filter(i => i.completed)
              : filter === "active" ? items.filter(i => !i.completed)
              : items.filter(i => i.category === filter);

  if (search.trim()) {
    const q = search.trim().toLowerCase();
    filtered = filtered.filter(i =>
      i.title.toLowerCase().includes(q) ||
      (i.desc || "").toLowerCase().includes(q) ||
      (i.notes || "").toLowerCase().includes(q)
    );
  }

  const sorters = {
    newest:    (a, b) => (b.createdAt || 0) - (a.createdAt || 0),
    oldest:    (a, b) => (a.createdAt || 0) - (b.createdAt || 0),
    progress:  (a, b) => pctOf(b) - pctOf(a),
    az:        (a, b) => a.title.localeCompare(b.title),
    category:  (a, b) => a.category.localeCompare(b.category) || a.title.localeCompare(b.title),
  };
  filtered = [...filtered].sort(sorters[sort] || sorters.newest);

  return (
    <div className="screen">
      <ChromeBar onHome={onHome} onAdd={onAdd} onStats={onStats} ownerMode={ownerMode} />
      <div className="grid-screen">
        <div className="section-head">
          <div className="section-title">All Wishes</div>
          <div className="section-meta">▶ {filtered.length} of {items.length} entries</div>
        </div>

        <div className="search-row">
          <div className="search-box">
            <span className="sb-pre">⌕</span>
            <input
              value={search}
              onChange={e => setSearch(e.target.value)}
              placeholder="Search wishes..."
            />
            {search && (
              <span className="sb-clear" onClick={()=>{play("back"); setSearch("");}}>✕</span>
            )}
          </div>
          <div className="sort-box">
            <label>SORT</label>
            <select value={sort} onChange={e=>{play("tick"); setSort(e.target.value);}}>
              <option value="newest">Newest</option>
              <option value="oldest">Oldest</option>
              <option value="progress">Most progress</option>
              <option value="az">A → Z</option>
              <option value="category">By category</option>
            </select>
          </div>
        </div>

        <div className="filter-bar">
          {[["all","All"],["active","Active"],["complete","Granted"]].map(([k,l]) => (
            <div key={k}
              className={`chip ${filter===k?"active":""}`}
              onMouseEnter={()=>play("hover")}
              onClick={()=>{play("tick"); setFilter(k);}}
            >{l}</div>
          ))}
          <div className="chip-sep"></div>
          {CAT_LIST.map(c => (
            <div key={c.id}
              className={`chip ${filter===c.id?"active":""}`}
              onMouseEnter={()=>play("hover")}
              onClick={()=>{play("tick"); setFilter(c.id);}}
            >{c.label}</div>
          ))}
        </div>

        {filtered.length === 0 ? (
          <div className="empty">
            <div className="em-big">No wishes here</div>
            <div>▶ {search ? "Try a different search" : (ownerMode ? "Press NEW to insert one" : "Check back soon")}</div>
          </div>
        ) : (
          <div className="grid">
            {filtered.map(item => {
              const p = pctOf(item);
              const cat = CATEGORIES[item.category];
              const donorCount = (item.donors || []).length;
              return (
                <div key={item.id}
                  className={`card ${item.completed?"complete":""} ${item.cover?"has-cover":""}`}
                  onMouseEnter={()=>play("hover")}
                  onClick={()=>{play("select"); onOpen(item.id);}}
                >
                  <div className="card-thumb">
                    {item.cover
                      ? <img src={item.cover} className="card-thumb-img" alt={item.title} />
                      : <span className="card-thumb-glyph">{cat?.glyph || "?"}</span>
                    }
                    {item.completed && <span className="card-granted-badge">✦</span>}
                  </div>
                  <div className="card-cat">{cat?.label}{donorCount > 0 && <span className="bk-tag"> · {donorCount} 🤍</span>}</div>
                  <div className="card-title">{item.title}</div>
                  <div className="bar" style={{marginTop:"auto"}}>
                    <div className="fill" style={{width:`${p}%`}}></div>
                  </div>
                  <div className="card-meta">{p}% · {progressForItem(item).toUpperCase()}</div>
                </div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ---------- DETAIL ----------
function DetailScreen({ item, onBack, onAdd, onStats, onHome, onUpdate, onDelete, onEdit, ownerMode, onOpenDonate }) {
  const { play } = useSound();
  if (!item) return null;
  const cat = CATEGORIES[item.category];
  const mode = progressForItem(item);
  const p = pctOf(item);
  const complete = item.completed;

  // Flash badge: fires when saved amount increases after a donation
  const prevSavedRef = useRef(item.saved);
  const [flashAmt, setFlashAmt] = useState(null);
  const [barPulse, setBarPulse] = useState(false);
  useEffect(() => {
    const prev = prevSavedRef.current;
    const curr = Number(item.saved || 0);
    if (curr > Number(prev || 0)) {
      const delta = curr - Number(prev || 0);
      setFlashAmt(delta);
      setBarPulse(true);
      const t1 = setTimeout(() => setFlashAmt(null), 2400);
      const t2 = setTimeout(() => setBarPulse(false), 900);
      prevSavedRef.current = curr;
      return () => { clearTimeout(t1); clearTimeout(t2); };
    }
    prevSavedRef.current = curr;
  }, [item.saved]);
  const donors = item.donors || [];
  const donorTotal = donors.reduce((s, d) => s + d.amount, 0);

  const update = (patch) => onUpdate(item.id, patch);

  const adjust = (delta) => {
    if (mode === "savings") {
      const next = Math.max(0, Math.min(Number(item.target||0), Number(item.saved||0) + delta));
      update({ saved: next });
      play(delta > 0 ? "ok" : "tick");
    } else if (mode === "hours") {
      const next = Math.max(0, Number(item.hours||0) + delta);
      update({ hours: next });
      play(delta > 0 ? "ok" : "tick");
    } else if (mode === "manual") {
      const next = Math.max(0, Math.min(100, Number(item.pct||0) + delta));
      update({ pct: next });
      play("tick");
    }
  };

  const toggleCheck = (idx) => {
    const list = [...(item.checklist || [])];
    list[idx] = { ...list[idx], done: !list[idx].done };
    update({ checklist: list });
    play("tick");
  };

  const markComplete = () => {
    play("trophy");
    update({ completed: true, completedAt: Date.now(), pct: 100, saved: item.target, hours: item.targetHours });
  };
  const reopen = () => { play("back"); update({ completed: false, completedAt: null }); };

  const fmtDate = (t) => t ? new Date(t).toLocaleDateString(undefined, { month:"short", day:"numeric", year:"numeric" }) : "—";

  return (
    <div className="screen">
      <ChromeBar onHome={onHome} onAdd={onAdd} onStats={onStats} ownerMode={ownerMode} />
      <div className="detail">
        <div className="detail-cover">
          <div className="cover-cat">▮ {cat.label} · {cat.short}-{String(item.id).slice(-4).toUpperCase()}</div>
          {item.cover
            ? <img src={item.cover} className="cover-photo" alt={item.title} />
            : <div className="cover-glyph">{cat.glyph}</div>
          }
          <div className="cover-id">DISC 01 / SIDE A</div>
        </div>
        <div className="detail-info">
          <div className="detail-cat">{cat.label} · {complete ? "GRANTED" : "ACTIVE"}{!ownerMode && " · 🔒 VIEW ONLY"}</div>
          <h2 className="detail-title">{item.title}</h2>
          {item.desc && <div className="detail-desc">{item.desc}</div>}

          <div className="detail-dates">
            <span>▮ Added {fmtDate(item.createdAt)}</span>
            {complete && <span>· Granted {fmtDate(item.completedAt)}</span>}
            {donors.length > 0 && <span>· {donors.length} backer{donors.length===1?"":"s"}</span>}
          </div>

          {item.sourceUrl && (
            <div className="detail-source">
              <span className="detail-source-label">▮ SOURCE</span>
              <a
                className="detail-source-link"
                href={item.sourceUrl}
                target="_blank"
                rel="noopener noreferrer"
                onClick={e => e.stopPropagation()}
              >
                {(() => { try { return new URL(item.sourceUrl).hostname.replace("www.",""); } catch(_){ return item.sourceUrl; }})()}
                {" ↗"}
              </a>
            </div>
          )}

          <div className="progress-block">
            <div className="progress-head">
              <div className="label">Progress · {mode}</div>
              <div className="pct">{p}%</div>
            </div>
            <div className={`bar ${complete?"complete":""}`}><div className={`fill${barPulse?" fill-pulse":""}`} style={{width:`${p}%`}}></div></div>

            {mode === "savings" && (
              <>
                <div className="progress-readout">
                  ${Number(item.saved||0).toLocaleString()} <span className="dim">/ ${Number(item.target||0).toLocaleString()} saved</span>
                  {donorTotal > 0 && <span className="dim"> · ${donorTotal.toLocaleString()} from backers</span>}
                  {flashAmt && <span className="flash-delta">▲ +${Number(flashAmt).toLocaleString()}</span>}
                </div>
                {ownerMode && !complete && (
                  <div className="progress-controls">
                    <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>adjust(-50)}>−$50</button>
                    <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>adjust(-10)}>−$10</button>
                    <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(10)}>+$10</button>
                    <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(50)}>+$50</button>
                    <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(100)}>+$100</button>
                  </div>
                )}
                {!complete && (
                  <div className="donate-cta">
                    <button className="btn donate" onMouseEnter={()=>play("hover")}
                      onClick={()=>{play("select"); onOpenDonate(item);}}>
                      ♥ {ownerMode ? "Test Donation Flow" : "Chip In"}
                    </button>
                    <span className="donate-note">Secure payment · powered by WishPay™</span>
                  </div>
                )}
              </>
            )}

            {mode === "hours" && ownerMode && (
              <>
                <div className="progress-readout">
                  {item.hours || 0}h <span className="dim">/ {item.targetHours || 0}h logged</span>
                </div>
                <div className="progress-controls">
                  <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>adjust(-1)}>−1h</button>
                  <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(1)}>+1h</button>
                  <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(5)}>+5h</button>
                  <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(10)}>+10h</button>
                </div>
              </>
            )}
            {mode === "hours" && !ownerMode && (
              <div className="progress-readout">
                {item.hours || 0}h <span className="dim">/ {item.targetHours || 0}h logged</span>
              </div>
            )}

            {mode === "manual" && ownerMode && (
              <div className="progress-controls">
                <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>adjust(-25)}>−25%</button>
                <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>adjust(-10)}>−10%</button>
                <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(10)}>+10%</button>
                <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={()=>adjust(25)}>+25%</button>
              </div>
            )}

            {mode === "checklist" && (
              <ul className="check-list" style={{marginTop:10}}>
                {(item.checklist || []).map((step, i) => (
                  <li key={i} className={`${step.done?"done":""} ${ownerMode?"":"readonly"}`}
                    onMouseEnter={()=>ownerMode && play("hover")}
                    onClick={()=>ownerMode && toggleCheck(i)}>
                    <span className="box">{step.done?"✓":""}</span>
                    {step.text}
                  </li>
                ))}
                {!(item.checklist || []).length && <li style={{color:"var(--chrome-3)"}}>No steps defined.</li>}
              </ul>
            )}
          </div>

          {donors.length > 0 && (
            <div className="progress-block">
              <div className="progress-head">
                <div className="label">▮ Backers Ledger</div>
                <div className="pct" style={{fontSize:16}}>{donors.length} · ${donorTotal.toLocaleString()}</div>
              </div>
              <ul className="donor-list">
                {[...donors].sort((a,b)=>b.ts-a.ts).map((d, i) => (
                  <li key={d.txnId || i} className="donor-row">
                    <div className="dn-amt">${d.amount.toLocaleString()}</div>
                    <div className="dn-mid">
                      <div className="dn-name">{d.name}</div>
                      {d.message && <div className="dn-msg">"{d.message}"</div>}
                    </div>
                    <div className="dn-meta">
                      <div className="dn-date">{fmtDate(d.ts)}</div>
                      <div className="dn-txn">{d.txnId}</div>
                    </div>
                  </li>
                ))}
              </ul>
            </div>
          )}

          {item.notes && (
            <div className="progress-block">
              <div className="progress-head"><div className="label">Notes</div></div>
              <div className="detail-desc" style={{whiteSpace:"pre-wrap"}}>{item.notes}</div>
            </div>
          )}

          <div className="form-actions">
            {ownerMode && (
              <button className="btn danger" onMouseEnter={()=>play("hover")}
                onClick={()=>{ if(confirm("Eject this wish forever?")){ play("bad"); onDelete(item.id); }}}>✕ Delete</button>
            )}
            {ownerMode && (
              <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>{play("select"); onEdit(item.id);}}>✎ Edit</button>
            )}
            <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>{play("back"); onBack();}}>◀ Back</button>
            {ownerMode && (complete
              ? <button className="btn" onMouseEnter={()=>play("hover")} onClick={reopen}>↻ Reopen</button>
              : <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={markComplete}>✦ Grant Wish</button>)}
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- ADD / EDIT ----------
function AddScreen({ existing, onSave, onCancel, onHome, onStats }) {
  const { play } = useSound();
  const isEdit = !!existing;
  const [cat, setCat] = useStateS(existing?.category || "product");
  const [title, setTitle] = useStateS(existing?.title || "");
  const [desc, setDesc] = useStateS(existing?.desc || "");
  const [target, setTarget] = useStateS(existing?.target?.toString() || "");
  const [targetHours, setTargetHours] = useStateS(existing?.targetHours?.toString() || "");
  const [checklist, setChecklist] = useStateS(
    existing?.checklist?.map(s => s.text).join("\n") || ""
  );
  const [notes, setNotes] = useStateS(existing?.notes || "");
  const [cover,      setCover]      = useStateS(existing?.cover || "");
  const [photoProc,  setPhotoProc]  = useStateS(false); // true while resizing

  // Resize + compress image to max 900px, JPEG 0.72 quality.
  // Uses createImageBitmap() so EXIF orientation is auto-applied — fixes
  // sideways portrait photos from iOS cameras.
  const handlePhoto = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    e.target.value = ""; // reset so same file can be re-selected
    setPhotoProc(true);
    try {
      const MAX = 1400; // store at higher res to avoid pixelation in the cover panel
      let bmp;
      if (typeof createImageBitmap !== "undefined") {
        // Modern path: respects EXIF orientation automatically
        bmp = await createImageBitmap(file);
      } else {
        // Fallback for very old browsers
        const dataUrl = await new Promise((res, rej) => {
          const fr = new FileReader();
          fr.onload  = e => res(e.target.result);
          fr.onerror = rej;
          fr.readAsDataURL(file);
        });
        bmp = await new Promise((res, rej) => {
          const img = new Image();
          img.onload  = () => res(img);
          img.onerror = rej;
          img.src = dataUrl;
        });
      }
      const scale  = Math.min(1, MAX / Math.max(bmp.width, bmp.height));
      const canvas = document.createElement("canvas");
      canvas.width  = Math.round(bmp.width  * scale);
      canvas.height = Math.round(bmp.height * scale);
      canvas.getContext("2d").drawImage(bmp, 0, 0, canvas.width, canvas.height);
      if (bmp.close) bmp.close(); // free ImageBitmap memory
      setCover(canvas.toDataURL("image/jpeg", 0.82));
      play("ok");
    } catch (err) {
      console.error("Photo processing failed:", err);
      play("bad");
    } finally {
      setPhotoProc(false);
    }
  };

  const mode = progressForItem({ category: cat });

  const submit = () => {
    if (!title.trim()) { play("bad"); return; }
    play("ok");
    if (isEdit) {
      const patch = {
        category: cat,
        title: title.trim(),
        desc: desc.trim(),
        notes: notes.trim(),
      };
      patch.cover = cover;
      if (mode === "savings") patch.target = Number(target) || 0;
      if (mode === "hours") patch.targetHours = Number(targetHours) || 0;
      if (mode === "checklist") {
        const existingMap = new Map((existing.checklist || []).map(s => [s.text, s.done]));
        patch.checklist = checklist.split("\n").map(s => s.trim()).filter(Boolean)
          .map(t => ({ text: t, done: existingMap.get(t) || false }));
      }
      onSave(existing.id, patch);
    } else {
      const item = {
        id: "w_" + Date.now().toString(36) + Math.random().toString(36).slice(2,5),
        category: cat,
        title: title.trim(),
        desc: desc.trim(),
        notes: notes.trim(),
        cover,
        createdAt: Date.now(),
        completed: false,
        completedAt: null,
        saved: 0, hours: 0, pct: 0,
      };
      if (mode === "savings") item.target = Number(target) || 0;
      if (mode === "hours") item.targetHours = Number(targetHours) || 0;
      if (mode === "checklist") {
        item.checklist = checklist.split("\n").map(s => s.trim()).filter(Boolean).map(t => ({ text: t, done: false }));
      }
      onSave(null, item);
    }
  };

  return (
    <div className="screen">
      <ChromeBar onHome={onHome} onAdd={()=>{}} onStats={onStats} />
      <div className="form-screen">
        <div className="section-head">
          <div className="section-title">{isEdit ? "Edit Wish" : "New Wish"}</div>
          <div className="section-meta">▶ INSERT DISC · {isEdit ? "REWRITE MODE" : "WRITE MODE"}</div>
        </div>

        <div className="field full">
          <label>Category</label>
          <div className="cat-pick">
            {CAT_LIST.map(c => (
              <div key={c.id}
                className={`cat-btn ${cat===c.id?"active":""}`}
                onMouseEnter={()=>play("hover")}
                onClick={()=>{play("tick"); setCat(c.id);}}
              >
                <span className="ico">{c.glyph}</span>
                {c.label}
              </div>
            ))}
          </div>
        </div>

        <div className="form-grid">
          <div className="field full">
            <label>Title</label>
            <input value={title} onChange={e=>setTitle(e.target.value)} placeholder="What do you wish for?" autoFocus />
          </div>
          <div className="field full">
            <label>Description</label>
            <textarea value={desc} onChange={e=>setDesc(e.target.value)} placeholder="Why does it matter?"></textarea>
          </div>

          {mode === "savings" && (
            <div className="field">
              <label>Target Cost ($)</label>
              <input type="number" value={target} onChange={e=>setTarget(e.target.value)} placeholder="0" />
            </div>
          )}
          {mode === "hours" && (
            <div className="field">
              <label>Target Hours</label>
              <input type="number" value={targetHours} onChange={e=>setTargetHours(e.target.value)} placeholder="0" />
            </div>
          )}
          {mode === "checklist" && (
            <div className="field full">
              <label>Steps (one per line)</label>
              <textarea value={checklist} onChange={e=>setChecklist(e.target.value)} placeholder={"Train 5k\nTrain 10k\nTrain half marathon\nRun the marathon"}></textarea>
            </div>
          )}
          {mode === "manual" && (
            <div className="field" style={{gridColumn:"1/-1", color:"var(--chrome-3)", fontFamily:"VT323, monospace", fontSize:14}}>
              ▮ Progress is tracked manually with %. Default mode for media.
            </div>
          )}

          <div className="field full">
            <label>Notes</label>
            <textarea value={notes} onChange={e=>setNotes(e.target.value)} placeholder="Anything else..."></textarea>
          </div>

          {/* ── Cover photo ── */}
          <div className="field full">
            <label>Cover Photo · optional</label>
            {cover ? (
              <div className="cover-upload-preview">
                <img src={cover} className="cup-img" alt="cover preview" />
                <div className="cup-overlay">
                  <span className="cup-badge">▮ VINTAGE FILTER ACTIVE</span>
                  <div className="cup-actions">
                    <label className="btn cup-change" onMouseEnter={()=>play("hover")}>
                      ↺ Change
                      <input type="file" accept="image/*" style={{display:"none"}} onChange={handlePhoto} />
                    </label>
                    <button className="btn cup-remove" onMouseEnter={()=>play("hover")} onClick={()=>{play("back"); setCover("");}}>✕</button>
                  </div>
                </div>
              </div>
            ) : photoProc ? (
              <div className="photo-processing">
                <div className="loading-disc" style={{width:32,height:32}}></div>
                <span>▮ Processing image…</span>
              </div>
            ) : (
              <label className="photo-upload-btn" onMouseEnter={()=>play("hover")}>
                <span className="pub-icon">◎</span>
                <span>Add Cover Photo</span>
                <span className="pub-hint">Tap to choose from gallery or camera</span>
                <input type="file" accept="image/*" style={{display:"none"}} onChange={handlePhoto} />
              </label>
            )}
          </div>
        </div>

        <div className="form-actions">
          <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>{play("back"); onCancel();}}>◀ Cancel</button>
          <button className="btn primary" onMouseEnter={()=>play("hover")} onClick={submit}>✦ {isEdit ? "Save Changes" : "Save Wish"}</button>
        </div>
      </div>
    </div>
  );
}

// ---------- STATS ----------
function StatsScreen({ items, trophies, onHome, onAdd, onExport, onImport, onReset, ownerMode }) {
  const { play } = useSound();
  const total = items.length;
  const completed = items.filter(i => i.completed).length;
  const active = total - completed;
  const avgPct = total ? Math.round(items.reduce((s,i) => s + pctOf(i), 0) / total) : 0;
  const totalSaved = items.reduce((s,i) => s + Number(i.saved||0), 0);
  const totalHours = items.reduce((s,i) => s + Number(i.hours||0), 0);
  const totalBacked = items.reduce((s,i) => s + (i.donors||[]).reduce((a,d)=>a+d.amount,0), 0);
  const backerCount = items.reduce((s,i) => s + (i.donors||[]).length, 0);
  const trophyCount = trophies.filter(t => t.unlocked).length;

  const byCat = CAT_LIST.map(c => {
    const list = items.filter(i => i.category === c.id);
    const avg = list.length ? Math.round(list.reduce((s,i)=>s+pctOf(i),0)/list.length) : 0;
    return { ...c, count: list.length, avg };
  });

  return (
    <div className="screen">
      <ChromeBar onHome={onHome} onAdd={onAdd} onStats={()=>{}} ownerMode={ownerMode} />
      <div className="stats">
        <div className="panel">
          <div className="panel-title">▮ Lifetime Progress</div>
          <div className="big-num">{avgPct}<span className="unit">%</span></div>
          <div style={{fontFamily:"VT323, monospace", color:"var(--chrome-3)", letterSpacing:2, marginTop:6, fontSize:12}}>
            AVERAGE COMPLETION ACROSS ALL WISHES
          </div>
          <div className="kpi-grid">
            <div className="kpi"><div className="v">{total}</div><div className="l">Total</div></div>
            <div className="kpi"><div className="v">{completed}</div><div className="l">Granted</div></div>
            <div className="kpi"><div className="v">{active}</div><div className="l">Active</div></div>
            <div className="kpi"><div className="v">${totalSaved.toLocaleString()}</div><div className="l">Saved</div></div>
            <div className="kpi"><div className="v">{totalHours}h</div><div className="l">Logged</div></div>
            <div className="kpi"><div className="v">{trophyCount}/{trophies.length}</div><div className="l">Trophies</div></div>
            <div className="kpi"><div className="v">${totalBacked.toLocaleString()}</div><div className="l">From Backers</div></div>
            <div className="kpi"><div className="v">{backerCount}</div><div className="l">Contributions</div></div>
          </div>

          {ownerMode && (
            <div className="data-actions">
              <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>{play("ok"); onExport();}}>↓ Export</button>
              <button className="btn" onMouseEnter={()=>play("hover")} onClick={()=>{play("ok"); onImport();}}>↑ Import</button>
              <button className="btn danger" onMouseEnter={()=>play("hover")} onClick={()=>{play("bad"); onReset();}}>↺ Reset</button>
            </div>
          )}
        </div>

        <div className="panel">
          <div className="panel-title">▮ By Category</div>
          {byCat.map(c => (
            <div key={c.id} className="cat-row">
              <div className="cn">{c.label}</div>
              <div className="cv">{c.count} item{c.count===1?"":"s"} · {c.avg}%</div>
              <div className="bar"><div className="fill" style={{width:`${c.avg}%`}}></div></div>
            </div>
          ))}
        </div>

        <div className="panel">
          <div className="panel-title">▮ Trophy Cabinet</div>
          <div className="trophy-list">
            {trophies.map(t => (
              <div key={t.id} className={`trophy ${t.unlocked?"":"locked"}`}>
                <div className="icon">{t.unlocked ? t.icon : "?"}</div>
                <div>
                  <div className="tn">{t.unlocked ? t.name : "▮ LOCKED"}</div>
                  <div className="td">{t.unlocked ? t.desc : t.hint}</div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { HubScreen, GridScreen, DetailScreen, AddScreen, StatsScreen, SignInModal, DonateModal });

// ---------- SIGN-IN MODAL (PIN gate) ----------
// ---------- SIGN-IN MODAL ----------
// PIN is verified server-side via /api/verify-pin so the actual PIN value
// never appears in client JS. Includes lockout after repeated failures.
const MAX_ATTEMPTS  = 5;   // failures before first lockout
const LOCKOUT_SECS  = [60, 300]; // 1 min after 5 fails, 5 min after 8 fails

function SignInModal({ onClose, onSuccess }) {
  const { play } = useSound();
  const [pin,        setPin]        = useStateS("");
  const [err,        setErr]        = useStateS("");
  const [shake,      setShake]      = useStateS(false);
  const [busy,       setBusy]       = useStateS(false);
  const [attempts,   setAttempts]   = useStateS(0);
  const [lockedUntil,setLockedUntil]= useStateS(0);
  const [countdown,  setCountdown]  = useStateS(0);
  const inputRef = useRef(null);

  // Countdown ticker while locked
  useEffect(() => {
    if (!lockedUntil) return;
    const tick = () => {
      const remaining = Math.ceil((lockedUntil - Date.now()) / 1000);
      if (remaining <= 0) { setLockedUntil(0); setCountdown(0); setErr(""); }
      else setCountdown(remaining);
    };
    tick();
    const t = setInterval(tick, 1000);
    return () => clearInterval(t);
  }, [lockedUntil]);

  const isLocked = Date.now() < lockedUntil;

  const addDigit = (d) => {
    if (isLocked || busy) return;
    play("tick");
    setErr("");
    setPin(p => p.length < 4 ? p + d : p);
  };

  const delDigit = () => {
    if (isLocked || busy) return;
    play("back");
    setPin(p => p.slice(0, -1));
  };

  const submit = async () => {
    if (pin.length < 4 || busy || isLocked) return;
    setBusy(true);
    setErr("");
    try {
      const res = await fetch("/api/verify-pin", {
        method:  "POST",
        headers: { "Content-Type": "application/json" },
        body:    JSON.stringify({ pin }),
      });
      const data = await res.json().catch(() => ({}));
      if (res.ok && data.ok) {
        play("trophy");
        onSuccess(data.token);
      } else {
        play("bad");
        const newAttempts = attempts + 1;
        setAttempts(newAttempts);
        setPin("");
        setShake(true);
        setTimeout(() => setShake(false), 500);
        if (newAttempts >= MAX_ATTEMPTS + 3) {
          setLockedUntil(Date.now() + LOCKOUT_SECS[1] * 1000);
          setErr(`TOO MANY ATTEMPTS — LOCKED ${LOCKOUT_SECS[1]/60} MIN`);
        } else if (newAttempts >= MAX_ATTEMPTS) {
          setLockedUntil(Date.now() + LOCKOUT_SECS[0] * 1000);
          setErr(`TOO MANY ATTEMPTS — LOCKED ${LOCKOUT_SECS[0]} SEC`);
        } else {
          const remaining = MAX_ATTEMPTS - newAttempts;
          setErr(`ACCESS DENIED · ${remaining} attempt${remaining===1?"":"s"} left`);
        }
      }
    } catch (e) {
      play("bad");
      setErr("Cannot reach server. Use vercel dev locally.");
    } finally {
      setBusy(false);
    }
  };

  // Also accept keyboard input
  useEffect(() => {
    const onKey = (e) => {
      if (e.key >= "0" && e.key <= "9") addDigit(e.key);
      else if (e.key === "Backspace")    delDigit();
      else if (e.key === "Enter")        submit();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [pin, isLocked, busy, attempts, lockedUntil]);

  const numpad = [
    ["1","2","3"],
    ["4","5","6"],
    ["7","8","9"],
    ["⌫","0","✦"],
  ];

  return (
    <div className="modal-veil" onClick={!busy ? onClose : null}>
      <div className={`modal floppy ${shake?"shake":""}`} onClick={(e)=>e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-title">▮ OWNER SIGN-IN</div>
          <button className="modal-x" onClick={()=>{play("back"); onClose();}} disabled={busy}>✕</button>
        </div>
        <div className="modal-body">
          <div className="floppy-art">
            <div className="floppy-disc">
              <div className="fl-label">
                <div className="fl-tt">PERSONAL</div>
                <div className="fl-st">DREAMTRAK · OWNER KEY</div>
              </div>
              <div className="fl-shut"></div>
            </div>
          </div>
          <div className="modal-prompt">
            {isLocked
              ? `⛔ Locked — try again in ${countdown}s`
              : "Enter your owner PIN to unlock editing."
            }
          </div>

          <div className="pin-row">
            {[0,1,2,3].map(i => (
              <div key={i} className={`pin-cell ${pin.length>i?"filled":""} ${isLocked?"locked":""}`}>
                {pin.length>i ? "●" : ""}
              </div>
            ))}
          </div>

          {err && <div className={`pin-err ${isLocked?"pin-err-lock":""}`}>▮ {err}</div>}

          <div className={`pin-numpad ${isLocked||busy?"pin-numpad-disabled":""}`}>
            {numpad.map((row, ri) => (
              <div key={ri} className="np-row">
                {row.map(k => (
                  <button key={k} className={`np-btn ${k==="✦"?"np-submit":""} ${k==="⌫"?"np-del":""}`}
                    disabled={isLocked || busy || (k==="✦" && pin.length<4)}
                    onClick={() => {
                      if (k === "⌫") delDigit();
                      else if (k === "✦") submit();
                      else addDigit(k);
                    }}>
                    {busy && k === "✦" ? "…" : k}
                  </button>
                ))}
              </div>
            ))}
          </div>
        </div>
        <div className="modal-actions">
          <button className="btn" onClick={()=>{play("back"); onClose();}} disabled={busy}>◀ Cancel</button>
        </div>
      </div>
    </div>
  );
}

// ---------- DONATE MODAL ----------
function DonateModal({ item, onClose, onSuccess }) {
  const { play } = useSound();
  const target = Number(item.target||0);
  const saved = Number(item.saved||0);
  const remaining = Math.max(0, target - saved);
  const presets = [10, 25, 50, 100].filter(v => !target || v <= remaining);
  const [amount, setAmount] = useStateS(presets[1] || presets[0] || 25);
  const [name, setName] = useStateS("");
  const [message, setMessage] = useStateS("");
  // Legacy mock-mode card fields (shown when Stripe isn't configured)
  const [card, setCard] = useStateS("4242 4242 4242 4242");
  const [exp, setExp] = useStateS("12/29");
  const [cvc, setCvc] = useStateS("123");
  const [phase, setPhase] = useStateS("form"); // form | processing | success | error
  const [result, setResult] = useStateS(null);
  const [err, setErr] = useStateS("");

  // Stripe Elements refs
  const cardNumberRef  = useRef(null);
  const cardExpiryRef  = useRef(null);
  const cardCvcRef     = useRef(null);
  const cardElRef      = useRef(null); // the Stripe CardNumberElement instance

  const isLive = WishPay.live;

  // Mount Stripe Elements when in live mode
  useEffect(() => {
    if (!isLive || !cardNumberRef.current) return;

    const stripe   = window.stripeInstance;
    const elements = stripe.elements();

    const elStyle = {
      base: {
        fontFamily:    "'VT323', monospace",
        fontSize:      "18px",
        color:         "#e8e0cc",
        letterSpacing: "0.04em",
        "::placeholder": { color: "#4a5a78" },
      },
      invalid: { color: "#ff4d6d" },
    };

    const cardNumber = elements.create("cardNumber", { style: elStyle, showIcon: true });
    const cardExpiry = elements.create("cardExpiry", { style: elStyle });
    const cardCvc    = elements.create("cardCvc",    { style: elStyle });

    cardNumber.mount(cardNumberRef.current);
    cardExpiry.mount(cardExpiryRef.current);
    cardCvc.mount(cardCvcRef.current);

    cardElRef.current = cardNumber;

    cardNumber.on("change", e => { if (e.error) setErr(e.error.message); else setErr(""); });

    return () => { cardNumber.destroy(); cardExpiry.destroy(); cardCvc.destroy(); };
  }, [isLive]);

  const charge = async () => {
    if (!amount || Number(amount) <= 0) { play("bad"); setErr("Enter an amount."); return; }
    // Mock mode: basic card format check
    if (!isLive && !card.replace(/\s/g,"").match(/^\d{12,19}$/)) { play("bad"); setErr("Invalid card number."); return; }
    setErr("");
    play("tick");

    // ── Live mode: tokenise the card BEFORE changing phase ───
    // The Stripe Element divs unmount when phase changes to "processing",
    // so we must extract a paymentMethodId while they are still in the DOM.
    let paymentMethodId = null;
    if (isLive) {
      const { error: pmErr, paymentMethod } = await window.stripeInstance.createPaymentMethod({
        type:             "card",
        card:             cardElRef.current,
        billing_details:  { name: (name || "Anonymous").trim() || "Anonymous" },
      });
      if (pmErr) { play("bad"); setErr(pmErr.message || "Card error."); return; }
      paymentMethodId = paymentMethod.id;
    }

    setPhase("processing");
    try {
      const txn = await WishPay.charge({
        amount:          Number(amount),
        name,
        message,
        wishId:          item.id,
        paymentMethodId, // pm_xxx string — survives element unmount
      });
      play("trophy");
      setResult(txn);
      setPhase("success");
      // NOTE: onSuccess is intentionally NOT called here.
      // It's called when the user clicks "Done" so the bar animation
      // plays in full view on the DetailScreen, not hidden behind the modal.
    } catch (e) {
      play("bad");
      setErr(e.message || "Payment failed.");
      setPhase("error");
    }
  };

  const cat = CATEGORIES[item.category];

  return (
    <div className="modal-veil" onClick={phase==="processing"?null:onClose}>
      <div className="modal donate" onClick={(e)=>e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-title">♥ CHIP IN · {item.title.toUpperCase()}</div>
          <button className="modal-x" onClick={()=>{play("back"); onClose();}} disabled={phase==="processing"}>✕</button>
        </div>

        {phase === "form" || phase === "error" ? (
          <>
            <div className="modal-body donate-body">
              <div className="don-summary">
                <div className="ds-glyph">{cat.glyph}</div>
                <div>
                  <div className="ds-cat">{cat.label}</div>
                  <div className="ds-progress">
                    ${saved.toLocaleString()} / ${target.toLocaleString()} · ${remaining.toLocaleString()} to go
                  </div>
                </div>
              </div>

              <div className="field full">
                <label>Amount ($)</label>
                <div className="amount-row">
                  {presets.map(v => (
                    <button key={v}
                      className={`amount-btn ${Number(amount)===v?"active":""}`}
                      onMouseEnter={()=>play("hover")}
                      onClick={()=>{play("tick"); setAmount(v);}}>
                      ${v}
                    </button>
                  ))}
                  <input type="number" className="amount-custom" value={amount}
                    onChange={(e)=>setAmount(e.target.value)} placeholder="Other" min="1" />
                </div>
              </div>

              <div className="form-grid">
                <div className="field full">
                  <label>Name shown to owner</label>
                  <input value={name} onChange={(e)=>setName(e.target.value)} placeholder="Your name (or 'Anonymous')" />
                </div>
                <div className="field full">
                  <label>Message (optional)</label>
                  <textarea value={message} onChange={(e)=>setMessage(e.target.value.slice(0,140))}
                    placeholder="A note of encouragement..." style={{minHeight:50}}></textarea>
                  <div className="char-count">{message.length}/140</div>
                </div>
                <div className="field full">
                  <label>Card Number</label>
                  {isLive
                    ? <div ref={cardNumberRef} className="stripe-el" />
                    : <input value={card} onChange={(e)=>setCard(e.target.value)} placeholder="4242 4242 4242 4242" />
                  }
                </div>
                <div className="field">
                  <label>Expiry</label>
                  {isLive
                    ? <div ref={cardExpiryRef} className="stripe-el" />
                    : <input value={exp} onChange={(e)=>setExp(e.target.value)} placeholder="MM/YY" />
                  }
                </div>
                <div className="field">
                  <label>CVC</label>
                  {isLive
                    ? <div ref={cardCvcRef} className="stripe-el" />
                    : <input value={cvc} onChange={(e)=>setCvc(e.target.value)} placeholder="123" />
                  }
                </div>
              </div>

              {err && <div className="pin-err">▮ {err}</div>}
              <div className="pay-foot">
                {isLive
                  ? "▮ Secured by Stripe · WishPay™ · Your card details are encrypted"
                  : "▮ Demo mode · WishPay™ Sandbox · No real card will be charged"
                }
              </div>
            </div>
            <div className="modal-actions">
              <button className="btn" onClick={()=>{play("back"); onClose();}}>◀ Cancel</button>
              <button className="btn primary" onClick={charge}>♥ Send ${Number(amount)||0}</button>
            </div>
          </>
        ) : phase === "processing" ? (
          <div className="modal-body donate-body" style={{minHeight:280}}>
            <div className="processing">
              <div className="loading-disc"></div>
              <div className="loading-text">Authorizing</div>
              <div className="proc-detail">▮ Contacting WishPay™ · {item.title}</div>
            </div>
          </div>
        ) : (
          <>
            <div className="modal-body donate-body">
              <div className="success-card">
                <div className="sc-stamp">✦ APPROVED</div>
                <div className="sc-amt">${result.amount.toLocaleString()}</div>
                <div className="sc-row"><span>FROM</span><b>{result.name}</b></div>
                <div className="sc-row"><span>WISH</span><b>{item.title}</b></div>
                <div className="sc-row"><span>TXN</span><b className="mono">{result.txnId}</b></div>
                <div className="sc-row"><span>METHOD</span><b className="mono">VISA •••• 4242</b></div>
                {result.message && <div className="sc-msg">"{result.message}"</div>}
                <div className="sc-foot">Thank you. The owner has been notified.</div>
              </div>
            </div>
            <div className="modal-actions">
              <button className="btn primary" onClick={()=>{play("ok"); onSuccess(result); onClose();}}>✦ Done</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}
