site: dawn.gg-style landing at root + port /skin/* for TG bot
- serve new launcher landing site (zern.cc root -> launcher server 1582)
- static assets mounted at /css /js /img
- port GET/HEAD /skin/{filename} from old site with identical headers
- exempt /skin/* + site assets from rate-limit and cache middleware
- Caddy: root domains -> 1582, old site preserved at legacy.zernmc.*
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
(() => {
|
||||
'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 = '<p class="section-sub">No news yet.</p>'; 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 = '<p class="section-sub">News is temporarily unavailable.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user