feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View file

@ -10,24 +10,24 @@ export function CTA() {
const clientOS = useClientOS()
return (
<section className="relative py-28 md:py-36 overflow-hidden">
<section className="relative py-24 md:py-32 overflow-hidden">
{/* Background radial glow */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[500px] bg-brand-amber/[0.045] rounded-full blur-[140px] pointer-events-none" />
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[550px] bg-brand-blue/[0.06] rounded-full blur-[160px] pointer-events-none" />
<Container className="relative text-center">
<Crosshair className="inline-block mb-8">
<WaveBars className="h-9 px-3" />
<Crosshair className="inline-block mb-7">
<WaveBars className="h-8 px-4" />
</Crosshair>
<h2
className="font-display text-display font-extrabold text-neutral-50 mb-6 max-w-3xl mx-auto tracking-tight break-keep"
className="text-3xl sm:text-4xl md:text-5xl font-medium text-neutral-50 mb-5 max-w-3xl mx-auto tracking-tight leading-[1.2] break-keep"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
{t.cta.title1} <span className="text-brand-amber">{t.cta.title2}</span>
{t.cta.title1} <span className="text-brand-blue font-medium">{t.cta.title2}</span>
</h2>
<p
className="text-base md:text-lg text-neutral-400 max-w-xl mx-auto mb-8 leading-relaxed break-keep"
className="text-base sm:text-lg text-neutral-300 max-w-xl mx-auto mb-7 leading-relaxed font-normal break-keep"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
{t.cta.subtitle1}
@ -36,40 +36,40 @@ export function CTA() {
</p>
{/* Hotkey Hint Box */}
<div className="inline-flex items-center gap-2 mb-10 px-4 py-2 rounded-xl bg-surface-800/80 border border-white/[0.08] shadow-inner">
<span className="font-mono text-nano text-neutral-400 uppercase tracking-widest">
Push-to-Talk:
<div className="inline-flex items-center gap-3 mb-9 px-4.5 py-2.5 rounded-2xl bg-surface-800/90 border border-white/10 shadow-glass-card">
<span className="text-xs text-neutral-400 font-normal">
:
</span>
<div className="flex items-center gap-1 font-mono text-xs font-semibold text-neutral-200">
<span className="px-2 py-0.5 rounded bg-surface-600 border border-white/[0.1]">Ctrl</span>
<span className="text-neutral-500">+</span>
<span className="px-2 py-0.5 rounded bg-surface-600 border border-white/[0.1]">Shift</span>
<span className="text-neutral-500">+</span>
<span className="px-2.5 py-0.5 rounded bg-brand-amber/20 border border-brand-amber/40 text-brand-amber">Space</span>
<div className="flex items-center gap-1.5 font-mono text-xs font-medium text-neutral-200">
<span className="px-2.5 py-1 rounded-md bg-surface-700 border border-white/10 shadow-sm">Ctrl</span>
<span className="text-neutral-400">+</span>
<span className="px-2.5 py-1 rounded-md bg-surface-700 border border-white/10 shadow-sm">Shift</span>
<span className="text-neutral-400">+</span>
<span className="px-3 py-1 rounded-md bg-brand-blue/20 border border-brand-blue/40 text-brand-blue shadow-sm">Space</span>
</div>
</div>
<div className="flex flex-col items-center gap-3">
<div className="flex flex-col items-center gap-3.5">
<GlowButton
href={clientOS.downloadUrl}
download={clientOS.downloadFilename || undefined}
variant="primary"
size="lg"
className="shadow-glow-md hover:shadow-glow-lg"
className="shadow-glow-md hover:shadow-glow-lg text-base sm:text-lg font-medium px-10 sm:px-12 py-4.5 rounded-2xl"
>
{locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn}
</GlowButton>
<div className="flex items-center gap-2 font-mono text-xs text-neutral-400">
<div className="flex items-center gap-2 text-xs text-neutral-400 font-normal">
<span>{locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn}</span>
<span>·</span>
<a href="#download" className="underline hover:text-brand-amber transition-colors">
{locale === 'ko' ? '다른 OS 다운로드' : 'Other Platforms'}
<a href="#download" className="underline text-brand-blue hover:text-brand-blue-light transition-colors font-medium">
{locale === 'ko' ? '릴리스 준비 상태' : 'Release readiness'}
</a>
</div>
</div>
<p className="font-mono text-nano text-neutral-500 mt-8 uppercase tracking-widest">
{t.cta.systemReq} · 100% OPEN ARCHITECTURE
<p className="text-xs text-neutral-400 mt-9 font-normal">
{t.cta.systemReq} · 100%
</p>
</Container>
</section>

View file

@ -1,42 +1,17 @@
import { useState, useEffect, type ChangeEvent, type DragEvent } from 'react'
import { useState, 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 [verifyStatus, setVerifyStatus] = useState<'idle' | 'computing' | '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')
@ -48,11 +23,7 @@ export function Download() {
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')
}
setVerifyStatus('custom')
} catch {
setVerifyStatus('custom')
}
@ -85,287 +56,238 @@ export function Download() {
}
return (
<section id="download" className="py-24 md:py-32 bg-surface-900/60 border-t border-white/[0.04] relative">
<section id="download" className="py-20 md:py-28 bg-surface-900/60 border-t border-white/[0.04] relative">
<Container>
<SectionHeader
index="06 / DOWNLOAD"
title={isKo ? 'D3RO Voice 다운로드 및 배포 센터' : 'Download D3RO Voice Desktop'}
title={isKo ? 'D3RO Voice 릴리스 준비 센터' : 'D3RO Voice Release Preparation'}
subtitle={
isKo
? '100% 로컬 Whisper 전사 엔진과 AI 음성 비서를 데스크톱에 무료로 설치하세요. 클라우드 전송 없이 프라이버시가 완벽히 보장됩니다.'
: 'Zero-latency on-device speech assistant with offline Whisper model. Instant installation for Windows, macOS, and private servers.'
? '다음 안정 버전 1.1.0의 설치 파일, 서명, 업데이트 경로를 검증하고 있습니다. 검증이 끝나기 전에는 바이너리를 제공하지 않습니다.'
: 'Version 1.1.0 installers, signatures, and update paths are being verified. No binary is offered before verification completes.'
}
/>
{/* 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="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-8">
<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-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">
<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 mb-1">
<h3 className="font-display text-xl font-bold text-neutral-100">
{detectedOs === 'mac' ? 'macOS Apple Silicon' : 'Windows 64-bit Installer'}
<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 1.1.0
</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 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 ? '배포 준비 중' : 'RELEASE PENDING'}
</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 className="text-xs sm:text-sm text-neutral-300 font-normal">
{isKo
? 'Windows x64 및 macOS Apple Silicon 후보 빌드 검증 중'
: 'Windows x64 and macOS Apple Silicon candidate builds under verification'}
</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 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-amber-400" />
<span className="text-xs font-medium text-neutral-200">v1.1.0 VERIFICATION</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]"
<div className="mb-5">
<div
aria-disabled="true"
className="w-full py-4.5 sm:py-5 px-8 sm:px-10 rounded-2xl bg-surface-700 text-neutral-300 text-base sm:text-lg font-medium flex items-center justify-center gap-3 leading-normal border border-white/10 cursor-not-allowed"
>
<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>
<span>{isKo ? '설치 파일 검증 후 제공' : 'Installer available after verification'}</span>
</div>
</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 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">{isKo ? '릴리스 증거:' : 'Release evidence:'}</span>
<span className="text-neutral-200">{isKo ? '아티팩트 생성 전' : 'Artifact not yet published'}</span>
</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 className="text-xs text-amber-300 font-medium flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-amber-400" />
{isKo ? '서명 및 업데이트 검증 대기' : 'Signing and update verification pending'}
</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">
<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="font-mono text-xs text-neutral-500">Auto-Update Feed: latest.yml Active</span>
<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-xl bg-surface-800/60 border border-white/[0.06] hover:border-brand-amber/30 transition-all flex flex-col justify-between">
<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="font-mono text-xs font-semibold text-brand-amber">WINDOWS 10 / 11</span>
<span className="font-mono text-[10px] text-neutral-500">x64</span>
<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="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">
<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
? '원클릭 NSIS 설치 프로그램. 백그라운드 자동 업데이트 및 DirectML GPU 가속 지원.'
: 'One-click installer with automatic background delta updates and DirectML acceleration.'}
? 'Windows x64 후보 빌드의 설치, 서명, 업데이트 복구 경로를 검증 중입니다.'
: 'The Windows x64 candidate is undergoing installation, signing, and update-recovery verification.'}
</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"
<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 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>
<span>Windows x64 installer</span>
<span>{isKo ? '검증 대기' : 'Verification pending'}</span>
</div>
</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 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="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>
<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="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">
<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 M1~M4 Metal 가속 지원 및 Universal DMG 설치 패키지.'
: 'Universal package optimized for Apple Silicon (M1/M2/M3/M4) Metal framework.'}
? 'Apple Silicon 후보 빌드의 코드 서명과 설치 동작을 검증 중입니다.'
: 'The Apple Silicon candidate is undergoing code-signing and installation verification.'}
</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"
<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 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>
<span>Apple Silicon DMG</span>
<span>{isKo ? '검증 대기' : 'Verification pending'}</span>
</div>
</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">
{/* 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="font-mono text-xs font-semibold text-emerald-400">ANDROID & IOS</span>
<span className="font-mono text-[10px] text-neutral-500">Mobile Edition</span>
<span className="text-xs font-medium text-emerald-400">ANDROID</span>
<span className="text-xs text-neutral-400">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">
<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
? '스마트폰에서도 동일하게 작동하는 로컬 음성 비서 및 실시간 마이크 연동. 이동 중에도 끊김 없는 고속 음성 기록.'
: 'On-device voice assistant for smartphones with instant mic streaming and seamless cloud sync.'}
? 'Google Play용 AAB와 스토어 메타데이터의 검증을 마친 뒤 Android 배포를 시작합니다.'
: 'Android distribution begins after the Play AAB and store metadata are verified.'}
</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"
<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 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>
<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>
{/* 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">
{/* 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="font-mono text-nano text-brand-amber uppercase tracking-widest block mb-1">
CRYPTOGRAPHIC INTEGRITY
<span className="text-xs font-medium text-brand-blue uppercase tracking-wider block mb-1">
LOCAL FILE UTILITY
</span>
<h3 className="font-display text-xl font-bold text-neutral-100">
{isKo ? '실시간 SHA-256 무결성 검증기' : 'SHA-256 Binary Integrity Verifier'}
<h3 className="text-lg sm:text-xl font-medium text-neutral-50 mb-1">
{isKo ? '로컬 SHA-256 계산기' : 'Client-Side SHA-256 Calculator'}
</h3>
<p className="text-xs text-neutral-400">
<p className="text-xs sm:text-sm text-neutral-300 font-normal">
{isKo
? '다운로드한 파일을 브라우저로 끌어다 놓아 위변조 및 해시 일치를 로컬에서 즉시 확인하세요.'
: 'Drag and drop your installer file to compute SHA-256 locally via browser WebCrypto API.'}
? '선택한 파일의 SHA-256 값을 브라우저에서 계산합니다. 공식 릴리스 판정은 제공하지 않습니다.'
: 'Compute a selected file hash locally with WebCrypto. This does not certify an official release.'}
</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 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={`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'
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>
<p className="font-mono text-xs text-neutral-300 mb-1">
{isKo
? '설치 파일(.exe / .dmg / .zip)을 이곳에 끌어다 놓으세요'
: 'Drop installer file (.exe / .dmg / .zip) here'}
<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="font-mono text-[11px] text-neutral-500">
{isKo ? '파일은 서버로 전송되지 않고 로컬에서 안전하게 계산됩니다.' : 'Processed locally in browser.'}
<p className="text-xs text-neutral-400 font-normal">
{isKo ? '파일은 서버로 전송되지 않고 로컬 브라우저에서 안전하게 계산됩니다.' : 'Calculated in-memory locally via browser WebCrypto.'}
</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>
)}
{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="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 className="font-mono text-xs text-neutral-200 break-all pt-1 border-t border-white/10">
{computedHash}
</div>
</div>
)}
@ -373,78 +295,81 @@ export function Download() {
</div>
{/* Release History Section */}
<div id="releases">
<div className="flex items-center justify-between mb-8">
<div>
<div className="flex items-center justify-between mb-5">
<div>
<span className="font-mono text-nano text-brand-amber uppercase tracking-widest block mb-1">
<span className="text-xs font-medium text-brand-blue uppercase tracking-wider block mb-1">
VERSION ARCHIVE
</span>
<h3 className="font-display text-xl font-bold text-neutral-100">
{isKo ? '릴리스 버전 히스토리' : 'Release Version History'}
<h3 className="text-xl font-medium text-neutral-50">
{isKo ? '릴리스 버전 히스토리' : 'Release Archive & Changelog'}
</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"
className="text-xs text-brand-blue hover:text-brand-blue-light transition-colors font-medium flex items-center gap-1.5"
>
<span>{isKo ? 'Forgejo Git 릴리스 전체 보기' : 'Forgejo Git Releases'}</span>
<span>Forgejo Git </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">
{/* 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="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 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="font-mono text-xs text-neutral-500">2026-08-20</span>
<span className="text-xs text-neutral-400">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"
<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"
>
Installer (.exe)
</a>
{isKo ? '과거 바이너리 미제공' : 'Historical binary unavailable'}
</span>
</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 .
<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>
</li>
<li>
<strong className="text-brand-amber"> </strong>: 15 +50 Cloud AI .
<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>
<strong className="text-brand-amber">100% Whisper </strong>: .
<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>
<strong className="text-brand-amber">Android & iOS </strong>: AI .
<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="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 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-xl bg-surface-800/40 border-l-4 border-l-neutral-700 border border-white/[0.04] opacity-80">
<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="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>
<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-400">
Deepgram, AssemblyAI, Groq STT .
<p className="text-xs text-neutral-300 font-normal">
Deepgram, AssemblyAI, Groq STT .
</p>
</div>
</div>

View file

@ -8,54 +8,63 @@ export function FAQ() {
const [openIndex, setOpenIndex] = useState<number | null>(null)
return (
<section id="faq" className="relative py-24 md:py-32">
<section id="faq" className="relative py-20 md:py-28">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-surface-800/30 to-transparent pointer-events-none" />
<Container className="relative max-w-3xl">
<Container className="relative max-w-4xl">
<SectionHeader
index={t.faq.index}
title={t.faq.title}
/>
<div className="space-y-px">
<div className="space-y-3.5">
{t.faq.items.map((faq, i) => {
const isOpen = openIndex === i
return (
<div
key={i}
className="border-b border-white/[0.04]"
className={`rounded-2xl border transition-all duration-200 overflow-hidden ${
isOpen
? 'bg-surface-800/90 border-brand-blue/40 shadow-glow-sm'
: 'bg-surface-800/60 border-white/10 hover:border-white/20'
}`}
>
<button
type="button"
onClick={() => setOpenIndex(isOpen ? null : i)}
className="w-full flex items-start justify-between gap-4 py-5 text-left group"
className="w-full flex items-start justify-between gap-4 p-5 sm:p-6 text-left group"
>
<div className="flex items-start gap-4">
<span className="font-mono text-nano text-neutral-600 mt-1 flex-shrink-0">
<div className="flex items-start gap-3.5">
<span className="font-mono text-xs font-medium text-brand-blue mt-0.5 flex-shrink-0 bg-brand-blue/10 px-2.5 py-0.5 rounded-md border border-brand-blue/25">
{String(i + 1).padStart(2, '0')}
</span>
<span className="text-sm font-medium text-neutral-200 group-hover:text-white transition-colors">
<span className="text-base sm:text-lg font-medium sm:font-medium text-neutral-100 group-hover:text-brand-blue transition-colors leading-snug">
{faq.q}
</span>
</div>
<svg
className={`w-4 h-4 text-neutral-500 flex-shrink-0 mt-1 transition-transform duration-200 ${
isOpen ? 'rotate-45' : ''
}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
<div className="w-7 h-7 rounded-full bg-surface-700/80 border border-white/10 flex items-center justify-center flex-shrink-0 mt-0.5 text-neutral-300 group-hover:text-brand-blue group-hover:border-brand-blue/40 transition-colors">
<svg
className={`w-3.5 h-3.5 transition-transform duration-200 ${
isOpen ? 'rotate-45 text-brand-blue' : ''
}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</div>
</button>
<div className={`accordion-content ${isOpen ? 'open' : ''}`}>
<div>
<p className="pl-10 pb-5 text-sm text-neutral-400 leading-relaxed">
{faq.a}
</p>
<div className="overflow-hidden min-h-0">
<div className="px-5 sm:px-6 pb-5 pt-0">
<p className="pl-10 text-sm sm:text-base text-neutral-300 leading-relaxed font-normal border-t border-white/[0.06] pt-3.5">
{faq.a}
</p>
</div>
</div>
</div>
</div>

View file

@ -15,9 +15,9 @@ interface FeatureTile {
}
const tagStyles = {
FREE: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20',
PRO: 'text-brand-amber bg-brand-amber/10 border-brand-amber/20',
'PRO+': 'text-violet-400 bg-violet-500/10 border-violet-500/20',
FREE: 'text-emerald-400 bg-emerald-500/15 border-emerald-500/30',
PRO: 'text-brand-blue bg-brand-blue/15 border-brand-blue/30',
'PRO+': 'text-purple-300 bg-purple-500/15 border-purple-500/30',
} as const
export function Features() {
@ -32,21 +32,21 @@ export function Features() {
description: locale === 'ko'
? 'VS Code, Notion, Slack, Word 등 모든 프로그램 위에서 Ctrl+Shift+Space로 즉시 호출되는 초경량 플로팅 캡슐. 타이핑 없이 생각의 속도로 입력하세요.'
: 'Lightweight floating HUD triggered with Ctrl+Shift+Space across any application. Dictate and transcribe at the speed of thought without leaving your workflow.',
highlight: 'Global Hotkey & Floating Overlays',
highlight: '글로벌 핫키 & 플로팅 오버레이',
icon: <MicIcon />,
previewNode: (
<div className="mt-4 p-3 rounded-xl bg-surface-950 border border-white/[0.06] flex items-center justify-between shadow-inner">
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-brand-amber animate-ping" />
<div className="flex items-center gap-1 h-5">
{[4, 12, 20, 16, 24, 14, 8, 18, 6].map((h, i) => (
<div key={i} className="w-1 bg-brand-amber rounded-full" style={{ height: `${h}px` }} />
<div className="mt-4 p-3.5 rounded-xl bg-surface-950/90 border border-white/10 flex items-center justify-between shadow-inner">
<div className="flex items-center gap-3">
<span className="w-2.5 h-2.5 rounded-full bg-brand-blue animate-ping" />
<div className="flex items-center gap-1.5 h-6">
{[6, 14, 22, 18, 24, 16, 10, 20, 8].map((h, i) => (
<div key={i} className="w-1.5 bg-brand-blue rounded-full shadow-sm" style={{ height: `${h}px` }} />
))}
</div>
<span className="font-mono text-nano text-neutral-400 ml-2">0:04</span>
<span className="font-mono text-xs font-medium text-neutral-300 ml-2">0:04</span>
</div>
<span className="font-mono text-nano uppercase tracking-widest text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">
AUTO-INJECTING
<span className="text-xs font-medium text-emerald-400 bg-emerald-500/15 px-2.5 py-0.5 rounded-full border border-emerald-500/30">
</span>
</div>
),
@ -59,7 +59,7 @@ export function Features() {
description: locale === 'ko'
? '음성 및 전사 텍스트가 외부 클라우드 서버로 단 1바이트도 전송되지 않습니다. 에어갭 환경 및 사내 보안 규정 100% 준수.'
: 'Zero cloud telemetry. Voice audio and transcriptions are strictly processed on your local machine using faster-whisper and Ollama.',
highlight: 'Zero Cloud Egress',
highlight: '클라우드 유출 0바이트',
icon: <ShieldIcon />,
},
{
@ -70,7 +70,7 @@ export function Features() {
description: locale === 'ko'
? '장시간 회의 녹음, 화자 자동 분리, 실시간 스크래치패드 및 마크다운 회의록(액션 아이템, 결정 사항) 자동 생성.'
: 'Long-form meeting recording with speaker diarization, real-time memo scratchpad, and automatic structured Markdown summary generation.',
highlight: 'Auto Markdown Action Items',
highlight: '자동 마크다운 회의록',
icon: <UsersIcon />,
},
{
@ -81,25 +81,25 @@ export function Features() {
description: locale === 'ko'
? '음성 입력 → 추임새 제거 → 전문 용어 사전 보정 → LLM 톤앤매너 변환 → 활성 창 자동 입력까지 1회 발화로 연속 실행하는 고성능 자동화 엔진.'
: 'Chain multi-step prompts in a single voice command: Transcribe → Clean Fillers → Phonetic Dictionary Fix → LLM Transform → Active Cursor Injection.',
highlight: 'Custom Prompts & Tone Shift',
highlight: '맞춤형 프롬프트 & 톤 변환',
icon: <ChainIcon />,
previewNode: (
<div className="mt-4 grid grid-cols-4 gap-2 font-mono text-nano text-neutral-400 bg-surface-950 p-2.5 rounded-lg border border-white/[0.04]">
<div className="text-center p-1.5 rounded bg-surface-800 border border-white/[0.05]">
<span className="text-brand-amber block">01. STT</span>
<span className="text-neutral-500">Whisper</span>
<div className="mt-4 grid grid-cols-2 sm:grid-cols-4 gap-2.5 text-xs text-neutral-300 bg-surface-950/90 p-3 rounded-xl border border-white/10">
<div className="text-center p-2 rounded-lg bg-surface-800 border border-white/10">
<span className="text-brand-blue font-medium block text-xs">01. STT</span>
<span className="text-neutral-400 text-[11px]">Whisper</span>
</div>
<div className="text-center p-1.5 rounded bg-surface-800 border border-white/[0.05]">
<span className="text-emerald-400 block">02. DICT</span>
<span className="text-neutral-500">Phonetic</span>
<div className="text-center p-2 rounded-lg bg-surface-800 border border-white/10">
<span className="text-emerald-400 font-medium block text-xs">02. </span>
<span className="text-neutral-400 text-[11px]"> </span>
</div>
<div className="text-center p-1.5 rounded bg-surface-800 border border-white/[0.05]">
<span className="text-violet-400 block">03. LLM</span>
<span className="text-neutral-500">Ollama</span>
<div className="text-center p-2 rounded-lg bg-surface-800 border border-white/10">
<span className="text-purple-300 font-medium block text-xs">03. LLM</span>
<span className="text-neutral-400 text-[11px]">Ollama Local</span>
</div>
<div className="text-center p-1.5 rounded bg-surface-800 border border-white/[0.05]">
<span className="text-neutral-200 block">04. INJECT</span>
<span className="text-neutral-500">Cursor</span>
<div className="text-center p-2 rounded-lg bg-surface-800 border border-white/10">
<span className="text-neutral-200 font-medium block text-xs">04. </span>
<span className="text-neutral-400 text-[11px]"> </span>
</div>
</div>
),
@ -112,7 +112,7 @@ export function Features() {
description: locale === 'ko'
? '내 PC의 PDF, 메모, 사내 문서를 로컬 벡터 DB에 인덱싱하여 음성으로 검색하고 대화할 수 있는 비공개 지식 베이스.'
: 'Index local PDFs, notes, and private documents into a local vector DB. Query your private knowledge base using voice commands.',
highlight: 'sqlite-vec & Local Embeddings',
highlight: 'sqlite-vec 로컬 임베딩',
icon: <DatabaseIcon />,
},
{
@ -123,13 +123,13 @@ export function Features() {
description: locale === 'ko'
? 'C++ 기반 faster-whisper 백엔드로 일반 노트북 CPU에서도 1.2초 미만의 경이로운 응답 속도를 실현.'
: 'Optimized C++ faster-whisper runtime delivers sub-1.2s transcription latency even on standard laptop CPUs without dedicated GPU.',
highlight: '< 1.2s Real-time Latency',
highlight: '1.2초 미만 실시간 응답',
icon: <ZapIcon />,
},
]
return (
<section id="features" className="relative py-24 md:py-32">
<section id="features" className="relative py-20 md:py-28">
<Container>
<SectionHeader
index={t.features.index}
@ -138,28 +138,28 @@ export function Features() {
/>
{/* Asymmetric Bento Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{bentoTiles.map((tile) => (
<div
key={tile.id}
className={`relative noise-texture bg-surface-700/40 border border-white/[0.06] rounded-2xl p-6 md:p-7 shadow-chassis transition-all duration-300 hover:border-brand-amber/30 hover:bg-surface-700/60 hover:shadow-glow-sm flex flex-col justify-between ${tile.span}`}
className={`relative noise-texture bg-surface-800/80 border border-white/10 rounded-3xl p-6 md:p-7 shadow-glass-card transition-all duration-300 hover:border-brand-blue/40 hover:bg-surface-700/85 hover:shadow-glow-sm flex flex-col justify-between backdrop-blur-xl ${tile.span}`}
>
<div>
{/* Top row: icon + tag */}
<div className="flex items-start justify-between mb-5">
<div className="w-11 h-11 rounded-xl bg-surface-600 border border-white/[0.08] flex items-center justify-center text-brand-amber shadow-sm">
<div className="w-11 h-11 rounded-2xl bg-surface-700/80 border border-white/10 flex items-center justify-center text-brand-blue shadow-sm">
{tile.icon}
</div>
<span className={`text-nano font-mono font-semibold tracking-widest uppercase px-2.5 py-0.5 border rounded-md ${tagStyles[tile.tag]}`}>
<span className={`text-xs font-medium px-2.5 py-0.5 border rounded-full shadow-sm ${tagStyles[tile.tag]}`}>
{tile.tag}
</span>
</div>
{/* Content */}
<h3 className="font-display text-xl font-bold text-neutral-100 mb-2.5">
<h3 className="text-lg sm:text-xl font-medium text-neutral-50 mb-2.5 tracking-tight">
{tile.title}
</h3>
<p className="text-sm text-neutral-400 leading-relaxed">
<p className="text-sm text-neutral-300 leading-relaxed font-normal">
{tile.description}
</p>
@ -169,11 +169,11 @@ export function Features() {
{/* Bottom tag / detail */}
{tile.highlight && (
<div className="mt-6 pt-4 border-t border-white/[0.04] flex items-center justify-between">
<span className="font-mono text-nano text-neutral-500 uppercase tracking-wider">
<div className="mt-5 pt-3.5 border-t border-white/[0.08] flex items-center justify-between">
<span className="text-xs font-normal text-neutral-400">
{tile.highlight}
</span>
<div className="w-6 h-6 rounded-full border border-white/[0.08] flex items-center justify-center text-neutral-500 hover:text-brand-amber hover:border-brand-amber/30 transition-colors">
<div className="w-6 h-6 rounded-full bg-surface-700/60 border border-white/10 flex items-center justify-center text-neutral-400 hover:text-brand-blue hover:border-brand-blue/40 transition-colors">
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7" />
<polyline points="7 7 17 7 17 17" />

View file

@ -11,35 +11,35 @@ export function Footer() {
const { t } = useI18n()
return (
<footer className="border-t border-white/[0.04] py-12 md:py-16">
<footer className="border-t border-white/10 py-12 md:py-16 bg-surface-950/80">
<Container>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
{/* Left: brand */}
<div>
<div className="flex items-center gap-2 mb-3">
<Led color="amber" size="sm" />
<span className="font-mono text-sm font-semibold tracking-tight text-neutral-300">
D3RO&middot;VOICE
<div className="flex items-center gap-2 mb-2.5">
<Led color="blue" size="sm" />
<span className="text-base font-medium tracking-tight text-white">
D3RO<span className="text-brand-blue font-medium">&middot;</span>VOICE
</span>
</div>
<p className="text-sm text-neutral-500 leading-relaxed max-w-sm">
<p className="text-sm text-neutral-400 leading-relaxed max-w-sm font-normal">
{t.footer.description}
</p>
</div>
{/* Right: links */}
<div className="flex flex-wrap gap-x-8 gap-y-3 md:justify-end">
<a href="https://github.com/user/D3ROVoice" className="font-mono text-nano uppercase tracking-widest text-neutral-500 hover:text-neutral-300 transition-colors flex items-center gap-1.5">
<div className="flex flex-wrap gap-x-7 gap-y-2.5 md:justify-end items-center">
<a href="https://git.chanpaca.net/yunchan/d3ro-voice" target="_blank" rel="noreferrer" className="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium flex items-center gap-1.5">
<GithubIcon />
GitHub
Repository
</a>
<a href="#features" className="font-mono text-nano uppercase tracking-widest text-neutral-500 hover:text-neutral-300 transition-colors">
<a href="#features" className="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium">
{t.nav.features}
</a>
<a href="#pricing" className="font-mono text-nano uppercase tracking-widest text-neutral-500 hover:text-neutral-300 transition-colors">
<a href="#pricing" className="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium">
{t.nav.pricing}
</a>
<a href="#faq" className="font-mono text-nano uppercase tracking-widest text-neutral-500 hover:text-neutral-300 transition-colors">
<a href="#faq" className="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium">
{t.nav.faq}
</a>
</div>
@ -50,7 +50,7 @@ export function Footer() {
{techStack.map((tech) => (
<span
key={tech}
className="font-mono text-nano uppercase tracking-widest text-neutral-600 bg-surface-700/50 px-2.5 py-1 rounded-sm border border-white/[0.03]"
className="text-xs text-neutral-400 bg-surface-800/80 px-2.5 py-1 rounded-lg border border-white/10 font-normal font-mono"
>
{tech}
</span>
@ -58,11 +58,11 @@ export function Footer() {
</div>
{/* Bottom bar */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 pt-6 border-t border-white/[0.04]">
<span className="font-mono text-nano text-neutral-600 uppercase tracking-widest">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 pt-5 border-t border-white/10 text-xs text-neutral-400 font-normal">
<span>
{t.footer.copyright.replace('{year}', String(new Date().getFullYear()))}
</span>
<span className="font-mono text-nano text-neutral-600 uppercase tracking-widest">
<span>
{t.footer.builtWith}
</span>
</div>

View file

@ -19,16 +19,19 @@ export function Header() {
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled
? 'bg-surface-950/85 backdrop-blur-xl border-b border-white/[0.06] shadow-lg'
: 'bg-transparent'
? 'bg-surface-950/90 backdrop-blur-2xl border-b border-white/10 shadow-2xl py-3'
: 'bg-transparent py-4'
}`}
>
<div className="mx-auto max-w-7xl px-5 md:px-8 lg:px-12 h-16 flex items-center justify-between">
<div className="mx-auto max-w-7xl px-5 md:px-8 lg:px-12 flex items-center justify-between">
{/* Logo */}
<a href="#" className="flex items-center gap-2 group">
<Led color="amber" pulse size="md" />
<span className="font-mono text-sm font-bold tracking-tight text-white">
D3RO<span className="text-brand-amber">&middot;</span>VOICE
<a href="#" className="flex items-center gap-2.5 group">
<Led color="blue" pulse size="md" />
<span className="text-base font-medium tracking-tight text-white flex items-center">
D3RO<span className="text-brand-blue font-medium">&middot;</span>VOICE
</span>
<span className="hidden sm:inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-brand-blue/15 text-brand-blue border border-brand-blue/30">
v1.0
</span>
</a>
@ -38,7 +41,7 @@ export function Header() {
<a
key={link.href}
href={link.href}
className="font-mono text-nano uppercase tracking-widest text-neutral-400 hover:text-white transition-colors"
className="text-sm font-medium text-neutral-300 hover:text-white transition-colors"
>
{t.nav[link.key]}
</a>
@ -46,48 +49,51 @@ export function Header() {
</nav>
{/* Right side: language + CTA */}
<div className="hidden md:flex items-center gap-3">
<div className="hidden md:flex items-center gap-3.5">
<LanguageSwitcher />
<a
href="#download"
className="glow-btn inline-flex items-center gap-2 px-4 py-2 font-mono text-xs uppercase tracking-wider text-white bg-brand-amber rounded-panel shadow-sm hover:bg-brand-amber-light transition-colors"
className="glow-btn inline-flex items-center gap-2 px-5 py-2.5 text-sm font-medium text-white bg-brand-blue rounded-xl shadow-glow-sm hover:bg-brand-blue-light transition-all"
>
<DownloadSmallIcon />
{t.nav.download}
</a>
</div>
{/* Mobile menu button */}
<button
type="button"
onClick={() => setMobileOpen(!mobileOpen)}
className="md:hidden w-9 h-9 flex items-center justify-center text-neutral-300"
aria-label="Menu"
className="md:hidden w-10 h-10 rounded-lg bg-surface-800 border border-white/10 flex items-center justify-center text-neutral-200"
aria-label="Toggle Navigation Menu"
>
<div className="space-y-1.5">
<span className={`block w-5 h-px bg-white transition-all ${mobileOpen ? 'rotate-45 translate-y-[3.5px]' : ''}`} />
<span className={`block w-5 h-px bg-white transition-all ${mobileOpen ? '-rotate-45 -translate-y-[3.5px]' : ''}`} />
<div className="space-y-1.5 w-5">
<span className={`block w-5 h-0.5 bg-white transition-all ${mobileOpen ? 'rotate-45 translate-y-2' : ''}`} />
<span className={`block w-5 h-0.5 bg-white transition-all ${mobileOpen ? 'opacity-0' : ''}`} />
<span className={`block w-5 h-0.5 bg-white transition-all ${mobileOpen ? '-rotate-45 -translate-y-2' : ''}`} />
</div>
</button>
</div>
{/* Mobile Nav */}
{mobileOpen && (
<div className="md:hidden bg-surface-900/95 backdrop-blur-2xl border-t border-white/[0.06] px-5 py-6 space-y-1 shadow-2xl">
<div className="md:hidden bg-surface-900/95 backdrop-blur-2xl border-t border-white/10 px-6 py-6 space-y-2 shadow-2xl">
{navItems.map((link) => (
<a
key={link.href}
href={link.href}
onClick={() => setMobileOpen(false)}
className="block font-mono text-xs uppercase tracking-widest text-neutral-400 hover:text-brand-amber py-3 border-b border-white/[0.03]"
className="block text-base font-medium text-neutral-200 hover:text-brand-blue py-3 border-b border-white/[0.05]"
>
{t.nav[link.key]}
</a>
))}
<div className="flex items-center justify-between mt-4 gap-3">
<div className="flex items-center justify-between pt-4 gap-3">
<LanguageSwitcher />
<a
href="#download"
onClick={() => setMobileOpen(false)}
className="flex-1 text-center px-5 py-2.5 font-mono text-xs uppercase tracking-wider text-white bg-brand-amber rounded-panel shadow-sm"
className="flex-1 text-center px-5 py-3 text-sm font-medium text-white bg-brand-blue rounded-xl shadow-glow-sm"
>
{t.nav.download}
</a>
@ -97,3 +103,12 @@ export function Header() {
</header>
)
}
function DownloadSmallIcon() {
return (
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
)
}

View file

@ -24,6 +24,13 @@ export function Hero() {
? '이번 프로젝트 배포 일정은 다음 주 금요일로 확정하겠습니다.'
: 'The project release is scheduled for next Friday.'
const heroDataPoints = [
{ label: t.hero.dataProcessing, value: '100%', unit: 'local' },
{ label: t.hero.dataCloudTraffic, value: '0', unit: 'bytes' },
{ label: t.hero.dataLatency, value: '<1.2', unit: 's' },
{ label: t.hero.dataPrivacy, value: '10/10', unit: '' },
]
// Waveform animation during recording
useEffect(() => {
if (simState !== 'recording') return
@ -72,69 +79,69 @@ export function Hero() {
}
return (
<section className="relative min-h-screen flex flex-col justify-center overflow-hidden bg-grid pt-28 pb-16">
<section className="relative min-h-[90vh] flex flex-col justify-center overflow-hidden bg-grid pt-28 pb-16">
{/* Ambient background glow */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[800px] rounded-full bg-brand-amber/[0.035] blur-[160px]" />
<div className="absolute top-1/2 right-10 w-[400px] h-[400px] rounded-full bg-blue-500/[0.02] blur-[140px]" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand-amber/20 to-transparent" />
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[800px] rounded-full bg-brand-blue/[0.045] blur-[160px]" />
<div className="absolute top-1/2 right-10 w-[500px] h-[500px] rounded-full bg-blue-500/[0.03] blur-[160px]" />
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand-blue/30 to-transparent" />
</div>
<Container className="relative flex-1 flex flex-col justify-center">
{/* Hero main grid */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8 items-center pt-4 mb-14">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-10 items-center pt-4 mb-14">
{/* Left Column: Typography & CTAs (7 cols) */}
<div className="lg:col-span-7 max-w-2xl">
{/* Status & Protocol Pill Row in Natural Document Flow */}
<div className="flex flex-wrap items-center gap-2.5 mb-6">
{/* Status & Protocol Pill Row */}
<div className="flex flex-wrap items-center gap-2 mb-6">
<Badge led ledColor="green">{t.hero.systemStatus}</Badge>
<Badge led>{t.hero.badge}</Badge>
<span className="font-mono text-nano uppercase tracking-widest text-neutral-400 bg-surface-700/60 px-2.5 py-1 rounded border border-white/[0.06]">
ON-DEVICE V3
<span className="text-xs font-medium text-neutral-300 bg-surface-800/80 px-3 py-1 rounded-full border border-white/10 shadow-sm">
V3
</span>
<span className="font-mono text-nano uppercase tracking-widest text-brand-amber bg-brand-amber/10 px-2.5 py-1 rounded border border-brand-amber/20">
<span className="text-xs font-medium text-brand-blue bg-brand-blue/10 px-3 py-1 rounded-full border border-brand-blue/25 shadow-sm">
{t.hero.protocol}
</span>
</div>
{/* Main Headline (2 Clean Lines with keep-all) */}
{/* Main Headline */}
<h1
className="font-display text-4xl sm:text-5xl lg:text-[3.4rem] font-bold text-neutral-50 mb-6 tracking-tight leading-[1.18] break-keep"
className="text-3xl sm:text-4xl lg:text-[3.25rem] font-medium text-neutral-50 mb-5 tracking-tight leading-[1.18] break-keep"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
<span className="block text-neutral-100">{t.hero.title1}</span>
<span className="block text-brand-amber font-extrabold">{t.hero.title2}</span>
<span className="block text-brand-blue font-medium">{t.hero.title2}</span>
</h1>
<p
className="text-base md:text-lg text-neutral-400 leading-relaxed mb-8 max-w-xl break-keep"
className="text-base sm:text-lg text-neutral-300 leading-relaxed mb-7 max-w-xl break-keep font-normal"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
{t.hero.subtitle}
</p>
{/* Hotkey Callout */}
<div className="flex items-center gap-3 mb-8 p-3 rounded-card bg-surface-700/40 border border-white/[0.05] max-w-md">
<span className="font-mono text-nano uppercase tracking-widest text-neutral-400">
GLOBAL PUSH-TO-TALK:
<div className="flex items-center gap-3.5 mb-7 p-3 rounded-xl bg-surface-800/80 border border-white/10 max-w-md shadow-sm">
<span className="text-xs font-medium text-neutral-400">
:
</span>
<div className="flex items-center gap-1.5 font-mono text-xs font-semibold text-neutral-200">
<span className="px-2 py-0.5 rounded bg-surface-500 border border-white/[0.1] shadow-sm">Ctrl</span>
<span className="text-neutral-500">+</span>
<span className="px-2 py-0.5 rounded bg-surface-500 border border-white/[0.1] shadow-sm">Shift</span>
<span className="text-neutral-500">+</span>
<span className="px-2.5 py-0.5 rounded bg-brand-amber/20 border border-brand-amber/40 text-brand-amber shadow-sm">Space</span>
<div className="flex items-center gap-1.5 text-xs font-medium text-neutral-200">
<span className="px-2.5 py-1 rounded-md bg-surface-700 border border-white/10 font-mono text-neutral-200 shadow-sm">Ctrl</span>
<span className="text-neutral-400">+</span>
<span className="px-2.5 py-1 rounded-md bg-surface-700 border border-white/10 font-mono text-neutral-200 shadow-sm">Shift</span>
<span className="text-neutral-400">+</span>
<span className="px-3 py-1 rounded-md bg-brand-blue/20 border border-brand-blue/40 text-brand-blue font-mono shadow-sm">Space</span>
</div>
</div>
{/* CTA Action Buttons with OS Negotiation */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4 mb-3">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3.5 mb-4">
<a
href={clientOS.downloadUrl}
download={clientOS.downloadFilename || undefined}
className="glow-btn inline-flex items-center justify-center gap-2.5 px-8 py-4 font-mono text-sm font-semibold uppercase tracking-wider text-white bg-brand-amber rounded-panel shadow-glow-sm hover:shadow-glow-md"
className="glow-btn inline-flex items-center justify-center gap-3 px-8 sm:px-9 py-3.5 sm:py-4 text-base font-medium text-white bg-brand-blue rounded-2xl shadow-glow-sm hover:shadow-glow-md active:scale-[0.98] transition-all leading-normal"
>
<span className="relative z-10 flex items-center gap-2.5">
<span className="relative z-10 flex items-center gap-2">
{clientOS.os === 'android' ? (
<AndroidIcon />
) : clientOS.os === 'ios' || clientOS.os === 'macos' ? (
@ -147,7 +154,7 @@ export function Hero() {
</a>
<a
href="#features"
className="inline-flex items-center justify-center gap-2 px-7 py-4 font-mono text-sm uppercase tracking-wider text-neutral-300 border border-white/[0.08] rounded-panel hover:border-brand-amber/30 hover:text-white bg-surface-800/40 backdrop-blur-sm transition-all"
className="inline-flex items-center justify-center gap-2.5 px-7 sm:px-8 py-3.5 sm:py-4 text-base font-medium text-neutral-200 border border-white/10 rounded-2xl hover:border-brand-blue/40 hover:bg-surface-700 hover:text-white bg-surface-800/60 backdrop-blur-sm transition-all leading-normal"
>
{t.hero.viewFeatures}
<ArrowIcon />
@ -155,21 +162,21 @@ export function Hero() {
</div>
{/* Platform Subtext & Other OS Navigator */}
<div className="flex flex-wrap items-center gap-y-2 gap-x-4 mb-3 font-mono text-xs">
<span className="text-brand-amber font-medium flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-brand-amber animate-pulse" />
<div className="flex flex-wrap items-center gap-y-2 gap-x-4 mb-3.5 text-xs font-normal">
<span className="text-brand-blue font-medium flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-brand-blue animate-pulse" />
{locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn}
</span>
<a
href="#download"
className="text-neutral-400 hover:text-neutral-200 underline decoration-neutral-600 underline-offset-4 transition-colors"
className="text-neutral-400 hover:text-neutral-200 underline decoration-neutral-600 underline-offset-4 transition-colors font-normal"
>
{locale === 'ko' ? '다른 OS 다운로드 (Win / Mac / Android / iOS) →' : 'Other Platforms (Win / Mac / Android / iOS) →'}
{locale === 'ko' ? '플랫폼별 릴리스 준비 상태 →' : 'Platform release readiness →'}
</a>
</div>
{/* Friction-Reduction Trust Microcopy */}
<div className="flex items-center gap-2 font-mono text-nano text-neutral-500">
<div className="flex items-center gap-2 text-xs text-neutral-400 font-normal">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
<span>{locale === 'ko' ? '영구 무료 · 회원가입 불필요 · 100% 오프라인 작동' : 'Free Forever · No Signup Required · 100% Offline'}</span>
</div>
@ -177,23 +184,23 @@ export function Hero() {
{/* Right Column: Live Interactive Voice Capsule (5 cols) */}
<div className="lg:col-span-5">
<div className="relative p-6 md:p-7 rounded-2xl bg-surface-800/90 border border-white/[0.08] shadow-2xl backdrop-blur-2xl noise-texture">
<div className="relative p-6 md:p-7 rounded-3xl bg-surface-800/90 border border-white/10 shadow-glass-card backdrop-blur-2xl noise-texture">
{/* Header */}
<div className="flex items-center justify-between mb-5 pb-3 border-b border-white/[0.06]">
<div className="flex items-center justify-between mb-4 pb-3 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<Led color={simState === 'recording' ? 'amber' : simState === 'processing' ? 'amber' : 'green'} pulse={simState !== 'idle'} />
<span className="font-mono text-nano uppercase tracking-widest text-neutral-300">
{simState === 'idle' ? 'INTERACTIVE SIMULATOR' : simState === 'recording' ? 'LISTENING & STREAMING' : simState === 'processing' ? 'AI POLISHING...' : 'PIPELINE COMPLETED'}
<Led color={simState === 'recording' ? 'blue' : simState === 'processing' ? 'blue' : 'green'} pulse={simState !== 'idle'} />
<span className="text-xs font-medium text-neutral-200">
{simState === 'idle' ? '인터랙티브 시뮬레이터' : simState === 'recording' ? '음성 듣는 중...' : simState === 'processing' ? 'AI 문장 정제 중...' : '전사 & 다듬기 완료'}
</span>
</div>
<span className="font-mono text-nano text-neutral-500 uppercase">
ZERO CLOUD EGRESS
<span className="text-xs font-medium text-emerald-400 bg-emerald-500/10 px-2.5 py-0.5 rounded-full border border-emerald-500/20">
0
</span>
</div>
{/* Interactive Audio Capsule UI */}
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between p-4 rounded-xl bg-surface-950 border border-white/[0.06] shadow-inner">
<div className="flex flex-col gap-3.5">
<div className="flex items-center justify-between p-3.5 rounded-xl bg-surface-950 border border-white/10 shadow-inner">
{/* Waveform Bars */}
<div className="flex items-center gap-1.5 h-8">
{waveLevels.map((lvl, idx) => (
@ -202,8 +209,8 @@ export function Hero() {
className="w-1.5 rounded-full transition-all duration-75"
style={{
height: `${Math.max(6, lvl * 32)}px`,
backgroundColor: simState === 'recording' ? '#f25b29' : 'rgba(255,255,255,0.2)',
boxShadow: simState === 'recording' ? '0 0 8px rgba(242,91,41,0.6)' : 'none',
backgroundColor: simState === 'recording' ? '#3b82f6' : 'rgba(255,255,255,0.25)',
boxShadow: simState === 'recording' ? '0 0 10px rgba(59,130,246,0.7)' : 'none',
}}
/>
))}
@ -211,71 +218,67 @@ export function Hero() {
{/* Trigger Button */}
<button
type="button"
onClick={simState === 'idle' || simState === 'done' ? startSimulation : resetSimulation}
className={`px-4 py-2 rounded-lg font-mono text-xs font-semibold uppercase tracking-wider transition-all flex items-center gap-2 ${
className={`px-4 py-2 rounded-xl text-xs font-medium transition-all flex items-center gap-2 shadow-sm ${
simState === 'recording'
? 'bg-red-500/20 text-red-400 border border-red-500/30 animate-pulse'
: 'bg-brand-amber text-white hover:bg-brand-amber-light shadow-glow-sm'
: 'bg-brand-blue text-white hover:bg-brand-blue-light shadow-glow-sm active:scale-[0.98]'
}`}
>
<MicIcon />
{simState === 'idle' ? 'Try Demo' : simState === 'recording' ? 'Listening...' : simState === 'processing' ? 'Polishing' : 'Test Again'}
<MicSmallIcon />
<span>{simState === 'recording' ? (locale === 'ko' ? '녹음 중지' : 'Stop') : simState === 'processing' ? (locale === 'ko' ? '처리 중...' : 'Polishing...') : (locale === 'ko' ? '데모 시작' : 'Try Demo')}</span>
</button>
</div>
{/* Real-time Transcription Stream */}
<div className="min-h-[110px] p-4 rounded-xl bg-surface-900 border border-white/[0.04] flex flex-col justify-center">
{/* Real-time Streaming Transcript Output Area */}
<div className="min-h-[140px] p-4 rounded-xl bg-surface-950/80 border border-white/10 flex flex-col justify-center">
{simState === 'idle' && (
<div className="text-center py-3">
<p className="text-xs font-mono text-neutral-500">
👆 Click &apos;Try Demo&apos; to watch on-device speech-to-text & AI polish in action
</p>
</div>
<p className="text-xs sm:text-sm text-neutral-400 text-center leading-relaxed font-normal">
👆 <strong className="text-neutral-200 font-medium">{locale === 'ko' ? "'데모 시작'" : "'Try Demo'"}</strong>{locale === 'ko' ? ' 버튼을 클릭하여 로컬 음성 전사 및 AI 다듬기 과정을 체험해보세요.' : ' to watch on-device speech-to-text & AI polish in action.'}
</p>
)}
{(simState === 'recording' || simState === 'processing') && (
<div className="space-y-1.5">
<span className="font-mono text-nano uppercase tracking-widest text-brand-amber/80 flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-brand-amber animate-ping" />
Live Transcription:
</span>
<p className="text-sm font-sans text-neutral-200 leading-relaxed font-medium">
{typedRaw}
<span className="inline-block w-2 h-4 ml-1 bg-brand-amber animate-pulse align-middle" />
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-brand-blue">
<span className="w-2 h-2 rounded-full bg-brand-blue animate-ping" />
<span>{locale === 'ko' ? '실시간 음성 전사 (faster-whisper)' : 'Streaming STT (faster-whisper)'}</span>
</div>
<p className="text-sm sm:text-base text-neutral-100 font-normal leading-relaxed">
"{typedRaw}"
<span className="inline-block w-1.5 h-4 ml-1 bg-brand-blue animate-pulse align-middle" />
</p>
</div>
)}
{simState === 'done' && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="font-mono text-nano uppercase tracking-widest text-emerald-400 flex items-center gap-1">
AI Polish & Context Applied:
</span>
<span className="font-mono text-nano text-neutral-500">
Inference: 0.84s
</span>
<div className="space-y-3">
<div className="p-2 rounded-lg bg-surface-900 border border-white/[0.05]">
<span className="text-[11px] text-neutral-400 block mb-0.5">{locale === 'ko' ? '원문 입력' : 'Raw Audio'}</span>
<p className="text-xs text-neutral-400 line-through font-normal leading-normal">{sampleRawText}</p>
</div>
<div className="p-2.5 rounded-lg bg-emerald-500/10 border border-emerald-500/25">
<span className="text-xs font-medium text-emerald-400 block mb-0.5"> {locale === 'ko' ? 'AI 완벽 정제본 (Ollama 로컬 LLM)' : 'Polished by Local LLM'}</span>
<p className="text-sm font-medium text-emerald-200 leading-relaxed">{typedClean}</p>
</div>
<p className="text-sm font-sans text-emerald-300 leading-relaxed font-medium bg-emerald-500/10 p-2.5 rounded-lg border border-emerald-500/20">
&quot;{typedClean}&quot;
</p>
</div>
)}
</div>
{/* Telemetry Hardware Stats */}
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-white/[0.04] font-mono text-nano text-neutral-500">
{/* Telemetry Stats Bar */}
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-white/[0.06] text-xs font-normal text-neutral-400">
<div>
<span className="block text-neutral-600">STT ENGINE</span>
<span className="text-neutral-300 font-semibold">faster-whisper</span>
<span className="block text-[11px] text-neutral-500">STT </span>
<span className="font-mono text-neutral-300">faster-whisper</span>
</div>
<div>
<span className="block text-neutral-600">LLM MODEL</span>
<span className="text-neutral-300 font-semibold">Ollama Local</span>
<span className="block text-[11px] text-neutral-500">LLM </span>
<span className="font-mono text-neutral-300">Ollama Local</span>
</div>
<div>
<span className="block text-neutral-600">CLOUD LEAK</span>
<span className="text-emerald-400 font-semibold">0.00 KB</span>
<span className="block text-[11px] text-neutral-500"> </span>
<span className="font-mono text-emerald-400 font-medium">0.00 KB</span>
</div>
</div>
</div>
@ -283,55 +286,42 @@ export function Hero() {
</div>
</div>
{/* Bottom Data Telemetry Strip */}
<div className="border-t border-white/[0.06] pt-8 mt-auto">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
<DataPoint label={t.hero.dataProcessing} value="100%" unit="local" />
<DataPoint label={t.hero.dataCloudTraffic} value="0" unit="bytes" />
<DataPoint label={t.hero.dataLatency} value="<1.2s" />
<DataPoint label={t.hero.dataPrivacy} value="10/10" />
</div>
{/* Bottom DataPoints Row */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 pt-10 border-t border-white/10">
{heroDataPoints.map((dp, i) => (
<DataPoint key={i} label={dp.label} value={dp.value} unit={dp.unit} />
))}
</div>
</Container>
</section>
)
}
function AndroidIcon() {
function WindowsIcon() {
return (
<svg className="w-4 h-4 text-emerald-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.523 15.3414c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.551 0 .9993.4482.9993.9993.0001.5511-.4483.9997-.9993.9997m-11.046 0c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.5511 0 .9993.4482.9993.9993 0 .5511-.4482.9997-.9993.9997m11.4045-6.02l1.996-3.4572a.416.416 0 0 0-.1521-.5676.416.416 0 0 0-.5676.1521l-2.0223 3.503C15.5902 8.411 13.856 8.125 12 8.125s-3.5902.286-5.1315.8267L4.8462 5.4487a.4161.4161 0 0 0-.5677-.1521.4157.4157 0 0 0-.1521.5676l1.996 3.4572C2.6889 11.1867.3432 14.6589 0 18.775h24c-.3432-4.1161-2.6889-7.5883-6.1185-9.4536" />
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801" />
</svg>
)
}
function AppleIcon() {
return (
<svg className="w-4 h-4 text-neutral-200" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M15.97 6.37c.61-.75 1.04-1.8 1.01-2.87-.96.04-2.13.64-2.79 1.43-.58.68-.99 1.74-.94 2.82 1.08.08 2.11-.63 2.72-1.38z" />
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M15.97 6.37c.61-.75 1.04-1.8 1.01-2.87-.96.04-2.12.64-2.77 1.41-.58.68-1.08 1.74-.95 2.78 1.07.08 2.14-.54 2.71-1.32z" />
</svg>
)
}
function WindowsIcon() {
function AndroidIcon() {
return (
<svg className="w-4 h-4 text-sky-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.551H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-13.051-1.851" />
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.523 15.3414c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.551 0 .9993.4482.9993.9993.0001.5511-.4483.9997-.9993.9997m-11.046 0c-.5511 0-.9993-.4486-.9993-.9997s.4482-.9993.9993-.9993c.5511 0 .9993.4482.9993.9993 0 .5511-.4482.9997-.9993.9997m11.4045-6.02l1.9973-3.4592a.416.416 0 00-.1521-.5676.416.416 0 00-.5676.1521l-2.0223 3.503C15.5902 8.4111 13.8533 8.0805 12 8.0805s-3.5902.3306-5.1367.8692L4.841 5.4467a.4161.4161 0 00-.5677-.1521.4157.4157 0 00-.1521.5676l1.9973 3.4592C2.6889 11.1867.3432 14.6589 0 18.761h24c-.3432-4.1021-2.6889-7.5743-6.1185-9.4396" />
</svg>
)
}
function ArrowIcon() {
return (
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7" />
<polyline points="7 7 17 7 17 17" />
</svg>
)
}
function MicIcon() {
function MicSmallIcon() {
return (
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="9" y="2" width="6" height="11" rx="3" />
@ -341,3 +331,11 @@ function MicIcon() {
)
}
function ArrowIcon() {
return (
<svg className="w-3.5 h-3.5 text-neutral-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7" />
<polyline points="7 7 17 7 17 17" />
</svg>
)
}

View file

@ -63,7 +63,7 @@ export function HowItWorks() {
const current = presets[activePreset]
return (
<section id="pipeline" className="relative py-24 md:py-32">
<section id="pipeline" className="relative py-20 md:py-28">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-surface-800/40 to-transparent pointer-events-none" />
<Container className="relative">
@ -74,122 +74,107 @@ export function HowItWorks() {
/>
{/* Preset Selector Bar */}
<div className="flex items-center justify-center gap-2 mb-10 overflow-x-auto pb-2">
<div className="flex items-center justify-center gap-2.5 mb-10 overflow-x-auto pb-2">
{presets.map((p, idx) => (
<button
key={p.id}
type="button"
onClick={() => setActivePreset(idx)}
className={`px-4 py-2 rounded-panel font-mono text-xs uppercase tracking-wider transition-all flex items-center gap-2 ${
className={`px-4.5 py-2 rounded-xl text-xs sm:text-sm font-medium transition-all flex items-center gap-2 shadow-sm active:scale-[0.98] ${
activePreset === idx
? 'bg-brand-amber text-white shadow-glow-sm font-semibold'
: 'bg-surface-700/60 text-neutral-400 hover:text-neutral-200 border border-white/[0.04]'
? 'bg-brand-blue text-white shadow-glow-sm font-medium'
: 'bg-surface-800/80 text-neutral-300 hover:text-white hover:bg-surface-700 border border-white/10'
}`}
>
<span className="w-1.5 h-1.5 rounded-full bg-current" />
<span className={`w-2 h-2 rounded-full ${activePreset === idx ? 'bg-white' : 'bg-brand-blue'}`} />
{p.title}
</button>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-start">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10 lg:gap-12 items-start">
{/* Left: Interactive CRT Studio Screen */}
<Crosshair className="order-2 lg:order-1">
<div className="crt-screen rounded-card p-6 md:p-8 aspect-[4/3] flex flex-col justify-between noise-texture shadow-2xl">
<div className="relative z-10 flex items-center justify-between mb-4">
<div className="crt-screen rounded-3xl p-6 md:p-7 flex flex-col justify-between noise-texture shadow-2xl border border-white/15">
<div className="relative z-10 flex items-center justify-between mb-4 pb-3 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<span className="font-mono text-nano uppercase tracking-widest text-brand-amber/80">
{current.tag} PIPELINE
<span className="text-xs font-medium text-brand-blue">
{current.tag}
</span>
<span className="font-mono text-nano text-neutral-500 bg-surface-600/60 px-2 py-0.5 rounded border border-white/[0.04]">
<span className="font-mono text-xs text-neutral-300 bg-surface-700/80 px-2 py-0.5 rounded-md border border-white/10">
{current.model}
</span>
</div>
<div className="flex items-center gap-2">
<Led color="green" pulse />
<span className="font-mono text-nano uppercase tracking-widest text-emerald-400">{t.pipeline.panelActive}</span>
<span className="text-xs font-medium text-emerald-400">{t.pipeline.panelActive}</span>
</div>
</div>
<div className="relative z-10 flex-1 flex flex-col justify-center space-y-4">
<div className="relative z-10 flex-1 flex flex-col justify-center space-y-3.5">
{/* Simulated live pipeline flow */}
<div className="font-mono text-xs text-neutral-500 leading-relaxed space-y-3">
<div className="flex items-center justify-between text-neutral-400 bg-surface-950/60 p-2.5 rounded border border-white/[0.04]">
<div className="flex items-center gap-2">
<div className="text-xs text-neutral-400 leading-relaxed space-y-3 font-normal">
<div className="flex items-center justify-between text-neutral-200 bg-surface-950/80 p-3 rounded-xl border border-white/10">
<div className="flex items-center gap-2.5">
<Led color="green" size="sm" />
<span className="text-emerald-400/90 font-semibold">{t.pipeline.sttReady}</span>
<span className="text-neutral-500">faster-whisper base</span>
<span className="text-emerald-400 font-medium text-xs">{t.pipeline.sttReady}</span>
</div>
<span className="text-nano text-neutral-600 font-mono">0ms</span>
<WaveBars className="h-4" />
</div>
<div className="p-3 rounded-lg bg-surface-900/90 border border-white/[0.06] space-y-1.5">
<div className="flex items-center justify-between">
<span className="font-mono text-nano uppercase tracking-widest text-brand-amber/70 flex items-center gap-1.5">
<Led color="amber" size="sm" pulse />
Raw Voice STT Output:
</span>
<span className="font-mono text-nano text-neutral-600">~0.42s</span>
<div className="p-3.5 rounded-xl bg-surface-950/90 border border-white/10 space-y-2">
<div className="flex items-center justify-between text-[11px] text-neutral-400 pb-1.5 border-b border-white/[0.06]">
<span>1단계: RAW (faster-whisper)</span>
<span className="font-mono text-neutral-400">~0.42s</span>
</div>
<p className="text-sm font-sans text-neutral-300 font-medium">
&quot;{current.raw}&quot;
<p className="text-neutral-300 font-normal leading-normal italic text-xs">
"{current.raw}"
</p>
</div>
<div className="p-3 rounded-lg bg-emerald-500/10 border border-emerald-500/20 space-y-1.5">
<div className="flex items-center justify-between">
<span className="font-mono text-nano uppercase tracking-widest text-emerald-400 flex items-center gap-1.5">
<Led color="green" size="sm" />
Transformed & Injected to Active Window:
</span>
<span className="font-mono text-nano text-emerald-400/80 font-bold">{current.latency}</span>
<div className="p-3.5 rounded-xl bg-brand-blue/[0.08] border border-brand-blue/30 space-y-2">
<div className="flex items-center justify-between text-[11px] text-brand-blue pb-1.5 border-b border-brand-blue/20">
<span className="font-medium">2단계: LLM </span>
<span className="font-mono text-emerald-400 font-medium">{current.latency}</span>
</div>
<p className="text-sm font-sans text-emerald-200 font-semibold">
&quot;{current.clean}&quot;
<p className="text-white font-medium leading-relaxed text-sm">
"{current.clean}"
</p>
</div>
</div>
</div>
<div className="relative z-10 flex items-center justify-between pt-4 border-t border-white/[0.04]">
<WaveBars className="h-6" />
<span className="font-mono text-nano text-neutral-400">
Total Engine Latency: <strong className="text-white">{current.latency}</strong> (Zero Cloud Traffic)
</span>
<div className="pt-3 border-t border-white/[0.08] flex items-center justify-between text-xs text-neutral-400 font-normal">
<span> : <strong className="text-emerald-400 font-mono font-medium">{current.latency}</strong></span>
<span className="text-emerald-400 font-medium"> 0</span>
</div>
</div>
</div>
</Crosshair>
{/* Right: Numbered Architecture Steps */}
<div className="order-1 lg:order-2 space-y-2">
{/* Right: Architecture Steps Breakdown */}
<div className="order-1 lg:order-2 space-y-4">
{t.pipeline.steps.map((step, i) => (
<div
key={i}
className="group relative flex gap-5 p-5 rounded-card border border-white/[0.04] bg-surface-700/20 hover:border-brand-amber/30 hover:bg-surface-700/40 transition-all"
className="group relative p-5 rounded-2xl bg-surface-800/80 border border-white/10 hover:border-brand-blue/40 hover:bg-surface-700/80 shadow-glass-card transition-all flex gap-4 backdrop-blur-xl"
>
{/* Number */}
<div className="flex-shrink-0 w-12 h-12 rounded-card bg-surface-600 border border-white/[0.06] flex items-center justify-center shadow-sm">
<span className="font-mono text-lg font-bold text-brand-amber">{String(i + 1).padStart(2, '0')}</span>
<div className="w-11 h-11 rounded-xl bg-surface-700 border border-white/10 flex items-center justify-center flex-shrink-0 text-brand-blue font-medium text-sm group-hover:border-brand-blue/40 group-hover:bg-brand-blue/10 transition-colors shadow-sm">
{String(i + 1).padStart(2, '0')}
</div>
<div className="flex-1 min-w-0">
<span className="font-mono text-nano uppercase tracking-widest text-brand-amber/70 block mb-1">
{step.label}
</span>
<h3 className="font-display text-lg font-semibold text-neutral-100 mb-1.5">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-[11px] text-brand-blue font-medium">{step.label}</span>
</div>
<h3 className="text-base sm:text-lg font-medium text-neutral-50 mb-1.5 tracking-tight group-hover:text-white transition-colors">
{step.title}
</h3>
<p className="text-sm text-neutral-400 leading-relaxed mb-2">
<p className="text-sm text-neutral-300 leading-relaxed font-normal">
{step.description}
</p>
<span className="inline-block font-mono text-nano text-neutral-400 bg-surface-600/80 px-2.5 py-1 rounded-sm border border-white/[0.05]">
<span className="mt-2.5 inline-block text-xs text-neutral-400 font-mono bg-surface-900/60 px-2.5 py-0.5 rounded border border-white/[0.05]">
{step.detail}
</span>
</div>
{/* Connector */}
{i < t.pipeline.steps.length - 1 && (
<div className="absolute left-[42px] top-[72px] w-px h-6 bg-gradient-to-b from-brand-amber/30 to-transparent hidden lg:block" />
)}
</div>
))}
</div>

View file

@ -8,7 +8,7 @@ function CellValue({ value }: { value: string | boolean }) {
if (value === true) {
return (
<span className="flex items-center justify-center">
<span className="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.8)]" />
<span className="w-2.5 h-2.5 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.9)]" />
</span>
)
}
@ -19,7 +19,7 @@ function CellValue({ value }: { value: string | boolean }) {
</span>
)
}
return <span className="font-mono text-xs text-neutral-300">{value}</span>
return <span className="text-xs font-medium text-neutral-200">{value}</span>
}
const PRICES = {
@ -112,8 +112,8 @@ export function Pricing() {
id: 'team',
name: locale === 'ko' ? 'Team' : 'Team',
price: `${currencySymbol}${formatPrice(teamPrice)}`,
period: isAnnual ? `${t.pricing.annual} / ${locale === 'ko' ? '인' : 'seat'}` : `${t.pricing.monthly} / ${locale === 'ko' ? '인' : 'seat'}`,
subPrice: isAnnual ? `${currencySymbol}${formatPrice(Math.round(activePrices.team.annual / 12))}${t.pricing.perMonth} / ${locale === 'ko' ? '인' : 'seat'}` : null,
period: isAnnual ? `${periodLabel} (1인)` : `${periodLabel} (1인)`,
subPrice: isAnnual ? `${currencySymbol}${formatPrice(Math.round(activePrices.team.annual / 12))}${t.pricing.perMonth}` : null,
desc: locale === 'ko' ? '팀 공유 사전 & 지식 협업 & 엔터프라이즈 거버넌스' : 'Team shared dictionaries, knowledge & governance',
popular: false,
buttonText: locale === 'ko' ? '팀 도입 문의' : 'Contact Sales',
@ -129,7 +129,7 @@ export function Pricing() {
]
return (
<section id="pricing" className="relative py-24 md:py-32">
<section id="pricing" className="relative py-20 md:py-28">
<Container>
<SectionHeader
index={t.pricing.index}
@ -138,21 +138,23 @@ export function Pricing() {
/>
{/* Reverse Trial Banner */}
<div className="mb-10 p-4 rounded-xl bg-gradient-to-r from-brand-amber/20 via-surface-700/60 to-brand-amber/20 border border-brand-amber/40 shadow-glow-sm flex flex-col md:flex-row items-center justify-between gap-4 max-w-4xl mx-auto">
<div className="flex items-center gap-3">
<span className="text-xl"></span>
<div className="mb-12 px-6 sm:px-8 py-5 sm:py-6 rounded-2xl sm:rounded-3xl bg-gradient-to-r from-brand-blue/15 via-surface-800/90 to-brand-blue/15 border border-brand-blue/40 shadow-glow-sm flex flex-col md:flex-row items-start md:items-center justify-between gap-5 max-w-4xl mx-auto backdrop-blur-xl">
<div className="flex items-center gap-4 sm:gap-5">
<div className="w-10 h-10 rounded-2xl bg-brand-blue/20 border border-brand-blue/40 flex items-center justify-center text-brand-blue flex-shrink-0 shadow-sm text-lg">
</div>
<div className="text-left">
<p className="font-mono text-xs font-bold uppercase tracking-wider text-brand-amber">
<p className="text-sm sm:text-base font-medium text-brand-blue">
{locale === 'ko' ? '14일 Reverse-Trial 무료 체험' : '14-Day Free Reverse Trial'}
</p>
<p className="text-xs text-neutral-300">
<p className="text-xs sm:text-sm text-neutral-300 mt-1 font-normal leading-relaxed">
{locale === 'ko'
? '앱 설치 즉시 신용카드 등록 없이 Pro+의 모든 AI 기능을 14일 동안 무료로 체험할 수 있습니다.'
: 'Experience full Pro+ capabilities for 14 days immediately after install — no credit card required.'}
</p>
</div>
</div>
<span className="px-3 py-1 rounded-full bg-brand-amber text-neutral-950 font-mono text-[10px] font-bold uppercase tracking-widest whitespace-nowrap">
<span className="px-3.5 py-1.5 rounded-full bg-brand-blue/20 text-brand-blue border border-brand-blue/35 text-xs font-medium whitespace-nowrap shadow-sm self-start md:self-auto flex-shrink-0">
{locale === 'ko' ? '자동 활성화' : 'Zero Friction'}
</span>
</div>
@ -160,23 +162,25 @@ export function Pricing() {
{/* Currency and Billing Toggles */}
<div className="flex flex-wrap items-center justify-center gap-4 mb-12">
{/* Currency Toggle */}
<div className="flex items-center rounded-panel bg-surface-800 p-1 border border-white/[0.08]">
<div className="flex items-center rounded-xl bg-surface-800 p-1 border border-white/10 shadow-sm">
<button
type="button"
onClick={() => setCurrency('usd')}
className={`font-mono text-xs px-3.5 py-1.5 rounded transition-all ${
className={`text-xs px-3.5 py-1.5 rounded-lg transition-all ${
currency === 'usd'
? 'bg-surface-500 text-neutral-100 font-bold shadow-sm'
: 'text-neutral-500 hover:text-neutral-300'
? 'bg-surface-600 text-neutral-50 font-medium shadow-sm'
: 'text-neutral-400 hover:text-white font-normal'
}`}
>
USD ($)
</button>
<button
type="button"
onClick={() => setCurrency('krw')}
className={`font-mono text-xs px-3.5 py-1.5 rounded transition-all ${
className={`text-xs px-3.5 py-1.5 rounded-lg transition-all ${
currency === 'krw'
? 'bg-surface-500 text-neutral-100 font-bold shadow-sm'
: 'text-neutral-500 hover:text-neutral-300'
? 'bg-surface-600 text-neutral-50 font-medium shadow-sm'
: 'text-neutral-400 hover:text-white font-normal'
}`}
>
KRW ()
@ -184,28 +188,30 @@ export function Pricing() {
</div>
{/* Billing Cycle Toggle */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-2.5">
<button
type="button"
onClick={() => setIsAnnual(false)}
className={`font-mono text-xs uppercase tracking-widest px-5 py-2.5 rounded-panel transition-all ${
className={`text-xs px-4 py-2 rounded-xl transition-all ${
!isAnnual
? 'bg-surface-500 text-neutral-100 border border-white/[0.12] shadow-sm'
: 'text-neutral-500 hover:text-neutral-300'
? 'bg-surface-600 text-neutral-50 border border-white/15 shadow-sm font-medium'
: 'text-neutral-400 hover:text-white border border-transparent font-normal'
}`}
>
{t.pricing.billingToggleMonthly}
</button>
<button
type="button"
onClick={() => setIsAnnual(true)}
className={`font-mono text-xs uppercase tracking-widest px-5 py-2.5 rounded-panel transition-all relative ${
className={`text-xs px-4 py-2 rounded-xl transition-all relative ${
isAnnual
? 'bg-surface-500 text-neutral-100 border border-brand-amber/40 shadow-glow-sm'
: 'text-neutral-500 hover:text-neutral-300'
? 'bg-surface-600 text-neutral-50 border border-brand-blue/40 shadow-glow-sm font-medium'
: 'text-neutral-400 hover:text-white border border-transparent font-normal'
}`}
>
{t.pricing.billingToggleAnnual}
{isAnnual && (
<span className="absolute -top-2.5 -right-2.5 px-2 py-0.5 rounded-full bg-brand-amber text-white text-[9px] font-mono font-bold uppercase tracking-wider shadow-sm">
<span className="absolute -top-2 -right-2 px-2 py-0.5 rounded-full bg-brand-blue text-white text-[10px] font-medium shadow-sm">
{t.pricing.savePercent}
</span>
)}
@ -214,128 +220,98 @@ export function Pricing() {
</div>
{/* 4 Tier Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 mb-16">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 xl:gap-5 mb-14">
{tiers.map((tier) => (
<div
key={tier.id}
className={`relative rounded-2xl p-6 flex flex-col justify-between noise-texture transition-all duration-300 ${
className={`relative rounded-3xl p-5 xl:p-6 flex flex-col justify-between noise-texture backdrop-blur-xl transition-all duration-300 ${
tier.popular
? 'bg-surface-700/70 border-2 border-brand-amber shadow-glow-md'
: 'bg-surface-700/40 border border-white/[0.06] hover:border-white/[0.15]'
? 'bg-surface-800/90 border-2 border-brand-blue shadow-glow-md'
: 'bg-surface-800/70 border border-white/10 hover:border-brand-blue/35 hover:bg-surface-700/80 shadow-glass-card'
}`}
>
{tier.popular && (
<div className="absolute -top-3.5 left-1/2 -translate-x-1/2">
<span className="px-3.5 py-1 rounded-full bg-brand-amber text-white text-nano font-mono font-bold uppercase tracking-widest shadow-md">
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="px-3.5 py-0.5 rounded-full bg-brand-blue text-white text-xs font-medium shadow-md">
{t.pricing.popular}
</span>
</div>
)}
<div>
<div className="flex items-center justify-between mb-4">
<span className={`font-mono text-xs font-bold uppercase tracking-widest ${tier.popular ? 'text-brand-amber' : 'text-neutral-400'}`}>
<div className="flex items-center justify-between mb-3">
<span className="text-lg font-medium text-neutral-50">
{tier.name}
</span>
</div>
<div className="mb-2 flex items-baseline gap-1.5">
<span className="font-display text-3xl font-extrabold text-neutral-50">
{tier.price}
</span>
<span className="font-sans text-xs text-neutral-400 font-normal">
{tier.period}
</span>
</div>
{tier.subPrice && (
<div className="font-mono text-nano text-neutral-500 mb-4">
{tier.subPrice}
</div>
)}
<p className="text-xs text-neutral-400 mb-5 leading-relaxed min-h-[38px]">
<p className="text-xs text-neutral-300 min-h-[36px] mb-4 font-normal leading-relaxed">
{tier.desc}
</p>
<div className="h-px bg-white/[0.06] mb-5" />
<div className="mb-5 pb-4 border-b border-white/[0.08]">
<div className="flex items-baseline flex-wrap gap-1.5">
<span className="text-2xl sm:text-[1.65rem] xl:text-[1.85rem] font-medium text-neutral-50 tracking-tight whitespace-nowrap">
{tier.price}
</span>
<span className="text-xs text-neutral-400 font-normal whitespace-nowrap">
{tier.period}
</span>
</div>
{tier.subPrice && (
<span className="block text-xs text-neutral-400 font-normal mt-1">
( {tier.subPrice})
</span>
)}
</div>
<ul className="space-y-2.5 mb-7">
{tier.features.map((feat, idx) => (
<li key={idx} className="flex items-start gap-2.5 text-xs text-neutral-300">
<span className="text-emerald-400 font-bold mt-0.5"></span>
<span>{feat}</span>
<ul className="space-y-2.5 mb-6 text-xs sm:text-sm text-neutral-300 font-normal">
{tier.features.map((feat, i) => (
<li key={i} className="flex items-start gap-2">
<span className="text-emerald-400 font-medium flex-shrink-0 mt-0.5"></span>
<span className="leading-snug">{feat}</span>
</li>
))}
</ul>
</div>
<GlowButton href="#" variant={tier.buttonVariant} size="md" className="w-full text-xs">
<GlowButton
href="#download"
variant={tier.buttonVariant}
size="md"
className="w-full text-xs sm:text-sm font-medium py-3 sm:py-3.5"
>
{tier.buttonText}
</GlowButton>
</div>
))}
</div>
{/* Detailed Comparison Table */}
{/* Feature Comparison Matrix */}
<div className="overflow-x-auto -mx-5 md:mx-0">
<table className="w-full min-w-[700px] border-collapse bg-surface-800/30 rounded-xl overflow-hidden border border-white/[0.04]">
{/* Header */}
<table className="w-full min-w-[700px] text-left border-collapse rounded-2xl overflow-hidden bg-surface-800/60 border border-white/10 shadow-glass-card backdrop-blur-xl">
<thead>
<tr className="border-b border-white/[0.08] bg-surface-700/50">
<th className="text-left py-4 px-5 font-mono text-nano uppercase tracking-widest text-neutral-400 w-[35%]">
{t.pricing.featureLabel}
</th>
<th className="text-center py-4 px-3 w-[15%] font-mono text-nano uppercase tracking-widest text-neutral-300">
{t.pricing.free}
</th>
<th className="text-center py-4 px-3 w-[18%] font-mono text-nano uppercase tracking-widest text-brand-amber bg-brand-amber/[0.04]">
{t.pricing.pro}
</th>
<th className="text-center py-4 px-3 w-[16%] font-mono text-nano uppercase tracking-widest text-neutral-300">
{t.pricing.proPlus}
</th>
<th className="text-center py-4 px-3 w-[16%] font-mono text-nano uppercase tracking-widest text-neutral-300">
Team
</th>
<tr className="border-b border-white/10 bg-surface-700/80 text-xs text-neutral-200 font-medium">
<th className="p-4 w-2/5"> </th>
<th className="p-4 text-center w-[15%]">FREE</th>
<th className="p-4 text-center w-[15%] text-brand-blue font-medium">PRO</th>
<th className="p-4 text-center w-[15%] text-purple-300">PRO+</th>
<th className="p-4 text-center w-[15%]">TEAM</th>
</tr>
</thead>
{/* Body */}
<tbody>
{t.pricing.rows.map((row, i) => (
<tr
key={i}
className={`border-b border-white/[0.03] hover:bg-surface-700/30 transition-colors ${
i % 2 === 0 ? 'bg-transparent' : 'bg-surface-800/20'
}`}
>
<td className="py-3 px-5 font-sans text-xs text-neutral-300 font-medium">
{row.feature}
</td>
<td className="py-3 px-3 text-center">
<CellValue value={row.free} />
</td>
<td className="py-3 px-3 text-center bg-brand-amber/[0.04]">
<CellValue value={row.pro} />
</td>
<td className="py-3 px-3 text-center">
<CellValue value={row.proPlus} />
</td>
<td className="py-3 px-3 text-center">
<CellValue value={true} />
</td>
<tbody className="divide-y divide-white/[0.05] text-xs sm:text-sm">
{t.pricing.rows.map((row, idx) => (
<tr key={idx} className="hover:bg-surface-700/30 transition-colors">
<td className="p-3.5 text-neutral-300 font-normal">{row.feature}</td>
<td className="p-3.5 text-center text-neutral-400"><CellValue value={row.free} /></td>
<td className="p-3.5 text-center bg-brand-blue/[0.03]"><CellValue value={row.pro} /></td>
<td className="p-3.5 text-center bg-purple-500/[0.03]"><CellValue value={row.proPlus} /></td>
<td className="p-3.5 text-center"><CellValue value={row.proPlus} /></td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-center font-mono text-nano text-neutral-500 mt-8 uppercase tracking-widest">
{locale === 'ko'
? '모든 가격은 세금 별도입니다. Stripe & Toss Payments / PortOne / Payple을 통한 안전한 결제 및 세금계산서 자동 발행.'
: 'All prices exclude tax. Secure global checkout via Stripe and localized recurring payments.'}
</p>
</Container>
</section>
)

View file

@ -21,20 +21,20 @@ export function Privacy() {
},
{
feature: locale === 'ko' ? '네트워크 전송 지연 (RTT)' : 'Network Roundtrip Latency',
d3ro: '0ms (On-Device Memory)',
d3ro: '0ms (온디바이스 메모리)',
cloud: '600ms ~ 2,500ms',
d3roOk: true,
},
{
feature: locale === 'ko' ? '무제한 딕테이션 비용' : 'Unlimited Voice Dictation Cost',
d3ro: locale === 'ko' ? '무료 ($0 Forever)' : '$0 Forever Free',
cloud: '$15 ~ $30 / Month',
d3ro: locale === 'ko' ? '영구 무료 (₩0)' : '$0 Forever Free',
cloud: '월 2만 ~ 4만원',
d3roOk: true,
},
]
return (
<section id="privacy" className="relative py-24 md:py-32 overflow-hidden">
<section id="privacy" className="relative py-20 md:py-28 overflow-hidden">
<Container>
<SectionHeader
index={t.privacy.index}
@ -43,31 +43,31 @@ export function Privacy() {
/>
{/* CRT instrument panel */}
<div className="crt-screen rounded-2xl p-6 md:p-10 noise-texture mb-12 shadow-2xl border border-white/[0.08]">
<div className="crt-screen rounded-3xl p-6 md:p-8 noise-texture mb-12 shadow-2xl border border-white/15">
<div className="relative z-10">
{/* Panel header */}
<div className="flex items-center justify-between mb-8 pb-4 border-b border-white/[0.06]">
<div className="flex items-center justify-between mb-6 pb-3.5 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<Led color="green" pulse />
<span className="font-mono text-nano uppercase tracking-widest text-emerald-400">
<span className="text-xs font-medium text-emerald-400">
{t.privacy.monitorTitle}
</span>
</div>
<span className="font-mono text-nano uppercase tracking-widest text-neutral-400 bg-surface-600/60 px-2.5 py-1 rounded border border-white/[0.04]">
AIR-GAP COMPLIANT
<span className="text-xs font-medium text-neutral-300 bg-surface-700/80 px-3 py-1 rounded-full border border-white/10">
</span>
</div>
{/* Metric grid */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-6 md:gap-8">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{t.privacy.metrics.map((metric, i) => (
<div key={i} className="flex flex-col gap-2 p-3 rounded-lg bg-surface-900/60 border border-white/[0.03]">
<span className="font-mono text-nano uppercase tracking-widest text-neutral-500">
<div key={i} className="flex flex-col gap-1.5 p-3.5 rounded-xl bg-surface-900/80 border border-white/10 shadow-inner">
<span className="text-xs text-neutral-400 font-normal">
{metric.label}
</span>
<div className="flex items-center gap-2">
<Led color="green" size="sm" />
<span className="font-mono text-sm font-semibold text-neutral-100 tracking-wide">
<span className="text-sm sm:text-base font-medium text-neutral-100">
{metric.value}
</span>
</div>
@ -78,23 +78,23 @@ export function Privacy() {
</div>
{/* Comparison Matrix: D3RO Voice vs Cloud Services */}
<div className="mb-12 overflow-x-auto">
<table className="w-full text-left border-collapse rounded-xl overflow-hidden bg-surface-800/40 border border-white/[0.06]">
<div className="mb-12 overflow-x-auto -mx-5 md:mx-0">
<table className="w-full min-w-[650px] text-left border-collapse rounded-2xl overflow-hidden bg-surface-800/60 border border-white/10 shadow-glass-card backdrop-blur-xl">
<thead>
<tr className="border-b border-white/[0.08] bg-surface-700/50 font-mono text-nano uppercase tracking-wider text-neutral-400">
<tr className="border-b border-white/10 bg-surface-700/80 text-xs text-neutral-300 font-medium">
<th className="p-4 w-1/2">{locale === 'ko' ? '보안 및 프라이버시 비교 기준' : 'Security & Privacy Criterion'}</th>
<th className="p-4 text-brand-amber font-bold w-1/4">D3RO VOICE (LOCAL)</th>
<th className="p-4 text-neutral-500 w-1/4">CLOUD STT SERVICES</th>
<th className="p-4 text-brand-blue font-medium w-1/4">D3RO VOICE ()</th>
<th className="p-4 text-neutral-400 w-1/4"> STT</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.04] text-xs">
<tbody className="divide-y divide-white/[0.06] text-sm">
{comparisonRows.map((row, idx) => (
<tr key={idx} className="hover:bg-surface-700/30 transition-colors">
<td className="p-4 font-sans font-medium text-neutral-200">{row.feature}</td>
<td className="p-4 font-mono font-bold text-emerald-400 bg-emerald-500/[0.04]">
<tr key={idx} className="hover:bg-surface-700/40 transition-colors">
<td className="p-4 text-neutral-200 font-normal">{row.feature}</td>
<td className="p-4 font-medium text-emerald-400 bg-emerald-500/10">
{row.d3ro}
</td>
<td className="p-4 font-mono text-neutral-500">
<td className="p-4 text-neutral-400 font-normal">
{row.cloud}
</td>
</tr>
@ -103,19 +103,19 @@ export function Privacy() {
</table>
</div>
{/* Guarantees list */}
{/* Guarantee bullet cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{t.privacy.guarantees.map((item, i) => (
{t.privacy.guarantees.map((g, i) => (
<div
key={i}
className="flex items-start gap-3 p-4 rounded-xl bg-surface-700/40 border border-white/[0.04]"
className="flex items-center gap-3.5 px-5 py-4 rounded-2xl bg-surface-800/70 border border-white/10 backdrop-blur-sm shadow-sm"
>
<div className="w-5 h-5 rounded-full bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<svg className="w-3 h-3 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<div className="w-5 h-5 rounded-full bg-emerald-500/15 border border-emerald-500/30 flex items-center justify-center text-emerald-400 flex-shrink-0">
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
<span className="text-sm text-neutral-300 leading-relaxed font-medium">{item}</span>
<span className="text-sm text-neutral-200 font-normal">{g}</span>
</div>
))}
</div>