(() => { '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); } } 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'); 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(); initNav(); initParallax(); initReveal(); initYear(); }); })();