feat(site): rewrite the landing page around verified facts and accessible controls

- Replace claims that contradicted the app: the default hotkey is Right Alt
  (hold to talk), local features are free with no daily cap, there is no
  14-day trial, Team/SSO/SCIM/ZDR are not offered, and the repository is not
  public. Remove invented metrics, status badges, the competitor table, the
  ad-mediation changelog, the hash calculator and the duplicate demo.
- Seven sections: hero with one labelled example, features, how it works,
  privacy, pricing (Free / Pro 2,900 / Pro+ 8,900 KRW a month), download, FAQ.
  Footer links the privacy policy, terms and account deletion pages.
- All copy moves into i18n and every one of the 10 locales is translated.
  Pricing and cloud quotas come from site/src/pricing.ts.
- Accessibility: skip link, labelled sections, aria-expanded with Esc and
  focus return for the menu, language list and FAQ, live status for the
  example, reduced-motion support, 44px targets, AA contrast on the primary
  button. Korean keep-all line breaking is scoped to :lang(ko) because
  Tailwind's break-keep blocked wrapping in Japanese and Chinese.
- JS 111 -> 98 KB gzip, CSS 7.8 -> 5.2 KB gzip.
This commit is contained in:
Yun Chan 2026-09-26 15:04:43 +09:00
parent ad6bb70c20
commit e689683b72
44 changed files with 2198 additions and 3782 deletions

View file

@ -1,399 +1,69 @@
import { useState, type ChangeEvent, type DragEvent } from 'react'
import { Container } from '../components/Container'
import { SectionHeader } from '../components/SectionHeader'
import { useI18n } from '../i18n'
import { SectionHeading } from '../components/SectionHeading'
import { DownloadIcon, ExternalIcon } from '../components/icons'
import { fmt, useI18n } from '../i18n'
import {
DESKTOP_FEED_URL,
DESKTOP_RELEASE_DATE,
DESKTOP_RELEASE_HUB_URL,
DESKTOP_VERSION,
DESKTOP_WINDOWS_INSTALLER_FILENAME,
DESKTOP_WINDOWS_INSTALLER_URL,
} from '../release'
export function Download() {
const { locale } = useI18n()
const isKo = locale === 'ko'
const { t, bcp47 } = useI18n()
const [verifyStatus, setVerifyStatus] = useState<'idle' | 'computing' | 'custom'>('idle')
const [computedHash, setComputedHash] = useState('')
const [fileName, setFileName] = useState('')
const [isDragOver, setIsDragOver] = useState(false)
const computeFileHash = async (file: File) => {
setFileName(`${file.name} (${(file.size / (1024 * 1024)).toFixed(1)} MB)`)
setVerifyStatus('computing')
try {
const arrayBuffer = await file.arrayBuffer()
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
setComputedHash(hashHex)
setVerifyStatus('custom')
} catch {
setVerifyStatus('custom')
}
}
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (files && files.length > 0) {
void computeFileHash(files[0])
}
}
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragOver(true)
}
const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragOver(false)
}
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragOver(false)
const files = e.dataTransfer.files
if (files && files.length > 0) {
void computeFileHash(files[0])
}
}
const date = new Intl.DateTimeFormat(bcp47, { dateStyle: 'long', timeZone: 'UTC' }).format(
new Date(`${DESKTOP_RELEASE_DATE}T00:00:00Z`),
)
const details = fmt(t.download.details, { date })
return (
<section id="download" className="py-20 md:py-28 bg-surface-900/60 border-t border-white/[0.04] relative">
<section id="download" aria-labelledby="download-title" className="section border-t border-white/10">
<Container>
<SectionHeader
index="06 / DOWNLOAD"
title={isKo ? 'D3RO Voice 릴리스 센터' : 'D3RO Voice Release Center'}
subtitle={
isKo
? `공식 릴리스 v${DESKTOP_VERSION} 설치 파일을 게시된 피드에서 내려받을 수 있습니다. 설치 후 자동 업데이트로 이후 버전을 받습니다.`
: `Download the official v${DESKTOP_VERSION} installer from the published update feed. Auto-update delivers later versions after install.`
}
/>
<SectionHeading id="download-title" title={t.download.title} subtitle={t.download.subtitle} />
{/* Primary Recommended OS Download Card */}
<div className="max-w-3xl mx-auto mb-16 p-7 md:p-9 rounded-3xl bg-surface-800/90 border border-brand-blue/40 shadow-glow-md relative overflow-hidden backdrop-blur-2xl noise-texture">
<div className="absolute top-0 right-0 w-64 h-64 bg-brand-blue/10 rounded-full blur-3xl pointer-events-none" />
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6 mb-7">
<div className="flex items-center gap-4">
<div className="w-13 h-13 rounded-2xl bg-brand-blue/15 border border-brand-blue/30 flex items-center justify-center text-brand-blue shadow-sm p-3">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
</div>
<div>
<div className="flex items-center gap-2.5 mb-1">
<h3 className="text-xl sm:text-2xl font-medium text-neutral-50">
D3RO Voice Desktop {DESKTOP_VERSION}
</h3>
<span className="px-2.5 py-0.5 rounded-full text-xs font-medium bg-emerald-500/20 text-emerald-300 border border-emerald-500/35">
{isKo ? '공식 릴리스' : 'OFFICIAL STABLE RELEASE'}
</span>
</div>
<p className="text-xs sm:text-sm text-neutral-300 font-normal">
Windows 10 / 11 (x64) · NSIS standalone installer
</p>
</div>
</div>
<div className="flex items-center gap-2 bg-surface-700/80 px-3 py-1 rounded-full border border-white/10">
<span className="w-2 h-2 rounded-full bg-emerald-400" />
<span className="text-xs font-medium text-neutral-200">v{DESKTOP_VERSION} RELEASE</span>
</div>
</div>
{/* Primary Action Button */}
<div className="mb-5">
<a
href={DESKTOP_WINDOWS_INSTALLER_URL}
className="glow-btn w-full py-4.5 sm:py-5 px-8 sm:px-10 rounded-2xl bg-brand-blue text-white text-base sm:text-lg font-medium flex items-center justify-center gap-3 leading-normal shadow-glow-sm hover:shadow-glow-md active:scale-[0.99] transition-all"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
<span>{isKo ? `Windows용 다운로드 (x64) - v${DESKTOP_VERSION}` : `Download for Windows (x64) - v${DESKTOP_VERSION}`}</span>
</a>
</div>
{/* Update Feed Verification Strip */}
<div className="pt-3.5 border-t border-white/10 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-xs">
<div className="flex items-center gap-2 text-neutral-300">
<span className="font-medium text-neutral-400">SHA-512:</span>
<a
href={`${DESKTOP_FEED_URL}/latest.yml`}
target="_blank"
rel="noreferrer"
className="font-mono text-neutral-200 bg-surface-900/60 px-2 py-0.5 rounded border border-white/10 hover:text-brand-blue"
>
latest.yml
</a>
</div>
<div className="flex items-center gap-3">
<a
href={DESKTOP_RELEASE_HUB_URL}
target="_blank"
rel="noreferrer"
className="text-brand-blue hover:underline"
>
{isKo ? '릴리스 노트' : 'Release notes'}
</a>
<span className="text-xs text-emerald-300 font-medium flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-emerald-400" />
{isKo ? '업데이트 피드 연결됨' : 'Update feed connected'}
</span>
</div>
</div>
</div>
{/* All Platform Packages Bento Grid */}
<div className="mb-16">
<div className="flex items-center justify-between mb-5">
<h3 className="text-xl font-medium text-neutral-100">
{isKo ? '전체 플랫폼 설치 패키지' : 'All Platform Packages'}
</h3>
<span className="text-xs text-neutral-400 font-normal">
{isKo ? '자동 업데이트: 서명된 피드 검증 후 활성화' : 'Auto-update: activates after signed feed verification'}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Windows Package */}
<div className="p-6 rounded-2xl bg-surface-800/80 border border-white/10 hover:border-brand-blue/40 shadow-glass-card backdrop-blur-xl transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-brand-blue">WINDOWS 10 / 11</span>
<span className="font-mono text-xs text-neutral-400">x64</span>
</div>
<h4 className="text-lg font-medium text-neutral-50 mb-1.5">Windows Setup</h4>
<p className="text-xs sm:text-sm text-neutral-300 mb-5 leading-relaxed font-normal">
{isKo
? '무설정 자동 업데이트와 오프라인 Whisper 로컬 AI를 지원하는 공식 설치 패키지입니다.'
: 'Official installation package with background auto-updates and zero-latency local AI.'}
</p>
</div>
<div className="space-y-2">
<a
href={DESKTOP_WINDOWS_INSTALLER_URL}
className="w-full py-2.5 px-3.5 rounded-xl bg-brand-blue/20 hover:bg-brand-blue/30 border border-brand-blue/40 text-brand-blue text-xs font-medium flex items-center justify-between transition-all"
>
<span>{DESKTOP_WINDOWS_INSTALLER_FILENAME}</span>
<span>{isKo ? '다운로드 (.exe)' : 'Download (.exe)'}</span>
</a>
</div>
</div>
{/* macOS Package */}
<div className="p-6 rounded-2xl bg-surface-800/80 border border-white/10 hover:border-purple-400/40 shadow-glass-card backdrop-blur-xl transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-purple-300">MACOS 12.0+</span>
<span className="font-mono text-xs text-neutral-400">Apple Silicon</span>
</div>
<h4 className="text-lg font-medium text-neutral-50 mb-1.5">macOS DMG</h4>
<p className="text-xs sm:text-sm text-neutral-300 mb-5 leading-relaxed font-normal">
{isKo
? 'Apple Silicon 후보 빌드의 코드 서명과 설치 동작을 검증 중입니다.'
: 'The Apple Silicon candidate is undergoing code-signing and installation verification.'}
</p>
</div>
<div className="space-y-2">
<div
aria-disabled="true"
className="w-full py-2.5 px-3.5 rounded-xl bg-surface-900/60 border border-white/10 text-neutral-400 text-xs font-medium flex items-center justify-between"
>
<span>Apple Silicon DMG</span>
<span>{isKo ? '검증 대기' : 'Verification pending'}</span>
</div>
</div>
</div>
{/* Android / iOS Mobile */}
<div className="p-6 rounded-2xl bg-surface-800/80 border border-white/10 hover:border-emerald-500/40 shadow-glass-card backdrop-blur-xl transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-emerald-400">ANDROID</span>
<span className="text-xs text-neutral-400">Mobile Edition</span>
</div>
<h4 className="text-lg font-medium text-neutral-50 mb-1.5">D3RO Voice Mobile</h4>
<p className="text-xs sm:text-sm text-neutral-300 mb-5 leading-relaxed font-normal">
{isKo
? 'Google Play용 AAB와 스토어 메타데이터의 검증을 마친 뒤 Android 배포를 시작합니다.'
: 'Android distribution begins after the Play AAB and store metadata are verified.'}
</p>
</div>
<div className="space-y-2">
<div
aria-disabled="true"
className="w-full py-2.5 px-3.5 rounded-xl bg-surface-900/60 border border-white/10 text-neutral-400 text-xs font-medium flex items-center justify-between"
>
<span>{isKo ? 'Android 공식 APK' : 'Official Android APK'}</span>
<span>{isKo ? '서명 증거 확인 대기' : 'Unavailable — evidence pending'}</span>
</div>
<div className="w-full py-2 px-3.5 rounded-xl bg-surface-900/50 text-neutral-500 text-xs flex items-center justify-between">
<span>iOS App Store / TestFlight</span>
<span>{isKo ? '검증된 배포 없음' : 'No verified distribution'}</span>
</div>
</div>
</div>
</div>
</div>
{/* Client-Side Cryptographic Verifier Box */}
<div className="p-7 md:p-8 rounded-3xl bg-surface-800/70 border border-white/10 shadow-glass-card backdrop-blur-xl mb-16">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-6">
<div>
<span className="text-xs font-medium text-brand-blue uppercase tracking-wider block mb-1">
LOCAL FILE UTILITY
</span>
<h3 className="text-lg sm:text-xl font-medium text-neutral-50 mb-1">
{isKo ? '로컬 SHA-256 계산기' : 'Client-Side SHA-256 Calculator'}
<div className="mt-14 grid grid-cols-1 gap-5 lg:grid-cols-12">
<div className="rounded-2xl border border-brand-blue/50 bg-surface-800 p-6 md:p-9 lg:col-span-8">
<div className="flex flex-wrap items-baseline justify-between gap-3">
<h3 className="text-h3 font-medium text-neutral-50">
{t.download.windowsName}{' '}
<span className="font-mono text-base font-normal text-neutral-300">v{DESKTOP_VERSION}</span>
</h3>
<p className="text-xs sm:text-sm text-neutral-300 font-normal">
{isKo
? '선택한 파일의 SHA-256 값을 브라우저에서 계산합니다. 공식 릴리스 판정은 제공하지 않습니다.'
: 'Compute a selected file hash locally with WebCrypto. This does not certify an official release.'}
</p>
<p className="text-sm text-neutral-300">{t.download.windowsMeta}</p>
</div>
<label className="px-4 py-2 rounded-xl bg-surface-700 hover:bg-surface-600 border border-white/10 hover:border-brand-blue/40 text-neutral-200 text-xs font-medium cursor-pointer transition-all flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
<span>{isKo ? '파일 선택하여 검증' : 'Select File'}</span>
<input type="file" onChange={handleFileChange} className="hidden" />
</label>
</div>
{/* Drag & Drop Target Area */}
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`p-7 rounded-2xl border-2 border-dashed transition-all text-center ${
isDragOver
? 'border-brand-blue bg-brand-blue/10'
: 'border-white/15 bg-surface-900/50 hover:border-white/25'
}`}
>
{verifyStatus === 'idle' && (
<div className="space-y-1.5 text-neutral-400">
<p className="text-xs sm:text-sm font-normal text-neutral-200">
{isKo ? '설치 파일(.exe / .dmg / .zip)을 이곳에 끌어다 놓으세요' : 'Drop installer file here to compute cryptographic hash'}
</p>
<p className="text-xs text-neutral-400 font-normal">
{isKo ? '파일은 서버로 전송되지 않고 로컬 브라우저에서 안전하게 계산됩니다.' : 'Calculated in-memory locally via browser WebCrypto.'}
</p>
</div>
)}
{verifyStatus === 'computing' && (
<div className="flex items-center justify-center gap-3 text-brand-blue text-xs sm:text-sm font-medium">
<span className="w-2 h-2 rounded-full bg-brand-blue animate-ping" />
<span>{isKo ? 'SHA-256 해시 계산 중...' : 'Computing SHA-256 hash locally...'}</span>
</div>
)}
{verifyStatus === 'custom' && (
<div className="space-y-2 text-left bg-surface-950 p-4 rounded-xl border border-white/10">
<div className="flex items-center justify-between text-xs text-neutral-300">
<span className="font-medium text-brand-blue">{isKo ? '로컬 해시 계산 완료' : 'Local Hash Computed'}</span>
<span>{fileName}</span>
</div>
<div className="font-mono text-xs text-neutral-200 break-all pt-1 border-t border-white/10">
{computedHash}
</div>
</div>
)}
</div>
</div>
{/* Release History Section */}
<div>
<div className="flex items-center justify-between mb-5">
<div>
<span className="text-xs font-medium text-brand-blue uppercase tracking-wider block mb-1">
VERSION ARCHIVE
</span>
<h3 className="text-xl font-medium text-neutral-50">
{isKo ? '릴리스 버전 히스토리' : 'Release Archive & Changelog'}
</h3>
</div>
<a
href={DESKTOP_RELEASE_HUB_URL}
target="_blank"
rel="noreferrer"
className="text-xs text-brand-blue hover:text-brand-blue-light transition-colors font-medium flex items-center gap-1.5"
>
<span>Forgejo Git 릴리스 전체 보기</span>
<span>→</span>
<a href={DESKTOP_WINDOWS_INSTALLER_URL} className="btn btn-primary btn-lg mt-7 w-full sm:w-auto">
<DownloadIcon />
{t.download.cta}
</a>
<p className="mt-5 keep-ko text-sm leading-relaxed text-neutral-400">{details}</p>
<p className="mt-3 flex flex-wrap gap-x-6 text-sm">
<a href={DESKTOP_RELEASE_HUB_URL} target="_blank" rel="noopener" className="link inline-flex min-h-[44px] items-center gap-1">
{t.download.releaseNotes}
<ExternalIcon />
<span className="sr-only">({t.a11y.opensInNewTab})</span>
</a>
<a href={`${DESKTOP_FEED_URL}/latest.yml`} target="_blank" rel="noopener" className="link inline-flex min-h-[44px] items-center gap-1">
{t.download.checksum}
<ExternalIcon />
<span className="sr-only">({t.a11y.opensInNewTab})</span>
</a>
</p>
</div>
<div className="space-y-4">
{/* v1.0.0 Release */}
<div className="p-6 rounded-2xl bg-surface-800/80 border-l-4 border-l-brand-blue border border-white/10 shadow-glass-card">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-3 pb-3 border-b border-white/[0.06]">
<div className="flex items-center gap-3">
<span className="text-base font-medium text-neutral-100">v1.0.0</span>
<span className="px-2.5 py-0.5 rounded-full text-xs font-medium bg-brand-blue/15 text-brand-blue border border-brand-blue/30">
ARCHIVED · DOWNLOAD UNAVAILABLE
</span>
<span className="text-xs text-neutral-400">2026-08-20</span>
</div>
<span
aria-disabled="true"
className="px-3.5 py-1.5 rounded-xl bg-surface-900/60 text-neutral-400 border border-white/10 text-xs font-medium self-start sm:self-auto"
>
{isKo ? '과거 바이너리 미제공' : 'Historical binary unavailable'}
</span>
</div>
<ul className="space-y-1.5 text-xs sm:text-sm text-neutral-300 mb-3.5 font-normal leading-relaxed">
<li className="flex items-start gap-2">
<span className="text-brand-blue font-medium">•</span>
<span><strong className="text-neutral-100 font-medium">10+ 글로벌 광고 미디에이션</strong>: EthicalAds, Carbon Ads, GAM, Playwire, AppLovin, Unity Ads 실시간 입찰.</span>
<div className="rounded-2xl border border-white/10 p-6 md:p-7 lg:col-span-4">
<h3 className="text-sm font-medium text-neutral-300">{t.download.soonTitle}</h3>
<ul className="mt-4 divide-y divide-white/10">
{t.download.soon.map((item) => (
<li key={item.title} className="py-4 first:pt-0 last:pb-0">
<p className="text-base text-neutral-100">{item.title}</p>
<p className="mt-1 keep-ko text-sm leading-relaxed text-neutral-400">{item.body}</p>
</li>
<li className="flex items-start gap-2">
<span className="text-brand-blue font-medium">•</span>
<span><strong className="text-neutral-100 font-medium">무료 티어 리워드 충전</strong>: 15초 스폰서 비디오 시청 완료 시 +50 Cloud AI 토큰 자동 리필.</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand-blue font-medium">•</span>
<span><strong className="text-neutral-100 font-medium">100% 로컬 Whisper 전사 엔진</strong>: 네트워크 연결 없이 실시간 고속 오프라인 음성 텍스트 변환.</span>
</li>
<li className="flex items-start gap-2">
<span className="text-brand-blue font-medium">•</span>
<span><strong className="text-neutral-100 font-medium">모바일 TEST-DEMO</strong>: 공식 서명 증거와 스토어 심사가 끝날 때까지 일반 다운로드는 제공하지 않습니다.</span>
</li>
</ul>
<div className="text-xs text-neutral-400 p-2.5 rounded-lg bg-surface-950 border border-white/[0.04]">
{isKo
? '이 항목은 변경 이력으로만 보존합니다. 설치 파일, 고정 해시, 용량 정보는 현재 릴리스로 제공하지 않습니다.'
: 'This entry is retained only as history. Its installer, fixed hash, and size are not offered as a current release.'}
</div>
</div>
{/* v0.2.1-alpha */}
<div className="p-5 rounded-2xl bg-surface-800/50 border-l-4 border-l-surface-600 border border-white/5 opacity-75">
<div className="flex items-center gap-3 mb-2">
<span className="text-sm font-medium text-neutral-200">v0.2.1-alpha</span>
<span className="px-2 py-0.5 rounded-full text-xs font-normal bg-white/5 text-neutral-400 border border-white/10">PRE-RELEASE</span>
<span className="text-xs text-neutral-500 font-mono">2026-08-15</span>
</div>
<p className="text-xs text-neutral-300 font-normal">
Deepgram, AssemblyAI, Groq 멀티 클라우드 STT 드라이버 디스패처 및 암호화 라이선스 키 검증 엔진 탑재.
</p>
</div>
))}
</ul>
</div>
</div>
</Container>