Landing page: testimonials slider, how-it-works page, 25 TTS voices

- Add testimonial slider with 8 fake caller reviews
- Add how-it-works page with visual architecture diagram
- Expand voice pools: Inworld 25 voices (14M/11F), ElevenLabs 22 (14M/8F)
- Voice pools auto-switch when TTS provider changes
- Add cover art locally, update cache-busted image refs
- Add "More from Luke" footer links (MMG, prints, YouTube)
- Ad channel configurable in settings UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 01:34:30 -07:00
parent f654a5cbb1
commit bd6c8ccbab
9 changed files with 926 additions and 23 deletions

View File

@@ -223,5 +223,83 @@ playerProgress.addEventListener('click', (e) => {
}
});
// Testimonials Slider
function initTestimonials() {
const track = document.getElementById('testimonials-track');
const dotsContainer = document.getElementById('testimonials-dots');
const cards = track.querySelectorAll('.testimonial-card');
if (!cards.length) return;
let currentIndex = 0;
let autoplayTimer = null;
const maxIndex = () => Math.max(0, cards.length - 1);
function buildDots() {
dotsContainer.innerHTML = '';
for (let i = 0; i < cards.length; i++) {
const dot = document.createElement('button');
dot.className = 'testimonial-dot' + (i === currentIndex ? ' active' : '');
dot.setAttribute('aria-label', `Testimonial ${i + 1}`);
dot.addEventListener('click', () => goTo(i));
dotsContainer.appendChild(dot);
}
}
function updatePosition() {
const cardWidth = cards[0].offsetWidth;
track.style.transform = `translateX(-${currentIndex * cardWidth}px)`;
dotsContainer.querySelectorAll('.testimonial-dot').forEach((d, i) => {
d.classList.toggle('active', i === currentIndex);
});
}
function goTo(index) {
currentIndex = Math.max(0, Math.min(index, maxIndex()));
updatePosition();
resetAutoplay();
}
function next() {
goTo(currentIndex >= maxIndex() ? 0 : currentIndex + 1);
}
function resetAutoplay() {
clearInterval(autoplayTimer);
autoplayTimer = setInterval(next, 10000);
}
// Touch/swipe support
let touchStartX = 0;
let touchDelta = 0;
track.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
touchDelta = 0;
clearInterval(autoplayTimer);
}, { passive: true });
track.addEventListener('touchmove', (e) => {
touchDelta = e.touches[0].clientX - touchStartX;
}, { passive: true });
track.addEventListener('touchend', () => {
if (Math.abs(touchDelta) > 50) {
touchDelta < 0 ? goTo(currentIndex + 1) : goTo(currentIndex - 1);
}
resetAutoplay();
});
// Recalculate on resize
window.addEventListener('resize', () => {
if (currentIndex > maxIndex()) currentIndex = maxIndex();
buildDots();
updatePosition();
});
buildDots();
updatePosition();
resetAutoplay();
}
// Init
fetchEpisodes();
initTestimonials();