b112aa176c
Retitle the differentiation, BYOK, and setup sections with richer copy, and add a Typeless / Superwhisper / Openless / OSGKeyboard table covering pricing, on-device ASR, BYOK, and platforms.
1030 lines
41 KiB
HTML
1030 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-Hans" data-theme="system">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>OSGKeyboard — 开口即文字 · iOS & Mac 语音输入</title>
|
||
<meta name="description" content="免费开源的跨端语音输入。本地识别、BYOK 润色、个性词库。iPhone、iPad、Mac — 开口即文字。">
|
||
<meta name="keywords" content="voice keyboard, dictation, BYOK, open source, privacy, 语音输入, 听写, OSGKeyboard">
|
||
<meta name="theme-color" content="#0a0a0b" media="(prefers-color-scheme: dark)">
|
||
<meta name="theme-color" content="#f7f7f5" media="(prefers-color-scheme: light)">
|
||
<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">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:title" content="OSGKeyboard — 开口即文字">
|
||
<meta property="og:description" content="免费 · 跨端 · 开源 · 隐私优先 · BYOK。语音留在本机,AI 用你自己的 Key。">
|
||
<meta property="og:url" content="https://hkgood.github.io/OSGKeyboard/">
|
||
<meta property="og:image" content="https://hkgood.github.io/OSGKeyboard/assets/screenshots/zh/dark/mac-dashboard.png">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "SoftwareApplication",
|
||
"name": "OSGKeyboard",
|
||
"operatingSystem": "iOS, iPadOS, macOS",
|
||
"applicationCategory": "UtilitiesApplication",
|
||
"url": "https://hkgood.github.io/OSGKeyboard/",
|
||
"image": "https://hkgood.github.io/OSGKeyboard/assets/app-icon.png",
|
||
"description": "Free open-source voice input for iPhone, iPad, and Mac. On-device recognition, BYOK AI polish, personal dictionary.",
|
||
"softwareVersion": "0.5.4",
|
||
"isAccessibleForFree": true,
|
||
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
|
||
"author": { "@type": "Organization", "name": "OSGKeyboard" }
|
||
}
|
||
</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=Outfit:wght@400;500;600;700&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300,0,0&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg: #f7f7f5;
|
||
--bg-elevated: #ffffff;
|
||
--bg-soft: #efefec;
|
||
--hero-wash: #e7f3eb;
|
||
--text: #121214;
|
||
--text-2: #5a5a62;
|
||
--text-3: #8e8e96;
|
||
--line: rgba(18, 18, 20, 0.10);
|
||
--line-strong: rgba(18, 18, 20, 0.16);
|
||
--accent: #2f9a52;
|
||
--accent-soft: rgba(47, 154, 82, 0.12);
|
||
--accent-glow: rgba(47, 154, 82, 0.28);
|
||
--nav: rgba(247, 247, 245, 0.78);
|
||
--shadow: 0 28px 80px rgba(18, 18, 20, 0.12);
|
||
--radius: 20px;
|
||
--max: 1120px;
|
||
--font: "Outfit", -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", sans-serif;
|
||
}
|
||
|
||
html[data-theme="dark"] {
|
||
--bg: #0a0a0b;
|
||
--bg-elevated: #131316;
|
||
--bg-soft: #18181b;
|
||
--hero-wash: #102018;
|
||
--text: #f4f4f2;
|
||
--text-2: #a8a8b0;
|
||
--text-3: #6f6f78;
|
||
--line: rgba(255, 255, 255, 0.09);
|
||
--line-strong: rgba(255, 255, 255, 0.14);
|
||
--accent: #4cc46e;
|
||
--accent-soft: rgba(76, 196, 110, 0.14);
|
||
--accent-glow: rgba(76, 196, 110, 0.32);
|
||
--nav: rgba(10, 10, 11, 0.78);
|
||
--shadow: 0 28px 80px rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
html[data-theme="system"] {
|
||
--bg: #0a0a0b;
|
||
--bg-elevated: #131316;
|
||
--bg-soft: #18181b;
|
||
--hero-wash: #102018;
|
||
--text: #f4f4f2;
|
||
--text-2: #a8a8b0;
|
||
--text-3: #6f6f78;
|
||
--line: rgba(255, 255, 255, 0.09);
|
||
--line-strong: rgba(255, 255, 255, 0.14);
|
||
--accent: #4cc46e;
|
||
--accent-soft: rgba(76, 196, 110, 0.14);
|
||
--accent-glow: rgba(76, 196, 110, 0.32);
|
||
--nav: rgba(10, 10, 11, 0.78);
|
||
--shadow: 0 28px 80px rgba(0, 0, 0, 0.45);
|
||
}
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
margin: 0;
|
||
font-family: var(--font);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.55;
|
||
-webkit-font-smoothing: antialiased;
|
||
overflow-x: hidden;
|
||
}
|
||
a { color: inherit; text-decoration: none; }
|
||
img { max-width: 100%; display: block; }
|
||
button { font: inherit; color: inherit; }
|
||
|
||
.material-symbols-outlined {
|
||
font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
|
||
line-height: 1;
|
||
user-select: none;
|
||
}
|
||
|
||
.wrap {
|
||
width: min(100% - 2.5rem, var(--max));
|
||
margin-inline: auto;
|
||
}
|
||
|
||
/* —— Nav —— */
|
||
.nav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 40;
|
||
background: var(--nav);
|
||
backdrop-filter: blur(18px) saturate(1.4);
|
||
-webkit-backdrop-filter: blur(18px) saturate(1.4);
|
||
border-bottom: 1px solid var(--line);
|
||
}
|
||
.nav-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
min-height: 64px;
|
||
gap: 1rem;
|
||
}
|
||
.brand {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.7rem;
|
||
font-weight: 600;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
.brand-mark {
|
||
width: 72px;
|
||
height: 22px;
|
||
color: var(--accent);
|
||
}
|
||
.brand-name { font-size: 1rem; }
|
||
.nav-actions { display: flex; align-items: center; gap: 0.35rem; }
|
||
.icon-btn, .pill-toggle button {
|
||
border: 1px solid var(--line);
|
||
background: transparent;
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||
}
|
||
.icon-btn {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: grid;
|
||
place-items: center;
|
||
color: var(--text-2);
|
||
}
|
||
.icon-btn:hover { color: var(--text); border-color: var(--line-strong); }
|
||
.icon-btn .material-symbols-outlined { font-size: 20px; }
|
||
.pill-toggle {
|
||
display: inline-flex;
|
||
padding: 2px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
}
|
||
.pill-toggle button {
|
||
border: 0;
|
||
padding: 0.28rem 0.65rem;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
color: var(--text-3);
|
||
background: transparent;
|
||
}
|
||
.pill-toggle button.active {
|
||
background: var(--text);
|
||
color: var(--bg);
|
||
}
|
||
.nav-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
padding: 0.4rem 0.7rem;
|
||
border-radius: 999px;
|
||
font-size: 0.85rem;
|
||
font-weight: 500;
|
||
color: var(--text-2);
|
||
}
|
||
.nav-link:hover { color: var(--text); }
|
||
.nav-link .material-symbols-outlined { font-size: 18px; }
|
||
|
||
/* —— Buttons —— */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.45rem;
|
||
padding: 0.85rem 1.35rem;
|
||
border-radius: 999px;
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
border: 1px solid transparent;
|
||
transition: transform 0.18s ease, background 0.18s, border-color 0.18s;
|
||
}
|
||
.btn:hover { transform: translateY(-1px); }
|
||
.btn .material-symbols-outlined { font-size: 20px; }
|
||
.btn-primary { background: var(--accent); color: #fff; }
|
||
.btn-ghost {
|
||
background: transparent;
|
||
border-color: var(--line-strong);
|
||
color: var(--text);
|
||
}
|
||
.btn-ghost:hover { background: var(--bg-soft); }
|
||
|
||
/* —— Hero —— */
|
||
.hero {
|
||
position: relative;
|
||
/* Full-bleed solid wash (no side gaps from inset gradients). */
|
||
background: var(--hero-wash);
|
||
padding: 4.5rem 0 2.5rem;
|
||
overflow: hidden;
|
||
}
|
||
.hero .wrap { position: relative; z-index: 1; }
|
||
.hero-copy {
|
||
max-width: 42rem;
|
||
margin: 0 auto 2.5rem;
|
||
text-align: center;
|
||
}
|
||
.eyebrow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.45rem;
|
||
margin-bottom: 1.1rem;
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
}
|
||
.eyebrow .material-symbols-outlined { font-size: 16px; }
|
||
h1 {
|
||
margin: 0 0 1rem;
|
||
font-size: clamp(2.6rem, 7vw, 4.4rem);
|
||
line-height: 1.02;
|
||
letter-spacing: -0.045em;
|
||
font-weight: 700;
|
||
}
|
||
.lead {
|
||
margin: 0 auto 1.75rem;
|
||
max-width: 34rem;
|
||
font-size: 1.12rem;
|
||
color: var(--text-2);
|
||
}
|
||
.hero-cta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 0.7rem;
|
||
margin-bottom: 1.4rem;
|
||
}
|
||
.hero-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 0.6rem 1.2rem;
|
||
font-size: 0.86rem;
|
||
color: var(--text-3);
|
||
}
|
||
.hero-meta span {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
}
|
||
.hero-meta .material-symbols-outlined { font-size: 16px; color: var(--accent); }
|
||
|
||
/* Marketing device-family composite (replaces CSS mockup frames). */
|
||
.hero-stage {
|
||
position: relative;
|
||
margin: 2.25rem auto 0;
|
||
max-width: 1080px;
|
||
transform: translateY(16px);
|
||
opacity: 0;
|
||
animation: rise-in 1s cubic-bezier(0.22, 1, 0.36, 1) 0.12s forwards;
|
||
}
|
||
.hero-stage img {
|
||
width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
}
|
||
|
||
@keyframes rise-in {
|
||
to { transform: translateY(0); opacity: 1; }
|
||
}
|
||
|
||
/* —— Reveal —— */
|
||
.reveal {
|
||
opacity: 0;
|
||
transform: translateY(28px);
|
||
transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
|
||
}
|
||
.reveal.in { opacity: 1; transform: none; }
|
||
.reveal-delay-1 { transition-delay: 0.08s; }
|
||
.reveal-delay-2 { transition-delay: 0.16s; }
|
||
.reveal-delay-3 { transition-delay: 0.24s; }
|
||
.reveal-delay-4 { transition-delay: 0.32s; }
|
||
|
||
/* —— Sections —— */
|
||
section.block { padding: 5rem 0; }
|
||
.block-head {
|
||
max-width: 38rem;
|
||
margin: 0 auto 2.75rem;
|
||
text-align: center;
|
||
}
|
||
.block-head h2 {
|
||
margin: 0 0 0.65rem;
|
||
font-size: clamp(1.7rem, 3.5vw, 2.35rem);
|
||
letter-spacing: -0.03em;
|
||
font-weight: 700;
|
||
}
|
||
.block-head p {
|
||
margin: 0;
|
||
color: var(--text-2);
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
/* —— Diff pillars —— */
|
||
.pillars {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
gap: 1rem;
|
||
}
|
||
@media (max-width: 960px) {
|
||
.pillars { grid-template-columns: repeat(3, 1fr); }
|
||
}
|
||
@media (max-width: 620px) {
|
||
.pillars { grid-template-columns: 1fr 1fr; }
|
||
}
|
||
.pillar {
|
||
padding: 1.35rem 1.15rem;
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--radius);
|
||
background: var(--bg-elevated);
|
||
transition: border-color 0.25s, transform 0.25s;
|
||
}
|
||
.pillar:hover {
|
||
border-color: var(--accent);
|
||
transform: translateY(-3px);
|
||
}
|
||
.pillar .material-symbols-outlined {
|
||
font-size: 26px;
|
||
color: var(--accent);
|
||
margin-bottom: 0.85rem;
|
||
}
|
||
.pillar h3 {
|
||
margin: 0 0 0.35rem;
|
||
font-size: 1rem;
|
||
letter-spacing: -0.015em;
|
||
}
|
||
.pillar p {
|
||
margin: 0;
|
||
font-size: 0.88rem;
|
||
color: var(--text-2);
|
||
line-height: 1.45;
|
||
}
|
||
|
||
/* —— Competitor matrix —— */
|
||
.vs-scroll {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
border: 1px solid var(--line);
|
||
border-radius: calc(var(--radius) + 4px);
|
||
background: var(--bg-elevated);
|
||
}
|
||
.vs-table {
|
||
width: 100%;
|
||
min-width: 720px;
|
||
border-collapse: collapse;
|
||
font-size: 0.88rem;
|
||
}
|
||
.vs-table th,
|
||
.vs-table td {
|
||
padding: 0.85rem 1rem;
|
||
text-align: left;
|
||
vertical-align: top;
|
||
border-bottom: 1px solid var(--line);
|
||
line-height: 1.45;
|
||
}
|
||
.vs-table thead th {
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
letter-spacing: -0.01em;
|
||
background: var(--bg-soft);
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
}
|
||
.vs-table tbody th {
|
||
font-weight: 600;
|
||
color: var(--text-2);
|
||
white-space: nowrap;
|
||
width: 7.5rem;
|
||
background: var(--bg-elevated);
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 1;
|
||
box-shadow: 4px 0 12px rgba(18, 18, 20, 0.04);
|
||
}
|
||
.vs-table tbody tr:last-child th,
|
||
.vs-table tbody tr:last-child td {
|
||
border-bottom: 0;
|
||
}
|
||
.vs-table .col-osg {
|
||
background: var(--accent-soft);
|
||
color: var(--text);
|
||
font-weight: 500;
|
||
}
|
||
.vs-table thead .col-osg {
|
||
color: var(--accent);
|
||
font-weight: 700;
|
||
}
|
||
.vs-table .cell-mute { color: var(--text-3); }
|
||
.vs-table .cell-price {
|
||
white-space: pre-line;
|
||
color: var(--text-2);
|
||
}
|
||
.vs-table .cell-price-warn {
|
||
color: var(--text);
|
||
font-weight: 500;
|
||
}
|
||
.vs-table .col-osg.cell-price {
|
||
color: var(--text);
|
||
font-weight: 600;
|
||
}
|
||
.vs-note {
|
||
margin: 1rem 0 0;
|
||
font-size: 0.9rem;
|
||
color: var(--text-2);
|
||
}
|
||
.vs-foot {
|
||
margin: 0.45rem 0 0;
|
||
font-size: 0.78rem;
|
||
color: var(--text-3);
|
||
}
|
||
|
||
/* —— BYOK points —— */
|
||
.compare {
|
||
border-radius: calc(var(--radius) + 4px);
|
||
border: 1px solid var(--line);
|
||
background: var(--hero-wash);
|
||
padding: 2rem 1.5rem;
|
||
overflow: hidden;
|
||
}
|
||
.compare-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 1.25rem;
|
||
}
|
||
@media (max-width: 800px) {
|
||
.compare-grid { grid-template-columns: 1fr 1fr; }
|
||
}
|
||
.compare-item {
|
||
padding: 0.5rem;
|
||
}
|
||
.compare-item .material-symbols-outlined {
|
||
font-size: 24px;
|
||
color: var(--accent);
|
||
margin-bottom: 0.6rem;
|
||
}
|
||
.compare-item h3 {
|
||
margin: 0 0 0.3rem;
|
||
font-size: 0.98rem;
|
||
}
|
||
.compare-item p {
|
||
margin: 0;
|
||
font-size: 0.88rem;
|
||
color: var(--text-2);
|
||
}
|
||
|
||
/* —— Steps —— */
|
||
.steps {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 1.5rem;
|
||
counter-reset: step;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.steps { grid-template-columns: 1fr; }
|
||
}
|
||
.step {
|
||
position: relative;
|
||
padding: 1.5rem 1.25rem 1.5rem 1.5rem;
|
||
border-top: 1px solid var(--line-strong);
|
||
}
|
||
.step::before {
|
||
counter-increment: step;
|
||
content: "0" counter(step);
|
||
display: block;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
color: var(--accent);
|
||
margin-bottom: 0.85rem;
|
||
}
|
||
.step h3 {
|
||
margin: 0 0 0.4rem;
|
||
font-size: 1.05rem;
|
||
}
|
||
.step p {
|
||
margin: 0;
|
||
color: var(--text-2);
|
||
font-size: 0.93rem;
|
||
}
|
||
|
||
/* —— Footer —— */
|
||
footer {
|
||
border-top: 1px solid var(--line);
|
||
padding: 2rem 0 2.75rem;
|
||
font-size: 0.85rem;
|
||
color: var(--text-3);
|
||
}
|
||
.footer-inner {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
align-items: center;
|
||
}
|
||
.footer-links {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1.1rem;
|
||
}
|
||
.footer-links a { color: var(--text-2); }
|
||
.footer-links a:hover { color: var(--text); }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation: none !important;
|
||
transition: none !important;
|
||
}
|
||
.reveal { opacity: 1; transform: none; }
|
||
.hero-stage { opacity: 1; transform: none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header class="nav">
|
||
<div class="wrap nav-inner">
|
||
<a class="brand" href="#top" aria-label="OSGKeyboard">
|
||
<svg class="brand-mark" viewBox="36 367 952 291" aria-hidden="true">
|
||
<g fill="currentColor">
|
||
<path d="M627.511 512.419C627.511 448.783 576.127 397.197 512.741 397.197C449.355 397.197 397.971 448.783 397.971 512.419C397.971 576.054 449.355 627.641 512.741 627.641V637.837C443.746 637.837 387.814 581.686 387.814 512.419C387.814 443.152 443.746 387 512.741 387C581.736 387 637.668 443.152 637.668 512.419C637.668 581.686 581.736 637.837 512.741 637.837V627.641C576.127 627.641 627.511 576.054 627.511 512.419Z"/>
|
||
<path d="M56.2632 512.419C56.2632 581.686 112.195 637.837 181.19 637.837C250.185 637.837 306.117 581.686 306.117 512.419C306.117 443.152 250.185 387 181.19 387C112.195 387 56.2632 443.152 56.2632 512.419Z"/>
|
||
<path d="M399.618 562.094L399.618 552.257L512.74 552.257L512.74 562.094L399.618 562.094Z"/>
|
||
<path d="M512.741 483.398V473.561H625.864V483.398H512.741Z"/>
|
||
<path d="M843.308 387.981C910.987 387.981 966.093 441.8 968.171 508.975H843.308V518.812H968.096C965.014 585.066 910.324 637.836 843.308 637.836C774.313 637.836 718.381 581.904 718.381 512.909C718.381 443.914 774.312 387.981 843.308 387.981ZM968.234 518.812H968.096C968.187 516.855 968.234 514.888 968.234 512.909C968.234 511.593 968.211 510.281 968.171 508.975H968.234V518.812Z"/>
|
||
</g>
|
||
</svg>
|
||
<span class="brand-name">OSGKeyboard</span>
|
||
</a>
|
||
<div class="nav-actions">
|
||
<div class="pill-toggle" role="group" aria-label="Language">
|
||
<button type="button" data-lang="en">EN</button>
|
||
<button type="button" class="active" data-lang="zh">中文</button>
|
||
</div>
|
||
<button type="button" class="icon-btn" id="themeToggle" aria-label="Toggle theme">
|
||
<span class="material-symbols-outlined" data-theme-icon>contrast</span>
|
||
</button>
|
||
<a class="nav-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">
|
||
<div class="hero-copy">
|
||
<div class="eyebrow">
|
||
<span class="material-symbols-outlined" aria-hidden="true">graphic_eq</span>
|
||
<span data-i18n="hero.eyebrow">Free · Open · Cross-platform</span>
|
||
</div>
|
||
<h1 data-i18n="hero.title">开口即文字。</h1>
|
||
<p class="lead" data-i18n="hero.lead">免费开源的跨端语音输入。本地识别,自带 API Key 润色,避免重复订阅。iPhone、iPad、Mac — 说完即落字。</p>
|
||
<div class="hero-cta">
|
||
<a class="btn btn-primary" href="https://github.com/hkgood/OSGKeyboard/releases" rel="noopener">
|
||
<span class="material-symbols-outlined" aria-hidden="true">download</span>
|
||
<span data-i18n="hero.cta.primary">立即获取</span>
|
||
</a>
|
||
<a class="btn btn-ghost" href="https://github.com/hkgood/OSGKeyboard" rel="noopener">
|
||
<span class="material-symbols-outlined" aria-hidden="true">terminal</span>
|
||
<span data-i18n="hero.cta.secondary">查看源码</span>
|
||
</a>
|
||
</div>
|
||
<div class="hero-meta">
|
||
<span><span class="material-symbols-outlined" aria-hidden="true">lock</span><span data-i18n="hero.meta1">默认本地识别</span></span>
|
||
<span><span class="material-symbols-outlined" aria-hidden="true">key</span><span data-i18n="hero.meta2">BYOK 不重复付费</span></span>
|
||
<span><span class="material-symbols-outlined" aria-hidden="true">devices</span><span data-i18n="hero.meta3">iOS · iPad · Mac</span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="hero-stage" aria-label="OSGKeyboard on Mac, iPad, and iPhone">
|
||
<img src="assets/hero-devices.png" width="3200" height="1432" alt="OSGKeyboard on iPad, Mac, and iPhone" loading="eager">
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Differentiation -->
|
||
<section class="block" id="why">
|
||
<div class="wrap">
|
||
<div class="block-head reveal">
|
||
<h2 data-i18n="diff.title">开源,尽是不同</h2>
|
||
<p data-i18n="diff.subtitle">免费、跨端、可审计、默认本地——差异写在产品里,不写在定价页。</p>
|
||
</div>
|
||
<div class="pillars">
|
||
<article class="pillar reveal">
|
||
<span class="material-symbols-outlined" aria-hidden="true">volunteer_activism</span>
|
||
<h3 data-i18n="diff.free.t">免费</h3>
|
||
<p data-i18n="diff.free.b">听写、历史、词库、跨端核心全开,没有 Pro 墙,不靠订阅解锁。</p>
|
||
</article>
|
||
<article class="pillar reveal reveal-delay-1">
|
||
<span class="material-symbols-outlined" aria-hidden="true">devices</span>
|
||
<h3 data-i18n="diff.cross.t">跨端</h3>
|
||
<p data-i18n="diff.cross.b">iPhone 键盘、iPad 分栏、Mac 全局热键,同一套体验;词库可同步。</p>
|
||
</article>
|
||
<article class="pillar reveal reveal-delay-2">
|
||
<span class="material-symbols-outlined" aria-hidden="true">code</span>
|
||
<h3 data-i18n="diff.open.t">开源</h3>
|
||
<p data-i18n="diff.open.b">GitHub 可审计、可 fork。你信任的是代码,不是隐私口号。</p>
|
||
</article>
|
||
<article class="pillar reveal reveal-delay-3">
|
||
<span class="material-symbols-outlined" aria-hidden="true">shield_lock</span>
|
||
<h3 data-i18n="diff.privacy.t">隐私</h3>
|
||
<p data-i18n="diff.privacy.b">默认本机识别,录音不上云;无追踪器,也不强制注册账号。</p>
|
||
</article>
|
||
<article class="pillar reveal reveal-delay-4">
|
||
<span class="material-symbols-outlined" aria-hidden="true">vpn_key</span>
|
||
<h3 data-i18n="diff.byok.t">BYOK</h3>
|
||
<p data-i18n="diff.byok.b">不填 Key 也能本地听写;要 AI 润色再带自己的 Key,不重复付费。</p>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Competitor matrix -->
|
||
<section class="block" id="vs">
|
||
<div class="wrap">
|
||
<div class="block-head reveal">
|
||
<h2 data-i18n="vs.title">和常见听写工具比一比</h2>
|
||
<p data-i18n="vs.subtitle">免费额度用着用着就撞墙;订阅不便宜。开源这条路:本地免费,云端 Key 自付。</p>
|
||
</div>
|
||
<div class="vs-scroll reveal">
|
||
<table class="vs-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col"></th>
|
||
<th scope="col">Typeless</th>
|
||
<th scope="col">Superwhisper</th>
|
||
<th scope="col">Openless</th>
|
||
<th scope="col" class="col-osg">OSGKeyboard</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<th scope="row" data-i18n="vs.row.open">开源</th>
|
||
<td class="cell-mute" data-i18n="vs.no">—</td>
|
||
<td class="cell-mute" data-i18n="vs.no">—</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="col-osg" data-i18n="vs.yes">✓</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row" data-i18n="vs.row.price">付费</th>
|
||
<td class="cell-price cell-price-warn" data-i18n="vs.typeless.price">每周约 8,000 词免费额度;订阅不便宜:Pro 约 $12/月起(年付)/ $30/月(月付)</td>
|
||
<td class="cell-price cell-price-warn" data-i18n="vs.superwhisper.price">免费档额度有限;订阅/买断不便宜:Pro 约 $8.5/月,买断约 $250</td>
|
||
<td class="cell-price" data-i18n="vs.openless.price">App 免费;识别 / 润色按你的 Key 计费</td>
|
||
<td class="col-osg cell-price" data-i18n="vs.osg.price">本地模式完全免费;云端润色 Key 自付(不另收听写订阅)</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row" data-i18n="vs.row.local">可本地识别</th>
|
||
<td class="cell-mute" data-i18n="vs.no">—</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="col-osg" data-i18n="vs.yes">✓</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row" data-i18n="vs.row.byok">BYOK</th>
|
||
<td class="cell-mute" data-i18n="vs.no">—</td>
|
||
<td data-i18n="vs.partial">部分(Pro)</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="col-osg" data-i18n="vs.yes">✓</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row">Mac</th>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="col-osg" data-i18n="vs.yes">✓</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row" data-i18n="vs.row.mobile">iPhone / iPad</th>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="cell-mute" data-i18n="vs.no">—</td>
|
||
<td class="col-osg" data-i18n="vs.yes">✓</td>
|
||
</tr>
|
||
<tr>
|
||
<th scope="row">Windows</th>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td data-i18n="vs.yes">✓</td>
|
||
<td class="col-osg cell-mute" data-i18n="vs.no">—</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="vs-note reveal" data-i18n="vs.note">与 Openless 同属开源阵营;我们补上 iPhone / iPad,暂无 Windows。</p>
|
||
<p class="vs-foot reveal" data-i18n="vs.foot">价格与功能据各产品公开说明整理,可能更新,以官方为准。</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- BYOK -->
|
||
<section class="block" id="byok">
|
||
<div class="wrap">
|
||
<div class="block-head reveal">
|
||
<h2 data-i18n="compare.title">BYOK,不花冤枉钱</h2>
|
||
<p data-i18n="compare.subtitle">订阅常把「听写壳」和「模型用量」捆在一起。本地识别免费起步;AI 润色按你已有的额度计费。</p>
|
||
</div>
|
||
<div class="compare reveal">
|
||
<div class="compare-grid">
|
||
<div class="compare-item">
|
||
<span class="material-symbols-outlined" aria-hidden="true">payments</span>
|
||
<h3 data-i18n="compare.1.t">躲开重复付费</h3>
|
||
<p data-i18n="compare.1.b">已有 DeepSeek / OpenAI 额度就直接用,不必再开一份「听写订阅」。</p>
|
||
</div>
|
||
<div class="compare-item">
|
||
<span class="material-symbols-outlined" aria-hidden="true">visibility</span>
|
||
<h3 data-i18n="compare.2.t">费用看得见</h3>
|
||
<p data-i18n="compare.2.b">模型账单只在服务商侧;OSGKeyboard 不经手、不加价。</p>
|
||
</div>
|
||
<div class="compare-item">
|
||
<span class="material-symbols-outlined" aria-hidden="true">swap_horiz</span>
|
||
<h3 data-i18n="compare.3.t">模型你说了算</h3>
|
||
<p data-i18n="compare.3.b">OpenAI 兼容端点即可接入;不需要 AI 时,本地识别照用不填 Key。</p>
|
||
</div>
|
||
<div class="compare-item">
|
||
<span class="material-symbols-outlined" aria-hidden="true">encrypted</span>
|
||
<h3 data-i18n="compare.4.t">Key 留在本机</h3>
|
||
<p data-i18n="compare.4.b">写入系统 Keychain,不经过我们的服务器。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Steps -->
|
||
<section class="block" id="start">
|
||
<div class="wrap">
|
||
<div class="block-head reveal">
|
||
<h2 data-i18n="steps.title">开箱可用,只要三步</h2>
|
||
<p data-i18n="steps.subtitle">不用订阅、不用注册账号。装好权限,说第一句就行。</p>
|
||
</div>
|
||
<div class="steps">
|
||
<div class="step reveal">
|
||
<h3 data-i18n="step1.t">装好并授权</h3>
|
||
<p data-i18n="step1.b">iOS:设置 → 键盘 → 开启完全访问。Mac:麦克风 + 辅助功能。完成时键盘列表能看到 OSG,或菜单栏出现图标。</p>
|
||
</div>
|
||
<div class="step reveal reveal-delay-1">
|
||
<h3 data-i18n="step2.t">先本地,Key 可选</h3>
|
||
<p data-i18n="step2.b">默认本地识别即可开口;要 AI 润色再填自己的 API Key。不配 Key 也能落字。</p>
|
||
</div>
|
||
<div class="step reveal reveal-delay-2">
|
||
<h3 data-i18n="step3.t">开口即写</h3>
|
||
<p data-i18n="step3.b">iOS 切到 OSGKeyboard 点麦克风;Mac 按住 Option。文字出现在当前光标处。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<footer>
|
||
<div class="wrap footer-inner">
|
||
<span data-i18n="footer.copy">© OSGKeyboard · v0.5.4 · 源码可见</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": "Free · Open · Cross-platform",
|
||
"hero.title": "Speak it. It's typed.",
|
||
"hero.lead": "Free, open-source voice input across iPhone, iPad, and Mac. On-device recognition. Bring your own API key — no double-billing.",
|
||
"hero.cta.primary": "Get it now",
|
||
"hero.cta.secondary": "View source",
|
||
"hero.meta1": "On-device by default",
|
||
"hero.meta2": "BYOK — no double pay",
|
||
"hero.meta3": "iOS · iPad · Mac",
|
||
"diff.title": "Open source. Built different.",
|
||
"diff.subtitle": "Free, cross-platform, auditable, on-device by default — designed in, not gated by pricing.",
|
||
"diff.free.t": "Free",
|
||
"diff.free.b": "Dictation, history, dictionary, and cross-device cores stay open. No Pro wall.",
|
||
"diff.cross.t": "Cross-platform",
|
||
"diff.cross.b": "iPhone keyboard, iPad split view, Mac global hotkey — one product language; dictionary can sync.",
|
||
"diff.open.t": "Open source",
|
||
"diff.open.b": "Audit and fork on GitHub. Trust the code, not a privacy slogan.",
|
||
"diff.privacy.t": "Privacy",
|
||
"diff.privacy.b": "On-device ASR by default. Audio stays local. No trackers, no forced account.",
|
||
"diff.byok.t": "BYOK",
|
||
"diff.byok.b": "Local dictation works without a key. Bring your own key only when you want AI polish.",
|
||
"vs.title": "How we compare",
|
||
"vs.subtitle": "Free tiers run out. Subscriptions add up. Open source: local free, cloud on your key.",
|
||
"vs.row.open": "Open source",
|
||
"vs.row.price": "Pricing",
|
||
"vs.row.local": "On-device ASR",
|
||
"vs.row.byok": "BYOK",
|
||
"vs.row.mobile": "iPhone / iPad",
|
||
"vs.yes": "✓",
|
||
"vs.no": "—",
|
||
"vs.partial": "Partial (Pro)",
|
||
"vs.typeless.price": "≈8,000 words/week free.\nSubscription isn’t cheap: Pro ~$12/mo (annual) / $30/mo (monthly).",
|
||
"vs.superwhisper.price": "Limited free tier.\nSub / lifetime isn’t cheap: Pro ~$8.5/mo, lifetime ~$250.",
|
||
"vs.openless.price": "Free app; ASR / polish billed to your key.",
|
||
"vs.osg.price": "Local mode fully free.\nCloud polish via your key — no dictation subscription.",
|
||
"vs.note": "Same open-source lane as Openless — we add iPhone / iPad; Windows isn’t available yet.",
|
||
"vs.foot": "Prices and features from public product pages; may change — check each vendor.",
|
||
"compare.title": "BYOK — skip the markup",
|
||
"compare.subtitle": "Subscriptions often bundle the dictation shell with model usage. Local ASR starts free; AI polish bills your existing credits.",
|
||
"compare.1.t": "No double billing",
|
||
"compare.1.b": "Use DeepSeek / OpenAI credits you already have — skip another dictation subscription.",
|
||
"compare.2.t": "Costs you can see",
|
||
"compare.2.b": "Model invoices stay with your provider. We never touch or markup them.",
|
||
"compare.3.t": "You pick the model",
|
||
"compare.3.b": "Any OpenAI-compatible endpoint. Skip the key entirely when local ASR is enough.",
|
||
"compare.4.t": "Keys stay on-device",
|
||
"compare.4.b": "Stored in Keychain. Never routed through our servers.",
|
||
"steps.title": "Ready in three steps",
|
||
"steps.subtitle": "No subscription. No account. Grant permissions once, then speak.",
|
||
"step1.t": "Install & authorize",
|
||
"step1.b": "iOS: Settings → Keyboard → Full Access. Mac: Microphone + Accessibility. Done when OSG appears in the keyboard list or the menu bar.",
|
||
"step2.t": "Local first, key optional",
|
||
"step2.b": "On-device ASR works out of the box. Add your API key only for AI polish — text still lands without one.",
|
||
"step3.t": "Speak it. It's typed.",
|
||
"step3.b": "iOS: switch to OSGKeyboard and tap the mic. Mac: hold Option. Text appears at the cursor.",
|
||
"footer.copy": "© OSGKeyboard · v0.5.4 · source available",
|
||
"footer.source": "Source",
|
||
"footer.privacy": "Privacy",
|
||
"footer.license": "License"
|
||
},
|
||
zh: {
|
||
"nav.github": "GitHub",
|
||
"hero.eyebrow": "免费 · 开源 · 跨端",
|
||
"hero.title": "开口即文字。",
|
||
"hero.lead": "免费开源的跨端语音输入。本地识别,自带 API Key 润色,避免重复订阅。iPhone、iPad、Mac — 说完即落字。",
|
||
"hero.cta.primary": "立即获取",
|
||
"hero.cta.secondary": "查看源码",
|
||
"hero.meta1": "默认本地识别",
|
||
"hero.meta2": "BYOK 不重复付费",
|
||
"hero.meta3": "iOS · iPad · Mac",
|
||
"diff.title": "开源,尽是不同",
|
||
"diff.subtitle": "免费、跨端、可审计、默认本地——差异写在产品里,不写在定价页。",
|
||
"diff.free.t": "免费",
|
||
"diff.free.b": "听写、历史、词库、跨端核心全开,没有 Pro 墙,不靠订阅解锁。",
|
||
"diff.cross.t": "跨端",
|
||
"diff.cross.b": "iPhone 键盘、iPad 分栏、Mac 全局热键,同一套体验;词库可同步。",
|
||
"diff.open.t": "开源",
|
||
"diff.open.b": "GitHub 可审计、可 fork。你信任的是代码,不是隐私口号。",
|
||
"diff.privacy.t": "隐私",
|
||
"diff.privacy.b": "默认本机识别,录音不上云;无追踪器,也不强制注册账号。",
|
||
"diff.byok.t": "BYOK",
|
||
"diff.byok.b": "不填 Key 也能本地听写;要 AI 润色再带自己的 Key,不重复付费。",
|
||
"vs.title": "和常见听写工具比一比",
|
||
"vs.subtitle": "免费额度用着用着就撞墙;订阅不便宜。开源这条路:本地免费,云端 Key 自付。",
|
||
"vs.row.open": "开源",
|
||
"vs.row.price": "付费",
|
||
"vs.row.local": "可本地识别",
|
||
"vs.row.byok": "BYOK",
|
||
"vs.row.mobile": "iPhone / iPad",
|
||
"vs.yes": "✓",
|
||
"vs.no": "—",
|
||
"vs.partial": "部分(Pro)",
|
||
"vs.typeless.price": "每周约 8,000 词免费额度。\n订阅不便宜:Pro 约 $12/月起(年付)/ $30/月(月付)。",
|
||
"vs.superwhisper.price": "免费档额度有限。\n订阅/买断不便宜:Pro 约 $8.5/月,买断约 $250。",
|
||
"vs.openless.price": "App 免费;识别 / 润色按你的 Key 计费。",
|
||
"vs.osg.price": "本地模式完全免费。\n云端润色 Key 自付(不另收听写订阅)。",
|
||
"vs.note": "与 Openless 同属开源阵营;我们补上 iPhone / iPad,暂无 Windows。",
|
||
"vs.foot": "价格与功能据各产品公开说明整理,可能更新,以官方为准。",
|
||
"compare.title": "BYOK,不花冤枉钱",
|
||
"compare.subtitle": "订阅常把「听写壳」和「模型用量」捆在一起。本地识别免费起步;AI 润色按你已有的额度计费。",
|
||
"compare.1.t": "躲开重复付费",
|
||
"compare.1.b": "已有 DeepSeek / OpenAI 额度就直接用,不必再开一份「听写订阅」。",
|
||
"compare.2.t": "费用看得见",
|
||
"compare.2.b": "模型账单只在服务商侧;OSGKeyboard 不经手、不加价。",
|
||
"compare.3.t": "模型你说了算",
|
||
"compare.3.b": "OpenAI 兼容端点即可接入;不需要 AI 时,本地识别照用不填 Key。",
|
||
"compare.4.t": "Key 留在本机",
|
||
"compare.4.b": "写入系统 Keychain,不经过我们的服务器。",
|
||
"steps.title": "开箱可用,只要三步",
|
||
"steps.subtitle": "不用订阅、不用注册账号。装好权限,说第一句就行。",
|
||
"step1.t": "装好并授权",
|
||
"step1.b": "iOS:设置 → 键盘 → 开启完全访问。Mac:麦克风 + 辅助功能。完成时键盘列表能看到 OSG,或菜单栏出现图标。",
|
||
"step2.t": "先本地,Key 可选",
|
||
"step2.b": "默认本地识别即可开口;要 AI 润色再填自己的 API Key。不配 Key 也能落字。",
|
||
"step3.t": "开口即写",
|
||
"step3.b": "iOS 切到 OSGKeyboard 点麦克风;Mac 按住 Option。文字出现在当前光标处。",
|
||
"footer.copy": "© OSGKeyboard · v0.5.4 · 源码可见",
|
||
"footer.source": "源代码",
|
||
"footer.privacy": "隐私",
|
||
"footer.license": "许可协议"
|
||
}
|
||
};
|
||
|
||
function applyLang(lang) {
|
||
const strings = copy[lang] || copy.zh;
|
||
document.documentElement.lang = lang === "zh" ? "zh-Hans" : "en";
|
||
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
||
const value = strings[el.getAttribute("data-i18n")];
|
||
if (value != null) el.textContent = value;
|
||
});
|
||
document.querySelectorAll(".pill-toggle button[data-lang]").forEach((btn) => {
|
||
btn.classList.toggle("active", btn.dataset.lang === lang);
|
||
});
|
||
try { localStorage.setItem("osg-lang", lang); } catch (_) {}
|
||
applyShots(lang);
|
||
}
|
||
|
||
function resolvedTheme() {
|
||
const theme = document.documentElement.getAttribute("data-theme") || "system";
|
||
if (theme === "light" || theme === "dark") return theme;
|
||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||
}
|
||
|
||
function shotURL(name, langOverride) {
|
||
let lang = langOverride;
|
||
if (!lang) {
|
||
try { lang = localStorage.getItem("osg-lang"); } catch (_) {}
|
||
}
|
||
if (!lang) lang = document.documentElement.lang.startsWith("zh") ? "zh" : "en";
|
||
const theme = resolvedTheme();
|
||
return `assets/screenshots/${lang}/${theme}/${name}.png`;
|
||
}
|
||
|
||
function applyShots(langOverride) {
|
||
document.querySelectorAll("img[data-shot]").forEach((img) => {
|
||
const name = img.getAttribute("data-shot");
|
||
if (!name) return;
|
||
const next = shotURL(name, langOverride);
|
||
if (img.getAttribute("src") !== next) img.setAttribute("src", next);
|
||
});
|
||
}
|
||
|
||
function applyTheme(theme) {
|
||
document.documentElement.setAttribute("data-theme", theme);
|
||
const icon = document.querySelector("[data-theme-icon]");
|
||
if (icon) {
|
||
icon.textContent = theme === "dark" ? "light_mode" : theme === "light" ? "dark_mode" : "contrast";
|
||
}
|
||
try { localStorage.setItem("osg-theme", theme); } catch (_) {}
|
||
applyShots();
|
||
}
|
||
|
||
function cycleTheme() {
|
||
const current = document.documentElement.getAttribute("data-theme") || "system";
|
||
const next = current === "system" ? "light" : current === "light" ? "dark" : "system";
|
||
applyTheme(next);
|
||
}
|
||
|
||
document.querySelectorAll(".pill-toggle button[data-lang]").forEach((btn) => {
|
||
btn.addEventListener("click", () => applyLang(btn.dataset.lang));
|
||
});
|
||
document.getElementById("themeToggle")?.addEventListener("click", cycleTheme);
|
||
|
||
const savedLang = (() => { try { return localStorage.getItem("osg-lang"); } catch (_) { return null; } })();
|
||
const savedTheme = (() => { try { return localStorage.getItem("osg-theme"); } catch (_) { return null; } })();
|
||
applyLang(savedLang || ((navigator.language || "").startsWith("zh") ? "zh" : "en"));
|
||
applyTheme(savedTheme || "system");
|
||
|
||
try {
|
||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
||
if ((document.documentElement.getAttribute("data-theme") || "system") === "system") {
|
||
applyShots();
|
||
}
|
||
});
|
||
} catch (_) {}
|
||
|
||
// Scroll reveal
|
||
const reveals = document.querySelectorAll(".reveal");
|
||
if ("IntersectionObserver" in window) {
|
||
const io = new IntersectionObserver((entries) => {
|
||
entries.forEach((entry) => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add("in");
|
||
io.unobserve(entry.target);
|
||
}
|
||
});
|
||
}, { rootMargin: "0px 0px -8% 0px", threshold: 0.12 });
|
||
reveals.forEach((el) => io.observe(el));
|
||
} else {
|
||
reveals.forEach((el) => el.classList.add("in"));
|
||
}
|
||
|
||
// Soft parallax after hero entrance animation settles
|
||
const stage = document.querySelector(".hero-stage");
|
||
if (stage && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
||
let ready = false;
|
||
setTimeout(() => { ready = true; }, 1100);
|
||
window.addEventListener("scroll", () => {
|
||
if (!ready) return;
|
||
const y = Math.min(window.scrollY * 0.06, 28);
|
||
stage.style.transform = `translateY(${y}px)`;
|
||
}, { passive: true });
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|