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">
|
<section class="section server" id="server">
|
||||||
<div class="section-head">
|
<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>
|
<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>
|
<p class="section-sub" data-i18n="server.sub">ZernMC — это кастомный Minecraft-сервер со своим лором, самописными плагинами и живым комьюнити. Вот что происходит прямо сейчас.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
<section class="section highlights" id="highlights">
|
<section class="section highlights" id="highlights">
|
||||||
<div class="section-head">
|
<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>
|
<h2 class="section-title" data-i18n-html="highlights.title">Хайлайты <span class="accent">сервера.</span></h2>
|
||||||
<p class="section-sub" data-i18n="highlights.sub">Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.</p>
|
<p class="section-sub" data-i18n="highlights.sub">Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
<section class="section features" id="features">
|
<section class="section features" id="features">
|
||||||
<div class="section-head">
|
<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>
|
<h2 class="section-title" data-i18n-html="features.title">Всё, что <span class="accent">тебе нужно.</span></h2>
|
||||||
<p class="section-sub" data-i18n="features.sub">Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.</p>
|
<p class="section-sub" data-i18n="features.sub">Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+60
-13
@@ -13,6 +13,7 @@
|
|||||||
'nav.download': 'Скачать',
|
'nav.download': 'Скачать',
|
||||||
'nav.news': 'Новости',
|
'nav.news': 'Новости',
|
||||||
'nav.play': 'Играть сейчас',
|
'nav.play': 'Играть сейчас',
|
||||||
|
'section.of': 'Раздел {n} из {m}',
|
||||||
'hero.kicker': 'КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР',
|
'hero.kicker': 'КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР',
|
||||||
'hero.title': 'Очнись от<br><span class="hero-title-accent">холода.</span>',
|
'hero.title': 'Очнись от<br><span class="hero-title-accent">холода.</span>',
|
||||||
'hero.sub': 'Zern оживляет твои любимые модпаки. Один лаунчер, любой загрузчик, без лишних хлопот — создан для сервера ZernMC и не только.',
|
'hero.sub': 'Zern оживляет твои любимые модпаки. Один лаунчер, любой загрузчик, без лишних хлопот — создан для сервера ZernMC и не только.',
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
'nav.download': 'Download',
|
'nav.download': 'Download',
|
||||||
'nav.news': 'News',
|
'nav.news': 'News',
|
||||||
'nav.play': 'Play now',
|
'nav.play': 'Play now',
|
||||||
|
'section.of': 'Section {n} of {m}',
|
||||||
'hero.kicker': 'CUSTOM MINECRAFT LAUNCHER',
|
'hero.kicker': 'CUSTOM MINECRAFT LAUNCHER',
|
||||||
'hero.title': 'Awaken from<br><span class="hero-title-accent">the cold.</span>',
|
'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.',
|
'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;
|
document.documentElement.lang = currentLang;
|
||||||
|
|
||||||
const toggle = document.getElementById('langToggleLabel');
|
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) => {
|
document.querySelectorAll('[data-i18n]').forEach((el) => {
|
||||||
el.textContent = tr(el.dataset.i18n);
|
el.textContent = tr(el.dataset.i18n);
|
||||||
@@ -423,6 +431,10 @@
|
|||||||
const next = document.getElementById('carouselNext');
|
const next = document.getElementById('carouselNext');
|
||||||
if (!track) return;
|
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) => {
|
HIGHLIGHTS.forEach((h, i) => {
|
||||||
const slide = document.createElement('div');
|
const slide = document.createElement('div');
|
||||||
slide.className = 'carousel-slide';
|
slide.className = 'carousel-slide';
|
||||||
@@ -431,11 +443,10 @@
|
|||||||
const vid = document.createElement('video');
|
const vid = document.createElement('video');
|
||||||
vid.src = '/highlights/' + h.file;
|
vid.src = '/highlights/' + h.file;
|
||||||
vid.controls = true;
|
vid.controls = true;
|
||||||
vid.autoplay = true;
|
|
||||||
vid.loop = true;
|
|
||||||
vid.muted = true;
|
vid.muted = true;
|
||||||
vid.playsInline = true;
|
vid.playsInline = true;
|
||||||
slide.appendChild(vid);
|
slide.appendChild(vid);
|
||||||
|
videos[i] = vid;
|
||||||
} else {
|
} else {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = '/highlights/' + h.file;
|
img.src = '/highlights/' + h.file;
|
||||||
@@ -456,38 +467,74 @@
|
|||||||
|
|
||||||
const dot = document.createElement('span');
|
const dot = document.createElement('span');
|
||||||
dot.className = 'carousel-dot' + (i === 0 ? ' active' : '');
|
dot.className = 'carousel-dot' + (i === 0 ? ' active' : '');
|
||||||
dot.addEventListener('click', () => goto(i));
|
dot.addEventListener('click', () => goto(i, true));
|
||||||
dotsWrap.appendChild(dot);
|
dotsWrap.appendChild(dot);
|
||||||
});
|
});
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let timer = null;
|
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;
|
index = (i + HIGHLIGHTS.length) % HIGHLIGHTS.length;
|
||||||
track.style.transform = 'translateX(-' + index * 100 + '%)';
|
track.style.transform = 'translateX(-' + index * 100 + '%)';
|
||||||
dotsWrap.querySelectorAll('.carousel-dot').forEach((d, k) => {
|
dotsWrap.querySelectorAll('.carousel-dot').forEach((d, k) => {
|
||||||
d.classList.toggle('active', k === index);
|
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); }
|
if (isVideoAt(index)) {
|
||||||
function prevSlide() { goto(index - 1); }
|
// A video slide always advances on its 'ended' event, never on a timer.
|
||||||
|
|
||||||
function start() {
|
|
||||||
stop();
|
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() {
|
function stop() {
|
||||||
if (timer) { clearInterval(timer); timer = null; }
|
if (timer) { clearInterval(timer); timer = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev) prev.addEventListener('click', () => { prevSlide(); start(); });
|
// A video slide advances only once its playback actually finished.
|
||||||
if (next) next.addEventListener('click', () => { nextSlide(); start(); });
|
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('mouseenter', stop);
|
||||||
track.addEventListener('mouseleave', start);
|
track.addEventListener('mouseleave', start);
|
||||||
|
|
||||||
start();
|
goto(0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripMarkup(text) {
|
function stripMarkup(text) {
|
||||||
|
|||||||
Reference in New Issue
Block a user