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
1
site/assets/icons.svg
Normal file
1
site/assets/icons.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
BIN
site/assets/panel-downloads.png
Normal file
BIN
site/assets/panel-downloads.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
35
site/fetch-releases.mjs
Normal file
35
site/fetch-releases.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// 배포 전에 공개 릴리스 저장소의 릴리스 목록을 releases.json 으로 굽는다.
|
||||
// (브라우저에서 Forgejo API 를 직접 호출하면 CORS 에 막히므로 빌드 시점에 정적 생성)
|
||||
// 사용: node site/fetch-releases.mjs
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const API = "https://git.chanpaca.net/api/v1/repos/Video-Downloader/releases/releases?limit=50";
|
||||
|
||||
const res = await fetch(API, { headers: { Accept: "application/json" } });
|
||||
if (!res.ok) throw new Error(`릴리스 API 실패: HTTP ${res.status}`);
|
||||
const raw = await res.json();
|
||||
|
||||
const releases = raw
|
||||
.filter((r) => !r.draft)
|
||||
.sort((a, b) => new Date(b.published_at) - new Date(a.published_at))
|
||||
.map((r) => ({
|
||||
tag: r.tag_name,
|
||||
name: r.name || r.tag_name,
|
||||
published_at: r.published_at,
|
||||
prerelease: r.prerelease,
|
||||
notes: r.body || "",
|
||||
assets: (r.assets || []).map((a) => ({
|
||||
name: a.name,
|
||||
size: a.size,
|
||||
url: a.browser_download_url,
|
||||
})),
|
||||
}));
|
||||
|
||||
const out = {
|
||||
generated_at: new Date().toISOString(),
|
||||
repo_url: "https://git.chanpaca.net/Video-Downloader/releases",
|
||||
releases,
|
||||
};
|
||||
|
||||
writeFileSync(new URL("./releases.json", import.meta.url), JSON.stringify(out, null, 2));
|
||||
console.log(`releases.json 생성: 릴리스 ${releases.length}건`);
|
||||
149
site/guide.html
Normal file
149
site/guide.html
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>사용 안내 - MEDIABROWSER</title>
|
||||
<meta name="description" content="MEDIABROWSER 설치부터 첫 다운로드, 다운로드 관리, 휴대폰 연결까지의 사용 안내.">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='24' fill='%232050f0'/><path d='M50 22v36M36 44l14 14 14-14M28 74h44' stroke='white' stroke-width='9' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav glass" aria-label="주 메뉴">
|
||||
<a class="nav__brand" href="index.html">
|
||||
<span class="nav__mark"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#download-simple"/></svg></span>
|
||||
MEDIABROWSER
|
||||
</a>
|
||||
<div class="nav__links">
|
||||
<a href="index.html#features">기능</a>
|
||||
<a href="index.html#mobile">모바일</a>
|
||||
<a href="guide.html" aria-current="page">사용 안내</a>
|
||||
</div>
|
||||
<a class="btn btn--primary btn--sm" href="releases.html">다운로드</a>
|
||||
</nav>
|
||||
|
||||
<header class="pagehead">
|
||||
<div class="orbs" aria-hidden="true">
|
||||
<div class="orb orb--a"></div>
|
||||
<div class="orb orb--b"></div>
|
||||
</div>
|
||||
<div class="wrap">
|
||||
<h1>사용 안내</h1>
|
||||
<p class="sub">설치부터 첫 다운로드, 휴대폰 연결까지. 처음이라면 위에서부터 순서대로 따라오세요.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap guide-layout">
|
||||
<aside class="guide-toc glass" aria-label="목차">
|
||||
<a href="#install">1. 설치</a>
|
||||
<a href="#first">2. 첫 다운로드</a>
|
||||
<a href="#manage">3. 다운로드 관리</a>
|
||||
<a href="#phone">4. 휴대폰 연결</a>
|
||||
<a href="#faq">자주 묻는 질문</a>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<section class="guide-step" id="install">
|
||||
<h2><span class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#rocket-launch"/></svg></span>설치</h2>
|
||||
<p><a href="releases.html" style="color:var(--accent);font-weight:700;text-decoration:none">다운로드 페이지</a>에서 플랫폼에 맞는 빌드를 받으세요.</p>
|
||||
<ul>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#windows-logo"/></svg>
|
||||
<span><b>Windows</b>: 설치자(exe)를 실행하고 안내를 따르면 끝입니다. x64 와 arm64 를 제공합니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#apple-logo"/></svg>
|
||||
<span><b>macOS 프리뷰</b>: tar.gz 를 풀고 앱을 실행합니다. 아직 서명이 없어 처음 실행 시 보안 확인이 필요할 수 있습니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#linux-logo"/></svg>
|
||||
<span><b>Linux 프리뷰</b>: tar.gz 를 풀고 실행 파일을 실행합니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#android-logo"/></svg>
|
||||
<span><b>Android</b>: APK 를 설치합니다. 데스크톱 앱과 페어링해 쓰는 컴패니언입니다.</span></li>
|
||||
</ul>
|
||||
<p>다운로드에 필요한 도구(yt-dlp, ffmpeg, deno)는 앱이 필요할 때 자동으로 설치합니다. 따로 준비할 것은 없습니다.</p>
|
||||
</section>
|
||||
|
||||
<section class="guide-step" id="first">
|
||||
<h2><span class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#cursor-click"/></svg></span>첫 다운로드</h2>
|
||||
<p>MEDIABROWSER 는 브라우저입니다. 평소처럼 사이트에 들어가 영상을 재생하면 나머지는 앱이 합니다.</p>
|
||||
<ul>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#globe-hemisphere-west"/></svg>
|
||||
<span>주소창에 사이트를 입력해 이동하고, 원하는 영상을 <b>재생</b>합니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#waveform"/></svg>
|
||||
<span>재생이 시작되면 스트림(HLS, DASH, MP4)이 감지되어 오른쪽 <b>다운로드 패널</b>에 나타납니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#download-simple"/></svg>
|
||||
<span>목록에서 <b>다운로드</b>를 누르면 로그인 상태 그대로 내려받기가 시작됩니다.</span></li>
|
||||
</ul>
|
||||
<figure class="guide-media glass">
|
||||
<img src="assets/panel-downloads.png" alt="다운로드 패널의 빈 상태 화면. 영상을 재생하면 여기에 나타납니다 라는 안내 문구가 표시되어 있다." width="426" height="330">
|
||||
<figcaption>감지 전의 다운로드 패널. 재생을 시작하면 이 목록이 채워집니다.</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section class="guide-step" id="manage">
|
||||
<h2><span class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#stack"/></svg></span>다운로드 관리</h2>
|
||||
<p>패널의 탭으로 전체, 진행, 완료, 실패를 나눠 보고, 항목마다 속도와 남은 시간, 세그먼트 진행을 확인합니다.</p>
|
||||
<ul>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#check-circle"/></svg>
|
||||
<span><b>모두 일시정지 / 모두 재개</b>로 한 번에 멈추고 다시 시작합니다. 항목별 일시정지도 됩니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#check-circle"/></svg>
|
||||
<span><b>예약</b>을 걸면 지정한 시간부터 내려받습니다. 이미 받은 영상은 중복 확인으로 알려줍니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#folders"/></svg>
|
||||
<span>받은 영상은 <b>라이브러리</b>에서 모아 보고, 재생목록과 즐겨찾기, 아카이브로 정리합니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#check-circle"/></svg>
|
||||
<span>목록은 <b>CSV</b> 로 내보내 기록으로 남길 수 있습니다.</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="guide-step" id="phone">
|
||||
<h2><span class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#device-mobile"/></svg></span>휴대폰 연결</h2>
|
||||
<p>데스크톱 앱에 내장된 서버가 휴대폰과 라이브러리를 이어줍니다. 같은 네트워크에서 페어링 한 번이면 됩니다.</p>
|
||||
<ul>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#qr-code"/></svg>
|
||||
<span>휴대폰에서 컴패니언 앱을 열고, 데스크톱 앱이 보여주는 <b>QR 코드</b>를 스캔합니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#play-circle"/></svg>
|
||||
<span>페어링이 끝나면 라이브러리의 영상을 휴대폰에서 <b>바로 재생</b>하거나 <b>파일로 전송</b>받습니다.</span></li>
|
||||
<li><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#shield-check"/></svg>
|
||||
<span>연결은 내 네트워크 안에서만 이루어집니다. 외부 서버를 거치지 않습니다.</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="guide-step" id="faq">
|
||||
<h2><span class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#gear"/></svg></span>자주 묻는 질문</h2>
|
||||
<div class="guide-faq">
|
||||
<details>
|
||||
<summary>ffmpeg 나 yt-dlp 를 따로 설치해야 하나요?</summary>
|
||||
<p>아니요. 필요한 시점에 앱이 자동으로 내려받아 관리합니다. 설정에서 직접 지정한 경로가 있으면 그걸 우선합니다.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>받은 파일은 어디에 저장되나요?</summary>
|
||||
<p>기본 저장 폴더에 저장되며, 설정에서 폴더를 바꿀 수 있습니다. 완료 목록에서 파일 위치를 바로 열 수 있습니다.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>다운로드가 중간에 끊기면 처음부터 다시 받나요?</summary>
|
||||
<p>아니요. 세그먼트 단위로 이어받기를 지원해서 받다 만 지점부터 다시 진행합니다.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>업데이트는 어떻게 하나요?</summary>
|
||||
<p>설정의 앱 업데이트 항목에서 새 버전을 확인할 수 있고, 다운로드 페이지에서 새 설치자를 받아 덮어 설치하면 됩니다.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>어떤 사이트를 지원하나요?</summary>
|
||||
<p>표준 스트리밍 형식(HLS, DASH, MP4)을 쓰는 사이트에서 동작합니다. 콘텐츠의 이용 약관과 저작권을 지키는 범위에서 사용해 주세요.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="wrap footer__row">
|
||||
<span class="footer__brand">MEDIABROWSER</span>
|
||||
<a href="releases.html">다운로드</a>
|
||||
<a href="guide.html">사용 안내</a>
|
||||
<a href="https://git.chanpaca.net/Video-Downloader/releases" rel="noopener">릴리스 저장소</a>
|
||||
<span class="footer__copy">© 2026 MEDIABROWSER</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="site.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
156
site/index.html
Normal file
156
site/index.html
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>MEDIABROWSER - 보던 영상, 그 자리에서 내 것으로</title>
|
||||
<meta name="description" content="MEDIABROWSER 는 재생 중인 미디어를 감지해 내려받는 데스크톱 브라우저입니다. 받은 영상은 라이브러리로 관리하고 휴대폰으로 이어봅니다.">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='24' fill='%232050f0'/><path d='M50 22v36M36 44l14 14 14-14M28 74h44' stroke='white' stroke-width='9' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav glass" aria-label="주 메뉴">
|
||||
<a class="nav__brand" href="index.html" aria-current="page">
|
||||
<span class="nav__mark"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#download-simple"/></svg></span>
|
||||
MEDIABROWSER
|
||||
</a>
|
||||
<div class="nav__links">
|
||||
<a href="#features">기능</a>
|
||||
<a href="#mobile">모바일</a>
|
||||
<a href="guide.html">사용 안내</a>
|
||||
</div>
|
||||
<a class="btn btn--primary btn--sm" href="releases.html">다운로드</a>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<div class="orbs" aria-hidden="true">
|
||||
<div class="orb orb--a"></div>
|
||||
<div class="orb orb--b"></div>
|
||||
<div class="orb orb--c"></div>
|
||||
</div>
|
||||
<div class="wrap hero__grid">
|
||||
<div>
|
||||
<h1>보던 영상,<br>그 자리에서 내 것으로.</h1>
|
||||
<p class="sub">MEDIABROWSER 는 재생 중인 미디어를 감지해 내려받는 브라우저입니다.
|
||||
받은 영상은 라이브러리로 관리하고, 휴대폰으로 이어봅니다.</p>
|
||||
<div class="hero__cta">
|
||||
<a class="btn btn--primary" href="releases.html">다운로드</a>
|
||||
<a class="btn btn--ghost" href="guide.html">사용 안내</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero__visual reveal">
|
||||
<figure class="hero__panel glass">
|
||||
<img src="assets/panel-downloads.png" alt="MEDIABROWSER 다운로드 패널. 전체·진행·완료·실패 탭과 모두 일시정지, 모두 재개, CSV 버튼이 있고, 영상을 재생하면 여기에 나타납니다 라는 안내가 보인다." width="426" height="330">
|
||||
<figcaption class="hero__caption">앱의 다운로드 패널. 재생을 감지하면 이 목록에 자동으로 쌓입니다.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="features" class="section-pad">
|
||||
<div class="wrap">
|
||||
<h2 class="reveal" style="font-size:clamp(28px,3.6vw,40px)">주소를 붙여넣는 게 아니라,<br>그냥 봅니다.</h2>
|
||||
<div class="bento">
|
||||
<div class="bento__cell bento__cell--wide reveal">
|
||||
<div class="bento__wave"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#waveform"/></svg></div>
|
||||
<div>
|
||||
<div class="bento__chipline" aria-label="지원 형식">
|
||||
<span class="chip">HLS</span><span class="chip">DASH</span><span class="chip">MP4</span>
|
||||
</div>
|
||||
<h3>재생하면 감지됩니다</h3>
|
||||
<p>내장 브라우저에서 영상이 재생되는 순간 스트림을 인식해 다운로드 목록에 올립니다.
|
||||
별도 확장이나 주소 복사가 필요 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bento__cell glass reveal">
|
||||
<h3>로그인 그대로</h3>
|
||||
<p>탭의 쿠키와 Referer 를 그대로 물려받아 내려받습니다. 브라우저에서 보이던 영상은 그대로 받아집니다.</p>
|
||||
</div>
|
||||
<div class="bento__cell glass reveal">
|
||||
<h3>끊겨도 이어서</h3>
|
||||
<p>병렬 분할 다운로드와 이어받기, 그리고 ffmpeg 리먹스까지 자동으로 처리합니다. AES-128 스트림도 지원합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-pad" style="background:linear-gradient(180deg,transparent,var(--bg-deep) 30%,var(--bg-deep) 70%,transparent)">
|
||||
<div class="wrap">
|
||||
<h2 class="reveal" style="font-size:clamp(28px,3.6vw,40px)">다운로드는 관리가 절반입니다.</h2>
|
||||
<div class="featlist">
|
||||
<div class="featlist__item reveal">
|
||||
<div class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#stack"/></svg></div>
|
||||
<div><h3>속도와 세그먼트가 보입니다</h3><p>항목마다 속도, 남은 시간, 세그먼트 맵을 실시간으로 표시합니다.</p></div>
|
||||
</div>
|
||||
<div class="featlist__item reveal">
|
||||
<div class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#play-circle"/></svg></div>
|
||||
<div><h3>전체 일시정지와 재개</h3><p>한 번에 멈추고 한 번에 다시 시작합니다. 예약 시간을 걸어둘 수도 있습니다.</p></div>
|
||||
</div>
|
||||
<div class="featlist__item reveal">
|
||||
<div class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#check-circle"/></svg></div>
|
||||
<div><h3>중복은 미리 알려줍니다</h3><p>이미 받은 영상을 다시 받으려 하면 내려받기 전에 확인해 줍니다.</p></div>
|
||||
</div>
|
||||
<div class="featlist__item reveal">
|
||||
<div class="featlist__icon"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#folders"/></svg></div>
|
||||
<div><h3>라이브러리와 아카이브</h3><p>받은 영상을 모아 보고, 정리하고, 재생목록과 즐겨찾기로 관리합니다.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mobile" class="section-pad">
|
||||
<div class="wrap mobile__grid">
|
||||
<div class="mobile__stage reveal" aria-hidden="true">
|
||||
<div class="mobile__card mobile__card--a glass">
|
||||
<svg class="icon"><use href="assets/icons.svg#qr-code"/></svg>
|
||||
<div><strong>QR 페어링</strong><span>화면의 코드를 한 번 스캔</span></div>
|
||||
</div>
|
||||
<div class="mobile__card mobile__card--b glass">
|
||||
<svg class="icon"><use href="assets/icons.svg#device-mobile"/></svg>
|
||||
<div><strong>스트리밍</strong><span>같은 네트워크에서 바로 재생</span></div>
|
||||
</div>
|
||||
<div class="mobile__card mobile__card--c glass">
|
||||
<svg class="icon"><use href="assets/icons.svg#download-simple"/></svg>
|
||||
<div><strong>전송</strong><span>파일을 휴대폰에 저장</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="reveal" style="font-size:clamp(28px,3.6vw,40px)">받은 영상,<br>휴대폰에서 이어보기.</h2>
|
||||
<p class="sub reveal" style="margin-top:20px">데스크톱 앱에 내장된 서버가 라이브러리를 휴대폰으로 스트리밍하거나 전송합니다.
|
||||
QR 코드 한 번으로 페어링하고, Android 컴패니언 앱으로 어디서든 이어봅니다. iOS 는 준비 중입니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-pad" style="padding-top:0">
|
||||
<div class="wrap">
|
||||
<div class="band glass reveal">
|
||||
<h2>Windows 에서 시작하세요.</h2>
|
||||
<p class="sub">Windows 는 설치자로 바로 시작할 수 있습니다.
|
||||
macOS 와 Linux 는 새 셸의 프리뷰 빌드, Android 는 컴패니언 앱을 제공합니다.</p>
|
||||
<div class="band__oses">
|
||||
<div class="band__os"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#windows-logo"/></svg>Windows</div>
|
||||
<div class="band__os"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#apple-logo"/></svg>macOS 프리뷰</div>
|
||||
<div class="band__os"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#linux-logo"/></svg>Linux 프리뷰</div>
|
||||
<div class="band__os"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#android-logo"/></svg>Android</div>
|
||||
</div>
|
||||
<a class="btn btn--primary" href="releases.html">다운로드</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="wrap footer__row">
|
||||
<span class="footer__brand">MEDIABROWSER</span>
|
||||
<a href="releases.html">다운로드</a>
|
||||
<a href="guide.html">사용 안내</a>
|
||||
<a href="https://git.chanpaca.net/Video-Downloader/releases" rel="noopener">릴리스 저장소</a>
|
||||
<span class="footer__copy">© 2026 MEDIABROWSER</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="site.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
76
site/releases.html
Normal file
76
site/releases.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>다운로드 - MEDIABROWSER</title>
|
||||
<meta name="description" content="MEDIABROWSER 릴리스 다운로드. Windows 설치자, macOS·Linux 프리뷰 빌드, Android 컴패니언 APK.">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='24' fill='%232050f0'/><path d='M50 22v36M36 44l14 14 14-14M28 74h44' stroke='white' stroke-width='9' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav glass" aria-label="주 메뉴">
|
||||
<a class="nav__brand" href="index.html">
|
||||
<span class="nav__mark"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#download-simple"/></svg></span>
|
||||
MEDIABROWSER
|
||||
</a>
|
||||
<div class="nav__links">
|
||||
<a href="index.html#features">기능</a>
|
||||
<a href="index.html#mobile">모바일</a>
|
||||
<a href="guide.html">사용 안내</a>
|
||||
</div>
|
||||
<a class="btn btn--primary btn--sm" href="releases.html" aria-current="page">다운로드</a>
|
||||
</nav>
|
||||
|
||||
<header class="pagehead">
|
||||
<div class="orbs" aria-hidden="true">
|
||||
<div class="orb orb--a"></div>
|
||||
<div class="orb orb--b"></div>
|
||||
</div>
|
||||
<div class="wrap">
|
||||
<h1>다운로드</h1>
|
||||
<p class="sub">사용하는 플랫폼에 맞는 빌드를 받으세요. 설치가 끝나면
|
||||
<a href="guide.html" style="color:var(--accent);font-weight:700;text-decoration:none">사용 안내</a>에서 첫 다운로드까지 3분이면 충분합니다.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap rel-layout">
|
||||
<aside class="rel-rail">
|
||||
<div class="rel-rail__box glass">
|
||||
<h3>플랫폼</h3>
|
||||
<a class="rel-rail__os" href="#windows"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#windows-logo"/></svg>Windows</a>
|
||||
<a class="rel-rail__os" href="#macos"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#apple-logo"/></svg>macOS 프리뷰</a>
|
||||
<a class="rel-rail__os" href="#linux"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#linux-logo"/></svg>Linux 프리뷰</a>
|
||||
<a class="rel-rail__os" href="#android"><svg class="icon" aria-hidden="true"><use href="assets/icons.svg#android-logo"/></svg>Android</a>
|
||||
</div>
|
||||
<div class="rel-rail__box glass">
|
||||
<p class="rel-rail__note">Windows 는 설치자(exe)를 실행하면 됩니다.
|
||||
macOS 와 Linux 는 tar.gz 를 풀어 실행하는 프리뷰 빌드입니다.
|
||||
전체 버전 기록은 <a href="https://git.chanpaca.net/Video-Downloader/releases" rel="noopener">릴리스 저장소</a>에 있습니다.</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main id="release-root" aria-live="polite">
|
||||
<div class="rel-empty glass" id="release-loading">
|
||||
<svg class="icon" aria-hidden="true"><use href="assets/icons.svg#download-simple"/></svg>
|
||||
<h2>릴리스 목록을 불러오는 중</h2>
|
||||
<p>잠시만 기다려 주세요.</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="wrap footer__row">
|
||||
<span class="footer__brand">MEDIABROWSER</span>
|
||||
<a href="releases.html">다운로드</a>
|
||||
<a href="guide.html">사용 안내</a>
|
||||
<a href="https://git.chanpaca.net/Video-Downloader/releases" rel="noopener">릴리스 저장소</a>
|
||||
<span class="footer__copy">© 2026 MEDIABROWSER</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="site.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
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>`;
|
||||
}
|
||||
})();
|
||||
289
site/styles.css
Normal file
289
site/styles.css
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
/* MEDIABROWSER 사이트 공용 디자인 시스템
|
||||
라이트 단일 테마(사용자 지정) · 글래스모피즘 · 절제된 스크롤 모션
|
||||
반경 체계: 버튼=필, 카드/패널=20px, 칩/입력=12px */
|
||||
|
||||
:root {
|
||||
--bg: #f5f7fc;
|
||||
--bg-deep: #e9edf8;
|
||||
--ink: #171a23;
|
||||
--muted: #5b6273;
|
||||
--line: rgba(23, 26, 35, 0.08);
|
||||
--accent: #2050f0;
|
||||
--accent-deep: #1740c8;
|
||||
--accent-soft: rgba(32, 80, 240, 0.09);
|
||||
--glass: rgba(255, 255, 255, 0.58);
|
||||
--glass-strong: rgba(255, 255, 255, 0.78);
|
||||
--glass-brd: rgba(255, 255, 255, 0.72);
|
||||
--shadow-soft: 0 24px 60px -24px rgba(32, 60, 160, 0.18);
|
||||
--shadow-lift: 0 32px 80px -28px rgba(32, 60, 160, 0.28);
|
||||
--r-card: 20px;
|
||||
--r-chip: 12px;
|
||||
--nav-h: 68px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
|
||||
|
||||
body {
|
||||
font-family: "Wanted Sans Variable", "Wanted Sans", system-ui, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
::selection { background: var(--accent); color: #fff; }
|
||||
|
||||
.icon { width: 1.25em; height: 1.25em; fill: currentColor; flex: none; vertical-align: -0.25em; }
|
||||
|
||||
/* ── 배경 오브(패럴랙스 레이어) ─────────────────────────────── */
|
||||
.orbs { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: -1; }
|
||||
.orb { position: absolute; border-radius: 50%; filter: blur(70px); opacity: 0.55; }
|
||||
.orb--a { width: 560px; height: 560px; left: -140px; top: -120px;
|
||||
background: radial-gradient(circle at 35% 35%, #c9d6ff, #eef1fb 70%); }
|
||||
.orb--b { width: 480px; height: 480px; right: -120px; top: 160px;
|
||||
background: radial-gradient(circle at 40% 40%, #d8e4ff, #f0f3fc 70%); }
|
||||
.orb--c { width: 380px; height: 380px; left: 34%; top: 560px;
|
||||
background: radial-gradient(circle at 50% 40%, #dfe9ff, #f4f6fd 72%); opacity: 0.45; }
|
||||
|
||||
/* 스크롤 패럴랙스: 지원 브라우저에서만, 모션 축소 설정이면 정지 */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@supports (animation-timeline: scroll()) {
|
||||
.orb { animation: orb-drift linear both; animation-timeline: scroll(); }
|
||||
.orb--a { --drift: 180px; }
|
||||
.orb--b { --drift: -240px; }
|
||||
.orb--c { --drift: 120px; }
|
||||
}
|
||||
}
|
||||
@keyframes orb-drift { to { transform: translateY(var(--drift, 0px)); } }
|
||||
|
||||
/* ── 글래스 재질 ────────────────────────────────────────────── */
|
||||
.glass {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.16)), var(--glass);
|
||||
backdrop-filter: blur(18px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
||||
border: 1px solid var(--glass-brd);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), var(--shadow-soft);
|
||||
}
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.glass { background: rgba(255, 255, 255, 0.96); backdrop-filter: none; -webkit-backdrop-filter: none; }
|
||||
}
|
||||
|
||||
/* ── 내비게이션 ─────────────────────────────────────────────── */
|
||||
.nav {
|
||||
position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
|
||||
width: min(1120px, calc(100% - 32px)); height: var(--nav-h);
|
||||
display: flex; align-items: center; gap: 28px; padding: 0 22px;
|
||||
border-radius: 999px; z-index: 50;
|
||||
}
|
||||
.nav__brand {
|
||||
font-weight: 800; letter-spacing: 0.04em; font-size: 15px;
|
||||
color: var(--ink); text-decoration: none; display: flex; align-items: center; gap: 9px;
|
||||
}
|
||||
.nav__mark {
|
||||
width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
|
||||
background: linear-gradient(135deg, var(--accent), #4d79ff); color: #fff;
|
||||
}
|
||||
.nav__mark .icon { width: 16px; height: 16px; }
|
||||
.nav__links { display: flex; gap: 22px; margin-left: auto; }
|
||||
.nav__links a { color: var(--muted); text-decoration: none; font-size: 14.5px; font-weight: 600; }
|
||||
.nav__links a:hover, .nav__links a[aria-current="page"] { color: var(--ink); }
|
||||
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
||||
border-radius: 999px; font-weight: 700; text-decoration: none; cursor: pointer;
|
||||
border: 0; white-space: nowrap;
|
||||
transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.18s, background 0.18s;
|
||||
}
|
||||
.btn:active { transform: translateY(1px) scale(0.98); }
|
||||
.btn--primary {
|
||||
background: var(--accent); color: #fff; padding: 12px 22px; font-size: 15px;
|
||||
box-shadow: 0 10px 24px -10px rgba(32, 80, 240, 0.55);
|
||||
}
|
||||
.btn--primary:hover { background: var(--accent-deep); }
|
||||
.btn--ghost {
|
||||
background: rgba(255, 255, 255, 0.6); color: var(--ink); padding: 12px 22px; font-size: 15px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.btn--ghost:hover { background: #fff; }
|
||||
.btn--sm { padding: 9px 16px; font-size: 14px; }
|
||||
|
||||
/* ── 공통 레이아웃 ──────────────────────────────────────────── */
|
||||
.wrap { width: min(1120px, calc(100% - 40px)); margin: 0 auto; }
|
||||
section { position: relative; }
|
||||
.section-pad { padding: 108px 0; }
|
||||
h1, h2, h3 { line-height: 1.22; letter-spacing: -0.02em; font-weight: 800; }
|
||||
.sub { color: var(--muted); font-size: 17px; max-width: 58ch; }
|
||||
|
||||
/* 스크롤 등장 — JS 가 html 에 .js 를 붙였을 때만 숨김(no-JS 에서도 내용이 보이는 점진적 향상) */
|
||||
html.js .reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1); }
|
||||
html.js .reveal.in { opacity: 1; transform: none; }
|
||||
@media (prefers-reduced-motion: reduce) { html.js .reveal { opacity: 1; transform: none; transition: none; } }
|
||||
|
||||
/* ── 히어로 ─────────────────────────────────────────────────── */
|
||||
.hero { padding: calc(var(--nav-h) + 78px) 0 96px; min-height: 86dvh; display: flex; align-items: center; }
|
||||
.hero__grid { display: grid; grid-template-columns: 7fr 5fr; gap: 56px; align-items: center; }
|
||||
.hero h1 { font-size: clamp(38px, 5.2vw, 62px); }
|
||||
.hero .sub { margin-top: 22px; font-size: 18px; }
|
||||
.hero__cta { display: flex; gap: 14px; margin-top: 34px; }
|
||||
.hero__visual { position: relative; }
|
||||
.hero__panel {
|
||||
border-radius: var(--r-card); padding: 14px;
|
||||
transform: rotate(1.8deg);
|
||||
}
|
||||
.hero__panel img {
|
||||
display: block; width: 100%; border-radius: 12px;
|
||||
box-shadow: 0 18px 44px -18px rgba(10, 16, 40, 0.45);
|
||||
}
|
||||
.hero__caption { margin-top: 12px; font-size: 13.5px; color: var(--muted); text-align: center; }
|
||||
|
||||
/* ── 벤토 ───────────────────────────────────────────────────── */
|
||||
.bento { display: grid; grid-template-columns: 3fr 2fr; grid-template-rows: auto auto; gap: 20px; margin-top: 52px; }
|
||||
.bento__cell { border-radius: var(--r-card); padding: 34px; position: relative; overflow: hidden; }
|
||||
.bento__cell--wide { grid-row: span 2; display: flex; flex-direction: column; justify-content: flex-end; min-height: 380px;
|
||||
background: linear-gradient(160deg, #1b2440 0%, #22346e 55%, #2050f0 130%); color: #fff; }
|
||||
.bento__cell--wide h3, .bento__cell--wide p { color: #fff; }
|
||||
.bento__cell--wide p { opacity: 0.82; }
|
||||
.bento__wave { position: absolute; top: 30px; left: 34px; opacity: 0.9; }
|
||||
.bento__wave .icon { width: 44px; height: 44px; color: #9db6ff; }
|
||||
.bento__cell h3 { font-size: 21px; margin-bottom: 10px; }
|
||||
.bento__cell p { color: var(--muted); font-size: 15.5px; }
|
||||
.bento__chipline { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
|
||||
.chip {
|
||||
display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
|
||||
border-radius: 999px; font-size: 13px; font-weight: 700;
|
||||
background: rgba(255, 255, 255, 0.14); border: 1px solid rgba(255, 255, 255, 0.22); color: #dfe8ff;
|
||||
}
|
||||
|
||||
/* ── 기능 그리드(카드 없는 정리) ────────────────────────────── */
|
||||
.featlist { display: grid; grid-template-columns: repeat(2, 1fr); gap: 40px 64px; margin-top: 56px; }
|
||||
.featlist__item { display: flex; gap: 16px; align-items: flex-start; }
|
||||
.featlist__icon {
|
||||
width: 42px; height: 42px; flex: none; border-radius: var(--r-chip); display: grid; place-items: center;
|
||||
background: var(--accent-soft); color: var(--accent);
|
||||
}
|
||||
.featlist__icon .icon { width: 21px; height: 21px; }
|
||||
.featlist__item h3 { font-size: 17px; margin-bottom: 4px; }
|
||||
.featlist__item p { color: var(--muted); font-size: 15px; }
|
||||
|
||||
/* ── 모바일 섹션 ────────────────────────────────────────────── */
|
||||
.mobile__grid { display: grid; grid-template-columns: 5fr 7fr; gap: 56px; align-items: center; }
|
||||
.mobile__stage { position: relative; min-height: 320px; }
|
||||
.mobile__card { position: absolute; border-radius: var(--r-card); display: flex; align-items: center; gap: 14px; padding: 20px 24px; }
|
||||
.mobile__card .icon { width: 30px; height: 30px; color: var(--accent); }
|
||||
.mobile__card strong { display: block; font-size: 15.5px; }
|
||||
.mobile__card span { font-size: 13.5px; color: var(--muted); }
|
||||
.mobile__card--a { top: 8%; left: 4%; transform: rotate(-2.4deg); }
|
||||
.mobile__card--b { top: 40%; right: 6%; transform: rotate(1.6deg); }
|
||||
.mobile__card--c { bottom: 4%; left: 14%; transform: rotate(-1deg); }
|
||||
|
||||
/* ── 플랫폼 밴드 ────────────────────────────────────────────── */
|
||||
.band { border-radius: 28px; padding: 64px 56px; text-align: center; }
|
||||
.band h2 { font-size: clamp(28px, 3.6vw, 40px); }
|
||||
.band .sub { margin: 16px auto 0; }
|
||||
.band__oses { display: flex; gap: 34px; justify-content: center; margin: 36px 0 40px; color: var(--muted); }
|
||||
.band__os { display: flex; flex-direction: column; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 600; }
|
||||
.band__os .icon { width: 34px; height: 34px; color: var(--ink); }
|
||||
.band__os--soon { opacity: 0.55; }
|
||||
|
||||
/* ── 푸터 ───────────────────────────────────────────────────── */
|
||||
footer { padding: 48px 0 60px; }
|
||||
.footer__row { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; border-top: 1px solid var(--line); padding-top: 32px; }
|
||||
.footer__row a { color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600; }
|
||||
.footer__row a:hover { color: var(--ink); }
|
||||
.footer__brand { font-weight: 800; letter-spacing: 0.04em; font-size: 14px; }
|
||||
.footer__copy { margin-left: auto; color: var(--muted); font-size: 13.5px; }
|
||||
|
||||
/* ── 릴리스 페이지 ──────────────────────────────────────────── */
|
||||
.pagehead { padding: calc(var(--nav-h) + 74px) 0 30px; }
|
||||
.pagehead h1 { font-size: clamp(32px, 4.2vw, 48px); }
|
||||
.pagehead .sub { margin-top: 16px; }
|
||||
|
||||
.rel-layout { display: grid; grid-template-columns: 280px 1fr; gap: 44px; align-items: start; padding-bottom: 110px; }
|
||||
.rel-rail { position: sticky; top: calc(var(--nav-h) + 30px); display: flex; flex-direction: column; gap: 10px; }
|
||||
.rel-rail__box { border-radius: var(--r-card); padding: 22px; }
|
||||
.rel-rail__box h3 { font-size: 15px; margin-bottom: 12px; }
|
||||
.rel-rail__os { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: var(--r-chip);
|
||||
color: var(--ink); text-decoration: none; font-size: 14.5px; font-weight: 600; }
|
||||
.rel-rail__os:hover { background: rgba(255, 255, 255, 0.75); }
|
||||
.rel-rail__os .icon { width: 19px; height: 19px; color: var(--muted); }
|
||||
.rel-rail__note { font-size: 13.5px; color: var(--muted); line-height: 1.55; }
|
||||
.rel-rail__note a { color: var(--accent); font-weight: 700; text-decoration: none; }
|
||||
|
||||
.rel-featured { border-radius: 24px; padding: 40px; }
|
||||
.rel-featured__meta { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
|
||||
.rel-featured__tag { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
|
||||
.rel-featured__date { color: var(--muted); font-size: 14.5px; }
|
||||
.rel-pill { display: inline-flex; padding: 4px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 800;
|
||||
background: var(--accent-soft); color: var(--accent); }
|
||||
.rel-pill--pre { background: rgba(214, 122, 20, 0.12); color: #a35a00; }
|
||||
.rel-featured__notes { margin-top: 18px; color: var(--muted); font-size: 15px; white-space: pre-line; max-width: 70ch; }
|
||||
|
||||
.dl-groups { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-top: 30px; }
|
||||
.dl-group { border-radius: var(--r-card); background: rgba(255, 255, 255, 0.66); border: 1px solid var(--line); padding: 22px; }
|
||||
.dl-group__head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; font-weight: 800; font-size: 16px; }
|
||||
.dl-group__head .icon { width: 21px; height: 21px; color: var(--ink); }
|
||||
.dl-group__sub { font-size: 13.5px; color: var(--muted); margin-bottom: 14px; }
|
||||
.dl-asset { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 0; }
|
||||
.dl-asset + .dl-asset { border-top: 1px solid var(--line); }
|
||||
.dl-asset__name { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
|
||||
.dl-asset__size { color: var(--muted); font-size: 12.5px; }
|
||||
.dl-asset .btn { flex: none; }
|
||||
.dl-reco { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
.rel-old { margin-top: 30px; display: flex; flex-direction: column; gap: 14px; }
|
||||
.rel-old details { border-radius: var(--r-card); border: 1px solid var(--line); background: rgba(255, 255, 255, 0.55); }
|
||||
.rel-old summary { cursor: pointer; padding: 18px 22px; font-weight: 700; display: flex; align-items: center; gap: 12px; list-style: none; }
|
||||
.rel-old summary::-webkit-details-marker { display: none; }
|
||||
.rel-old summary .rel-featured__date { margin-left: auto; }
|
||||
.rel-old__body { padding: 0 22px 20px; }
|
||||
|
||||
.rel-empty { border-radius: 24px; padding: 72px 40px; text-align: center; }
|
||||
.rel-empty .icon { width: 44px; height: 44px; color: var(--accent); }
|
||||
.rel-empty h2 { font-size: 24px; margin: 18px 0 10px; }
|
||||
.rel-empty p { color: var(--muted); max-width: 48ch; margin: 0 auto; }
|
||||
|
||||
/* ── 가이드 페이지 ──────────────────────────────────────────── */
|
||||
.guide-layout { display: grid; grid-template-columns: 240px 1fr; gap: 52px; align-items: start; padding-bottom: 110px; }
|
||||
.guide-toc { position: sticky; top: calc(var(--nav-h) + 30px); border-radius: var(--r-card); padding: 22px; }
|
||||
.guide-toc a { display: block; padding: 7px 10px; border-radius: 10px; color: var(--muted); text-decoration: none; font-size: 14.5px; font-weight: 600; }
|
||||
.guide-toc a:hover { color: var(--ink); background: rgba(255, 255, 255, 0.75); }
|
||||
.guide-step { padding: 44px 0; border-top: 1px solid var(--line); }
|
||||
.guide-step:first-of-type { border-top: 0; padding-top: 10px; }
|
||||
.guide-step h2 { font-size: 24px; display: flex; align-items: center; gap: 13px; }
|
||||
.guide-step h2 .featlist__icon { width: 38px; height: 38px; }
|
||||
.guide-step > p { color: var(--muted); margin-top: 14px; max-width: 66ch; }
|
||||
.guide-step ul { margin: 16px 0 0 2px; list-style: none; display: flex; flex-direction: column; gap: 9px; }
|
||||
.guide-step li { display: flex; gap: 10px; color: var(--ink); font-size: 15px; }
|
||||
.guide-step li .icon { width: 18px; height: 18px; color: var(--accent); margin-top: 4px; }
|
||||
.guide-step li span b { font-weight: 750; }
|
||||
.guide-media { margin-top: 24px; border-radius: var(--r-card); padding: 12px; max-width: 460px; }
|
||||
.guide-media img { display: block; width: 100%; border-radius: 12px; }
|
||||
.guide-media figcaption { font-size: 13px; color: var(--muted); padding: 10px 6px 2px; }
|
||||
.guide-faq { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.guide-faq details { border: 1px solid var(--line); border-radius: var(--r-chip); background: rgba(255, 255, 255, 0.55); }
|
||||
.guide-faq summary { cursor: pointer; padding: 15px 18px; font-weight: 700; font-size: 15px; list-style: none; }
|
||||
.guide-faq summary::-webkit-details-marker { display: none; }
|
||||
.guide-faq p { padding: 0 18px 16px; color: var(--muted); font-size: 14.5px; }
|
||||
|
||||
/* ── 모바일 반응형 ──────────────────────────────────────────── */
|
||||
@media (max-width: 860px) {
|
||||
.nav { gap: 14px; }
|
||||
.nav__links { display: none; }
|
||||
.hero { min-height: auto; padding-bottom: 64px; }
|
||||
.hero__grid, .mobile__grid { grid-template-columns: 1fr; gap: 40px; }
|
||||
.bento { grid-template-columns: 1fr; }
|
||||
.bento__cell--wide { min-height: 300px; }
|
||||
.featlist { grid-template-columns: 1fr; gap: 28px; }
|
||||
.section-pad { padding: 72px 0; }
|
||||
.band { padding: 48px 24px; }
|
||||
.band__oses { gap: 22px; flex-wrap: wrap; }
|
||||
.rel-layout, .guide-layout { grid-template-columns: 1fr; gap: 28px; }
|
||||
.rel-rail, .guide-toc { position: static; }
|
||||
.dl-groups { grid-template-columns: 1fr; }
|
||||
.mobile__stage { min-height: 380px; }
|
||||
.rel-featured { padding: 26px; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue