cdf833935a
Fix cold-start overlay recursion that overflowed the main-thread stack when recording began while the ready overlay was still up; also remove temporary on-screen Flow DEBUG panels after the orange-mic investigation, and land the macOS overlay/catalog/layout polish plus related Flow recovery hardening.
694 lines
29 KiB
HTML
694 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
<!-- Primary SEO -->
|
|
<title>OSGKeyboard — On-device voice-to-text keyboard for iOS</title>
|
|
<meta name="description" content="OSGKeyboard is a source-available iOS keyboard that turns speech into polished text in any app. On-device recognition (iOS 26+), AI polish, translation, and a personal dictionary. On-device by default — your voice never leaves the device unless you opt into the cloud engine.">
|
|
<meta name="keywords" content="voice keyboard, voice to text, dictation, iOS keyboard, speech to text, on-device ASR, SpeechAnalyzer, AI polish, translation keyboard, open source keyboard, OSGKeyboard">
|
|
<meta name="author" content="OSGKeyboard">
|
|
<meta name="robots" content="index, follow, max-image-preview:large">
|
|
<meta name="theme-color" content="#0a0a0b">
|
|
<link rel="canonical" href="https://hkgood.github.io/OSGKeyboard/">
|
|
<link rel="icon" href="assets/app-icon.png" type="image/png">
|
|
<link rel="apple-touch-icon" href="assets/app-icon.png">
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="OSGKeyboard">
|
|
<meta property="og:title" content="OSGKeyboard — On-device voice-to-text keyboard for iOS">
|
|
<meta property="og:description" content="Turn speech into polished text in any app. On-device recognition (iOS 26+), AI polish, translation, personal dictionary. On-device by default — audio stays on your phone unless you opt into the cloud engine.">
|
|
<meta property="og:url" content="https://hkgood.github.io/OSGKeyboard/">
|
|
<meta property="og:image" content="https://hkgood.github.io/OSGKeyboard/assets/app-icon.png">
|
|
<meta property="og:image:width" content="1024">
|
|
<meta property="og:image:height" content="1024">
|
|
<meta property="og:locale" content="en_US">
|
|
<meta property="og:locale:alternate" content="zh_CN">
|
|
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="OSGKeyboard — On-device voice-to-text keyboard for iOS">
|
|
<meta name="twitter:description" content="Turn speech into polished text in any app. On-device recognition, AI polish, translation, personal dictionary.">
|
|
<meta name="twitter:image" content="https://hkgood.github.io/OSGKeyboard/assets/app-icon.png">
|
|
|
|
<!-- Structured data -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "SoftwareApplication",
|
|
"name": "OSGKeyboard",
|
|
"operatingSystem": "iOS 26.0 or later",
|
|
"applicationCategory": "UtilitiesApplication",
|
|
"url": "https://hkgood.github.io/OSGKeyboard/",
|
|
"image": "https://hkgood.github.io/OSGKeyboard/assets/app-icon.png",
|
|
"description": "A source-available iOS keyboard that turns speech into polished text in any app. On-device recognition (iOS 26+), AI polish, translation, and a personal dictionary. Local or cloud engine.",
|
|
"softwareVersion": "0.3.6",
|
|
"isAccessibleForFree": true,
|
|
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
|
|
"author": { "@type": "Organization", "name": "OSGKeyboard" },
|
|
"featureList": [
|
|
"On-device speech recognition (SpeechAnalyzer, iOS 26+)",
|
|
"AI polish with three intensity levels",
|
|
"Post-polish translation",
|
|
"Personal dictionary",
|
|
"Long-lived Flow session",
|
|
"Local or cloud engine"
|
|
]
|
|
}
|
|
</script>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300,0,0" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
color-scheme: light dark;
|
|
--bg: #ffffff;
|
|
--bg-soft: #f7f7f8;
|
|
--text: #0c0c0e;
|
|
--text-secondary: #55555f;
|
|
--text-tertiary: #90909a;
|
|
--line: rgba(0, 0, 0, 0.08);
|
|
--accent: #2d8a4a;
|
|
--accent-soft: rgba(45, 138, 74, 0.10);
|
|
--nav-bg: rgba(255, 255, 255, 0.72);
|
|
--max: 1020px;
|
|
--radius: 18px;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--bg: #0a0a0b;
|
|
--bg-soft: #131316;
|
|
--text: #f4f4f6;
|
|
--text-secondary: #a6a6b0;
|
|
--text-tertiary: #6e6e78;
|
|
--line: rgba(255, 255, 255, 0.10);
|
|
--accent: #4cc46e;
|
|
--accent-soft: rgba(76, 196, 110, 0.14);
|
|
--nav-bg: rgba(10, 10, 11, 0.72);
|
|
}
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
|
|
|
|
body {
|
|
margin: 0;
|
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
text-rendering: optimizeLegibility;
|
|
}
|
|
|
|
a { color: inherit; text-decoration: none; }
|
|
|
|
.material-symbols-outlined {
|
|
font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
|
|
line-height: 1;
|
|
user-select: none;
|
|
}
|
|
|
|
.wrap {
|
|
max-width: var(--max);
|
|
margin: 0 auto;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
/* Nav */
|
|
.nav {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 20;
|
|
background: var(--nav-bg);
|
|
backdrop-filter: blur(20px) saturate(1.6);
|
|
-webkit-backdrop-filter: blur(20px) saturate(1.6);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
|
|
.nav-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
min-height: 60px;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
font-weight: 650;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
.brand img { width: 28px; height: 28px; border-radius: 7px; }
|
|
|
|
.nav-right { display: flex; align-items: center; gap: 0.5rem; }
|
|
|
|
.lang-toggle {
|
|
display: inline-flex;
|
|
padding: 2px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--line);
|
|
}
|
|
|
|
.lang-toggle button {
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--text-tertiary);
|
|
font: inherit;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
padding: 0.3rem 0.7rem;
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
transition: color 0.2s, background 0.2s;
|
|
}
|
|
|
|
.lang-toggle button.active { background: var(--text); color: var(--bg); }
|
|
|
|
.ghost-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
padding: 0.4rem 0.6rem;
|
|
border-radius: 999px;
|
|
transition: color 0.2s;
|
|
}
|
|
.ghost-link:hover { color: var(--text); }
|
|
.ghost-link .material-symbols-outlined { font-size: 18px; }
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.45rem;
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
padding: 0.75rem 1.3rem;
|
|
border-radius: 999px;
|
|
transition: transform 0.15s ease, opacity 0.15s ease;
|
|
}
|
|
.btn:hover { transform: translateY(-1px); }
|
|
.btn .material-symbols-outlined { font-size: 20px; }
|
|
|
|
.btn-primary { background: var(--accent); color: #fff; }
|
|
.btn-quiet { color: var(--text); border: 1px solid var(--line); }
|
|
|
|
/* Hero */
|
|
.hero { padding: 5.5rem 0 3.5rem; }
|
|
|
|
.hero-grid {
|
|
display: grid;
|
|
grid-template-columns: 1.05fr 0.95fr;
|
|
gap: 3rem;
|
|
align-items: center;
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.hero { padding: 3.5rem 0 2.5rem; }
|
|
.hero-grid { grid-template-columns: 1fr; gap: 2.5rem; }
|
|
.hero-visual { order: -1; }
|
|
}
|
|
|
|
.eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
color: var(--accent);
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.eyebrow .material-symbols-outlined { font-size: 17px; }
|
|
|
|
h1 {
|
|
margin: 0 0 1.25rem;
|
|
font-size: clamp(2.3rem, 5.4vw, 3.6rem);
|
|
line-height: 1.05;
|
|
letter-spacing: -0.035em;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.lead {
|
|
margin: 0 0 2rem;
|
|
font-size: 1.14rem;
|
|
color: var(--text-secondary);
|
|
max-width: 33rem;
|
|
}
|
|
|
|
.hero-cta { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1.75rem; }
|
|
|
|
.hero-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem 1.25rem;
|
|
font-size: 0.85rem;
|
|
color: var(--text-tertiary);
|
|
}
|
|
.hero-meta span { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
.hero-meta .material-symbols-outlined { font-size: 17px; color: var(--accent); }
|
|
|
|
/* Phone mock */
|
|
.phone {
|
|
max-width: 300px;
|
|
margin: 0 auto;
|
|
border-radius: 40px;
|
|
padding: 10px;
|
|
background: var(--bg-soft);
|
|
border: 1px solid var(--line);
|
|
box-shadow: 0 40px 90px rgba(0, 0, 0, 0.18);
|
|
}
|
|
|
|
.phone-screen {
|
|
border-radius: 32px;
|
|
overflow: hidden;
|
|
background: #16161a;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 440px;
|
|
}
|
|
|
|
.screen-top { padding: 1.3rem 1.2rem 0.4rem; color: #f4f4f6; }
|
|
.screen-app { font-size: 0.8rem; color: #8a8a94; margin: 0 0 0.15rem; }
|
|
.screen-line { font-size: 0.95rem; line-height: 1.5; margin: 0; color: #e8e8ec; }
|
|
.screen-caret { color: #4cc46e; font-weight: 700; }
|
|
|
|
.kb {
|
|
margin-top: auto;
|
|
background: #1e1e22;
|
|
border-top: 1px solid rgba(255,255,255,0.06);
|
|
padding: 0.9rem 0.9rem 1.1rem;
|
|
}
|
|
|
|
.kb-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; }
|
|
.kb-chip {
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
color: #cfcfd6;
|
|
padding: 0.28rem 0.6rem;
|
|
border-radius: 999px;
|
|
background: rgba(255,255,255,0.08);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
}
|
|
.kb-chip .material-symbols-outlined { font-size: 14px; }
|
|
|
|
.kb-mic {
|
|
width: 60px; height: 60px;
|
|
margin: 0.2rem auto 0;
|
|
border-radius: 50%;
|
|
background: var(--accent);
|
|
display: grid;
|
|
place-items: center;
|
|
color: #fff;
|
|
box-shadow: 0 0 0 8px var(--accent-soft);
|
|
position: relative;
|
|
}
|
|
.kb-mic .material-symbols-outlined { font-size: 28px; }
|
|
.kb-mic::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: -8px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--accent);
|
|
opacity: 0;
|
|
animation: ring 2.6s ease-out infinite;
|
|
}
|
|
@keyframes ring {
|
|
0% { transform: scale(0.85); opacity: 0.5; }
|
|
100% { transform: scale(1.55); opacity: 0; }
|
|
}
|
|
|
|
/* Section */
|
|
section.block { padding: 3.5rem 0; }
|
|
.block-head { max-width: 40rem; margin: 0 0 2.75rem; }
|
|
.block-head h2 {
|
|
margin: 0 0 0.6rem;
|
|
font-size: clamp(1.6rem, 3.2vw, 2.1rem);
|
|
letter-spacing: -0.025em;
|
|
font-weight: 700;
|
|
}
|
|
.block-head p { margin: 0; color: var(--text-secondary); font-size: 1.02rem; }
|
|
|
|
/* Features — light, editorial, no heavy cards */
|
|
.features {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 2rem 2.5rem;
|
|
}
|
|
@media (max-width: 780px) { .features { grid-template-columns: repeat(2, 1fr); gap: 2rem; } }
|
|
@media (max-width: 480px) { .features { grid-template-columns: 1fr; } }
|
|
|
|
.feature .ficon {
|
|
width: 44px; height: 44px;
|
|
border-radius: 12px;
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
display: grid;
|
|
place-items: center;
|
|
margin-bottom: 0.9rem;
|
|
}
|
|
.feature .ficon .material-symbols-outlined { font-size: 24px; }
|
|
.feature h3 { margin: 0 0 0.35rem; font-size: 1.02rem; font-weight: 650; letter-spacing: -0.01em; }
|
|
.feature p { margin: 0; font-size: 0.93rem; color: var(--text-secondary); }
|
|
|
|
/* Steps */
|
|
.steps {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 2.5rem;
|
|
counter-reset: s;
|
|
}
|
|
@media (max-width: 720px) { .steps { grid-template-columns: 1fr; gap: 2rem; } }
|
|
|
|
.step { position: relative; padding-top: 0.5rem; }
|
|
.step::before {
|
|
counter-increment: s;
|
|
content: counter(s);
|
|
display: grid;
|
|
place-items: center;
|
|
width: 30px; height: 30px;
|
|
border-radius: 50%;
|
|
border: 1.5px solid var(--accent);
|
|
color: var(--accent);
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.85rem;
|
|
}
|
|
.step h3 { margin: 0 0 0.35rem; font-size: 1.05rem; font-weight: 650; }
|
|
.step p { margin: 0; color: var(--text-secondary); font-size: 0.95rem; }
|
|
|
|
/* Closing */
|
|
.closing {
|
|
border-top: 1px solid var(--line);
|
|
border-bottom: 1px solid var(--line);
|
|
padding: 3.5rem 0;
|
|
}
|
|
.closing-inner {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1.5rem;
|
|
}
|
|
.closing h2 { margin: 0 0 0.5rem; font-size: 1.5rem; letter-spacing: -0.02em; }
|
|
.closing p { margin: 0; color: var(--text-secondary); max-width: 34rem; }
|
|
.closing .lock { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--accent); font-weight: 600; font-size: 0.85rem; margin-bottom: 0.75rem; }
|
|
.closing .lock .material-symbols-outlined { font-size: 18px; }
|
|
|
|
/* Footer */
|
|
footer { padding: 2.5rem 0 3rem; color: var(--text-tertiary); font-size: 0.85rem; }
|
|
.footer-inner { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.75rem; align-items: center; }
|
|
.footer-links { display: flex; flex-wrap: wrap; gap: 1.1rem; }
|
|
.footer-links a { color: var(--text-secondary); }
|
|
.footer-links a:hover { color: var(--text); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="nav">
|
|
<div class="wrap nav-inner">
|
|
<a class="brand" href="#top">
|
|
<img src="assets/app-icon.png" width="28" height="28" alt="OSGKeyboard app icon">
|
|
OSGKeyboard
|
|
</a>
|
|
<div class="nav-right">
|
|
<div class="lang-toggle" role="group" aria-label="Language">
|
|
<button type="button" class="active" data-lang="en">EN</button>
|
|
<button type="button" data-lang="zh">中文</button>
|
|
</div>
|
|
<a class="ghost-link" href="https://github.com/hkgood/OSGKeyboard" rel="noopener">
|
|
<span class="material-symbols-outlined" aria-hidden="true">code</span>
|
|
<span data-i18n="nav.github">GitHub</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="top">
|
|
<!-- Hero -->
|
|
<section class="hero">
|
|
<div class="wrap hero-grid">
|
|
<div>
|
|
<div class="eyebrow">
|
|
<span class="material-symbols-outlined" aria-hidden="true">verified</span>
|
|
<span data-i18n="hero.eyebrow">Source available · iOS 26+</span>
|
|
</div>
|
|
<h1 data-i18n="hero.title">Speak naturally.<br>Get polished text anywhere.</h1>
|
|
<p class="lead" data-i18n="hero.lead">A source-available keyboard that transcribes your voice on-device, then polishes — and optionally translates — the result. Works in every app you type in.</p>
|
|
<div class="hero-cta">
|
|
<a class="btn btn-primary" href="https://github.com/hkgood/OSGKeyboard" rel="noopener">
|
|
<span class="material-symbols-outlined" aria-hidden="true">download</span>
|
|
<span data-i18n="hero.cta.primary">Get it on GitHub</span>
|
|
</a>
|
|
<a class="btn btn-quiet" href="privacy/">
|
|
<span class="material-symbols-outlined" aria-hidden="true">shield</span>
|
|
<span data-i18n="hero.cta.secondary">Privacy</span>
|
|
</a>
|
|
</div>
|
|
<div class="hero-meta">
|
|
<span><span class="material-symbols-outlined" aria-hidden="true">devices</span><span data-i18n="hero.meta1">On-device recognition</span></span>
|
|
<span><span class="material-symbols-outlined" aria-hidden="true">bolt</span><span data-i18n="hero.meta2">Local or cloud AI</span></span>
|
|
<span><span class="material-symbols-outlined" aria-hidden="true">lock</span><span data-i18n="hero.meta3">On-device by default — audio stays on your phone</span></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hero-visual">
|
|
<div class="phone">
|
|
<div class="phone-screen">
|
|
<div class="screen-top">
|
|
<p class="screen-app" data-i18n="mock.app">Notes</p>
|
|
<p class="screen-line" data-i18n="mock.line">Tomorrow at 3 PM, sync with the design team on the new onboarding flow.<span class="screen-caret">|</span></p>
|
|
</div>
|
|
<div class="kb">
|
|
<div class="kb-bar">
|
|
<span class="kb-chip"><span class="material-symbols-outlined" aria-hidden="true">devices</span><span data-i18n="mock.chipEngine">On-device</span></span>
|
|
<span class="kb-chip"><span class="material-symbols-outlined" aria-hidden="true">translate</span><span data-i18n="mock.chipTranslate">Translate</span></span>
|
|
</div>
|
|
<div class="kb-mic"><span class="material-symbols-outlined" aria-hidden="true">mic</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Features -->
|
|
<section class="block wrap">
|
|
<div class="block-head">
|
|
<h2 data-i18n="features.title">Everything you need to type by voice</h2>
|
|
<p data-i18n="features.subtitle">On-device by default, refined by AI when you want it — in a single, native keyboard.</p>
|
|
</div>
|
|
<div class="features">
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">mic</span></div>
|
|
<h3 data-i18n="feat.dictation.t">Tap-to-talk dictation</h3>
|
|
<p data-i18n="feat.dictation.b">Tap to start, tap to stop. Up to 3.5 minutes per take, with a live countdown.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">devices</span></div>
|
|
<h3 data-i18n="feat.ondevice.t">On-device recognition</h3>
|
|
<p data-i18n="feat.ondevice.b">iOS 26 SpeechAnalyzer transcribes on your device by default — audio is uploaded only if you explicitly enable the cloud engine.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">auto_awesome</span></div>
|
|
<h3 data-i18n="feat.polish.t">AI polish</h3>
|
|
<p data-i18n="feat.polish.b">Punctuation, structure, and clarity in three intensity levels. Polish sends only text — on the default engine your audio never goes online.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">translate</span></div>
|
|
<h3 data-i18n="feat.translate.t">Built-in translation</h3>
|
|
<p data-i18n="feat.translate.b">Translate after polish into English, 中文, 日本語, 한국어 and more — right from the keyboard.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">menu_book</span></div>
|
|
<h3 data-i18n="feat.dict.t">Personal dictionary</h3>
|
|
<p data-i18n="feat.dict.b">Keep names, products, and jargon accurate. The AI preserves your terms every time.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<div class="ficon"><span class="material-symbols-outlined" aria-hidden="true">all_inclusive</span></div>
|
|
<h3 data-i18n="feat.flow.t">Flow session</h3>
|
|
<p data-i18n="feat.flow.b">A long-lived session keeps the recorder warm, so you can dictate take after take.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- How it works -->
|
|
<section class="block wrap">
|
|
<div class="block-head">
|
|
<h2 data-i18n="steps.title">Three steps to your first message</h2>
|
|
<p data-i18n="steps.subtitle">Set up once, then dictate anywhere you type.</p>
|
|
</div>
|
|
<div class="steps">
|
|
<div class="step">
|
|
<h3 data-i18n="step1.t">Enable the keyboard</h3>
|
|
<p data-i18n="step1.b">Add OSGKeyboard in iOS Settings and allow Full Access — needed for the mic and network.</p>
|
|
</div>
|
|
<div class="step">
|
|
<h3 data-i18n="step2.t">Choose your engine</h3>
|
|
<p data-i18n="step2.b">Local: on-device ASR with built-in polish, no API key. Cloud: use your own OpenAI-compatible key.</p>
|
|
</div>
|
|
<div class="step">
|
|
<h3 data-i18n="step3.t">Tap to talk</h3>
|
|
<p data-i18n="step3.b">Switch to OSGKeyboard, tap the mic, speak, tap again. Polished text lands at your cursor.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Closing / privacy -->
|
|
<section class="closing">
|
|
<div class="wrap closing-inner">
|
|
<div>
|
|
<span class="lock"><span class="material-symbols-outlined" aria-hidden="true">lock</span><span data-i18n="close.badge">Privacy by design</span></span>
|
|
<h2 data-i18n="close.title">Your voice stays yours</h2>
|
|
<p data-i18n="close.body">Speech is processed on-device by default (the optional cloud engine uploads recordings to the provider you configure). We never log ordinary keystrokes; polish and translation receive only transcribed text.</p>
|
|
</div>
|
|
<a class="btn btn-quiet" href="privacy/">
|
|
<span class="material-symbols-outlined" aria-hidden="true">description</span>
|
|
<span data-i18n="close.cta">Read the privacy policy</span>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<footer class="wrap">
|
|
<div class="footer-inner">
|
|
<span data-i18n="footer.copy">© OSGKeyboard · v0.3.6 · source available, non-commercial</span>
|
|
<span class="footer-links">
|
|
<a href="https://github.com/hkgood/OSGKeyboard" rel="noopener" data-i18n="footer.source">Source</a>
|
|
<a href="privacy/" data-i18n="footer.privacy">Privacy</a>
|
|
<a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE" rel="noopener" data-i18n="footer.license">License</a>
|
|
</span>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
const copy = {
|
|
en: {
|
|
"nav.github": "GitHub",
|
|
"hero.eyebrow": "Source available · iOS 26+",
|
|
"hero.title": "Speak naturally.<br>Get polished text anywhere.",
|
|
"hero.lead": "A source-available keyboard that transcribes your voice on-device, then polishes — and optionally translates — the result. Works in every app you type in.",
|
|
"hero.cta.primary": "Get it on GitHub",
|
|
"hero.cta.secondary": "Privacy",
|
|
"hero.meta1": "On-device recognition",
|
|
"hero.meta2": "Local or cloud AI",
|
|
"hero.meta3": "On-device by default — audio stays on your phone",
|
|
"mock.app": "Notes",
|
|
"mock.line": "Tomorrow at 3 PM, sync with the design team on the new onboarding flow.",
|
|
"mock.chipEngine": "On-device",
|
|
"mock.chipTranslate": "Translate",
|
|
"features.title": "Everything you need to type by voice",
|
|
"features.subtitle": "On-device by default, refined by AI when you want it — in a single, native keyboard.",
|
|
"feat.dictation.t": "Tap-to-talk dictation",
|
|
"feat.dictation.b": "Tap to start, tap to stop. Up to 3.5 minutes per take, with a live countdown.",
|
|
"feat.ondevice.t": "On-device recognition",
|
|
"feat.ondevice.b": "iOS 26 SpeechAnalyzer transcribes on your device by default — audio is uploaded only if you explicitly enable the cloud engine.",
|
|
"feat.polish.t": "AI polish",
|
|
"feat.polish.b": "Punctuation, structure, and clarity in three intensity levels. Polish sends only text — on the default engine your audio never goes online.",
|
|
"feat.translate.t": "Built-in translation",
|
|
"feat.translate.b": "Translate after polish into English, 中文, 日本語, 한국어 and more — right from the keyboard.",
|
|
"feat.dict.t": "Personal dictionary",
|
|
"feat.dict.b": "Keep names, products, and jargon accurate. The AI preserves your terms every time.",
|
|
"feat.flow.t": "Flow session",
|
|
"feat.flow.b": "A long-lived session keeps the recorder warm, so you can dictate take after take.",
|
|
"steps.title": "Three steps to your first message",
|
|
"steps.subtitle": "Set up once, then dictate anywhere you type.",
|
|
"step1.t": "Enable the keyboard",
|
|
"step1.b": "Add OSGKeyboard in iOS Settings and allow Full Access — needed for the mic and network.",
|
|
"step2.t": "Choose your engine",
|
|
"step2.b": "Local: on-device ASR with built-in polish, no API key. Cloud: use your own OpenAI-compatible key.",
|
|
"step3.t": "Tap to talk",
|
|
"step3.b": "Switch to OSGKeyboard, tap the mic, speak, tap again. Polished text lands at your cursor.",
|
|
"close.badge": "Privacy by design",
|
|
"close.title": "Your voice stays yours",
|
|
"close.body": "Speech is processed on-device by default (the optional cloud engine uploads recordings to the provider you configure). We never log ordinary keystrokes; polish and translation receive only transcribed text.",
|
|
"close.cta": "Read the privacy policy",
|
|
"footer.copy": "© OSGKeyboard · v0.3.6 · source available, non-commercial",
|
|
"footer.source": "Source",
|
|
"footer.privacy": "Privacy",
|
|
"footer.license": "License"
|
|
},
|
|
zh: {
|
|
"nav.github": "GitHub",
|
|
"hero.eyebrow": "源码可见 · iOS 26+",
|
|
"hero.title": "自然地说,<br>随处得到润色好的文字。",
|
|
"hero.lead": "源码可见的语音键盘:在设备端转写你的语音,再润色,并可选翻译。适用于所有需要输入的 App。",
|
|
"hero.cta.primary": "前往 GitHub",
|
|
"hero.cta.secondary": "隐私政策",
|
|
"hero.meta1": "设备端识别",
|
|
"hero.meta2": "本地或云端 AI",
|
|
"hero.meta3": "默认端侧识别,音频留在手机",
|
|
"mock.app": "备忘录",
|
|
"mock.line": "明天下午三点,和设计团队同步新引导流程。",
|
|
"mock.chipEngine": "本地",
|
|
"mock.chipTranslate": "翻译",
|
|
"features.title": "语音输入所需的一切",
|
|
"features.subtitle": "默认全程端侧,需要时再交给 AI 润色——都在一个原生键盘里完成。",
|
|
"feat.dictation.t": "点按听写",
|
|
"feat.dictation.b": "点一下开始,再点一下结束。单次最长 3.5 分钟,倒计时实时显示。",
|
|
"feat.ondevice.t": "设备端识别",
|
|
"feat.ondevice.b": "默认由 iOS 26 SpeechAnalyzer 在本机转写——仅当你显式开启云端引擎时,录音才会上传到你配置的服务商。",
|
|
"feat.polish.t": "AI 润色",
|
|
"feat.polish.b": "自动补全标点、结构与表达,分三档强度。润色仅发送文字;默认引擎下音频不联网。",
|
|
"feat.translate.t": "内置翻译",
|
|
"feat.translate.b": "润色后可翻译为 English、中文、日本語、한국어 等——直接在键盘上完成。",
|
|
"feat.dict.t": "个性词库",
|
|
"feat.dict.b": "让人名、产品名与专有名词保持准确,AI 每次都会原样保留你的词汇。",
|
|
"feat.flow.t": "Flow 会话",
|
|
"feat.flow.b": "长生命周期会话让录音随时就绪,连续多次听写无需跳回主 App。",
|
|
"steps.title": "三步发出第一条消息",
|
|
"steps.subtitle": "只需设置一次,随后在任意输入处开口即写。",
|
|
"step1.t": "启用键盘",
|
|
"step1.b": "在 iOS 设置中添加 OSGKeyboard 并允许完全访问——麦克风与网络所需。",
|
|
"step2.t": "选择引擎",
|
|
"step2.b": "本地:设备端 ASR + 内置润色,无需 API Key。云端:使用你自己的 OpenAI 兼容 Key。",
|
|
"step3.t": "点按说话",
|
|
"step3.b": "切换到 OSGKeyboard,点麦克风,说话,再点结束,润色好的文字自动插入光标处。",
|
|
"close.badge": "隐私优先设计",
|
|
"close.title": "你的声音,始终属于你",
|
|
"close.body": "默认在设备端处理语音(可选的云端引擎会把录音上传到你配置的服务商)。我们不记录普通击键;润色与翻译只接收转写文字。",
|
|
"close.cta": "查看隐私政策",
|
|
"footer.copy": "© OSGKeyboard · v0.3.6 · 源码可见,禁止商用",
|
|
"footer.source": "源代码",
|
|
"footer.privacy": "隐私",
|
|
"footer.license": "许可协议"
|
|
}
|
|
};
|
|
|
|
function applyLang(lang) {
|
|
const strings = copy[lang] || copy.en;
|
|
document.documentElement.lang = lang === "zh" ? "zh-Hans" : "en";
|
|
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
|
const value = strings[el.getAttribute("data-i18n")];
|
|
if (value == null) return;
|
|
if (el.getAttribute("data-i18n") === "hero.title") el.innerHTML = value;
|
|
else el.textContent = value;
|
|
});
|
|
document.querySelectorAll(".lang-toggle button").forEach((btn) => {
|
|
btn.classList.toggle("active", btn.dataset.lang === lang);
|
|
});
|
|
try { localStorage.setItem("osg-lang", lang); } catch (_) {}
|
|
}
|
|
|
|
document.querySelectorAll(".lang-toggle button").forEach((btn) => {
|
|
btn.addEventListener("click", () => applyLang(btn.dataset.lang));
|
|
});
|
|
|
|
const saved = (() => { try { return localStorage.getItem("osg-lang"); } catch (_) { return null; } })();
|
|
const zh = navigator.language && navigator.language.toLowerCase().startsWith("zh");
|
|
applyLang(saved || (zh ? "zh" : "en"));
|
|
</script>
|
|
</body>
|
|
</html>
|