profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM

Commit

Explain the first-run experience

commit f508c5c

6 changed files with +826 and −158

Jump to a changed file
  1. client/index.html +2 −2
  2. client/src/App.css +630 −99
  3. client/src/App.tsx +138 −35
  4. client/src/components/OutcomePreview.tsx +27 −0
  5. client/src/index.css +28 −21
  6. spec/TASKS.md +1 −1
modified client/index.html +2 −2
@@ -6,12 +6,12 @@
6 6 <title>VoiceTask</title>
7 7 <link
8 8 rel="icon"
9 - href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='13' fill='%23c46a4a'/%3E%3Crect x='11' y='12' width='2.6' height='7' rx='1.3' fill='%23f6f1e7'/%3E%3Crect x='18.4' y='12' width='2.6' height='7' rx='1.3' fill='%23f6f1e7'/%3E%3C/svg%3E"
9 + href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='13' fill='%234856e8'/%3E%3Crect x='11' y='12' width='2.6' height='7' rx='1.3' fill='white'/%3E%3Crect x='18.4' y='12' width='2.6' height='7' rx='1.3' fill='white'/%3E%3C/svg%3E"
10 10 />
11 11 <link rel="preconnect" href="https://fonts.googleapis.com" />
12 12 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13 13 <link
14 - href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400..700;1,9..144,400..700&family=Instrument+Sans:ital,wght@0,400..700;1,400..700&display=swap"
14 + href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400..700&family=Sora:wght@500..800&display=swap"
15 15 rel="stylesheet"
16 16 />
17 17 </head>
modified client/src/App.css +630 −99
@@ -2,7 +2,7 @@
2 2 position: relative;
3 3 max-width: 680px;
4 4 margin: 0 auto;
5 - padding: 28px 20px 72px;
5 + padding: 32px 24px 80px;
6 6 }
7 7
8 8 .muted {
@@ -16,104 +16,232 @@
16 16 text-align: center;
17 17 }
18 18
19 -/* ---------- Home ---------- */
20 -
21 19 .home-screen {
22 - padding-top: 64px;
20 + max-width: 1160px;
21 + padding-top: 36px;
23 22 }
24 23
25 -.hero {
24 +.home-nav {
26 25 display: flex;
27 - flex-direction: column;
28 26 align-items: center;
29 - text-align: center;
30 - margin-bottom: 44px;
27 + justify-content: space-between;
28 + gap: 20px;
29 + margin-bottom: 72px;
31 30 animation: rise 0.7s ease both;
32 31 }
33 32
34 -.hero .orb-wrap {
35 - transform: scale(0.62);
36 - margin: -28px 0 -20px;
37 -}
38 -
39 33 .wordmark {
40 34 font-family: var(--serif);
41 - font-size: 44px;
42 - font-weight: 500;
43 - letter-spacing: -0.02em;
44 - margin: 0 0 6px;
35 + font-size: 25px;
36 + font-weight: 750;
37 + letter-spacing: -0.04em;
45 38 }
46 39
47 40 .wordmark-dot {
41 + color: var(--pulse);
42 +}
43 +
44 +.local-badge {
45 + display: inline-flex;
46 + align-items: center;
47 + gap: 8px;
48 + padding: 8px 12px;
49 + border: 1px solid var(--line);
50 + border-radius: 999px;
51 + background: color-mix(in srgb, var(--card) 80%, transparent);
52 + color: var(--ink-soft);
53 + font-size: 13px;
54 + font-weight: 600;
55 +}
56 +
57 +.local-badge > span {
58 + width: 8px;
59 + height: 8px;
60 + border-radius: 50%;
61 + background: var(--moss);
62 + box-shadow: 0 0 0 4px color-mix(in srgb, var(--moss) 13%, transparent);
63 +}
64 +
65 +.home-main {
66 + display: grid;
67 + grid-template-columns: minmax(0, 1.12fr) minmax(360px, 0.88fr);
68 + gap: 72px;
69 + align-items: start;
70 +}
71 +
72 +.home-intro {
73 + padding-top: 14px;
74 + animation: rise 0.65s 0.05s ease both;
75 +}
76 +
77 +.eyebrow,
78 +.card-kicker {
79 + margin: 0 0 14px;
48 80 color: var(--clay);
81 + font-size: 12px;
82 + font-weight: 700;
83 + letter-spacing: 0.13em;
84 + text-transform: uppercase;
85 +}
86 +
87 +.home-intro h1 {
88 + max-width: 700px;
89 + margin: 0;
90 + font-size: clamp(45px, 5.2vw, 72px);
91 + line-height: 1.02;
92 + letter-spacing: -0.055em;
93 + text-wrap: balance;
94 +}
95 +
96 +.hero-copy {
97 + max-width: 610px;
98 + margin-top: 24px;
99 + color: var(--ink-soft);
100 + font-size: 19px;
101 + line-height: 1.55;
102 +}
103 +
104 +.privacy-note {
105 + display: flex;
106 + align-items: center;
107 + gap: 12px;
108 + max-width: 560px;
109 + margin-top: 24px;
110 + color: var(--ink-soft);
111 + font-size: 14px;
112 +}
113 +
114 +.privacy-note strong {
115 + color: var(--ink);
116 + font-weight: 700;
49 117 }
50 118
51 -.tagline {
119 +.privacy-icon {
120 + position: relative;
121 + width: 30px;
122 + height: 30px;
123 + flex: 0 0 30px;
124 + border: 1px solid color-mix(in srgb, var(--moss) 34%, var(--line));
125 + border-radius: 10px;
126 + background: color-mix(in srgb, var(--moss) 9%, var(--card));
127 +}
128 +
129 +.privacy-icon::before {
130 + content: '';
131 + position: absolute;
132 + left: 9px;
133 + top: 6px;
134 + width: 10px;
135 + height: 10px;
136 + border: 2px solid var(--moss);
137 + border-bottom: 0;
138 + border-radius: 8px 8px 0 0;
139 +}
140 +
141 +.privacy-icon > span {
142 + position: absolute;
143 + left: 7px;
144 + top: 13px;
145 + width: 14px;
146 + height: 11px;
147 + border-radius: 4px;
148 + background: var(--moss);
149 +}
150 +
151 +.start-card {
152 + padding: 30px;
153 + border: 1px solid color-mix(in srgb, var(--clay) 18%, var(--line));
154 + border-radius: 24px;
155 + background: var(--card);
156 + box-shadow: 0 28px 70px -44px rgba(32, 37, 92, 0.48);
157 + animation: rise 0.65s 0.12s ease both;
158 +}
159 +
160 +.start-card h2 {
161 + margin: 0;
162 + font-size: 28px;
163 + line-height: 1.2;
164 + letter-spacing: -0.035em;
165 +}
166 +
167 +.card-kicker {
168 + margin-bottom: 10px;
169 +}
170 +
171 +.card-intro {
172 + margin-top: 9px;
52 173 color: var(--ink-soft);
53 - font-size: 16px;
54 - max-width: 380px;
174 + font-size: 15px;
55 175 }
56 176
57 177 .create-form {
58 178 display: flex;
59 179 flex-direction: column;
60 - gap: 22px;
61 - max-width: 400px;
62 - margin: 0 auto 56px;
63 - animation: rise 0.7s 0.12s ease both;
180 + gap: 24px;
181 + margin-top: 28px;
64 182 }
65 183
66 -.create-form label {
184 +.form-field {
67 185 display: flex;
68 186 flex-direction: column;
69 - gap: 6px;
187 + align-items: stretch;
70 188 }
71 189
72 -.create-form label span {
73 - font-size: 12px;
74 - text-transform: uppercase;
75 - letter-spacing: 0.1em;
190 +.form-field > label,
191 +.field-label {
192 + color: var(--ink);
193 + font-size: 14px;
194 + font-weight: 700;
195 +}
196 +
197 +.form-field > p {
198 + margin: 4px 0 10px;
76 199 color: var(--ink-soft);
200 + font-size: 13px;
201 + line-height: 1.45;
77 202 }
78 203
79 -.create-form input {
80 - padding: 10px 2px;
81 - border: none;
82 - border-bottom: 1.5px solid var(--line);
83 - background: transparent;
204 +.create-form input,
205 +.folder-field input {
206 + width: 100%;
207 + padding: 12px 14px;
208 + border: 1.5px solid var(--line);
209 + border-radius: 12px;
210 + background: var(--card);
84 211 color: var(--ink);
85 - font-size: 17px;
86 - transition: border-color 0.25s ease;
212 + font-size: 15px;
213 + transition: border-color 0.2s ease, box-shadow 0.2s ease;
87 214 }
88 215
89 -.create-form input:focus {
216 +.create-form input:focus,
217 +.folder-field input:focus {
90 218 outline: none;
91 - border-bottom-color: var(--clay);
219 + border-color: var(--clay);
220 + box-shadow: 0 0 0 4px color-mix(in srgb, var(--clay) 10%, transparent);
92 221 }
93 222
94 223 .create-form input::placeholder {
95 224 color: var(--ink-soft);
96 - opacity: 0.55;
225 + opacity: 0.62;
97 226 }
98 227
99 228 .btn-primary {
100 - align-self: center;
101 - padding: 11px 28px;
229 + padding: 12px 22px;
102 230 border: none;
103 - border-radius: 999px;
231 + border-radius: 12px;
104 232 background: var(--clay);
105 233 color: var(--card);
106 234 font-size: 15px;
107 - letter-spacing: 0.02em;
235 + font-weight: 700;
108 236 cursor: pointer;
109 - transition: background 0.25s ease, transform 0.2s ease, box-shadow 0.25s ease;
110 - box-shadow: 0 6px 18px -8px rgba(196, 106, 74, 0.55);
237 + transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
238 + box-shadow: 0 10px 24px -14px color-mix(in srgb, var(--clay) 72%, transparent);
111 239 }
112 240
113 241 .btn-primary:hover:not(:disabled) {
114 242 background: var(--clay-deep);
115 - transform: translateY(-1px);
116 - box-shadow: 0 10px 22px -8px rgba(196, 106, 74, 0.6);
243 + transform: translateY(-2px);
244 + box-shadow: 0 14px 30px -16px color-mix(in srgb, var(--clay) 76%, transparent);
117 245 }
118 246
119 247 .btn-primary:disabled {
@@ -123,37 +251,284 @@
123 251
124 252 .folder-field {
125 253 display: flex;
126 - gap: 8px;
254 + gap: 10px;
127 255 }
128 256
129 257 .folder-field input {
130 258 flex: 1;
131 259 }
132 260
133 -.browse-button {
134 - align-self: center;
135 - padding: 9px 16px;
136 - border-radius: 999px;
137 - border: 1.5px solid var(--line);
138 - background: transparent;
139 - color: var(--ink);
261 +.choose-folder-button {
262 + width: 100%;
263 + padding: 12px 16px;
264 + border: 1.5px solid var(--clay);
265 + border-radius: 12px;
266 + background: var(--clay-soft);
267 + color: var(--clay-deep);
140 268 cursor: pointer;
141 - white-space: nowrap;
142 269 font-size: 14px;
143 - transition: background 0.25s ease, border-color 0.25s ease;
270 + font-weight: 700;
271 + transition: background 0.2s ease, transform 0.2s ease;
144 272 }
145 273
146 -.browse-button:hover {
147 - background: var(--paper-deep);
148 - border-color: var(--clay);
274 +.choose-folder-button:hover {
275 + background: color-mix(in srgb, var(--clay-soft) 70%, var(--card));
276 + transform: translateY(-1px);
277 +}
278 +
279 +.selected-folder {
280 + display: flex;
281 + align-items: flex-start;
282 + gap: 8px;
283 + padding: 10px 12px;
284 + border-radius: 10px;
285 + background: color-mix(in srgb, var(--moss) 8%, var(--card));
286 + color: var(--ink) !important;
287 + font-family: var(--mono);
288 + font-size: 12px !important;
289 + overflow-wrap: anywhere;
290 +}
291 +
292 +.selected-folder span {
293 + color: var(--moss);
294 + font-family: var(--sans);
295 + font-weight: 700;
296 +}
297 +
298 +.path-label {
299 + margin: 14px 0 7px;
300 + color: var(--ink-soft) !important;
301 + font-size: 12px !important;
302 + font-weight: 600 !important;
303 +}
304 +
305 +.start-button {
306 + width: 100%;
307 + min-height: 48px;
308 + margin-top: 2px;
309 +}
310 +
311 +.form-footnote {
312 + margin-top: -14px;
313 + color: var(--ink-soft);
314 + font-size: 12px;
315 + text-align: center;
316 +}
317 +
318 +.start-card > .error {
319 + text-align: left;
320 +}
321 +
322 +.outcome-preview {
323 + position: relative;
324 + display: grid;
325 + grid-template-columns: minmax(0, 0.82fr) 50px minmax(0, 1fr);
326 + align-items: center;
327 + gap: 0;
328 + max-width: 650px;
329 + margin: 38px 0 0;
330 + padding: 16px;
331 + border: 1px solid color-mix(in srgb, var(--clay) 16%, var(--line));
332 + border-radius: 20px;
333 + background: color-mix(in srgb, var(--card) 88%, var(--clay-soft));
334 + box-shadow: 0 24px 54px -46px rgba(32, 37, 92, 0.7);
335 +}
336 +
337 +.preview-conversation,
338 +.preview-requirement {
339 + min-height: 132px;
340 + padding: 16px;
341 + border-radius: 14px;
342 +}
343 +
344 +.preview-conversation {
345 + display: flex;
346 + flex-direction: column;
347 + justify-content: space-between;
348 + background: var(--ink);
349 + color: var(--card);
350 +}
351 +
352 +.preview-label {
353 + color: color-mix(in srgb, var(--card) 68%, transparent);
354 + font-size: 10px;
355 + font-weight: 700;
356 + letter-spacing: 0.11em;
357 + text-transform: uppercase;
358 +}
359 +
360 +.preview-conversation blockquote {
361 + margin: 12px 0 14px;
362 + font-size: 14px;
363 + line-height: 1.4;
364 +}
365 +
366 +.preview-wave {
367 + display: flex;
368 + align-items: center;
369 + gap: 3px;
370 + height: 20px;
371 +}
372 +
373 +.preview-wave span {
374 + width: 3px;
375 + height: 4px;
376 + border-radius: 2px;
377 + background: var(--pulse);
378 +}
379 +
380 +.preview-wave span:nth-child(3n + 1) {
381 + height: 12px;
382 +}
383 +
384 +.preview-wave span:nth-child(4n + 2) {
385 + height: 18px;
386 +}
387 +
388 +.preview-connector {
389 + position: relative;
390 + height: 2px;
391 + background: var(--clay);
392 +}
393 +
394 +.preview-connector::before,
395 +.preview-connector::after {
396 + content: '';
397 + position: absolute;
398 + top: 50%;
399 + border-radius: 50%;
400 + background: var(--clay);
401 + transform: translateY(-50%);
402 +}
403 +
404 +.preview-connector::before {
405 + left: -3px;
406 + width: 8px;
407 + height: 8px;
408 +}
409 +
410 +.preview-connector::after {
411 + right: -3px;
412 + width: 10px;
413 + height: 10px;
414 +}
415 +
416 +.preview-connector span::after {
417 + content: '';
418 + position: absolute;
419 + right: -2px;
420 + top: 50%;
421 + width: 7px;
422 + height: 7px;
423 + border-top: 2px solid var(--card);
424 + border-right: 2px solid var(--card);
425 + transform: translateY(-50%) rotate(45deg);
426 +}
427 +
428 +.preview-requirement {
429 + border: 1px solid var(--line);
430 + background: var(--card);
431 +}
432 +
433 +.preview-document-header {
434 + display: flex;
435 + justify-content: space-between;
436 + gap: 10px;
437 + padding-bottom: 10px;
438 + border-bottom: 1px solid var(--line);
439 + color: var(--ink-soft);
440 + font-family: var(--mono);
441 + font-size: 10px;
442 +}
443 +
444 +.preview-requirement p {
445 + margin: 15px 0 13px;
446 + color: var(--ink);
447 + font-size: 14px;
448 + font-weight: 650;
449 + line-height: 1.4;
450 +}
451 +
452 +.source-marker {
453 + display: inline-flex;
454 + padding: 4px 7px;
455 + border-radius: 6px;
456 + background: var(--clay-soft);
457 + color: var(--clay-deep);
458 + font-family: var(--mono);
459 + font-size: 10px;
460 +}
461 +
462 +.outcome-preview figcaption {
463 + grid-column: 1 / -1;
464 + margin: 12px 2px 0;
465 + color: var(--ink-soft);
466 + font-size: 12px;
467 +}
468 +
469 +.process-section {
470 + display: grid;
471 + grid-template-columns: minmax(220px, 0.72fr) minmax(0, 1.58fr);
472 + gap: 54px;
473 + margin-top: 96px;
474 + padding: 38px 0;
475 + border-top: 1px solid var(--line);
476 + border-bottom: 1px solid var(--line);
477 +}
478 +
479 +.process-heading h2 {
480 + margin: 0;
481 + font-size: 29px;
482 + line-height: 1.2;
483 + letter-spacing: -0.035em;
484 +}
485 +
486 +.process-steps {
487 + display: grid;
488 + grid-template-columns: repeat(3, 1fr);
489 + gap: 28px;
490 + margin: 0;
491 + padding: 0;
492 + list-style: none;
493 +}
494 +
495 +.process-steps li {
496 + display: flex;
497 + gap: 13px;
498 +}
499 +
500 +.step-number {
501 + display: inline-flex;
502 + align-items: center;
503 + justify-content: center;
504 + width: 28px;
505 + height: 28px;
506 + flex: 0 0 28px;
507 + border-radius: 9px;
508 + background: var(--ink);
509 + color: var(--card);
510 + font-family: var(--mono);
511 + font-size: 12px;
512 +}
513 +
514 +.process-steps h3 {
515 + margin: 2px 0 7px;
516 + font-family: var(--serif);
517 + font-size: 15px;
518 +}
519 +
520 +.process-steps p {
521 + color: var(--ink-soft);
522 + font-size: 13px;
523 + line-height: 1.5;
149 524 }
150 525
151 526 /* Folder browser modal */
152 527
153 528 .folder-browser-overlay {
154 529 position: fixed;
155 530 inset: 0;
156 - background: rgba(44, 38, 32, 0.45);
531 + background: rgba(23, 25, 45, 0.48);
157 532 display: flex;
158 533 align-items: center;
159 534 justify-content: center;
@@ -171,7 +546,7 @@
171 546 display: flex;
172 547 flex-direction: column;
173 548 gap: 14px;
174 - box-shadow: 0 24px 60px -24px rgba(44, 38, 32, 0.5);
549 + box-shadow: 0 24px 60px -24px rgba(23, 25, 45, 0.5);
175 550 }
176 551
177 552 .folder-browser h2 {
@@ -267,47 +642,83 @@
267 642 }
268 643
269 644 .session-list {
270 - max-width: 400px;
271 - margin: 0 auto;
645 + margin: 72px 0 0;
272 646 animation: rise 0.7s 0.22s ease both;
273 647 }
274 648
649 +.session-list-heading {
650 + display: flex;
651 + align-items: flex-end;
652 + justify-content: space-between;
653 + gap: 20px;
654 + margin-bottom: 16px;
655 +}
656 +
657 +.session-list-heading .eyebrow {
658 + margin-bottom: 6px;
659 +}
660 +
275 661 .session-list h2 {
276 - font-size: 13px;
277 - font-family: var(--sans);
278 - font-weight: 500;
279 - text-transform: uppercase;
280 - letter-spacing: 0.12em;
662 + margin: 0;
663 + font-size: 26px;
664 + letter-spacing: -0.035em;
665 +}
666 +
667 +.session-list-heading > span {
668 + display: inline-flex;
669 + align-items: center;
670 + justify-content: center;
671 + min-width: 34px;
672 + height: 28px;
673 + padding: 0 9px;
674 + border-radius: 9px;
675 + background: var(--paper-deep);
281 676 color: var(--ink-soft);
282 - margin-bottom: 10px;
677 + font-family: var(--mono);
678 + font-size: 12px;
283 679 }
680 +
284 681 .session-list ul {
285 - list-style: none;
286 - padding: 0;
682 + display: grid;
683 + grid-template-columns: repeat(2, minmax(0, 1fr));
684 + gap: 12px;
287 685 margin: 0;
288 - display: flex;
289 - flex-direction: column;
686 + padding: 0;
687 + list-style: none;
290 688 }
291 689
292 690 .session-row {
293 691 display: flex;
294 692 align-items: center;
295 - gap: 4px;
693 + gap: 2px;
694 + min-width: 0;
695 + padding-right: 8px;
696 + border: 1px solid var(--line);
697 + border-radius: 14px;
698 + background: var(--card);
699 + transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
700 +}
701 +
702 +.session-row:hover {
703 + border-color: color-mix(in srgb, var(--clay) 34%, var(--line));
704 + transform: translateY(-1px);
705 + box-shadow: 0 12px 28px -24px rgba(32, 37, 92, 0.6);
296 706 }
297 707
298 708 .session-row .session-item {
299 709 flex: 1;
710 + min-width: 0;
300 711 }
301 712
302 713 .session-delete {
303 714 border: none;
304 715 background: none;
305 716 color: var(--ink-soft);
306 - font-size: 18px;
717 + font-size: 19px;
307 718 line-height: 1;
308 - padding: 6px 8px;
719 + padding: 9px;
309 720 cursor: pointer;
310 - opacity: 0;
721 + opacity: 0.56;
311 722 transition: opacity 0.2s ease, color 0.2s ease;
312 723 }
313 724
@@ -324,6 +735,7 @@
324 735 display: flex;
325 736 align-items: center;
326 737 gap: 12px;
738 + flex-shrink: 0;
327 739 }
328 740
329 741 .session-date {
@@ -332,30 +744,29 @@
332 744 }
333 745
334 746 .session-item {
335 - width: 100%;
336 747 display: flex;
337 748 justify-content: space-between;
338 749 align-items: center;
339 - padding: 13px 4px;
750 + gap: 16px;
751 + width: 100%;
752 + padding: 15px 12px 15px 16px;
340 753 border: none;
341 - border-bottom: 1px solid var(--line);
342 754 background: transparent;
343 755 cursor: pointer;
344 756 color: var(--ink);
345 757 text-align: left;
346 - font-size: 16px;
347 - transition: padding 0.25s ease;
348 -}
349 -
350 -.session-item:hover {
351 - padding-left: 10px;
758 + font-size: 15px;
352 759 }
353 760
354 761 .session-item:hover .session-name {
355 762 color: var(--clay-deep);
356 763 }
357 764
358 765 .session-name {
766 + overflow: hidden;
767 + font-weight: 650;
768 + text-overflow: ellipsis;
769 + white-space: nowrap;
359 770 transition: color 0.25s ease;
360 771 }
361 772
@@ -487,7 +898,7 @@
487 898 height: 14px;
488 899 border-radius: 50%;
489 900 background: var(--card);
490 - box-shadow: 0 1px 3px rgba(44, 38, 32, 0.25);
901 + box-shadow: 0 1px 3px rgba(23, 25, 45, 0.25);
491 902 transition: transform 0.25s cubic-bezier(0.34, 1.4, 0.64, 1);
492 903 }
493 904
@@ -600,11 +1011,11 @@
600 1011 inset: 0;
601 1012 border-radius: 50%;
602 1013 background:
603 - radial-gradient(circle at 33% 28%, #f9ddbd 0%, #eeb289 42%, #cd7a54 78%, #b4593a 100%);
1014 + radial-gradient(circle at 33% 26%, #fff0ed 0%, #ffb3a6 36%, #ff705c 68%, #5c63dc 112%);
604 1015 box-shadow:
605 - inset -8px -12px 24px rgba(140, 60, 30, 0.28),
606 - inset 6px 8px 18px rgba(255, 245, 225, 0.55),
607 - 0 18px 40px -18px rgba(140, 70, 40, 0.5);
1016 + inset -8px -12px 24px rgba(90, 46, 126, 0.24),
1017 + inset 6px 8px 18px rgba(255, 255, 255, 0.58),
1018 + 0 18px 40px -18px rgba(72, 86, 232, 0.5);
608 1019 animation: breathe 5.2s ease-in-out infinite;
609 1020 }
610 1021
@@ -627,7 +1038,7 @@
627 1038 width: 9px;
628 1039 height: 22px;
629 1040 border-radius: 6px;
630 - background: #38261c;
1041 + background: var(--ink);
631 1042 animation: blink 5.4s ease-in-out infinite;
632 1043 transition: transform 0.3s ease;
633 1044 }
@@ -645,7 +1056,7 @@
645 1056 width: 3.5px;
646 1057 height: 4px;
647 1058 border-radius: 2px;
648 - background: #38261c;
1059 + background: var(--ink);
649 1060 }
650 1061
651 1062 /* level ring, driven by mic input via JS */
@@ -764,7 +1175,7 @@
764 1175 justify-content: center;
765 1176 transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease,
766 1177 transform 0.2s ease, box-shadow 0.3s ease;
767 - box-shadow: 0 10px 24px -14px rgba(44, 38, 32, 0.4);
1178 + box-shadow: 0 10px 24px -14px rgba(23, 25, 45, 0.4);
768 1179 touch-action: none;
769 1180 }
770 1181
@@ -779,7 +1190,7 @@
779 1190 border-color: var(--clay);
780 1191 color: var(--card);
781 1192 transform: scale(1.08);
782 - box-shadow: 0 0 0 10px rgba(196, 106, 74, 0.16), 0 14px 30px -14px rgba(169, 85, 56, 0.7);
1193 + box-shadow: 0 0 0 10px rgba(72, 86, 232, 0.14), 0 14px 30px -14px rgba(53, 65, 199, 0.68);
783 1194 }
784 1195
785 1196 .mic-button:disabled {
@@ -869,7 +1280,7 @@
869 1280 border: 1px solid var(--line);
870 1281 border-radius: 16px;
871 1282 background: var(--card);
872 - box-shadow: 0 12px 30px -24px rgba(44, 38, 32, 0.5);
1283 + box-shadow: 0 12px 30px -24px rgba(23, 25, 45, 0.5);
873 1284 }
874 1285
875 1286 .generate-panel h2,
@@ -1080,13 +1491,133 @@
1080 1491 }
1081 1492 }
1082 1493
1494 +@media (max-width: 900px) {
1495 + .home-nav {
1496 + margin-bottom: 48px;
1497 + }
1498 +
1499 + .home-main {
1500 + grid-template-columns: 1fr;
1501 + gap: 48px;
1502 + }
1503 +
1504 + .home-intro {
1505 + padding-top: 0;
1506 + }
1507 +
1508 + .home-intro h1 {
1509 + max-width: 760px;
1510 + }
1511 +
1512 + .hero-copy {
1513 + max-width: 680px;
1514 + }
1515 +
1516 + .start-card {
1517 + max-width: 650px;
1518 + }
1519 +
1520 + .process-section {
1521 + grid-template-columns: 1fr;
1522 + gap: 28px;
1523 + margin-top: 72px;
1524 + }
1525 +}
1526 +
1527 +@media (max-width: 680px) {
1528 + .screen {
1529 + padding-right: 18px;
1530 + padding-left: 18px;
1531 + }
1532 +
1533 + .home-screen {
1534 + padding-top: 28px;
1535 + }
1536 +
1537 + .home-nav {
1538 + margin-bottom: 42px;
1539 + }
1540 +
1541 + .local-badge {
1542 + padding: 7px 10px;
1543 + font-size: 11px;
1544 + }
1545 +
1546 + .home-intro h1 {
1547 + font-size: clamp(40px, 13vw, 58px);
1548 + }
1549 +
1550 + .hero-copy {
1551 + font-size: 17px;
1552 + }
1553 +
1554 + .outcome-preview {
1555 + grid-template-columns: 1fr;
1556 + gap: 10px;
1557 + }
1558 +
1559 + .preview-connector {
1560 + width: 2px;
1561 + height: 24px;
1562 + margin: 0 auto;
1563 + }
1564 +
1565 + .preview-connector::before {
1566 + top: -2px;
1567 + left: 50%;
1568 + transform: translateX(-50%);
1569 + }
1570 +
1571 + .preview-connector::after {
1572 + top: auto;
1573 + right: auto;
1574 + bottom: -2px;
1575 + left: 50%;
1576 + transform: translateX(-50%);
1577 + }
1578 +
1579 + .preview-connector span::after {
1580 + right: auto;
1581 + top: auto;
1582 + bottom: -1px;
1583 + left: 50%;
1584 + transform: translateX(-50%) rotate(135deg);
1585 + }
1586 +
1587 + .outcome-preview figcaption {
1588 + margin-top: 2px;
1589 + }
1590 +
1591 + .start-card {
1592 + padding: 24px 20px;
1593 + border-radius: 20px;
1594 + }
1595 +
1596 + .process-steps {
1597 + grid-template-columns: 1fr;
1598 + gap: 20px;
1599 + }
1600 +
1601 + .session-list ul {
1602 + grid-template-columns: 1fr;
1603 + }
1604 +
1605 + .session-item {
1606 + flex-direction: column;
1607 + align-items: flex-start;
1608 + gap: 6px;
1609 + }
1610 +}
1611 +
1083 1612 @media (prefers-reduced-motion: reduce) {
1084 1613 .orb,
1085 1614 .orb-eye,
1086 1615 .orb-ripple,
1087 1616 .orb-mouth-bar,
1088 1617 .waveform-bar,
1089 - .hero,
1618 + .home-nav,
1619 + .home-intro,
1620 + .start-card,
1090 1621 .create-form,
1091 1622 .session-list,
1092 1623 .question,
modified client/src/App.tsx +138 −35
@@ -6,6 +6,7 @@import { BlockerImport } from './components/BlockerImport'
6 6 import { CoveragePanel } from './components/CoveragePanel'
7 7 import { FolderBrowser } from './components/FolderBrowser'
8 8 import { GeneratePanel } from './components/GeneratePanel'
9 +import { OutcomePreview } from './components/OutcomePreview'
9 10 import { PushToTalkButton } from './components/PushToTalkButton'
10 11 import { QuestionCard } from './components/QuestionCard'
11 12 import { Transcript } from './components/Transcript'
@@ -46,7 +47,7 @@function SessionListScreen({ onOpen }: { onOpen: (id: string) => void }) {
46 47 }, [])
47 48
48 49 async function handleDelete(id: string) {
49 - if (!window.confirm('Delete this session? The transcript is lost.')) return
50 + if (!window.confirm('Delete this interview? Its saved transcript will be removed from VoiceTask.')) return
50 51 setError(null)
51 52 try {
52 53 await api.deleteSession(id)
@@ -72,37 +73,135 @@function SessionListScreen({ onOpen }: { onOpen: (id: string) => void }) {
72 73
73 74 return (
74 75 <div className="screen home-screen">
75 - <div className="hero">
76 - <VoiceOrb state="idle" />
77 - <h1 className="wordmark">
76 + <header className="home-nav">
77 + <span className="wordmark">
78 78 voicetask<span className="wordmark-dot">.</span>
79 - </h1>
80 - <p className="tagline">Talk through what you want to build. It interviews you, then writes the spec.</p>
81 - </div>
79 + </span>
80 + <span className="local-badge">
81 + <span aria-hidden="true" />
82 + Saved on this computer
83 + </span>
84 + </header>
82 85
83 - <form className="create-form" onSubmit={handleCreate}>
84 - <label>
85 - <span>Project name</span>
86 - <input value={name} onChange={(e) => setName(e.target.value)} placeholder="my next thing" required />
87 - </label>
88 - <label>
89 - <span>Project folder</span>
90 - <div className="folder-field">
91 - <input
92 - value={targetDir}
93 - onChange={(e) => setTargetDir(e.target.value)}
94 - placeholder="where should we save this?"
95 - required
96 - />
97 - <button type="button" className="browse-button" onClick={() => setBrowsingFolder(true)}>
98 - Browse…
99 - </button>
86 + <main className="home-main">
87 + <section className="home-intro">
88 + <p className="eyebrow">Voice-first build planning</p>
89 + <h1>Turn a conversation into a plan someone can build.</h1>
90 + <p className="hero-copy">
91 + VoiceTask asks one useful question at a time, keeps track of the details, and turns
92 + your answers into a build brief you can send on.
93 + </p>
94 + <div className="privacy-note">
95 + <span className="privacy-icon" aria-hidden="true">
96 + <span />
97 + </span>
98 + <p>
99 + <strong>No account needed.</strong> Your interview is saved locally, and the finished
100 + files go to the folder you choose.
101 + </p>
100 102 </div>
101 - </label>
102 - <button type="submit" className="btn-primary" disabled={creating}>
103 - {creating ? 'Starting…' : 'Start talking'}
104 - </button>
105 - </form>
103 + <OutcomePreview />
104 + </section>
105 +
106 + <section className="start-card" aria-labelledby="start-heading">
107 + <p className="card-kicker">Start here</p>
108 + <h2 id="start-heading">What are you working on?</h2>
109 + <p className="card-intro">A rough idea is enough. The interview will help fill in the rest.</p>
110 +
111 + <form className="create-form" onSubmit={handleCreate}>
112 + <div className="form-field">
113 + <label htmlFor="project-name">Name your idea</label>
114 + <p id="project-name-help">Use a short name so you can find this interview later.</p>
115 + <input
116 + id="project-name"
117 + value={name}
118 + onChange={(event) => setName(event.target.value)}
119 + placeholder="Family recipe app"
120 + aria-describedby="project-name-help"
121 + required
122 + />
123 + </div>
124 +
125 + <div className="form-field">
126 + <span className="field-label" id="project-folder-label">
127 + Choose where to save the build brief
128 + </span>
129 + <p id="project-folder-help">
130 + VoiceTask writes the finished files here. You can also download them as a zip.
131 + </p>
132 + <button
133 + type="button"
134 + className="choose-folder-button"
135 + onClick={() => setBrowsingFolder(true)}
136 + aria-describedby="project-folder-help"
137 + >
138 + {targetDir ? 'Choose a different folder' : 'Choose a folder'}
139 + </button>
140 + {targetDir && (
141 + <p className="selected-folder" title={targetDir}>
142 + <span>Selected</span>
143 + {targetDir}
144 + </p>
145 + )}
146 + <label className="path-label" htmlFor="project-folder">
147 + Or enter a folder path
148 + </label>
149 + <div className="folder-field">
150 + <input
151 + id="project-folder"
152 + value={targetDir}
153 + onChange={(event) => setTargetDir(event.target.value)}
154 + placeholder="Paste an absolute folder path"
155 + aria-labelledby="project-folder-label"
156 + aria-describedby="project-folder-help"
157 + required
158 + />
159 + </div>
160 + </div>
161 +
162 + <button type="submit" className="btn-primary start-button" disabled={creating}>
163 + {creating ? 'Starting...' : 'Start my interview'}
164 + </button>
165 + <p className="form-footnote">Answer by voice or typing. Stop whenever you have said enough.</p>
166 + </form>
167 +
168 + {error && (
169 + <p className="error" role="alert">
170 + {error}
171 + </p>
172 + )}
173 + </section>
174 + </main>
175 +
176 + <section className="process-section" aria-labelledby="process-heading">
177 + <div className="process-heading">
178 + <p className="eyebrow">How it works</p>
179 + <h2 id="process-heading">From rough idea to clear handoff</h2>
180 + </div>
181 + <ol className="process-steps">
182 + <li>
183 + <span className="step-number">1</span>
184 + <div>
185 + <h3>Talk naturally</h3>
186 + <p>Describe the thing you want to build by voice or typing.</p>
187 + </div>
188 + </li>
189 + <li>
190 + <span className="step-number">2</span>
191 + <div>
192 + <h3>Fill in the gaps</h3>
193 + <p>Answer one focused question at a time while VoiceTask tracks what is covered.</p>
194 + </div>
195 + </li>
196 + <li>
197 + <span className="step-number">3</span>
198 + <div>
199 + <h3>Send the brief</h3>
200 + <p>Download the requirements, plan, tasks, checks, and handoff notes in one zip.</p>
201 + </div>
202 + </li>
203 + </ol>
204 + </section>
106 205
107 206 {browsingFolder && (
108 207 <FolderBrowser
@@ -114,11 +213,15 @@function SessionListScreen({ onOpen }: { onOpen: (id: string) => void }) {
114 213 />
115 214 )}
116 215
117 - {error && <p className="error">{error}</p>}
118 -
119 216 {sessions.length > 0 && (
120 - <div className="session-list">
121 - <h2>Earlier sessions</h2>
217 + <section className="session-list" aria-labelledby="sessions-heading">
218 + <div className="session-list-heading">
219 + <div>
220 + <p className="eyebrow">Pick up where you left off</p>
221 + <h2 id="sessions-heading">Earlier interviews</h2>
222 + </div>
223 + <span>{sessions.length}</span>
224 + </div>
122 225 <ul>
123 226 {sessions.map((session) => (
124 227 <li key={session.id} className="session-row">
@@ -139,12 +242,12 @@function SessionListScreen({ onOpen }: { onOpen: (id: string) => void }) {
139 242 aria-label={`Delete ${session.name}`}
140 243 onClick={() => void handleDelete(session.id)}
141 244 >
142 - &times;
245 + <span aria-hidden="true">&times;</span>
143 246 </button>
144 247 </li>
145 248 ))}
146 249 </ul>
147 - </div>
250 + </section>
148 251 )}
149 252 </div>
150 253 )
added client/src/components/OutcomePreview.tsx +27 −0
@@ -0,0 +1,27 @@
1 +export function OutcomePreview() {
2 + return (
3 + <figure className="outcome-preview">
4 + <div className="preview-conversation">
5 + <span className="preview-label">Example: you say</span>
6 + <blockquote>"Only the owner should be able to delete a project."</blockquote>
7 + <div className="preview-wave" aria-hidden="true">
8 + {Array.from({ length: 16 }, (_, index) => (
9 + <span key={index} />
10 + ))}
11 + </div>
12 + </div>
13 + <div className="preview-connector" aria-hidden="true">
14 + <span />
15 + </div>
16 + <div className="preview-requirement">
17 + <div className="preview-document-header">
18 + <span>Build brief</span>
19 + <span>SPEC.md</span>
20 + </div>
21 + <p>Only project owners can delete a project.</p>
22 + <span className="source-marker">[S7] from your words</span>
23 + </div>
24 + <figcaption>Every requirement stays linked to what you actually said.</figcaption>
25 + </figure>
26 + )
27 +}
modified client/src/index.css +28 −21
@@ -1,19 +1,22 @@
1 1 :root {
2 - --paper: #f6f1e7;
3 - --paper-deep: #eee7d7;
4 - --card: #fcfaf4;
5 - --ink: #2c2620;
6 - --ink-soft: #7a6f61;
7 - --line: #e3d9c8;
8 - --clay: #c46a4a;
9 - --clay-deep: #a95538;
10 - --clay-soft: #f0ddd2;
11 - --moss: #7a8b6f;
12 - --amber: #d9a441;
13 - --danger: #b2503c;
2 + --paper: #f5f7ff;
3 + --paper-deep: #eaedfa;
4 + --card: #ffffff;
5 + --ink: #17192d;
6 + --ink-soft: #666b80;
7 + --line: #dfe2f0;
8 + --clay: #4856e8;
9 + --clay-deep: #3541c7;
10 + --clay-soft: #e5e7ff;
11 + --pulse: #ff6b57;
12 + --pulse-soft: #ffe7e2;
13 + --moss: #178866;
14 + --amber: #a76d00;
15 + --danger: #c2414a;
14 16
15 - --serif: 'Fraunces', Georgia, serif;
16 - --sans: 'Instrument Sans', 'Segoe UI', sans-serif;
17 + --serif: 'Sora', 'Segoe UI Variable Display', 'Segoe UI', sans-serif;
18 + --sans: 'DM Sans', 'Segoe UI', sans-serif;
19 + --mono: ui-monospace, 'Cascadia Code', 'SFMono-Regular', Consolas, monospace;
17 20
18 21 font: 16px/1.55 var(--sans);
19 22 color-scheme: light;
@@ -29,18 +32,17 @@
29 32 body {
30 33 margin: 0;
31 34 min-height: 100vh;
32 - background:
33 - radial-gradient(1100px 500px at 50% -10%, rgba(196, 106, 74, 0.07), transparent 65%),
34 - var(--paper);
35 + background: var(--paper);
35 36 }
36 37
37 -body::after {
38 +body::before {
38 39 content: '';
39 40 position: fixed;
40 - inset: 0;
41 + inset: 0 0 auto;
42 + height: 5px;
41 43 pointer-events: none;
42 - opacity: 0.05;
43 - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
44 + background: linear-gradient(90deg, var(--clay) 0 58%, var(--pulse) 58% 76%, var(--moss) 76%);
45 + z-index: 20;
44 46 }
45 47
46 48 h1,
@@ -66,3 +68,8 @@input {
66 68 ::selection {
67 69 background: var(--clay-soft);
68 70 }
71 +
72 +:where(button, a, input, summary):focus-visible {
73 + outline: 3px solid color-mix(in srgb, var(--clay) 38%, transparent);
74 + outline-offset: 3px;
75 +}
modified spec/TASKS.md +1 −1
@@ -82,7 +82,7 @@Work strictly in order unless a task's Depends line allows otherwise. One task a
82 82 - Depends: T15
83 83 - Verify: `npm run typecheck`, `npm test`, and `npm run build` exit 0. Manual: with a real-provider `.env` present, `npm run demo` reports mock providers and opens no provider connection.
84 84
85 -- [ ] T17 Mainstream first-run explanation
85 +- [x] T17 Mainstream first-run explanation
86 86 - Redesign the home screen around a direct outcome statement, the three-part flow, local privacy, and a representative source-backed requirement. Make folder browsing the obvious path, add plain helper copy to both fields, and keep earlier sessions easy to resume. Implement the visual system and responsive layout in PLAN.md without a UI dependency.
87 87 - Depends: T16
88 88 - Verify: `npm run typecheck` and `npm run build` exit 0. Static audit: every first-run claim maps to an existing requirement and all interactive controls have visible keyboard focus.