' +
'
' + story.name + '';
item.addEventListener('click', function() { openStory(idx); });
item.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openStory(idx); }
});
strip.appendChild(item);
});
}
// --- Open viewer ---
function openStory(index) {
if (index < 0 || index >= STORIES.length) return;
currentStoryIndex = index;
currentSlideIndex = 0;
var story = STORIES[index];
headerAvatar.src = story.avatar;
headerName.textContent = story.name;
headerTime.textContent = story.time || '';
renderProgressBars(story.slides.length);
renderSlides(story.slides);
showSlide(0);
viewer.classList.add('is-active');
document.body.style.overflow = 'hidden';
startTimer();
}
function closeViewer() {
stopTimer();
viewer.classList.remove('is-active');
document.body.style.overflow = '';
// Mark as seen
if (currentStoryIndex >= 0) {
STORIES[currentStoryIndex].seen = true;
renderStrip();
}
currentStoryIndex = -1;
currentSlideIndex = 0;
slidesContainer.innerHTML = '';
progressContainer.innerHTML = '';
}
// --- Progress bars ---
function renderProgressBars(count) {
progressContainer.innerHTML = '';
for (var i = 0; i < count; i++) {
var bar = document.createElement('div');
bar.className = 'hp-stories__progress-bar';
bar.innerHTML = '
';
progressContainer.appendChild(bar);
}
}
function updateProgressBars() {
var bars = progressContainer.querySelectorAll('.hp-stories__progress-bar');
bars.forEach(function(bar, i) {
var fill = bar.querySelector('.hp-stories__progress-fill');
bar.classList.remove('hp-stories__progress-bar--done');
fill.style.width = '0%';
if (i < currentSlideIndex) {
bar.classList.add('hp-stories__progress-bar--done');
fill.style.width = '100%';
}
});
}
// --- Slides ---
function renderSlides(slides) {
slidesContainer.innerHTML = '';
slides.forEach(function(slide, i) {
var el = document.createElement('div');
el.className = 'hp-stories__slide';
var textHTML = slide.text
? '
'
: '';
el.innerHTML =
'

' +
textHTML;
slidesContainer.appendChild(el);
});
}
function showSlide(index) {
var story = STORIES[currentStoryIndex];
if (!story) return;
if (index < 0) {
// Go to previous story
if (currentStoryIndex > 0) {
openStory(currentStoryIndex - 1);
} else {
currentSlideIndex = 0;
showSlide(0);
}
return;
}
if (index >= story.slides.length) {
// Go to next story
if (currentStoryIndex < STORIES.length - 1) {
openStory(currentStoryIndex + 1);
} else {
closeViewer();
}
return;
}
currentSlideIndex = index;
var slideEls = slidesContainer.querySelectorAll('.hp-stories__slide');
slideEls.forEach(function(el, i) {
el.classList.toggle('is-active', i === index);
});
updateProgressBars();
startTimer();
}
// --- Timer ---
function startTimer() {
stopTimer();
isPaused = false;
progressStart = Date.now();
var fillEl = progressContainer.querySelectorAll('.hp-stories__progress-bar')[currentSlideIndex];
if (!fillEl) return;
var fill = fillEl.querySelector('.hp-stories__progress-fill');
progressTimer = function tick() {
if (isPaused) { progressTimer = requestAnimationFrame(tick); return; }
var elapsed = Date.now() - progressStart;
var pct = Math.min((elapsed / SLIDE_DURATION) * 100, 100);
fill.style.width = pct + '%';
if (pct < 100) {
progressTimer = requestAnimationFrame(tick);
}
};
progressTimer = requestAnimationFrame(progressTimer);
timer = setTimeout(function() {
showSlide(currentSlideIndex + 1);
}, SLIDE_DURATION);
}
function stopTimer() {
if (timer) { clearTimeout(timer); timer = null; }
if (progressTimer) {
if (typeof progressTimer === 'number') cancelAnimationFrame(progressTimer);
progressTimer = null;
}
}
function pauseTimer() {
isPaused = true;
if (timer) { clearTimeout(timer); timer = null; }
}
function resumeTimer() {
if (!isPaused) return;
isPaused = false;
var elapsed = Date.now() - progressStart;
var remaining = Math.max(SLIDE_DURATION - elapsed, 0);
if (remaining <= 0) {
showSlide(currentSlideIndex + 1);
return;
}
progressStart = Date.now() - elapsed;
timer = setTimeout(function() {
showSlide(currentSlideIndex + 1);
}, remaining);
}
// --- Event listeners ---
closeBtn.addEventListener('click', closeViewer);
tapLeft.addEventListener('click', function() {
showSlide(currentSlideIndex - 1);
});
tapRight.addEventListener('click', function() {
showSlide(currentSlideIndex + 1);
});
navPrev.addEventListener('click', function() {
showSlide(currentSlideIndex - 1);
});
navNext.addEventListener('click', function() {
showSlide(currentSlideIndex + 1);
});
// Pause on hold (press & hold)
viewerWrap.addEventListener('mousedown', function(e) {
if (e.target === tapLeft || e.target === tapRight || e.target === closeBtn) return;
pauseTimer();
});
viewerWrap.addEventListener('mouseup', function() {
resumeTimer();
});
viewerWrap.addEventListener('mouseleave', function() {
resumeTimer();
});
// Touch hold to pause
viewerWrap.addEventListener('touchstart', function(e) {
if (e.target === tapLeft || e.target === tapRight || e.target === closeBtn) return;
pauseTimer();
}, { passive: true });
viewerWrap.addEventListener('touchend', function() {
resumeTimer();
});
// Keyboard navigation
document.addEventListener('keydown', function(e) {
if (!viewer.classList.contains('is-active')) return;
if (e.key === 'Escape') { closeViewer(); return; }
if (e.key === 'ArrowLeft') { showSlide(currentSlideIndex - 1); return; }
if (e.key === 'ArrowRight') { showSlide(currentSlideIndex + 1); return; }
if (e.key === ' ') { e.preventDefault(); isPaused ? resumeTimer() : pauseTimer(); }
});
// Swipe support
var touchStartX = 0;
var touchStartY = 0;
viewerWrap.addEventListener('touchstart', function(e) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: true });
viewerWrap.addEventListener('touchend', function(e) {
var dx = e.changedTouches[0].clientX - touchStartX;
var dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) {
if (dx < 0) {
showSlide(currentSlideIndex + 1);
} else {
showSlide(currentSlideIndex - 1);
}
}
});
// Click outside to close (on dark background)
viewer.addEventListener('click', function(e) {
if (e.target === viewer) { closeViewer(); }
});
// Init
renderStrip();
})();