// MEDIABROWSER Official Site Script // 1) IntersectionObserver Scroll Reveal // 2) Mobile Navigation Menu Controller // 3) Hero UI Simulator Micro-Interactions // 4) Dynamic Releases Renderer (releases.json) // ── 1. Scroll Reveal Controller ────────────────────────────── (function initReveal() { const items = document.querySelectorAll(".reveal"); if (!items.length) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) { return; } document.documentElement.classList.add("js"); const io = new IntersectionObserver((entries) => { for (const e of entries) { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } } }, { threshold: 0.12, rootMargin: "0px 0px -6% 0px" }); items.forEach((el) => io.observe(el)); })(); // ── 2. Mobile Nav Drawer Controller ────────────────────────── (function initMobileNav() { const toggleBtn = document.getElementById("nav-toggle"); const overlay = document.getElementById("mobile-menu"); if (!toggleBtn || !overlay) return; function toggleMenu() { const isActive = overlay.classList.toggle("is-active"); toggleBtn.setAttribute("aria-expanded", String(isActive)); document.body.style.overflow = isActive ? "hidden" : ""; } toggleBtn.addEventListener("click", toggleMenu); overlay.querySelectorAll(".mobile-menu-link").forEach((link) => { link.addEventListener("click", () => { overlay.classList.remove("is-active"); toggleBtn.setAttribute("aria-expanded", "false"); document.body.style.overflow = ""; }); }); })(); // ── 3. Hero UI Simulator Micro-Animation ───────────────────── (function initHeroSimulator() { const progressEl = document.getElementById("sim-progress"); const percentEl = document.getElementById("sim-percent"); const speedEl = document.getElementById("sim-speed"); if (!progressEl || !percentEl || !speedEl) return; let current = 72; let direction = 1; setInterval(() => { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; if (direction === 1) { current += 0.8; if (current >= 96) direction = -1; } else { current = 64; direction = 1; } const rounded = Math.min(99, Math.floor(current)); progressEl.style.width = rounded + "%"; percentEl.textContent = rounded + "%"; const baseSpeed = 18.2 + (Math.random() * 2.4 - 1.2); speedEl.textContent = baseSpeed.toFixed(1) + " MB/s"; }, 1200); })(); // ── 4. Releases Renderer ───────────────────────────────────── const PLATFORMS = [ { id: "windows", label: "Windows", icon: "windows-logo", sub: "x64 및 ARM64 네이티브 설치자 (exe)", match: (n) => /setup.*\.exe$/i.test(n) || /win/i.test(n) }, { id: "macos", label: "macOS 프리뷰", icon: "apple-logo", sub: "Apple Silicon & Intel tar.gz 프리뷰 빌드", match: (n) => /osx|darwin|mac/i.test(n) }, { id: "linux", label: "Linux 프리뷰", icon: "linux-logo", sub: "Ubuntu, Fedora, Arch tar.gz 프리뷰 빌드", match: (n) => /linux/i.test(n) }, { id: "android", label: "Android", icon: "android-logo", sub: "스마트폰 로컬 스트리밍 컴패니언 APK", match: (n) => /\.apk$/i.test(n) }, ]; function detectPreferredAsset(name) { const ua = navigator.userAgent; if (/Windows/i.test(ua)) return /setup-x64\.exe$/i.test(name); if (/Android/i.test(ua)) return /\.apk$/i.test(name); if (/Mac/i.test(ua)) return /osx-arm64/i.test(name); if (/Linux/i.test(ua)) return /linux-x64/i.test(name); return false; } function fmtSize(bytes) { if (!bytes) return ""; const mb = bytes / 1048576; return mb >= 1024 ? (mb / 1024).toFixed(1) + " GB" : mb.toFixed(1) + " MB"; } function fmtDate(iso) { if (!iso) return ""; const d = new Date(iso); return `${d.getFullYear()}년 ${d.getMonth() + 1}월 ${d.getDate()}일`; } function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); } function assetRow(a, recommended) { return `
${esc(a.name)}
${fmtSize(a.size)} ${recommended ? '· 현재 기기에 권장' : ''}
다운로드
`; } function groupsHtml(assets) { const used = new Set(); const groups = PLATFORMS.map((p) => { const list = assets.filter((a) => !used.has(a.name) && p.match(a.name)); list.forEach((a) => used.add(a.name)); if (!list.length) return ""; return `
${p.label}
${p.sub}
${list.map((a) => assetRow(a, detectPreferredAsset(a.name))).join("")}
`; }).filter(Boolean); const rest = assets.filter((a) => !used.has(a.name)); if (rest.length) { groups.push(`
기타 아카이브
추가 바이너리 및 체크섬 파일
${rest.map((a) => assetRow(a, false)).join("")}
`); } return `
${groups.join("")}
`; } function releaseCard(r, featured) { const pill = r.prerelease ? `프리뷰 빌드` : `공식 안정판`; const meta = ` ${r.notes ? `` : ""} ${groupsHtml(r.assets)}`; if (featured) { return `
`; } return `
${esc(r.name || r.tag)} ${pill} ${fmtDate(r.published_at)}
${r.notes ? `` : ""} ${groupsHtml(r.assets)}
`; } function emptyState() { return `

첫 공개 릴리스를 준비하고 있습니다

빌드 파이프라인이 정상 작동 중이며 곧 공식 설치자가 배포됩니다. 먼저 사용 안내를 둘러보세요.

`; } (async function initReleases() { const root = document.getElementById("release-root"); if (!root) return; try { const res = await fetch("releases.json", { cache: "no-cache" }); if (!res.ok) throw new Error("HTTP " + res.status); const data = await res.json(); const releases = (data.releases || []); if (!releases.length) { root.innerHTML = emptyState(); return; } const [latest, ...older] = releases; let html = releaseCard(latest, true); if (older.length) { html += `

이전 버전 기록

${older.map((r) => releaseCard(r, false)).join("")}
`; } root.innerHTML = html; } catch (err) { root.innerHTML = `

릴리스 목록을 불러오지 못했습니다

네트워크 상태를 확인하고 새로고침해 주세요. 급하신 경우 릴리스 저장소에서 직접 다운로드하실 수 있습니다.

`; } })();