(() => { 'use strict'; const API = ''; // same origin (root -> launcher server) function fmtSize(bytes) { if (!bytes) return '—'; const units = ['B', 'KB', 'MB', 'GB']; let i = 0; let n = bytes; while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; } return n.toFixed(1) + ' ' + units[i]; } async function loadLauncherInfo() { try { const res = await fetch(API + '/launcher/info'); if (!res.ok) return; const data = await res.json(); const version = data.current_version || '—'; const heroVersion = document.getElementById('hero-version'); const heroSize = document.getElementById('hero-size'); const dlVersion = document.getElementById('download-version'); const dlSize = document.getElementById('download-size'); const dlBtn = document.getElementById('download-btn'); if (heroVersion) heroVersion.textContent = version; if (dlVersion) dlVersion.textContent = version; if (data.files && data.files.zips && data.files.zips.length) { const latest = data.files.zips[0]; if (heroSize) heroSize.textContent = fmtSize(latest.size); if (dlSize) dlSize.textContent = fmtSize(latest.size); } if (dlBtn && data.new_format && data.new_format.download_url) { dlBtn.href = API + data.new_format.download_url; } } catch (e) { console.warn('[site] failed to load launcher info', e); } } async function loadMirrors() { try { const res = await fetch(API + '/launcher/mirrors'); if (!res.ok) return; const data = await res.json(); const container = document.getElementById('mirrors'); if (!container || !data.mirrors || !data.mirrors.length) return; const tags = data.mirrors.slice(0, 5).map((m) => { const a = document.createElement('a'); a.className = 'mirror-tag'; a.href = (m.url || '') + '/launcher/download/latest'; a.textContent = m.name || m; return a; }); tags.forEach((t) => container.appendChild(t)); } catch (e) { console.warn('[site] failed to load mirrors', e); } } async function loadServerStatus() { const statusEl = document.getElementById('srv-status'); const playersEl = document.getElementById('srv-players'); const versionEl = document.getElementById('srv-version'); const dot = document.querySelector('#srv-status .status-dot'); if (!statusEl) return; try { const res = await fetch(API + '/api/status'); const data = await res.json(); if (data.online) { if (dot) dot.className = 'status-dot status-dot--online'; statusEl.innerHTML = 'Online'; if (playersEl) playersEl.textContent = `${data.players_online} / ${data.players_max}`; if (versionEl && data.version) versionEl.textContent = data.version; } else { if (dot) dot.className = 'status-dot status-dot--offline'; statusEl.innerHTML = 'Offline'; if (playersEl) playersEl.textContent = '— / —'; } } catch (e) { if (dot) dot.className = 'status-dot status-dot--offline'; statusEl.innerHTML = 'Unavailable'; } } const HIGHLIGHTS = [ { file: '1.jpg', type: 'image', caption: 'Zern' }, { file: '2.mp4', type: 'video', caption: null }, { file: '3.jpg', type: 'image', caption: 'Это честно не я' }, { file: '4.mp4', type: 'video', caption: null }, { file: '5.jpg', type: 'image', caption: 'Спавн ТЦ (старое)' }, { file: '6.jpg', type: 'image', caption: 'Спавн склад (старое)' }, { file: '7.jpg', type: 'image', caption: null }, { file: '8.jpg', type: 'image', caption: null }, { file: '9.jpg', type: 'image', caption: null }, { file: '10.jpg', type: 'image', caption: null }, { file: '11.jpg', type: 'image', caption: null }, { file: '12.jpg', type: 'image', caption: null }, { file: '13.jpg', type: 'image', caption: null }, { file: '14.jpg', type: 'image', caption: null }, { file: '15.jpg', type: 'image', caption: null }, ]; function initCarousel() { const track = document.getElementById('carousel'); const dotsWrap = document.getElementById('carouselDots'); const prev = document.getElementById('carouselPrev'); const next = document.getElementById('carouselNext'); if (!track) return; HIGHLIGHTS.forEach((h, i) => { const slide = document.createElement('div'); slide.className = 'carousel-slide'; if (h.type === 'video') { 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); } else { const img = document.createElement('img'); img.src = '/highlights/' + h.file; img.alt = h.caption || 'Zern highlight'; img.loading = 'lazy'; slide.appendChild(img); } if (h.caption) { const cap = document.createElement('span'); cap.className = 'carousel-caption'; cap.textContent = h.caption; slide.appendChild(cap); } track.appendChild(slide); const dot = document.createElement('span'); dot.className = 'carousel-dot' + (i === 0 ? ' active' : ''); dot.addEventListener('click', () => goto(i)); dotsWrap.appendChild(dot); }); let index = 0; let timer = null; function goto(i) { 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); }); } function nextSlide() { goto(index + 1); } function prevSlide() { goto(index - 1); } function start() { stop(); timer = setInterval(nextSlide, 6500); } function stop() { if (timer) { clearInterval(timer); timer = null; } } if (prev) prev.addEventListener('click', () => { prevSlide(); start(); }); if (next) next.addEventListener('click', () => { nextSlide(); start(); }); track.addEventListener('mouseenter', stop); track.addEventListener('mouseleave', start); start(); } function stripMarkup(text) { if (!text) return ''; return text .replace(/\[[^\]]*photo=[^\]]*\]/g, '') .replace(/\[[^\]]*\]/g, '') .replace(/\*\*\*/g, '') .replace(/\*\*/g, '') .replace(/[*_~`#]/g, '') .replace(/\n+/g, ' ') .trim(); } async function loadNews() { const grid = document.getElementById('news-grid'); if (!grid) return; try { const res = await fetch(API + '/news'); if (!res.ok) throw new Error('news fetch failed'); const data = await res.json(); const news = (data.news || []).slice(0, 6); if (!news.length) { grid.innerHTML = '
No news yet.
'; return; } grid.innerHTML = ''; news.forEach((n) => { const card = document.createElement('article'); card.className = 'news-card'; const tags = document.createElement('div'); tags.className = 'news-tags'; if (n.type) { const t = document.createElement('span'); t.className = 'news-tag'; t.textContent = n.type; tags.appendChild(t); } if (n.version) { const v = document.createElement('span'); v.className = 'news-tag news-tag--version'; v.textContent = n.version; tags.appendChild(v); } const title = document.createElement('h3'); title.textContent = n.title || 'Update'; const body = document.createElement('p'); const text = stripMarkup(n.body); body.textContent = text.length > 220 ? text.slice(0, 220) + '…' : text; card.appendChild(tags); card.appendChild(title); card.appendChild(body); grid.appendChild(card); }); } catch (e) { console.warn('[site] failed to load news', e); grid.innerHTML = 'News is temporarily unavailable.
'; } } function initNav() { const nav = document.getElementById('nav'); const onScroll = () => nav.classList.toggle('scrolled', window.scrollY > 24); window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); } function initParallax() { const els = document.querySelectorAll('[data-parallax]'); if (!els.length) return; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; const onScroll = () => { const y = window.scrollY; if (y > window.innerHeight) return; els.forEach((el) => { const speed = parseFloat(el.dataset.parallax); el.style.transform = 'translateY(' + (y * speed) + 'px)'; }); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); } function initReveal() { const els = document.querySelectorAll('.section-head, .feature-card, .download-card, .news-card, .server-card, .theme-card, .carousel'); if (!('IntersectionObserver' in window)) { els.forEach((el) => el.classList.add('visible')); return; } const io = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('visible'); io.unobserve(entry.target); } }); }, { threshold: 0.12 }); els.forEach((el) => { el.classList.add('reveal'); io.observe(el); }); } function initYear() { const el = document.getElementById('year'); if (el) el.textContent = new Date().getFullYear(); } document.addEventListener('DOMContentLoaded', () => { loadLauncherInfo(); loadMirrors(); loadNews(); loadServerStatus(); setInterval(loadServerStatus, 15000); initCarousel(); initNav(); initParallax(); initReveal(); initYear(); }); })();