MEDIABROWSER 배포 사이트 + 공개 릴리스 저장소 전환 (DEPLOYMENT_PLAN §5-ter, BACKLOG 12차) — site/: 프로모션(라이트 글래스모피즘, CSS scroll-driven 패럴랙스+IO 등장, 모션·투명도 축소 존중, no-JS 백지 방지 html.js 게이트)·릴리스(플랫폼 그룹, OS 자동 감지 권장, 이전 버전 접힘, 빈 상태)·사용 안내(설치→첫 다운로드→관리→휴대폰 연결→FAQ) 3페이지. 히어로/가이드 이미지는 VD_DATA_DIR 격리 프로필로 띄운 실제 앱 다운로드 패널 캡처(밝기 보정). 릴리스 데이터는 CORS 회피를 위해 배포 시 fetch-releases.mjs 가 releases.json 으로 정적 생성(git 무시). 공개 저장소 Video-Downloader/releases 신설(비공개 소스 저장소는 자산 익명 다운로드 불가) — CI 업로드를 env RELEASE_REPO 로 전환, draft 익명 비노출이라 QA 후 Publish 흐름 유지. build/deploy-site.ps1(wrangler pages deploy) 추가, 라이브: https://mediabrowser-7r4.pages.dev — Playwright 로 3페이지 전수 스크린샷 검증(빈 상태+샘플 데이터 렌더 포함)
This commit is contained in:
parent
b536622606
commit
c8b061959b
15 changed files with 923 additions and 2 deletions
131
site/site.js
Normal file
131
site/site.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// MEDIABROWSER 사이트 공용 스크립트
|
||||
// 1) 스크롤 등장(reveal) — IntersectionObserver, 모션 축소 설정이면 즉시 표시
|
||||
// 2) releases.html — releases.json(배포 시 생성)을 읽어 릴리스 목록 렌더
|
||||
|
||||
(function initReveal() {
|
||||
const items = document.querySelectorAll(".reveal");
|
||||
if (!items.length) return;
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches || !("IntersectionObserver" in window)) {
|
||||
return; // .js 게이트를 켜지 않으면 CSS 기본값(표시 상태) 그대로
|
||||
}
|
||||
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.15, rootMargin: "0px 0px -8% 0px" });
|
||||
items.forEach((el) => io.observe(el));
|
||||
})();
|
||||
|
||||
// ── 릴리스 렌더 ──────────────────────────────────────────────
|
||||
const PLATFORMS = [
|
||||
{ id: "windows", label: "Windows", icon: "windows-logo", sub: "설치자를 실행하면 됩니다",
|
||||
match: (n) => /setup.*\.exe$/i.test(n) || /win/i.test(n) },
|
||||
{ id: "macos", label: "macOS 프리뷰", icon: "apple-logo", sub: "tar.gz 를 풀어 실행하는 프리뷰 빌드",
|
||||
match: (n) => /osx|darwin|mac/i.test(n) },
|
||||
{ id: "linux", label: "Linux 프리뷰", icon: "linux-logo", sub: "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 `<div class="dl-asset">
|
||||
<div><div class="dl-asset__name">${esc(a.name)}</div><div class="dl-asset__size">${fmtSize(a.size)}</div></div>
|
||||
<a class="btn btn--primary btn--sm ${recommended ? "dl-reco" : ""}" href="${esc(a.url)}" title="${recommended ? "이 기기에 권장" : ""}">받기</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
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 `<div class="dl-group" id="${p.id}">
|
||||
<div class="dl-group__head"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#${p.icon}"/></svg>${p.label}</div>
|
||||
<div class="dl-group__sub">${p.sub}</div>
|
||||
${list.map((a) => assetRow(a, detectPreferredAsset(a.name))).join("")}
|
||||
</div>`;
|
||||
}).filter(Boolean);
|
||||
const rest = assets.filter((a) => !used.has(a.name));
|
||||
if (rest.length) {
|
||||
groups.push(`<div class="dl-group"><div class="dl-group__head">기타</div><div class="dl-group__sub">그 밖의 파일</div>
|
||||
${rest.map((a) => assetRow(a, false)).join("")}</div>`);
|
||||
}
|
||||
return `<div class="dl-groups">${groups.join("")}</div>`;
|
||||
}
|
||||
|
||||
function releaseCard(r, featured) {
|
||||
const pill = r.prerelease ? `<span class="rel-pill rel-pill--pre">프리뷰</span>` : `<span class="rel-pill">안정판</span>`;
|
||||
const meta = `<div class="rel-featured__meta">
|
||||
<span class="rel-featured__tag">${esc(r.name || r.tag)}</span>${pill}
|
||||
<span class="rel-featured__date">${fmtDate(r.published_at)}</span>
|
||||
</div>
|
||||
${r.notes ? `<p class="rel-featured__notes">${esc(r.notes)}</p>` : ""}
|
||||
${groupsHtml(r.assets)}`;
|
||||
if (featured) return `<article class="rel-featured glass">${meta}</article>`;
|
||||
return `<details><summary><span class="rel-featured__tag" style="font-size:19px">${esc(r.name || r.tag)}</span>${pill}
|
||||
<span class="rel-featured__date">${fmtDate(r.published_at)}</span></summary>
|
||||
<div class="rel-old__body">${r.notes ? `<p class="rel-featured__notes">${esc(r.notes)}</p>` : ""}${groupsHtml(r.assets)}</div>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
function emptyState() {
|
||||
return `<div class="rel-empty glass">
|
||||
<svg class="icon" aria-hidden="true"><use href="assets/icons.svg#rocket-launch"/></svg>
|
||||
<h2>첫 공개 릴리스를 준비하고 있어요</h2>
|
||||
<p>빌드 파이프라인은 이미 돌아가고 있습니다. 곧 이 자리에서 설치자를 받을 수 있습니다.
|
||||
먼저 <a href="guide.html" style="color:var(--accent);font-weight:700;text-decoration:none">사용 안내</a>를 둘러보세요.</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
(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 += `<div class="rel-old"><h2 style="font-size:20px;margin-top:14px">이전 버전</h2>
|
||||
${older.map((r) => releaseCard(r, false)).join("")}</div>`;
|
||||
}
|
||||
root.innerHTML = html;
|
||||
} catch (err) {
|
||||
root.innerHTML = `<div class="rel-empty glass">
|
||||
<h2>목록을 불러오지 못했어요</h2>
|
||||
<p>잠시 후 새로고침해 주세요. 급하다면
|
||||
<a href="https://git.chanpaca.net/Video-Downloader/releases" style="color:var(--accent);font-weight:700;text-decoration:none" rel="noopener">릴리스 저장소</a>에서 직접 받을 수 있습니다.</p>
|
||||
</div>`;
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue