Files
wangjia 40760aa884
ci / server (push) Failing after 14s
ci / design-tokens (push) Failing after 11s
dudu MVP:五端语音输入法初始提交
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调
- desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导)
- android:Compose 主 App + IME(键盘内录音直传)
- ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写)
- design/design-pipeline:设计系统 + token 导出 iOS/Android 主题
- doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:38:37 +08:00

624 lines
28 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dudu — 按住说话,文字上屏</title>
<meta name="description" content="dudu 语音输入法:大模型识别,流式实时上屏。Mac / Windows / iPhone / iPad / Android。每天免费 3 分钟。">
<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@500;600;700&display=swap" rel="stylesheet">
<style>
/* ────────────────────────────────────────────────────────────────
令牌来源 design/tokens/(宣传页独立部署,按值内联;改动需同步)
品牌硬规则:单一品牌蓝 · 无渐变 · 无 emoji · 波形动画仅录音中
──────────────────────────────────────────────────────────────── */
:root {
--bg: #F6F7FA;
--card: #FFFFFF;
--surface-2: #F0F1F4;
--surface-3: #E4E6EB;
--border-1: #E4E6EB;
--border-2: #D2D5DD;
--text-1: #1B1E26;
--text-2: #5A6072;
--text-3: #A6ABB8;
--accent: #4F6EF7;
--accent-hover: #3D58DB;
--accent-press: #2F44B3;
--accent-soft: #EEF1FE;
--accent-text: #3D58DB;
--positive: #16A34A;
--positive-soft: #DCFCE7;
--warning: #E8890C;
--warning-soft: #FCEFD7;
--ink: #11131A;
--ink-card: #171A22;
--ink-border: #272C38;
--ink-text-2: #9AA1B2;
--overlay-bg: rgba(24, 26, 34, 0.95);
--overlay-text: #F2F4FA;
--overlay-text-2: #9AA3BD;
--overlay-text-3: #646E8C;
--shadow-overlay: 0 12px 36px rgba(10, 12, 18, 0.38);
--shadow-card: 0 1px 3px rgba(17, 19, 26, 0.05);
--shadow-window: 0 8px 28px rgba(17, 19, 26, 0.14);
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
--sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "MiSans",
"HarmonyOS Sans SC", "Microsoft YaHei", sans-serif;
--latin: "Outfit", -apple-system, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, monospace;
}
* { box-sizing: border-box; margin: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
background: var(--bg);
color: var(--text-1);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
::selection { background: var(--accent-soft); color: var(--accent-text); }
a { color: inherit; text-decoration: none; }
.latin { font-family: var(--latin); }
/* ── 结构网格:1px 边线是主要分隔语言 ── */
.frame { max-width: 1120px; margin: 0 auto; padding: 0 32px; }
@media (max-width: 720px) { .frame { padding: 0 20px; } }
/* ── 导航 ── */
nav {
position: sticky; top: 0; z-index: 50;
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--border-1);
}
.nav-inner {
display: flex; align-items: center; justify-content: space-between;
height: 60px;
}
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--text-1); }
.brand svg { color: var(--accent); }
.brand .name { font-family: var(--latin); font-weight: 600; font-size: 19px; letter-spacing: .02em; }
.nav-links { display: flex; align-items: center; gap: 28px; font-size: 14px; color: var(--text-2); }
.nav-links a:hover { color: var(--text-1); }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
border: 1px solid transparent; border-radius: 6px; cursor: pointer;
font-family: var(--sans); font-weight: 500; white-space: nowrap;
transition: background 120ms var(--ease-out), border-color 120ms var(--ease-out);
}
.btn-sm { height: 32px; padding: 0 14px; font-size: 13px; }
.btn-lg { height: 48px; padding: 0 26px; font-size: 16px; border-radius: 999px; }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-press); }
.btn-secondary { background: var(--card); border-color: var(--border-2); color: var(--text-1); }
.btn-secondary:hover { background: var(--surface-2); }
.btn-ghost { background: transparent; color: var(--accent-text); }
.btn-ghost:hover { background: var(--accent-soft); }
/* ── Hero ── */
.hero { border-bottom: 1px solid var(--border-1); overflow: hidden; }
.hero-grid {
display: grid; grid-template-columns: 6fr 5fr; gap: 48px;
padding: 96px 0 110px; align-items: center;
}
@media (max-width: 920px) {
.hero-grid { grid-template-columns: 1fr; padding: 64px 0 72px; gap: 56px; }
}
.hero h1 {
font-size: clamp(48px, 7.2vw, 84px);
line-height: 1.12; letter-spacing: -0.015em;
font-weight: 700;
}
.hero h1 .accent-word { color: var(--accent); }
.hero .sub {
margin-top: 22px; font-size: 18px; color: var(--text-2);
display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
}
.hero .sub .dot { color: var(--text-3); }
.hero-cta { margin-top: 38px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.hero-note { font-size: 13px; color: var(--text-3); }
.hero-note .num { font-family: var(--latin); }
/* ── 演示剧场 ── */
.stage { position: relative; }
.demo-window {
background: var(--card); border: 1px solid var(--border-1);
border-radius: 14px; box-shadow: var(--shadow-window); overflow: hidden;
}
.demo-titlebar {
display: flex; align-items: center; gap: 7px;
padding: 11px 14px; border-bottom: 1px solid var(--border-1); background: var(--surface-2);
}
.demo-titlebar i { width: 11px; height: 11px; border-radius: 50%; background: var(--border-2); }
.demo-titlebar span { margin-left: 8px; font-size: 12px; color: var(--text-3); }
.demo-doc { padding: 22px 24px 30px; min-height: 150px; font-size: 16px; line-height: 1.9; }
.demo-doc .placeholder { color: var(--text-3); }
.caret {
display: inline-block; width: 2px; height: 1.15em; vertical-align: -0.18em;
background: var(--accent); animation: blink 1.1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.overlay {
position: absolute; left: 50%; transform: translateX(-50%);
bottom: -34px; width: min(340px, 86%);
background: var(--overlay-bg); color: var(--overlay-text);
border-radius: 14px; padding: 14px 18px;
box-shadow: var(--shadow-overlay);
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
opacity: 0; translate: 0 4px;
transition: opacity 200ms var(--ease-out), translate 200ms var(--ease-out);
pointer-events: none;
}
.overlay.show { opacity: 1; translate: 0 0; }
.wave { display: inline-flex; align-items: center; gap: 3px; height: 24px; color: #7C9BFF; }
.wave i { display: block; width: 3px; border-radius: 2px; background: currentColor; }
.recording .wave i { animation: wavebounce 0.9s cubic-bezier(0.45, 0, 0.2, 1) infinite alternate; }
@keyframes wavebounce { from { transform: scaleY(0.35); } to { transform: scaleY(1); } }
.overlay .ovl-status {
display: flex; align-items: center; gap: 8px;
color: var(--overlay-text-2); font-size: 15px; margin-top: 8px;
}
.overlay .ovl-text { font-size: 15px; line-height: 1.6; margin-top: 8px; min-height: 24px; }
.overlay .ovl-text .partial { color: var(--overlay-text-2); }
.overlay .ovl-hint { font-size: 11px; color: var(--overlay-text-3); margin-top: 8px; text-align: right; }
.micbar {
margin-top: 64px; width: 100%; height: 56px; border: none; cursor: pointer;
border-radius: 999px; background: var(--accent); color: #fff;
font-family: var(--sans); font-size: 16px; font-weight: 600;
display: inline-flex; align-items: center; justify-content: center; gap: 9px;
user-select: none; -webkit-user-select: none; touch-action: none;
transition: background 120ms var(--ease-out);
}
.micbar:active, .micbar.recording { background: var(--positive); }
.micbar:hover:not(.recording) { background: var(--accent-hover); }
.stage-tip { margin-top: 12px; text-align: center; font-size: 13px; color: var(--text-3); }
kbd {
font-family: var(--mono); font-size: 12px; color: var(--text-2);
background: var(--surface-2); border: 1px solid var(--border-2); border-bottom-width: 2px;
border-radius: 4px; padding: 1px 7px;
}
/* ── 指标带(墨色) ── */
.metrics { background: var(--ink); color: #ECEEF4; border-bottom: 1px solid var(--ink-border); }
.metrics-grid { display: grid; grid-template-columns: repeat(3, 1fr); }
.metric {
padding: 44px 32px; text-align: center;
border-left: 1px solid var(--ink-border);
}
.metric:first-child { border-left: none; }
.metric .num {
font-family: var(--latin); font-weight: 700;
font-size: clamp(34px, 4.4vw, 52px); line-height: 1.1;
}
.metric .num small { font-size: 0.45em; font-weight: 600; color: var(--ink-text-2); margin-left: 2px; }
.metric .label { margin-top: 8px; font-size: 14px; color: var(--ink-text-2); }
@media (max-width: 720px) {
.metrics-grid { grid-template-columns: 1fr; }
.metric { border-left: none; border-top: 1px solid var(--ink-border); padding: 28px 20px; }
.metric:first-child { border-top: none; }
}
/* ── 章节通用 ── */
section { border-bottom: 1px solid var(--border-1); }
.section-head { padding: 88px 0 0; }
.kicker {
font-size: 13px; font-weight: 600; color: var(--accent-text);
letter-spacing: .12em;
}
.section-head h2 {
margin-top: 12px; font-size: clamp(30px, 4vw, 44px);
line-height: 1.25; letter-spacing: -0.01em; font-weight: 700; max-width: 18em;
}
.reveal { opacity: 0; translate: 0 14px; transition: opacity 320ms var(--ease-out), translate 320ms var(--ease-out); }
.reveal.in { opacity: 1; translate: 0 0; }
/* ── 特性 ── */
.features {
display: grid; grid-template-columns: repeat(3, 1fr);
margin: 56px 0 96px; border: 1px solid var(--border-1); border-radius: 14px;
background: var(--card); overflow: hidden;
}
.feature { padding: 36px 32px 40px; border-left: 1px solid var(--border-1); }
.feature:first-child { border-left: none; }
.feature .icon {
width: 44px; height: 44px; border-radius: 10px;
background: var(--accent-soft); color: var(--accent-text);
display: inline-flex; align-items: center; justify-content: center;
}
.feature h3 { margin-top: 18px; font-size: 19px; font-weight: 600; }
.feature p { margin-top: 8px; font-size: 14.5px; color: var(--text-2); }
@media (max-width: 820px) {
.features { grid-template-columns: 1fr; }
.feature { border-left: none; border-top: 1px solid var(--border-1); }
.feature:first-child { border-top: none; }
}
/* ── 三步 ── */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; margin: 64px 0 96px; }
.step .no {
font-family: var(--latin); font-weight: 700; font-size: 72px; line-height: 1;
color: var(--surface-3);
}
.step h3 { margin-top: 14px; font-size: 19px; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.step p { margin-top: 6px; font-size: 14.5px; color: var(--text-2); max-width: 26em; }
@media (max-width: 820px) { .steps { grid-template-columns: 1fr; gap: 40px; } }
/* ── 平台 ── */
.platforms { display: flex; flex-wrap: wrap; gap: 12px; margin: 48px 0 96px; }
.platform {
display: inline-flex; align-items: center; gap: 9px;
padding: 12px 22px; border: 1px solid var(--border-1); border-radius: 999px;
background: var(--card); font-size: 15px; font-weight: 500; box-shadow: var(--shadow-card);
}
.platform svg { color: var(--text-2); }
.platform .soon { font-size: 12px; color: var(--text-3); }
/* ── 定价 ── */
.pricing { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 56px 0 28px; align-items: stretch; }
.price-card {
background: var(--card); border: 1px solid var(--border-1); border-radius: 14px;
padding: 30px 28px 28px; display: flex; flex-direction: column; box-shadow: var(--shadow-card);
position: relative;
}
.price-card.hot { border: 1.5px solid var(--accent); }
.price-card .tag {
position: absolute; top: -11px; left: 24px;
font-size: 12px; font-weight: 600; padding: 2px 12px; border-radius: 999px;
background: var(--warning-soft); color: var(--warning);
}
.price-card .minutes { font-size: 17px; font-weight: 600; }
.price-card .minutes .num { font-family: var(--latin); font-size: 22px; }
.price-card .price { margin-top: 14px; font-family: var(--latin); font-weight: 700; font-size: 56px; line-height: 1; }
.price-card .price .cur { font-size: 24px; font-weight: 600; vertical-align: 14px; margin-right: 2px; }
.price-card .unit { margin-top: 8px; font-size: 13px; color: var(--text-3); }
.price-card .btn { margin-top: 26px; width: 100%; }
.pricing-note { text-align: center; font-size: 14px; color: var(--text-2); padding-bottom: 96px; }
.pricing-note .num { font-family: var(--latin); }
@media (max-width: 820px) { .pricing { grid-template-columns: 1fr; } }
/* ── 尾部 CTA(墨色) ── */
.cta { background: var(--ink); color: #ECEEF4; border-bottom: 1px solid var(--ink-border); }
.cta-inner { padding: 110px 0; text-align: center; }
.cta .logo-big { color: #5C77E8; }
.cta h2 { margin-top: 26px; font-size: clamp(34px, 5vw, 56px); font-weight: 700; letter-spacing: -0.01em; }
.cta p { margin-top: 14px; color: var(--ink-text-2); font-size: 16px; }
.cta .btn { margin-top: 36px; }
.cta .btn-primary { background: #5C77E8; }
.cta .btn-primary:hover { background: #6E89F9; }
/* ── 页脚 ── */
footer { background: var(--ink); color: var(--ink-text-2); }
.footer-inner {
display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px;
padding: 28px 0; font-size: 13px;
}
.footer-inner .brand { color: #ECEEF4; }
.footer-inner .brand svg { color: #5C77E8; }
.footer-links { display: flex; gap: 22px; }
.footer-links a:hover { color: #ECEEF4; }
</style>
</head>
<body>
<!-- ════════ 导航 ════════ -->
<nav>
<div class="frame nav-inner">
<a class="brand" href="#">
<svg width="26" height="26" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"><circle cx="24" cy="24" r="18"/><path d="M17 20.5v7"/><path d="M24 16.5v15"/><path d="M31 20.5v7"/></svg>
<span class="name">dudu</span>
</a>
<div class="nav-links">
<a href="#features">特性</a>
<a href="#how">怎么用</a>
<a href="#pricing">价格</a>
<a class="btn btn-sm btn-secondary" href="#download">下载</a>
</div>
</div>
</nav>
<!-- ════════ Hero ════════ -->
<header class="hero">
<div class="frame hero-grid">
<div>
<h1>按住说话<br><span class="accent-word">文字上屏</span></h1>
<div class="sub">
<span>大模型识别</span><span class="dot">·</span>
<span>流式实时</span><span class="dot">·</span>
<span>全平台输入法</span>
</div>
<div class="hero-cta">
<a class="btn btn-lg btn-primary" href="#download">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="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" x2="12" y1="15" y2="3"/></svg>
免费下载
</a>
<span class="hero-note">每天免费 <span class="num">3</span> 分钟 · 微信登录即用</span>
</div>
</div>
<!-- 演示剧场:按住下方按钮(或长按空格)真实体验流式上屏 -->
<div class="stage" id="stage">
<div class="demo-window">
<div class="demo-titlebar"><i></i><i></i><i></i><span>备忘录 — 周报草稿</span></div>
<div class="demo-doc">
<span id="doc-text"></span><span class="placeholder" id="doc-placeholder">光标在这里 — 在任意应用按住快捷键即可语音输入</span><span class="caret"></span>
</div>
<div class="overlay" id="overlay">
<span class="wave" id="wave"></span>
<div class="ovl-status" id="ovl-status">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
<span>聆听中…</span>
</div>
<div class="ovl-text" id="ovl-text" hidden></div>
<div class="ovl-hint">松开 ⌘⇧Space 完成输入</div>
</div>
</div>
<button class="micbar" id="micbar" type="button">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
<span id="micbar-label">按住试一试</span>
</button>
<div class="stage-tip">就像在你电脑上按住 <kbd></kbd><kbd></kbd><kbd>Space</kbd> 一样</div>
</div>
</div>
</header>
<!-- ════════ 指标带 ════════ -->
<div class="metrics">
<div class="frame metrics-grid">
<div class="metric reveal">
<div class="num">&lt;500<small>ms</small></div>
<div class="label">按下到首字出现</div>
</div>
<div class="metric reveal" style="transition-delay:60ms">
<div class="num">5<small></small></div>
<div class="label">Mac · Windows · iPhone · iPad · Android</div>
</div>
<div class="metric reveal" style="transition-delay:120ms">
<div class="num">3<small>分钟/天</small></div>
<div class="label">免费试用 · 不需要先付费</div>
</div>
</div>
</div>
<!-- ════════ 特性 ════════ -->
<section id="features">
<div class="frame">
<div class="section-head reveal">
<div class="kicker">为什么是 dudu</div>
<h2>识别得准,上屏得快<br>用起来像没有这层软件</h2>
</div>
<div class="features reveal">
<div class="feature">
<span class="icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v3"/></svg></span>
<h3>大模型识别</h3>
<p>云端大模型听写,中英混说、专有名词、自动标点都不在话下,说完即定稿</p>
</div>
<div class="feature">
<span class="icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg></span>
<h3>流式实时上屏</h3>
<p>说话的同时文字就出现,不等你说完;松开按键,整句注入焦点输入框</p>
</div>
<div class="feature">
<span class="icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg></span>
<h3>哪里都能用</h3>
<p>桌面端快捷键全局可用,手机上是真正的系统键盘——微信、文档、代码编辑器,焦点在哪写到哪</p>
</div>
</div>
</div>
</section>
<!-- ════════ 怎么用 ════════ -->
<section id="how">
<div class="frame">
<div class="section-head reveal">
<div class="kicker">怎么用</div>
<h2>三个动作,零学习成本</h2>
</div>
<div class="steps">
<div class="step reveal">
<div class="no">01</div>
<h3>按住 <kbd></kbd><kbd></kbd><kbd>Space</kbd></h3>
<p>桌面端任意输入框旁按住快捷键;手机上按住键盘的大麦克风</p>
</div>
<div class="step reveal" style="transition-delay:60ms">
<div class="no">02</div>
<h3>说话</h3>
<p>识别结果实时浮现,说错了上滑取消,不打断你的思路</p>
</div>
<div class="step reveal" style="transition-delay:120ms">
<div class="no">03</div>
<h3>松开,上屏</h3>
<p>整句文字直接写进光标处,标点已排好,接着说下一句就行</p>
</div>
</div>
</div>
</section>
<!-- ════════ 平台 ════════ -->
<section id="download">
<div class="frame">
<div class="section-head reveal">
<div class="kicker">全平台</div>
<h2>你的每一台设备</h2>
</div>
<div class="platforms reveal">
<span class="platform"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>Mac</span>
<span class="platform"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>Windows</span>
<span class="platform"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2"/><path d="M12 18h.01"/></svg>iPhone<span class="soon">键盘扩展</span></span>
<span class="platform"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="22" x="3" y="1" rx="2"/><path d="M12 19h.01"/></svg>iPad</span>
<span class="platform"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="2"/><path d="M12 18h.01"/></svg>Android<span class="soon">系统输入法</span></span>
</div>
</div>
</section>
<!-- ════════ 价格 ════════ -->
<section id="pricing">
<div class="frame">
<div class="section-head reveal">
<div class="kicker">价格</div>
<h2>按时长买,永不过期<br>不订阅、不扣月费</h2>
</div>
<div class="pricing reveal">
<div class="price-card">
<div class="minutes"><span class="num">100</span> 分钟</div>
<div class="price"><span class="cur">¥</span>9</div>
<div class="unit">约 ¥0.09 / 分钟</div>
<a class="btn btn-lg btn-secondary" href="#download">选这档</a>
</div>
<div class="price-card hot">
<span class="tag">最多人选 · 省 13%</span>
<div class="minutes"><span class="num">500</span> 分钟</div>
<div class="price"><span class="cur">¥</span>39</div>
<div class="unit">约 ¥0.078 / 分钟</div>
<a class="btn btn-lg btn-primary" href="#download">选这档</a>
</div>
<div class="price-card">
<span class="tag">省 28%</span>
<div class="minutes"><span class="num">2000</span> 分钟</div>
<div class="price"><span class="cur">¥</span>129</div>
<div class="unit">约 ¥0.065 / 分钟</div>
<a class="btn btn-lg btn-secondary" href="#download">选这档</a>
</div>
</div>
<div class="pricing-note">每天另有 <span class="num">3</span> 分钟免费试用 · 微信支付,到账即用 · 时长永不过期</div>
</div>
</section>
<!-- ════════ 尾部 CTA ════════ -->
<div class="cta">
<div class="frame cta-inner reveal">
<svg class="logo-big" width="64" height="64" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"><circle cx="24" cy="24" r="18"/><path d="M17 20.5v7"/><path d="M24 16.5v15"/><path d="M31 20.5v7"/></svg>
<h2>从下一句话开始<br>用说的</h2>
<p>下载 dudu,微信扫码登录,每天免费 3 分钟</p>
<a class="btn btn-lg btn-primary" href="#download">免费下载 dudu</a>
</div>
</div>
<!-- ════════ 页脚 ════════ -->
<footer>
<div class="frame footer-inner">
<a class="brand" href="#">
<svg width="20" height="20" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round"><circle cx="24" cy="24" r="18"/><path d="M17 20.5v7"/><path d="M24 16.5v15"/><path d="M31 20.5v7"/></svg>
<span class="name latin" style="font-size:15px;font-weight:600">dudu</span>
</a>
<div class="footer-links">
<a href="#">用户协议</a>
<a href="#">隐私政策</a>
<a href="#">联系我们</a>
</div>
<div>© 2026 dudu · 沪ICP备XXXXXXXX号</div>
</div>
</footer>
<script>
/* ── 波形条 ── */
const WAVE_PATTERN = [0.3, 0.55, 0.85, 0.45, 0.7, 1, 0.4, 0.6, 0.25, 0.8, 0.5, 0.35];
const wave = document.getElementById('wave');
WAVE_PATTERN.forEach((p, i) => {
const bar = document.createElement('i');
bar.style.height = Math.max(4, Math.round(p * 24)) + 'px';
bar.style.animationDelay = (i % 6) * 0.07 + 's';
wave.appendChild(bar);
});
/* ── 演示剧场:按住 = 流式识别,松开 = 上屏 ── */
const SENTENCES = [
'帮我把这份周报整理一下,重点突出本周的进展和下周的计划。',
'好的,我马上把文件发给你,大概十分钟之内。',
'明天上午十点的会改到下午三点,记得同步给项目组。',
];
const stage = document.getElementById('stage');
const overlay = document.getElementById('overlay');
const ovlStatus = document.getElementById('ovl-status');
const ovlText = document.getElementById('ovl-text');
const docText = document.getElementById('doc-text');
const docPlaceholder = document.getElementById('doc-placeholder');
const micbar = document.getElementById('micbar');
const micbarLabel = document.getElementById('micbar-label');
let timer = null, idx = 0, finalText = '', partialText = '', recording = false;
function render() {
const has = finalText || partialText;
ovlStatus.hidden = !!has;
ovlText.hidden = !has;
ovlText.innerHTML = '';
ovlText.append(finalText);
if (partialText) {
const s = document.createElement('span');
s.className = 'partial';
s.textContent = partialText;
ovlText.appendChild(s);
}
}
function start() {
if (recording) return;
recording = true;
finalText = ''; partialText = '';
render();
stage.classList.add('recording');
micbar.classList.add('recording');
micbarLabel.textContent = '松开完成输入';
overlay.classList.add('show');
const sentence = SENTENCES[idx % SENTENCES.length];
let i = 0;
timer = setInterval(() => {
i += 1;
if (i > sentence.length) { clearInterval(timer); return; }
const seen = sentence.slice(0, i);
const lastPunct = Math.max(seen.lastIndexOf(''), seen.lastIndexOf('。'));
finalText = sentence.slice(0, lastPunct + 1);
partialText = sentence.slice(lastPunct + 1, i);
render();
}, 75);
}
function stop() {
if (!recording) return;
recording = false;
clearInterval(timer);
stage.classList.remove('recording');
micbar.classList.remove('recording');
micbarLabel.textContent = '按住试一试';
overlay.classList.remove('show');
const committed = finalText + partialText;
if (committed) {
idx += 1;
docPlaceholder.hidden = true;
// 模拟注入:整句快速"打"进文档
let j = 0;
const base = docText.textContent;
const typeTimer = setInterval(() => {
j += 3;
docText.textContent = base + committed.slice(0, j);
if (j >= committed.length) clearInterval(typeTimer);
}, 16);
}
finalText = ''; partialText = '';
}
micbar.addEventListener('pointerdown', (e) => { e.preventDefault(); start(); });
window.addEventListener('pointerup', stop);
micbar.addEventListener('pointercancel', stop);
window.addEventListener('keydown', (e) => {
if (e.code === 'Space' && !e.repeat && e.target === document.body) { e.preventDefault(); start(); }
});
window.addEventListener('keyup', (e) => { if (e.code === 'Space') stop(); });
window.addEventListener('blur', stop);
/* ── 滚动浮现 ── */
const io = new IntersectionObserver((entries) => {
entries.forEach((en) => { if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); } });
}, { threshold: 0.15 });
document.querySelectorAll('.reveal').forEach((el) => io.observe(el));
</script>
</body>
</html>