d3ro-voice/site/src/sections/Download.tsx

455 lines
23 KiB
TypeScript

import { useState, useEffect, type ChangeEvent, type DragEvent } from 'react'
import { Container } from '../components/Container'
import { SectionHeader } from '../components/SectionHeader'
import { Led } from '../components/Led'
import { useI18n } from '../i18n'
const OFFICIAL_HASH = 'b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2'
export function Download() {
const { locale } = useI18n()
const isKo = locale === 'ko'
const [detectedOs, setDetectedOs] = useState<'windows' | 'mac' | 'linux'>('windows')
const [copied, setCopied] = useState(false)
const [verifyStatus, setVerifyStatus] = useState<'idle' | 'computing' | 'match' | 'custom'>('idle')
const [computedHash, setComputedHash] = useState('')
const [fileName, setFileName] = useState('')
const [isDragOver, setIsDragOver] = useState(false)
useEffect(() => {
if (typeof window !== 'undefined') {
const ua = window.navigator.userAgent.toLowerCase()
if (ua.includes('mac') || ua.includes('darwin')) {
setDetectedOs('mac')
} else if (ua.includes('linux')) {
setDetectedOs('linux')
} else {
setDetectedOs('windows')
}
}
}, [])
const copyHash = (hash: string) => {
navigator.clipboard.writeText(hash).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}
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)
if (hashHex.toLowerCase() === OFFICIAL_HASH.toLowerCase()) {
setVerifyStatus('match')
} else {
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])
}
}
return (
<section id="download" className="py-24 md:py-32 bg-surface-900/60 border-t border-white/[0.04] relative">
<Container>
<SectionHeader
index="06 / DOWNLOAD"
title={isKo ? 'D3RO Voice 다운로드 및 배포 센터' : 'Download D3RO Voice Desktop'}
subtitle={
isKo
? '100% 로컬 Whisper 전사 엔진과 AI 음성 비서를 데스크톱에 무료로 설치하세요. 클라우드 전송 없이 프라이버시가 완벽히 보장됩니다.'
: 'Zero-latency on-device speech assistant with offline Whisper model. Instant installation for Windows, macOS, and private servers.'
}
/>
{/* Primary Recommended OS Download Card */}
<div className="max-w-3xl mx-auto mb-16 p-8 md:p-10 rounded-2xl bg-surface-800/90 border border-brand-amber/40 shadow-glow-md relative overflow-hidden backdrop-blur-md">
<div className="absolute top-0 right-0 w-64 h-64 bg-brand-amber/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-8">
<div className="flex items-center gap-4">
<div className="w-14 h-14 rounded-xl bg-brand-amber/15 border border-brand-amber/30 flex items-center justify-center text-brand-amber shadow-sm">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<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 mb-1">
<h3 className="font-display text-xl font-bold text-neutral-100">
{detectedOs === 'mac' ? 'macOS Apple Silicon' : 'Windows 64-bit Installer'}
</h3>
<span className="px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">
{isKo ? '추천 빌드' : 'RECOMMENDED'}
</span>
</div>
<p className="font-mono text-xs text-neutral-400">
{detectedOs === 'mac'
? 'D3RO-Voice-1.0.0-arm64.dmg · 98 MB · Metal Hardware Accel'
: 'D3RO-Voice-Setup-1.0.0-x64.exe · 102 MB · NSIS Installer'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Led color="green" pulse />
<span className="font-mono text-xs text-neutral-400">v1.0.0 STABLE</span>
</div>
</div>
{/* Primary Action Button */}
<div className="mb-6">
<a
href={
detectedOs === 'mac'
? 'https://git.chanpaca.net/yunchan/d3ro-voice/releases'
: 'https://git.chanpaca.net/attachments/c281a224-2538-496a-bf74-bc188938c734'
}
download={detectedOs === 'mac' ? undefined : 'D3RO-Voice-Setup-1.0.0-x64.exe'}
className="w-full py-4 px-8 rounded-xl bg-brand-amber hover:bg-brand-amber-light text-white font-mono text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-3 shadow-glow-sm hover:shadow-glow-md transition-all active:scale-[0.99]"
>
<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>
{detectedOs === 'mac'
? isKo
? 'macOS용 다운로드 (v1.0.0 DMG)'
: 'Download for macOS (v1.0.0)'
: isKo
? 'Windows용 다운로드 (v1.0.0 NSIS)'
: 'Download for Windows (v1.0.0)'}
</span>
</a>
</div>
{/* SHA-256 Quick Verification Strip */}
<div className="pt-4 border-t border-white/[0.08] flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-xs">
<div className="flex items-center gap-2 font-mono text-neutral-400">
<span>SHA-256:</span>
<span className="text-neutral-200 truncate max-w-[280px]">b0ac051443151a2e34e8192f1fcf7955...</span>
<button
type="button"
onClick={() => copyHash(OFFICIAL_HASH)}
className="text-brand-amber hover:text-brand-amber-light underline font-sans text-xs ml-1"
>
{copied ? (isKo ? '복사됨!' : 'Copied!') : isKo ? '복사' : 'Copy'}
</button>
</div>
<span className="font-mono text-[11px] text-emerald-400 flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
{isKo ? '코드 서명 및 무결성 검증 완료' : 'Signed & Verified'}
</span>
</div>
</div>
{/* All Platform Packages Bento Grid */}
<div className="mb-20">
<div className="flex items-center justify-between mb-6">
<h3 className="font-display text-lg font-bold text-neutral-200">
{isKo ? '전체 플랫폼 설치 패키지' : 'All Platform Packages'}
</h3>
<span className="font-mono text-xs text-neutral-500">Auto-Update Feed: latest.yml Active</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Windows Package */}
<div className="p-6 rounded-xl bg-surface-800/60 border border-white/[0.06] hover:border-brand-amber/30 transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-xs font-semibold text-brand-amber">WINDOWS 10 / 11</span>
<span className="font-mono text-[10px] text-neutral-500">x64</span>
</div>
<h4 className="font-display text-base font-bold text-neutral-100 mb-1">Windows Setup</h4>
<p className="text-xs text-neutral-400 mb-6 leading-relaxed">
{isKo
? '원클릭 NSIS 설치 프로그램. 백그라운드 자동 업데이트 및 DirectML GPU 가속 지원.'
: 'One-click installer with automatic background delta updates and DirectML acceleration.'}
</p>
</div>
<div className="space-y-2">
<a
href="https://git.chanpaca.net/attachments/c281a224-2538-496a-bf74-bc188938c734"
download="D3RO-Voice-Setup-1.0.0-x64.exe"
className="w-full py-2.5 px-4 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.08] hover:border-brand-amber/40 text-neutral-200 font-mono text-xs flex items-center justify-between transition-all"
>
<span className="font-semibold">Setup Installer (.exe)</span>
<span className="text-neutral-400">102 MB</span>
</a>
<a
href="https://git.chanpaca.net/yunchan/d3ro-voice/releases"
target="_blank"
rel="noreferrer"
className="w-full py-2 px-4 rounded-lg bg-surface-900/40 text-neutral-500 font-mono text-[11px] flex items-center justify-between"
>
<span>Blockmap (.blockmap)</span>
<span>105 KB</span>
</a>
</div>
</div>
{/* macOS Package */}
<div className="p-6 rounded-xl bg-surface-800/60 border border-white/[0.06] hover:border-brand-amber/30 transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-xs font-semibold text-purple-400">MACOS 12.0+</span>
<span className="font-mono text-[10px] text-neutral-500">Apple Silicon & Intel</span>
</div>
<h4 className="font-display text-base font-bold text-neutral-100 mb-1">macOS DMG</h4>
<p className="text-xs text-neutral-400 mb-6 leading-relaxed">
{isKo
? 'Apple Silicon M1~M4 Metal 가속 지원 및 Universal DMG 설치 패키지.'
: 'Universal package optimized for Apple Silicon (M1/M2/M3/M4) Metal framework.'}
</p>
</div>
<div className="space-y-2">
<a
href="https://git.chanpaca.net/yunchan/d3ro-voice/releases"
target="_blank"
rel="noreferrer"
className="w-full py-2.5 px-4 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.08] hover:border-purple-400/40 text-neutral-200 font-mono text-xs flex items-center justify-between transition-all"
>
<span className="font-semibold">Apple Silicon DMG (.dmg)</span>
<span className="text-neutral-400">98 MB</span>
</a>
<a
href="https://git.chanpaca.net/yunchan/d3ro-voice/releases"
target="_blank"
rel="noreferrer"
className="w-full py-2 px-4 rounded-lg bg-surface-900/40 text-neutral-500 font-mono text-[11px] flex items-center justify-between"
>
<span>Portable (.zip)</span>
<span>96 MB</span>
</a>
</div>
</div>
{/* Android & iOS Mobile App Package */}
<div className="p-6 rounded-xl bg-surface-800/60 border border-white/[0.06] hover:border-brand-amber/30 transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-xs font-semibold text-emerald-400">ANDROID & IOS</span>
<span className="font-mono text-[10px] text-neutral-500">Mobile Edition</span>
</div>
<h4 className="font-display text-base font-bold text-neutral-100 mb-1">D3RO Voice Mobile</h4>
<p className="text-xs text-neutral-400 mb-6 leading-relaxed">
{isKo
? '스마트폰에서도 동일하게 작동하는 로컬 음성 비서 및 실시간 마이크 연동. 이동 중에도 끊김 없는 고속 음성 기록.'
: 'On-device voice assistant for smartphones with instant mic streaming and seamless cloud sync.'}
</p>
</div>
<div className="space-y-2">
<a
href="https://git.chanpaca.net/attachments/0b015367-dd8b-488c-8cc0-4db413b51792"
download="d3ro-voice-v1.0.0-signed.apk"
className="w-full py-2.5 px-4 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.08] hover:border-emerald-400/40 text-neutral-200 font-mono text-xs flex items-center justify-between transition-all"
>
<span className="font-semibold">Android Release APK (.apk)</span>
<span className="text-emerald-400">Download (47.5MB) </span>
</a>
<a
href="#download"
className="w-full py-2 px-4 rounded-lg bg-surface-900/40 text-neutral-400 font-mono text-[11px] flex items-center justify-between"
>
<span>{isKo ? 'iOS App Store / TestFlight' : 'iOS TestFlight'}</span>
<span className="text-neutral-500">Coming Soon</span>
</a>
</div>
</div>
</div>
</div>
{/* Live Client-Side SHA-256 Verifier */}
<div className="mb-20 p-8 rounded-2xl bg-surface-800/40 border border-white/[0.06] backdrop-blur-sm">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-6">
<div>
<span className="font-mono text-nano text-brand-amber uppercase tracking-widest block mb-1">
CRYPTOGRAPHIC INTEGRITY
</span>
<h3 className="font-display text-xl font-bold text-neutral-100">
{isKo ? '실시간 SHA-256 무결성 검증기' : 'SHA-256 Binary Integrity Verifier'}
</h3>
<p className="text-xs text-neutral-400">
{isKo
? '다운로드한 파일을 브라우저로 끌어다 놓아 위변조 및 해시 일치를 로컬에서 즉시 확인하세요.'
: 'Drag and drop your installer file to compute SHA-256 locally via browser WebCrypto API.'}
</p>
</div>
<label className="px-4 py-2 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.1] text-neutral-200 font-mono text-xs font-semibold cursor-pointer transition-all">
{isKo ? '파일 선택하여 검증' : 'Select File to Verify'}
<input type="file" className="hidden" onChange={handleFileChange} />
</label>
</div>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`border-2 border-dashed rounded-xl p-8 text-center transition-all ${
isDragOver ? 'border-brand-amber bg-brand-amber/10' : 'border-white/[0.08] bg-surface-900/40'
}`}
>
{verifyStatus === 'idle' && (
<div>
<p className="font-mono text-xs text-neutral-300 mb-1">
{isKo
? '설치 파일(.exe / .dmg / .zip)을 이곳에 끌어다 놓으세요'
: 'Drop installer file (.exe / .dmg / .zip) here'}
</p>
<p className="font-mono text-[11px] text-neutral-500">
{isKo ? '파일은 서버로 전송되지 않고 로컬에서 안전하게 계산됩니다.' : 'Processed locally in browser.'}
</p>
</div>
)}
{verifyStatus !== 'idle' && (
<div className="text-left font-mono text-xs space-y-2">
<div className="flex items-center justify-between">
<span className="font-bold text-neutral-200">{fileName}</span>
{verifyStatus === 'computing' && (
<span className="px-2 py-0.5 rounded bg-amber-500/20 text-amber-300">Computing...</span>
)}
{verifyStatus === 'match' && (
<span className="px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-400 font-bold">
{isKo ? '공식 릴리스 일치 (정상)' : 'OFFICIAL MATCH (GENUINE)'}
</span>
)}
{verifyStatus === 'custom' && (
<span className="px-2 py-0.5 rounded bg-brand-amber/20 text-brand-amber font-bold">
{isKo ? '로컬 해시 계산 완료' : 'LOCAL HASH COMPUTED'}
</span>
)}
</div>
<div className="p-3 rounded bg-surface-950/60 border border-white/[0.04] space-y-1">
<div className="text-neutral-400">
Calculated:{' '}
<span className="text-brand-amber">{computedHash || (isKo ? '계산 중...' : 'Hashing...')}</span>
</div>
<div className="text-neutral-400">
Official:{' '}<span className="text-emerald-400">{OFFICIAL_HASH}</span>
</div>
</div>
</div>
)}
</div>
</div>
{/* Release History Section */}
<div id="releases">
<div className="flex items-center justify-between mb-8">
<div>
<span className="font-mono text-nano text-brand-amber uppercase tracking-widest block mb-1">
VERSION ARCHIVE
</span>
<h3 className="font-display text-xl font-bold text-neutral-100">
{isKo ? '릴리스 버전 히스토리' : 'Release Version History'}
</h3>
</div>
<a
href="https://git.chanpaca.net/yunchan/d3ro-voice/releases"
target="_blank"
rel="noreferrer"
className="font-mono text-xs text-brand-amber hover:underline flex items-center gap-1"
>
<span>{isKo ? 'Forgejo Git 릴리스 전체 보기' : 'Forgejo Git Releases'}</span>
<span></span>
</a>
</div>
<div className="space-y-4">
{/* v1.0.0 */}
<div className="p-6 rounded-xl bg-surface-800/80 border-l-4 border-l-brand-amber border border-white/[0.06]">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-3">
<div className="flex items-center gap-3">
<span className="font-mono text-base font-bold text-neutral-100">v1.0.0</span>
<span className="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-brand-amber/20 text-brand-amber border border-brand-amber/30">
LATEST STABLE
</span>
<span className="font-mono text-xs text-neutral-500">2026-08-20</span>
</div>
<a
href="/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe"
download
className="px-3 py-1.5 rounded-lg bg-brand-amber/15 hover:bg-brand-amber/25 text-brand-amber font-mono text-xs font-bold border border-brand-amber/30 transition-all text-center"
>
Installer (.exe)
</a>
</div>
<ul className="text-xs text-neutral-300 space-y-1.5 mb-4 list-disc list-inside">
<li>
<strong className="text-brand-amber">10+ </strong>: EthicalAds, Carbon Ads, GAM, Playwire, AppLovin, Unity Ads .
</li>
<li>
<strong className="text-brand-amber"> </strong>: 15 +50 Cloud AI .
</li>
<li>
<strong className="text-brand-amber">100% Whisper </strong>: .
</li>
<li>
<strong className="text-brand-amber">Android & iOS </strong>: AI .
</li>
</ul>
<div className="p-2.5 rounded bg-surface-950/60 font-mono text-[11px] text-neutral-400 flex items-center justify-between">
<span className="truncate max-w-[450px]">SHA-256: b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2</span>
<span className="text-brand-amber">102 MB</span>
</div>
</div>
{/* v0.2.1-alpha */}
<div className="p-5 rounded-xl bg-surface-800/40 border-l-4 border-l-neutral-700 border border-white/[0.04] opacity-80">
<div className="flex items-center gap-3 mb-2">
<span className="font-mono text-sm font-bold text-neutral-200">v0.2.1-alpha</span>
<span className="px-2 py-0.5 rounded text-[10px] font-mono bg-white/5 text-neutral-400">PRE-RELEASE</span>
<span className="font-mono text-xs text-neutral-500">2026-08-15</span>
</div>
<p className="text-xs text-neutral-400">
Deepgram, AssemblyAI, Groq STT .
</p>
</div>
</div>
</div>
</Container>
</section>
)
}