-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathecho.html
More file actions
133 lines (122 loc) · 10.2 KB
/
Copy pathecho.html
File metadata and controls
133 lines (122 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Echo — hear your text in a local voice model</title>
<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=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700&family=Hanken+Grotesk:wght@400;500;600&family=Spline+Sans+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
/* HOUSE "Atlas cockpit" system (brandbrain tokens): near-black surfaces, panel cards on
hairline edges, ONE lime accent, Bricolage display / Hanken body / Spline mono for numbers
and kickers. Sentence case. No decoration for its own sake. Add app-specific CSS at the
END of this block only. */
:root {
--page: #0A0C10; --inset: #070809; --panel: #12151C; --raised: #1A1F29;
--edge: #262C38; --edge-soft: #1C212B;
--ink: #E8EDF4; --ink-sec: #B4BECE; --ink-dim: #99A3B7; --ink-faint: #6E7C90;
--accent: #C8F250; --accent-soft: #232B0D;
--ok: #3DD68C; --warn: #F59E0B; --danger: #FF2D6E; --blue: #7DE0FF;
--display: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
--sans: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
--mono: "Spline Sans Mono", ui-monospace, monospace;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; min-height: 100%; }
body { background: var(--page); color: var(--ink); font: 14px/1.6 var(--sans); -webkit-font-smoothing: antialiased; }
::selection { background: rgba(200, 242, 80, 0.3); }
button { font-family: inherit; }
.kicker { font: 500 10px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--ink-dim); }
header { display: flex; align-items: center; gap: 14px; padding: 13px 20px; border-bottom: 1px solid var(--edge-soft); }
.word { font: 700 20px/1 var(--display); letter-spacing: -0.02em; display: flex; align-items: center; gap: 9px; }
.word em { font-style: normal; color: var(--accent); }
.word .mk { width: 22px; height: 22px; border-radius: 6px; background: var(--accent); box-shadow: 0 0 14px rgba(200,242,80,.4); }
#chip-dock { margin-left: auto; }
main { max-width: 720px; margin: 0 auto; padding: 40px 20px 90px; }
.hero h1 { font: 700 clamp(1.7rem, 4vw, 2.5rem)/1.05 var(--display); letter-spacing: -0.02em; margin: 0 0 10px; }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero p { color: var(--ink-sec); margin: 0 0 26px; font-size: 14.5px; max-width: 520px; }
.steps-card { margin-top: 8px; }
.steps { font: 400 12.5px/1.9 var(--mono); color: var(--ink-faint); }
.steps b { color: var(--accent); font-weight: 500; }
.start .ctx { font: 500 11.5px/1 var(--mono); color: var(--accent); margin-bottom: 10px; }
.bindrow { display: flex; gap: 8px; }
.bindrow input { flex: 1; min-width: 0; background: var(--inset); border: 1px solid var(--edge); border-radius: 10px; color: var(--ink); font: 400 13px/1.4 var(--sans); padding: 12px 13px; outline: none; }
.bindrow input:focus { border-color: color-mix(in srgb, var(--accent) 55%, var(--edge)); }
.primary { font: 600 13.5px/1 var(--sans); border-radius: 10px; padding: 12px 18px; cursor: pointer; border: 1px solid var(--accent); background: var(--accent); color: var(--page); transition: filter .15s, opacity .15s; }
.primary:hover { filter: brightness(1.08); }
.primary:disabled { opacity: .5; cursor: default; }
.run { display: flex; flex-direction: column; }
.runbar { display: flex; align-items: center; gap: 10px; padding-bottom: 12px; border-bottom: 1px solid var(--edge-soft); }
.run-input { font: 400 12.5px/1.4 var(--sans); color: var(--ink-sec); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.sect { display: block; margin: 20px 0 10px; }
.act { font: 500 11.5px/1 var(--mono); background: var(--inset); border: 1px solid var(--edge); border-radius: 999px; color: var(--ink-sec); padding: 7px 11px; cursor: pointer; transition: border-color .15s, color .15s; }
.act:hover { border-color: var(--accent); color: var(--ink); }
.act:disabled { opacity: .45; cursor: default; }
/* status line while working */
.researching { display: flex; align-items: center; gap: 8px; margin-top: 14px; font: 500 11.5px/1.4 var(--mono); color: var(--ink-dim); }
.scan { position: relative; width: 26px; height: 6px; border-radius: 3px; background: var(--inset); overflow: hidden; }
.scan::after { content: ""; position: absolute; inset: 0; width: 40%; background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: scan 1.1s linear infinite; }
@keyframes scan { from { transform: translateX(-100%); } to { transform: translateX(260%); } }
.err { color: var(--danger); font: 400 12px/1.6 var(--mono); margin-top: 12px; }
/* option cards (the brandbrain atom) */
.opts { display: flex; flex-direction: column; gap: 8px; }
.opt { position: relative; border: 1px solid var(--edge); background: var(--inset); border-radius: 14px; padding: 13px 14px; cursor: pointer; transition: border-color .15s, background .15s; }
.opt:hover { border-color: var(--edge-soft); }
.opt.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent-soft) 55%, var(--inset)); }
.opt .check { position: absolute; right: 11px; top: 11px; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--edge); display: grid; place-items: center; color: transparent; font: 700 11px/1 var(--sans); }
.opt.sel .check { border-color: var(--accent); background: var(--accent); color: var(--page); }
.opt .rec { display: inline-block; font: 500 9px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent); background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-radius: 999px; padding: 3px 7px; margin-bottom: 7px; }
.opt .o-label { font: 600 13.5px/1.3 var(--display); color: var(--ink); padding-right: 22px; }
.opt .o-text { font: 400 13px/1.5 var(--sans); color: var(--ink-sec); margin-top: 5px; white-space: pre-wrap; word-break: break-word; }
.opt .o-img { width: 100%; border-radius: 8px; border: 1px solid var(--edge); display: block; margin-top: 8px; }
/* steer input */
.steer { margin-top: 16px; display: flex; flex-direction: column; gap: 7px; }
.steer .chips { display: flex; flex-wrap: wrap; gap: 6px; }
.steer .chip { font: 500 11px/1 var(--sans); border: 1px solid var(--edge); background: var(--panel); color: var(--ink-sec); border-radius: 999px; padding: 6px 10px; cursor: pointer; }
.steer .chip:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--edge)); color: var(--ink); }
.steer .row { display: flex; gap: 8px; align-items: center; }
.steer .box { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; border: 1px solid var(--edge); background: var(--panel); border-radius: 10px; padding: 8px 11px; }
.steer .box:focus-within { border-color: var(--edge-soft); }
.steer input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--ink); font: 400 12.5px/1.4 var(--sans); }
.steer input::placeholder { color: var(--ink-dim); }
.steer .send { flex: none; font: 600 12px/1 var(--sans); background: var(--accent); color: var(--page); border: 0; border-radius: 9px; padding: 9px 12px; cursor: pointer; }
.md { font: 400 13.5px/1.7 var(--sans); color: var(--ink-sec); white-space: pre-wrap; word-break: break-word; background: var(--panel); border: 1px solid var(--edge); border-radius: 14px; padding: 16px 18px; }
.md a { color: var(--blue); }
.md b { color: var(--ink); }
.toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); background: var(--raised); border: 1px solid var(--edge); color: var(--ink); font: 500 12.5px/1.4 var(--sans); padding: 11px 16px; border-radius: 10px; box-shadow: 0 14px 40px -14px rgba(0,0,0,.7); z-index: 200; max-width: 80vw; }
.toast.err { border-color: var(--danger); color: #ffd6e2; }
/* ==== app-specific CSS below this line ==== */
.echo { display: flex; flex-direction: column; gap: 14px; }
.say { width: 100%; min-height: 170px; resize: vertical; background: var(--inset); border: 1px solid var(--edge); border-radius: 14px; color: var(--ink); font: 400 15px/1.6 var(--sans); padding: 16px 18px; outline: none; }
.say:focus { border-color: color-mix(in srgb, var(--accent) 55%, var(--edge)); }
.say::placeholder { color: var(--ink-dim); }
.ctrls { display: flex; gap: 10px; align-items: center; }
.voice { background: var(--panel); border: 1px solid var(--edge); border-radius: 10px; color: var(--ink); font: 500 13px/1 var(--sans); padding: 11px 12px; outline: none; cursor: pointer; }
.voice:focus { border-color: var(--edge-soft); }
.novoice { font: 500 11.5px/1 var(--mono); color: var(--ink-faint); }
.ctrls .primary { margin-left: auto; }
.clip { display: flex; flex-direction: column; gap: 11px; background: var(--panel); border: 1px solid var(--edge); border-radius: 14px; padding: 16px 18px; }
.clip audio { width: 100%; }
.clipmeta { display: flex; align-items: center; gap: 12px; }
.clipmeta .kicker { color: var(--accent); }
.clipmeta .act { margin-left: auto; text-decoration: none; }
</style>
</head>
<body>
<header>
<span class="word"><span class="mk"></span>Ec<em>ho</em></span>
<div id="chip-dock"></div>
</header>
<main>
<section class="hero" id="hero">
<h1>Type it. <em>Hear it.</em> On your machine.</h1>
<p>Echo reads your text aloud with a voice model running locally — routed through Switchboard, so the audio is synthesized on your machine and never leaves it. Point it at a voice-cloning server and it speaks in your voice.</p>
</section>
<div id="view"></div>
</main>
<script type="module" src="./dist/echo.js"></script>
</body>
</html>