wally-ui 1.14.1 → 1.16.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.
Files changed (62) hide show
  1. package/dist/cli.js +0 -0
  2. package/package.json +1 -1
  3. package/playground/showcase/public/sitemap.xml +15 -0
  4. package/playground/showcase/src/app/app.routes.server.ts +8 -0
  5. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.html +11 -2
  6. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.ts +13 -3
  7. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.css +0 -0
  8. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.html +41 -0
  9. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.service.spec.ts +16 -0
  10. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.service.ts +175 -0
  11. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.spec.ts +23 -0
  12. package/playground/showcase/src/app/components/audio-waveform/audio-waveform.ts +64 -0
  13. package/playground/showcase/src/app/components/combobox/combobox-content/combobox-content.css +0 -0
  14. package/playground/showcase/src/app/components/combobox/combobox-content/combobox-content.html +41 -0
  15. package/playground/showcase/src/app/components/combobox/combobox-content/combobox-content.spec.ts +228 -0
  16. package/playground/showcase/src/app/components/combobox/combobox-content/combobox-content.ts +217 -0
  17. package/playground/showcase/src/app/components/combobox/combobox-empty/combobox-empty.css +0 -0
  18. package/playground/showcase/src/app/components/combobox/combobox-empty/combobox-empty.html +3 -0
  19. package/playground/showcase/src/app/components/combobox/combobox-empty/combobox-empty.spec.ts +56 -0
  20. package/playground/showcase/src/app/components/combobox/combobox-empty/combobox-empty.ts +11 -0
  21. package/playground/showcase/src/app/components/combobox/combobox-group/combobox-group.css +0 -0
  22. package/playground/showcase/src/app/components/combobox/combobox-group/combobox-group.html +11 -0
  23. package/playground/showcase/src/app/components/combobox/combobox-group/combobox-group.spec.ts +57 -0
  24. package/playground/showcase/src/app/components/combobox/combobox-group/combobox-group.ts +11 -0
  25. package/playground/showcase/src/app/components/combobox/combobox-input/combobox-input.css +0 -0
  26. package/playground/showcase/src/app/components/combobox/combobox-input/combobox-input.html +71 -0
  27. package/playground/showcase/src/app/components/combobox/combobox-input/combobox-input.spec.ts +468 -0
  28. package/playground/showcase/src/app/components/combobox/combobox-input/combobox-input.ts +90 -0
  29. package/playground/showcase/src/app/components/combobox/combobox-item/combobox-item.css +0 -0
  30. package/playground/showcase/src/app/components/combobox/combobox-item/combobox-item.html +58 -0
  31. package/playground/showcase/src/app/components/combobox/combobox-item/combobox-item.spec.ts +173 -0
  32. package/playground/showcase/src/app/components/combobox/combobox-item/combobox-item.ts +37 -0
  33. package/playground/showcase/src/app/components/combobox/combobox-search/combobox-search.css +0 -0
  34. package/playground/showcase/src/app/components/combobox/combobox-search/combobox-search.html +11 -0
  35. package/playground/showcase/src/app/components/combobox/combobox-search/combobox-search.spec.ts +166 -0
  36. package/playground/showcase/src/app/components/combobox/combobox-search/combobox-search.ts +36 -0
  37. package/playground/showcase/src/app/components/combobox/combobox-trigger/combobox-trigger.css +0 -0
  38. package/playground/showcase/src/app/components/combobox/combobox-trigger/combobox-trigger.html +8 -0
  39. package/playground/showcase/src/app/components/combobox/combobox-trigger/combobox-trigger.spec.ts +137 -0
  40. package/playground/showcase/src/app/components/combobox/combobox-trigger/combobox-trigger.ts +30 -0
  41. package/playground/showcase/src/app/components/combobox/combobox.css +0 -0
  42. package/playground/showcase/src/app/components/combobox/combobox.html +3 -0
  43. package/playground/showcase/src/app/components/combobox/combobox.spec.ts +391 -0
  44. package/playground/showcase/src/app/components/combobox/combobox.ts +59 -0
  45. package/playground/showcase/src/app/components/combobox/lib/models/combobox.model.ts +13 -0
  46. package/playground/showcase/src/app/components/combobox/lib/service/combobox.service.spec.ts +530 -0
  47. package/playground/showcase/src/app/components/combobox/lib/service/combobox.service.ts +191 -0
  48. package/playground/showcase/src/app/components/combobox/lib/types/combobox-position.type.ts +1 -0
  49. package/playground/showcase/src/app/components/combobox/lib/types/combobox-trigger-mode.type.ts +1 -0
  50. package/playground/showcase/src/app/core/services/seo.service.ts +100 -0
  51. package/playground/showcase/src/app/pages/documentation/components/audio-waveform-docs/audio-waveform-docs.css +1 -0
  52. package/playground/showcase/src/app/pages/documentation/components/audio-waveform-docs/audio-waveform-docs.examples.ts +146 -0
  53. package/playground/showcase/src/app/pages/documentation/components/audio-waveform-docs/audio-waveform-docs.html +576 -0
  54. package/playground/showcase/src/app/pages/documentation/components/audio-waveform-docs/audio-waveform-docs.ts +124 -0
  55. package/playground/showcase/src/app/pages/documentation/components/combobox-docs/combobox-docs.component.css +0 -0
  56. package/playground/showcase/src/app/pages/documentation/components/combobox-docs/combobox-docs.component.html +383 -0
  57. package/playground/showcase/src/app/pages/documentation/components/combobox-docs/combobox-docs.component.spec.ts +23 -0
  58. package/playground/showcase/src/app/pages/documentation/components/combobox-docs/combobox-docs.component.ts +333 -0
  59. package/playground/showcase/src/app/pages/documentation/components/combobox-docs/combobox-docs.examples.ts +226 -0
  60. package/playground/showcase/src/app/pages/documentation/components/components.html +27 -0
  61. package/playground/showcase/src/app/pages/documentation/components/components.routes.ts +8 -0
  62. package/playground/showcase/src/app/pages/home/home.html +1 -1
@@ -0,0 +1,576 @@
1
+ <main class="min-h-dvh bg-white dark:bg-[#0a0a0a] font-mono" role="main">
2
+ <div class="max-w-4xl mx-auto px-4 py-8 sm:px-6 md:px-8">
3
+ <!-- Breadcrumb Navigation -->
4
+ <nav class="mb-8" aria-label="Breadcrumb navigation">
5
+ <wally-breadcrumb [items]="breadcrumbItems"></wally-breadcrumb>
6
+ </nav>
7
+
8
+ <!-- Header -->
9
+ <header class="mb-12" role="banner">
10
+ <h1 id="page-title" class="text-3xl sm:text-4xl font-bold text-[#0a0a0a] dark:text-white mb-4 uppercase">
11
+ <span aria-hidden="true">&gt;_ </span>Audio Waveform
12
+ </h1>
13
+ <p class="text-sm sm:text-base text-neutral-700 dark:text-neutral-300 border-l-2 border-neutral-300 dark:border-neutral-700 pl-4 leading-relaxed">
14
+ Real-time audio waveform visualizer component with microphone input, FFT frequency analysis, and automatic audio recording. Features responsive bar count, smooth 60fps animations, and built-in recording functionality with download support.
15
+ </p>
16
+
17
+ <!-- AI Assistant Links -->
18
+ <div class="flex flex-wrap gap-0 mt-6 border-2 border-neutral-300 dark:border-neutral-700" role="list"
19
+ aria-label="Open documentation in AI coding assistants">
20
+ <a [href]="claudeUrl" target="_blank"
21
+ class="group flex-1 min-w-[200px] flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-[#0a0a0a] border-b-2 sm:border-b-0 sm:border-r-2 border-neutral-300 dark:border-neutral-700 hover:bg-[#0a0a0a] dark:hover:bg-white transition-all duration-150 cursor-pointer"
22
+ rel="noopener noreferrer" role="listitem"
23
+ aria-label="Open Audio Waveform component documentation in Claude AI assistant (opens in new tab)">
24
+ <svg xmlns="http://www.w3.org/2000/svg"
25
+ class="size-5 text-[#0a0a0a] dark:text-neutral-400 group-hover:text-white dark:group-hover:text-[#0a0a0a] transition-colors duration-150"
26
+ viewBox="0 0 24 24" aria-hidden="true">
27
+ <path fill="currentColor"
28
+ d="m4.714 15.956l4.718-2.648l.079-.23l-.08-.128h-.23l-.79-.048l-2.695-.073l-2.337-.097l-2.265-.122l-.57-.121l-.535-.704l.055-.353l.48-.321l.685.06l1.518.104l2.277.157l1.651.098l2.447.255h.389l.054-.158l-.133-.097l-.103-.098l-2.356-1.596l-2.55-1.688l-1.336-.972l-.722-.491L2 6.223l-.158-1.008l.656-.722l.88.06l.224.061l.893.686l1.906 1.476l2.49 1.833l.364.304l.146-.104l.018-.072l-.164-.274l-1.354-2.446l-1.445-2.49l-.644-1.032l-.17-.619a3 3 0 0 1-.103-.729L6.287.133L6.7 0l.995.134l.42.364l.619 1.415L9.735 4.14l1.555 3.03l.455.898l.243.832l.09.255h.159V9.01l.127-1.706l.237-2.095l.23-2.695l.08-.76l.376-.91l.747-.492l.583.28l.48.685l-.067.444l-.286 1.851l-.558 2.903l-.365 1.942h.213l.243-.242l.983-1.306l1.652-2.064l.728-.82l.85-.904l.547-.431h1.032l.759 1.129l-.34 1.166l-1.063 1.347l-.88 1.142l-1.263 1.7l-.79 1.36l.074.11l.188-.02l2.853-.606l1.542-.28l1.84-.315l.832.388l.09.395l-.327.807l-1.967.486l-2.307.462l-3.436.813l-.043.03l.049.061l1.548.146l.662.036h1.62l3.018.225l.79.522l.473.638l-.08.485l-1.213.62l-1.64-.389l-3.825-.91l-1.31-.329h-.183v.11l1.093 1.068l2.003 1.81l2.508 2.33l.127.578l-.321.455l-.34-.049l-2.204-1.657l-.85-.747l-1.925-1.62h-.127v.17l.443.649l2.343 3.521l.122 1.08l-.17.353l-.607.213l-.668-.122l-1.372-1.924l-1.415-2.168l-1.141-1.943l-.14.08l-.674 7.254l-.316.37l-.728.28l-.607-.461l-.322-.747l.322-1.476l.388-1.924l.316-1.53l.285-1.9l.17-.632l-.012-.042l-.14.018l-1.432 1.967l-2.18 2.945l-1.724 1.845l-.413.164l-.716-.37l.066-.662l.401-.589l2.386-3.036l1.439-1.882l.929-1.086l-.006-.158h-.055L4.138 18.56l-1.13.146l-.485-.456l.06-.746l.231-.243l1.907-1.312Z" />
29
+ </svg>
30
+ <span
31
+ class="text-xs sm:text-sm font-medium text-[#0a0a0a] dark:text-neutral-400 group-hover:text-white dark:group-hover:text-[#0a0a0a] transition-colors duration-150">
32
+ Open in Claude
33
+ </span>
34
+ </a>
35
+
36
+ <a [href]="chatGptUrl" target="_blank"
37
+ class="group flex-1 min-w-[200px] flex items-center justify-center gap-2 px-4 py-3 bg-white dark:bg-[#0a0a0a] hover:bg-[#0a0a0a] dark:hover:bg-white transition-all duration-150 cursor-pointer"
38
+ rel="noopener noreferrer" role="listitem"
39
+ aria-label="Open Audio Waveform component documentation in ChatGPT AI assistant (opens in new tab)">
40
+ <svg xmlns="http://www.w3.org/2000/svg"
41
+ class="size-5 text-[#0a0a0a] dark:text-neutral-400 group-hover:text-white dark:group-hover:text-[#0a0a0a] transition-colors duration-150"
42
+ viewBox="0 0 48 48" aria-hidden="true">
43
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
44
+ d="M18.38 27.94v-14.4l11.19-6.46c6.2-3.58 17.3 5.25 12.64 13.33" />
45
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
46
+ d="m18.38 20.94l12.47-7.2l11.19 6.46c6.2 3.58 4.1 17.61-5.23 17.61" />
47
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
48
+ d="m24.44 17.44l12.47 7.2v12.93c0 7.16-13.2 12.36-17.86 4.28" />
49
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
50
+ d="M30.5 21.2v14.14L19.31 41.8c-6.2 3.58-17.3-5.25-12.64-13.33" />
51
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
52
+ d="m30.5 27.94l-12.47 7.2l-11.19-6.46c-6.21-3.59-4.11-17.61 5.22-17.61" />
53
+ <path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
54
+ d="m24.44 31.44l-12.47-7.2V11.31c0-7.16 13.2-12.36 17.86-4.28" />
55
+ </svg>
56
+ <span
57
+ class="text-xs sm:text-sm font-medium text-[#0a0a0a] dark:text-neutral-400 group-hover:text-white dark:group-hover:text-[#0a0a0a] transition-colors duration-150">
58
+ Open in ChatGPT
59
+ </span>
60
+ </a>
61
+ </div>
62
+ </header>
63
+
64
+ <!-- Installation -->
65
+ <section id="installation" class="mb-12" aria-labelledby="installation-heading">
66
+ <h2 id="installation-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
67
+ [ Installation ]
68
+ </h2>
69
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
70
+ <pre><code [innerHTML]="installationCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
71
+ </div>
72
+ </section>
73
+
74
+ <!-- Preview -->
75
+ <section id="preview" class="mb-12" aria-labelledby="preview-heading">
76
+ <h2 id="preview-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
77
+ [ Live Preview ]
78
+ </h2>
79
+ <div class="p-8 border-2 border-neutral-300 dark:border-neutral-700 bg-white dark:bg-[#0a0a0a]" role="img"
80
+ aria-label="Live interactive audio waveform visualizer demo">
81
+ <div class="flex flex-col gap-4">
82
+ <!-- Waveform Component -->
83
+ @if (isRecording()) {
84
+ <wally-audio-waveform
85
+ [isStartRecording]="isRecording()"
86
+ [isStopRecording]="!isRecording()"
87
+ [showTimer]="true">
88
+ </wally-audio-waveform>
89
+ }
90
+
91
+ <!-- Controls -->
92
+ <div class="flex gap-2 justify-center">
93
+ @if (!isRecording()) {
94
+ <wally-button
95
+ variant="destructive"
96
+ (buttonClick)="startRecording()"
97
+ ariaLabel="Start audio recording">
98
+ Start Recording
99
+ </wally-button>
100
+ } @else {
101
+ <wally-button
102
+ variant="secondary"
103
+ (buttonClick)="stopRecording()"
104
+ ariaLabel="Stop audio recording">
105
+ Stop Recording
106
+ </wally-button>
107
+ }
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <aside class="mt-4 p-4 bg-amber-50 dark:bg-amber-900/10 border-2 border-amber-400 dark:border-amber-700" role="note">
112
+ <p class="text-xs sm:text-sm text-amber-700 dark:text-amber-400 leading-relaxed">
113
+ <strong>Tip:</strong> Click "Start Recording" to see the real-time waveform visualization. Allow microphone access when prompted. The component automatically adapts bar count for mobile (30 bars) and desktop (65 bars).
114
+ </p>
115
+ </aside>
116
+ </section>
117
+
118
+ <!-- Import -->
119
+ <section id="import" class="mb-12" aria-labelledby="import-heading">
120
+ <h2 id="import-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
121
+ [ Import ]
122
+ </h2>
123
+ <div class="space-y-4">
124
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
125
+ <pre><code [innerHTML]="importCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
126
+ </div>
127
+
128
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
129
+ <pre><code [innerHTML]="componentImportCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
130
+ </div>
131
+ </div>
132
+ </section>
133
+
134
+ <!-- Basic Usage -->
135
+ <section id="basic-usage" class="mb-12" aria-labelledby="basic-usage-heading">
136
+ <h2 id="basic-usage-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
137
+ [ Basic Usage ]
138
+ </h2>
139
+
140
+ <!-- Minimal Example -->
141
+ <article class="mb-8" aria-labelledby="minimal-example-heading">
142
+ <h3 id="minimal-example-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
143
+ <span aria-hidden="true">&gt; </span>Minimal Example
144
+ </h3>
145
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3 mb-4">
146
+ <pre><code [innerHTML]="basicUsageCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
147
+ </div>
148
+ </article>
149
+
150
+ <!-- With Timer -->
151
+ <article class="mb-8" aria-labelledby="with-timer-heading">
152
+ <h3 id="with-timer-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
153
+ <span aria-hidden="true">&gt; </span>With Recording Timer
154
+ </h3>
155
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3 mb-4">
156
+ <pre><code [innerHTML]="withTimerCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
157
+ </div>
158
+ </article>
159
+
160
+ <!-- Complete Example -->
161
+ <article class="mb-8" aria-labelledby="complete-example-heading">
162
+ <h3 id="complete-example-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
163
+ <span aria-hidden="true">&gt; </span>Complete Example with Controls
164
+ </h3>
165
+ <div class="space-y-4">
166
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
167
+ <pre><code [innerHTML]="completeExampleCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
168
+ </div>
169
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
170
+ <pre><code [innerHTML]="completeExampleTsCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
171
+ </div>
172
+ </div>
173
+ </article>
174
+ </section>
175
+
176
+ <!-- Visual Examples -->
177
+ <section id="visual-examples" class="mb-12" aria-labelledby="visual-examples-heading">
178
+ <h2 id="visual-examples-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
179
+ [ Visual Examples ]
180
+ </h2>
181
+
182
+ <!-- Without Timer -->
183
+ <article class="mb-8" aria-labelledby="without-timer-example-heading">
184
+ <h3 id="without-timer-example-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
185
+ <span aria-hidden="true">&gt; </span>Without Timer
186
+ </h3>
187
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
188
+ Basic waveform visualization without recording timer display.
189
+ </p>
190
+ <div class="p-8 border-2 border-neutral-300 dark:border-neutral-700 bg-white dark:bg-[#0a0a0a]" role="img"
191
+ aria-label="Audio waveform example without timer">
192
+ <div class="flex flex-col gap-4">
193
+ @if (isRecordingWithoutTimer()) {
194
+ <wally-audio-waveform
195
+ [isStartRecording]="isRecordingWithoutTimer()"
196
+ [isStopRecording]="!isRecordingWithoutTimer()"
197
+ [showTimer]="false">
198
+ </wally-audio-waveform>
199
+ }
200
+
201
+ <div class="flex gap-2 justify-center">
202
+ @if (!isRecordingWithoutTimer()) {
203
+ <wally-button
204
+ variant="destructive"
205
+ (buttonClick)="startRecordingWithoutTimer()"
206
+ ariaLabel="Start recording without timer">
207
+ Start Recording
208
+ </wally-button>
209
+ } @else {
210
+ <wally-button
211
+ variant="secondary"
212
+ (buttonClick)="stopRecordingWithoutTimer()"
213
+ ariaLabel="Stop recording">
214
+ Stop Recording
215
+ </wally-button>
216
+ }
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </article>
221
+
222
+ <!-- With Timer -->
223
+ <article class="mb-8" aria-labelledby="with-timer-example-heading">
224
+ <h3 id="with-timer-example-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
225
+ <span aria-hidden="true">&gt; </span>With Timer
226
+ </h3>
227
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
228
+ Waveform visualization with recording timer in MM:SS format.
229
+ </p>
230
+ <div class="p-8 border-2 border-neutral-300 dark:border-neutral-700 bg-white dark:bg-[#0a0a0a]" role="img"
231
+ aria-label="Audio waveform example with timer">
232
+ <div class="flex flex-col gap-4">
233
+ @if (isRecordingWithTimer()) {
234
+ <wally-audio-waveform
235
+ [isStartRecording]="isRecordingWithTimer()"
236
+ [isStopRecording]="!isRecordingWithTimer()"
237
+ [showTimer]="true">
238
+ </wally-audio-waveform>
239
+ }
240
+
241
+ <div class="flex gap-2 justify-center">
242
+ @if (!isRecordingWithTimer()) {
243
+ <wally-button
244
+ variant="destructive"
245
+ (buttonClick)="startRecordingWithTimer()"
246
+ ariaLabel="Start recording with timer">
247
+ Start Recording
248
+ </wally-button>
249
+ } @else {
250
+ <wally-button
251
+ variant="secondary"
252
+ (buttonClick)="stopRecordingWithTimer()"
253
+ ariaLabel="Stop recording">
254
+ Stop Recording
255
+ </wally-button>
256
+ }
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </article>
261
+
262
+ <!-- Download Example -->
263
+ <article class="mb-8" aria-labelledby="download-example-heading">
264
+ <h3 id="download-example-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
265
+ <span aria-hidden="true">&gt; </span>With Download Functionality
266
+ </h3>
267
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
268
+ Record audio and download the result as a WebM file.
269
+ </p>
270
+ <div class="p-8 border-2 border-neutral-300 dark:border-neutral-700 bg-white dark:bg-[#0a0a0a]" role="img"
271
+ aria-label="Audio waveform with download functionality">
272
+ <div class="flex flex-col gap-4">
273
+ <wally-audio-waveform
274
+ #downloadWaveform
275
+ [isStartRecording]="isRecordingWithDownload()"
276
+ [isStopRecording]="!isRecordingWithDownload()"
277
+ [showTimer]="true">
278
+ </wally-audio-waveform>
279
+
280
+ <div class="flex gap-2 justify-center flex-wrap">
281
+ @if (!isRecordingWithDownload() && !downloadWaveform.audioWaveformService.recordedAudioUrl()) {
282
+ <wally-button
283
+ variant="destructive"
284
+ (buttonClick)="startRecordingWithDownload()"
285
+ ariaLabel="Start recording">
286
+ Start Recording
287
+ </wally-button>
288
+ }
289
+
290
+ @if (isRecordingWithDownload()) {
291
+ <wally-button
292
+ variant="secondary"
293
+ (buttonClick)="stopRecordingWithDownload()"
294
+ ariaLabel="Stop recording">
295
+ Stop Recording
296
+ </wally-button>
297
+ }
298
+
299
+ @if (downloadWaveform.audioWaveformService.recordedAudioUrl()) {
300
+ <wally-button
301
+ variant="primary"
302
+ (buttonClick)="downloadWaveform.audioWaveformService.downloadRecording('my-recording.webm')"
303
+ ariaLabel="Download recording">
304
+ Download Recording
305
+ </wally-button>
306
+ <wally-button
307
+ variant="ghost"
308
+ (buttonClick)="downloadWaveform.audioWaveformService.clearRecording()"
309
+ ariaLabel="Clear recording">
310
+ Clear
311
+ </wally-button>
312
+ }
313
+ </div>
314
+ </div>
315
+ </div>
316
+ </article>
317
+ </section>
318
+
319
+ <!-- Service Integration -->
320
+ <section id="service-integration" class="mb-12" aria-labelledby="service-heading">
321
+ <h2 id="service-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
322
+ [ Service Integration ]
323
+ </h2>
324
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-6 leading-relaxed">
325
+ Access the AudioWaveformService to download, retrieve, or clear recorded audio.
326
+ </p>
327
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
328
+ <pre><code [innerHTML]="serviceIntegrationCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
329
+ </div>
330
+ </section>
331
+
332
+ <!-- How It Works -->
333
+ <section id="how-it-works" class="mb-12" aria-labelledby="how-it-works-heading">
334
+ <h2 id="how-it-works-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
335
+ [ How It Works ]
336
+ </h2>
337
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
338
+ The component uses the Web Audio API to analyze microphone input in real-time with FFT (Fast Fourier Transform) frequency analysis.
339
+ </p>
340
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3 mb-6">
341
+ <pre><code [innerHTML]="webAudioConceptsCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
342
+ </div>
343
+
344
+ <article aria-labelledby="technical-flow-heading">
345
+ <h3 id="technical-flow-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
346
+ <span aria-hidden="true">&gt; </span>Technical Flow
347
+ </h3>
348
+ <ol class="space-y-2 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed list-decimal list-inside">
349
+ <li><strong class="text-[#0a0a0a] dark:text-white">Microphone Access:</strong> getUserMedia() requests permission</li>
350
+ <li><strong class="text-[#0a0a0a] dark:text-white">Audio Context:</strong> Creates AudioContext for processing</li>
351
+ <li><strong class="text-[#0a0a0a] dark:text-white">Frequency Analysis:</strong> AnalyserNode performs FFT (256 samples → 128 frequencies)</li>
352
+ <li><strong class="text-[#0a0a0a] dark:text-white">Data Extraction:</strong> getByteFrequencyData() returns values 0-255</li>
353
+ <li><strong class="text-[#0a0a0a] dark:text-white">Bar Grouping:</strong> Averages frequencies into 30/65 bars (responsive)</li>
354
+ <li><strong class="text-[#0a0a0a] dark:text-white">Normalization:</strong> Converts to 0-100% for CSS heights</li>
355
+ <li><strong class="text-[#0a0a0a] dark:text-white">Animation Loop:</strong> requestAnimationFrame updates at 60fps</li>
356
+ <li><strong class="text-[#0a0a0a] dark:text-white">Recording:</strong> MediaRecorder saves audio as WebM Blob</li>
357
+ </ol>
358
+ </article>
359
+ </section>
360
+
361
+ <!-- Configuration -->
362
+ <section id="configuration" class="mb-12" aria-labelledby="configuration-heading">
363
+ <h2 id="configuration-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
364
+ [ Configuration ]
365
+ </h2>
366
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
367
+ Service-level configuration (read-only constants for optimal performance).
368
+ </p>
369
+
370
+ <div class="space-y-4">
371
+ <div class="space-y-2">
372
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
373
+ <pre><code [innerHTML]="configFFTSizeCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
374
+ </div>
375
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">FFT size for frequency analysis. Higher values provide more detail but use more CPU.</p>
376
+ </div>
377
+
378
+ <div class="space-y-2">
379
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
380
+ <pre><code [innerHTML]="configBarCountCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
381
+ </div>
382
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">Automatically adjusts based on viewport width for optimal display.</p>
383
+ </div>
384
+
385
+ <div class="space-y-2">
386
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
387
+ <pre><code [innerHTML]="configSmoothingCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
388
+ </div>
389
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">Smoothing constant for animation. 0 = no smoothing (choppy), 1 = max smoothing (laggy).</p>
390
+ </div>
391
+ </div>
392
+ </section>
393
+
394
+ <!-- Properties -->
395
+ <section id="properties" class="mb-12" aria-labelledby="properties-heading">
396
+ <h2 id="properties-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
397
+ [ Properties ]
398
+ </h2>
399
+
400
+ <div class="overflow-x-auto">
401
+ <table class="w-full border-collapse border-2 border-neutral-300 dark:border-neutral-700">
402
+ <thead>
403
+ <tr class="bg-neutral-100 dark:bg-[#1a1a1a]">
404
+ <th class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-left text-xs sm:text-sm font-bold text-[#0a0a0a] dark:text-white">Property</th>
405
+ <th class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-left text-xs sm:text-sm font-bold text-[#0a0a0a] dark:text-white">Type</th>
406
+ <th class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-left text-xs sm:text-sm font-bold text-[#0a0a0a] dark:text-white">Default</th>
407
+ <th class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-left text-xs sm:text-sm font-bold text-[#0a0a0a] dark:text-white">Description</th>
408
+ </tr>
409
+ </thead>
410
+ <tbody>
411
+ <!-- Input Properties -->
412
+ <tr class="bg-blue-50 dark:bg-blue-900/20">
413
+ <td colspan="4" class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-2 text-xs sm:text-sm font-bold text-blue-900 dark:text-blue-100 uppercase">Input Properties</td>
414
+ </tr>
415
+ <tr>
416
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
417
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">isStartRecording</span>
418
+ </td>
419
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
420
+ <span class="text-xs">InputSignal&lt;boolean&gt;</span>
421
+ </td>
422
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
423
+ <span class="text-xs">false</span>
424
+ </td>
425
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Signal to start recording. Set to true to begin capturing audio</td>
426
+ </tr>
427
+ <tr>
428
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
429
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">isStopRecording</span>
430
+ </td>
431
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
432
+ <span class="text-xs">InputSignal&lt;boolean&gt;</span>
433
+ </td>
434
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
435
+ <span class="text-xs">false</span>
436
+ </td>
437
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Signal to stop recording. Set to true to stop capturing audio</td>
438
+ </tr>
439
+ <tr>
440
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
441
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">showTimer</span>
442
+ </td>
443
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
444
+ <span class="text-xs">InputSignal&lt;boolean&gt;</span>
445
+ </td>
446
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
447
+ <span class="text-xs">false</span>
448
+ </td>
449
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Controls visibility of recording timer (MM:SS format)</td>
450
+ </tr>
451
+
452
+ <!-- Service Signals -->
453
+ <tr class="bg-green-50 dark:bg-green-900/20">
454
+ <td colspan="4" class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-2 text-xs sm:text-sm font-bold text-green-900 dark:text-green-100 uppercase">Service Signals (Read-Only)</td>
455
+ </tr>
456
+ <tr>
457
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
458
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">isRecording</span>
459
+ </td>
460
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
461
+ <span class="text-xs">WritableSignal&lt;boolean&gt;</span>
462
+ </td>
463
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">-</td>
464
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Current recording state (true = recording, false = stopped)</td>
465
+ </tr>
466
+ <tr>
467
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
468
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">audioData</span>
469
+ </td>
470
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
471
+ <span class="text-xs">WritableSignal&lt;number[]&gt;</span>
472
+ </td>
473
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">-</td>
474
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Array of normalized bar heights (0-100%) updated at 60fps</td>
475
+ </tr>
476
+ <tr>
477
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
478
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">recordedAudioBlob</span>
479
+ </td>
480
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
481
+ <span class="text-xs">WritableSignal&lt;Blob | null&gt;</span>
482
+ </td>
483
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">-</td>
484
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Recorded audio as a Blob (audio/webm format). Available after stopping recording</td>
485
+ </tr>
486
+ <tr>
487
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm">
488
+ <span class="bg-neutral-100 dark:bg-[#1a1a1a] text-[#0a0a0a] dark:text-white px-2 py-1 text-xs">recordedAudioUrl</span>
489
+ </td>
490
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">
491
+ <span class="text-xs">WritableSignal&lt;string | null&gt;</span>
492
+ </td>
493
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400">-</td>
494
+ <td class="border-2 border-neutral-300 dark:border-neutral-700 px-4 py-3 text-xs sm:text-sm text-neutral-700 dark:text-neutral-300">Temporary URL for recorded audio. Use for playback or download</td>
495
+ </tr>
496
+ </tbody>
497
+ </table>
498
+ </div>
499
+ </section>
500
+
501
+ <!-- Methods -->
502
+ <section id="methods" class="mb-12" aria-labelledby="methods-heading">
503
+ <h2 id="methods-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
504
+ [ Service Methods ]
505
+ </h2>
506
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
507
+ Public methods available on AudioWaveformService for managing recorded audio.
508
+ </p>
509
+
510
+ <div class="space-y-4">
511
+ <div class="space-y-2">
512
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
513
+ <pre><code [innerHTML]="methodDownloadRecordingCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
514
+ </div>
515
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">Downloads the recorded audio file. Optionally specify custom filename.</p>
516
+ </div>
517
+
518
+ <div class="space-y-2">
519
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
520
+ <pre><code [innerHTML]="methodClearRecordingCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
521
+ </div>
522
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">Clears recorded audio and revokes URL to free memory. Call after download or discard.</p>
523
+ </div>
524
+ </div>
525
+ </section>
526
+
527
+ <!-- Future Features -->
528
+ <section id="future-features" class="mb-12" aria-labelledby="future-features-heading">
529
+ <h2 id="future-features-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
530
+ [ Future Features ]
531
+ </h2>
532
+
533
+ <article aria-labelledby="transcription-heading">
534
+ <h3 id="transcription-heading" class="text-sm sm:text-base font-bold text-[#0a0a0a] dark:text-white mb-3 uppercase">
535
+ <span aria-hidden="true">&gt; </span>Audio Transcription (Coming Soon)
536
+ </h3>
537
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
538
+ Automatic speech-to-text transcription will be added in a future release. The API preview below shows the planned implementation.
539
+ </p>
540
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
541
+ <pre><code [innerHTML]="futureTranscriptionCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
542
+ </div>
543
+ </article>
544
+ </section>
545
+
546
+ <!-- Browser Support -->
547
+ <section id="browser-support" class="mb-12" aria-labelledby="browser-support-heading">
548
+ <h2 id="browser-support-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
549
+ [ Browser Support ]
550
+ </h2>
551
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3">
552
+ <pre><code [innerHTML]="browserSupportCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
553
+ </div>
554
+ </section>
555
+
556
+ <!-- Accessibility -->
557
+ <section id="accessibility" class="mb-12" aria-labelledby="accessibility-heading">
558
+ <h2 id="accessibility-heading" class="text-[10px] sm:text-xs text-neutral-500 dark:text-neutral-500 uppercase tracking-wider mb-4">
559
+ [ Accessibility ]
560
+ </h2>
561
+ <p class="text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 mb-4 leading-relaxed">
562
+ The component follows accessibility best practices for audio recording interfaces.
563
+ </p>
564
+ <div class="bg-neutral-100 dark:bg-[#121212] border border-neutral-300 dark:border-neutral-700 p-3 mb-6">
565
+ <pre><code [innerHTML]="accessibilityExampleCode" class="text-xs sm:text-sm text-[#0a0a0a] dark:text-white"></code></pre>
566
+ </div>
567
+
568
+ <ul class="space-y-2 text-xs sm:text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed list-disc list-inside">
569
+ <li><strong class="text-[#0a0a0a] dark:text-white">Waveform Visualization:</strong> Decorative element (aria-hidden), not essential for understanding recording state</li>
570
+ <li><strong class="text-[#0a0a0a] dark:text-white">Recording Timer:</strong> Announced via ARIA live region when showTimer=true</li>
571
+ <li><strong class="text-[#0a0a0a] dark:text-white">Control Buttons:</strong> Use clear aria-label attributes (e.g., "Start audio recording")</li>
572
+ <li><strong class="text-[#0a0a0a] dark:text-white">Keyboard Support:</strong> All interactions keyboard-accessible via standard button controls</li>
573
+ </ul>
574
+ </section>
575
+ </div>
576
+ </main>