const pageLabel = document.getElementById("pageLabel"); const statusText = document.getElementById("status"); const slideImage = document.getElementById("slideImage"); const imageFallback = document.getElementById("imageFallback"); const slideTitle = document.getElementById("slideTitle"); const slideScript = document.getElementById("slideScript"); const prevBtn = document.getElementById("prevBtn"); const playBtn = document.getElementById("playBtn"); const stopBtn = document.getElementById("stopBtn"); const nextBtn = document.getElementById("nextBtn"); const player = document.getElementById("player"); const workspace = document.getElementById("workspace"); const slidePane = document.getElementById("slidePane"); const scriptPane = document.getElementById("scriptPane"); const resizer = document.getElementById("resizer"); let slides = []; let currentIndex = 0; let isPlaying = false; let isPaused = false; let isDragging = false; let pointerId = null; function setStatus(text) { statusText.textContent = text; } function updatePlayButton() { if (isPlaying && !isPaused) { playBtn.textContent = "⏸ Pause"; return; } if (isPlaying && isPaused) { playBtn.textContent = "▶ Resume"; return; } playBtn.textContent = "▶ Play"; } function updateButtons() { prevBtn.disabled = currentIndex <= 0; nextBtn.disabled = currentIndex >= slides.length - 1; } function renderSlide(index) { if (!slides.length) { pageLabel.textContent = "No slides"; slideTitle.textContent = ""; slideScript.textContent = "No script data was loaded."; setStatus("No slides"); updateButtons(); return; } if (index < 0) index = 0; if (index >= slides.length) index = slides.length - 1; currentIndex = index; const slide = slides[currentIndex]; pageLabel.textContent = `Slide ${currentIndex} / ${slides.length - 1}`; slideTitle.textContent = slide.title || `Slide ${currentIndex}`; slideScript.textContent = slide.speech || ""; imageFallback.hidden = true; slideImage.hidden = false; slideImage.onerror = () => { slideImage.hidden = true; imageFallback.hidden = false; }; slideImage.src = `${slide.image}?t=${Date.now()}`; updateButtons(); if (!isPlaying) { setStatus("Ready"); } } async function loadSlides() { try { const response = await fetch("/api/slides"); if (!response.ok) { throw new Error(`Failed to load slides: ${response.status}`); } slides = await response.json(); if (!Array.isArray(slides) || slides.length === 0) { throw new Error("No slides found. Check scripts.txt."); } renderSlide(0); setStatus(`Ready · ${slides.length} slides`); updatePlayButton(); } catch (error) { console.error(error); pageLabel.textContent = "Load failed"; slideTitle.textContent = "Error"; slideScript.textContent = String(error.message || error); setStatus("Error"); } } function stopAudio(status = "Stopped") { isPlaying = false; isPaused = false; player.pause(); player.removeAttribute("src"); player.load(); setStatus(status); updatePlayButton(); } function playCurrent() { if (!slides.length) return; isPlaying = true; isPaused = false; const slide = slides[currentIndex]; setStatus(`Reading ${currentIndex + 1}/${slides.length}`); player.pause(); player.currentTime = 0; player.src = `${slide.audio}?t=${Date.now()}`; updatePlayButton(); player.play().catch(error => { console.error(error); stopAudio("Audio play failed"); }); } function goToSlide(index, keepPlaying = false) { const wasPlaying = (isPlaying && !isPaused) || keepPlaying; player.pause(); player.currentTime = 0; renderSlide(index); if (wasPlaying) { playCurrent(); } else { isPlaying = false; isPaused = false; updatePlayButton(); } } prevBtn.addEventListener("click", () => { if (currentIndex <= 0) return; goToSlide(currentIndex - 1, isPlaying && !isPaused); }); nextBtn.addEventListener("click", () => { if (currentIndex >= slides.length - 1) return; goToSlide(currentIndex + 1, isPlaying && !isPaused); }); playBtn.addEventListener("click", () => { if (!isPlaying) { playCurrent(); return; } if (isPlaying && !isPaused) { player.pause(); isPaused = true; setStatus("Paused"); updatePlayButton(); return; } if (isPlaying && isPaused) { player.play() .then(() => { isPaused = false; setStatus( `Reading ${currentIndex + 1}/${slides.length}` ); updatePlayButton(); }) .catch(error => { console.error(error); stopAudio( "Audio play failed" ); }); } }); stopBtn.addEventListener("click", () => { stopAudio("Stopped"); }); player.addEventListener("ended", () => { if (!isPlaying) return; if (isPaused) return; if (currentIndex < slides.length - 1) { goToSlide( currentIndex + 1, true ); } else { stopAudio( "Finished" ); } }); document.addEventListener("keydown", event => { if (event.key === "ArrowLeft") { prevBtn.click(); } if (event.key === "ArrowRight") { nextBtn.click(); } if (event.key === " ") { event.preventDefault(); playBtn.click(); } }); function startResize(event) { if (window.innerWidth <= 900) return; isDragging = true; pointerId = event.pointerId; document.body.classList.add( "dragging" ); try { resizer.setPointerCapture( pointerId ); } catch (error) { // ignore } } function resizePanes(clientX) { if (!isDragging) return; if (window.innerWidth <= 900) return; const rect = workspace.getBoundingClientRect(); const x = clientX - rect.left; const percent = (x / rect.width) * 100; const scriptWidth = Math.max( 18, Math.min( 58, percent ) ); const slideWidth = 100 - scriptWidth; scriptPane.style.flexBasis = `${scriptWidth}%`; scriptPane.style.width = `${scriptWidth}%`; slidePane.style.flexBasis = `${slideWidth}%`; } function stopResize() { if (!isDragging) return; isDragging = false; pointerId = null; document.body.classList.remove( "dragging" ); } resizer.addEventListener( "pointerdown", event => { startResize(event); } ); window.addEventListener( "pointermove", event => { resizePanes(event.clientX); } ); window.addEventListener( "pointerup", () => { stopResize(); } ); window.addEventListener( "pointercancel", () => { stopResize(); } ); window.addEventListener( "resize", () => { if ( window.innerWidth <= 900 ) { slidePane.style.flexBasis = ""; scriptPane.style.flexBasis = ""; scriptPane.style.width = ""; stopResize(); } } ); loadSlides();