site: unify section indices, show current lang, smarter highlight carousel

- Section badges are now consistently 'N / 07' everywhere (was mixing 06/07)
  and get a localized title tooltip explaining 'Section N of 7'.
- Language toggle now shows the CURRENT language (RU/EN) instead of the
  next one, so the state is obvious before clicking.
- Carousel: manual navigation (arrows/dots) holds the slide 22s before
  resuming, idle auto-advance returns to 6.5s; video slides no longer loop
  and only advance when playback actually ends.
This commit is contained in:
SashegDev
2026-08-20 14:52:25 +00:00
parent 7068558137
commit 592de204b6
2 changed files with 64 additions and 17 deletions
+3 -3
View File
@@ -70,7 +70,7 @@
<section class="section server" id="server">
<div class="section-head">
<span class="section-index">01 / 06</span>
<span class="section-index">01 / 07</span>
<h2 class="section-title" data-i18n-html="server.title">Сервер <span class="accent">жив.</span></h2>
<p class="section-sub" data-i18n="server.sub">ZernMC — это кастомный Minecraft-сервер со своим лором, самописными плагинами и живым комьюнити. Вот что происходит прямо сейчас.</p>
</div>
@@ -96,7 +96,7 @@
<section class="section highlights" id="highlights">
<div class="section-head">
<span class="section-index">02 / 06</span>
<span class="section-index">02 / 07</span>
<h2 class="section-title" data-i18n-html="highlights.title">Хайлайты <span class="accent">сервера.</span></h2>
<p class="section-sub" data-i18n="highlights.sub">Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.</p>
</div>
@@ -111,7 +111,7 @@
<section class="section features" id="features">
<div class="section-head">
<span class="section-index">03 / 06</span>
<span class="section-index">03 / 07</span>
<h2 class="section-title" data-i18n-html="features.title">Всё, что <span class="accent">тебе нужно.</span></h2>
<p class="section-sub" data-i18n="features.sub">Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.</p>
</div>
+60 -13
View File
@@ -13,6 +13,7 @@
'nav.download': 'Скачать',
'nav.news': 'Новости',
'nav.play': 'Играть сейчас',
'section.of': 'Раздел {n} из {m}',
'hero.kicker': 'КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР',
'hero.title': 'Очнись от<br><span class="hero-title-accent">холода.</span>',
'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<br><span class="hero-title-accent">the cold.</span>',
'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();
}
}
});
function nextSlide() { goto(index + 1); }
function prevSlide() { goto(index - 1); }
function start() {
if (isVideoAt(index)) {
// A video slide always advances on its 'ended' event, never on a timer.
stop();
timer = setInterval(nextSlide, 6500);
return;
}
// Image slides: manual navigation holds much longer, idle auto-advance restores.
schedule(manual ? MANUAL_MS : AUTO_MS);
}
function nextSlide(manual) { goto(index + 1, !!manual); }
function prevSlide() { goto(index - 1, true); }
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) {