@seams/voice-capture 0.68.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  //#region src/card/rowMotion.ts
2
2
  const ROW_OUT_MS = 150;
3
- const ROW_IN_MS = 200;
4
- const ROW_IN_DELAY_MS = 20;
5
- const ROW_STAGGER_MS = 30;
3
+ const ROW_IN_MS = 140;
4
+ const ROW_IN_DELAY_MS = 0;
5
+ const ROW_STAGGER_MS = 20;
6
6
  const ROW_EASE = "cubic-bezier(0.2, 0, 0, 1)";
7
7
  //#endregion
8
8
  export { ROW_EASE, ROW_IN_DELAY_MS, ROW_IN_MS, ROW_OUT_MS, ROW_STAGGER_MS };
@@ -8,7 +8,29 @@ import { VoicePrompt, leaveSurface } from "./prompt.js";
8
8
  //#region src/card/surface.ts
9
9
  const LABEL = "Say this with your voice";
10
10
  const PROMPT_LABEL = "Say this sentence";
11
- const PROMPT_MESSAGE = "Read it naturally, then pause.";
11
+ /**
12
+ * The status line before a recording: empty, so the sentence is the one text to read. It holds
13
+ * "Opening microphone…", a loading line or a problem with the recording.
14
+ */
15
+ const PROMPT_MESSAGE = "";
16
+ /** The colours that a phrase card reads. */
17
+ const PHRASE_CARD_COLORS = [
18
+ "colorBackground",
19
+ "textPrimary",
20
+ "textSecondary",
21
+ "textMuted",
22
+ "borderPrimary",
23
+ "surface",
24
+ "buttonBackground",
25
+ "textButton",
26
+ "error",
27
+ "info",
28
+ "voiceLine",
29
+ "voiceEcho",
30
+ "voiceEchoDeep",
31
+ "voiceFill",
32
+ "voicePixelBackground"
33
+ ];
12
34
  /**
13
35
  * Shows the given sentence in the prompt. The surface records clips with the quality check, and
14
36
  * stays open so the caller can show its check and the result in the same card. A too quiet or
@@ -27,6 +49,14 @@ var CardSurface = class {
27
49
  pressed;
28
50
  /** Back, when the card shows it apart from Close. */
29
51
  back;
52
+ /** Minimize, when the host can hide the card. */
53
+ minimize;
54
+ minimizedHook;
55
+ progressedHook;
56
+ /** The person pressed Minimize, and the card has not asked to show again since. */
57
+ minimized = false;
58
+ /** The last stage that the host heard, as a key. */
59
+ progress = "";
30
60
  recorder = null;
31
61
  recording = null;
32
62
  choice = null;
@@ -55,8 +85,10 @@ var CardSurface = class {
55
85
  this.element.setAttribute("role", "dialog");
56
86
  this.element.setAttribute("aria-modal", "true");
57
87
  this.element.setAttribute("aria-label", LABEL);
88
+ paintColors(this.element, input.colors);
58
89
  this.prompt = new VoicePrompt(document, LABEL, sentence);
59
- this.prompt.title.textContent = PROMPT_LABEL;
90
+ const promptLabel = input.context?.sentenceLabel ?? PROMPT_LABEL;
91
+ this.prompt.title.textContent = promptLabel;
60
92
  const shown = /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
61
93
  this.prompt.phrase.textContent = shown;
62
94
  delete this.prompt.phrase.dataset.voiceidPhrase;
@@ -65,7 +97,11 @@ var CardSurface = class {
65
97
  const controls = frame.controls ?? "close";
66
98
  this.pressed = frame.pressed;
67
99
  const back = controls === "close" ? null : this.prompt.showBack(controls === "back_and_close");
100
+ if (input.context) this.prompt.showContext(input.context.label, input.context.text);
68
101
  this.back = back === this.prompt.cancel ? null : back;
102
+ this.minimizedHook = frame.minimized;
103
+ this.progressedHook = frame.progressed;
104
+ this.minimize = frame.minimized ? this.prompt.showMinimize() : null;
69
105
  const handedOverPrompt = handedOver?.querySelector(":scope > .seams-voiceid-prompt") ?? null;
70
106
  const rows = handedOverPrompt ? handedOverRows(handedOverPrompt) : null;
71
107
  if (handedOverPrompt) {
@@ -74,23 +110,31 @@ var CardSurface = class {
74
110
  this.autoStart = false;
75
111
  }
76
112
  this.card = new PhraseCard(this.prompt, {
77
- label: PROMPT_LABEL,
113
+ label: promptLabel,
78
114
  sentence: shown,
79
115
  message: PROMPT_MESSAGE,
80
116
  action: "Record"
81
117
  }, rows);
118
+ if (this.autoStart) {
119
+ this.prompt.record.hidden = true;
120
+ this.prompt.element.dataset.autoStart = "";
121
+ this.prompt.element.tabIndex = -1;
122
+ }
82
123
  this.element.append(this.prompt.element);
83
124
  this.prompt.record.addEventListener("click", this.press);
84
125
  const corner = controls === "back" ? this.pressBack : this.pressClose;
85
126
  this.prompt.cancel.addEventListener("click", corner);
86
127
  this.back?.addEventListener("click", this.pressBack);
128
+ this.minimize?.addEventListener("click", this.pressMinimize);
87
129
  this.element.addEventListener("cancel", this.cancel);
88
130
  outer.addEventListener("abort", this.cancel);
89
131
  if (handedOver) handedOver.replaceWith(this.element);
90
132
  else parent.appendChild(this.element);
133
+ const voiceLine = input.voiceLine ?? themeVoiceLine(this.prompt.element);
134
+ if (voiceLine === "pixels") this.prompt.useVoiceLine(voiceLine);
91
135
  if (frame.hugged) this.element.classList.add("seams-voiceid-hugged");
92
136
  this.unmounted = frame.mounted?.(this.element);
93
- if (rows) this.prompt.element.focus({ preventScroll: true });
137
+ if (rows || this.autoStart) this.prompt.element.focus({ preventScroll: true });
94
138
  else this.prompt.record.focus();
95
139
  if (outer.aborted) this.cancel();
96
140
  }
@@ -102,15 +146,36 @@ var CardSurface = class {
102
146
  this.recording = recording;
103
147
  if (this.view) {
104
148
  this.view = null;
149
+ this.report({
150
+ stage: "recording",
151
+ done: 0,
152
+ total: 0
153
+ });
105
154
  this.card.showPrompt();
106
155
  } else if (this.autoStart) this.begin(recording);
107
156
  this.autoStart = false;
108
157
  return recording.promise;
109
158
  }
110
159
  show(view) {
160
+ delete this.prompt.element.dataset.autoStart;
111
161
  if (this.closed) return Promise.resolve("closed");
112
162
  this.settleChoice("replaced");
113
163
  this.view = view;
164
+ if (view.kind === "check") {
165
+ const done = view.segments.filter((state) => state === "done" || state === "skip").length;
166
+ this.report({
167
+ stage: "checking",
168
+ done,
169
+ total: view.segments.length
170
+ });
171
+ } else {
172
+ this.report({
173
+ stage: "result",
174
+ done: 0,
175
+ total: 0
176
+ });
177
+ this.needPerson();
178
+ }
114
179
  this.card.show(view);
115
180
  return new Promise((resolve) => {
116
181
  this.choice = resolve;
@@ -129,6 +194,7 @@ var CardSurface = class {
129
194
  this.prompt.cancel.removeEventListener("click", this.pressBack);
130
195
  this.prompt.cancel.removeEventListener("click", this.pressClose);
131
196
  this.back?.removeEventListener("click", this.pressBack);
197
+ this.minimize?.removeEventListener("click", this.pressMinimize);
132
198
  this.card.dispose();
133
199
  this.prompt.dispose();
134
200
  leaveSurface(this.element);
@@ -144,6 +210,11 @@ var CardSurface = class {
144
210
  };
145
211
  begin(recording) {
146
212
  this.started = true;
213
+ this.report({
214
+ stage: "recording",
215
+ done: 0,
216
+ total: 0
217
+ });
147
218
  this.prompt.record.disabled = true;
148
219
  this.prompt.status.textContent = this.speechDetectorReady ? "Opening microphone…" : `Loading ${captureConfig().label}…`;
149
220
  this.attempt(recording, this.autoStart);
@@ -192,14 +263,46 @@ var CardSurface = class {
192
263
  };
193
264
  /** The end of speech starts the check in place; every other capture event is the prompt's. */
194
265
  capture = (event) => {
195
- if (event.kind === "processing") this.card.beginCheck();
196
- else this.prompt.capture(event);
266
+ if (event.kind === "processing") {
267
+ this.report({
268
+ stage: "checking",
269
+ done: 0,
270
+ total: 0
271
+ });
272
+ this.card.beginCheck();
273
+ } else this.prompt.capture(event);
197
274
  };
198
275
  retry(message, action = "Try again") {
199
276
  this.started = false;
277
+ delete this.prompt.element.dataset.autoStart;
200
278
  this.prompt.record.textContent = action;
201
279
  this.prompt.ready(message);
202
280
  this.prompt.record.focus();
281
+ this.report({
282
+ stage: "recording",
283
+ done: 0,
284
+ total: 0
285
+ });
286
+ this.needPerson();
287
+ }
288
+ /** Tells the host each new stage of the card. */
289
+ report(progress) {
290
+ const key = `${progress.stage}:${progress.done}:${progress.total}`;
291
+ if (key === this.progress || this.closed) return;
292
+ this.progress = key;
293
+ this.progressedHook?.(progress);
294
+ }
295
+ /** The person asks the host to hide the card. A host that showed it again can hide it again. */
296
+ pressMinimize = () => {
297
+ if (this.closed) return;
298
+ this.minimized = true;
299
+ this.minimizedHook?.(true);
300
+ };
301
+ /** The card needs the person: the host shows the card that it hid. */
302
+ needPerson() {
303
+ if (!this.minimized) return;
304
+ this.minimized = false;
305
+ this.minimizedHook?.(false);
203
306
  }
204
307
  settleChoice(choice) {
205
308
  const settle = this.choice;
@@ -226,6 +329,18 @@ var CardSurface = class {
226
329
  if (this.outer.aborted) this.close();
227
330
  };
228
331
  };
332
+ /** Sets the card's own colours. Keys that a card does not read are left out. */
333
+ function paintColors(element, colors) {
334
+ if (!colors) return;
335
+ for (const key of PHRASE_CARD_COLORS) {
336
+ const value = colors[key];
337
+ if (value) element.style.setProperty(`--seams-colors-${key}`, value);
338
+ }
339
+ }
340
+ /** The theme's `--seams-shape-voiceLine`: `pixels`, or else the wave lines. */
341
+ function themeVoiceLine(element) {
342
+ return getComputedStyle(element).getPropertyValue("--seams-shape-voiceLine").trim() === "pixels" ? "pixels" : "wave";
343
+ }
229
344
  /** A failed early load runs again when Record needs the detector, and shows its error there. */
230
345
  function ignoreEarlyLoad() {}
231
346
  /** The rows that the handed-over card shows now. */
@@ -1,2 +1,2 @@
1
- import { t as VoiceVisual } from "../chunks/visual-Di3ojTED.js";
2
- export { VoiceVisual };
1
+ import { n as beamPalette, t as VoiceVisual } from "../chunks/visual-DU1OVwCA.js";
2
+ export { VoiceVisual, beamPalette };
@@ -1430,6 +1430,31 @@ ${Le.split("{id}").join(de)}` : ca }), /* @__PURE__ */ jsxs("div", {
1430
1430
  ]
1431
1431
  })] });
1432
1432
  });
1433
+ /** The Beam's palette without a theme value. */
1434
+ const DEFAULT_BEAM = "ocean";
1435
+ /**
1436
+ * The band of the Beam in greys, for the `mono` palette: its own band has colour fringes in
1437
+ * every palette, pink on light and red and blue on dark.
1438
+ */
1439
+ const GREY_BAND = {
1440
+ light: {
1441
+ core: "#8b98a5",
1442
+ above: "#cfd9de",
1443
+ mid: "#aab8c2",
1444
+ below: "#657786"
1445
+ },
1446
+ dark: {
1447
+ core: "#ffffff",
1448
+ above: "#8b98a5",
1449
+ mid: "#cfd9de",
1450
+ below: "#536471"
1451
+ }
1452
+ };
1453
+ /** The theme's `voiceBeam`, the name of a Beam palette, or the default one. */
1454
+ function beamPalette(value) {
1455
+ const name = value.trim();
1456
+ return Object.hasOwn(Va, name) ? name : DEFAULT_BEAM;
1457
+ }
1433
1458
  /** Shared by enrollment, unlock and signing. The recorder remains the only audio owner. */
1434
1459
  var VoiceVisual = class {
1435
1460
  element;
@@ -1459,6 +1484,10 @@ var VoiceVisual = class {
1459
1484
  attributeFilter: ["data-theme"]
1460
1485
  });
1461
1486
  }
1487
+ /** Draws the voice as wave lines (the default) or as columns of pixels. */
1488
+ setLineStyle(style) {
1489
+ this.ribbon.setLineStyle(style);
1490
+ }
1462
1491
  listen() {
1463
1492
  this.setPhase("listening");
1464
1493
  }
@@ -1503,18 +1532,21 @@ var VoiceVisual = class {
1503
1532
  }
1504
1533
  render = () => {
1505
1534
  if (this.disposed) return;
1535
+ const mode = (this.prompt.closest("[data-theme]")?.getAttribute("data-theme") ?? this.prompt.ownerDocument.documentElement.getAttribute("data-theme")) === "light" ? "light" : "dark";
1536
+ const palette = beamPalette(getComputedStyle(this.prompt).getPropertyValue("--seams-colors-voiceBeam"));
1506
1537
  render(createElement(VoiceBeamView, {
1507
1538
  className: "seams-voiceid-beam",
1508
- theme: (this.prompt.closest("[data-theme]")?.getAttribute("data-theme") ?? this.prompt.ownerDocument.documentElement.getAttribute("data-theme")) === "light" ? "light" : "dark",
1539
+ theme: mode,
1540
+ ...palette === "mono" ? { bandColors: GREY_BAND[mode] } : {},
1509
1541
  level: this.ribbon.readLevel,
1510
1542
  processing: this.phase === "processing",
1511
1543
  active: this.phase !== "idle",
1512
1544
  staticColors: true,
1513
- colorVariant: "ocean",
1545
+ colorVariant: palette,
1514
1546
  borderRadius: Number.parseFloat(getComputedStyle(this.prompt).borderRadius) || 16,
1515
1547
  children: null
1516
1548
  }), this.glow);
1517
1549
  };
1518
1550
  };
1519
1551
  //#endregion
1520
- export { VoiceVisual as t };
1552
+ export { beamPalette as n, VoiceVisual as t };
@@ -1,6 +1,6 @@
1
- export { openPhraseCard, type PhraseCardControl, type PhraseCardControls, type PhraseCardFrame, type PhraseCardInput, } from './surface';
1
+ export { openPhraseCard, type PhraseCardColor, type PhraseCardColors, type PhraseCardContext, type PhraseCardControl, type PhraseCardControls, type PhraseCardFrame, type PhraseCardInput, type PhraseCardProgress, } from './surface';
2
2
  export type { PhraseCheckView, PhraseChoice, PhraseRecording, PhraseResultView, PhraseSegmentState, PhraseSurface, PhraseText, PhraseView, } from './types';
3
3
  export { leaveSurface, VoicePrompt, type PhraseDisplay, type PromptChange } from './prompt';
4
4
  export { PhraseCard, type HandedOverRows, type PromptCopy } from './phraseCard';
5
- export { VoiceRibbon, type VoiceCheckingStyle } from './ribbon';
5
+ export { VoiceRibbon, type VoiceCheckingStyle, type VoiceLineStyle } from './ribbon';
6
6
  export { VoiceVisual } from './visual';
@@ -1,5 +1,5 @@
1
1
  import type { VoiceCaptureEvent } from '../microphone';
2
- import type { VoiceCheckingStyle } from './ribbon';
2
+ import type { VoiceCheckingStyle, VoiceLineStyle } from './ribbon';
3
3
  /** How the card shows a phrase: by default as it is, with a capital letter and a full stop. */
4
4
  export type PhraseDisplay = (phrase: string) => string;
5
5
  /** A new state of the card's rows. Rows that it leaves out keep their content. */
@@ -45,12 +45,24 @@ export declare class VoicePrompt {
45
45
  constructor(document: Document, label: string, phrase: string, checking?: VoiceCheckingStyle,
46
46
  /** The text to show for a phrase, such as the phrase with its commas. */
47
47
  display?: PhraseDisplay);
48
+ /** Draws the voice as wave lines (the default) or as columns of pixels. */
49
+ useVoiceLine(style: VoiceLineStyle): void;
50
+ /**
51
+ * Shows the text that the sentence speaks for, such as the note that a spoken summary signs,
52
+ * above the sentence. It stays for the check and the result.
53
+ */
54
+ showContext(label: string, text: string): void;
48
55
  /**
49
56
  * In an app's dialog with a step before this card, Back in the leading corner returns to that
50
57
  * step. With `close`, Close stays in the trailing corner and ends the whole dialog; without it,
51
58
  * Back takes Close's place. Each one ends the card as cancelled. The result is the Back control.
52
59
  */
53
60
  showBack(close: boolean): HTMLButtonElement;
61
+ /**
62
+ * Minimize in the trailing corner, beside Close. It stays in the check, where Close is hidden,
63
+ * so the person can hide the card while the check goes on. The result is the control.
64
+ */
65
+ showMinimize(): HTMLButtonElement;
54
66
  /**
55
67
  * Moves the rows to a new state. The rows whose content changes fade out together, take their
56
68
  * new content and fade in one after another; the other rows stay still. A change that comes
@@ -2,6 +2,8 @@ import type { VoiceActivityEvent } from '../microphone';
2
2
  type RibbonPhase = 'idle' | 'listening' | 'processing';
3
3
  /** How the line shows that the voice is being checked. */
4
4
  export type VoiceCheckingStyle = 'ring' | 'segments';
5
+ /** How the card draws the voice while it listens: sine wave lines, or columns of pixels. */
6
+ export type VoiceLineStyle = 'wave' | 'pixels';
5
7
  /** Visual feedback from recorder activity. Never opens or retains an audio stream. */
6
8
  export declare class VoiceRibbon {
7
9
  private readonly checking;
@@ -25,8 +27,21 @@ export declare class VoiceRibbon {
25
27
  private target;
26
28
  private level;
27
29
  private stopped;
30
+ private lineStyle;
31
+ /** The pixel style's dots by column, top row first. Made when the style is first chosen. */
32
+ private pixels;
33
+ /** The level of each column, the newest at the right end. */
34
+ private readonly pixelLevels;
35
+ /** The rows that each column shows now, above and below the middle row. */
36
+ private readonly pixelHeights;
37
+ private pixelClock;
28
38
  constructor(document: Document, checking?: VoiceCheckingStyle);
29
39
  readonly readLevel: () => number;
40
+ /** Draws the voice as wave lines (the default) or as columns of pixels. */
41
+ setLineStyle(style: VoiceLineStyle): void;
42
+ private makePixels;
43
+ /** The voice line's width now: 1, or the theme's `voiceLineWidth`. */
44
+ private lineWidth;
30
45
  setPhase(phase: RibbonPhase): void;
31
46
  /**
32
47
  * Closes the ring into the success seal; resolves when the check is drawn.
@@ -41,8 +56,12 @@ export declare class VoiceRibbon {
41
56
  private wake;
42
57
  private readonly draw;
43
58
  private idleAmplitude;
59
+ /** Adds the level of each step at the right end; the older columns move left. */
60
+ private stepPixels;
44
61
  private paint;
45
62
  private paintLine;
63
+ /** Each column shows the middle dot and as many rows above and below it as its level. */
64
+ private paintPixels;
46
65
  private paintSegments;
47
66
  /** Each segment brightens one beat after the one before it, from the resting line's opacity. */
48
67
  private segmentOpacity;
@@ -1,5 +1,5 @@
1
1
  export declare const ROW_OUT_MS = 150;
2
- export declare const ROW_IN_MS = 200;
3
- export declare const ROW_IN_DELAY_MS = 20;
4
- export declare const ROW_STAGGER_MS = 30;
2
+ export declare const ROW_IN_MS = 140;
3
+ export declare const ROW_IN_DELAY_MS = 0;
4
+ export declare const ROW_STAGGER_MS = 20;
5
5
  export declare const ROW_EASE = "cubic-bezier(0.2, 0, 0, 1)";
@@ -1,4 +1,5 @@
1
1
  import { type VoiceRecordingOptions } from '../microphone';
2
+ import type { VoiceLineStyle } from './ribbon';
2
3
  import type { PhraseSurface } from './types';
3
4
  /** What the host page adds around the card. Every field is optional. */
4
5
  export type PhraseCardFrame = Readonly<{
@@ -14,6 +15,16 @@ export type PhraseCardFrame = Readonly<{
14
15
  controls?: PhraseCardControls;
15
16
  /** Runs when the person presses Back or Close, before the card ends. Escape is not a press. */
16
17
  pressed?(control: PhraseCardControl): void;
18
+ /**
19
+ * With it, the card draws Minimize in the trailing corner, beside Close, also in the check.
20
+ * A press runs `minimized(true)`. The card does not hide itself: the host hides it, and the
21
+ * recording, the check and the caller's work go on. The card runs `minimized(false)` when it
22
+ * needs the person again: on a result, and when a recording needs another try. The host can
23
+ * show the card again before that.
24
+ */
25
+ minimized?(minimized: boolean): void;
26
+ /** Runs on each new stage of the card, so the host can show the progress of a hidden card. */
27
+ progressed?(progress: PhraseCardProgress): void;
17
28
  /** The host sizes its frame to the surface, so the card follows the frame. */
18
29
  hugged?: boolean;
19
30
  /** Runs when the surface is in the document. Its return value runs when the surface closes. */
@@ -23,6 +34,16 @@ export type PhraseCardFrame = Readonly<{
23
34
  export type PhraseCardControls = 'close' | 'back' | 'back_and_close';
24
35
  /** One corner control of a phrase card. */
25
36
  export type PhraseCardControl = 'back' | 'close';
37
+ /**
38
+ * The stage of a phrase card: it records the sentence (or waits for Record), it checks the
39
+ * recording, or it shows a result.
40
+ */
41
+ export type PhraseCardProgress = Readonly<{
42
+ stage: 'recording' | 'checking' | 'result';
43
+ /** The parts of the check that are over, of `total`. Both are 0 outside a check. */
44
+ done: number;
45
+ total: number;
46
+ }>;
26
47
  export type PhraseCardInput = Readonly<{
27
48
  sentence: string;
28
49
  signal: AbortSignal;
@@ -35,6 +56,47 @@ export type PhraseCardInput = Readonly<{
35
56
  * start audio without a press, the card stays with its Record button.
36
57
  */
37
58
  autoStart?: boolean;
59
+ /**
60
+ * The text that the sentence speaks for, such as the note that a spoken summary signs. With
61
+ * it, the card is a form: the text in a soft box under its label, the sentence centred in a
62
+ * box under its own label, and one button at the foot that is the voice line while the card
63
+ * records. The text stays for the check and the result.
64
+ */
65
+ context?: PhraseCardContext;
66
+ /**
67
+ * How the card draws the voice while it listens: `wave`, sine wave lines (the default), or
68
+ * `pixels`, columns of dots in blue tones. Without it, the theme value `--seams-shape-voiceLine`
69
+ * (`wave` or `pixels`) decides, so a Wallet host can choose it with its theme.
70
+ */
71
+ voiceLine?: VoiceLineStyle;
72
+ /**
73
+ * The card's colours, as CSS colours, for example `{ buttonBackground: '#1d9bf0' }`. Each one
74
+ * sets the CSS variable `--seams-colors-<key>` on this card, over the page's own value. A
75
+ * Seams Wallet app sets the same keys in `appearance.theme.colors`.
76
+ */
77
+ colors?: PhraseCardColors;
78
+ }>;
79
+ /** The colours that a phrase card reads. */
80
+ declare const PHRASE_CARD_COLORS: readonly [
81
+ /** The card, its text, its lines and its soft boxes. */
82
+ 'colorBackground', 'textPrimary', 'textSecondary', 'textMuted', 'borderPrimary', 'surface',
83
+ /** The main button (Record, the voice line of a form card, Done) and its text. */
84
+ 'buttonBackground', 'textButton',
85
+ /** A failed check, and a held one. */
86
+ 'error', 'info',
87
+ /** The voice line, its echoes, and the bands of the pixel style. */
88
+ 'voiceLine', 'voiceEcho', 'voiceEchoDeep',
89
+ /** A form card: the fill of the sentence's box, and the button behind the pixels. */
90
+ 'voiceFill', 'voicePixelBackground'];
91
+ export type PhraseCardColor = (typeof PHRASE_CARD_COLORS)[number];
92
+ export type PhraseCardColors = Partial<Readonly<Record<PhraseCardColor, string>>>;
93
+ /** The text that a phrase card shows above its sentence. */
94
+ export type PhraseCardContext = Readonly<{
95
+ /** The label above the text, such as "Your note on @alice". */
96
+ label: string;
97
+ text: string;
98
+ /** The label above the sentence, such as "Read this aloud to post". Default: "Say this sentence". */
99
+ sentenceLabel?: string;
38
100
  }>;
39
101
  /**
40
102
  * Shows the given sentence in the prompt. The surface records clips with the quality check, and
@@ -42,3 +104,4 @@ export type PhraseCardInput = Readonly<{
42
104
  * interrupted recording stays on the surface for another try.
43
105
  */
44
106
  export declare function openPhraseCard(input: PhraseCardInput): PhraseSurface;
107
+ export {};
@@ -1,5 +1,8 @@
1
+ import { type VoiceBeamColorVariant } from 'voice-glow';
1
2
  import type { VoiceActivityEvent } from '../microphone';
2
- import { type VoiceCheckingStyle } from './ribbon';
3
+ import { type VoiceCheckingStyle, type VoiceLineStyle } from './ribbon';
4
+ /** The theme's `voiceBeam`, the name of a Beam palette, or the default one. */
5
+ export declare function beamPalette(value: string): VoiceBeamColorVariant;
3
6
  /** Shared by enrollment, unlock and signing. The recorder remains the only audio owner. */
4
7
  export declare class VoiceVisual {
5
8
  readonly element: SVGSVGElement;
@@ -10,6 +13,8 @@ export declare class VoiceVisual {
10
13
  private phase;
11
14
  private disposed;
12
15
  constructor(document: Document, prompt: HTMLElement, checking: VoiceCheckingStyle);
16
+ /** Draws the voice as wave lines (the default) or as columns of pixels. */
17
+ setLineStyle(style: VoiceLineStyle): void;
13
18
  listen(): void;
14
19
  add(event: VoiceActivityEvent): void;
15
20
  checking(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/voice-capture",
3
- "version": "0.68.0",
3
+ "version": "0.69.0",
4
4
  "type": "module",
5
5
  "description": "Records one spoken sentence in the browser, with speech detection, a quality check and a phrase card",
6
6
  "license": "Apache-2.0",