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:
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user