diff --git a/server/site/index.html b/server/site/index.html index 8867630..78d5abf 100644 --- a/server/site/index.html +++ b/server/site/index.html @@ -70,7 +70,7 @@
- 01 / 06 + 01 / 07

Сервер жив.

ZernMC — это кастомный Minecraft-сервер со своим лором, самописными плагинами и живым комьюнити. Вот что происходит прямо сейчас.

@@ -96,7 +96,7 @@
- 02 / 06 + 02 / 07

Хайлайты сервера.

Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.

@@ -111,7 +111,7 @@
- 03 / 06 + 03 / 07

Всё, что тебе нужно.

Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.

diff --git a/server/site/js/main.js b/server/site/js/main.js index 50a4cf5..cc53eba 100644 --- a/server/site/js/main.js +++ b/server/site/js/main.js @@ -13,6 +13,7 @@ 'nav.download': 'Скачать', 'nav.news': 'Новости', 'nav.play': 'Играть сейчас', + 'section.of': 'Раздел {n} из {m}', 'hero.kicker': 'КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР', 'hero.title': 'Очнись от
холода.', 'hero.sub': 'Zern оживляет твои любимые модпаки. Один лаунчер, любой загрузчик, без лишних хлопот — создан для сервера ZernMC и не только.', @@ -126,6 +127,7 @@ 'nav.download': 'Download', 'nav.news': 'News', 'nav.play': 'Play now', + 'section.of': 'Section {n} of {m}', 'hero.kicker': 'CUSTOM MINECRAFT LAUNCHER', 'hero.title': 'Awaken from
the cold.', 'hero.sub': 'Zern brings your favourite modpacks to life. One launcher, every loader, no hassle — built for the ZernMC server and beyond.', @@ -253,7 +255,13 @@ document.documentElement.lang = currentLang; const toggle = document.getElementById('langToggleLabel'); - if (toggle) toggle.textContent = currentLang === 'ru' ? 'EN' : 'RU'; + if (toggle) toggle.textContent = currentLang === 'ru' ? 'RU' : 'EN'; + + // Section indices: add a tooltip explaining what "02 / 07" means. + document.querySelectorAll('.section-index').forEach((el) => { + const m = (el.textContent || '').trim().match(/^(\d+)\s*\/\s*(\d+)$/); + if (m) el.setAttribute('title', tr('section.of').replace('{n}', m[1]).replace('{m}', m[2])); + }); document.querySelectorAll('[data-i18n]').forEach((el) => { el.textContent = tr(el.dataset.i18n); @@ -423,6 +431,10 @@ const next = document.getElementById('carouselNext'); if (!track) return; + const AUTO_MS = 6500; // auto-advance when idle + const MANUAL_MS = 22000; // hold much longer after manual navigation + const videos = []; + HIGHLIGHTS.forEach((h, i) => { const slide = document.createElement('div'); slide.className = 'carousel-slide'; @@ -431,11 +443,10 @@ const vid = document.createElement('video'); vid.src = '/highlights/' + h.file; vid.controls = true; - vid.autoplay = true; - vid.loop = true; vid.muted = true; vid.playsInline = true; slide.appendChild(vid); + videos[i] = vid; } else { const img = document.createElement('img'); img.src = '/highlights/' + h.file; @@ -456,38 +467,74 @@ const dot = document.createElement('span'); dot.className = 'carousel-dot' + (i === 0 ? ' active' : ''); - dot.addEventListener('click', () => goto(i)); + dot.addEventListener('click', () => goto(i, true)); dotsWrap.appendChild(dot); }); let index = 0; let timer = null; - function goto(i) { + function schedule(delay) { + stop(); + timer = setInterval(() => nextSlide(false), delay); + } + + function isVideoAt(i) { + const h = HIGHLIGHTS[i]; + return h && h.type === 'video'; + } + + function goto(i, manual) { index = (i + HIGHLIGHTS.length) % HIGHLIGHTS.length; track.style.transform = 'translateX(-' + index * 100 + '%)'; dotsWrap.querySelectorAll('.carousel-dot').forEach((d, k) => { d.classList.toggle('active', k === index); }); + + // Only the visible video plays; others pause to save bandwidth. + videos.forEach((vid, k) => { + if (vid) { + if (k === index) { + if (vid.paused) { + vid.currentTime = 0; + const p = vid.play(); + if (p && p.catch) p.catch(() => {}); + } + } else { + vid.pause(); + } + } + }); + + if (isVideoAt(index)) { + // A video slide always advances on its 'ended' event, never on a timer. + stop(); + return; + } + // Image slides: manual navigation holds much longer, idle auto-advance restores. + schedule(manual ? MANUAL_MS : AUTO_MS); } - function nextSlide() { goto(index + 1); } - function prevSlide() { goto(index - 1); } + function nextSlide(manual) { goto(index + 1, !!manual); } + function prevSlide() { goto(index - 1, true); } - function start() { - stop(); - timer = setInterval(nextSlide, 6500); - } + function start() { goto(index, false); } function stop() { if (timer) { clearInterval(timer); timer = null; } } - if (prev) prev.addEventListener('click', () => { prevSlide(); start(); }); - if (next) next.addEventListener('click', () => { nextSlide(); start(); }); + // A video slide advances only once its playback actually finished. + videos.forEach((vid) => { + if (!vid) return; + vid.addEventListener('ended', () => nextSlide(false)); + }); + + if (prev) prev.addEventListener('click', () => { prevSlide(); }); + if (next) next.addEventListener('click', () => { nextSlide(true); }); track.addEventListener('mouseenter', stop); track.addEventListener('mouseleave', start); - start(); + goto(0, false); } function stripMarkup(text) {