// Slides tab โ€” PowerPoint-style viewer with prev/next/play controls. // Shows uploaded slide images when present; otherwise the built-in demo deck. function SlidesTab({ slides, images, deckName }){ const uploaded = Array.isArray(images) && images.length > 0; const count = uploaded ? images.length : slides.length; const [i, setI] = React.useState(() => { const s = parseInt(localStorage.getItem('cwa-slide')||'0',10); return isNaN(s) ? 0 : Math.min(Math.max(s,0), count-1); }); const [playing, setPlaying] = React.useState(false); React.useEffect(()=>{ if(i>count-1) setI(0); }, [count]); React.useEffect(()=>{ localStorage.setItem('cwa-slide', String(i)); }, [i]); React.useEffect(()=>{ if(!playing) return; const t = setTimeout(()=> setI(p => (p+1) % count), 4000); return ()=> clearTimeout(t); }, [playing, i, count]); // keyboard: โ† / โ†’ navigate, Space advances React.useEffect(()=>{ const onKey = (e)=>{ if(/input|textarea|select/i.test(e.target.tagName) || e.target.isContentEditable) return; if(e.key==='ArrowRight' || e.key==='PageDown'){ e.preventDefault(); setI(p=>Math.min(count-1,p+1)); } else if(e.key==='ArrowLeft' || e.key==='PageUp'){ e.preventDefault(); setI(p=>Math.max(0,p-1)); } else if(e.key===' '){ e.preventDefault(); setI(p=>Math.min(count-1,p+1)); } }; window.addEventListener('keydown', onKey); return ()=> window.removeEventListener('keydown', onKey); }, [count]); const go = (n)=> setI(Math.min(Math.max(n,0), count-1)); const s = uploaded ? null : (slides[i] || {}); function renderSlide(s){ if(s.kind==='title') return (
{s.kicker &&
{s.kicker}
}
{s.title}
{s.sub &&
{s.sub}
}
); if(s.kind==='verse') return (
“{s.body}”
{s.ref}
); if(s.kind==='section') return (
{s.title}
{s.sub &&
{s.sub}
}
); if(s.kind==='points') return (
{s.title}
{s.items.map((it,n)=>(
{n+1} {it}
))}
); return null; } return (
{deckName}  ยท  {i+1} / {count}
{uploaded ? {`Slide : renderSlide(s)}
{i+1} of {count}
); } window.SlidesTab = SlidesTab;