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

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

View file

@ -1,4 +1,4 @@
import { I18nProvider } from './i18n'
import { I18nProvider, useI18n } from './i18n'
import { Header } from './sections/Header'
import { Hero } from './sections/Hero'
import { Features } from './sections/Features'
@ -7,26 +7,32 @@ import { Privacy } from './sections/Privacy'
import { Pricing } from './sections/Pricing'
import { Download } from './sections/Download'
import { FAQ } from './sections/FAQ'
import { CTA } from './sections/CTA'
import { Footer } from './sections/Footer'
function Page() {
const { t } = useI18n()
return (
<div id="top">
<a href="#main" className="skip-link">{t.a11y.skipToContent}</a>
<Header />
<main id="main" tabIndex={-1} className="outline-none">
<Hero />
<Features />
<HowItWorks />
<Privacy />
<Pricing />
<Download />
<FAQ />
</main>
<Footer />
</div>
)
}
export function App() {
return (
<I18nProvider>
<div className="min-h-screen bg-surface-950">
<Header />
<main>
<Hero />
<Features />
<HowItWorks />
<Privacy />
<Pricing />
<Download />
<FAQ />
<CTA />
</main>
<Footer />
</div>
<Page />
</I18nProvider>
)
}

View file

@ -1,26 +0,0 @@
import type { ReactNode } from 'react'
import { Led } from './Led'
interface BadgeProps {
children: ReactNode
led?: boolean
ledColor?: 'blue' | 'green' | 'red'
className?: string
}
export function Badge({ children, led = false, ledColor = 'blue', className = '' }: BadgeProps) {
return (
<span
className={`
inline-flex items-center gap-2 px-3 py-1
text-xs font-medium
text-neutral-300 bg-surface-800/90 border border-white/10
rounded-full shadow-sm select-none
${className}
`}
>
{led && <Led color={ledColor} size="sm" />}
<span>{children}</span>
</span>
)
}

View file

@ -1,22 +0,0 @@
interface DataPointProps {
label: string
value: string
unit?: string
className?: string
}
export function DataPoint({ label, value, unit, className = '' }: DataPointProps) {
return (
<div className={`flex flex-col ${className}`}>
<span className="text-xs font-normal text-neutral-400 mb-1">
{label}
</span>
<span className="text-2xl sm:text-3xl font-medium text-neutral-50 tracking-tight flex items-baseline">
{value}
{unit && (
<span className="text-xs font-normal text-brand-blue ml-1.5">{unit}</span>
)}
</span>
</div>
)
}

View file

@ -0,0 +1,113 @@
import { useEffect, useRef, useState } from 'react'
import { Crosshair } from './Crosshair'
import { Led } from './Led'
import { WaveBars } from './WaveBars'
import { PlayIcon } from './icons'
import { useI18n } from '../i18n'
import { usePrefersReducedMotion } from '../hooks/usePrefersReducedMotion'
type Phase = 'idle' | 'listening' | 'polishing' | 'done'
const TYPE_INTERVAL_MS = 40
const POLISH_DELAY_MS = 700
/**
* 받아쓰기 과정을 보여 주는 스크립트 예시. 실제 녹음은 하지 않으며 그 사실을 화면에 적는다.
* 감소 모션이면 타이핑 연출 없이 결과로 바로 간다.
*/
export function DictationDemo() {
const { t } = useI18n()
const reducedMotion = usePrefersReducedMotion()
const [phase, setPhase] = useState<Phase>('idle')
const [typed, setTyped] = useState('')
const timers = useRef<number[]>([])
const clearTimers = () => {
timers.current.forEach((id) => window.clearTimeout(id))
timers.current = []
}
useEffect(() => clearTimers, [])
// 언어가 바뀌면 예시 문장이 달라지므로 처음 상태로 돌린다.
useEffect(() => {
clearTimers()
setPhase('idle')
setTyped('')
}, [t])
const play = () => {
clearTimers()
const raw = t.demo.sampleRaw
if (reducedMotion) {
setTyped(raw)
setPhase('done')
return
}
setTyped('')
setPhase('listening')
const chars = Array.from(raw)
chars.forEach((_, i) => {
timers.current.push(window.setTimeout(() => setTyped(chars.slice(0, i + 1).join('')), i * TYPE_INTERVAL_MS))
})
const heard = chars.length * TYPE_INTERVAL_MS
timers.current.push(window.setTimeout(() => setPhase('polishing'), heard))
timers.current.push(window.setTimeout(() => setPhase('done'), heard + POLISH_DELAY_MS))
}
const running = phase === 'listening' || phase === 'polishing'
const status = phase === 'idle' ? t.demo.title : t.demo[phase]
return (
<Crosshair>
<figure className="crt-screen rounded-2xl p-6 md:p-7">
<div className="relative z-10">
<div className="flex items-center justify-between gap-3 border-b border-white/10 pb-4">
<p className="flex items-center gap-2 text-sm text-neutral-200" aria-live="polite">
<Led color={phase === 'done' ? 'green' : 'blue'} pulse={running} />
<span>{status}</span>
</p>
<button
type="button"
onClick={() => { if (!running) play() }}
aria-disabled={running}
className="btn btn-secondary btn-sm aria-disabled:cursor-default aria-disabled:opacity-60"
>
<PlayIcon />
{phase === 'done' ? t.demo.replay : t.demo.play}
</button>
</div>
<div className="flex h-12 items-center justify-center">
<WaveBars active={phase === 'listening'} />
</div>
<div className="min-h-[164px] space-y-3">
{phase === 'idle' ? (
<p className="keep-ko pt-6 text-center text-sm leading-relaxed text-neutral-400">{t.demo.idle}</p>
) : (
<>
<div className="rounded-lg border border-white/10 bg-surface-950/80 p-3.5">
<p className="mb-1 text-xs text-neutral-400">{t.demo.rawLabel}</p>
<p className={`keep-ko text-sm leading-relaxed ${phase === 'done' ? 'text-neutral-400' : 'text-neutral-100'}`}>
{typed}
</p>
</div>
{phase === 'done' && (
<div className="rounded-lg border border-brand-blue/40 bg-brand-blue/10 p-3.5">
<p className="mb-1 text-xs text-brand-blue-light">{t.demo.cleanLabel}</p>
<p className="keep-ko text-base leading-relaxed text-neutral-50">{t.demo.sampleClean}</p>
</div>
)}
</>
)}
</div>
<figcaption className="mt-4 border-t border-white/10 pt-3 text-xs text-neutral-400">
{t.demo.note}
</figcaption>
</div>
</figure>
</Crosshair>
)
}

View file

@ -1,50 +0,0 @@
import type { ReactNode } from 'react'
interface GlowButtonProps {
children: ReactNode
href?: string
download?: string
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
className?: string
onClick?: () => void
}
export function GlowButton({
children,
href,
download,
variant = 'primary',
size = 'md',
className = '',
onClick,
}: GlowButtonProps) {
const base = 'glow-btn inline-flex items-center justify-center font-medium tracking-normal select-none rounded-xl transition-all duration-200 leading-normal'
const variants = {
primary: 'bg-brand-blue text-white hover:bg-brand-blue-light shadow-glow-sm hover:shadow-glow-md active:scale-[0.98]',
secondary: 'bg-surface-700/80 text-neutral-100 border border-white/10 hover:border-brand-blue/40 hover:bg-surface-600 hover:text-white active:scale-[0.98]',
}
const sizes = {
sm: 'px-4.5 py-2 text-xs',
md: 'px-6 py-2.5 sm:py-3 text-xs sm:text-sm font-medium',
lg: 'px-8 sm:px-10 py-3.5 sm:py-4 text-sm sm:text-base font-medium',
}
const cls = `${base} ${variants[variant]} ${sizes[size]} ${className}`
if (href) {
return (
<a href={href} download={download} onClick={onClick} className={cls}>
<span className="relative z-10 flex items-center gap-2.5">{children}</span>
</a>
)
}
return (
<button type="button" onClick={onClick} className={cls}>
<span className="relative z-10 flex items-center gap-2.5">{children}</span>
</button>
)
}

View file

@ -1,25 +0,0 @@
import type { ReactNode } from 'react'
interface InstrumentCardProps {
children: ReactNode
className?: string
hover?: boolean
}
export function InstrumentCard({ children, className = '', hover = true }: InstrumentCardProps) {
return (
<div
className={`
relative noise-texture
bg-surface-800/80 border border-white/10
rounded-2xl shadow-glass-card
p-6 md:p-7 backdrop-blur-xl
${hover ? 'transition-all duration-300 hover:border-brand-blue/35 hover:bg-surface-700/85 hover:shadow-glow-sm' : ''}
${className}
`}
>
<div className="relative z-10">
{children}
</div>
</div>
)
}

View file

@ -1,65 +1,97 @@
import { useState, useRef, useEffect } from 'react'
import { useState, useRef, useEffect, useId } from 'react'
import { useI18n, LOCALES } from '../i18n'
/**
* 언어 선택. 디스클로저 패턴: 버튼이 목록을 열고 닫으며,
* Esc·바깥 클릭·포커스 이탈로 닫힌다. 현재 언어는 aria-current로 알린다.
*/
export function LanguageSwitcher() {
const { locale, setLocale } = useI18n()
const { t, locale, setLocale } = useI18n()
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
const rootRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
const listId = useId()
const current = LOCALES.find((l) => l.code === locale)
useEffect(() => {
function handleClick(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
if (!open) return
const onPointer = (e: PointerEvent) => {
if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false)
}
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.stopPropagation()
setOpen(false)
buttonRef.current?.focus()
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [])
document.addEventListener('pointerdown', onPointer)
document.addEventListener('keydown', onKey, true)
return () => {
document.removeEventListener('pointerdown', onPointer)
document.removeEventListener('keydown', onKey, true)
}
}, [open])
return (
<div ref={ref} className="relative">
<div
ref={rootRef}
className="relative"
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setOpen(false)
}}
>
<button
ref={buttonRef}
type="button"
onClick={() => setOpen(!open)}
className="flex items-center gap-2 px-3 py-1.5 text-xs font-medium text-neutral-300 hover:text-white bg-surface-800/80 hover:bg-surface-700 border border-white/10 hover:border-brand-blue/40 rounded-lg transition-all shadow-sm"
aria-label="Change language"
onClick={() => setOpen((v) => !v)}
aria-expanded={open}
aria-controls={listId}
aria-label={`${t.a11y.language}: ${current?.nativeName ?? ''}`}
className="flex h-11 items-center gap-2 rounded-lg border border-white/10 bg-surface-800 px-3 text-sm text-neutral-200 transition-colors hover:border-white/20 hover:text-white md:h-9"
>
<GlobeIcon />
<span>{current?.label ?? 'EN'}</span>
<span aria-hidden="true">{current?.label ?? 'EN'}</span>
<ChevronIcon open={open} />
</button>
{open && (
<div className="absolute right-0 top-full mt-2 w-44 py-1.5 bg-surface-800/95 backdrop-blur-2xl border border-white/10 rounded-xl shadow-2xl z-50 max-h-80 overflow-y-auto">
{LOCALES.map((l) => (
<button
key={l.code}
type="button"
onClick={() => {
setLocale(l.code)
setOpen(false)
}}
className={`w-full text-left px-3.5 py-2 text-xs flex items-center justify-between transition-colors ${
l.code === locale
? 'text-brand-blue bg-brand-blue/10 font-medium'
: 'text-neutral-300 hover:text-white hover:bg-surface-700/80 font-normal'
}`}
>
<span>{l.nativeName}</span>
<span className="text-[11px] text-neutral-400 font-normal">{l.label}</span>
</button>
))}
</div>
)}
<ul
id={listId}
hidden={!open}
className="absolute bottom-full left-0 z-50 mb-2 max-h-80 w-44 overflow-y-auto rounded-xl border border-white/10 bg-surface-800 py-1.5 shadow-chassis md:bottom-auto md:left-auto md:right-0 md:top-full md:mb-0 md:mt-2"
>
{LOCALES.map((l) => {
const active = l.code === locale
return (
<li key={l.code}>
<button
type="button"
lang={l.code}
aria-current={active ? 'true' : undefined}
onClick={() => {
setLocale(l.code)
setOpen(false)
buttonRef.current?.focus()
}}
className={`flex w-full items-center justify-between px-3.5 py-2.5 text-left text-sm transition-colors ${
active ? 'bg-brand-blue/10 text-brand-blue-light' : 'text-neutral-200 hover:bg-surface-700 hover:text-white'
}`}
>
<span>{l.nativeName}</span>
<span aria-hidden="true" className="text-xs text-neutral-400">{l.label}</span>
</button>
</li>
)
})}
</ul>
</div>
)
}
function GlobeIcon() {
return (
<svg className="w-3.5 h-3.5 text-brand-blue" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" className="h-4 w-4 text-neutral-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
@ -69,15 +101,7 @@ function GlobeIcon() {
function ChevronIcon({ open }: { open: boolean }) {
return (
<svg
className={`w-3 h-3 text-neutral-400 transition-transform ${open ? 'rotate-180' : ''}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<svg aria-hidden="true" className={`h-3 w-3 text-neutral-400 transition-transform ${open ? 'rotate-180' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
</svg>
)

View file

@ -1,34 +0,0 @@
interface SectionHeaderProps {
index: string
title: string
subtitle?: string
className?: string
}
export function SectionHeader({ index, title, subtitle, className = '' }: SectionHeaderProps) {
return (
<div className={`mb-12 md:mb-16 ${className}`}>
<div className="flex items-center gap-3 mb-3.5">
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium text-brand-blue bg-brand-blue/10 border border-brand-blue/25 shadow-sm">
<span className="w-1.5 h-1.5 rounded-full bg-brand-blue" />
{index}
</span>
<div className="h-px flex-1 bg-gradient-to-r from-brand-blue/20 via-white/[0.08] to-transparent" />
</div>
<h2
className="text-2xl sm:text-3xl md:text-4xl font-medium text-neutral-50 tracking-tight leading-[1.22] break-keep"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
{title}
</h2>
{subtitle && (
<p
className="mt-3 max-w-2xl text-sm sm:text-base text-neutral-300 leading-relaxed font-normal break-keep"
style={{ wordBreak: 'keep-all', overflowWrap: 'break-word' }}
>
{subtitle}
</p>
)}
</div>
)
}

View file

@ -0,0 +1,20 @@
interface SectionHeadingProps {
id: string
title: string
subtitle?: string
className?: string
}
/** 섹션 제목. `id`는 섹션의 aria-labelledby 대상이다. */
export function SectionHeading({ id, title, subtitle, className = '' }: SectionHeadingProps) {
return (
<div className={className}>
<h2 id={id} className="keep-ko text-balance text-h2 font-medium text-neutral-50">
{title}
</h2>
{subtitle && (
<p className="mt-4 max-w-xl keep-ko text-base leading-relaxed text-neutral-300">{subtitle}</p>
)}
</div>
)
}

View file

@ -1,47 +1,58 @@
import { useEffect, useRef } from 'react'
import { usePrefersReducedMotion } from '../hooks/usePrefersReducedMotion'
const BAR_COUNT = 9
// 녹음 UI와 같은 cos 분포: 가운데가 가장 높다.
const COS_WEIGHTS = Array.from({ length: BAR_COUNT }, (_, i) =>
Math.cos((i - 4) * (Math.PI / 9)),
)
const REST_HEIGHT = 4
const MAX_EXTRA = 28
interface WaveBarsProps {
className?: string
/** 소리를 듣는 중일 때만 움직인다. 멈추면 낮은 정지 막대로 돌아간다. */
active: boolean
}
export function WaveBars({ className = '' }: WaveBarsProps) {
const barsRef = useRef<(HTMLDivElement | null)[]>([])
export function WaveBars({ active }: WaveBarsProps) {
const barsRef = useRef<(HTMLSpanElement | null)[]>([])
const reducedMotion = usePrefersReducedMotion()
const animate = active && !reducedMotion
useEffect(() => {
let frame: number
let t = 0
const animate = () => {
t += 0.05
barsRef.current.forEach((bar, i) => {
if (!bar) return
const base = COS_WEIGHTS[i]
const wave = Math.sin(t + i * 0.4) * 0.5 + 0.5
const h = 4 + base * wave * 28
bar.style.height = `${h}px`
const bars = barsRef.current
if (!animate) {
bars.forEach((bar, i) => {
if (bar) bar.style.height = `${active ? REST_HEIGHT + COS_WEIGHTS[i] * MAX_EXTRA * 0.6 : REST_HEIGHT}px`
})
frame = requestAnimationFrame(animate)
return
}
frame = requestAnimationFrame(animate)
let frame = 0
let t = 0
const tick = () => {
t += 0.12
bars.forEach((bar, i) => {
if (!bar) return
const wave = Math.sin(t + i * 0.7) * 0.5 + 0.5
bar.style.height = `${REST_HEIGHT + COS_WEIGHTS[i] * wave * MAX_EXTRA}px`
})
frame = requestAnimationFrame(tick)
}
frame = requestAnimationFrame(tick)
return () => cancelAnimationFrame(frame)
}, [])
}, [animate, active])
return (
<div className={`flex items-center gap-[3px] h-10 ${className}`} aria-hidden="true">
<span className="flex h-9 items-center gap-1" aria-hidden="true">
{Array.from({ length: BAR_COUNT }, (_, i) => (
<div
<span
key={i}
ref={(el) => { barsRef.current[i] = el }}
className="w-[3px] rounded-full bg-brand-blue/60"
style={{ height: 4, transition: 'height 60ms ease-out' }}
className={`block w-1 rounded-full ${active ? 'bg-brand-blue-light' : 'bg-white/25'}`}
style={{ height: REST_HEIGHT }}
/>
))}
</div>
</span>
)
}

View file

@ -0,0 +1,14 @@
import { Led } from './Led'
/** 브랜드 워드마크. 헤더와 푸터가 함께 쓴다. */
export function Wordmark() {
return (
<span className="flex items-center gap-2.5 text-base font-medium tracking-tight text-white">
<Led color="blue" size="md" />
<span>
D3RO<span className="text-brand-blue-light" aria-hidden="true">·</span>
<span className="sr-only"> </span>VOICE
</span>
</span>
)
}

View file

@ -0,0 +1,49 @@
// 사이트 공용 아이콘. 모두 장식이므로 aria-hidden 이고, 의미는 옆 텍스트가 전달한다.
interface IconProps {
className?: string
}
export function DownloadIcon({ className = 'h-4 w-4' }: IconProps) {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" 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>
)
}
export function ExternalIcon({ className = 'h-3.5 w-3.5' }: IconProps) {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M7 17 17 7" />
<path d="M8 7h9v9" />
</svg>
)
}
export function CheckIcon({ className = 'h-4 w-4' }: IconProps) {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
)
}
export function PlusIcon({ className = 'h-4 w-4' }: IconProps) {
return (
<svg aria-hidden="true" className={className} 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>
)
}
export function PlayIcon({ className = 'h-3.5 w-3.5' }: IconProps) {
return (
<svg aria-hidden="true" className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M7 4.5v15l13-7.5z" />
</svg>
)
}

View file

@ -1,31 +0,0 @@
import { useEffect, useRef } from 'react'
/**
* Intersection Observer hook for reveal-on-scroll animations.
* Returns a ref to attach to the element that should animate in.
*/
export function useReveal<T extends HTMLElement = HTMLDivElement>(
threshold = 0.15,
) {
const ref = useRef<T>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
el.classList.add('visible')
observer.unobserve(el)
}
},
{ threshold },
)
observer.observe(el)
return () => observer.disconnect()
}, [threshold])
return ref
}

View file

@ -1,110 +1,28 @@
import { useState, useEffect } from 'react'
import {
DESKTOP_VERSION,
DESKTOP_WINDOWS_INSTALLER_FILENAME,
DESKTOP_WINDOWS_INSTALLER_URL,
} from '../release'
export type ClientOSType = 'android' | 'ios' | 'windows' | 'macos' | 'linux'
export type ClientOSType = 'windows' | 'macos' | 'linux' | 'android' | 'ios'
export interface ClientOSInfo {
os: ClientOSType
osName: string
isMobile: boolean
downloadUrl: string
downloadFilename: string
badgeText: string
buttonLabelKo: string
buttonLabelEn: string
subtextKo: string
subtextEn: string
interface NavigatorWithUAData extends Navigator {
userAgentData?: { platform?: string }
}
export const OS_CONFIGS: Record<ClientOSType, ClientOSInfo> = {
android: {
os: 'android',
osName: 'Android',
isMobile: true,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'ANDROID RELEASE PENDING',
buttonLabelKo: 'Android 공식 배포 준비 중',
buttonLabelEn: 'Android release unavailable',
subtextKo: '서명된 배포 증거가 확인될 때까지 다운로드를 제공하지 않습니다.',
subtextEn: 'Download stays unavailable until signed release evidence is verified.',
},
ios: {
os: 'ios',
osName: 'iOS',
isMobile: true,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'iOS DISTRIBUTION UNAVAILABLE',
buttonLabelKo: 'iOS 배포 미지원',
buttonLabelEn: 'iOS distribution unavailable',
subtextKo: '현재 검증된 App Store 또는 TestFlight 배포가 없습니다.',
subtextEn: 'No verified App Store or TestFlight distribution is available.',
},
windows: {
os: 'windows',
osName: 'Windows',
isMobile: false,
downloadUrl: DESKTOP_WINDOWS_INSTALLER_URL,
downloadFilename: DESKTOP_WINDOWS_INSTALLER_FILENAME,
badgeText: `DESKTOP ${DESKTOP_VERSION} OFFICIAL RELEASE`,
buttonLabelKo: `Windows용 다운로드 (x64) - v${DESKTOP_VERSION}`,
buttonLabelEn: `Download for Windows (x64) - v${DESKTOP_VERSION}`,
subtextKo: `서명된 공식 릴리스 v${DESKTOP_VERSION}이며, 설치 후 자동 업데이트 피드로 갱신됩니다.`,
subtextEn: `Signed official release v${DESKTOP_VERSION}, updated through the auto-update feed after install.`,
},
macos: {
os: 'macos',
osName: 'macOS',
isMobile: false,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'MACOS APPLE SILICON PREPARATION',
buttonLabelKo: 'macOS 릴리스 준비 중',
buttonLabelEn: 'macOS release in preparation',
subtextKo: 'Apple Silicon용 빌드는 서명과 설치 검증을 마친 뒤 제공합니다.',
subtextEn: 'The Apple Silicon build will be published after signing and installation verification.',
},
linux: {
os: 'linux',
osName: 'Linux',
isMobile: false,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'LINUX PACKAGE UNAVAILABLE',
buttonLabelKo: 'Linux 배포 미지원',
buttonLabelEn: 'Linux package unavailable',
subtextKo: '현재 검증된 AppImage 또는 DEB 패키지가 없습니다.',
subtextEn: 'No verified AppImage or DEB package is available.',
},
function detectOS(): ClientOSType {
const nav = navigator as NavigatorWithUAData
const ua = nav.userAgent || ''
const platform = nav.userAgentData?.platform || nav.platform || ''
if (/android/i.test(ua)) return 'android'
if (/iphone|ipad|ipod/i.test(ua) || (platform === 'MacIntel' && nav.maxTouchPoints > 1)) return 'ios'
if (/macintosh|mac os x/i.test(ua) || /mac/i.test(platform)) return 'macos'
if (/linux/i.test(ua) || /linux/i.test(platform)) return 'linux'
return 'windows'
}
export function useClientOS(): ClientOSInfo {
const [osInfo, setOsInfo] = useState<ClientOSInfo>(OS_CONFIGS.windows)
/** 방문자의 OS. 첫 렌더는 'windows'로 시작해 SSR·첫 페인트 차이를 없앤다. */
export function useClientOS(): ClientOSType {
const [os, setOs] = useState<ClientOSType>('windows')
useEffect(() => {
if (typeof window === 'undefined' || !navigator) return
const ua = navigator.userAgent || ''
const platform = (navigator as any).userAgentData?.platform || navigator.platform || ''
if (/android/i.test(ua)) {
setOsInfo(OS_CONFIGS.android)
} else if (/iphone|ipad|ipod/i.test(ua) || (platform === 'MacIntel' && navigator.maxTouchPoints > 1)) {
setOsInfo(OS_CONFIGS.ios)
} else if (/macintosh|mac os x/i.test(ua) || /mac/i.test(platform)) {
setOsInfo(OS_CONFIGS.macos)
} else if (/linux/i.test(ua) || /linux/i.test(platform)) {
setOsInfo(OS_CONFIGS.linux)
} else if (/windows|win32|win64/i.test(ua) || /win/i.test(platform)) {
setOsInfo(OS_CONFIGS.windows)
} else {
setOsInfo(OS_CONFIGS.windows)
}
setOs(detectOS())
}, [])
return osInfo
return os
}

View file

@ -0,0 +1,17 @@
import { useEffect, useState } from 'react'
const QUERY = '(prefers-reduced-motion: reduce)'
/** 사용자의 감소 모션 설정을 따라간다. 설정이 바뀌면 다시 렌더한다. */
export function usePrefersReducedMotion(): boolean {
const [reduced, setReduced] = useState(() => window.matchMedia(QUERY).matches)
useEffect(() => {
const media = window.matchMedia(QUERY)
const onChange = () => setReduced(media.matches)
media.addEventListener('change', onChange)
return () => media.removeEventListener('change', onChange)
}, [])
return reduced
}

View file

@ -14,21 +14,9 @@ import { vi } from './locales/vi'
// ── Types ──────────────────────────────────────────────
export type Locale = 'en' | 'ko' | 'ja' | 'zh' | 'es' | 'fr' | 'de' | 'pt' | 'ru' | 'vi'
export interface FeatureItem {
export interface TitledText {
title: string
description: string
}
export interface PipelineStep {
label: string
title: string
description: string
detail: string
}
export interface PrivacyMetricItem {
label: string
value: string
body: string
}
export interface FaqItem {
@ -36,128 +24,143 @@ export interface FaqItem {
a: string
}
export interface PricingFeatureRow {
feature: string
free: string | boolean
pro: string | boolean
proPlus: string | boolean
export interface PlanCopy {
name: string
desc: string
cta: string
features: string[]
}
/**
* 사이트의 모든 문구. 컴포넌트에 문자열을 직접 쓰지 않는다.
* `{name}` 자리표시자는 `fmt()`로 채운다.
*/
export interface Translations {
meta: {
title: string
description: string
}
a11y: {
skipToContent: string
primaryNav: string
openMenu: string
closeMenu: string
language: string
opensInNewTab: string
}
nav: {
features: string
pipeline: string
how: string
privacy: string
pricing: string
faq: string
download: string
releases?: string
}
hero: {
badge: string
title1: string
title2: string
title3: string
subtitle: string
downloadBtn: string
viewFeatures: string
dataProcessing: string
dataCloudTraffic: string
dataLatency: string
dataPrivacy: string
systemStatus: string
protocol: string
hotkeyKey: string
hotkeyAction: string
secondaryCta: string
trust: string
/** 방문자가 Windows가 아닐 때 다운로드 버튼 아래에 보인다. */
windowsOnly: string
}
demo: {
title: string
note: string
idle: string
listening: string
polishing: string
done: string
play: string
replay: string
rawLabel: string
cleanLabel: string
sampleRaw: string
sampleClean: string
}
features: {
index: string
title: string
subtitle: string
items: [FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem]
items: [TitledText, TitledText, TitledText, TitledText, TitledText, TitledText]
}
pipeline: {
index: string
how: {
title: string
subtitle: string
steps: [PipelineStep, PipelineStep, PipelineStep, PipelineStep]
panelTitle: string
panelActive: string
panelLatency: string
sttReady: string
llmConnected: string
transcription: string
aiPolish: string
sampleInput: string
sampleOutput: string
steps: [TitledText, TitledText, TitledText, TitledText]
}
privacy: {
index: string
title: string
subtitle: string
monitorTitle: string
allClear: string
metrics: [PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem]
guarantees: [string, string, string, string, string, string]
points: [TitledText, TitledText, TitledText, TitledText]
policyLink: string
}
pricing: {
index: string
title: string
subtitle: string
featureLabel: string
free: string
pro: string
proPlus: string
forever: string
monthly: string
annual: string
perMonth: string
popular: string
savePercent: string
downloadFree: string
getPro: string
getProPlus: string
taxNote: string
billingToggleMonthly: string
billingToggleAnnual: string
rows: [PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow]
unlimited: string
note: string
free: PlanCopy
pro: PlanCopy
proPlus: PlanCopy
}
download: {
title: string
subtitle: string
windowsName: string
windowsMeta: string
cta: string
/** {date}: 게시일 (release.ts의 DESKTOP_RELEASE_DATE, 버전 동기화 대상) */
details: string
releaseNotes: string
checksum: string
soonTitle: string
soon: [TitledText, TitledText]
}
faq: {
index: string
title: string
items: [FaqItem, FaqItem, FaqItem, FaqItem, FaqItem, FaqItem]
}
cta: {
title1: string
title2: string
subtitle1: string
subtitle2: string
downloadBtn: string
systemReq: string
}
footer: {
description: string
privacy: string
terms: string
deleteAccount: string
releaseNotes: string
copyright: string
builtWith: string
}
}
/** `{key}` 자리표시자를 값으로 바꾼다. */
export function fmt(template: string, values: Record<string, string | number>): string {
return template.replace(/\{(\w+)\}/g, (match, key: string) =>
key in values ? String(values[key]) : match,
)
}
// ── Locale metadata ────────────────────────────────────
export interface LocaleMeta {
code: Locale
label: string
nativeName: string
/** Intl 포맷용 BCP 47 태그 */
bcp47: string
}
export const LOCALES: LocaleMeta[] = [
{ code: 'en', label: 'EN', nativeName: 'English' },
{ code: 'ko', label: 'KO', nativeName: '한국어' },
{ code: 'ja', label: 'JA', nativeName: '日本語' },
{ code: 'zh', label: 'ZH', nativeName: '中文' },
{ code: 'es', label: 'ES', nativeName: 'Espanol' },
{ code: 'fr', label: 'FR', nativeName: 'Francais' },
{ code: 'de', label: 'DE', nativeName: 'Deutsch' },
{ code: 'pt', label: 'PT', nativeName: 'Portugues' },
{ code: 'ru', label: 'RU', nativeName: 'Русский' },
{ code: 'vi', label: 'VI', nativeName: 'Tieng Viet' },
{ code: 'ko', label: 'KO', nativeName: '한국어', bcp47: 'ko-KR' },
{ code: 'en', label: 'EN', nativeName: 'English', bcp47: 'en-US' },
{ code: 'ja', label: 'JA', nativeName: '日本語', bcp47: 'ja-JP' },
{ code: 'zh', label: 'ZH', nativeName: '简体中文', bcp47: 'zh-CN' },
{ code: 'es', label: 'ES', nativeName: 'Español', bcp47: 'es-ES' },
{ code: 'fr', label: 'FR', nativeName: 'Français', bcp47: 'fr-FR' },
{ code: 'de', label: 'DE', nativeName: 'Deutsch', bcp47: 'de-DE' },
{ code: 'pt', label: 'PT', nativeName: 'Português', bcp47: 'pt-BR' },
{ code: 'ru', label: 'RU', nativeName: 'Русский', bcp47: 'ru-RU' },
{ code: 'vi', label: 'VI', nativeName: 'Tiếng Việt', bcp47: 'vi-VN' },
]
// ── Translations map ───────────────────────────────────
@ -168,31 +171,31 @@ const translations: Record<Locale, Translations> = {
// ── Storage ────────────────────────────────────────────
const STORAGE_KEY = 'd3ro-locale'
function isLocale(value: string): value is Locale {
return value in translations
}
function detectLocale(): Locale {
// 1. Check localStorage
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored && stored in translations) return stored as Locale
} catch { /* noop */ }
if (stored && isLocale(stored)) return stored
} catch { /* 저장소 차단 환경 */ }
// 2. Check browser language
const browserLang = navigator.language.toLowerCase()
const exact = LOCALES.find((l) => browserLang === l.code || browserLang.startsWith(l.code + '-'))
if (exact) return exact.code
// 3. Fallback
return 'en'
const match = LOCALES.find((l) => browserLang === l.code || browserLang.startsWith(l.code + '-'))
return match ? match.code : 'en'
}
function persistLocale(locale: Locale): void {
try {
localStorage.setItem(STORAGE_KEY, locale)
} catch { /* noop */ }
} catch { /* 저장소 차단 환경 */ }
}
// ── Context ────────────────────────────────────────────
interface I18nContextValue {
locale: Locale
bcp47: string
t: Translations
setLocale: (locale: Locale) => void
}
@ -213,21 +216,20 @@ interface I18nProviderProps {
export function I18nProvider({ children }: I18nProviderProps) {
const [locale, setLocaleState] = useState<Locale>(detectLocale)
const setLocale = useCallback((newLocale: Locale) => {
setLocaleState(newLocale)
persistLocale(newLocale)
document.documentElement.lang = newLocale
const setLocale = useCallback((next: Locale) => {
setLocaleState(next)
persistLocale(next)
}, [])
const t = translations[locale]
const bcp47 = LOCALES.find((l) => l.code === locale)?.bcp47 ?? 'en-US'
// 문서 언어와 메타데이터도 선택한 언어를 따른다.
useEffect(() => {
document.documentElement.lang = locale
}, [locale])
document.title = t.meta.title
document.querySelector('meta[name="description"]')?.setAttribute('content', t.meta.description)
}, [locale, t])
const value: I18nContextValue = {
locale,
t: translations[locale],
setLocale,
}
return createElement(I18nContext.Provider, { value }, children)
return createElement(I18nContext.Provider, { value: { locale, bcp47, t, setLocale } }, children)
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const de: Translations = {
meta: {
title: 'D3RO Voice — Spracheingabe, die auf Ihrem PC läuft',
description: 'Halten Sie eine Taste gedrückt, sprechen Sie, und der Text erscheint in der App, die Sie gerade verwenden. Spracherkennung und Textbereinigung laufen beide auf Ihrem eigenen Windows-PC.',
},
a11y: {
skipToContent: 'Zum Inhalt springen',
primaryNav: 'Hauptmenü',
openMenu: 'Menü öffnen',
closeMenu: 'Menü schließen',
language: 'Sprache wählen',
opensInNewTab: 'öffnet in einem neuen Tab',
},
nav: {
features: 'FUNKTIONEN',
pipeline: 'PIPELINE',
privacy: 'DATENSCHUTZ',
pricing: 'PREISE',
features: 'Funktionen',
how: 'Funktionsweise',
privacy: 'Datenschutz',
pricing: 'Preise',
faq: 'FAQ',
download: 'Download',
},
hero: {
badge: '100% LOKAL \u00b7 NULL CLOUD',
title1: 'LOKALER KI',
title2: 'SPRACH',
title3: 'ASSISTENT.',
subtitle: 'Whisper + Ollama betriebene Sprache-zu-Text-Pipeline lauft komplett auf Ihrem Rechner. Diktat, KI-Korrektur, Echtzeit-Untertitel, Sprachgesprache \u2014 kein Internet erforderlich.',
downloadBtn: 'Fur Windows herunterladen',
viewFeatures: 'Funktionen ansehen',
dataProcessing: 'Verarbeitung',
dataCloudTraffic: 'Cloud-Verkehr',
dataLatency: 'STT-Latenz',
dataPrivacy: 'Datenschutz-Score',
systemStatus: 'SYSTEMSTATUS: BETRIEBSBEREIT',
protocol: 'PROTOKOLL: 2025.04',
title1: 'Sprechen Sie, und es erscheint',
title2: 'dort, wo Ihr Cursor steht.',
subtitle: 'Halten Sie die Tastenkombination gedrückt, sprechen Sie, lassen Sie los. Spracherkennung und Bereinigung laufen auf Ihrem PC, und das Ergebnis wird direkt in die App eingefügt, die Sie gerade verwenden.',
hotkeyKey: 'rechte Alt-Taste',
hotkeyAction: 'gedrückt halten zum Sprechen',
secondaryCta: 'Funktionsweise ansehen',
trust: 'Kostenlos starten · Kein Konto nötig · Funktioniert offline, sobald die Modelle heruntergeladen sind',
windowsOnly: 'Derzeit ist nur eine Windows-Version verfügbar. Öffnen Sie diese Seite auf einem Windows-PC, um sie herunterzuladen.',
},
demo: {
title: 'Beispiel',
note: 'Dies ist ein vorbereitetes Beispiel. Es wird nichts aufgenommen.',
idle: 'Drücken Sie auf Wiedergabe, um zu sehen, wie Sprache zu Text wird und bereinigt wird.',
listening: 'Hört zu',
polishing: 'Wird bereinigt',
done: 'Eingefügt',
play: 'Beispiel abspielen',
replay: 'Erneut abspielen',
rawLabel: 'Was gehört wurde',
cleanLabel: 'Was eingefügt wurde',
sampleRaw: 'ähm also ich denke wir können den Release quasi auf nächsten Freitag legen vielleicht',
sampleClean: 'Ich denke, wir können den Release für nächsten Freitag ansetzen.',
},
features: {
index: '01 / FUNKTIONEN',
title: 'Vollstandige Sprachintelligenz.',
subtitle: 'Vom Diktat bis zum KI-Gesprach. Alles lauft offline auf Ihrer Hardware.',
title: 'Von kurzen Notizen bis zum Besprechungsprotokoll, alles auf Ihrem PC.',
subtitle: 'Alles unten ist kostenlos und läuft mit den Standardeinstellungen auf Ihrem eigenen Rechner.',
items: [
{ title: 'Sprachdiktat', description: 'Hotkey halten, sprechen, loslassen. Whisper transkribiert und fugt Text sofort in Ihre aktive App ein.' },
{ title: 'KI-Textkorrektur', description: 'Ollama LLM verfeinert Ihren transkribierten Text zu sauberer, grammatisch korrekter Prosa. Formell oder leger.' },
{ title: 'Sofortige Ubersetzung', description: 'Sprechen Sie in einer Sprache, erhalten Sie Text in einer anderen. Automatische Quellerkennung, Ubersetzung auf dem Gerat.' },
{ title: 'Live-Untertitel', description: 'Echtzeit-Untertitel-Overlay auf Ihrem Bildschirm. Meetings, Vorlesungen, Videos. Automatischer Export von Besprechungsnotizen.' },
{ title: 'Sprachgesprach', description: 'Lokaler ChatGPT-Sprachmodus. Vollstandige STT-LLM-TTS-Schleife fur naturliche KI-Gesprache, komplett offline.' },
{ title: 'Datei-Transkription', description: 'Audio- oder Videodateien per Drag & Drop. Whisper transkribiert den gesamten Inhalt mit Zeitstempeln.' },
{ title: 'Multi-LLM-Kette', description: 'Verketten Sie mehrere KI-Befehle: Transkribieren, Ubersetzen, dann Zusammenfassen mit einem Tastendruck.' },
{ title: 'Bildschirmkontext', description: 'Erfasst automatisch aktive App und markierten Text. Sagen Sie "erklare diesen Code" und die KI sieht, was Sie sehen.' },
{ title: 'Sprachnotiz', description: 'Automatisch organisierte Sprachnotizen mit #Tags. Als Markdown exportieren. Suchen, filtern, kategorisieren.' },
{ title: 'Überall per Spracheingabe tippen', body: 'Funktioniert überall dort, wo ein Cursor ist: Notizen, Browser, Code-Editoren, Chat-Apps. Drücken Sie die Tastenkombination zweimal, um freihändig weiterzusprechen.' },
{ title: 'Textbereinigung', body: 'Entfernt Füllwörter und ordnet Sätze mit einem lokalen Ollama-Modell. Sie können auch eigene Befehle erstellen, etwa zum Übersetzen oder Zusammenfassen.' },
{ title: 'Vorschläge für den nächsten Satz', body: 'Während Sie schreiben, bietet ein lokales Modell Möglichkeiten zum Weiterschreiben an. Wählen Sie mit Ctrl+Alt+Auf/Ab und fügen Sie mit Ctrl+Alt+Enter ein.' },
{ title: 'Live-Untertitel', body: 'Zeigt Untertitel über Ihrem Bildschirm für Meetings, Vorlesungen und Videos. Fertige Zeilen werden anhand der umliegenden Zeilen noch einmal korrigiert.' },
{ title: 'Besprechungsnotizen', body: 'Zeichnet lange Besprechungen auf und erstellt nach deren Ende eine Zusammenfassung mit Aufgaben.' },
{ title: 'Dateitranskription', body: 'Legen Sie eine Audio- oder Videodatei ab und erhalten Sie eine Abschrift mit Zeitstempeln.' },
],
},
pipeline: {
index: '02 / PIPELINE',
title: 'Vier Schritte. Zwei Sekunden.',
subtitle: 'Ein Tastendruck und Ihre Sprache wird zu poliertem Text.',
how: {
title: 'Halten, sprechen, loslassen. Fertig.',
subtitle: 'Kein Fensterwechsel nötig. Alles passiert dort, wo Sie ohnehin schon getippt haben.',
steps: [
{ label: 'EINGABE', title: 'Hotkey drucken', description: 'Right Alt (oder Ihre benutzerdefinierte Taste) gedruckt halten zum Aufnehmen. Doppeltippen fur KI-Modus. Umschalten fur Freisprechen.', detail: 'Halten / Umschalten / Doppeldruck' },
{ label: 'TRANSKRIBIEREN', title: 'Whisper STT', description: 'Lokale faster-whisper Engine konvertiert 16kHz PCM-Audio in Echtzeit zu Text. GPU-Beschleunigung unterstutzt.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'VERARBEITEN', title: 'Ollama LLM', description: 'Lokales LLM korrigiert Grammatik, passt den Ton an, ubersetzt oder fasst zusammen. Benutzerdefinierte Anweisungen unterstutzt.', detail: 'gemma4 / llama3.2 / phi4 / benutzerdefiniert' },
{ label: 'AUSGABE', title: 'Auto-Einfugen', description: 'Korrigierter Text wird an der Cursorposition in jeder App eingefugt. Notepad, VS Code, Chrome, Slack, uberall.', detail: 'Zwischenablage + Ctrl+V / 100ms Latenz' },
{ title: 'Halten', body: 'Die Aufnahme läuft, solange Sie die rechte Alt-Taste gedrückt halten. Sie können die Tastenkombination in den Einstellungen ändern.' },
{ title: 'Erkennen', body: 'Die eingebaute faster-whisper-Engine wandelt Sprache in Text um. Eine NVIDIA-GPU macht es schneller.' },
{ title: 'Bereinigen', body: 'Falls aktiviert, ordnet oder übersetzt ein lokales Modell den Text. Schalten Sie es aus, um genau das zu behalten, was gehört wurde.' },
{ title: 'Eingeben', body: 'Der Text wird an Ihrer Cursorposition eingefügt. Der Inhalt Ihrer Zwischenablage wird danach wiederhergestellt.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Aktiv',
panelLatency: 'LATENZ: 1.2s',
sttReady: 'STT Bereit',
llmConnected: 'LLM Verbunden',
transcription: 'Transkription',
aiPolish: 'KI-Korrektur',
sampleInput: 'Bitte fassen Sie die heutigen Besprechungsnotizen zusammen...',
sampleOutput: 'Bitte fassen Sie die heutigen Besprechungsnotizen zusammen.',
},
privacy: {
index: '03 / DATENSCHUTZ',
title: 'Ihre Stimme verlasst nie den Rechner.',
subtitle: 'Jedes Byte Audio, jede Transkription, jede KI-Interaktion bleibt auf Ihrem Gerat.',
monitorTitle: 'Datenschutz-Monitor',
allClear: 'ALLES OK',
metrics: [
{ label: 'CLOUD-VERKEHR', value: '0 BYTES' },
{ label: 'DATENVERSCHLUSSELUNG', value: 'LOKALES SQLite' },
{ label: 'TELEMETRIE', value: 'DEAKTIVIERT' },
{ label: 'NETZWERK ERFORDERLICH', value: 'NEIN' },
{ label: 'AUDIOSPEICHER', value: 'NUR LOKAL' },
{ label: 'OPEN SOURCE', value: 'JA' },
],
guarantees: [
'Kein Internet nach dem initialen Modell-Download erforderlich',
'Sprachaufnahmen nur in lokalem SQLite gespeichert',
'Alle Daten jederzeit mit einem Klick loschen',
'Open Source - jede Zeile Code einsehbar',
'Keine Konten, keine Anmeldungen, kein Tracking',
'Whisper und Ollama laufen beide auf Ihrer Hardware',
title: 'Standardmäßig bleibt alles auf Ihrem PC.',
subtitle: 'Es gibt Cloud-Funktionen, aber sie werden erst genutzt, wenn Sie sie aktivieren.',
points: [
{ title: 'Sprache wird auf Ihrem PC verarbeitet', body: 'Die Standard-Spracherkennung nutzt eine lokale Engine, und Aufnahmen sowie Verlauf werden in einer Datenbank auf Ihrem PC gespeichert.' },
{ title: 'Kein Konto erforderlich', body: 'Jede lokale Funktion funktioniert ohne Anmeldung.' },
{ title: 'Nutzungsanalyse ist deaktiviert', body: 'Die Analyse des Tippverhaltens wird nur mit Ihrer Zustimmung aktiviert, und selbst dann bleiben die Ergebnisse auf Ihrem PC.' },
{ title: 'Die Cloud ist optional', body: 'Wenn Sie die Cloud-Spracherkennung (mit eigenem API-Schlüssel) oder die Cloud-Textbereinigung (Anmeldung erforderlich) aktivieren, werden nur diese Anfragen an einen externen Server gesendet.' },
],
policyLink: 'Vollständige Datenschutzerklärung lesen',
},
pricing: {
index: '04 / PREISE',
title: 'Alle Funktionen freischalten.',
subtitle: 'Kostenlos starten, upgraden wenn notig. Jederzeit kundbar.',
featureLabel: 'Funktion',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'fur immer gratis',
monthly: '/Monat',
annual: '/Jahr',
title: 'Jede lokale Funktion ist kostenlos.',
subtitle: 'Kostenpflichtige Pläne ermöglichen eine häufigere Nutzung der Cloud-Textbereinigung mit größeren Modellen.',
perMonth: '/Monat',
savePercent: '17% sparen',
billingToggleMonthly: 'Monatlich',
billingToggleAnnual: 'Jahrlich',
popular: 'Beliebt',
downloadFree: 'Gratis herunterladen',
getPro: 'Pro abonnieren',
getProPlus: 'Pro+ abonnieren',
taxNote: 'Alle Preise zzgl. MwSt. Sichere Zahlung uber Payple.',
rows: [
{ feature: 'Sprachdiktat', free: '15/Tag', pro: true, proPlus: true },
{ feature: 'KI-Textkorrektur', free: '3/Tag', pro: true, proPlus: true },
{ feature: 'Verlaufsaufbewahrung', free: '3 Tage', pro: true, proPlus: true },
{ feature: 'Benutzerdefinierte Anweisungen', free: 'Voreinstellungen', pro: true, proPlus: true },
{ feature: 'Live-Untertitel', free: false, pro: true, proPlus: true },
{ feature: 'Bildschirmkontext', free: false, pro: true, proPlus: true },
{ feature: 'Sprachnotiz + Tags', free: false, pro: true, proPlus: true },
{ feature: 'Multi-LLM-Kette', free: false, pro: true, proPlus: true },
{ feature: 'Sprachbefehle', free: false, pro: true, proPlus: true },
{ feature: 'Verlauf exportieren', free: false, pro: true, proPlus: true },
{ feature: 'Datei-Transkription', free: false, pro: false, proPlus: true },
{ feature: 'Sprachgesprach', free: false, pro: false, proPlus: true },
{ feature: 'Besprechungszusammenfassung', free: false, pro: false, proPlus: true },
{ feature: 'Lokales RAG', free: false, pro: false, proPlus: true },
{ feature: 'OS-Automatisierung', free: false, pro: false, proPlus: true },
unlimited: 'unbegrenzt',
note: 'Die Preise sind monatlich, in koreanischen Won. Zahlung und Kündigung verwalten Sie auf Ihrer Web-Kontoseite.',
free: {
name: 'Free',
desc: 'Alles, was auf Ihrem PC läuft',
cta: 'Kostenlos herunterladen',
features: [
'Unbegrenztes Diktieren, Untertitel, Besprechungsnotizen und Dateitranskription',
'Unbegrenzte Bereinigung mit lokalen Modellen',
'Cloud-Bereinigung {haiku} Mal pro Woche (Anmeldung erforderlich)',
],
},
pro: {
name: 'Pro',
desc: 'Wenn Sie die Cloud-Bereinigung täglich nutzen',
cta: 'Pro abonnieren',
features: [
'Alles aus Free',
'Cloud-Bereinigung pro Tag: Standardmodell {haiku} Mal',
'Erweitertes Modell {sonnet} Mal · Top-Modell {opus} Mal',
],
},
proPlus: {
name: 'Pro+',
desc: 'Reichlich Spielraum für große Modelle',
cta: 'Pro+ abonnieren',
features: [
'Alles aus Pro',
'Cloud-Bereinigung pro Tag: Standardmodell {haiku}',
'Erweitertes Modell {sonnet} Mal · Top-Modell {opus} Mal',
],
},
},
download: {
title: 'Download',
subtitle: 'Nach der Installation werden neue Versionen automatisch installiert.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64-Bit)',
cta: 'Für Windows herunterladen',
details: 'Veröffentlicht am {date} · Das Sprachmodell wird beim ersten Start der App heruntergeladen.',
releaseNotes: 'Versionshinweise',
checksum: 'Datei-Prüfsummen (latest.yml)',
soonTitle: 'Demnächst',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Wird hier veröffentlicht, sobald Signierung und Installationsprüfungen abgeschlossen sind.' },
{ title: 'Android · iOS', body: 'Wird hier veröffentlicht, sobald die Store-Prüfung abgeschlossen ist.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Haufige Fragen.',
title: 'Häufig gestellte Fragen',
items: [
{ q: 'Muss ich Ollama und Whisper separat installieren?', a: 'Whisper (faster-whisper) ist in der App enthalten - keine separate Installation notig. Ollama wird nur fur KI-Korrektur/Ubersetzung benotigt und kann einfach uber die App installiert werden. Basisdiktat funktioniert ohne Ollama.' },
{ q: 'Welche GPU brauche ich?', a: 'Keine GPU erforderlich - funktioniert nur mit CPU. Eine NVIDIA GPU (CUDA) beschleunigt die Transkription 5-10x. Mit dem base-Modell ist Echtzeit-Transkription auf CPU moglich.' },
{ q: 'Funktioniert es komplett offline?', a: 'Ja. Sobald Sie die Whisper- und Ollama-Modelle heruntergeladen haben, funktioniert alles ohne Internet. Die Lizenzverifizierung ist nur bei der Erstaktivierung online, danach 30 Tage Offline-Karenzzeit.' },
{ q: 'Wie genau ist die Spracherkennung?', a: 'Whisper large-v3 bietet hervorragende Genauigkeit fur uber 99 Sprachen. Die benutzerdefinierte Worterbuchfunktion verbessert die Genauigkeit fur Fachterminologie zusatzlich.' },
{ q: 'Ist das ein Abonnement?', a: 'Pro und Pro+ sind monatliche oder jahrliche Abonnements. Sparen Sie etwa 17% bei jahrlicher Abrechnung. Jederzeit kundbar. Lokale Verarbeitung bedeutet keine Cloud-Kosten, aber Abonnements finanzieren kontinuierliche Updates und Premium-Funktionen.' },
{ q: 'Was ist mit macOS und Linux?', a: 'Derzeit nur Windows. Auf Electron aufgebaut, ist macOS/Linux-Unterstutzung technisch machbar und je nach Nachfrage geplant.' },
{ q: 'Muss ich Ollama separat installieren?', a: 'Für das Diktieren nicht: Die Spracherkennungs-Engine ist in die App integriert. Um Textbereinigung und Übersetzung auf Ihrem PC auszuführen, benötigen Sie Ollama, und die App führt Sie durch dessen Installation.' },
{ q: 'Brauche ich eine GPU?', a: 'Nein, es läuft auf der CPU. Eine NVIDIA-GPU (CUDA) macht die Erkennung schneller.' },
{ q: 'Funktioniert es ohne Internet?', a: 'Ja. Sobald das Sprachmodell und ein Ollama-Modell heruntergeladen sind, funktionieren Diktieren und Bereinigung offline. Kostenpflichtige Pläne gehen gelegentlich online, um das Abonnement zu bestätigen, und funktionieren bis zu 30 Tage ohne Verbindung weiter.' },
{ q: 'In welchen Apps funktioniert es?', a: 'In den meisten Windows-Apps, in denen Sie tippen können. Der Text wird durch Einfügen eingefügt, daher akzeptieren Felder, die das Einfügen blockieren, ihn möglicherweise nicht.' },
{ q: 'Wie viel kann ich kostenlos nutzen?', a: 'Jede Funktion, die auf Ihrem PC läuft, ist kostenlos und ohne Nutzungslimits. Kostenpflichtige Pläne erhöhen, wie viel Cloud-Textbereinigung Sie nutzen können.' },
{ q: 'Gibt es eine macOS- oder Mobilversion?', a: 'Derzeit ist nur Windows verfügbar. Die macOS-App (Apple Silicon) und die mobilen Apps werden hier veröffentlicht, sobald sie ihre Prüfungen bestanden haben.' },
],
},
cta: {
title1: 'Sprechen.',
title2: 'KI schreibt.',
subtitle1: 'Keine Cloud. Premium-Funktionen, Ihre Bedingungen. Keine Datenschutzbedenken.',
subtitle2: 'Jetzt starten.',
downloadBtn: 'Fur Windows herunterladen',
systemReq: 'Windows 10/11 \u00b7 64-Bit \u00b7 ~200MB \u00b7 In Sekunden bereit',
},
footer: {
description: 'Vollstandig lokaler KI-Sprachassistent. Angetrieben von Whisper und Ollama. Ihre Stimme, Ihr Rechner, Ihre Daten.',
copyright: '\u00a9 {year} D3RO Voice. Alle Rechte vorbehalten.',
builtWith: 'Gebaut mit Electron + React + TypeScript',
description: 'Spracheingabe, die auf Ihrem PC läuft.',
privacy: 'Datenschutzerklärung',
terms: 'Nutzungsbedingungen',
deleteAccount: 'Konto löschen',
releaseNotes: 'Versionshinweise',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const en: Translations = {
meta: {
title: 'D3RO Voice — Voice typing that runs on your PC',
description: 'Hold a key, speak, and the text lands in whatever app you are using. Speech recognition and text cleanup both run on your own Windows PC.',
},
a11y: {
skipToContent: 'Skip to content',
primaryNav: 'Main menu',
openMenu: 'Open menu',
closeMenu: 'Close menu',
language: 'Choose language',
opensInNewTab: 'opens in a new tab',
},
nav: {
features: 'FEATURES',
pipeline: 'PIPELINE',
privacy: 'PRIVACY',
pricing: 'PRICING',
features: 'Features',
how: 'How it works',
privacy: 'Privacy',
pricing: 'Pricing',
faq: 'FAQ',
download: 'Download',
},
hero: {
badge: '100% ON-DEVICE · ZERO CLOUD',
title1: 'Speak at the Speed of Thought.',
title2: 'AI Polishes the Rest.',
title3: 'D3RO Voice.',
subtitle: '3x faster than typing. Whisper + Ollama voice-to-text pipeline running 100% on your machine. Zero cloud egress, unlimited dictation, and real-time meeting notes.',
downloadBtn: 'Download for Windows — Free',
viewFeatures: 'Explore Features',
dataProcessing: 'Processing',
dataCloudTraffic: 'Cloud Egress',
dataLatency: 'STT Latency',
dataPrivacy: 'Privacy Rating',
systemStatus: 'SYSTEM STATUS: OPERATIONAL',
protocol: 'PROTOCOL: 2025.04',
title1: 'Speak, and it appears',
title2: 'where your cursor is.',
subtitle: 'Hold the shortcut, talk, let go. Speech recognition and cleanup run on your PC, and the result is typed straight into the app you are already using.',
hotkeyKey: 'Right Alt',
hotkeyAction: 'hold to talk',
secondaryCta: 'See how it works',
trust: 'Free to start · No account needed · Works offline once models are downloaded',
windowsOnly: 'Only a Windows version is available right now. Open this page on a Windows PC to download it.',
},
demo: {
title: 'Example',
note: 'This is a scripted example. Nothing is recorded.',
idle: 'Press play to see speech turn into text and get cleaned up.',
listening: 'Listening',
polishing: 'Cleaning up',
done: 'Typed',
play: 'Play example',
replay: 'Play again',
rawLabel: 'What was heard',
cleanLabel: 'What was typed',
sampleRaw: 'uh so I think we can, like, put the release at next Friday maybe',
sampleClean: 'I think we can schedule the release for next Friday.',
},
features: {
index: '01 / FEATURES',
title: 'Full Voice Intelligence.',
subtitle: 'From dictation to AI conversation. Everything runs on your hardware, offline.',
title: 'From quick notes to meeting minutes, all on your PC.',
subtitle: 'Everything below is free and, with the default settings, runs on your own machine.',
items: [
{ title: 'Voice Dictation', description: 'Hold a hotkey, speak, release. Whisper transcribes and inserts text into your active app instantly.' },
{ title: 'AI Text Polish', description: 'Ollama LLM refines your transcribed text into clean, grammatically correct prose. Formal or casual.' },
{ title: 'Instant Translation', description: 'Speak in one language, get text in another. Auto-detect source, translate on-device.' },
{ title: 'Live Captions', description: 'Real-time subtitle overlay on your screen. Meetings, lectures, videos. Auto meeting notes export.' },
{ title: 'Voice Conversation', description: 'Local ChatGPT voice mode. Full STT-LLM-TTS loop for natural AI conversations, completely offline.' },
{ title: 'File Transcription', description: 'Drag and drop audio or video files. Whisper transcribes the entire content with timestamps.' },
{ title: 'Multi-LLM Chain', description: 'Pipeline multiple AI commands: transcribe, translate, then summarize with a single hotkey press.' },
{ title: 'Screen Context', description: 'Auto-captures active app and selected text. Ask "explain this code" and the AI sees what you see.' },
{ title: 'Voice Memo', description: 'Auto-organized voice notes with #tags. Export as markdown. Search, filter, categorize.' },
{ title: 'Type by voice anywhere', body: 'Works wherever there is a cursor: notes, browsers, code editors, chat apps. Press the shortcut twice to keep talking hands-free.' },
{ title: 'Text cleanup', body: 'Removes filler words and tidies up sentences with a local Ollama model. You can also create your own commands, such as translate or summarize.' },
{ title: 'Next-sentence suggestions', body: 'While you write, a local model offers ways to continue. Pick one with Ctrl+Alt+Up/Down and insert it with Ctrl+Alt+Enter.' },
{ title: 'Live captions', body: 'Shows captions on top of your screen for meetings, lectures, and videos. Finished lines are corrected once more using the lines around them.' },
{ title: 'Meeting notes', body: 'Records long meetings and, when they end, writes up a summary and action items.' },
{ title: 'File transcription', body: 'Drop in an audio or video file and get a transcript with timestamps.' },
],
},
pipeline: {
index: '02 / PIPELINE',
title: 'Four Steps. Two Seconds.',
subtitle: 'One hotkey press and your speech becomes polished text.',
how: {
title: 'Hold, speak, release. Done.',
subtitle: 'No switching windows. It all happens where you were already typing.',
steps: [
{ label: 'INPUT', title: 'Press Hotkey', description: 'Hold Right Alt (or your custom key) to start recording. Double-tap for AI mode. Toggle for hands-free.', detail: 'Hold-to-talk / Toggle / Double-press' },
{ label: 'TRANSCRIBE', title: 'Whisper STT', description: 'Local faster-whisper engine converts 16kHz PCM audio to text in real-time. GPU acceleration supported.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'PROCESS', title: 'Ollama LLM', description: 'Local LLM polishes grammar, adjusts tone, translates, or summarizes. Custom instructions supported.', detail: 'gemma4 / llama3.2 / phi4 / custom' },
{ label: 'OUTPUT', title: 'Auto Insert', description: 'Polished text is pasted at your cursor position in any app. Notepad, VS Code, Chrome, Slack, anywhere.', detail: 'Clipboard + Ctrl+V / 100ms latency' },
{ title: 'Hold', body: 'Recording runs while you hold Right Alt. You can change the shortcut in Settings.' },
{ title: 'Recognize', body: 'The built-in faster-whisper engine turns speech into text. An NVIDIA GPU makes it faster.' },
{ title: 'Clean up', body: 'If enabled, a local model tidies or translates the text. Turn it off to keep exactly what was heard.' },
{ title: 'Type', body: 'The text is pasted at your cursor. Whatever was on your clipboard is put back afterwards.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Active',
panelLatency: 'LATENCY: 1.2s',
sttReady: 'STT Ready',
llmConnected: 'LLM Connected',
transcription: 'Transcription',
aiPolish: 'AI Polish',
sampleInput: 'Please summarize the meeting notes from today...',
sampleOutput: 'Please summarize today\u2019s meeting notes.',
},
privacy: {
index: '03 / PRIVACY',
title: 'Your Voice Never Leaves.',
subtitle: 'Every byte of audio, every transcription, every AI interaction stays on your machine.',
monitorTitle: 'Privacy Monitor',
allClear: 'ALL CLEAR',
metrics: [
{ label: 'CLOUD TRAFFIC', value: '0 BYTES' },
{ label: 'DATA ENCRYPTION', value: 'LOCAL SQLite' },
{ label: 'TELEMETRY', value: 'DISABLED' },
{ label: 'NETWORK REQUIRED', value: 'NO' },
{ label: 'AUDIO STORAGE', value: 'LOCAL ONLY' },
{ label: 'OPEN SOURCE', value: 'YES' },
],
guarantees: [
'Zero internet required after initial model download',
'Voice recordings stored in local SQLite only',
'Delete all data anytime with one click',
'Open source - inspect every line of code',
'No accounts, no sign-ups, no tracking',
'Whisper and Ollama both run on your hardware',
title: 'By default, it stays on your PC.',
subtitle: 'There are cloud features, but they are not used until you turn them on.',
points: [
{ title: 'Speech is processed on your PC', body: 'The default speech recognition uses a local engine, and recordings and history are stored in a database on your PC.' },
{ title: 'No account required', body: 'Every local feature works without signing in.' },
{ title: 'Usage analysis is off', body: 'Typing-pattern analysis only turns on with your consent, and even then the results stay on your PC.' },
{ title: 'The cloud is opt-in', body: 'If you turn on cloud speech recognition (with your own API key) or cloud text cleanup (sign-in required), only those requests are sent to an outside server.' },
],
policyLink: 'Read the full privacy policy',
},
pricing: {
index: '04 / PRICING',
title: 'Unlock the Full Power.',
subtitle: 'Start free, upgrade when you need more. Cancel anytime.',
featureLabel: 'Feature',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'forever',
monthly: '/mo',
annual: '/yr',
title: 'Every local feature is free.',
subtitle: 'Paid plans let you use cloud text cleanup more often and with larger models.',
perMonth: '/mo',
savePercent: 'Save 17%',
billingToggleMonthly: 'Monthly',
billingToggleAnnual: 'Annual',
popular: 'Popular',
downloadFree: 'Download Free',
getPro: 'Subscribe to Pro',
getProPlus: 'Subscribe to Pro+',
taxNote: 'All prices exclude tax. Secure payment via Payple.',
rows: [
{ feature: 'Voice Dictation', free: '15/day', pro: true, proPlus: true },
{ feature: 'AI Text Polish', free: '3/day', pro: true, proPlus: true },
{ feature: 'History Retention', free: '3 days', pro: true, proPlus: true },
{ feature: 'Custom Instructions', free: 'Presets', pro: true, proPlus: true },
{ feature: 'Live Captions', free: false, pro: true, proPlus: true },
{ feature: 'Screen Context', free: false, pro: true, proPlus: true },
{ feature: 'Voice Memo + Tags', free: false, pro: true, proPlus: true },
{ feature: 'Multi-LLM Chain', free: false, pro: true, proPlus: true },
{ feature: 'Voice Commands', free: false, pro: true, proPlus: true },
{ feature: 'History Export', free: false, pro: true, proPlus: true },
{ feature: 'File Transcription', free: false, pro: false, proPlus: true },
{ feature: 'Voice Conversation', free: false, pro: false, proPlus: true },
{ feature: 'Meeting Summary', free: false, pro: false, proPlus: true },
{ feature: 'Local RAG', free: false, pro: false, proPlus: true },
{ feature: 'OS Automation', free: false, pro: false, proPlus: true },
unlimited: 'unlimited',
note: 'Prices are monthly, in Korean won. Payment and cancellation are managed on your web account page.',
free: {
name: 'Free',
desc: 'Everything that runs on your PC',
cta: 'Download free',
features: [
'Unlimited dictation, captions, meeting notes, and file transcription',
'Unlimited cleanup with local models',
'Cloud cleanup {haiku} times a week (sign-in required)',
],
},
pro: {
name: 'Pro',
desc: 'If you use cloud cleanup every day',
cta: 'Subscribe to Pro',
features: [
'Everything in Free',
'Cloud cleanup per day: standard model {haiku} times',
'Advanced model {sonnet} times · top model {opus} times',
],
},
proPlus: {
name: 'Pro+',
desc: 'Plenty of room for large models',
cta: 'Subscribe to Pro+',
features: [
'Everything in Pro',
'Cloud cleanup per day: standard model {haiku}',
'Advanced model {sonnet} times · top model {opus} times',
],
},
},
download: {
title: 'Download',
subtitle: 'Once installed, new versions are installed automatically.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64-bit)',
cta: 'Download for Windows',
details: 'Published {date} · The speech model is downloaded the first time you run the app.',
releaseNotes: 'Release notes',
checksum: 'File hashes (latest.yml)',
soonTitle: 'Coming later',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Will be posted here once signing and installation checks are complete.' },
{ title: 'Android · iOS', body: 'Will be posted here once store review is complete.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Common Questions.',
title: 'Frequently asked questions',
items: [
{ q: 'Do I need to install Ollama and Whisper separately?', a: 'Whisper (faster-whisper) is bundled with the app - no separate install needed. Ollama is required only for AI polish/translation features and can be easily installed via in-app guidance. Basic dictation works without Ollama.' },
{ q: 'What GPU do I need?', a: 'No GPU required - it works on CPU alone. An NVIDIA GPU (CUDA) accelerates transcription 5-10x. With the base model, CPU real-time transcription is possible.' },
{ q: 'Does it work completely offline?', a: 'Yes. Once you download the Whisper and Ollama models, everything works without internet. License verification is online only for initial activation, then 30-day offline grace period.' },
{ q: 'How accurate is the speech recognition?', a: 'Whisper large-v3 provides excellent accuracy for 99+ languages. The custom dictionary feature further improves accuracy for domain-specific terminology.' },
{ q: 'Is this a subscription?', a: 'Pro and Pro+ are monthly or annual subscriptions. Save about 17% with annual billing. Cancel anytime. Local processing means no cloud costs, but subscriptions fund continuous updates and premium features.' },
{ q: 'What about macOS and Linux?', a: 'Currently Windows only. Built on Electron so macOS/Linux support is technically feasible and planned based on demand.' },
{ q: 'Do I need to install Ollama separately?', a: 'Not for dictation: the speech engine is built into the app. To run text cleanup and translation on your PC you need Ollama, and the app walks you through installing it.' },
{ q: 'Do I need a GPU?', a: 'No, it runs on the CPU. An NVIDIA GPU (CUDA) makes recognition faster.' },
{ q: 'Does it work without internet?', a: 'Yes. Once the speech model and an Ollama model are downloaded, dictation and cleanup work offline. Paid plans occasionally go online to confirm the subscription, and keep working for up to 30 days without a connection.' },
{ q: 'Which apps does it work in?', a: 'Most Windows apps where you can type. Text is inserted by pasting, so fields that block pasting may not accept it.' },
{ q: 'How much can I do for free?', a: 'Every feature that runs on your PC is free with no usage limits. Paid plans raise how much cloud text cleanup you can use.' },
{ q: 'Is there a macOS or mobile version?', a: 'Only Windows is available right now. The macOS (Apple Silicon) and mobile apps will be posted here once they pass their checks.' },
],
},
cta: {
title1: 'Speak.',
title2: 'AI Writes.',
subtitle1: 'No cloud. Premium features, your terms. No privacy concerns.',
subtitle2: 'Start now.',
downloadBtn: 'Download for Windows',
systemReq: 'Windows 10/11 \u00b7 64-bit \u00b7 ~200MB \u00b7 Ready in seconds',
},
footer: {
description: 'Fully local AI voice assistant. Powered by Whisper and Ollama. Your voice, your machine, your data.',
copyright: '\u00a9 {year} D3RO Voice. All rights reserved.',
builtWith: 'Built with Electron + React + TypeScript',
description: 'Voice typing that runs on your PC.',
privacy: 'Privacy policy',
terms: 'Terms of service',
deleteAccount: 'Delete account',
releaseNotes: 'Release notes',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const es: Translations = {
meta: {
title: 'D3RO Voice — Dictado por voz que funciona en tu PC',
description: 'Mantén presionada una tecla, habla, y el texto aparece en la aplicación que estés usando. El reconocimiento de voz y la limpieza de texto se ejecutan en tu propia PC con Windows.',
},
a11y: {
skipToContent: 'Saltar al contenido',
primaryNav: 'Menú principal',
openMenu: 'Abrir menú',
closeMenu: 'Cerrar menú',
language: 'Elegir idioma',
opensInNewTab: 'se abre en una pestaña nueva',
},
nav: {
features: 'FUNCIONES',
pipeline: 'PROCESO',
privacy: 'PRIVACIDAD',
pricing: 'PRECIOS',
faq: 'FAQ',
features: 'Funciones',
how: 'Cómo funciona',
privacy: 'Privacidad',
pricing: 'Precios',
faq: 'Preguntas frecuentes',
download: 'Descargar',
},
hero: {
badge: '100% LOCAL \u00b7 CERO NUBE',
title1: 'IA LOCAL',
title2: 'ASISTENTE',
title3: 'DE VOZ.',
subtitle: 'Pipeline de voz a texto con Whisper + Ollama ejecutandose completamente en tu equipo. Dictado, pulido con IA, subtitulos en tiempo real, conversaciones por voz \u2014 sin internet.',
downloadBtn: 'Descargar para Windows',
viewFeatures: 'Ver funciones',
dataProcessing: 'Procesamiento',
dataCloudTraffic: 'Trafico en la nube',
dataLatency: 'Latencia STT',
dataPrivacy: 'Puntuacion de privacidad',
systemStatus: 'ESTADO DEL SISTEMA: OPERATIVO',
protocol: 'PROTOCOLO: 2025.04',
title1: 'Habla, y aparece',
title2: 'donde está tu cursor.',
subtitle: 'Mantén presionado el atajo, habla, suelta. El reconocimiento de voz y la limpieza se ejecutan en tu PC, y el resultado se escribe directamente en la aplicación que ya estás usando.',
hotkeyKey: 'Alt derecho',
hotkeyAction: 'mantén presionado para hablar',
secondaryCta: 'Ver cómo funciona',
trust: 'Gratis para empezar · Sin necesidad de cuenta · Funciona sin conexión una vez descargados los modelos',
windowsOnly: 'Por ahora solo hay una versión para Windows. Abre esta página en una PC con Windows para descargarla.',
},
demo: {
title: 'Ejemplo',
note: 'Este es un ejemplo guionado. No se graba nada.',
idle: 'Presiona reproducir para ver cómo el habla se convierte en texto y se limpia.',
listening: 'Escuchando',
polishing: 'Limpiando',
done: 'Escrito',
play: 'Reproducir ejemplo',
replay: 'Reproducir de nuevo',
rawLabel: 'Lo que se escuchó',
cleanLabel: 'Lo que se escribió',
sampleRaw: 'eh o sea creo que podemos, como, poner el lanzamiento el próximo viernes tal vez',
sampleClean: 'Creo que podemos programar el lanzamiento para el próximo viernes.',
},
features: {
index: '01 / FUNCIONES',
title: 'Inteligencia de voz completa.',
subtitle: 'Desde dictado hasta conversacion con IA. Todo se ejecuta en tu hardware, sin conexion.',
title: 'De notas rápidas a actas de reunión, todo en tu PC.',
subtitle: 'Todo lo siguiente es gratis y, con la configuración predeterminada, se ejecuta en tu propia máquina.',
items: [
{ title: 'Dictado por voz', description: 'Manten una tecla, habla, suelta. Whisper transcribe e inserta texto en tu app activa al instante.' },
{ title: 'Pulido con IA', description: 'Ollama LLM refina tu texto transcrito en prosa limpia y gramaticalmente correcta. Formal o casual.' },
{ title: 'Traduccion instantanea', description: 'Habla en un idioma, obtene texto en otro. Deteccion automatica del idioma, traduccion en el dispositivo.' },
{ title: 'Subtitulos en vivo', description: 'Subtitulos en tiempo real superpuestos en tu pantalla. Reuniones, clases, videos. Exportacion automatica de notas.' },
{ title: 'Conversacion por voz', description: 'Modo de voz ChatGPT local. Bucle completo STT-LLM-TTS para conversaciones naturales con IA, totalmente offline.' },
{ title: 'Transcripcion de archivos', description: 'Arrastra y suelta archivos de audio o video. Whisper transcribe todo el contenido con marcas de tiempo.' },
{ title: 'Cadena multi-LLM', description: 'Encadena multiples comandos de IA: transcribir, traducir, luego resumir con una sola pulsacion.' },
{ title: 'Contexto de pantalla', description: 'Captura automatica de la app activa y texto seleccionado. Di "explica este codigo" y la IA ve lo que tu ves.' },
{ title: 'Notas de voz', description: 'Notas de voz auto-organizadas con #etiquetas. Exporta como markdown. Busca, filtra, categoriza.' },
{ title: 'Dicta por voz en cualquier lugar', body: 'Funciona donde haya un cursor: notas, navegadores, editores de código, apps de chat. Presiona el atajo dos veces para seguir hablando con las manos libres.' },
{ title: 'Limpieza de texto', body: 'Elimina muletillas y ordena las frases con un modelo local de Ollama. También puedes crear tus propios comandos, como traducir o resumir.' },
{ title: 'Sugerencias de siguiente frase', body: 'Mientras escribes, un modelo local te ofrece formas de continuar. Elige una con Ctrl+Alt+Flecha arriba/abajo e insértala con Ctrl+Alt+Enter.' },
{ title: 'Subtítulos en vivo', body: 'Muestra subtítulos sobre tu pantalla para reuniones, clases y videos. Las líneas terminadas se corrigen una vez más usando las líneas de alrededor.' },
{ title: 'Notas de reunión', body: 'Graba reuniones largas y, al terminar, redacta un resumen y las tareas pendientes.' },
{ title: 'Transcripción de archivos', body: 'Arrastra un archivo de audio o video y obtén una transcripción con marcas de tiempo.' },
],
},
pipeline: {
index: '02 / PROCESO',
title: 'Cuatro pasos. Dos segundos.',
subtitle: 'Una pulsacion y tu voz se convierte en texto pulido.',
how: {
title: 'Mantén presionado, habla, suelta. Listo.',
subtitle: 'Sin cambiar de ventana. Todo ocurre donde ya estabas escribiendo.',
steps: [
{ label: 'ENTRADA', title: 'Pulsa la tecla', description: 'Manten Right Alt (o tu tecla personalizada) para grabar. Doble toque para modo IA. Alternar para manos libres.', detail: 'Mantener / Alternar / Doble pulsacion' },
{ label: 'TRANSCRIBIR', title: 'Whisper STT', description: 'El motor local faster-whisper convierte audio PCM 16kHz a texto en tiempo real. Compatible con aceleracion GPU.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'PROCESAR', title: 'Ollama LLM', description: 'El LLM local pule la gramatica, ajusta el tono, traduce o resume. Instrucciones personalizadas soportadas.', detail: 'gemma4 / llama3.2 / phi4 / personalizado' },
{ label: 'SALIDA', title: 'Insercion automatica', description: 'El texto pulido se pega en la posicion del cursor en cualquier app. Bloc de notas, VS Code, Chrome, Slack, donde sea.', detail: 'Portapapeles + Ctrl+V / 100ms latencia' },
{ title: 'Mantener presionado', body: 'La grabación se ejecuta mientras mantienes presionado Alt derecho. Puedes cambiar el atajo en Configuración.' },
{ title: 'Reconocer', body: 'El motor integrado faster-whisper convierte el habla en texto. Una GPU NVIDIA lo hace más rápido.' },
{ title: 'Limpiar', body: 'Si está activado, un modelo local ordena o traduce el texto. Desactívalo para conservar exactamente lo que se escuchó.' },
{ title: 'Escribir', body: 'El texto se pega donde está tu cursor. Lo que tenías en el portapapeles se restaura después.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Activo',
panelLatency: 'LATENCIA: 1.2s',
sttReady: 'STT Listo',
llmConnected: 'LLM Conectado',
transcription: 'Transcripcion',
aiPolish: 'Pulido IA',
sampleInput: 'Por favor resume las notas de la reunion de hoy...',
sampleOutput: 'Por favor, resume las notas de la reunion de hoy.',
},
privacy: {
index: '03 / PRIVACIDAD',
title: 'Tu voz nunca sale.',
subtitle: 'Cada byte de audio, cada transcripcion, cada interaccion con IA permanece en tu equipo.',
monitorTitle: 'Monitor de privacidad',
allClear: 'TODO OK',
metrics: [
{ label: 'TRAFICO EN NUBE', value: '0 BYTES' },
{ label: 'CIFRADO DE DATOS', value: 'SQLite LOCAL' },
{ label: 'TELEMETRIA', value: 'DESACTIVADA' },
{ label: 'RED REQUERIDA', value: 'NO' },
{ label: 'ALMACENAMIENTO DE AUDIO', value: 'SOLO LOCAL' },
{ label: 'CODIGO ABIERTO', value: 'SI' },
],
guarantees: [
'Sin internet despues de la descarga inicial del modelo',
'Grabaciones de voz almacenadas solo en SQLite local',
'Elimina todos los datos en cualquier momento con un clic',
'Codigo abierto - inspecciona cada linea de codigo',
'Sin cuentas, sin registros, sin rastreo',
'Whisper y Ollama se ejecutan en tu hardware',
title: 'Por defecto, todo se queda en tu PC.',
subtitle: 'Hay funciones en la nube, pero no se usan hasta que las activas.',
points: [
{ title: 'El habla se procesa en tu PC', body: 'El reconocimiento de voz predeterminado usa un motor local, y las grabaciones y el historial se guardan en una base de datos en tu PC.' },
{ title: 'No se necesita cuenta', body: 'Todas las funciones locales funcionan sin iniciar sesión.' },
{ title: 'El análisis de uso está desactivado', body: 'El análisis de patrones de escritura solo se activa con tu consentimiento, y aun así los resultados se quedan en tu PC.' },
{ title: 'La nube es opcional', body: 'Si activas el reconocimiento de voz en la nube (con tu propia clave de API) o la limpieza de texto en la nube (requiere iniciar sesión), solo esas solicitudes se envían a un servidor externo.' },
],
policyLink: 'Leer la política de privacidad completa',
},
pricing: {
index: '04 / PRECIOS',
title: 'Desbloquea todo el poder.',
subtitle: 'Empieza gratis, mejora cuando lo necesites. Cancela cuando quieras.',
featureLabel: 'Funcion',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'gratis siempre',
monthly: '/mes',
annual: '/ano',
title: 'Todas las funciones locales son gratis.',
subtitle: 'Los planes de pago te permiten usar la limpieza de texto en la nube con más frecuencia y con modelos más grandes.',
perMonth: '/mes',
savePercent: 'Ahorra 17%',
billingToggleMonthly: 'Mensual',
billingToggleAnnual: 'Anual',
popular: 'Popular',
downloadFree: 'Descargar gratis',
getPro: 'Suscribirse a Pro',
getProPlus: 'Suscribirse a Pro+',
taxNote: 'Todos los precios sin impuestos. Pago seguro via Payple.',
rows: [
{ feature: 'Dictado por voz', free: '15/dia', pro: true, proPlus: true },
{ feature: 'Pulido con IA', free: '3/dia', pro: true, proPlus: true },
{ feature: 'Retencion de historial', free: '3 dias', pro: true, proPlus: true },
{ feature: 'Instrucciones personalizadas', free: 'Presets', pro: true, proPlus: true },
{ feature: 'Subtitulos en vivo', free: false, pro: true, proPlus: true },
{ feature: 'Contexto de pantalla', free: false, pro: true, proPlus: true },
{ feature: 'Notas de voz + etiquetas', free: false, pro: true, proPlus: true },
{ feature: 'Cadena multi-LLM', free: false, pro: true, proPlus: true },
{ feature: 'Comandos de voz', free: false, pro: true, proPlus: true },
{ feature: 'Exportar historial', free: false, pro: true, proPlus: true },
{ feature: 'Transcripcion de archivos', free: false, pro: false, proPlus: true },
{ feature: 'Conversacion por voz', free: false, pro: false, proPlus: true },
{ feature: 'Resumen de reuniones', free: false, pro: false, proPlus: true },
{ feature: 'RAG local', free: false, pro: false, proPlus: true },
{ feature: 'Automatizacion del SO', free: false, pro: false, proPlus: true },
unlimited: 'ilimitado',
note: 'Los precios son mensuales, en wones coreanos (KRW). El pago y la cancelación se gestionan en la página de tu cuenta web.',
free: {
name: 'Free',
desc: 'Todo lo que funciona en tu PC',
cta: 'Descargar gratis',
features: [
'Dictado, subtítulos, notas de reunión y transcripción de archivos ilimitados',
'Limpieza ilimitada con modelos locales',
'Limpieza en la nube {haiku} veces por semana (requiere iniciar sesión)',
],
},
pro: {
name: 'Pro',
desc: 'Si usas la limpieza en la nube todos los días',
cta: 'Suscribirse a Pro',
features: [
'Todo lo de Free',
'Limpieza en la nube por día: modelo estándar {haiku} veces',
'Modelo avanzado {sonnet} veces · modelo superior {opus} veces',
],
},
proPlus: {
name: 'Pro+',
desc: 'Amplio margen para modelos grandes',
cta: 'Suscribirse a Pro+',
features: [
'Todo lo de Pro',
'Limpieza en la nube por día: modelo estándar {haiku}',
'Modelo avanzado {sonnet} veces · modelo superior {opus} veces',
],
},
},
download: {
title: 'Descargar',
subtitle: 'Una vez instalado, las nuevas versiones se instalan automáticamente.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64 bits)',
cta: 'Descargar para Windows',
details: 'Publicado el {date} · El modelo de voz se descarga la primera vez que ejecutas la app.',
releaseNotes: 'Notas de la versión',
checksum: 'Hashes de archivo (latest.yml)',
soonTitle: 'Próximamente',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Se publicará aquí en cuanto se completen la firma y las verificaciones de instalación.' },
{ title: 'Android · iOS', body: 'Se publicará aquí en cuanto se complete la revisión de la tienda.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Preguntas frecuentes.',
title: 'Preguntas frecuentes',
items: [
{ q: 'Necesito instalar Ollama y Whisper por separado?', a: 'Whisper (faster-whisper) viene incluido con la app, sin instalacion adicional. Ollama solo se requiere para funciones de pulido/traduccion con IA y se instala facilmente desde la app. El dictado basico funciona sin Ollama.' },
{ q: 'Que GPU necesito?', a: 'No se requiere GPU, funciona solo con CPU. Una GPU NVIDIA (CUDA) acelera la transcripcion 5-10x. Con el modelo base, la transcripcion en tiempo real por CPU es posible.' },
{ q: 'Funciona completamente offline?', a: 'Si. Una vez descargados los modelos de Whisper y Ollama, todo funciona sin internet. La verificacion de licencia es online solo para la activacion inicial, luego 30 dias de gracia offline.' },
{ q: 'Que tan preciso es el reconocimiento de voz?', a: 'Whisper large-v3 ofrece excelente precision para mas de 99 idiomas. La funcion de diccionario personalizado mejora aun mas la precision para terminologia especializada.' },
{ q: 'Es una suscripcion?', a: 'Pro y Pro+ son suscripciones mensuales o anuales. Ahorra aproximadamente un 17% con la facturacion anual. Cancela en cualquier momento. El procesamiento local significa sin costos de nube, pero las suscripciones financian actualizaciones continuas y funciones premium.' },
{ q: 'Y macOS y Linux?', a: 'Actualmente solo Windows. Construido con Electron, el soporte para macOS/Linux es tecnicamente factible y esta planeado segun la demanda.' },
{ q: '¿Necesito instalar Ollama por separado?', a: 'Para dictar no hace falta: el motor de voz viene integrado en la app. Para ejecutar la limpieza de texto y la traducción en tu PC necesitas Ollama, y la app te guía para instalarlo.' },
{ q: '¿Necesito una GPU?', a: 'No, funciona con la CPU. Una GPU NVIDIA (CUDA) hace el reconocimiento más rápido.' },
{ q: '¿Funciona sin internet?', a: 'Sí. Una vez descargados el modelo de voz y un modelo de Ollama, el dictado y la limpieza funcionan sin conexión. Los planes de pago se conectan ocasionalmente para confirmar la suscripción, y siguen funcionando hasta 30 días sin conexión.' },
{ q: '¿En qué aplicaciones funciona?', a: 'En la mayoría de las apps de Windows donde puedas escribir. El texto se inserta pegándolo, así que los campos que bloquean pegar pueden no aceptarlo.' },
{ q: '¿Cuánto puedo hacer gratis?', a: 'Todas las funciones que se ejecutan en tu PC son gratis y sin límite de uso. Los planes de pago aumentan cuánto puedes usar la limpieza de texto en la nube.' },
{ q: '¿Hay versión para macOS o móvil?', a: 'Por ahora solo hay Windows disponible. Las apps de macOS (Apple Silicon) y móviles se publicarán aquí en cuanto pasen sus verificaciones.' },
],
},
cta: {
title1: 'Habla.',
title2: 'La IA escribe.',
subtitle1: 'Sin nube. Funciones premium, a tu manera. Sin preocupaciones de privacidad.',
subtitle2: 'Empieza ahora.',
downloadBtn: 'Descargar para Windows',
systemReq: 'Windows 10/11 \u00b7 64-bit \u00b7 ~200MB \u00b7 Listo en segundos',
},
footer: {
description: 'Asistente de voz IA completamente local. Potenciado por Whisper y Ollama. Tu voz, tu equipo, tus datos.',
copyright: '\u00a9 {year} D3RO Voice. Todos los derechos reservados.',
builtWith: 'Construido con Electron + React + TypeScript',
description: 'Dictado por voz que funciona en tu PC.',
privacy: 'Política de privacidad',
terms: 'Términos de servicio',
deleteAccount: 'Eliminar cuenta',
releaseNotes: 'Notas de la versión',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const fr: Translations = {
meta: {
title: 'D3RO Voice — La saisie vocale qui tourne sur votre PC',
description: 'Maintenez une touche, parlez, et le texte apparaît dans l\'application que vous utilisez. La reconnaissance vocale et la mise en forme du texte s\'exécutent toutes deux sur votre propre PC Windows.',
},
a11y: {
skipToContent: 'Passer au contenu',
primaryNav: 'Menu principal',
openMenu: 'Ouvrir le menu',
closeMenu: 'Fermer le menu',
language: 'Choisir la langue',
opensInNewTab: 'ouvre un nouvel onglet',
},
nav: {
features: 'FONCTIONS',
pipeline: 'PIPELINE',
privacy: 'VIE PRIVEE',
pricing: 'TARIFS',
features: 'Fonctionnalités',
how: 'Fonctionnement',
privacy: 'Confidentialité',
pricing: 'Tarifs',
faq: 'FAQ',
download: 'Telecharger',
download: 'Télécharger',
},
hero: {
badge: '100% LOCAL \u00b7 ZERO CLOUD',
title1: 'IA LOCALE',
title2: 'ASSISTANT',
title3: 'VOCAL.',
subtitle: 'Pipeline voix-vers-texte avec Whisper + Ollama fonctionnant entierement sur votre machine. Dictee, correction IA, sous-titres en direct, conversations vocales \u2014 aucun internet requis.',
downloadBtn: 'Telecharger pour Windows',
viewFeatures: 'Voir les fonctions',
dataProcessing: 'Traitement',
dataCloudTraffic: 'Trafic cloud',
dataLatency: 'Latence STT',
dataPrivacy: 'Score vie privee',
systemStatus: 'ETAT SYSTEME : OPERATIONNEL',
protocol: 'PROTOCOLE : 2025.04',
title1: 'Parlez, et le texte apparaît',
title2: 'là où se trouve votre curseur.',
subtitle: 'Maintenez le raccourci, parlez, relâchez. La reconnaissance vocale et la mise en forme s\'exécutent sur votre PC, et le résultat est saisi directement dans l\'application que vous utilisez déjà.',
hotkeyKey: 'Alt de droite',
hotkeyAction: 'maintenir pour parler',
secondaryCta: 'Voir comment ça marche',
trust: 'Gratuit pour commencer · Aucun compte requis · Fonctionne hors ligne une fois les modèles téléchargés',
windowsOnly: 'Seule une version Windows est disponible pour l\'instant. Ouvrez cette page sur un PC Windows pour la télécharger.',
},
demo: {
title: 'Exemple',
note: 'Ceci est un exemple préparé à l\'avance. Rien n\'est enregistré.',
idle: 'Appuyez sur lecture pour voir la parole se transformer en texte, puis être mise en forme.',
listening: 'Écoute en cours',
polishing: 'Mise en forme',
done: 'Saisi',
play: 'Lancer l\'exemple',
replay: 'Rejouer',
rawLabel: 'Ce qui a été entendu',
cleanLabel: 'Ce qui a été saisi',
sampleRaw: 'euh donc je pense qu\'on peut, genre, mettre la sortie vendredi prochain peut-être',
sampleClean: 'Je pense que nous pouvons prévoir la sortie pour vendredi prochain.',
},
features: {
index: '01 / FONCTIONS',
title: 'Intelligence vocale complete.',
subtitle: 'De la dictee a la conversation IA. Tout fonctionne sur votre materiel, hors ligne.',
title: 'Des notes rapides aux comptes rendus de réunion, tout sur votre PC.',
subtitle: 'Tout ce qui suit est gratuit et, avec les réglages par défaut, s\'exécute sur votre propre machine.',
items: [
{ title: 'Dictee vocale', description: 'Maintenez une touche, parlez, relachez. Whisper transcrit et insere le texte dans votre application active instantanement.' },
{ title: 'Correction IA', description: 'Ollama LLM affine votre texte transcrit en prose propre et grammaticalement correcte. Formel ou decontracte.' },
{ title: 'Traduction instantanee', description: 'Parlez dans une langue, obtenez le texte dans une autre. Detection automatique de la source, traduction sur l\'appareil.' },
{ title: 'Sous-titres en direct', description: 'Sous-titres en temps reel superposes a votre ecran. Reunions, cours, videos. Export automatique des notes.' },
{ title: 'Conversation vocale', description: 'Mode vocal ChatGPT local. Boucle complete STT-LLM-TTS pour des conversations IA naturelles, entierement hors ligne.' },
{ title: 'Transcription de fichiers', description: 'Glissez-deposez des fichiers audio ou video. Whisper transcrit l\'integralite du contenu avec horodatage.' },
{ title: 'Chaine multi-LLM', description: 'Enchainez plusieurs commandes IA : transcrire, traduire, puis resumer en une seule pression.' },
{ title: 'Contexte d\'ecran', description: 'Capture automatique de l\'app active et du texte selectionne. Dites "explique ce code" et l\'IA voit ce que vous voyez.' },
{ title: 'Memo vocal', description: 'Notes vocales auto-organisees avec #tags. Exportez en markdown. Recherchez, filtrez, classez.' },
{ title: 'Saisie vocale partout', body: 'Fonctionne partout où il y a un curseur : notes, navigateurs, éditeurs de code, applications de messagerie. Appuyez deux fois sur le raccourci pour continuer à parler mains libres.' },
{ title: 'Mise en forme du texte', body: 'Supprime les mots de remplissage et arrange les phrases avec un modèle Ollama local. Vous pouvez aussi créer vos propres commandes, comme traduire ou résumer.' },
{ title: 'Suggestions de phrase suivante', body: 'Pendant que vous écrivez, un modèle local propose des façons de continuer. Choisissez-en une avec Ctrl+Alt+Haut/Bas et insérez-la avec Ctrl+Alt+Enter.' },
{ title: 'Sous-titres en direct', body: 'Affiche des sous-titres par-dessus votre écran pour les réunions, cours et vidéos. Les lignes terminées sont corrigées une nouvelle fois à l\'aide des lignes qui les entourent.' },
{ title: 'Notes de réunion', body: 'Enregistre les réunions longues et, une fois celles-ci terminées, rédige un résumé et une liste des actions à suivre.' },
{ title: 'Transcription de fichiers', body: 'Déposez un fichier audio ou vidéo et obtenez une transcription avec horodatage.' },
],
},
pipeline: {
index: '02 / PIPELINE',
title: 'Quatre etapes. Deux secondes.',
subtitle: 'Une pression de touche et votre parole devient du texte soigne.',
how: {
title: 'Maintenez, parlez, relâchez. Terminé.',
subtitle: 'Pas besoin de changer de fenêtre. Tout se passe là où vous étiez déjà en train d\'écrire.',
steps: [
{ label: 'ENTREE', title: 'Appuyez sur la touche', description: 'Maintenez Right Alt (ou votre touche personnalisee) pour enregistrer. Double appui pour le mode IA. Basculez pour mains libres.', detail: 'Maintenir / Basculer / Double appui' },
{ label: 'TRANSCRIRE', title: 'Whisper STT', description: 'Le moteur local faster-whisper convertit l\'audio PCM 16kHz en texte en temps reel. Acceleration GPU supportee.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'TRAITER', title: 'Ollama LLM', description: 'Le LLM local corrige la grammaire, ajuste le ton, traduit ou resume. Instructions personnalisees supportees.', detail: 'gemma4 / llama3.2 / phi4 / personnalise' },
{ label: 'SORTIE', title: 'Insertion automatique', description: 'Le texte corrige est colle a la position du curseur dans n\'importe quelle app. Bloc-notes, VS Code, Chrome, Slack, partout.', detail: 'Presse-papiers + Ctrl+V / 100ms latence' },
{ title: 'Maintenir', body: 'L\'enregistrement se déroule tant que vous maintenez Alt de droite. Vous pouvez changer le raccourci dans les paramètres.' },
{ title: 'Reconnaître', body: 'Le moteur faster-whisper intégré transforme la parole en texte. Un GPU NVIDIA accélère le processus.' },
{ title: 'Mettre en forme', body: 'Si activé, un modèle local arrange ou traduit le texte. Désactivez-le pour garder exactement ce qui a été entendu.' },
{ title: 'Saisir', body: 'Le texte est collé à l\'emplacement de votre curseur. Le contenu de votre presse-papiers est ensuite restauré.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Actif',
panelLatency: 'LATENCE : 1.2s',
sttReady: 'STT Pret',
llmConnected: 'LLM Connecte',
transcription: 'Transcription',
aiPolish: 'Correction IA',
sampleInput: 'Veuillez resumer les notes de reunion d\'aujourd\'hui...',
sampleOutput: 'Veuillez resumer les notes de la reunion d\'aujourd\'hui.',
},
privacy: {
index: '03 / VIE PRIVEE',
title: 'Votre voix ne sort jamais.',
subtitle: 'Chaque octet audio, chaque transcription, chaque interaction IA reste sur votre machine.',
monitorTitle: 'Moniteur de confidentialite',
allClear: 'TOUT OK',
metrics: [
{ label: 'TRAFIC CLOUD', value: '0 OCTETS' },
{ label: 'CHIFFREMENT', value: 'SQLite LOCAL' },
{ label: 'TELEMETRIE', value: 'DESACTIVEE' },
{ label: 'RESEAU REQUIS', value: 'NON' },
{ label: 'STOCKAGE AUDIO', value: 'LOCAL UNIQUEMENT' },
{ label: 'OPEN SOURCE', value: 'OUI' },
],
guarantees: [
'Aucun internet requis apres le telechargement initial du modele',
'Enregistrements vocaux stockes uniquement dans SQLite local',
'Supprimez toutes les donnees a tout moment en un clic',
'Open source - inspectez chaque ligne de code',
'Pas de compte, pas d\'inscription, pas de pistage',
'Whisper et Ollama fonctionnent tous deux sur votre materiel',
title: 'Par défaut, tout reste sur votre PC.',
subtitle: 'Il existe des fonctionnalités cloud, mais elles ne sont utilisées que si vous les activez.',
points: [
{ title: 'La parole est traitée sur votre PC', body: 'La reconnaissance vocale par défaut utilise un moteur local, et les enregistrements ainsi que l\'historique sont stockés dans une base de données sur votre PC.' },
{ title: 'Aucun compte requis', body: 'Chaque fonctionnalité locale fonctionne sans connexion.' },
{ title: 'L\'analyse d\'usage est désactivée', body: 'L\'analyse des habitudes de frappe ne s\'active qu\'avec votre consentement, et même alors les résultats restent sur votre PC.' },
{ title: 'Le cloud est facultatif', body: 'Si vous activez la reconnaissance vocale cloud (avec votre propre clé API) ou la mise en forme cloud (connexion requise), seules ces requêtes sont envoyées à un serveur externe.' },
],
policyLink: 'Lire la politique de confidentialité complète',
},
pricing: {
index: '04 / TARIFS',
title: 'Debloquez toute la puissance.',
subtitle: 'Commencez gratuitement, passez a la version superieure quand vous voulez. Annulez a tout moment.',
featureLabel: 'Fonction',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'gratuit a vie',
monthly: '/mois',
annual: '/an',
title: 'Chaque fonctionnalité locale est gratuite.',
subtitle: 'Les forfaits payants permettent d\'utiliser la mise en forme cloud plus souvent et avec des modèles plus grands.',
perMonth: '/mois',
savePercent: 'Economisez 17%',
billingToggleMonthly: 'Mensuel',
billingToggleAnnual: 'Annuel',
popular: 'Populaire',
downloadFree: 'Telecharger gratuit',
getPro: 'S\'abonner a Pro',
getProPlus: 'S\'abonner a Pro+',
taxNote: 'Tous les prix hors taxes. Paiement securise via Payple.',
rows: [
{ feature: 'Dictee vocale', free: '15/jour', pro: true, proPlus: true },
{ feature: 'Correction IA', free: '3/jour', pro: true, proPlus: true },
{ feature: 'Conservation historique', free: '3 jours', pro: true, proPlus: true },
{ feature: 'Instructions personnalisees', free: 'Predefinis', pro: true, proPlus: true },
{ feature: 'Sous-titres en direct', free: false, pro: true, proPlus: true },
{ feature: 'Contexte d\'ecran', free: false, pro: true, proPlus: true },
{ feature: 'Memo vocal + tags', free: false, pro: true, proPlus: true },
{ feature: 'Chaine multi-LLM', free: false, pro: true, proPlus: true },
{ feature: 'Commandes vocales', free: false, pro: true, proPlus: true },
{ feature: 'Export historique', free: false, pro: true, proPlus: true },
{ feature: 'Transcription de fichiers', free: false, pro: false, proPlus: true },
{ feature: 'Conversation vocale', free: false, pro: false, proPlus: true },
{ feature: 'Resume de reunion', free: false, pro: false, proPlus: true },
{ feature: 'RAG local', free: false, pro: false, proPlus: true },
{ feature: 'Automatisation OS', free: false, pro: false, proPlus: true },
unlimited: 'illimité',
note: 'Les prix sont mensuels, en wons coréens. Le paiement et la résiliation se gèrent depuis votre page de compte web.',
free: {
name: 'Free',
desc: 'Tout ce qui s\'exécute sur votre PC',
cta: 'Télécharger gratuitement',
features: [
'Dictée, sous-titres, notes de réunion et transcription de fichiers illimités',
'Mise en forme illimitée avec les modèles locaux',
'Mise en forme cloud {haiku} fois par semaine (connexion requise)',
],
},
pro: {
name: 'Pro',
desc: 'Si vous utilisez la mise en forme cloud tous les jours',
cta: 'S\'abonner à Pro',
features: [
'Tout ce qui est dans Free',
'Mise en forme cloud par jour : modèle standard {haiku} fois',
'Modèle avancé {sonnet} fois · modèle le plus performant {opus} fois',
],
},
proPlus: {
name: 'Pro+',
desc: 'Large place pour les grands modèles',
cta: 'S\'abonner à Pro+',
features: [
'Tout ce qui est dans Pro',
'Mise en forme cloud par jour : modèle standard {haiku}',
'Modèle avancé {sonnet} fois · modèle le plus performant {opus} fois',
],
},
},
download: {
title: 'Télécharger',
subtitle: 'Une fois installées, les nouvelles versions s\'installent automatiquement.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64 bits)',
cta: 'Télécharger pour Windows',
details: 'Publié le {date} · Le modèle de reconnaissance vocale est téléchargé au premier lancement de l\'application.',
releaseNotes: 'Notes de version',
checksum: 'Empreintes de fichiers (latest.yml)',
soonTitle: 'Bientôt disponible',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Sera publié ici une fois la signature et les vérifications d\'installation terminées.' },
{ title: 'Android · iOS', body: 'Sera publié ici une fois la validation par les stores terminée.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Questions frequentes.',
title: 'Questions fréquentes',
items: [
{ q: 'Dois-je installer Ollama et Whisper separement ?', a: 'Whisper (faster-whisper) est integre a l\'application - aucune installation separee requise. Ollama n\'est necessaire que pour les fonctions de correction/traduction IA et s\'installe facilement via le guide integre. La dictee basique fonctionne sans Ollama.' },
{ q: 'Quel GPU me faut-il ?', a: 'Aucun GPU requis - ca fonctionne uniquement sur CPU. Un GPU NVIDIA (CUDA) accelere la transcription 5-10x. Avec le modele base, la transcription en temps reel sur CPU est possible.' },
{ q: 'Ca fonctionne completement hors ligne ?', a: 'Oui. Une fois les modeles Whisper et Ollama telecharges, tout fonctionne sans internet. La verification de licence est en ligne uniquement pour l\'activation initiale, puis 30 jours de grace hors ligne.' },
{ q: 'Quelle est la precision de la reconnaissance vocale ?', a: 'Whisper large-v3 offre une excellente precision pour plus de 99 langues. La fonction de dictionnaire personnalise ameliore encore la precision pour la terminologie specialisee.' },
{ q: 'C\'est un abonnement ?', a: 'Pro et Pro+ sont des abonnements mensuels ou annuels. Economisez environ 17% avec la facturation annuelle. Annulez a tout moment. Le traitement local signifie aucun cout cloud, mais les abonnements financent les mises a jour continues et les fonctionnalites premium.' },
{ q: 'Et macOS et Linux ?', a: 'Actuellement Windows uniquement. Construit avec Electron, le support macOS/Linux est techniquement faisable et prevu selon la demande.' },
{ q: 'Dois-je installer Ollama séparément ?', a: 'Pas pour la dictée : le moteur de reconnaissance vocale est intégré à l\'application. Pour exécuter la mise en forme et la traduction du texte sur votre PC, Ollama est nécessaire, et l\'application vous guide dans son installation.' },
{ q: 'Ai-je besoin d\'un GPU ?', a: 'Non, cela fonctionne sur le CPU. Un GPU NVIDIA (CUDA) accélère la reconnaissance.' },
{ q: 'Est-ce que ça fonctionne sans internet ?', a: 'Oui. Une fois le modèle de reconnaissance vocale et un modèle Ollama téléchargés, la dictée et la mise en forme fonctionnent hors ligne. Les forfaits payants se connectent occasionnellement pour confirmer l\'abonnement, et continuent de fonctionner jusqu\'à 30 jours sans connexion.' },
{ q: 'Dans quelles applications ça fonctionne ?', a: 'La plupart des applications Windows où vous pouvez saisir du texte. Le texte est inséré par collage, donc les champs qui bloquent le collage peuvent ne pas l\'accepter.' },
{ q: 'Combien puis-je faire gratuitement ?', a: 'Chaque fonctionnalité qui s\'exécute sur votre PC est gratuite, sans limite d\'utilisation. Les forfaits payants augmentent la quantité de mise en forme cloud que vous pouvez utiliser.' },
{ q: 'Existe-t-il une version macOS ou mobile ?', a: 'Seule la version Windows est disponible pour l\'instant. Les applications macOS (Apple Silicon) et mobiles seront publiées ici une fois leurs vérifications passées.' },
],
},
cta: {
title1: 'Parlez.',
title2: 'L\'IA ecrit.',
subtitle1: 'Pas de cloud. Fonctionnalites premium, a vos conditions. Aucun souci de vie privee.',
subtitle2: 'Commencez maintenant.',
downloadBtn: 'Telecharger pour Windows',
systemReq: 'Windows 10/11 \u00b7 64-bit \u00b7 ~200 Mo \u00b7 Pret en secondes',
},
footer: {
description: 'Assistant vocal IA entierement local. Propulse par Whisper et Ollama. Votre voix, votre machine, vos donnees.',
copyright: '\u00a9 {year} D3RO Voice. Tous droits reserves.',
builtWith: 'Construit avec Electron + React + TypeScript',
description: 'La saisie vocale qui tourne sur votre PC.',
privacy: 'Politique de confidentialité',
terms: 'Conditions d\'utilisation',
deleteAccount: 'Supprimer le compte',
releaseNotes: 'Notes de version',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const ja: Translations = {
meta: {
title: 'D3RO Voice — PCで動く音声入力',
description: 'キーを押しながら話すと、使っているアプリにそのままテキストが入力されます。音声認識も文章の整形も、すべて自分のWindows PCで処理されます。',
},
a11y: {
skipToContent: 'コンテンツへスキップ',
primaryNav: 'メインメニュー',
openMenu: 'メニューを開く',
closeMenu: 'メニューを閉じる',
language: '言語を選択',
opensInNewTab: '新しいタブで開く',
},
nav: {
features: '機能',
pipeline: 'パイプライン',
how: '使い方',
privacy: 'プライバシー',
pricing: '料金',
faq: 'FAQ',
faq: 'よくある質問',
download: 'ダウンロード',
},
hero: {
badge: '100% ローカル \u00b7 クラウドゼロ',
title1: 'ローカルAI',
title2: '音声',
title3: 'アシスタント.',
subtitle: 'Whisper + Ollama搭載の音声テキスト変換パイプラインがあなたのマシン上で完全に動作。ディクテーション、AI校正、リアルタイム字幕、音声会話 \u2014 インターネット不要。',
downloadBtn: 'Windows版をダウンロード',
viewFeatures: '機能を見る',
dataProcessing: '処理',
dataCloudTraffic: 'クラウド通信',
dataLatency: 'STTレイテンシ',
dataPrivacy: 'プライバシースコア',
systemStatus: 'システム状態: 稼働中',
protocol: 'プロトコル: 2025.04',
title1: '話すだけで、カーソルの位置に',
title2: '文字になります。',
subtitle: 'ショートカットを押しながら話して、離すだけ。音声認識と文章の整形はPC内で処理され、結果はいま使っているアプリにそのまま入力されます。',
hotkeyKey: '右Alt',
hotkeyAction: '押している間だけ話せる',
secondaryCta: '使い方を見る',
trust: '無料で開始 · アカウント不要 · モデルのダウンロード後はオフラインでも利用可能',
windowsOnly: '現在はWindows版のみ提供しています。ダウンロードするにはWindows PCでこのページを開いてください。',
},
demo: {
title: 'デモ',
note: 'これは台本による例です。実際には録音していません。',
idle: '再生を押すと、話した内容がテキストになり整形される様子が見られます。',
listening: '聞き取り中',
polishing: '整形中',
done: '入力完了',
play: 'デモを再生',
replay: 'もう一度再生',
rawLabel: '認識された内容',
cleanLabel: '入力された内容',
sampleRaw: 'えっと、リリースは来週の金曜日あたりでいいと思います',
sampleClean: 'リリースは来週の金曜日に予定しましょう。',
},
features: {
index: '01 / 機能',
title: '完全な音声インテリジェンス.',
subtitle: 'ディクテーションからAI会話まで。すべてがオフラインであなたのハードウェア上で実行されます。',
title: 'ちょっとしたメモから議事録まで、すべてPCの中で。',
subtitle: '以下の機能はすべて無料で、初期設定では自分のPC内で動作します。',
items: [
{ title: '音声ディクテーション', description: 'ホットキーを押して話し、離す。Whisperが即座に音声をテキストに変換し、アクティブなアプリに挿入します。' },
{ title: 'AIテキスト校正', description: 'Ollama LLMが文字起こしテキストを文法的に正確で読みやすい文章に校正。フォーマルからカジュアルまで。' },
{ title: '即時翻訳', description: '一つの言語で話し、別の言語でテキストを取得。ソース言語を自動検出し、デバイス上で翻訳。' },
{ title: 'リアルタイム字幕', description: '画面上にリアルタイム字幕オーバーレイ。会議、講義、動画視聴に。議事録の自動エクスポート。' },
{ title: '音声会話', description: 'ローカルChatGPT音声モード。STT-LLM-TTSの完全ループで自然なAI会話を完全オフラインで。' },
{ title: 'ファイル文字起こし', description: 'オーディオまたはビデオファイルをドラッグ&ドロップ。Whisperがタイムスタンプ付きで全内容を文字起こし。' },
{ title: 'マルチLLMチェーン', description: '複数のAIコマンドをパイプライン化:文字起こし、翻訳、要約をホットキー一押しで実行。' },
{ title: 'スクリーンコンテキスト', description: 'アクティブアプリと選択テキストを自動キャプチャ。「このコードを説明して」と言えばAIがあなたの見ているものを認識。' },
{ title: '音声メモ', description: '#タグで自動整理される音声ノート。マークダウンでエクスポート。検索、フィルター、分類。' },
{ title: '音声入力はどこでも', body: 'メモ、ブラウザ、コードエディタ、チャットアプリなど、カーソルがある場所ならどこでも使えます。ショートカットを2回押すと、キーを離した後も話し続けられます。' },
{ title: '文章の整形', body: 'ローカルのOllamaモデルで、言い淀みを取り除き文章を整えます。翻訳や要約など、自分だけのコマンドも作成できます。' },
{ title: '次の文章の提案', body: '入力中にローカルモデルが続きの文章候補を表示します。Ctrl+Alt+上下で選び、Ctrl+Alt+Enterで挿入します。' },
{ title: 'ライブ字幕', body: '会議や講義、動画を追えるよう、画面上に字幕を表示します。確定した行は前後の文脈を見てもう一度修正されます。' },
{ title: '会議の記録', body: '長い会議を録音し、終了後に要約とアクションアイテムをまとめます。' },
{ title: 'ファイルの文字起こし', body: '音声ファイルや動画ファイルを入れると、タイムスタンプ付きの文字起こしが得られます。' },
],
},
pipeline: {
index: '02 / パイプライン',
title: '4ステップ、2秒。',
subtitle: 'ホットキー一押しで音声が洗練されたテキストに。',
how: {
title: '押して、話して、離せば完了。',
subtitle: 'ウィンドウを切り替える必要はありません。すべて元の作業画面の中で完結します。',
steps: [
{ label: '入力', title: 'ホットキー押下', description: 'Right Alt(またはカスタムキー)を長押しで録音開始。ダブルタップでAIモード。トグルでハンズフリー。', detail: '長押し / トグル / ダブルプレス' },
{ label: '文字起こし', title: 'Whisper STT', description: 'ローカルfaster-whisperエンジンが16kHz PCMオーディオをリアルタイムでテキストに変換。GPU加速対応。', detail: 'faster-whisper / base ~ large-v3' },
{ label: '処理', title: 'Ollama LLM', description: 'ローカルLLMが文法を校正し、トーンを調整し、翻訳または要約。カスタム指示に対応。', detail: 'gemma4 / llama3.2 / phi4 / カスタム' },
{ label: '出力', title: '自動挿入', description: '校正されたテキストが任意のアプリのカーソル位置に自動貼り付け。メモ帳、VS Code、Chrome、Slackなど。', detail: 'クリップボード + Ctrl+V / 100msレイテンシ' },
{ title: '押す', body: '右Altを押している間、録音されます。ショートカットは設定で変更できます。' },
{ title: '認識', body: '内蔵のfaster-whisperエンジンが音声をテキストに変換します。NVIDIA GPUがあるとさらに高速になります。' },
{ title: '整形', body: '有効にすると、ローカルモデルが文章を整えたり翻訳したりします。オフにすると、認識した内容がそのまま使われます。' },
{ title: '入力', body: 'カーソルの位置に貼り付けられます。クリップボードの内容は貼り付け後に元に戻ります。' },
],
panelTitle: 'D3RO Voice パイプライン',
panelActive: 'アクティブ',
panelLatency: 'レイテンシ: 1.2秒',
sttReady: 'STT準備完了',
llmConnected: 'LLM接続済み',
transcription: '文字起こし',
aiPolish: 'AI校正',
sampleInput: '今日の会議メモをまとめてください...',
sampleOutput: '本日の会議メモをまとめてください。',
},
privacy: {
index: '03 / プライバシー',
title: 'あなたの声は外に出ません.',
subtitle: 'オーディオのすべてのバイト、すべての文字起こし、すべてのAIインタラクションがあなたのマシンに留まります。',
monitorTitle: 'プライバシーモニター',
allClear: '異常なし',
metrics: [
{ label: 'クラウド通信', value: '0バイト' },
{ label: 'データ暗号化', value: 'ローカルSQLite' },
{ label: 'テレメトリ', value: '無効' },
{ label: 'ネットワーク必要', value: 'いいえ' },
{ label: 'オーディオ保存', value: 'ローカルのみ' },
{ label: 'オープンソース', value: 'はい' },
],
guarantees: [
'初回モデルダウンロード後はインターネット不要',
'音声録音はローカルSQLiteにのみ保存',
'ワンクリックでいつでもすべてのデータを削除',
'オープンソース - すべてのコードを確認可能',
'アカウント不要、サインアップ不要、トラッキング不要',
'WhisperとOllamaの両方があなたのハードウェアで実行',
title: '初期設定では、すべてPCの中で完結します。',
subtitle: 'クラウド機能もありますが、自分でオンにするまでは使われません。',
points: [
{ title: '音声はPCで処理されます', body: '既定の音声認識はローカルエンジンを使い、録音と履歴はPC内のデータベースに保存されます。' },
{ title: 'アカウントは不要です', body: 'サインインしなくても、すべてのローカル機能を利用できます。' },
{ title: '利用状況の分析はオフです', body: '入力パターンの分析は同意した場合のみ有効になり、その場合も結果はPC内にとどまります。' },
{ title: 'クラウドはオンにしたときだけ', body: 'クラウド音声認識(自分のAPIキーを使用)やクラウド文章整形(サインインが必要)をオンにすると、その分のリクエストだけが外部サーバーに送信されます。' },
],
policyLink: 'プライバシーポリシー全文を見る',
},
pricing: {
index: '04 / 料金',
title: 'すべての機能をアンロック。',
subtitle: '無料で始めて、必要な時にアップグレード。いつでもキャンセル可能。',
featureLabel: '機能',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: '永久無料',
monthly: '/月',
annual: '/年',
title: 'ローカル機能はすべて無料です。',
subtitle: '有料プランでは、クラウドの文章整形をより多く、より大きなモデルで利用できます。',
perMonth: '/月',
savePercent: '17%お得',
billingToggleMonthly: '月額',
billingToggleAnnual: '年額',
popular: '人気',
downloadFree: '無料ダウンロード',
getPro: 'Proを購読',
getProPlus: 'Pro+を購読',
taxNote: '表示価格は税抜きです。Paypleによる安全な決済。',
rows: [
{ feature: '音声ディクテーション', free: '15回/日', pro: true, proPlus: true },
{ feature: 'AIテキスト校正', free: '3回/日', pro: true, proPlus: true },
{ feature: '履歴保持', free: '3日間', pro: true, proPlus: true },
{ feature: 'カスタム指示', free: 'プリセット', pro: true, proPlus: true },
{ feature: 'リアルタイム字幕', free: false, pro: true, proPlus: true },
{ feature: 'スクリーンコンテキスト', free: false, pro: true, proPlus: true },
{ feature: '音声メモ + タグ', free: false, pro: true, proPlus: true },
{ feature: 'マルチLLMチェーン', free: false, pro: true, proPlus: true },
{ feature: '音声コマンド', free: false, pro: true, proPlus: true },
{ feature: '履歴エクスポート', free: false, pro: true, proPlus: true },
{ feature: 'ファイル文字起こし', free: false, pro: false, proPlus: true },
{ feature: '音声会話', free: false, pro: false, proPlus: true },
{ feature: '議事録要約', free: false, pro: false, proPlus: true },
{ feature: 'ローカルRAG', free: false, pro: false, proPlus: true },
{ feature: 'OS自動化', free: false, pro: false, proPlus: true },
unlimited: '無制限',
note: '料金は韓国ウォン建ての月額です。お支払いと解約はウェブのアカウントページで管理します。',
free: {
name: 'Free',
desc: 'PC内で完結するすべての機能',
cta: '無料でダウンロード',
features: [
'音声入力・字幕・会議記録・ファイル文字起こしが無制限',
'ローカルモデルによる文章整形が無制限',
'クラウド整形は週{haiku}回(サインインが必要)',
],
},
pro: {
name: 'Pro',
desc: 'クラウド整形を毎日使うなら',
cta: 'Proを購読',
features: [
'Freeのすべての機能',
'クラウド整形は1日あたり標準モデル{haiku}回',
'上位モデル{sonnet}回 · 最上位モデル{opus}回',
],
},
proPlus: {
name: 'Pro+',
desc: '大きなモデルをたっぷり',
cta: 'Pro+を購読',
features: [
'Proのすべての機能',
'クラウド整形は1日あたり標準モデル{haiku}',
'上位モデル{sonnet}回 · 最上位モデル{opus}回',
],
},
},
download: {
title: 'ダウンロード',
subtitle: 'インストール後は、新しいバージョンが自動的に適用されます。',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64ビット)',
cta: 'Windows版をダウンロード',
details: '{date}公開 · 音声認識モデルは初回起動時にダウンロードされます。',
releaseNotes: 'リリースノート',
checksum: 'ファイルハッシュ(latest.yml)',
soonTitle: '準備中',
soon: [
{ title: 'macOS (Apple Silicon)', body: '署名とインストール検証が完了次第、ここに掲載します。' },
{ title: 'Android · iOS', body: 'ストア審査が完了次第、ここに掲載します。' },
],
},
faq: {
index: '05 / FAQ',
title: 'よくある質問.',
title: 'よくある質問',
items: [
{ q: 'OllamaとWhisperを別途インストールする必要がありますか?', a: 'Whisper(faster-whisper)はアプリにバンドルされており、別途インストールは不要です。Ollamaは AI校正/翻訳機能にのみ必要で、アプリ内ガイドで簡単にインストールできます。基本ディクテーションはOllamaなしで動作します。' },
{ q: 'どんなGPUが必要ですか?', a: 'GPUは不要 - CPUだけで動作します。NVIDIA GPU(CUDA)があれば文字起こしが5-10倍高速化。baseモデルならCPUでリアルタイム文字起こしが可能です。' },
{ q: '完全にオフラインで動作しますか?', a: 'はい。WhisperとOllamaのモデルをダウンロードすれば、すべてがインターネットなしで動作します。ライセンス認証は初回アクティベーション時のみオンラインが必要で、その後30日間のオフライン猶予期間があります。' },
{ q: '音声認識の精度はどうですか?', a: 'Whisper large-v3は99以上の言語で優れた精度を提供します。カスタム辞書機能により、専門用語の認識精度をさらに向上できます。' },
{ q: 'サブスクリプションですか?', a: 'ProとPro+は月額または年額のサブスクリプションです。年額払いで約17%お得。いつでもキャンセル可能。ローカル処理ベースなのでクラウドコストはありませんが、継続的なアップデートとプレミアム機能のためのサブスクリプションです。' },
{ q: 'macOSとLinuxには対応していますか?', a: '現在はWindows専用です。ElectronベースなのでmacOS/Linuxサポートは技術的に可能で、需要に応じて対応予定です。' },
{ q: 'Ollamaは別途インストールが必要ですか?', a: '音声入力には不要です。音声認識エンジンはアプリに内蔵されています。文章の整形や翻訳をPC上で行うにはOllamaが必要で、アプリの案内に沿ってインストールできます。' },
{ q: 'GPUは必要ですか?', a: '不要です。CPUだけでも動作します。NVIDIA GPU(CUDA)があると認識がさらに速くなります。' },
{ q: 'インターネットなしでも使えますか?', a: 'はい。音声認識モデルとOllamaモデルを一度ダウンロードすれば、音声入力と文章整形はオフラインで動作します。有料プランは購読確認のため時々オンラインになりますが、接続がなくても最大30日間は利用できます。' },
{ q: 'どのアプリで使えますか?', a: '文字入力ができるほとんどのWindowsアプリで動作します。テキストは貼り付けによって入力されるため、貼り付けを禁止している入力欄では使えないことがあります。' },
{ q: '無料でどこまで使えますか?', a: 'PC内で動作する機能はすべて無料で、回数制限もありません。有料プランはクラウドの文章整形を使える量を増やします。' },
{ q: 'macOSやモバイル版はありますか?', a: '現在はWindows版のみ提供しています。macOS(Apple Silicon)版とモバイル版は、検証が完了次第このページに掲載します。' },
],
},
cta: {
title1: '話す。',
title2: 'AIが書く。',
subtitle1: 'クラウドなし。プレミアム機能はあなたの条件で。プライバシーの心配なし。',
subtitle2: '今すぐ始めましょう。',
downloadBtn: 'Windows版をダウンロード',
systemReq: 'Windows 10/11 \u00b7 64ビット \u00b7 約200MB \u00b7 数秒で準備完了',
},
footer: {
description: '完全ローカルAI音声アシスタント。WhisperとOllama搭載。あなたの声、あなたのマシン、あなたのデータ。',
copyright: '\u00a9 {year} D3RO Voice. All rights reserved.',
builtWith: 'Electron + React + TypeScriptで構築',
description: 'PCで動く音声入力。',
privacy: 'プライバシーポリシー',
terms: '利用規約',
deleteAccount: 'アカウント削除',
releaseNotes: 'リリースノート',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const ko: Translations = {
meta: {
title: 'D3RO Voice — 내 PC에서 돌아가는 음성 받아쓰기',
description: '단축키를 누른 채 말하면 지금 쓰고 있는 앱에 글이 입력됩니다. 음성 인식과 문장 다듬기가 모두 내 PC에서 실행되는 Windows용 음성 받아쓰기.',
},
a11y: {
skipToContent: '본문으로 건너뛰기',
primaryNav: '주요 메뉴',
openMenu: '메뉴 열기',
closeMenu: '메뉴 닫기',
language: '언어 선택',
opensInNewTab: '새 탭에서 열림',
},
nav: {
features: '기능',
pipeline: '파이프라인',
privacy: '프라이버시',
pricing: '가격',
faq: 'FAQ',
how: '작동 방식',
privacy: '개인정보',
pricing: '요금',
faq: '자주 묻는 질문',
download: '다운로드',
},
hero: {
badge: '100% 온디바이스 · 클라우드 제로',
title1: '생각의 속도로 말하고,',
title2: 'AI가 완벽히 다듬습니다.',
title3: 'D3RO Voice.',
subtitle: '타이핑보다 3배 빠른 100% 로컬 음성 비서. 클라우드 전송 0바이트, 무제한 받아쓰기부터 실시간 화자 분리 회의록까지 당신의 PC에서 안전하게.',
downloadBtn: 'Windows용 무료 다운로드',
viewFeatures: '주요 기능 둘러보기',
dataProcessing: '처리 방식',
dataCloudTraffic: '클라우드 유출',
dataLatency: 'STT 지연시간',
dataPrivacy: '프라이버시 지수',
systemStatus: '시스템 상태: 정상 가동',
protocol: '프로토콜: 2025.04',
title1: '말하면, 커서 자리에',
title2: '글이 됩니다.',
subtitle: '단축키를 누른 채 말하고 떼세요. 음성 인식과 문장 다듬기가 내 PC에서 실행되고, 결과는 지금 쓰고 있는 앱에 바로 입력됩니다.',
hotkeyKey: '오른쪽 Alt',
hotkeyAction: '누른 채 말하기',
secondaryCta: '작동 방식 보기',
trust: '무료로 시작 · 계정 없이 사용 · 모델을 받은 뒤에는 인터넷 없이 동작',
windowsOnly: '지금은 Windows용만 제공합니다. Windows PC에서 이 페이지를 열어 받아 주세요.',
},
demo: {
title: '예시 화면',
note: '실제로 녹음하지 않는 예시입니다.',
idle: '재생을 누르면 말이 글로 바뀌고 다듬어지는 과정을 보여 드립니다.',
listening: '듣는 중',
polishing: '다듬는 중',
done: '입력 완료',
play: '예시 재생',
replay: '다시 재생',
rawLabel: '인식된 말',
cleanLabel: '입력된 문장',
sampleRaw: '어… 이번 배포는 그러니까 다음 주 금요일까지로 잡으면 될 것 같아요',
sampleClean: '이번 배포는 다음 주 금요일까지로 잡겠습니다.',
},
features: {
index: '01 / 기능',
title: '완전한 음성 인텔리전스.',
subtitle: '받아쓰기부터 AI 대화까지. 모든 것이 오프라인으로 당신의 하드웨어에서 실행됩니다.',
title: '받아쓰기부터 회의 기록까지, 내 PC 안에서.',
subtitle: '아래 기능은 모두 무료이고, 기본 설정에서 PC 안에서 실행됩니다.',
items: [
{ title: '음성 받아쓰기', description: '핫키를 누르고, 말하고, 놓으세요. Whisper가 즉시 음성을 텍스트로 변환하여 활성 앱에 삽입합니다.' },
{ title: 'AI 텍스트 다듬기', description: 'Ollama LLM이 전사된 텍스트를 깔끔하고 문법적으로 정확한 문장으로 다듬어줍니다. 격식체와 구어체 모두 지원.' },
{ title: '즉시 번역', description: '한 언어로 말하면 다른 언어로 텍스트를 받으세요. 소스 언어 자동 감지, 기기에서 직접 번역.' },
{ title: '실시간 자막', description: '화면 위에 실시간 자막 오버레이. 회의, 강의, 영상 시청에 활용. 회의록 자동 내보내기.' },
{ title: '음성 대화', description: '로컬 ChatGPT 음성 모드. STT-LLM-TTS 완전 루프로 자연스러운 AI 대화를 완전 오프라인으로.' },
{ title: '파일 전사', description: '오디오 또는 비디오 파일을 드래그 앤 드롭. Whisper가 타임스탬프와 함께 전체 내용을 전사합니다.' },
{ title: '멀티 LLM 체인', description: '여러 AI 명령어를 파이프라인으로: 전사, 번역, 요약을 핫키 한 번으로 실행.' },
{ title: '스크린 컨텍스트', description: '활성 앱과 선택된 텍스트를 자동 캡처. "이 코드 설명해줘"라고 말하면 AI가 당신이 보는 것을 봅니다.' },
{ title: '음성 메모', description: '#태그로 자동 정리되는 음성 노트. 마크다운으로 내보내기. 검색, 필터, 분류.' },
{ title: '어디서나 받아쓰기', body: '메모장, 브라우저, 코드 편집기, 메신저처럼 커서가 있는 곳이면 입력됩니다. 단축키를 두 번 누르면 손을 떼고도 계속 말할 수 있습니다.' },
{ title: '문장 다듬기', body: '말버릇과 군더더기를 걷어 내고 문장을 정리합니다. 로컬 Ollama 모델로 실행되며, 번역이나 요약 같은 명령도 직접 만들 수 있습니다.' },
{ title: '다음 문장 제안', body: '글을 쓰는 동안 로컬 모델이 이어 쓸 문장 후보를 보여 줍니다. Ctrl+Alt+위아래 화살표로 고르고 Ctrl+Alt+Enter로 넣습니다.' },
{ title: '실시간 자막', body: '화면 위에 자막을 띄워 회의, 강의, 영상을 따라갑니다. 끝난 문장은 앞뒤 맥락을 보고 한 번 더 고칩니다.' },
{ title: '회의 기록', body: '긴 회의를 녹음하고, 끝나면 요약과 할 일을 문서로 정리합니다.' },
{ title: '파일 전사', body: '녹음 파일이나 영상 파일을 넣으면 시간이 표시된 원고로 옮깁니다.' },
],
},
pipeline: {
index: '02 / 파이프라인',
title: '4단계. 2초.',
subtitle: '핫키 한 번이면 음성이 다듬어진 텍스트가 됩니다.',
how: {
title: '누르고, 말하고, 떼면 끝.',
subtitle: '다른 창으로 옮겨 가지 않고 쓰던 자리에서 끝납니다.',
steps: [
{ label: '입력', title: '핫키 누르기', description: 'Right Alt(또는 설정한 키)를 길게 눌러 녹음을 시작. 더블탭으로 AI 모드. 토글로 핸즈프리.', detail: '길게 누르기 / 토글 / 더블프레스' },
{ label: '전사', title: 'Whisper STT', description: '로컬 faster-whisper 엔진이 16kHz PCM 오디오를 실시간으로 텍스트로 변환. GPU 가속 지원.', detail: 'faster-whisper / base ~ large-v3' },
{ label: '처리', title: 'Ollama LLM', description: '로컬 LLM이 문법을 다듬고, 톤을 조절하고, 번역하거나 요약합니다. 커스텀 명령어 지원.', detail: 'gemma4 / llama3.2 / phi4 / 커스텀' },
{ label: '출력', title: '자동 삽입', description: '다듬어진 텍스트가 어떤 앱에서든 커서 위치에 자동 붙여넣기. 메모장, VS Code, Chrome, Slack, 어디서나.', detail: '클립보드 + Ctrl+V / 100ms 지연' },
{ title: '누르기', body: '오른쪽 Alt를 누르고 있는 동안 녹음합니다. 단축키는 설정에서 바꿀 수 있습니다.' },
{ title: '인식', body: '앱에 들어 있는 faster-whisper가 음성을 글자로 옮깁니다. NVIDIA GPU가 있으면 더 빨라집니다.' },
{ title: '다듬기', body: '켜 두면 로컬 모델이 문장을 정리하거나 번역합니다. 끄면 인식한 그대로 씁니다.' },
{ title: '입력', body: '커서가 있는 자리에 붙여 넣습니다. 클립보드에 있던 내용은 붙여 넣은 뒤 원래대로 돌려놓습니다.' },
],
panelTitle: 'D3RO Voice 파이프라인',
panelActive: '활성',
panelLatency: '지연시간: 1.2초',
sttReady: 'STT 준비 완료',
llmConnected: 'LLM 연결됨',
transcription: '전사',
aiPolish: 'AI 다듬기',
sampleInput: '오늘 회의 노트 정리해줘...',
sampleOutput: '오늘 회의록을 정리해 주세요.',
},
privacy: {
index: '03 / 프라이버시',
title: '음성은 절대 외부로 나가지 않습니다.',
subtitle: '오디오의 모든 바이트, 모든 전사, 모든 AI 상호작용이 당신의 컴퓨터에 머무릅니다.',
monitorTitle: '프라이버시 모니터',
allClear: '이상 없음',
metrics: [
{ label: '클라우드 트래픽', value: '0 바이트' },
{ label: '데이터 암호화', value: '로컬 SQLite' },
{ label: '텔레메트리', value: '비활성화' },
{ label: '네트워크 필요', value: '아니오' },
{ label: '오디오 저장', value: '로컬 전용' },
{ label: '오픈 소스', value: '예' },
],
guarantees: [
'초기 모델 다운로드 이후 인터넷 불필요',
'음성 녹음은 로컬 SQLite에만 저장',
'클릭 한 번으로 언제든 모든 데이터 삭제',
'오픈 소스 - 모든 코드를 직접 확인 가능',
'계정 없음, 가입 없음, 추적 없음',
'Whisper와 Ollama 모두 당신의 하드웨어에서 실행',
title: '기본값은 내 PC 안에서.',
subtitle: '클라우드 기능도 있지만, 직접 켜기 전에는 쓰이지 않습니다.',
points: [
{ title: '음성은 PC에서 처리합니다', body: '기본 음성 인식은 로컬 엔진을 쓰고, 녹음과 기록은 PC의 데이터베이스에 저장됩니다.' },
{ title: '계정 없이 쓸 수 있습니다', body: '로그인하지 않아도 모든 로컬 기능을 쓸 수 있습니다.' },
{ title: '사용 기록 분석은 꺼져 있습니다', body: '입력 패턴 분석은 동의해야 켜지고, 켜더라도 결과는 PC에만 저장됩니다.' },
{ title: '클라우드는 켤 때만 씁니다', body: '클라우드 음성 인식(직접 입력한 API 키)이나 클라우드 문장 다듬기(로그인 필요)를 켜면 그 요청만 외부 서버로 보냅니다.' },
],
policyLink: '개인정보처리방침 전문 보기',
},
pricing: {
index: '04 / 가격',
title: '전체 기능을 잠금 해제하세요.',
subtitle: '무료로 시작하고, 필요할 때 업그레이드. 언제든 취소 가능.',
featureLabel: '기능',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: '영구 무료',
monthly: '/월',
annual: '/연',
title: '로컬 기능은 전부 무료입니다.',
subtitle: '유료 요금제는 클라우드 문장 다듬기를 더 많이, 더 큰 모델로 쓸 수 있게 해 줍니다.',
perMonth: '/월',
savePercent: '17% 할인',
billingToggleMonthly: '월간',
billingToggleAnnual: '연간',
popular: '인기',
downloadFree: '무료 다운로드',
getPro: 'Pro 구독',
getProPlus: 'Pro+ 구독',
taxNote: '모든 가격은 세금 별도입니다. Payple를 통한 안전한 결제.',
rows: [
{ feature: '음성 받아쓰기', free: '15회/일', pro: true, proPlus: true },
{ feature: 'AI 텍스트 다듬기', free: '3회/일', pro: true, proPlus: true },
{ feature: '히스토리 보존', free: '3일', pro: true, proPlus: true },
{ feature: '커스텀 명령어', free: '프리셋', pro: true, proPlus: true },
{ feature: '실시간 자막', free: false, pro: true, proPlus: true },
{ feature: '스크린 컨텍스트', free: false, pro: true, proPlus: true },
{ feature: '음성 메모 + 태그', free: false, pro: true, proPlus: true },
{ feature: '멀티 LLM 체인', free: false, pro: true, proPlus: true },
{ feature: '음성 단축키', free: false, pro: true, proPlus: true },
{ feature: '히스토리 내보내기', free: false, pro: true, proPlus: true },
{ feature: '파일 전사', free: false, pro: false, proPlus: true },
{ feature: '음성 대화', free: false, pro: false, proPlus: true },
{ feature: '회의록 요약', free: false, pro: false, proPlus: true },
{ feature: '로컬 RAG', free: false, pro: false, proPlus: true },
{ feature: 'OS 자동화', free: false, pro: false, proPlus: true },
unlimited: '무제한',
note: '가격은 원화 기준 월 요금입니다. 결제와 해지는 웹 계정 페이지에서 관리합니다.',
free: {
name: 'Free',
desc: 'PC 안에서 하는 모든 기능',
cta: '무료 다운로드',
features: [
'받아쓰기, 자막, 회의 기록, 파일 전사 횟수 제한 없음',
'로컬 모델 문장 다듬기 횟수 제한 없음',
'클라우드 문장 다듬기 주 {haiku}회 (로그인 필요)',
],
},
pro: {
name: 'Pro',
desc: '클라우드 다듬기를 매일 쓴다면',
cta: 'Pro 구독하기',
features: [
'Free의 모든 기능',
'클라우드 다듬기 하루 기본 모델 {haiku}회',
'고급 모델 {sonnet}회 · 최고 모델 {opus}회',
],
},
proPlus: {
name: 'Pro+',
desc: '큰 모델을 넉넉하게',
cta: 'Pro+ 구독하기',
features: [
'Pro의 모든 기능',
'클라우드 다듬기 하루 기본 모델 {haiku}',
'고급 모델 {sonnet}회 · 최고 모델 {opus}회',
],
},
},
download: {
title: '다운로드',
subtitle: '설치한 뒤에는 새 버전이 나오면 자동으로 업데이트됩니다.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64비트)',
cta: 'Windows용 다운로드',
details: '{date} 게시 · 음성 인식 모델은 처음 실행할 때 내려받습니다.',
releaseNotes: '릴리스 노트',
checksum: '파일 해시(latest.yml)',
soonTitle: '준비 중',
soon: [
{ title: 'macOS (Apple Silicon)', body: '서명과 설치 검증을 마치면 이곳에 올립니다.' },
{ title: 'Android · iOS', body: '스토어 심사를 마치면 이곳에 올립니다.' },
],
},
faq: {
index: '05 / FAQ',
title: '자주 묻는 질문.',
title: '자주 묻는 질문',
items: [
{ q: 'Ollama와 Whisper를 따로 설치해야 하나요?', a: 'Whisper(faster-whisper)는 앱에 내장되어 있어 별도 설치가 필요 없습니다. Ollama는 AI 다듬기/번역 기능에만 필요하며, 인앱 가이드를 통해 쉽게 설치할 수 있습니다. 기본 받아쓰기는 Ollama 없이도 작동합니다.' },
{ q: '어떤 GPU가 필요한가요?', a: 'GPU가 없어도 CPU만으로 작동합니다. NVIDIA GPU(CUDA)가 있으면 전사 속도가 5-10배 빨라집니다. base 모델 기준 CPU 실시간 전사가 가능합니다.' },
{ q: '완전히 오프라인으로 작동하나요?', a: '네. Whisper와 Ollama 모델을 다운로드하면 인터넷 없이 모든 것이 작동합니다. 라이선스 검증은 최초 활성화 시에만 온라인이 필요하며, 이후 30일 오프라인 유예 기간이 제공됩니다.' },
{ q: '음성 인식 정확도는 어떤가요?', a: 'Whisper large-v3는 99개 이상의 언어에서 뛰어난 정확도를 제공합니다. 커스텀 사전 기능으로 전문 용어의 인식률을 더욱 높일 수 있습니다.' },
{ q: '구독 모델인가요?', a: 'Pro와 Pro+는 월간/연간 구독입니다. 연간 결제 시 약 17% 할인. 언제든 취소 가능. 로컬 처리 기반이라 클라우드 비용은 없지만, 지속적인 업데이트와 프리미엄 기능을 위한 구독입니다.' },
{ q: 'macOS와 Linux는 지원하나요?', a: '현재는 Windows 전용입니다. Electron 기반이므로 macOS/Linux 지원이 기술적으로 가능하며, 수요에 따라 지원할 예정입니다.' },
{ q: 'Ollama를 따로 설치해야 하나요?', a: '받아쓰기에는 필요 없습니다. 음성 인식 엔진은 앱에 들어 있습니다. 문장 다듬기와 번역을 PC에서 돌리려면 Ollama가 필요하며, 앱 안내에 따라 설치할 수 있습니다.' },
{ q: 'GPU가 없어도 되나요?', a: 'CPU만으로 동작합니다. NVIDIA GPU(CUDA)가 있으면 인식이 더 빨라집니다.' },
{ q: '인터넷 없이 쓸 수 있나요?', a: '네. 음성 인식 모델과 Ollama 모델을 한 번 내려받은 뒤에는 인터넷 없이 받아쓰기와 문장 다듬기가 동작합니다. 유료 요금제는 구독 확인을 위해 가끔 온라인 연결이 필요하며, 연결이 없어도 30일까지는 그대로 쓸 수 있습니다.' },
{ q: '어떤 앱에서 쓸 수 있나요?', a: '글을 입력할 수 있는 대부분의 Windows 앱에서 동작합니다. 붙여넣기 방식으로 입력하므로 붙여넣기를 막아 둔 입력란에서는 동작하지 않을 수 있습니다.' },
{ q: '무료로 어디까지 쓸 수 있나요?', a: 'PC 안에서 실행되는 기능은 모두 무료이고 횟수 제한이 없습니다. 유료 요금제는 클라우드 문장 다듬기 사용량을 늘려 줍니다.' },
{ q: 'macOS나 모바일에서도 쓸 수 있나요?', a: '지금은 Windows용만 제공합니다. macOS(Apple Silicon)와 모바일 앱은 검증을 마치는 대로 이 페이지에 올립니다.' },
],
},
cta: {
title1: '말하세요.',
title2: 'AI가 씁니다.',
subtitle1: '클라우드 없음. 프리미엄 기능, 당신의 조건. 프라이버시 걱정 없음.',
subtitle2: '지금 시작하세요.',
downloadBtn: 'Windows용 다운로드',
systemReq: 'Windows 10/11 \u00b7 64비트 \u00b7 ~200MB \u00b7 몇 초면 준비 완료',
},
footer: {
description: '완전 로컬 AI 음성 어시스턴트. Whisper와 Ollama 기반. 당신의 음성, 당신의 컴퓨터, 당신의 데이터.',
copyright: '\u00a9 {year} D3RO Voice. All rights reserved.',
builtWith: 'Electron + React + TypeScript로 제작',
description: '내 PC에서 돌아가는 음성 받아쓰기.',
privacy: '개인정보처리방침',
terms: '이용약관',
deleteAccount: '계정 삭제',
releaseNotes: '릴리스 노트',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const pt: Translations = {
meta: {
title: 'D3RO Voice — Digitação por voz que roda no seu PC',
description: 'Segure uma tecla, fale, e o texto aparece no aplicativo que você estiver usando. O reconhecimento de voz e a limpeza de texto rodam no seu próprio PC com Windows.',
},
a11y: {
skipToContent: 'Pular para o conteúdo',
primaryNav: 'Menu principal',
openMenu: 'Abrir menu',
closeMenu: 'Fechar menu',
language: 'Escolher idioma',
opensInNewTab: 'abre em uma nova aba',
},
nav: {
features: 'RECURSOS',
pipeline: 'PIPELINE',
privacy: 'PRIVACIDADE',
pricing: 'PRECOS',
faq: 'FAQ',
download: 'Download',
features: 'Recursos',
how: 'Como funciona',
privacy: 'Privacidade',
pricing: 'Preços',
faq: 'Perguntas frequentes',
download: 'Baixar',
},
hero: {
badge: '100% LOCAL \u00b7 ZERO NUVEM',
title1: 'IA LOCAL',
title2: 'ASSISTENTE',
title3: 'DE VOZ.',
subtitle: 'Pipeline de voz para texto com Whisper + Ollama rodando inteiramente na sua maquina. Ditado, polimento com IA, legendas em tempo real, conversas por voz \u2014 sem internet.',
downloadBtn: 'Baixar para Windows',
viewFeatures: 'Ver recursos',
dataProcessing: 'Processamento',
dataCloudTraffic: 'Trafego na nuvem',
dataLatency: 'Latencia STT',
dataPrivacy: 'Score de privacidade',
systemStatus: 'STATUS DO SISTEMA: OPERACIONAL',
protocol: 'PROTOCOLO: 2025.04',
title1: 'Fale, e o texto aparece',
title2: 'onde está o seu cursor.',
subtitle: 'Segure o atalho, fale, solte. O reconhecimento de voz e a limpeza rodam no seu PC, e o resultado é digitado direto no aplicativo que você já está usando.',
hotkeyKey: 'Alt direito',
hotkeyAction: 'segure para falar',
secondaryCta: 'Ver como funciona',
trust: 'Grátis para começar · Sem necessidade de conta · Funciona offline depois de baixar os modelos',
windowsOnly: 'No momento só há uma versão para Windows. Abra esta página em um PC com Windows para baixá-la.',
},
demo: {
title: 'Exemplo',
note: 'Este é um exemplo roteirizado. Nada é gravado.',
idle: 'Aperte reproduzir para ver a fala virar texto e ser corrigida.',
listening: 'Ouvindo',
polishing: 'Corrigindo',
done: 'Digitado',
play: 'Reproduzir exemplo',
replay: 'Reproduzir de novo',
rawLabel: 'O que foi ouvido',
cleanLabel: 'O que foi digitado',
sampleRaw: 'é... então acho que a gente pode, tipo, marcar o lançamento pra próxima sexta talvez',
sampleClean: 'Acho que podemos agendar o lançamento para a próxima sexta-feira.',
},
features: {
index: '01 / RECURSOS',
title: 'Inteligencia de voz completa.',
subtitle: 'Do ditado a conversa com IA. Tudo roda no seu hardware, offline.',
title: 'De anotações rápidas a atas de reunião, tudo no seu PC.',
subtitle: 'Tudo abaixo é gratuito e, com as configurações padrão, roda na sua própria máquina.',
items: [
{ title: 'Ditado por voz', description: 'Segure uma tecla, fale, solte. Whisper transcreve e insere texto no seu app ativo instantaneamente.' },
{ title: 'Polimento com IA', description: 'Ollama LLM refina seu texto transcrito em prosa limpa e gramaticalmente correta. Formal ou casual.' },
{ title: 'Traducao instantanea', description: 'Fale em um idioma, receba texto em outro. Deteccao automatica da origem, traducao no dispositivo.' },
{ title: 'Legendas ao vivo', description: 'Sobreposicao de legendas em tempo real na sua tela. Reunioes, aulas, videos. Exportacao automatica de notas.' },
{ title: 'Conversa por voz', description: 'Modo de voz ChatGPT local. Loop completo STT-LLM-TTS para conversas naturais com IA, totalmente offline.' },
{ title: 'Transcricao de arquivos', description: 'Arraste e solte arquivos de audio ou video. Whisper transcreve todo o conteudo com marcacoes de tempo.' },
{ title: 'Cadeia multi-LLM', description: 'Encadeie multiplos comandos de IA: transcrever, traduzir, depois resumir com um unico atalho.' },
{ title: 'Contexto de tela', description: 'Captura automatica do app ativo e texto selecionado. Diga "explique este codigo" e a IA ve o que voce ve.' },
{ title: 'Memo de voz', description: 'Notas de voz auto-organizadas com #tags. Exporte como markdown. Pesquise, filtre, categorize.' },
{ title: 'Digite por voz em qualquer lugar', body: 'Funciona em qualquer lugar com cursor: notas, navegadores, editores de código, apps de chat. Aperte o atalho duas vezes para continuar falando com as mãos livres.' },
{ title: 'Limpeza de texto', body: 'Remove vícios de linguagem e organiza as frases com um modelo local do Ollama. Você também pode criar seus próprios comandos, como traduzir ou resumir.' },
{ title: 'Sugestões da próxima frase', body: 'Enquanto você escreve, um modelo local sugere formas de continuar. Escolha uma com Ctrl+Alt+Seta para cima/baixo e insira com Ctrl+Alt+Enter.' },
{ title: 'Legendas ao vivo', body: 'Mostra legendas na sua tela para reuniões, aulas e vídeos. As linhas concluídas são corrigidas mais uma vez com base nas linhas ao redor.' },
{ title: 'Notas de reunião', body: 'Grava reuniões longas e, ao final, escreve um resumo e os próximos passos.' },
{ title: 'Transcrição de arquivos', body: 'Solte um arquivo de áudio ou vídeo e receba uma transcrição com marcações de tempo.' },
],
},
pipeline: {
index: '02 / PIPELINE',
title: 'Quatro passos. Dois segundos.',
subtitle: 'Um atalho e sua fala se torna texto polido.',
how: {
title: 'Segure, fale, solte. Pronto.',
subtitle: 'Sem trocar de janela. Tudo acontece onde você já estava digitando.',
steps: [
{ label: 'ENTRADA', title: 'Pressione a tecla', description: 'Segure Right Alt (ou sua tecla personalizada) para gravar. Toque duplo para modo IA. Alternar para viva-voz.', detail: 'Segurar / Alternar / Toque duplo' },
{ label: 'TRANSCREVER', title: 'Whisper STT', description: 'Motor local faster-whisper converte audio PCM 16kHz em texto em tempo real. Aceleracao GPU suportada.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'PROCESSAR', title: 'Ollama LLM', description: 'LLM local corrige gramatica, ajusta tom, traduz ou resume. Instrucoes personalizadas suportadas.', detail: 'gemma4 / llama3.2 / phi4 / personalizado' },
{ label: 'SAIDA', title: 'Insercao automatica', description: 'Texto polido e colado na posicao do cursor em qualquer app. Bloco de notas, VS Code, Chrome, Slack, em qualquer lugar.', detail: 'Area de transferencia + Ctrl+V / 100ms latencia' },
{ title: 'Segurar', body: 'A gravação roda enquanto você segura o Alt direito. Você pode mudar o atalho nas Configurações.' },
{ title: 'Reconhecer', body: 'O mecanismo faster-whisper embutido transforma a fala em texto. Uma GPU NVIDIA deixa isso mais rápido.' },
{ title: 'Corrigir', body: 'Se ativado, um modelo local organiza ou traduz o texto. Desative para manter exatamente o que foi ouvido.' },
{ title: 'Digitar', body: 'O texto é colado onde está o seu cursor. O que estava na área de transferência é restaurado depois.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Ativo',
panelLatency: 'LATENCIA: 1.2s',
sttReady: 'STT Pronto',
llmConnected: 'LLM Conectado',
transcription: 'Transcricao',
aiPolish: 'Polimento IA',
sampleInput: 'Por favor, resuma as notas da reuniao de hoje...',
sampleOutput: 'Por favor, resuma as notas da reuniao de hoje.',
},
privacy: {
index: '03 / PRIVACIDADE',
title: 'Sua voz nunca sai.',
subtitle: 'Cada byte de audio, cada transcricao, cada interacao com IA permanece na sua maquina.',
monitorTitle: 'Monitor de privacidade',
allClear: 'TUDO OK',
metrics: [
{ label: 'TRAFEGO NA NUVEM', value: '0 BYTES' },
{ label: 'CRIPTOGRAFIA', value: 'SQLite LOCAL' },
{ label: 'TELEMETRIA', value: 'DESATIVADA' },
{ label: 'REDE NECESSARIA', value: 'NAO' },
{ label: 'ARMAZENAMENTO DE AUDIO', value: 'APENAS LOCAL' },
{ label: 'CODIGO ABERTO', value: 'SIM' },
],
guarantees: [
'Sem internet apos o download inicial do modelo',
'Gravacoes de voz armazenadas apenas no SQLite local',
'Exclua todos os dados a qualquer momento com um clique',
'Codigo aberto - inspecione cada linha de codigo',
'Sem contas, sem cadastros, sem rastreamento',
'Whisper e Ollama rodam no seu hardware',
title: 'Por padrão, tudo fica no seu PC.',
subtitle: 'Existem recursos na nuvem, mas eles não são usados até você ativá-los.',
points: [
{ title: 'A fala é processada no seu PC', body: 'O reconhecimento de voz padrão usa um mecanismo local, e as gravações e o histórico ficam salvos em um banco de dados no seu PC.' },
{ title: 'Não é preciso ter conta', body: 'Todo recurso local funciona sem fazer login.' },
{ title: 'A análise de uso está desativada', body: 'A análise de padrões de digitação só é ativada com seu consentimento, e mesmo assim os resultados ficam no seu PC.' },
{ title: 'A nuvem é opcional', body: 'Se você ativar o reconhecimento de voz na nuvem (com sua própria chave de API) ou a limpeza de texto na nuvem (exige login), só essas solicitações são enviadas a um servidor externo.' },
],
policyLink: 'Ler a política de privacidade completa',
},
pricing: {
index: '04 / PRECOS',
title: 'Desbloqueie todo o poder.',
subtitle: 'Comece gratis, faca upgrade quando precisar. Cancele a qualquer momento.',
featureLabel: 'Recurso',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'gratis para sempre',
monthly: '/mes',
annual: '/ano',
perMonth: '/mes',
savePercent: 'Economize 17%',
billingToggleMonthly: 'Mensal',
billingToggleAnnual: 'Anual',
popular: 'Popular',
downloadFree: 'Baixar gratis',
getPro: 'Assinar Pro',
getProPlus: 'Assinar Pro+',
taxNote: 'Todos os precos excluem impostos. Pagamento seguro via Payple.',
rows: [
{ feature: 'Ditado por voz', free: '15/dia', pro: true, proPlus: true },
{ feature: 'Polimento com IA', free: '3/dia', pro: true, proPlus: true },
{ feature: 'Retencao de historico', free: '3 dias', pro: true, proPlus: true },
{ feature: 'Instrucoes personalizadas', free: 'Presets', pro: true, proPlus: true },
{ feature: 'Legendas ao vivo', free: false, pro: true, proPlus: true },
{ feature: 'Contexto de tela', free: false, pro: true, proPlus: true },
{ feature: 'Memo de voz + tags', free: false, pro: true, proPlus: true },
{ feature: 'Cadeia multi-LLM', free: false, pro: true, proPlus: true },
{ feature: 'Comandos de voz', free: false, pro: true, proPlus: true },
{ feature: 'Exportar historico', free: false, pro: true, proPlus: true },
{ feature: 'Transcricao de arquivos', free: false, pro: false, proPlus: true },
{ feature: 'Conversa por voz', free: false, pro: false, proPlus: true },
{ feature: 'Resumo de reuniao', free: false, pro: false, proPlus: true },
{ feature: 'RAG local', free: false, pro: false, proPlus: true },
{ feature: 'Automacao do SO', free: false, pro: false, proPlus: true },
title: 'Todo recurso local é gratuito.',
subtitle: 'Os planos pagos permitem usar a limpeza de texto na nuvem com mais frequência e com modelos maiores.',
perMonth: '/mês',
unlimited: 'ilimitado',
note: 'Os preços são mensais, em wons sul-coreanos (KRW). O pagamento e o cancelamento são gerenciados na página da sua conta web.',
free: {
name: 'Free',
desc: 'Tudo que roda no seu PC',
cta: 'Baixar grátis',
features: [
'Ditado, legendas, notas de reunião e transcrição de arquivos ilimitados',
'Limpeza ilimitada com modelos locais',
'Limpeza na nuvem {haiku} vezes por semana (exige login)',
],
},
pro: {
name: 'Pro',
desc: 'Se você usa a limpeza na nuvem todo dia',
cta: 'Assinar o Pro',
features: [
'Tudo do Free',
'Limpeza na nuvem por dia: modelo padrão {haiku} vezes',
'Modelo avançado {sonnet} vezes · modelo top {opus} vezes',
],
},
proPlus: {
name: 'Pro+',
desc: 'Bastante espaço para modelos grandes',
cta: 'Assinar o Pro+',
features: [
'Tudo do Pro',
'Limpeza na nuvem por dia: modelo padrão {haiku}',
'Modelo avançado {sonnet} vezes · modelo top {opus} vezes',
],
},
},
download: {
title: 'Baixar',
subtitle: 'Depois de instalado, as novas versões são instaladas automaticamente.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64 bits)',
cta: 'Baixar para Windows',
details: 'Publicado em {date} · O modelo de voz é baixado na primeira vez que você executa o app.',
releaseNotes: 'Notas de versão',
checksum: 'Hashes de arquivo (latest.yml)',
soonTitle: 'Em breve',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Será publicado aqui assim que a assinatura e as verificações de instalação forem concluídas.' },
{ title: 'Android · iOS', body: 'Será publicado aqui assim que a análise da loja for concluída.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Perguntas frequentes.',
title: 'Perguntas frequentes',
items: [
{ q: 'Preciso instalar Ollama e Whisper separadamente?', a: 'Whisper (faster-whisper) ja vem com o app - sem instalacao separada. Ollama e necessario apenas para recursos de polimento/traducao com IA e pode ser instalado facilmente pelo guia do app. O ditado basico funciona sem Ollama.' },
{ q: 'Qual GPU preciso?', a: 'Nenhuma GPU necessaria - funciona apenas com CPU. Uma GPU NVIDIA (CUDA) acelera a transcricao 5-10x. Com o modelo base, transcricao em tempo real por CPU e possivel.' },
{ q: 'Funciona completamente offline?', a: 'Sim. Apos baixar os modelos Whisper e Ollama, tudo funciona sem internet. A verificacao de licenca e online apenas na ativacao inicial, depois 30 dias de carencia offline.' },
{ q: 'Qual a precisao do reconhecimento de voz?', a: 'Whisper large-v3 oferece excelente precisao para mais de 99 idiomas. O recurso de dicionario personalizado melhora ainda mais a precisao para terminologia especializada.' },
{ q: 'E uma assinatura?', a: 'Pro e Pro+ sao assinaturas mensais ou anuais. Economize cerca de 17% com a cobranca anual. Cancele a qualquer momento. O processamento local significa sem custos de nuvem, mas as assinaturas financiam atualizacoes continuas e recursos premium.' },
{ q: 'E o macOS e Linux?', a: 'Atualmente apenas Windows. Construido com Electron, o suporte macOS/Linux e tecnicamente viavel e planejado conforme a demanda.' },
{ q: 'Preciso instalar o Ollama separadamente?', a: 'Para ditado, não: o mecanismo de voz já vem embutido no app. Para rodar a limpeza de texto e a tradução no seu PC, você precisa do Ollama, e o app te guia na instalação.' },
{ q: 'Preciso de uma GPU?', a: 'Não, funciona na CPU. Uma GPU NVIDIA (CUDA) deixa o reconhecimento mais rápido.' },
{ q: 'Funciona sem internet?', a: 'Sim. Depois de baixar o modelo de voz e um modelo do Ollama, o ditado e a limpeza funcionam offline. Os planos pagos se conectam de vez em quando para confirmar a assinatura, e continuam funcionando por até 30 dias sem conexão.' },
{ q: 'Em quais aplicativos funciona?', a: 'Na maioria dos apps do Windows onde dá para digitar. O texto é inserido por colagem, então campos que bloqueiam colar podem não aceitar.' },
{ q: 'Quanto dá pra usar de graça?', a: 'Todo recurso que roda no seu PC é gratuito e sem limite de uso. Os planos pagos aumentam quanto você pode usar a limpeza de texto na nuvem.' },
{ q: 'Tem versão para macOS ou celular?', a: 'No momento só há Windows disponível. Os apps de macOS (Apple Silicon) e celular serão publicados aqui assim que passarem pelas verificações.' },
],
},
cta: {
title1: 'Fale.',
title2: 'A IA escreve.',
subtitle1: 'Sem nuvem. Recursos premium, nos seus termos. Sem preocupacoes com privacidade.',
subtitle2: 'Comece agora.',
downloadBtn: 'Baixar para Windows',
systemReq: 'Windows 10/11 \u00b7 64-bit \u00b7 ~200MB \u00b7 Pronto em segundos',
},
footer: {
description: 'Assistente de voz IA totalmente local. Alimentado por Whisper e Ollama. Sua voz, sua maquina, seus dados.',
copyright: '\u00a9 {year} D3RO Voice. Todos os direitos reservados.',
builtWith: 'Construido com Electron + React + TypeScript',
description: 'Digitação por voz que roda no seu PC.',
privacy: 'Política de privacidade',
terms: 'Termos de serviço',
deleteAccount: 'Excluir conta',
releaseNotes: 'Notas de versão',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const ru: Translations = {
meta: {
title: 'D3RO Voice — голосовой ввод, который работает на вашем ПК',
description: 'Зажмите клавишу, скажите — и текст появится в том приложении, где вы работаете. Распознавание речи и очистка текста выполняются прямо на вашем компьютере с Windows.',
},
a11y: {
skipToContent: 'Перейти к содержимому',
primaryNav: 'Главное меню',
openMenu: 'Открыть меню',
closeMenu: 'Закрыть меню',
language: 'Выбор языка',
opensInNewTab: 'открывается в новой вкладке',
},
nav: {
features: 'ФУНКЦИИ',
pipeline: 'КОНВЕЙЕР',
privacy: 'ПРИВАТНОСТЬ',
pricing: 'ЦЕНЫ',
faq: 'FAQ',
features: 'Возможности',
how: 'Как это работает',
privacy: 'Конфиденциальность',
pricing: 'Тарифы',
faq: 'Вопросы',
download: 'Скачать',
},
hero: {
badge: '100% ЛОКАЛЬНО \u00b7 БЕЗ ОБЛАКА',
title1: 'ЛОКАЛЬНЫЙ ИИ',
title2: 'ГОЛОСОВОЙ',
title3: 'АССИСТЕНТ.',
subtitle: 'Конвейер преобразования речи в текст на Whisper + Ollama, полностью работающий на вашем компьютере. Диктовка, ИИ-корректировка, субтитры в реальном времени, голосовые разговоры \u2014 интернет не нужен.',
downloadBtn: 'Скачать для Windows',
viewFeatures: 'Смотреть функции',
dataProcessing: 'Обработка',
dataCloudTraffic: 'Облачный трафик',
dataLatency: 'Задержка STT',
dataPrivacy: 'Оценка приватности',
systemStatus: 'СТАТУС СИСТЕМЫ: РАБОТАЕТ',
protocol: 'ПРОТОКОЛ: 2025.04',
title1: 'Говорите — и текст появляется',
title2: 'там, где стоит курсор.',
subtitle: 'Зажмите сочетание клавиш, скажите и отпустите. Распознавание речи и очистка текста выполняются на вашем ПК, а результат сразу вставляется в то приложение, с которым вы работаете.',
hotkeyKey: 'Правый Alt',
hotkeyAction: 'зажмите, чтобы говорить',
secondaryCta: 'Посмотреть, как это работает',
trust: 'Бесплатный старт · Без регистрации · Работает офлайн после загрузки моделей',
windowsOnly: 'Сейчас доступна только версия для Windows. Откройте эту страницу на компьютере с Windows, чтобы скачать приложение.',
},
demo: {
title: 'Пример',
note: 'Это заранее подготовленный пример. Ничего не записывается.',
idle: 'Нажмите «Воспроизвести», чтобы увидеть, как речь превращается в текст и очищается.',
listening: 'Слушаю',
polishing: 'Очищаю',
done: 'Введено',
play: 'Воспроизвести пример',
replay: 'Воспроизвести ещё раз',
rawLabel: 'Что было услышано',
cleanLabel: 'Что было введено',
sampleRaw: 'э-э, ну я думаю, можно, типа, поставить релиз на следующую пятницу, наверное',
sampleClean: 'Думаю, мы можем назначить релиз на следующую пятницу.',
},
features: {
index: '01 / ФУНКЦИИ',
title: 'Полный голосовой интеллект.',
subtitle: 'От диктовки до ИИ-разговора. Все работает на вашем оборудовании, офлайн.',
title: 'От быстрых заметок до протоколов встреч — и всё на вашем ПК.',
subtitle: 'Всё перечисленное ниже бесплатно и по умолчанию работает на вашем компьютере.',
items: [
{ title: 'Голосовая диктовка', description: 'Удерживайте горячую клавишу, говорите, отпустите. Whisper мгновенно транскрибирует и вставляет текст в активное приложение.' },
{ title: 'ИИ-корректировка текста', description: 'Ollama LLM превращает транскрибированный текст в чистую, грамматически правильную прозу. Формально или неформально.' },
{ title: 'Мгновенный перевод', description: 'Говорите на одном языке, получайте текст на другом. Автоматическое определение языка, перевод на устройстве.' },
{ title: 'Субтитры в реальном времени', description: 'Наложение субтитров в реальном времени на экран. Совещания, лекции, видео. Автоматический экспорт заметок.' },
{ title: 'Голосовой разговор', description: 'Локальный голосовой режим ChatGPT. Полный цикл STT-LLM-TTS для естественных ИИ-разговоров, полностью офлайн.' },
{ title: 'Транскрипция файлов', description: 'Перетащите аудио- или видеофайлы. Whisper транскрибирует весь контент с временными метками.' },
{ title: 'Мульти-LLM цепочка', description: 'Цепочка из нескольких ИИ-команд: транскрибировать, перевести, затем обобщить одним нажатием клавиши.' },
{ title: 'Контекст экрана', description: 'Автоматический захват активного приложения и выделенного текста. Скажите "объясни этот код" и ИИ увидит то же, что и вы.' },
{ title: 'Голосовые заметки', description: 'Автоматически организованные голосовые заметки с #тегами. Экспорт в markdown. Поиск, фильтрация, категоризация.' },
{ title: 'Голосовой ввод в любом приложении', body: 'Работает везде, где есть курсор: в заметках, браузере, редакторах кода, мессенджерах. Нажмите сочетание клавиш дважды, чтобы говорить, не удерживая клавишу.' },
{ title: 'Очистка текста', body: 'Убирает слова-паразиты и приводит предложения в порядок с помощью локальной модели Ollama. Можно также создавать свои команды, например для перевода или пересказа.' },
{ title: 'Подсказки следующего предложения', body: 'Пока вы пишете, локальная модель предлагает варианты продолжения. Выбирайте вариант клавишами Ctrl+Alt+вверх/вниз и вставляйте его сочетанием Ctrl+Alt+Enter.' },
{ title: 'Живые субтитры', body: 'Показывает субтитры поверх экрана во время встреч, лекций и видео. Законченные строки ещё раз исправляются с учётом соседних фраз.' },
{ title: 'Заметки со встреч', body: 'Записывает длинные встречи и по их завершении составляет краткое содержание и список задач.' },
{ title: 'Расшифровка файлов', body: 'Перетащите аудио- или видеофайл — и получите расшифровку с таймкодами.' },
],
},
pipeline: {
index: '02 / КОНВЕЙЕР',
title: 'Четыре шага. Две секунды.',
subtitle: 'Одно нажатие клавиши \u2014 и ваша речь становится отредактированным текстом.',
how: {
title: 'Зажмите, скажите, отпустите. Готово.',
subtitle: 'Никакого переключения между окнами. Всё происходит там, где вы уже печатали.',
steps: [
{ label: 'ВВОД', title: 'Нажмите горячую клавишу', description: 'Удерживайте Right Alt (или вашу клавишу) для записи. Двойное нажатие для ИИ-режима. Переключение для свободных рук.', detail: 'Удержание / Переключение / Двойное нажатие' },
{ label: 'ТРАНСКРИПЦИЯ', title: 'Whisper STT', description: 'Локальный движок faster-whisper преобразует 16кГц PCM аудио в текст в реальном времени. Поддержка GPU-ускорения.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'ОБРАБОТКА', title: 'Ollama LLM', description: 'Локальная LLM корректирует грамматику, настраивает тон, переводит или обобщает. Пользовательские инструкции поддерживаются.', detail: 'gemma4 / llama3.2 / phi4 / пользовательский' },
{ label: 'ВЫВОД', title: 'Автовставка', description: 'Отредактированный текст вставляется в позицию курсора в любом приложении. Блокнот, VS Code, Chrome, Slack, где угодно.', detail: 'Буфер обмена + Ctrl+V / 100мс задержка' },
{ title: 'Зажмите', body: 'Пока вы удерживаете правый Alt, идёт запись. Сочетание клавиш можно изменить в настройках.' },
{ title: 'Распознавание', body: 'Встроенный движок faster-whisper превращает речь в текст. С видеокартой NVIDIA распознавание работает быстрее.' },
{ title: 'Очистка', body: 'Если включено, локальная модель приводит текст в порядок или переводит его. Отключите эту функцию, чтобы сохранить текст ровно таким, каким он был услышан.' },
{ title: 'Ввод', body: 'Текст вставляется туда, где стоит курсор. Всё, что было в буфере обмена, возвращается обратно после вставки.' },
],
panelTitle: 'D3RO Voice Конвейер',
panelActive: 'Активен',
panelLatency: 'ЗАДЕРЖКА: 1.2с',
sttReady: 'STT Готов',
llmConnected: 'LLM Подключен',
transcription: 'Транскрипция',
aiPolish: 'ИИ-корректировка',
sampleInput: 'Пожалуйста, обобщите заметки с сегодняшнего совещания...',
sampleOutput: 'Пожалуйста, обобщите заметки с сегодняшнего совещания.',
},
privacy: {
index: '03 / ПРИВАТНОСТЬ',
title: 'Ваш голос никогда не покидает компьютер.',
subtitle: 'Каждый байт аудио, каждая транскрипция, каждое взаимодействие с ИИ остается на вашем устройстве.',
monitorTitle: 'Монитор приватности',
allClear: 'ВСЕ В ПОРЯДКЕ',
metrics: [
{ label: 'ОБЛАЧНЫЙ ТРАФИК', value: '0 БАЙТ' },
{ label: 'ШИФРОВАНИЕ ДАННЫХ', value: 'ЛОКАЛЬНЫЙ SQLite' },
{ label: 'ТЕЛЕМЕТРИЯ', value: 'ОТКЛЮЧЕНА' },
{ label: 'СЕТЬ ТРЕБУЕТСЯ', value: 'НЕТ' },
{ label: 'ХРАНЕНИЕ АУДИО', value: 'ТОЛЬКО ЛОКАЛЬНО' },
{ label: 'ОТКРЫТЫЙ КОД', value: 'ДА' },
],
guarantees: [
'Интернет не нужен после первоначальной загрузки модели',
'Голосовые записи хранятся только в локальном SQLite',
'Удалите все данные в любой момент одним кликом',
'Открытый исходный код \u2014 проверьте каждую строку',
'Без аккаунтов, без регистрации, без отслеживания',
'Whisper и Ollama работают на вашем оборудовании',
title: 'По умолчанию всё остаётся на вашем ПК.',
subtitle: 'Облачные функции существуют, но не используются, пока вы сами их не включите.',
points: [
{ title: 'Речь обрабатывается на вашем ПК', body: 'По умолчанию распознавание речи выполняется локальным движком, а записи и история хранятся в базе данных на вашем компьютере.' },
{ title: 'Аккаунт не нужен', body: 'Все локальные функции работают без входа в систему.' },
{ title: 'Анализ использования отключён', body: 'Анализ манеры набора текста включается только с вашего согласия, и даже тогда результаты остаются на вашем ПК.' },
{ title: 'Облако — по желанию', body: 'Если вы включите облачное распознавание речи (с собственным API-ключом) или облачную очистку текста (нужен вход в систему), на внешний сервер будут отправляться только эти запросы.' },
],
policyLink: 'Читать политику конфиденциальности полностью',
},
pricing: {
index: '04 / ЦЕНЫ',
title: 'Разблокируйте все возможности.',
subtitle: 'Начните бесплатно, обновитесь когда нужно. Отмена в любое время.',
featureLabel: 'Функция',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'навсегда бесплатно',
monthly: '/мес',
annual: '/год',
perMonth: '/мес',
savePercent: 'Скидка 17%',
billingToggleMonthly: 'Ежемесячно',
billingToggleAnnual: 'Ежегодно',
popular: 'Популярно',
downloadFree: 'Скачать бесплатно',
getPro: 'Подписаться на Pro',
getProPlus: 'Подписаться на Pro+',
taxNote: 'Все цены без учета налогов. Безопасная оплата через Payple.',
rows: [
{ feature: 'Голосовая диктовка', free: '15/день', pro: true, proPlus: true },
{ feature: 'ИИ-корректировка текста', free: '3/день', pro: true, proPlus: true },
{ feature: 'Хранение истории', free: '3 дня', pro: true, proPlus: true },
{ feature: 'Пользовательские инструкции', free: 'Пресеты', pro: true, proPlus: true },
{ feature: 'Субтитры в реальном времени', free: false, pro: true, proPlus: true },
{ feature: 'Контекст экрана', free: false, pro: true, proPlus: true },
{ feature: 'Голосовые заметки + теги', free: false, pro: true, proPlus: true },
{ feature: 'Мульти-LLM цепочка', free: false, pro: true, proPlus: true },
{ feature: 'Голосовые команды', free: false, pro: true, proPlus: true },
{ feature: 'Экспорт истории', free: false, pro: true, proPlus: true },
{ feature: 'Транскрипция файлов', free: false, pro: false, proPlus: true },
{ feature: 'Голосовой разговор', free: false, pro: false, proPlus: true },
{ feature: 'Резюме совещания', free: false, pro: false, proPlus: true },
{ feature: 'Локальный RAG', free: false, pro: false, proPlus: true },
{ feature: 'Автоматизация ОС', free: false, pro: false, proPlus: true },
title: 'Все локальные функции бесплатны.',
subtitle: 'Платные тарифы дают чаще пользоваться облачной очисткой текста и с более крупными моделями.',
perMonth: '/мес.',
unlimited: 'без ограничений',
note: 'Цены указаны за месяц в южнокорейских вонах. Оплата и отмена подписки управляются на странице вашего аккаунта на сайте.',
free: {
name: 'Free',
desc: 'Всё, что работает на вашем ПК',
cta: 'Скачать бесплатно',
features: [
'Безлимитный набор текста, субтитры, заметки со встреч и расшифровка файлов',
'Безлимитная очистка локальными моделями',
'Облачная очистка {haiku} раз в неделю (нужен вход в систему)',
],
},
pro: {
name: 'Pro',
desc: 'Если вы пользуетесь облачной очисткой каждый день',
cta: 'Оформить Pro',
features: [
'Всё из Free',
'Облачная очистка в день: базовая модель — {haiku} раз',
'Продвинутая модель — {sonnet} раз · топовая модель — {opus} раз',
],
},
proPlus: {
name: 'Pro+',
desc: 'С запасом для крупных моделей',
cta: 'Оформить Pro+',
features: [
'Всё из Pro',
'Облачная очистка в день: базовая модель — {haiku}',
'Продвинутая модель — {sonnet} раз · топовая модель — {opus} раз',
],
},
},
download: {
title: 'Скачать',
subtitle: 'После установки новые версии устанавливаются автоматически.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64-бит)',
cta: 'Скачать для Windows',
details: 'Опубликовано {date} · Модель распознавания речи загружается при первом запуске приложения.',
releaseNotes: 'Список изменений',
checksum: 'Хеши файлов (latest.yml)',
soonTitle: 'Скоро',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Появится здесь после завершения подписи и проверки установки.' },
{ title: 'Android · iOS', body: 'Появится здесь после прохождения проверки в магазинах приложений.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Частые вопросы.',
title: 'Частые вопросы',
items: [
{ q: 'Нужно ли устанавливать Ollama и Whisper отдельно?', a: 'Whisper (faster-whisper) встроен в приложение \u2014 отдельная установка не нужна. Ollama требуется только для функций ИИ-корректировки/перевода и легко устанавливается через встроенное руководство. Базовая диктовка работает без Ollama.' },
{ q: 'Какая GPU нужна?', a: 'GPU не требуется \u2014 работает только на CPU. NVIDIA GPU (CUDA) ускоряет транскрипцию в 5-10 раз. С моделью base транскрипция в реальном времени на CPU возможна.' },
{ q: 'Работает ли полностью офлайн?', a: 'Да. После загрузки моделей Whisper и Ollama все работает без интернета. Проверка лицензии онлайн только при первой активации, затем 30 дней офлайн-льготного периода.' },
{ q: 'Насколько точно распознавание речи?', a: 'Whisper large-v3 обеспечивает отличную точность для более чем 99 языков. Функция пользовательского словаря дополнительно повышает точность для специализированной терминологии.' },
{ q: 'Это подписка?', a: 'Pro и Pro+ \u2014 это ежемесячные или ежегодные подписки. Экономьте около 17% при ежегодной оплате. Отмена в любое время. Локальная обработка означает отсутствие облачных расходов, но подписки финансируют постоянные обновления и премиум-функции.' },
{ q: 'А macOS и Linux?', a: 'Пока только Windows. Построено на Electron, поддержка macOS/Linux технически возможна и планируется в зависимости от спроса.' },
{ q: 'Нужно ли отдельно устанавливать Ollama?', a: 'Для голосового ввода — нет: движок распознавания речи встроен в приложение. Чтобы выполнять очистку текста и перевод на своём ПК, нужен Ollama, и приложение поможет вам его установить.' },
{ q: 'Нужна ли видеокарта?', a: 'Нет, всё работает на процессоре. Видеокарта NVIDIA (CUDA) ускоряет распознавание.' },
{ q: 'Работает ли без интернета?', a: 'Да. После загрузки модели распознавания речи и модели Ollama голосовой ввод и очистка текста работают офлайн. Платные тарифы иногда выходят в сеть, чтобы подтвердить подписку, и продолжают работать без подключения до 30 дней.' },
{ q: 'В каких приложениях это работает?', a: 'В большинстве приложений Windows, куда можно вводить текст. Текст вставляется через буфер обмена, поэтому в полях, где вставка заблокирована, он может не сработать.' },
{ q: 'Сколько можно делать бесплатно?', a: 'Все функции, которые работают на вашем ПК, бесплатны и без ограничений по количеству. Платные тарифы увеличивают лимит облачной очистки текста.' },
{ q: 'Есть ли версия для macOS или мобильных устройств?', a: 'Сейчас доступна только версия для Windows. Версии для macOS (Apple Silicon) и мобильных устройств появятся здесь после прохождения проверок.' },
],
},
cta: {
title1: 'Говорите.',
title2: 'ИИ пишет.',
subtitle1: 'Без облака. Премиум-функции на ваших условиях. Без проблем с приватностью.',
subtitle2: 'Начните сейчас.',
downloadBtn: 'Скачать для Windows',
systemReq: 'Windows 10/11 \u00b7 64-бит \u00b7 ~200МБ \u00b7 Готово за секунды',
},
footer: {
description: 'Полностью локальный ИИ-голосовой ассистент. На базе Whisper и Ollama. Ваш голос, ваш компьютер, ваши данные.',
copyright: '\u00a9 {year} D3RO Voice. Все права защищены.',
builtWith: 'Создано с Electron + React + TypeScript',
description: 'Голосовой ввод, который работает на вашем ПК.',
privacy: 'Политика конфиденциальности',
terms: 'Условия использования',
deleteAccount: 'Удалить аккаунт',
releaseNotes: 'Список изменений',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const vi: Translations = {
meta: {
title: 'D3RO Voice — Gõ bằng giọng nói, chạy ngay trên máy tính của bạn',
description: 'Giữ một phím, nói, và chữ sẽ xuất hiện trong bất kỳ ứng dụng nào bạn đang dùng. Nhận diện giọng nói và làm sạch văn bản đều chạy trên máy tính Windows của bạn.',
},
a11y: {
skipToContent: 'Bỏ qua đến nội dung chính',
primaryNav: 'Menu chính',
openMenu: 'Mở menu',
closeMenu: 'Đóng menu',
language: 'Chọn ngôn ngữ',
opensInNewTab: 'mở trong tab mới',
},
nav: {
features: 'TINH NANG',
pipeline: 'QUY TRINH',
privacy: 'RIENG TU',
pricing: 'GIA CA',
faq: 'FAQ',
download: 'Tai xuong',
features: 'Tính năng',
how: 'Cách hoạt động',
privacy: 'Quyền riêng tư',
pricing: 'Giá',
faq: 'Câu hỏi thường gặp',
download: 'Tải xuống',
},
hero: {
badge: '100% CUC BO \u00b7 KHONG DAM MAY',
title1: 'TRO LY',
title2: 'GIONG NOI',
title3: 'AI CUC BO.',
subtitle: 'Duong ong chuyen giong noi thanh van ban voi Whisper + Ollama chay hoan toan tren may tinh cua ban. Chinh ta, tinh chinh AI, phu de truc tiep, hoi thoai giong noi \u2014 khong can internet.',
downloadBtn: 'Tai xuong cho Windows',
viewFeatures: 'Xem tinh nang',
dataProcessing: 'Xu ly',
dataCloudTraffic: 'Luu luong dam may',
dataLatency: 'Do tre STT',
dataPrivacy: 'Diem rieng tu',
systemStatus: 'TRANG THAI HE THONG: HOAT DONG',
protocol: 'GIAO THUC: 2025.04',
title1: 'Bạn nói, chữ hiện ra',
title2: 'ngay tại vị trí con trỏ.',
subtitle: 'Giữ phím tắt, nói, rồi thả ra. Nhận diện giọng nói và làm sạch văn bản chạy trên máy tính của bạn, kết quả được gõ thẳng vào ứng dụng bạn đang dùng.',
hotkeyKey: 'Alt phải',
hotkeyAction: 'giữ để nói',
secondaryCta: 'Xem cách hoạt động',
trust: 'Miễn phí để bắt đầu · Không cần tài khoản · Hoạt động offline sau khi tải mô hình',
windowsOnly: 'Hiện chỉ có phiên bản Windows. Hãy mở trang này trên máy tính Windows để tải về.',
},
demo: {
title: 'Ví dụ',
note: 'Đây là ví dụ dựng sẵn. Không có gì được ghi âm.',
idle: 'Nhấn phát để xem lời nói biến thành chữ và được làm sạch.',
listening: 'Đang nghe',
polishing: 'Đang làm sạch',
done: 'Đã gõ xong',
play: 'Phát ví dụ',
replay: 'Phát lại',
rawLabel: 'Những gì được nghe thấy',
cleanLabel: 'Những gì được gõ ra',
sampleRaw: 'À thì... em nghĩ mình có thể, kiểu, để đợt phát hành vào thứ Sáu tuần sau chắc được',
sampleClean: 'Tôi nghĩ chúng ta có thể lên lịch phát hành vào thứ Sáu tuần sau.',
},
features: {
index: '01 / TINH NANG',
title: 'Tri tue giong noi toan dien.',
subtitle: 'Tu chinh ta den hoi thoai AI. Tat ca chay tren phan cung cua ban, ngoai tuyen.',
title: 'Từ ghi chú nhanh đến biên bản cuộc họp, tất cả ngay trên máy tính của bạn.',
subtitle: 'Mọi tính năng dưới đây đều miễn phí và, với cài đặt mặc định, chạy trên chính máy tính của bạn.',
items: [
{ title: 'Chinh ta giong noi', description: 'Giu phim nong, noi, tha. Whisper phien am va chen van ban vao ung dung dang hoat dong ngay lap tuc.' },
{ title: 'Tinh chinh van ban AI', description: 'Ollama LLM tinh chinh van ban phien am thanh van xuoi sach, dung ngu phap. Trang trong hoac binh thuong.' },
{ title: 'Dich tuc thi', description: 'Noi bang mot ngon ngu, nhan van ban bang ngon ngu khac. Tu dong nhan dien nguon, dich tren thiet bi.' },
{ title: 'Phu de truc tiep', description: 'Lop phu de thoi gian thuc tren man hinh. Cuoc hop, bai giang, video. Tu dong xuat ghi chu cuoc hop.' },
{ title: 'Hoi thoai giong noi', description: 'Che do giong noi ChatGPT cuc bo. Vong lap STT-LLM-TTS day du cho hoi thoai AI tu nhien, hoan toan ngoai tuyen.' },
{ title: 'Phien am tap tin', description: 'Keo va tha tap tin am thanh hoac video. Whisper phien am toan bo noi dung voi moc thoi gian.' },
{ title: 'Chuoi da LLM', description: 'Ket noi nhieu lenh AI: phien am, dich, roi tom tat chi voi mot lan nhan phim.' },
{ title: 'Ngu canh man hinh', description: 'Tu dong chup ung dung dang hoat dong va van ban duoc chon. Noi "giai thich doan ma nay" va AI thay nhung gi ban thay.' },
{ title: 'Ghi chu giong noi', description: 'Ghi chu giong noi tu dong sap xep voi #the. Xuat ra markdown. Tim kiem, loc, phan loai.' },
{ title: 'Gõ bằng giọng nói ở bất kỳ đâu', body: 'Hoạt động ở bất cứ nơi nào có con trỏ: ghi chú, trình duyệt, trình soạn mã, ứng dụng nhắn tin. Nhấn phím tắt hai lần để tiếp tục nói mà không cần giữ phím.' },
{ title: 'Làm sạch văn bản', body: 'Loại bỏ từ đệm và chỉnh lại câu bằng một mô hình Ollama chạy cục bộ. Bạn cũng có thể tạo lệnh riêng, chẳng hạn dịch hoặc tóm tắt.' },
{ title: 'Gợi ý câu tiếp theo', body: 'Trong khi bạn viết, một mô hình cục bộ đề xuất cách viết tiếp. Chọn gợi ý bằng Ctrl+Alt+mũi tên lên/xuống rồi chèn vào bằng Ctrl+Alt+Enter.' },
{ title: 'Phụ đề trực tiếp', body: 'Hiển thị phụ đề đè lên màn hình cho các cuộc họp, bài giảng và video. Các dòng đã hoàn tất được chỉnh sửa thêm một lần dựa trên các dòng xung quanh.' },
{ title: 'Ghi chú cuộc họp', body: 'Ghi âm các cuộc họp dài và khi kết thúc, viết ra bản tóm tắt cùng danh sách việc cần làm.' },
{ title: 'Chuyển văn bản từ tệp', body: 'Thả vào một tệp âm thanh hoặc video để nhận bản chuyển văn bản kèm dấu thời gian.' },
],
},
pipeline: {
index: '02 / QUY TRINH',
title: 'Bon buoc. Hai giay.',
subtitle: 'Mot lan nhan phim nong va loi noi cua ban tro thanh van ban hoan chinh.',
how: {
title: 'Giữ, nói, thả ra. Xong.',
subtitle: 'Không cần chuyển cửa sổ. Mọi thứ diễn ra ngay nơi bạn đang gõ.',
steps: [
{ label: 'DAU VAO', title: 'Nhan phim nong', description: 'Giu Right Alt (hoac phim tuy chinh) de ghi am. Nhan doi cho che do AI. Chuyen doi cho che do ranh tay.', detail: 'Giu / Chuyen doi / Nhan doi' },
{ label: 'PHIEN AM', title: 'Whisper STT', description: 'Bo may faster-whisper cuc bo chuyen doi am thanh PCM 16kHz thanh van ban thoi gian thuc. Ho tro tang toc GPU.', detail: 'faster-whisper / base ~ large-v3' },
{ label: 'XU LY', title: 'Ollama LLM', description: 'LLM cuc bo sua ngu phap, dieu chinh giong dieu, dich hoac tom tat. Ho tro chi dan tuy chinh.', detail: 'gemma4 / llama3.2 / phi4 / tuy chinh' },
{ label: 'DAU RA', title: 'Tu dong chen', description: 'Van ban da tinh chinh duoc dan vao vi tri con tro trong bat ky ung dung nao. Notepad, VS Code, Chrome, Slack, bat cu dau.', detail: 'Clipboard + Ctrl+V / 100ms do tre' },
{ title: 'Giữ phím', body: 'Việc ghi âm diễn ra trong khi bạn giữ Alt phải. Bạn có thể đổi phím tắt trong phần Cài đặt.' },
{ title: 'Nhận diện', body: 'Bộ máy faster-whisper tích hợp sẵn chuyển giọng nói thành chữ. Card đồ họa NVIDIA giúp việc này nhanh hơn.' },
{ title: 'Làm sạch', body: 'Nếu được bật, một mô hình cục bộ sẽ chỉnh lại hoặc dịch văn bản. Tắt tính năng này để giữ nguyên đúng những gì được nghe thấy.' },
{ title: 'Gõ ra', body: 'Văn bản được dán vào đúng vị trí con trỏ. Nội dung từng có trong clipboard sẽ được khôi phục lại sau đó.' },
],
panelTitle: 'D3RO Voice Pipeline',
panelActive: 'Hoat dong',
panelLatency: 'DO TRE: 1.2s',
sttReady: 'STT San sang',
llmConnected: 'LLM Da ket noi',
transcription: 'Phien am',
aiPolish: 'Tinh chinh AI',
sampleInput: 'Vui long tom tat ghi chu cuoc hop hom nay...',
sampleOutput: 'Vui long tom tat ghi chu cuoc hop hom nay.',
},
privacy: {
index: '03 / RIENG TU',
title: 'Giong noi cua ban khong bao gio roi di.',
subtitle: 'Moi byte am thanh, moi phien am, moi tuong tac AI deu o tren may tinh cua ban.',
monitorTitle: 'Giam sat rieng tu',
allClear: 'AN TOAN',
metrics: [
{ label: 'LUU LUONG DAM MAY', value: '0 BYTES' },
{ label: 'MA HOA DU LIEU', value: 'SQLite CUC BO' },
{ label: 'DO LUONG TU XA', value: 'TAT' },
{ label: 'YEU CAU MANG', value: 'KHONG' },
{ label: 'LUU TRU AM THANH', value: 'CHI CUC BO' },
{ label: 'MA NGUON MO', value: 'CO' },
],
guarantees: [
'Khong can internet sau khi tai mo hinh lan dau',
'Ban ghi giong noi chi luu trong SQLite cuc bo',
'Xoa tat ca du lieu bat ky luc nao chi voi mot click',
'Ma nguon mo - kiem tra tung dong ma',
'Khong tai khoan, khong dang ky, khong theo doi',
'Whisper va Ollama deu chay tren phan cung cua ban',
title: 'Mặc định, mọi thứ ở lại trên máy tính của bạn.',
subtitle: 'Có các tính năng đám mây, nhưng chúng không được dùng cho đến khi bạn tự bật lên.',
points: [
{ title: 'Giọng nói được xử lý trên máy tính của bạn', body: 'Nhận diện giọng nói mặc định dùng bộ máy cục bộ, bản ghi âm và lịch sử được lưu trong cơ sở dữ liệu trên máy tính của bạn.' },
{ title: 'Không cần tài khoản', body: 'Mọi tính năng chạy cục bộ đều hoạt động mà không cần đăng nhập.' },
{ title: 'Phân tích sử dụng đang tắt', body: 'Phân tích thói quen gõ chỉ bật khi bạn đồng ý, và kể cả khi đó kết quả vẫn ở lại trên máy tính của bạn.' },
{ title: 'Đám mây chỉ dùng khi bạn chọn', body: 'Nếu bạn bật nhận diện giọng nói trên đám mây (dùng API key của riêng bạn) hoặc làm sạch văn bản trên đám mây (cần đăng nhập), chỉ những yêu cầu đó được gửi đến máy chủ bên ngoài.' },
],
policyLink: 'Đọc toàn bộ chính sách quyền riêng tư',
},
pricing: {
index: '04 / GIA CA',
title: 'Mo khoa toan bo suc manh.',
subtitle: 'Bat dau mien phi, nang cap khi can. Huy bat cu luc nao.',
featureLabel: 'Tinh nang',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: 'mien phi mai mai',
monthly: '/thang',
annual: '/nam',
perMonth: '/thang',
savePercent: 'Tiet kiem 17%',
billingToggleMonthly: 'Hang thang',
billingToggleAnnual: 'Hang nam',
popular: 'Pho bien',
downloadFree: 'Tai mien phi',
getPro: 'Dang ky Pro',
getProPlus: 'Dang ky Pro+',
taxNote: 'Tat ca gia chua bao gom thue. Thanh toan an toan qua Payple.',
rows: [
{ feature: 'Chinh ta giong noi', free: '15/ngay', pro: true, proPlus: true },
{ feature: 'Tinh chinh van ban AI', free: '3/ngay', pro: true, proPlus: true },
{ feature: 'Luu tru lich su', free: '3 ngay', pro: true, proPlus: true },
{ feature: 'Chi dan tuy chinh', free: 'Mac dinh', pro: true, proPlus: true },
{ feature: 'Phu de truc tiep', free: false, pro: true, proPlus: true },
{ feature: 'Ngu canh man hinh', free: false, pro: true, proPlus: true },
{ feature: 'Ghi chu giong noi + the', free: false, pro: true, proPlus: true },
{ feature: 'Chuoi da LLM', free: false, pro: true, proPlus: true },
{ feature: 'Lenh giong noi', free: false, pro: true, proPlus: true },
{ feature: 'Xuat lich su', free: false, pro: true, proPlus: true },
{ feature: 'Phien am tap tin', free: false, pro: false, proPlus: true },
{ feature: 'Hoi thoai giong noi', free: false, pro: false, proPlus: true },
{ feature: 'Tom tat cuoc hop', free: false, pro: false, proPlus: true },
{ feature: 'RAG cuc bo', free: false, pro: false, proPlus: true },
{ feature: 'Tu dong hoa OS', free: false, pro: false, proPlus: true },
title: 'Mọi tính năng cục bộ đều miễn phí.',
subtitle: 'Các gói trả phí cho phép bạn dùng tính năng làm sạch văn bản trên đám mây nhiều hơn và với mô hình lớn hơn.',
perMonth: '/tháng',
unlimited: 'không giới hạn',
note: 'Giá được tính theo tháng, bằng won Hàn Quốc (KRW). Việc thanh toán và hủy được quản lý trên trang tài khoản web của bạn.',
free: {
name: 'Free',
desc: 'Mọi thứ chạy trên máy tính của bạn',
cta: 'Tải miễn phí',
features: [
'Gõ chính tả, phụ đề, ghi chú cuộc họp và chuyển văn bản từ tệp không giới hạn',
'Làm sạch bằng mô hình cục bộ không giới hạn',
'Làm sạch trên đám mây {haiku} lần mỗi tuần (cần đăng nhập)',
],
},
pro: {
name: 'Pro',
desc: 'Dành cho ai dùng làm sạch trên đám mây mỗi ngày',
cta: 'Đăng ký Pro',
features: [
'Tất cả trong Free',
'Làm sạch trên đám mây mỗi ngày: mô hình tiêu chuẩn {haiku} lần',
'Mô hình nâng cao {sonnet} lần · mô hình cao cấp nhất {opus} lần',
],
},
proPlus: {
name: 'Pro+',
desc: 'Dư sức dùng các mô hình lớn',
cta: 'Đăng ký Pro+',
features: [
'Tất cả trong Pro',
'Làm sạch trên đám mây mỗi ngày: mô hình tiêu chuẩn {haiku}',
'Mô hình nâng cao {sonnet} lần · mô hình cao cấp nhất {opus} lần',
],
},
},
download: {
title: 'Tải xuống',
subtitle: 'Sau khi cài đặt, phiên bản mới sẽ được cài tự động.',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11 (64-bit)',
cta: 'Tải cho Windows',
details: 'Phát hành {date} · Mô hình nhận diện giọng nói được tải về trong lần chạy đầu tiên.',
releaseNotes: 'Ghi chú phát hành',
checksum: 'Mã băm tệp (latest.yml)',
soonTitle: 'Sắp ra mắt',
soon: [
{ title: 'macOS (Apple Silicon)', body: 'Sẽ được đăng ở đây sau khi hoàn tất ký số và kiểm tra cài đặt.' },
{ title: 'Android · iOS', body: 'Sẽ được đăng ở đây sau khi hoàn tất kiểm duyệt của store.' },
],
},
faq: {
index: '05 / FAQ',
title: 'Cau hoi thuong gap.',
title: 'Câu hỏi thường gặp',
items: [
{ q: 'Toi co can cai dat Ollama va Whisper rieng khong?', a: 'Whisper (faster-whisper) da duoc tich hop trong ung dung - khong can cai dat rieng. Ollama chi can thiet cho tinh nang tinh chinh/dich AI va co the de dang cai dat qua huong dan trong ung dung. Chinh ta co ban hoat dong khong can Ollama.' },
{ q: 'Toi can GPU nao?', a: 'Khong can GPU - hoat dong chi voi CPU. GPU NVIDIA (CUDA) tang toc phien am 5-10 lan. Voi mo hinh base, phien am thoi gian thuc tren CPU la kha thi.' },
{ q: 'Co hoat dong hoan toan ngoai tuyen khong?', a: 'Co. Sau khi tai cac mo hinh Whisper va Ollama, moi thu hoat dong khong can internet. Xac minh giay phep chi truc tuyen khi kich hoat lan dau, sau do 30 ngay an han ngoai tuyen.' },
{ q: 'Do chinh xac nhan dien giong noi the nao?', a: 'Whisper large-v3 cung cap do chinh xac tuyet voi cho hon 99 ngon ngu. Tinh nang tu dien tuy chinh cai thien them do chinh xac cho thuat ngu chuyen nganh.' },
{ q: 'Day co phai dang ky khong?', a: 'Pro va Pro+ la dang ky hang thang hoac hang nam. Tiet kiem khoang 17% voi thanh toan hang nam. Huy bat cu luc nao. Xu ly cuc bo nghia la khong co chi phi dam may, nhung dang ky tai tro cho cac ban cap nhat lien tuc va tinh nang cao cap.' },
{ q: 'Con macOS va Linux thi sao?', a: 'Hien tai chi ho tro Windows. Duoc xay dung tren Electron nen ho tro macOS/Linux la kha thi ve mat ky thuat va duoc len ke hoach theo nhu cau.' },
{ q: 'Tôi có cần cài Ollama riêng không?', a: 'Với gõ chính tả thì không cần: bộ máy nhận diện giọng nói đã tích hợp sẵn trong ứng dụng. Để chạy làm sạch văn bản và dịch trên máy tính của bạn thì cần Ollama, và ứng dụng sẽ hướng dẫn bạn cài đặt.' },
{ q: 'Tôi có cần card đồ họa không?', a: 'Không, ứng dụng chạy được trên CPU. Card đồ họa NVIDIA (CUDA) giúp nhận diện nhanh hơn.' },
{ q: 'Có dùng được khi không có internet không?', a: 'Có. Sau khi tải xong mô hình nhận diện giọng nói và một mô hình Ollama, việc gõ chính tả và làm sạch văn bản hoạt động offline. Các gói trả phí thỉnh thoảng kết nối mạng để xác nhận gói đăng ký, và vẫn hoạt động tới 30 ngày mà không cần kết nối.' },
{ q: 'Ứng dụng hoạt động trong những ứng dụng nào?', a: 'Hầu hết các ứng dụng Windows có thể gõ chữ. Văn bản được chèn vào bằng cách dán, nên những ô nhập chặn việc dán có thể không nhận được.' },
{ q: 'Tôi có thể dùng miễn phí đến mức nào?', a: 'Mọi tính năng chạy trên máy tính của bạn đều miễn phí và không giới hạn số lần dùng. Các gói trả phí tăng thêm mức dùng làm sạch văn bản trên đám mây.' },
{ q: 'Có phiên bản macOS hay di động không?', a: 'Hiện chỉ có phiên bản Windows. Ứng dụng macOS (Apple Silicon) và di động sẽ được đăng ở đây sau khi vượt qua các bước kiểm tra.' },
],
},
cta: {
title1: 'Noi.',
title2: 'AI viet.',
subtitle1: 'Khong dam may. Tinh nang cao cap, theo dieu kien cua ban. Khong lo ngai ve quyen rieng tu.',
subtitle2: 'Bat dau ngay.',
downloadBtn: 'Tai xuong cho Windows',
systemReq: 'Windows 10/11 \u00b7 64-bit \u00b7 ~200MB \u00b7 San sang trong vai giay',
},
footer: {
description: 'Tro ly giong noi AI hoan toan cuc bo. Duoc ho tro boi Whisper va Ollama. Giong noi cua ban, may tinh cua ban, du lieu cua ban.',
copyright: '\u00a9 {year} D3RO Voice. Moi quyen duoc bao luu.',
builtWith: 'Duoc xay dung voi Electron + React + TypeScript',
description: 'Gõ bằng giọng nói, chạy ngay trên máy tính của bạn.',
privacy: 'Chính sách quyền riêng tư',
terms: 'Điều khoản dịch vụ',
deleteAccount: 'Xóa tài khoản',
releaseNotes: 'Ghi chú phát hành',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -1,149 +1,152 @@
import type { Translations } from '../index'
export const zh: Translations = {
meta: {
title: 'D3RO Voice — 在你电脑上运行的语音输入',
description: '按住快捷键说话,文字就会输入到你正在使用的应用中。语音识别和文本润色都在你自己的 Windows 电脑上完成。',
},
a11y: {
skipToContent: '跳至内容',
primaryNav: '主菜单',
openMenu: '打开菜单',
closeMenu: '关闭菜单',
language: '选择语言',
opensInNewTab: '在新标签页中打开',
},
nav: {
features: '功能',
pipeline: '流程',
how: '工作原理',
privacy: '隐私',
pricing: '价格',
faq: '常见问题',
download: '下载',
},
hero: {
badge: '100% 本地 \u00b7 零云端',
title1: '本地AI',
title2: '语音',
title3: '助手.',
subtitle: 'Whisper + Ollama 驱动的语音转文字流程,完全在您的设备上运行。听写、AI润色、实时字幕、语音对话 \u2014 无需互联网。',
downloadBtn: '下载 Windows 版',
viewFeatures: '查看功能',
dataProcessing: '处理方式',
dataCloudTraffic: '云端流量',
dataLatency: 'STT 延迟',
dataPrivacy: '隐私评分',
systemStatus: '系统状态: 正常运行',
protocol: '协议: 2025.04',
title1: '说出来,文字就会出现在',
title2: '光标所在的位置。',
subtitle: '按住快捷键说话,松开即可。语音识别和文本润色都在你的电脑上完成,结果会直接输入到你正在使用的应用中。',
hotkeyKey: '右 Alt',
hotkeyAction: '按住说话',
secondaryCta: '查看工作原理',
trust: '免费开始 · 无需账号 · 下载模型后可离线使用',
windowsOnly: '目前仅提供 Windows 版本。请在 Windows 电脑上打开此页面进行下载。',
},
demo: {
title: '示例',
note: '这是一个脚本示例,不会录音。',
idle: '点击播放,查看语音转换为文字并被润色的过程。',
listening: '正在聆听',
polishing: '正在润色',
done: '已输入',
play: '播放示例',
replay: '重新播放',
rawLabel: '识别到的内容',
cleanLabel: '输入的内容',
sampleRaw: '呃就是说我们可以把发布定在下周五左右吧',
sampleClean: '我们把发布定在下周五。',
},
features: {
index: '01 / 功能',
title: '完整的语音智能.',
subtitle: '从听写到AI对话,一切都在您的硬件上离线运行。',
title: '从随手记录到会议纪要,一切都在你的电脑上完成。',
subtitle: '以下功能全部免费,且在默认设置下都在你自己的电脑上运行。',
items: [
{ title: '语音听写', description: '按住快捷键,说话,松开。Whisper 即时转录并将文字插入到当前活动应用中。' },
{ title: 'AI文本润色', description: 'Ollama LLM 将转录文本润色为干净、语法正确的文字。支持正式和非正式风格。' },
{ title: '即时翻译', description: '用一种语言说话,获取另一种语言的文本。自动检测源语言,设备端翻译。' },
{ title: '实时字幕', description: '屏幕上的实时字幕叠加。适用于会议、讲座、视频。自动导出会议记录。' },
{ title: '语音对话', description: '本地 ChatGPT 语音模式。完整的 STT-LLM-TTS 循环,完全离线的自然AI对话。' },
{ title: '文件转录', description: '拖放音频或视频文件。Whisper 将带时间戳转录全部内容。' },
{ title: '多LLM链', description: '串联多个AI命令:转录、翻译、然后摘要,一个快捷键完成。' },
{ title: '屏幕上下文', description: '自动捕获活动应用和选中文本。说"解释这段代码",AI就能看到您所看到的。' },
{ title: '语音备忘录', description: '带 #标签 的自动整理语音笔记。导出为 Markdown。搜索、筛选、分类。' },
{ title: '随处语音输入', body: '只要有光标就能用:笔记、浏览器、代码编辑器、聊天应用。连按两次快捷键即可解放双手继续说话。' },
{ title: '文本润色', body: '用本地 Ollama 模型去除口头禅、整理句子。你也可以创建自己的指令,比如翻译或摘要。' },
{ title: '下一句建议', body: '写作时,本地模型会给出续写建议。用 Ctrl+Alt+上下方向键选择,Ctrl+Alt+Enter 插入。' },
{ title: '实时字幕', body: '在屏幕上显示字幕,方便跟上会议、讲座和视频。已完成的句子会结合上下文再修正一次。' },
{ title: '会议记录', body: '录制较长的会议,结束后自动生成摘要和待办事项。' },
{ title: '文件转录', body: '拖入音频或视频文件,即可获得带时间戳的转录文本。' },
],
},
pipeline: {
index: '02 / 流程',
title: '四步,两秒。',
subtitle: '按一下快捷键,语音即变为精炼文字。',
how: {
title: '按住、说话、松开,完成。',
subtitle: '不用切换窗口,一切都在你原本输入的地方完成。',
steps: [
{ label: '输入', title: '按下快捷键', description: '长按 Right Alt(或自定义按键)开始录音。双击进入AI模式。切换免提模式。', detail: '长按 / 切换 / 双击' },
{ label: '转录', title: 'Whisper STT', description: '本地 faster-whisper 引擎将 16kHz PCM 音频实时转换为文本。支持GPU加速。', detail: 'faster-whisper / base ~ large-v3' },
{ label: '处理', title: 'Ollama LLM', description: '本地 LLM 润色语法、调整语气、翻译或摘要。支持自定义指令。', detail: 'gemma4 / llama3.2 / phi4 / 自定义' },
{ label: '输出', title: '自动插入', description: '润色后的文本自动粘贴到任何应用的光标位置。记事本、VS Code、Chrome、Slack,任何地方。', detail: '剪贴板 + Ctrl+V / 100ms延迟' },
{ title: '按住', body: '按住右 Alt 键时进行录音。快捷键可以在设置中修改。' },
{ title: '识别', body: '内置的 faster-whisper 引擎将语音转换为文字。NVIDIA 显卡可以让识别更快。' },
{ title: '润色', body: '开启后,本地模型会整理或翻译文字。关闭则保留识别到的原始内容。' },
{ title: '输入', body: '文字会粘贴到光标所在位置。粘贴后,剪贴板中的原内容会恢复。' },
],
panelTitle: 'D3RO Voice 流程',
panelActive: '运行中',
panelLatency: '延迟: 1.2秒',
sttReady: 'STT 就绪',
llmConnected: 'LLM 已连接',
transcription: '转录',
aiPolish: 'AI润色',
sampleInput: '请帮我整理今天的会议记录...',
sampleOutput: '请整理今天的会议记录。',
},
privacy: {
index: '03 / 隐私',
title: '您的声音永远不会外泄.',
subtitle: '每一个字节的音频、每一次转录、每一次AI交互都留在您的设备上。',
monitorTitle: '隐私监控',
allClear: '全部正常',
metrics: [
{ label: '云端流量', value: '0 字节' },
{ label: '数据加密', value: '本地 SQLite' },
{ label: '遥测', value: '已禁用' },
{ label: '需要网络', value: '否' },
{ label: '音频存储', value: '仅本地' },
{ label: '开源', value: '是' },
],
guarantees: [
'初次模型下载后无需互联网',
'语音录制仅存储在本地 SQLite',
'一键即可随时删除所有数据',
'开源 - 可检查每一行代码',
'无需账号、无需注册、无跟踪',
'Whisper 和 Ollama 均在您的硬件上运行',
title: '默认情况下,一切都在你的电脑上完成。',
subtitle: '也提供云端功能,但在你开启之前不会被使用。',
points: [
{ title: '语音在你的电脑上处理', body: '默认语音识别使用本地引擎,录音和记录都保存在你电脑上的数据库中。' },
{ title: '无需账号', body: '所有本地功能无需登录即可使用。' },
{ title: '用量分析默认关闭', body: '输入模式分析只有在你同意后才会开启,即便开启,结果也只保存在你的电脑上。' },
{ title: '云端功能需手动开启', body: '如果你开启云端语音识别(使用自己的 API 密钥)或云端文本润色(需要登录),只有这部分请求会发送到外部服务器。' },
],
policyLink: '阅读完整隐私政策',
},
pricing: {
index: '04 / 价格',
title: '解锁全部功能.',
subtitle: '免费开始,按需升级。随时取消。',
featureLabel: '功能',
free: 'Free',
pro: 'Pro',
proPlus: 'Pro+',
forever: '永久免费',
monthly: '/月',
annual: '/年',
title: '所有本地功能均免费。',
subtitle: '付费套餐可以让你更频繁地使用云端文本润色,并使用更大的模型。',
perMonth: '/月',
savePercent: '省17%',
billingToggleMonthly: '月付',
billingToggleAnnual: '年付',
popular: '热门',
downloadFree: '免费下载',
getPro: '订阅 Pro',
getProPlus: '订阅 Pro+',
taxNote: '所有价格不含税。通过 Payple 安全支付。',
rows: [
{ feature: '语音听写', free: '15次/天', pro: true, proPlus: true },
{ feature: 'AI文本润色', free: '3次/天', pro: true, proPlus: true },
{ feature: '历史保留', free: '3天', pro: true, proPlus: true },
{ feature: '自定义指令', free: '预设', pro: true, proPlus: true },
{ feature: '实时字幕', free: false, pro: true, proPlus: true },
{ feature: '屏幕上下文', free: false, pro: true, proPlus: true },
{ feature: '语音备忘录 + 标签', free: false, pro: true, proPlus: true },
{ feature: '多LLM链', free: false, pro: true, proPlus: true },
{ feature: '语音命令', free: false, pro: true, proPlus: true },
{ feature: '历史导出', free: false, pro: true, proPlus: true },
{ feature: '文件转录', free: false, pro: false, proPlus: true },
{ feature: '语音对话', free: false, pro: false, proPlus: true },
{ feature: '会议摘要', free: false, pro: false, proPlus: true },
{ feature: '本地 RAG', free: false, pro: false, proPlus: true },
{ feature: 'OS 自动化', free: false, pro: false, proPlus: true },
unlimited: '无限',
note: '价格为韩元月付费用,付款与取消订阅均在网页版账号页面管理。',
free: {
name: 'Free',
desc: '所有在你电脑上运行的功能',
cta: '免费下载',
features: [
'语音输入、字幕、会议记录、文件转录次数不限',
'本地模型润色次数不限',
'云端润色每周 {haiku} 次(需要登录)',
],
},
pro: {
name: 'Pro',
desc: '如果你每天都要用云端润色',
cta: '订阅 Pro',
features: [
'包含 Free 的所有功能',
'云端润色每天:标准模型 {haiku} 次',
'高级模型 {sonnet} 次 · 顶级模型 {opus} 次',
],
},
proPlus: {
name: 'Pro+',
desc: '大模型畅快使用',
cta: '订阅 Pro+',
features: [
'包含 Pro 的所有功能',
'云端润色每天:标准模型 {haiku}',
'高级模型 {sonnet} 次 · 顶级模型 {opus} 次',
],
},
},
download: {
title: '下载',
subtitle: '安装后,新版本会自动更新。',
windowsName: 'Windows',
windowsMeta: 'Windows 10 · 11(64 位)',
cta: '下载 Windows 版',
details: '{date} 发布 · 语音识别模型会在首次运行时下载。',
releaseNotes: '更新日志',
checksum: '文件哈希值(latest.yml)',
soonTitle: '即将推出',
soon: [
{ title: 'macOS (Apple Silicon)', body: '完成签名和安装验证后将发布于此。' },
{ title: 'Android · iOS', body: '完成应用商店审核后将发布于此。' },
],
},
faq: {
index: '05 / 常见问题',
title: '常见问题.',
title: '常见问题',
items: [
{ q: '需要单独安装 Ollama 和 Whisper 吗?', a: 'Whisper(faster-whisper)已内置于应用中,无需单独安装。Ollama 仅用于 AI 润色/翻译功能,可通过应用内引导轻松安装。基础听写无需 Ollama 即可使用。' },
{ q: '需要什么 GPU?', a: '无需 GPU - 仅用 CPU 即可运行。NVIDIA GPU(CUDA)可将转录速度提升 5-10 倍。使用 base 模型时,CPU 实时转录完全可行。' },
{ q: '能完全离线工作吗?', a: '是的。下载 Whisper 和 Ollama 模型后,一切都可以在没有互联网的情况下运行。许可证验证仅在首次激活时需要在线,之后有 30 天的离线宽限期。' },
{ q: '语音识别准确率如何?', a: 'Whisper large-v3 支持 99 种以上语言,准确率极高。自定义词典功能可进一步提升专业术语的识别精度。' },
{ q: '这是订阅制吗?', a: 'Pro 和 Pro+ 是月度或年度订阅。年付可节省约17%。随时可取消。基于本地处理,没有云端成本,但订阅用于持续更新和高级功能。' },
{ q: '支持 macOS 和 Linux 吗?', a: '目前仅支持 Windows。基于 Electron 构建,macOS/Linux 支持在技术上可行,将根据需求推出。' },
{ q: '需要单独安装 Ollama 吗?', a: '语音输入不需要,语音识别引擎已内置在应用中。如果要在电脑上运行文本润色和翻译,需要 Ollama,应用会引导你完成安装。' },
{ q: '需要显卡吗?', a: '不需要,CPU 即可运行。NVIDIA 显卡(CUDA)可以让识别速度更快。' },
{ q: '没有网络也能用吗?', a: '可以。下载好语音识别模型和 Ollama 模型后,语音输入和文本润色都可以离线使用。付费套餐会偶尔联网以确认订阅状态,断网状态下最长可继续使用 30 天。' },
{ q: '可以在哪些应用中使用?', a: '大多数支持输入文字的 Windows 应用都可以使用。文字通过粘贴方式输入,因此禁止粘贴的输入框可能无法使用。' },
{ q: '免费版能用到什么程度?', a: '所有在电脑上运行的功能都完全免费,没有次数限制。付费套餐提升的是云端文本润色的使用额度。' },
{ q: '有 macOS 或移动版吗?', a: '目前仅提供 Windows 版本。macOS(Apple Silicon)版和移动版会在完成相应验证后发布于此页面。' },
],
},
cta: {
title1: '开口说。',
title2: 'AI来写。',
subtitle1: '无云端。高级功能,由你做主。无隐私顾虑。',
subtitle2: '立即开始。',
downloadBtn: '下载 Windows 版',
systemReq: 'Windows 10/11 \u00b7 64位 \u00b7 约200MB \u00b7 几秒即可就绪',
},
footer: {
description: '完全本地的 AI 语音助手。由 Whisper 和 Ollama 驱动。您的声音,您的设备,您的数据。',
copyright: '\u00a9 {year} D3RO Voice. All rights reserved.',
builtWith: '使用 Electron + React + TypeScript 构建',
description: '在你电脑上运行的语音输入。',
privacy: '隐私政策',
terms: '服务条款',
deleteAccount: '删除账号',
releaseNotes: '更新日志',
copyright: '© {year} D3RO Voice',
},
}

View file

@ -2,60 +2,79 @@
@tailwind components;
@tailwind utilities;
/* ── Base Reset & Globals ──────────────────────────────── */
/* ── Base ─────────────────────────────────────────────── */
@layer base {
:root {
--font-sans: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;
--font-display: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", ui-monospace, monospace;
--brand-blue: #3b82f6;
--brand-blue-soft: rgba(255, 255, 255, 0.25);
--brand-glow: rgba(59, 130, 246, 0.7);
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-family: var(--font-sans);
}
::selection {
background-color: rgba(59, 130, 246, 0.35);
color: #ffffff;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #08090c;
}
::-webkit-scrollbar-thumb {
background: #222838;
border-radius: 4px;
border: 2px solid #08090c;
}
::-webkit-scrollbar-thumb:hover {
background: #363f57;
@apply bg-surface-950 font-sans text-neutral-100;
}
body {
overflow-x: hidden;
background-color: #08090c;
color: #f4f4f5;
}
/* Accessibility Focus Rings */
::selection {
@apply bg-brand-blue/35 text-white;
}
:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
@apply outline outline-2 outline-offset-2 outline-brand-blue-light;
}
/* 고정 헤더(64px)가 앵커 대상의 머리를 가리지 않게 한다. */
[id] {
scroll-margin-top: 5rem;
}
/* 의미적 강조는 세미볼드까지 — 볼드(700) 금지 (design.md 타이포 원칙 1) */
strong, b { font-weight: 600; }
[hidden] { display: none !important; }
}
/* ── Components ───────────────────────────────────────── */
@layer components {
.section {
@apply py-20 md:py-28;
}
/* 버튼: 최소 44px 터치 타깃. 흰 글자 대비 4.5:1 이상을 위해 채움색은 blue-dark 계열. */
.btn {
@apply inline-flex min-h-[44px] flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded-xl px-5 py-2 text-center text-base font-medium leading-snug transition-colors duration-150;
}
.btn-sm {
@apply min-h-[40px] px-4 text-sm;
}
.btn-lg {
@apply min-h-[52px] px-7;
}
.btn-primary {
@apply bg-brand-blue-dark text-white hover:bg-brand-blue-deep;
}
.btn-secondary {
@apply border border-white/15 bg-surface-800 text-neutral-100 hover:border-white/30 hover:bg-surface-700;
}
.link {
@apply text-brand-blue-light underline decoration-brand-blue-light/40 underline-offset-4 transition-colors hover:decoration-brand-blue-light;
}
.kbd {
@apply inline-flex min-h-[32px] items-center rounded-md border border-white/15 border-b-white/25 bg-surface-700 px-2.5 font-sans text-sm text-neutral-100;
}
.kbd-accent {
@apply border-brand-blue/50 border-b-brand-blue bg-brand-blue/15 text-brand-blue-light;
}
.skip-link {
@apply fixed left-4 top-3 z-[60] -translate-y-24 rounded-lg bg-surface-800 px-4 py-3 text-sm text-white shadow-chassis focus:translate-y-0;
}
}
/* ── Grid Background Pattern ──────────────────────────── */
/* ── Grid Background Pattern (히어로) ─────────────────── */
.bg-grid {
background-image:
linear-gradient(rgba(59, 130, 246, 0.035) 1px, transparent 1px),
@ -63,24 +82,18 @@
background-size: 64px 64px;
}
.bg-grid-dense {
background-image:
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
background-size: 24px 24px;
}
/* ── CRT Screen Effect (Crisp High-Contrast) ─────────── */
/* ── CRT Screen (히어로 예시 화면의 계기판 재질) ─────── */
.crt-screen {
position: relative;
overflow: hidden;
background: linear-gradient(180deg, #05070a 0%, #0a0e14 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
border: 1px solid rgba(59, 130, 246, 0.25);
box-shadow:
inset 0 2px 10px rgba(0, 0, 0, 0.9),
0 0 32px rgba(59, 130, 246, 0.08);
}
/* 정지된 주사선만 남긴다. 움직이는 스캔 효과는 읽기를 방해해 제거했다. */
.crt-screen::before {
content: '';
position: absolute;
@ -89,172 +102,67 @@
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.2) 2px,
rgba(0, 0, 0, 0.2) 4px
rgba(0, 0, 0, 0.18) 2px,
rgba(0, 0, 0, 0.18) 4px
);
pointer-events: none;
z-index: 2;
}
.crt-screen::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(59, 130, 246, 0.03) 0%,
transparent 50%,
rgba(59, 130, 246, 0.015) 100%
);
animation: scan 8s linear infinite;
pointer-events: none;
z-index: 3;
}
@keyframes scan {
0% { transform: translateY(-100%); }
100% { transform: translateY(100%); }
}
/* ── Crosshair Decorations ─────────────────────────────── */
.crosshair-box {
--ch-size: 16px;
--ch-color: rgba(59, 130, 246, 0.4);
--ch-color: rgba(59, 130, 246, 0.45);
position: relative;
}
.crosshair-box::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
width: var(--ch-size);
height: var(--ch-size);
border-top: 1.5px solid var(--ch-color);
border-left: 1.5px solid var(--ch-color);
pointer-events: none;
}
.crosshair-box::before,
.crosshair-box::after {
content: '';
position: absolute;
bottom: -1px;
right: -1px;
width: var(--ch-size);
height: var(--ch-size);
pointer-events: none;
}
.crosshair-box::before {
top: -6px;
left: -6px;
border-top: 1.5px solid var(--ch-color);
border-left: 1.5px solid var(--ch-color);
}
.crosshair-box::after {
bottom: -6px;
right: -6px;
border-bottom: 1.5px solid var(--ch-color);
border-right: 1.5px solid var(--ch-color);
pointer-events: none;
}
/* ── Instrument Noise Texture ──────────────────────────── */
.noise-texture {
position: relative;
}
.noise-texture::before {
content: '';
position: absolute;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 256px 256px;
pointer-events: none;
border-radius: inherit;
}
/* ── Glass Surfaces & Double Bezel ─────────────────────── */
.glass-panel {
background: rgba(19, 22, 31, 0.75);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 12px 32px -4px rgba(0, 0, 0, 0.5);
}
.glass-panel-hover {
transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
.glass-panel-hover:hover {
border-color: rgba(59, 130, 246, 0.35);
background: rgba(26, 30, 43, 0.85);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 0 20px rgba(59, 130, 246, 0.15);
}
/* ── Glow Button ───────────────────────────────────────── */
.glow-btn {
position: relative;
overflow: hidden;
transition: all 250ms cubic-bezier(0.16, 1, 0.3, 1);
}
.glow-btn::before {
content: '';
position: absolute;
inset: -2px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.45), rgba(59, 130, 246, 0) 65%);
border-radius: inherit;
opacity: 0;
transition: opacity 250ms;
z-index: 0;
}
.glow-btn:hover::before {
opacity: 1;
}
.glow-btn:hover {
box-shadow: 0 0 24px rgba(59, 130, 246, 0.35), 0 0 48px rgba(59, 130, 246, 0.12);
transform: translateY(-1px);
}
.glow-btn:active {
transform: translateY(0) scale(0.98);
}
/* ── Accordion ─────────────────────────────────────────── */
.accordion-content {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 250ms cubic-bezier(0.16, 1, 0.3, 1), opacity 200ms ease;
overflow: hidden;
opacity: 0;
visibility: hidden;
}
.accordion-content.open {
grid-template-rows: 1fr;
opacity: 1;
visibility: visible;
}
.accordion-content > div {
overflow: hidden;
min-height: 0;
}
/* ── Reveal Animation ──────────────────────────────────── */
.reveal {
opacity: 0;
transform: translateY(16px);
}
.reveal.visible {
animation: fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes fade-in-up {
0% { opacity: 0; transform: translateY(16px); }
100% { opacity: 1; transform: translateY(0); }
}
/* ── Utilities ─────────────────────────────────────────── */
@layer base {
/* 의미적 강조는 세미볼드까지 — 볼드(700) 헤드라인 금지 (한글 과중 방지) */
strong, b { font-weight: 600; }
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.break-keep {
word-break: keep-all;
/* 한글 어절 단위 줄바꿈. 중국어·일본어에 keep-all을 걸면 줄바꿈 기회가 사라지므로 한국어에만 적용한다. */
.keep-ko {
overflow-wrap: break-word;
}
.border-hairline {
border-color: rgba(255, 255, 255, 0.08);
.keep-ko:lang(ko) {
word-break: keep-all;
}
}
/* ── Reduced motion ───────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

28
site/src/pricing.ts Normal file
View file

@ -0,0 +1,28 @@
// site/src/pricing.ts
// 요금제 표시 SSOT. 결제 금액의 정본은 서버 결제 카탈로그
// (server/supabase/functions/_shared/billing-catalog.ts, payple.ts)이며,
// 여기 값은 그 카탈로그와 같아야 한다. 사용량 한도는
// packages/core/src/constants.ts의 PREMIUM_MODEL_LIMITS와 같은 값이다.
export type PaidTier = 'pro' | 'pro_plus'
/** 월 요금(원). */
export const PLAN_PRICE_KRW = {
free: 0,
pro: 2900,
proPlus: 8900,
} as const
/** 클라우드 다듬기 사용량. -1은 무제한. Free는 주간, 유료는 일간. */
export const PLAN_CLOUD_QUOTA = {
free: { haiku: 250 },
pro: { haiku: 1500, sonnet: 300, opus: 50 },
proPlus: { haiku: -1, sonnet: 1500, opus: 300 },
} as const
/** 데스크톱 앱 결제 완료 URL과 같은 웹 결제 페이지. */
const BILLING_URL = 'https://d3ro.chanpaca.net/billing'
export function billingUrl(tier: PaidTier): string {
return `${BILLING_URL}?tier=${tier}`
}

View file

@ -1,77 +0,0 @@
import { Container } from '../components/Container'
import { Crosshair } from '../components/Crosshair'
import { WaveBars } from '../components/WaveBars'
import { GlowButton } from '../components/GlowButton'
import { useI18n } from '../i18n'
import { useClientOS } from '../hooks/useClientOS'
export function CTA() {
const { t, locale } = useI18n()
const clientOS = useClientOS()
return (
<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-[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-7">
<WaveBars className="h-8 px-4" />
</Crosshair>
<h2
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-blue font-medium">{t.cta.title2}</span>
</h2>
<p
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}
<br />
{t.cta.subtitle2}
</p>
{/* Hotkey Hint Box */}
<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.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.5">
<GlowButton
href={clientOS.downloadUrl}
download={clientOS.downloadFilename || undefined}
variant="primary"
size="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 text-xs text-neutral-400 font-normal">
<span>{locale === 'ko' ? clientOS.subtextKo : clientOS.subtextEn}</span>
<span>·</span>
<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="text-xs text-neutral-400 mt-9 font-normal">
{t.cta.systemReq} · 100% 오픈 아키텍처
</p>
</Container>
</section>
)
}

View file

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

View file

@ -1,6 +1,7 @@
import { useState } from 'react'
import { SectionHeader } from '../components/SectionHeader'
import { Container } from '../components/Container'
import { SectionHeading } from '../components/SectionHeading'
import { PlusIcon } from '../components/icons'
import { useI18n } from '../i18n'
export function FAQ() {
@ -8,64 +9,32 @@ export function FAQ() {
const [openIndex, setOpenIndex] = useState<number | null>(null)
return (
<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" />
<section id="faq" aria-labelledby="faq-title" className="section border-t border-white/10 bg-surface-900/60">
<Container className="grid grid-cols-1 gap-12 lg:grid-cols-12 lg:gap-10">
<SectionHeading id="faq-title" title={t.faq.title} className="lg:col-span-4" />
<Container className="relative max-w-4xl">
<SectionHeader
index={t.faq.index}
title={t.faq.title}
/>
<div className="space-y-3.5">
{t.faq.items.map((faq, i) => {
const isOpen = openIndex === i
<div className="border-b border-white/10 lg:col-span-8">
{t.faq.items.map((item, i) => {
const open = openIndex === i
const buttonId = `faq-q-${i}`
const panelId = `faq-a-${i}`
return (
<div
key={i}
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 p-5 sm:p-6 text-left group"
>
<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-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>
<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 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 key={item.q} className="border-t border-white/10">
<h3>
<button
id={buttonId}
type="button"
aria-expanded={open}
aria-controls={panelId}
onClick={() => setOpenIndex(open ? null : i)}
className="flex w-full items-start justify-between gap-6 py-5 text-left text-lg text-neutral-50 transition-colors hover:text-brand-blue-light"
>
<span className="keep-ko">{item.q}</span>
<PlusIcon className={`mt-1.5 h-4 w-4 flex-shrink-0 text-neutral-400 transition-transform ${open ? 'rotate-45' : ''}`} />
</button>
</h3>
<div id={panelId} role="region" aria-labelledby={buttonId} hidden={!open}>
<p className="max-w-2xl keep-ko pb-6 text-base leading-relaxed text-neutral-300">{item.a}</p>
</div>
</div>
)

View file

@ -1,247 +1,29 @@
import type { ReactNode } from 'react'
import { SectionHeader } from '../components/SectionHeader'
import { Container } from '../components/Container'
import { SectionHeading } from '../components/SectionHeading'
import { useI18n } from '../i18n'
interface FeatureTile {
id: string
span: 'col-span-1' | 'col-span-1 md:col-span-2'
tag: 'FREE' | 'PRO' | 'PRO+'
title: string
description: string
highlight?: string
icon: ReactNode
previewNode?: ReactNode
}
const tagStyles = {
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() {
const { t, locale } = useI18n()
const bentoTiles: FeatureTile[] = [
{
id: 'hud',
span: 'col-span-1 md:col-span-2',
tag: 'FREE',
title: locale === 'ko' ? '전역 Push-to-Talk 음성 캡슐 HUD' : 'Global Push-to-Talk Capsule HUD',
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: '글로벌 핫키 & 플로팅 오버레이',
icon: <MicIcon />,
previewNode: (
<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-xs font-medium text-neutral-300 ml-2">0:04</span>
</div>
<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>
),
},
{
id: 'privacy',
span: 'col-span-1',
tag: 'FREE',
title: locale === 'ko' ? '100% 로컬 보안 실드' : '100% On-Device Local Privacy',
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: '클라우드 유출 0바이트',
icon: <ShieldIcon />,
},
{
id: 'meeting',
span: 'col-span-1',
tag: 'PRO',
title: locale === 'ko' ? '회의 스튜디오 & 화자 분리' : 'Meeting Studio & Diarization',
description: locale === 'ko'
? '장시간 회의 녹음, 화자 자동 분리, 실시간 스크래치패드 및 마크다운 회의록(액션 아이템, 결정 사항) 자동 생성.'
: 'Long-form meeting recording with speaker diarization, real-time memo scratchpad, and automatic structured Markdown summary generation.',
highlight: '자동 마크다운 회의록',
icon: <UsersIcon />,
},
{
id: 'chains',
span: 'col-span-1 md:col-span-2',
tag: 'PRO',
title: locale === 'ko' ? '다단계 AI 파이프라인 체이닝 & 매크로' : 'Multi-Step LLM Pipeline Chaining',
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: '맞춤형 프롬프트 & 톤 변환',
icon: <ChainIcon />,
previewNode: (
<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-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-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-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>
),
},
{
id: 'rag',
span: 'col-span-1',
tag: 'PRO+',
title: locale === 'ko' ? '로컬 문서 지식베이스 (RAG)' : 'Local Document Intelligence (RAG)',
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 로컬 임베딩',
icon: <DatabaseIcon />,
},
{
id: 'performance',
span: 'col-span-1',
tag: 'FREE',
title: locale === 'ko' ? '초저지연 고성능 아키텍처' : 'Sub-Second Local Latency',
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.2초 미만 실시간 응답',
icon: <ZapIcon />,
},
]
const { t } = useI18n()
return (
<section id="features" className="relative py-20 md:py-28">
<Container>
<SectionHeader
index={t.features.index}
<section id="features" aria-labelledby="features-title" className="section border-t border-white/10">
<Container className="grid grid-cols-1 gap-12 lg:grid-cols-12 lg:gap-10">
<SectionHeading
id="features-title"
title={t.features.title}
subtitle={t.features.subtitle}
className="lg:sticky lg:top-28 lg:col-span-4 lg:self-start"
/>
{/* Asymmetric Bento Grid */}
<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-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-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-xs font-medium px-2.5 py-0.5 border rounded-full shadow-sm ${tagStyles[tile.tag]}`}>
{tile.tag}
</span>
</div>
{/* Content */}
<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-300 leading-relaxed font-normal">
{tile.description}
</p>
{/* Optional interactive preview */}
{tile.previewNode}
</div>
{/* Bottom tag / detail */}
{tile.highlight && (
<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 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" />
</svg>
</div>
</div>
)}
</div>
<ul className="grid grid-cols-1 gap-x-10 sm:grid-cols-2 lg:col-span-8">
{t.features.items.map((item) => (
<li key={item.title} className="border-t border-white/10 py-7">
<h3 className="keep-ko text-h3 font-medium text-neutral-50">{item.title}</h3>
<p className="mt-2.5 keep-ko text-base leading-relaxed text-neutral-300">{item.body}</p>
</li>
))}
</div>
</ul>
</Container>
</section>
)
}
function MicIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<rect x="9" y="2" width="6" height="11" rx="3" />
<path d="M5 10a7 7 0 0 0 14 0" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
)
}
function ShieldIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
<path d="M9 12l2 2 4-4" />
</svg>
)
}
function UsersIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
)
}
function ChainIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
)
}
function DatabaseIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
)
}
function ZapIcon() {
return (
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
)
}

View file

@ -1,80 +1,47 @@
import { Container } from '../components/Container'
import { Led } from '../components/Led'
import { useI18n } from '../i18n'
const techStack = [
'Electron', 'React 19', 'faster-whisper', 'Ollama',
'better-sqlite3', 'TypeScript', 'uiohook-napi',
]
import { Wordmark } from '../components/Wordmark'
import { fmt, useI18n } from '../i18n'
import { DESKTOP_RELEASE_HUB_URL } from '../release'
export function Footer() {
const { t } = useI18n()
const links = [
{ href: './privacy/', label: t.footer.privacy },
{ href: './terms/', label: t.footer.terms },
{ href: './delete-account/', label: t.footer.deleteAccount },
]
return (
<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-10">
{/* Left: brand */}
<div>
<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-400 leading-relaxed max-w-sm font-normal">
{t.footer.description}
</p>
</div>
{/* Right: links */}
<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 />
Repository
</a>
<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="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium">
{t.nav.pricing}
</a>
<a href="#faq" className="text-xs text-neutral-400 hover:text-brand-blue transition-colors font-medium">
{t.nav.faq}
</a>
</div>
<footer className="border-t border-white/10 py-12">
<Container className="flex flex-col gap-8 md:flex-row md:items-start md:justify-between">
<div>
<Wordmark />
<p className="mt-3 text-sm text-neutral-400">{t.footer.description}</p>
<p className="mt-6 text-sm text-neutral-400">{fmt(t.footer.copyright, { year: new Date().getFullYear() })}</p>
</div>
{/* Tech stack bar */}
<div className="flex flex-wrap items-center gap-2 mb-8">
{techStack.map((tech) => (
<span
key={tech}
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>
<ul className="flex flex-wrap gap-x-6 gap-y-1 text-sm">
{links.map((link) => (
<li key={link.href}>
<a href={link.href} className="inline-block py-2 text-neutral-300 transition-colors hover:text-white">
{link.label}
</a>
</li>
))}
</div>
{/* Bottom bar */}
<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>
{t.footer.builtWith}
</span>
</div>
<li>
<a
href={DESKTOP_RELEASE_HUB_URL}
target="_blank"
rel="noopener"
className="inline-block py-2 text-neutral-300 transition-colors hover:text-white"
>
{t.footer.releaseNotes}
<span className="sr-only"> ({t.a11y.opensInNewTab})</span>
</a>
</li>
</ul>
</Container>
</footer>
)
}
function GithubIcon() {
return (
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
)
}

View file

@ -1,114 +1,113 @@
import { useState, useEffect } from 'react'
import { Led } from '../components/Led'
import { useState, useEffect, useRef } from 'react'
import { LanguageSwitcher } from '../components/LanguageSwitcher'
import { Wordmark } from '../components/Wordmark'
import { navItems } from '../tokens'
import { useI18n } from '../i18n'
export function Header() {
const { t } = useI18n()
const [scrolled, setScrolled] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
const [menuOpen, setMenuOpen] = useState(false)
const toggleRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 20)
const onScroll = () => setScrolled(window.scrollY > 8)
onScroll()
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
// 열린 메뉴: Esc로 닫고 토글 버튼으로 포커스를 돌린다. 넓은 화면으로 바뀌면 닫는다.
useEffect(() => {
if (!menuOpen) return
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setMenuOpen(false)
toggleRef.current?.focus()
}
}
const wide = window.matchMedia('(min-width: 768px)')
const onWide = () => { if (wide.matches) setMenuOpen(false) }
document.addEventListener('keydown', onKey)
wide.addEventListener('change', onWide)
return () => {
document.removeEventListener('keydown', onKey)
wide.removeEventListener('change', onWide)
}
}, [menuOpen])
const solid = scrolled || menuOpen
return (
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled
? 'bg-surface-950/90 backdrop-blur-2xl border-b border-white/10 shadow-2xl py-3'
: 'bg-transparent py-4'
className={`fixed inset-x-0 top-0 z-50 border-b transition-colors duration-200 ${
solid ? 'bg-surface-950/95 backdrop-blur-xl border-white/10' : 'bg-transparent border-transparent'
}`}
>
<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.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>
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-6 px-5 md:px-8 lg:px-12">
<a href="#top" className="-mx-1 rounded px-1 py-2">
<Wordmark />
</a>
{/* Desktop Nav */}
<nav className="hidden md:flex items-center gap-8">
{navItems.map((link) => (
<a
key={link.href}
href={link.href}
className="text-sm font-medium text-neutral-300 hover:text-white transition-colors"
>
{t.nav[link.key]}
</a>
))}
<nav aria-label={t.a11y.primaryNav} className="hidden md:block">
<ul className="flex items-center gap-7">
{navItems.map((item) => (
<li key={item.key}>
<a href={item.href} className="py-2 text-sm text-neutral-300 transition-colors hover:text-white">
{t.nav[item.key]}
</a>
</li>
))}
</ul>
</nav>
{/* Right side: language + CTA */}
<div className="hidden md:flex items-center gap-3.5">
<div className="hidden items-center gap-3 md:flex">
<LanguageSwitcher />
<a
href="#download"
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 />
<a href="#download" className="btn btn-primary btn-sm">
{t.nav.download}
</a>
</div>
{/* Mobile menu button */}
<button
ref={toggleRef}
type="button"
onClick={() => setMobileOpen(!mobileOpen)}
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"
onClick={() => setMenuOpen((open) => !open)}
aria-expanded={menuOpen}
aria-controls="mobile-menu"
aria-label={menuOpen ? t.a11y.closeMenu : t.a11y.openMenu}
className="flex h-11 w-11 items-center justify-center rounded-lg border border-white/10 bg-surface-800 text-neutral-100 md:hidden"
>
<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>
<span aria-hidden="true" className="relative block h-3.5 w-5">
<span className={`absolute left-0 block h-0.5 w-5 bg-current transition-transform ${menuOpen ? 'top-1.5 rotate-45' : 'top-0'}`} />
<span className={`absolute left-0 top-1.5 block h-0.5 w-5 bg-current transition-opacity ${menuOpen ? 'opacity-0' : ''}`} />
<span className={`absolute left-0 block h-0.5 w-5 bg-current transition-transform ${menuOpen ? 'top-1.5 -rotate-45' : 'top-3'}`} />
</span>
</button>
</div>
{/* Mobile Nav */}
{mobileOpen && (
<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 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 pt-4 gap-3">
<LanguageSwitcher />
<a
href="#download"
onClick={() => setMobileOpen(false)}
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>
</div>
<div id="mobile-menu" hidden={!menuOpen} className="border-t border-white/10 bg-surface-950 px-5 pb-6 md:hidden">
<nav aria-label={t.a11y.primaryNav}>
<ul>
{navItems.map((item) => (
<li key={item.key} className="border-b border-white/[0.06]">
<a
href={item.href}
onClick={() => setMenuOpen(false)}
className="block py-3.5 text-base text-neutral-100"
>
{t.nav[item.key]}
</a>
</li>
))}
</ul>
</nav>
<div className="flex items-center gap-3 pt-5">
<LanguageSwitcher />
<a href="#download" onClick={() => setMenuOpen(false)} className="btn btn-primary flex-1">
{t.nav.download}
</a>
</div>
)}
</div>
</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

@ -1,341 +1,56 @@
import { useState, useEffect, useRef } from 'react'
import { Badge } from '../components/Badge'
import { DataPoint } from '../components/DataPoint'
import { Container } from '../components/Container'
import { Led } from '../components/Led'
import { DictationDemo } from '../components/DictationDemo'
import { DownloadIcon } from '../components/icons'
import { useI18n } from '../i18n'
import { useClientOS } from '../hooks/useClientOS'
import { DESKTOP_VERSION, DESKTOP_WINDOWS_INSTALLER_URL } from '../release'
export function Hero() {
const { t, locale } = useI18n()
const clientOS = useClientOS()
// Interactive Live Simulator State
const [simState, setSimState] = useState<'idle' | 'recording' | 'processing' | 'done'>('idle')
const [typedRaw, setTypedRaw] = useState('')
const [typedClean, setTypedClean] = useState('')
const [waveLevels, setWaveLevels] = useState([0.3, 0.5, 0.8, 1, 0.9, 0.7, 0.4, 0.6, 0.3])
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const sampleRawText = locale === 'ko'
? '어... 이번 프로젝트 배포는 다음 주 금요일까지로 잡으면 될 것 같아요.'
: 'Um... I think we can schedule the release for next Friday.'
const sampleCleanText = locale === 'ko'
? '이번 프로젝트 배포 일정은 다음 주 금요일로 확정하겠습니다.'
: '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
const interval = setInterval(() => {
setWaveLevels(Array.from({ length: 9 }, () => 0.2 + Math.random() * 0.8))
}, 90)
return () => clearInterval(interval)
}, [simState])
const startSimulation = () => {
if (simState !== 'idle' && simState !== 'done') return
setSimState('recording')
setTypedRaw('')
setTypedClean('')
let charIdx = 0
const rawInterval = setInterval(() => {
if (charIdx < sampleRawText.length) {
setTypedRaw(sampleRawText.slice(0, charIdx + 1))
charIdx++
} else {
clearInterval(rawInterval)
setSimState('processing')
timerRef.current = setTimeout(() => {
setSimState('done')
let cleanIdx = 0
const cleanInterval = setInterval(() => {
if (cleanIdx < sampleCleanText.length) {
setTypedClean(sampleCleanText.slice(0, cleanIdx + 1))
cleanIdx++
} else {
clearInterval(cleanInterval)
}
}, 25)
}, 600)
}
}, 45)
}
const resetSimulation = () => {
if (timerRef.current) clearTimeout(timerRef.current)
setSimState('idle')
setTypedRaw('')
setTypedClean('')
}
const { t } = useI18n()
const os = useClientOS()
return (
<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-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>
<section aria-labelledby="hero-title" className="relative overflow-hidden bg-grid pb-20 pt-32 md:pb-28 md:pt-40">
<Container className="relative grid grid-cols-1 items-center gap-14 lg:grid-cols-12 lg:gap-10">
<div className="lg:col-span-7">
<h1 id="hero-title" className="keep-ko text-balance text-display font-medium text-neutral-50">
<span className="block">{t.hero.title1}</span>
<span className="block text-brand-blue-light">{t.hero.title2}</span>
</h1>
<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-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 */}
<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="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="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>
<p className="mt-6 max-w-xl keep-ko text-lg leading-relaxed text-neutral-300">
{t.hero.subtitle}
</p>
{/* Main Headline */}
<h1
className="text-[1.875rem] sm:text-[2.25rem] 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-blue font-medium">{t.hero.title2}</span>
</h1>
<p className="mt-7 flex flex-wrap items-center gap-3 text-sm text-neutral-300">
<kbd className="kbd kbd-accent">{t.hero.hotkeyKey}</kbd>
<span>{t.hero.hotkeyAction}</span>
</p>
<p
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}
<div className="mt-9 flex flex-col gap-3 sm:flex-row sm:items-center">
<a href={DESKTOP_WINDOWS_INSTALLER_URL} className="btn btn-primary btn-lg">
<DownloadIcon />
<span>{t.download.cta}</span>
<span className="font-mono text-sm font-normal text-white">v{DESKTOP_VERSION}</span>
</a>
<a href="#how" className="btn btn-secondary btn-lg">
{t.hero.secondaryCta}
</a>
</div>
{os !== 'windows' && (
<p className="mt-4 max-w-md keep-ko border-l-2 border-brand-blue pl-3 text-sm text-neutral-200">
{t.hero.windowsOnly}
</p>
)}
{/* Hotkey Callout */}
<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 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-3.5 mb-4">
<a
href={clientOS.downloadUrl}
download={clientOS.downloadFilename || undefined}
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">
{clientOS.os === 'android' ? (
<AndroidIcon />
) : clientOS.os === 'ios' || clientOS.os === 'macos' ? (
<AppleIcon />
) : (
<WindowsIcon />
)}
{locale === 'ko' ? clientOS.buttonLabelKo : clientOS.buttonLabelEn}
</span>
</a>
<a
href="#features"
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 />
</a>
</div>
{/* Platform Subtext & Other OS Navigator */}
<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 font-normal"
>
{locale === 'ko' ? '플랫폼별 릴리스 준비 상태 →' : 'Platform release readiness →'}
</a>
</div>
{/* Friction-Reduction Trust Microcopy */}
<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>
</div>
{/* Right Column: Live Interactive Voice Capsule (5 cols) */}
<div className="lg:col-span-5">
<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-4 pb-3 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<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="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-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) => (
<div
key={idx}
className="w-1.5 rounded-full transition-all duration-75"
style={{
height: `${Math.max(6, lvl * 32)}px`,
backgroundColor: simState === 'recording' ? 'var(--brand-blue)' : 'var(--brand-blue-soft)',
boxShadow: simState === 'recording' ? '0 0 10px var(--brand-glow)' : 'none',
}}
/>
))}
</div>
{/* Trigger Button */}
<button
type="button"
onClick={simState === 'idle' || simState === 'done' ? startSimulation : resetSimulation}
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-blue text-white hover:bg-brand-blue-light shadow-glow-sm active:scale-[0.98]'
}`}
>
<MicSmallIcon />
<span>{simState === 'recording' ? (locale === 'ko' ? '녹음 중지' : 'Stop') : simState === 'processing' ? (locale === 'ko' ? '처리 중...' : 'Polishing...') : (locale === 'ko' ? '데모 시작' : 'Try Demo')}</span>
</button>
</div>
{/* 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' && (
<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-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-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>
</div>
)}
</div>
{/* 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-[11px] text-neutral-500">STT 엔진</span>
<span className="font-mono text-neutral-300">faster-whisper</span>
</div>
<div>
<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-[11px] text-neutral-500">클라우드 유출</span>
<span className="font-mono text-emerald-400 font-medium">0.00 KB</span>
</div>
</div>
</div>
</div>
</div>
<p className="mt-5 keep-ko text-sm text-neutral-400">{t.hero.trust}</p>
</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 className="lg:col-span-5">
<DictationDemo />
</div>
</Container>
</section>
)
}
function WindowsIcon() {
return (
<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" 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 AndroidIcon() {
return (
<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 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" />
<path d="M5 10a7 7 0 0 0 14 0" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
)
}
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

@ -1,184 +1,27 @@
import { useState } from 'react'
import { SectionHeader } from '../components/SectionHeader'
import { Crosshair } from '../components/Crosshair'
import { Container } from '../components/Container'
import { Led } from '../components/Led'
import { WaveBars } from '../components/WaveBars'
import { SectionHeading } from '../components/SectionHeading'
import { useI18n } from '../i18n'
interface ScenarioPreset {
id: string
title: string
tag: string
raw: string
clean: string
latency: string
model: string
}
export function HowItWorks() {
const { t, locale } = useI18n()
const [activePreset, setActivePreset] = useState(0)
const presets: ScenarioPreset[] = [
{
id: 'business',
title: locale === 'ko' ? '비즈니스 회의 요약 및 정제' : 'Business Polish',
tag: 'LLM POLISH',
raw: locale === 'ko'
? '어... 그 다음 주 화요일 회의에서 마케팅 예산 편성안을 검토해보도록 합시다.'
: 'Um... let us review the marketing budget allocation plan in next Tuesday\'s meeting.',
clean: locale === 'ko'
? '다음 주 화요일 회의 안건으로 마케팅 예산 편성안을 검토하겠습니다.'
: 'Action: Review marketing budget allocation plan during Tuesday\'s meeting.',
latency: '0.78s',
model: 'gemma4:e4b (Local)',
},
{
id: 'developer',
title: locale === 'ko' ? '개발자 Git 커밋 메시지' : 'Developer Git Commit',
tag: 'MACRO CHAIN',
raw: locale === 'ko'
? '로그인할 때 리프레시 토큰 만료 에러 핸들링 추가했어'
: 'Add refresh token expiration error handling when logging in',
clean: 'fix(auth): handle refresh token expiration error on user login',
latency: '0.62s',
model: 'qwen2.5-coder:7b',
},
{
id: 'translate',
title: locale === 'ko' ? '실시간 다국어 비즈니스 번역' : 'Real-time Translation',
tag: 'TRANSLATION',
raw: locale === 'ko'
? '이번 분기 실적 보고서를 오늘 퇴근 전까지 공유해주세요.'
: 'Please share this quarter\'s performance report before the end of the day.',
clean: locale === 'ko'
? 'Please share this quarter\'s performance report before the end of the day.'
: '이번 분기 실적 보고서를 오늘 퇴근 전까지 전달 부탁드립니다.',
latency: '0.91s',
model: 'faster-whisper + Ollama',
},
]
const current = presets[activePreset]
const { t } = useI18n()
return (
<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" />
<section id="how" aria-labelledby="how-title" className="section border-t border-white/10 bg-surface-900/60">
<Container>
<SectionHeading id="how-title" title={t.how.title} subtitle={t.how.subtitle} />
<Container className="relative">
<SectionHeader
index={t.pipeline.index}
title={t.pipeline.title}
subtitle={t.pipeline.subtitle}
/>
{/* Preset Selector Bar */}
<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.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-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-2 h-2 rounded-full ${activePreset === idx ? 'bg-white' : 'bg-brand-blue'}`} />
{p.title}
</button>
{/* 순서가 곧 정보이므로 번호 목록을 쓴다. */}
<ol className="mt-14 grid grid-cols-1 gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10 sm:grid-cols-2 lg:grid-cols-4">
{t.how.steps.map((step, i) => (
<li key={step.title} className="bg-surface-900 p-6 md:p-7">
<span aria-hidden="true" className="font-mono text-sm text-brand-blue-light">
{String(i + 1).padStart(2, '0')}
</span>
<h3 className="mt-4 keep-ko text-h3 font-medium text-neutral-50">{step.title}</h3>
<p className="mt-2.5 keep-ko text-base leading-relaxed text-neutral-300">{step.body}</p>
</li>
))}
</div>
<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-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="text-xs font-medium text-brand-blue">
{current.tag} 파이프라인
</span>
<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="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-3.5">
{/* Simulated live pipeline flow */}
<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 font-medium text-xs">{t.pipeline.sttReady}</span>
</div>
<WaveBars className="h-4" />
</div>
<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-neutral-300 font-normal leading-normal italic text-xs">
"{current.raw}"
</p>
</div>
<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-white font-medium leading-relaxed text-sm">
"{current.clean}"
</p>
</div>
</div>
<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: 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 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"
>
<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>
<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-300 leading-relaxed font-normal">
{step.description}
</p>
<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>
</div>
))}
</div>
</div>
</ol>
</Container>
</section>
)

View file

@ -1,319 +1,84 @@
import { useState } from 'react'
import { SectionHeader } from '../components/SectionHeader'
import { Container } from '../components/Container'
import { GlowButton } from '../components/GlowButton'
import { useI18n } from '../i18n'
import { SectionHeading } from '../components/SectionHeading'
import { CheckIcon, ExternalIcon } from '../components/icons'
import { fmt, useI18n, type PlanCopy } from '../i18n'
import { PLAN_CLOUD_QUOTA, PLAN_PRICE_KRW, billingUrl } from '../pricing'
function CellValue({ value }: { value: string | boolean }) {
if (value === true) {
return (
<span className="flex items-center justify-center">
<span className="w-2.5 h-2.5 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.9)]" />
</span>
)
}
if (value === false) {
return (
<span className="flex items-center justify-center">
<span className="w-2 h-2 rounded-full bg-neutral-700" />
</span>
)
}
return <span className="text-xs font-medium text-neutral-200">{value}</span>
}
const PRICES = {
usd: {
pro: { monthly: 9.9, annual: 99 },
proPlus: { monthly: 19.9, annual: 199 },
team: { monthly: 25, annual: 240 },
},
krw: {
pro: { monthly: 12900, annual: 119000 },
proPlus: { monthly: 24900, annual: 229000 },
team: { monthly: 32000, annual: 299000 },
}
interface PlanView {
key: 'free' | 'pro' | 'proPlus'
copy: PlanCopy
price: number
quota: Record<string, number>
href: string
external: boolean
emphasized: boolean
}
export function Pricing() {
const { t, locale } = useI18n()
const [isAnnual, setIsAnnual] = useState(true)
const [currency, setCurrency] = useState<'usd' | 'krw'>(locale === 'ko' ? 'krw' : 'usd')
const { t, bcp47 } = useI18n()
const currencySymbol = currency === 'krw' ? '₩' : '$'
const activePrices = PRICES[currency]
const money = new Intl.NumberFormat(bcp47, { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 })
const count = new Intl.NumberFormat(bcp47)
const quotaValue = (n: number) => (n < 0 ? t.pricing.unlimited : count.format(n))
const formatPrice = (val: number) => {
if (currency === 'krw') {
return val.toLocaleString()
}
return val.toString()
}
const proPrice = isAnnual ? activePrices.pro.annual : activePrices.pro.monthly
const proPlusPrice = isAnnual ? activePrices.proPlus.annual : activePrices.proPlus.monthly
const teamPrice = isAnnual ? activePrices.team.annual : activePrices.team.monthly
const periodLabel = isAnnual ? t.pricing.annual : t.pricing.monthly
const tiers = [
{
id: 'free',
name: t.pricing.free,
price: `${currencySymbol}0`,
period: t.pricing.forever,
desc: locale === 'ko' ? '100% 온디바이스 무제한 로컬 음성 인식' : '100% on-device unlimited private dictation',
popular: false,
buttonText: t.pricing.downloadFree,
buttonVariant: 'secondary' as const,
features: [
locale === 'ko' ? '무제한 on-device faster-whisper STT' : 'Unlimited faster-whisper local STT',
locale === 'ko' ? '전역 Push-to-Talk HUD 캡슐' : 'Global Push-to-Talk capsule HUD',
locale === 'ko' ? '로컬 Ollama LLM 무제한 연동' : 'Unlimited local Ollama LLM connect',
locale === 'ko' ? '100% 온디바이스 제로 텔레메트리' : '100% On-device zero cloud leak',
],
},
{
id: 'pro',
name: t.pricing.pro,
price: `${currencySymbol}${formatPrice(proPrice)}`,
period: periodLabel,
subPrice: isAnnual ? `${currencySymbol}${formatPrice(Math.round(activePrices.pro.annual / 12))}${t.pricing.perMonth}` : null,
desc: locale === 'ko' ? '회의 스튜디오 & 하이브리드 고속 AI' : 'Meeting studio & hybrid fast AI acceleration',
popular: true,
buttonText: t.pricing.getPro,
buttonVariant: 'primary' as const,
features: [
locale === 'ko' ? 'Free 티어의 모든 기능 포함' : 'Includes all Free features',
locale === 'ko' ? '회의 스튜디오 & 실시간 화자 분리' : 'Meeting Studio & Speaker Diarization',
locale === 'ko' ? '마크다운 회의록 & 액션 아이템 자동 생성' : 'Auto Markdown Minutes & Action Items',
locale === 'ko' ? '다단계 AI 파이프라인 체이닝 & 매크로' : 'Multi-step Prompt Chaining & Macros',
locale === 'ko' ? '클라우드 LLM 하이브리드 초고속 다듬기' : 'Hybrid Cloud LLM Acceleration',
],
},
{
id: 'proPlus',
name: t.pricing.proPlus,
price: `${currencySymbol}${formatPrice(proPlusPrice)}`,
period: periodLabel,
subPrice: isAnnual ? `${currencySymbol}${formatPrice(Math.round(activePrices.proPlus.annual / 12))}${t.pricing.perMonth}` : null,
desc: locale === 'ko' ? '초저지연 클라우드 STT & 개인 문서 RAG' : 'Ultra-low latency STT & private vector RAG',
popular: false,
buttonText: t.pricing.getProPlus,
buttonVariant: 'secondary' as const,
features: [
locale === 'ko' ? 'Pro 티어의 모든 기능 포함' : 'Includes all Pro features',
locale === 'ko' ? '초저지연 Cloud STT (Deepgram Nova-3)' : 'Ultra-Low Latency Cloud STT',
locale === 'ko' ? '로컬 문서 지식베이스 (sqlite-vec RAG)' : 'Private Document Knowledge Base (RAG)',
locale === 'ko' ? '실시간 음성 양방향 대화 캔버스' : 'Real-time Voice Duplex Canvas',
locale === 'ko' ? '우선 기술 지원 및 신기능 얼리액세스' : 'Priority Tech Support & Early Access',
],
},
{
id: 'team',
name: locale === 'ko' ? 'Team' : 'Team',
price: `${currencySymbol}${formatPrice(teamPrice)}`,
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',
buttonVariant: 'secondary' as const,
features: [
locale === 'ko' ? 'Pro+ 티어의 모든 기능 포함' : 'Includes all Pro+ features',
locale === 'ko' ? '팀 공용 맞춤형 사전 및 도메인 용어집' : 'Shared Team Dictionaries & Jargon',
locale === 'ko' ? '팀 워크스페이스 회의록 협업' : 'Team Meeting Collaboration Workspace',
locale === 'ko' ? 'SAML 2.0 SSO 및 SCIM 자동 계정 관리' : 'SAML 2.0 SSO & SCIM Provisioning',
locale === 'ko' ? 'Zero Data Retention (ZDR) 데이터 보안 협약' : 'Zero Data Retention (ZDR) Compliance',
],
},
const plans: PlanView[] = [
{ key: 'free', copy: t.pricing.free, price: PLAN_PRICE_KRW.free, quota: PLAN_CLOUD_QUOTA.free, href: '#download', external: false, emphasized: false },
{ key: 'pro', copy: t.pricing.pro, price: PLAN_PRICE_KRW.pro, quota: PLAN_CLOUD_QUOTA.pro, href: billingUrl('pro'), external: true, emphasized: true },
{ key: 'proPlus', copy: t.pricing.proPlus, price: PLAN_PRICE_KRW.proPlus, quota: PLAN_CLOUD_QUOTA.proPlus, href: billingUrl('pro_plus'), external: true, emphasized: false },
]
return (
<section id="pricing" className="relative py-20 md:py-28">
<section id="pricing" aria-labelledby="pricing-title" className="section border-t border-white/10 bg-surface-900/60">
<Container>
<SectionHeader
index={t.pricing.index}
title={t.pricing.title}
subtitle={t.pricing.subtitle}
/>
<SectionHeading id="pricing-title" title={t.pricing.title} subtitle={t.pricing.subtitle} />
{/* Reverse Trial Banner */}
<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="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 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.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>
<ul className="mt-14 grid grid-cols-1 gap-5 md:grid-cols-3">
{plans.map((plan) => {
const values = Object.fromEntries(Object.entries(plan.quota).map(([k, v]) => [k, quotaValue(v)]))
return (
<li
key={plan.key}
className={`flex flex-col rounded-2xl border bg-surface-800 p-6 md:p-7 ${
plan.emphasized ? 'border-brand-blue' : 'border-white/10'
}`}
>
<h3 className="text-h3 font-medium text-neutral-50">{plan.copy.name}</h3>
<p className="mt-1.5 keep-ko text-sm text-neutral-300">{plan.copy.desc}</p>
{/* 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-xl bg-surface-800 p-1 border border-white/10 shadow-sm">
<button
type="button"
onClick={() => setCurrency('usd')}
className={`text-xs px-3.5 py-1.5 rounded-lg transition-all ${
currency === 'usd'
? '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={`text-xs px-3.5 py-1.5 rounded-lg transition-all ${
currency === 'krw'
? 'bg-surface-600 text-neutral-50 font-medium shadow-sm'
: 'text-neutral-400 hover:text-white font-normal'
}`}
>
KRW (₩)
</button>
</div>
{/* Billing Cycle Toggle */}
<div className="flex items-center gap-2.5">
<button
type="button"
onClick={() => setIsAnnual(false)}
className={`text-xs px-4 py-2 rounded-xl transition-all ${
!isAnnual
? '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={`text-xs px-4 py-2 rounded-xl transition-all relative ${
isAnnual
? '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 -right-2 px-2 py-0.5 rounded-full bg-brand-blue text-white text-[10px] font-medium shadow-sm">
{t.pricing.savePercent}
</span>
)}
</button>
</div>
</div>
{/* 4 Tier Cards */}
<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-3xl p-5 xl:p-6 flex flex-col justify-between noise-texture backdrop-blur-xl transition-all duration-300 ${
tier.popular
? '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 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-3">
<span className="text-lg font-medium text-neutral-50">
{tier.name}
</span>
</div>
<p className="text-xs text-neutral-300 min-h-[36px] mb-4 font-normal leading-relaxed">
{tier.desc}
<p className="mt-6 flex items-baseline gap-1 border-b border-white/10 pb-6">
<span className="text-4xl font-medium tracking-tight text-neutral-50">{money.format(plan.price)}</span>
{plan.price > 0 && <span className="text-sm text-neutral-400">{t.pricing.perMonth}</span>}
</p>
<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-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>
<ul className="mt-6 flex-1 space-y-3">
{plan.copy.features.map((feature) => (
<li key={feature} className="flex gap-2.5 keep-ko text-sm leading-relaxed text-neutral-200">
<CheckIcon className="mt-0.5 h-4 w-4 flex-shrink-0 text-brand-blue-light" />
<span>{fmt(feature, values)}</span>
</li>
))}
</ul>
</div>
<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>
<a
href={plan.href}
{...(plan.external ? { target: '_blank', rel: 'noopener' } : {})}
className={`btn mt-8 w-full ${plan.emphasized ? 'btn-primary' : 'btn-secondary'}`}
>
{plan.copy.cta}
{plan.external && (
<>
<ExternalIcon />
<span className="sr-only">({t.a11y.opensInNewTab})</span>
</>
)}
</a>
</li>
)
})}
</ul>
{/* Feature Comparison Matrix */}
<div className="overflow-x-auto -mx-5 md:mx-0">
<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/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>
<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="mt-6 keep-ko text-sm text-neutral-400">{t.pricing.note}</p>
</Container>
</section>
)
}

View file

@ -1,124 +1,28 @@
import { SectionHeader } from '../components/SectionHeader'
import { Container } from '../components/Container'
import { Led } from '../components/Led'
import { SectionHeading } from '../components/SectionHeading'
import { useI18n } from '../i18n'
export function Privacy() {
const { t, locale } = useI18n()
const comparisonRows = [
{
feature: locale === 'ko' ? '음성 데이터 외부 서버 전송' : 'Voice Uploaded to External Servers',
d3ro: locale === 'ko' ? '0% (절대 전송 안 함)' : '0% (Never Uploaded)',
cloud: locale === 'ko' ? '100% (클라우드 저장)' : '100% (Saved to Cloud)',
d3roOk: true,
},
{
feature: locale === 'ko' ? '오프라인 단독 동작 가능 여부' : '100% Offline / Air-Gapped Operation',
d3ro: locale === 'ko' ? '완전 지원 (인터넷 불필요)' : 'Fully Supported (No Internet Needed)',
cloud: locale === 'ko' ? '불가 (연결 끊기면 중단)' : 'Fails without Internet',
d3roOk: true,
},
{
feature: locale === 'ko' ? '네트워크 전송 지연 (RTT)' : 'Network Roundtrip Latency',
d3ro: '0ms (온디바이스 메모리)',
cloud: '600ms ~ 2,500ms',
d3roOk: true,
},
{
feature: locale === 'ko' ? '무제한 딕테이션 비용' : 'Unlimited Voice Dictation Cost',
d3ro: locale === 'ko' ? '영구 무료 (₩0)' : '$0 Forever Free',
cloud: '월 2만 ~ 4만원',
d3roOk: true,
},
]
const { t } = useI18n()
return (
<section id="privacy" className="relative py-20 md:py-28 overflow-hidden">
<section id="privacy" aria-labelledby="privacy-title" className="section border-t border-white/10">
<Container>
<SectionHeader
index={t.privacy.index}
title={t.privacy.title}
subtitle={t.privacy.subtitle}
/>
<SectionHeading id="privacy-title" title={t.privacy.title} subtitle={t.privacy.subtitle} />
{/* CRT instrument panel */}
<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-6 pb-3.5 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<Led color="green" pulse />
<span className="text-xs font-medium text-emerald-400">
{t.privacy.monitorTitle}
</span>
</div>
<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-4">
{t.privacy.metrics.map((metric, i) => (
<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="text-sm sm:text-base font-medium text-neutral-100">
{metric.value}
</span>
</div>
</div>
))}
</div>
</div>
</div>
{/* Comparison Matrix: D3RO Voice vs Cloud Services */}
<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/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-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.06] text-sm">
{comparisonRows.map((row, idx) => (
<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 text-neutral-400 font-normal">
{row.cloud}
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Guarantee bullet cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{t.privacy.guarantees.map((g, i) => (
<div
key={i}
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/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-200 font-normal">{g}</span>
{/* 사양서처럼 항목과 설명을 짝지어 읽게 한다. */}
<dl className="mt-14 border-b border-white/10">
{t.privacy.points.map((point) => (
<div key={point.title} className="grid grid-cols-1 gap-2 border-t border-white/10 py-6 md:grid-cols-12 md:gap-10">
<dt className="keep-ko text-lg font-medium text-neutral-50 md:col-span-4">{point.title}</dt>
<dd className="keep-ko text-base leading-relaxed text-neutral-300 md:col-span-8">{point.body}</dd>
</div>
))}
</div>
</dl>
<p className="mt-4">
<a href="./privacy/" className="link inline-flex min-h-[44px] items-center">{t.privacy.policyLink}</a>
</p>
</Container>
</section>
)

View file

@ -1,112 +1,15 @@
/**
* D3RO Voice Landing Page - Centralized Design Tokens
* D3RO Voice 랜딩 페이지 — 구조 상수.
*
* All visual constants live here. Components reference these tokens
* instead of hardcoding colors, spacing, or typography values.
*
* Tailwind classes map to these same values via tailwind.config.js.
* Use tokens when building inline styles or JS-driven visuals.
* 색·타입·간격·그림자 값의 정본은 tailwind.config.js 다. 이 파일은
* 여러 섹션이 함께 쓰는 구조 값(내비게이션 순서 등)만 둔다.
*/
// ── Colors ──────────────────────────────────────────────
export const color = {
brand: {
blue: '#3b82f6',
blueLight: '#60a5fa',
blueDark: '#2563eb',
blueGlow: 'rgba(59,130,246,0.5)',
blueDim: 'rgba(59,130,246,0.14)',
blueMuted: 'rgba(59,130,246,0.08)',
},
surface: {
950: '#08090c',
900: '#0d0f14',
800: '#13161f',
700: '#1a1e2b',
600: '#222838',
500: '#2b3245',
400: '#363f57',
300: '#434d69',
200: '#535f7f',
100: '#68769c',
},
neutral: {
50: '#ffffff',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
},
semantic: {
success: '#22c55e',
warning: '#eab308',
error: '#ef4444',
},
} as const
// ── Typography ──────────────────────────────────────────
// 한+영 혼용 헤드라인의 스트로크 일관성을 위해 디스플레이도 Pretendard로 통일.
export const font = {
display: '"Pretendard Variable", Pretendard, -apple-system, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',
sans: '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',
mono: '"JetBrains Mono", "Fira Code", monospace',
} as const
// ── Spacing (px) ────────────────────────────────────────
export const space = {
section: { y: 120, yMobile: 80 },
grid: { gap: 16, gapLg: 24 },
container: { maxWidth: 1280, px: 24, pxLg: 48 },
} as const
// ── Shadows ─────────────────────────────────────────────
export const shadow = {
insetPanel: 'inset 0 1px 3px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06)',
chassis: '0 4px 20px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)',
glowSm: '0 0 12px rgba(59,130,246,0.3)',
glowMd: '0 0 24px rgba(59,130,246,0.25), 0 0 48px rgba(59,130,246,0.1)',
glowLg: '0 0 36px rgba(59,130,246,0.35), 0 0 72px rgba(59,130,246,0.15)',
} as const
// ── Radii ───────────────────────────────────────────────
export const radius = {
instrument: '4px',
panel: '8px',
card: '16px',
cardLarge: '24px',
pill: '999px',
} as const
// ── Z-Index ─────────────────────────────────────────────
export const zIndex = {
header: 50,
overlay: 40,
modal: 60,
} as const
// ── Timing ──────────────────────────────────────────────
export const timing = {
fast: '150ms',
normal: '250ms',
slow: '400ms',
easeOut: 'cubic-bezier(0.16, 1, 0.3, 1)',
} as const
// ── Feature Labels ──────────────────────────────────────
export const tierLabel = {
free: 'FREE',
pro: 'PRO',
proPlus: 'PRO+',
} as const
// ── Nav Items ───────────────────────────────────────────
// Labels come from i18n (t.nav.*). Only hrefs and keys are defined here.
// 라벨은 i18n(t.nav.*)에서 온다. 여기에는 순서와 앵커만 둔다.
export const navItems = [
{ key: 'features' as const, href: '#features' },
{ key: 'pipeline' as const, href: '#pipeline' },
{ key: 'privacy' as const, href: '#privacy' },
{ key: 'pricing' as const, href: '#pricing' },
{ key: 'faq' as const, href: '#faq' },
{ key: 'features', href: '#features' },
{ key: 'how', href: '#how' },
{ key: 'privacy', href: '#privacy' },
{ key: 'pricing', href: '#pricing' },
{ key: 'faq', href: '#faq' },
] as const