@arraypress/waveform-player 1.0.0 → 1.0.1

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.
package/README.md CHANGED
@@ -2,67 +2,122 @@
2
2
 
3
3
  A lightweight, customizable audio player with waveform visualization. Under 6KB gzipped.
4
4
 
5
- ![Version](https://img.shields.io/npm/v/waveform-player)
6
- ![Size](https://img.shields.io/badge/gzipped-6KB-brightgreen)
7
- ![License](https://img.shields.io/npm/l/waveform-player)
5
+ **[Live Demo](https://waveformplayer.com)** | **[Documentation](https://waveformplayer.com/#docs)** | **[NPM Package](https://www.npmjs.com/package/@arraypress/waveform-player)**
8
6
 
9
- ## Features
7
+ ![Version](https://img.shields.io/npm/v/@arraypress/waveform-player)
8
+ ![Size](https://img.shields.io/bundlephobia/minzip/@arraypress/waveform-player)
9
+ ![License](https://img.shields.io/npm/l/@arraypress/waveform-player)
10
+ ![Downloads](https://img.shields.io/npm/dm/@arraypress/waveform-player)
10
11
 
11
- - 🎨 **6 Visual Styles** - Bars, mirror, line, blocks, dots
12
- - 🎯 **Tiny Footprint** - Under 6KB gzipped
13
- - **Zero Dependencies** - Pure JavaScript
14
- - 🎭 **Fully Customizable** - Colors, sizes, styles
15
- - 📱 **Responsive** - Works on all devices
16
- - 🎵 **BPM Detection** - Automatic tempo detection (optional)
17
- - 💾 **Waveform Caching** - Pre-generate waveforms for performance
18
- - 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
12
+ ![WaveformPlayer Demo](https://waveformplayer.com/assets/img/og-image.png)
13
+
14
+ ## Why WaveformPlayer?
15
+
16
+ - **Zero Config** - Just add `data-waveform-player` to any div. No JavaScript required.
17
+ - **Tiny** - 6KB gzipped vs 40KB+ for alternatives
18
+ - **Real Waveforms** - Actual audio analysis, not fake waves
19
+ - **No Dependencies** - No jQuery, no bloat, pure vanilla JS
20
+ - **Works Everywhere** - WordPress, Shopify, React, Vue, or plain HTML
21
+
22
+ ## Quick Start
23
+
24
+ Simplest possible usage:
25
+
26
+ ```html
27
+ <!-- Just this. That's it. -->
28
+ <div data-waveform-player data-url="song.mp3"></div>
29
+ ```
19
30
 
20
31
  ## Installation
21
32
 
22
33
  ### NPM
23
34
  ```bash
24
- npm install waveform-player
35
+ npm install @arraypress/waveform-player
25
36
  ```
26
37
 
27
38
  ### CDN
28
39
  ```html
29
- <link rel="stylesheet" href="https://unpkg.com/waveform-player/dist/waveform-player.css">
30
- <script src="https://unpkg.com/waveform-player/dist/waveform-player.min.js"></script>
40
+ <link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.css">
41
+ <script src="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.min.js"></script>
31
42
  ```
32
43
 
33
- ## Quick Start
44
+ ### Download
45
+ ```html
46
+ <link rel="stylesheet" href="waveform-player.css">
47
+ <script src="waveform-player.js"></script>
48
+ ```
49
+
50
+ ## Features
34
51
 
35
- ### HTML
52
+ - 🎨 **6 Visual Styles** - Bars, mirror, line, blocks, dots, seekbar
53
+ - 🎯 **Tiny Footprint** - Under 6KB gzipped
54
+ - ⚡ **Zero Dependencies** - Pure JavaScript
55
+ - 🎭 **Fully Customizable** - Colors, sizes, styles
56
+ - 📱 **Responsive** - Works on all devices
57
+ - 🎵 **BPM Detection** - Automatic tempo detection (optional)
58
+ - 💾 **Waveform Caching** - Pre-generate waveforms for performance
59
+ - 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
60
+
61
+ ## Comparison
62
+
63
+ | Feature | WaveformPlayer | WaveSurfer.js | Amplitude.js |
64
+ |---------|---------------|---------------|--------------|
65
+ | Size (gzipped) | 6KB | 40KB+ | 35KB+ |
66
+ | Zero Config | ✅ | ❌ | ❌ |
67
+ | Dependencies | None | None | None |
68
+ | Waveform Styles | 6 | 3 | N/A |
69
+ | Setup Time | 30 seconds | 5+ minutes | 5+ minutes |
70
+ | Real Waveforms | ✅ | ✅ | ❌ |
71
+
72
+ ## Usage
73
+
74
+ ### HTML (Zero JavaScript)
36
75
  ```html
37
76
  <div data-waveform-player
38
77
  data-url="audio.mp3"
39
- data-title="My Song">
78
+ data-title="My Song"
79
+ data-subtitle="Artist Name"
80
+ data-waveform-style="mirror">
40
81
  </div>
41
82
  ```
42
83
 
43
- ### JavaScript
84
+ ### JavaScript API
44
85
  ```javascript
45
- import WaveformPlayer from 'waveform-player';
86
+ import WaveformPlayer from '@arraypress/waveform-player';
46
87
 
47
88
  const player = new WaveformPlayer('#player', {
48
89
  url: 'audio.mp3',
49
90
  waveformStyle: 'mirror',
50
- height: 80
91
+ height: 80,
92
+ barWidth: 2,
93
+ barSpacing: 1
51
94
  });
52
95
  ```
53
96
 
97
+ ## Visual Styles
98
+
99
+ Choose from 6 built-in styles:
100
+
101
+ - **bars** - Classic waveform bars
102
+ - **mirror** - SoundCloud-style mirrored waveform
103
+ - **line** - Smooth oscilloscope line
104
+ - **blocks** - LED meter blocks
105
+ - **dots** - Circular dots
106
+ - **seekbar** - Minimal progress bar
107
+
54
108
  ## Options
55
109
 
56
110
  | Option | Type | Default | Description |
57
111
  |--------|------|---------|-------------|
58
112
  | `url` | string | `''` | Audio file URL |
59
- | `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots |
113
+ | `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots, seekbar |
60
114
  | `height` | number | `60` | Waveform height in pixels |
61
115
  | `barWidth` | number | `3` | Width of waveform bars |
62
116
  | `barSpacing` | number | `1` | Space between bars |
63
117
  | `samples` | number | `200` | Number of waveform samples |
64
118
  | `waveformColor` | string | `'rgba(255,255,255,0.3)'` | Waveform color |
65
119
  | `progressColor` | string | `'rgba(255,255,255,0.9)'` | Progress color |
120
+ | `buttonColor` | string | `'rgba(255,255,255,0.9)'` | Play button color |
66
121
  | `showTime` | boolean | `true` | Show time display |
67
122
  | `showBPM` | boolean | `false` | Enable BPM detection |
68
123
  | `autoplay` | boolean | `false` | Autoplay on load |
@@ -110,10 +165,10 @@ new WaveformPlayer('#player', {
110
165
  For better performance, generate waveform data server-side:
111
166
 
112
167
  ```javascript
113
- // Generate waveform data
168
+ // Generate waveform data once
114
169
  const waveformData = await WaveformPlayer.generateWaveformData('audio.mp3');
115
170
 
116
- // Use pre-generated data
171
+ // Use pre-generated data for instant display
117
172
  new WaveformPlayer('#player', {
118
173
  url: 'audio.mp3',
119
174
  waveform: waveformData // Bypass client-side processing
@@ -146,20 +201,62 @@ const player = WaveformPlayer.getInstance('my-player');
146
201
  }
147
202
  ```
148
203
 
204
+ ## Framework Integration
205
+
206
+ ### React
207
+ ```jsx
208
+ import { useEffect, useRef } from 'react';
209
+ import WaveformPlayer from '@arraypress/waveform-player';
210
+
211
+ function AudioPlayer({ url }) {
212
+ const playerRef = useRef();
213
+
214
+ useEffect(() => {
215
+ const player = new WaveformPlayer(playerRef.current, { url });
216
+ return () => player.destroy();
217
+ }, [url]);
218
+
219
+ return <div ref={playerRef} />;
220
+ }
221
+ ```
222
+
223
+ ### Vue
224
+ ```vue
225
+ <template>
226
+ <div ref="player"></div>
227
+ </template>
228
+
229
+ <script>
230
+ import WaveformPlayer from '@arraypress/waveform-player';
231
+
232
+ export default {
233
+ mounted() {
234
+ this.player = new WaveformPlayer(this.$refs.player, {
235
+ url: this.audioUrl
236
+ });
237
+ },
238
+ beforeDestroy() {
239
+ this.player?.destroy();
240
+ }
241
+ }
242
+ </script>
243
+ ```
244
+
149
245
  ## Browser Support
150
246
 
151
247
  - Chrome/Edge 90+
152
248
  - Firefox 88+
153
249
  - Safari 14+
154
- - Mobile browsers
250
+ - Mobile browsers (iOS Safari, Chrome Android)
155
251
 
156
252
  ## Examples
157
253
 
158
- Check the `/examples` directory for:
159
- - Basic player setup
160
- - Multiple players
161
- - Custom styling
254
+ See the [live demo](https://waveformplayer.com) for:
255
+ - All visual styles
256
+ - Custom styling examples
162
257
  - Event handling
258
+ - Player builder
259
+ - BPM detection
163
260
  - Pre-generated waveforms
164
261
 
165
262
  ## Development
@@ -180,7 +277,7 @@ npm run size
180
277
 
181
278
  ## License
182
279
 
183
- MIT © David Sherlock / ArrayPress
280
+ MIT © [ArrayPress](https://github.com/arraypress)
184
281
 
185
282
  ## Credits
186
283
 
@@ -1,185 +1 @@
1
- /**
2
- * WaveformPlayer.css
3
- * Ultra-minimal styles
4
- */
5
-
6
- /* Base structure */
7
- .waveform-player {
8
- font-family: inherit;
9
- color: inherit;
10
- line-height: 1.4;
11
- }
12
-
13
- .waveform-player * {
14
- box-sizing: border-box;
15
- }
16
-
17
- .waveform-player-inner {
18
- padding: 12px;
19
- border-radius: 4px;
20
- }
21
-
22
- /* Body container */
23
- .waveform-body {
24
- display: flex;
25
- flex-direction: column;
26
- gap: 8px;
27
- }
28
-
29
- /* Track row */
30
- .waveform-track {
31
- display: flex;
32
- align-items: center;
33
- gap: 12px;
34
- position: relative;
35
- }
36
-
37
- /* Play button */
38
- .waveform-btn {
39
- width: 36px;
40
- height: 36px;
41
- min-width: 36px;
42
- border-radius: 50%;
43
- border: 2px solid currentColor;
44
- background: transparent;
45
- color: inherit;
46
- cursor: pointer;
47
- display: flex;
48
- align-items: center;
49
- justify-content: center;
50
- transition: all 0.2s ease;
51
- padding: 0;
52
- opacity: 0.9;
53
- flex-shrink: 0;
54
- }
55
-
56
- .waveform-btn:hover:not(:disabled) {
57
- opacity: 1;
58
- transform: scale(1.05);
59
- }
60
-
61
- .waveform-btn:disabled {
62
- cursor: not-allowed;
63
- opacity: 0.3;
64
- }
65
-
66
- /* Icons */
67
- .waveform-btn > * {
68
- display: flex;
69
- align-items: center;
70
- justify-content: center;
71
- width: 100%;
72
- height: 100%;
73
- }
74
-
75
- .waveform-btn svg {
76
- width: 16px;
77
- height: 16px;
78
- fill: currentColor;
79
- display: block;
80
- }
81
-
82
- .waveform-icon-play svg {
83
- margin-left: 1px;
84
- }
85
-
86
- /* Waveform container */
87
- .waveform-container {
88
- flex: 1;
89
- position: relative;
90
- min-height: 60px;
91
- cursor: pointer;
92
- overflow: hidden;
93
- min-width: 0;
94
- width: 100%;
95
- }
96
-
97
- .waveform-container canvas {
98
- display: block;
99
- width: 100%;
100
- height: 100%;
101
- max-width: 100%;
102
- transition: opacity 0.3s ease;
103
- }
104
-
105
- /* Info section */
106
- .waveform-info {
107
- display: flex;
108
- align-items: center;
109
- gap: 8px;
110
- font-size: 13px;
111
- min-height: 20px;
112
- }
113
-
114
- .waveform-text {
115
- flex: 1;
116
- display: flex;
117
- flex-direction: column;
118
- gap: 2px;
119
- min-width: 0;
120
- }
121
-
122
- .waveform-title {
123
- white-space: nowrap;
124
- overflow: hidden;
125
- text-overflow: ellipsis;
126
- font-weight: 500;
127
- }
128
-
129
- .waveform-subtitle {
130
- font-size: 11px;
131
- white-space: nowrap;
132
- overflow: hidden;
133
- text-overflow: ellipsis;
134
- }
135
-
136
- .waveform-time {
137
- font-size: 11px;
138
- white-space: nowrap;
139
- flex-shrink: 0;
140
- }
141
-
142
- /* Loading state - simplified */
143
- .waveform-loading {
144
- position: absolute;
145
- inset: 0;
146
- background: rgba(0, 0, 0, 0.1);
147
- z-index: 1;
148
- }
149
-
150
- /* Error state */
151
- .waveform-error {
152
- position: absolute;
153
- inset: 0;
154
- display: flex;
155
- align-items: center;
156
- justify-content: center;
157
- background: rgba(0, 0, 0, 0.2);
158
- z-index: 1;
159
- }
160
-
161
- .waveform-error-text {
162
- font-size: 12px;
163
- opacity: 0.7;
164
- text-align: center;
165
- padding: 0 20px;
166
- }
167
-
168
- /* Minimal responsive */
169
- @media (max-width: 480px) {
170
- .waveform-btn {
171
- width: 32px;
172
- height: 32px;
173
- min-width: 32px;
174
- }
175
-
176
- .waveform-container {
177
- min-height: 50px;
178
- }
179
- }
180
-
181
- /* Accessibility */
182
- .waveform-btn:focus-visible {
183
- outline: 2px solid currentColor;
184
- outline-offset: 2px;
185
- }
1
+ .waveform-player{font-family:inherit;color:inherit;line-height:1.4}.waveform-player *{box-sizing:border-box}.waveform-player-inner{padding:12px;border-radius:4px}.waveform-body{display:flex;flex-direction:column;gap:8px}.waveform-track{display:flex;align-items:center;gap:12px;position:relative}.waveform-btn{width:36px;height:36px;min-width:36px;border-radius:50%;border:2px solid currentColor;background:transparent;color:inherit;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;opacity:.9;flex-shrink:0}.waveform-btn:hover:not(:disabled){opacity:1;transform:scale(1.05)}.waveform-btn:disabled{cursor:not-allowed;opacity:.3}.waveform-btn>*{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.waveform-btn svg{width:16px;height:16px;fill:currentColor;display:block}.waveform-icon-play svg{margin-left:1px}.waveform-container{flex:1;position:relative;min-height:60px;cursor:pointer;overflow:hidden;min-width:0;width:100%}.waveform-container canvas{display:block;width:100%;height:100%;max-width:100%;transition:opacity .3s ease}.waveform-info{display:flex;align-items:center;gap:8px;font-size:13px;min-height:20px}.waveform-text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.waveform-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.waveform-subtitle{font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.waveform-time{font-size:11px;white-space:nowrap;flex-shrink:0}.waveform-loading{position:absolute;inset:0;background:#0000001a;z-index:1}.waveform-error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#0003;z-index:1}.waveform-error-text{font-size:12px;opacity:.7;text-align:center;padding:0 20px}@media (max-width: 480px){.waveform-btn{width:32px;height:32px;min-width:32px}.waveform-container{min-height:50px}}.waveform-btn:focus-visible{outline:2px solid currentColor;outline-offset:2px}
@@ -1,5 +1,5 @@
1
1
  (() => {
2
- // src/utils.js
2
+ // src/js/utils.js
3
3
  function parseDataAttributes(element) {
4
4
  const options = {};
5
5
  if (element.dataset.url) options.url = element.dataset.url;
@@ -112,7 +112,7 @@
112
112
  return result;
113
113
  }
114
114
 
115
- // src/drawing.js
115
+ // src/js/drawing.js
116
116
  function drawBars(ctx, canvas, peaks, progress, options) {
117
117
  const dpr = window.devicePixelRatio || 1;
118
118
  const barWidth = options.barWidth * dpr;
@@ -349,7 +349,7 @@
349
349
  drawFunc(ctx, canvas, peaks, progress, options);
350
350
  }
351
351
 
352
- // src/bpm.js
352
+ // src/js/bpm.js
353
353
  function detectBPM(buffer) {
354
354
  try {
355
355
  const channelData = buffer.getChannelData(0);
@@ -412,7 +412,7 @@
412
412
  return onsets;
413
413
  }
414
414
 
415
- // src/audio.js
415
+ // src/js/audio.js
416
416
  function extractPeaks(buffer, samples = 200) {
417
417
  const sampleSize = buffer.length / samples;
418
418
  const sampleStep = ~~(sampleSize / 10) || 1;
@@ -469,7 +469,7 @@
469
469
  return data;
470
470
  }
471
471
 
472
- // src/themes.js
472
+ // src/js/themes.js
473
473
  var DEFAULT_OPTIONS = {
474
474
  // Core settings
475
475
  url: "",
@@ -520,7 +520,7 @@
520
520
  seekbar: { barWidth: 1, barSpacing: 0 }
521
521
  };
522
522
 
523
- // src/core.js
523
+ // src/js/core.js
524
524
  var WaveformPlayer = class _WaveformPlayer {
525
525
  /** @type {Map<string, WaveformPlayer>} */
526
526
  static instances = /* @__PURE__ */ new Map();
@@ -1058,7 +1058,7 @@
1058
1058
  }
1059
1059
  };
1060
1060
 
1061
- // src/index.js
1061
+ // src/js/index.js
1062
1062
  function autoInit() {
1063
1063
  if (typeof document === "undefined") return;
1064
1064
  const elements = document.querySelectorAll("[data-waveform-player]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arraypress/waveform-player",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Lightweight, customizable audio player with waveform visualization",
5
5
  "main": "dist/waveform-player.js",
6
6
  "module": "dist/waveform-player.esm.js",
@@ -12,12 +12,16 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "scripts": {
15
- "build": "npm run build:iife && npm run build:esm && npm run build:min",
16
- "build:iife": "esbuild src/index.js --bundle --format=iife --outfile=dist/waveform-player.js",
17
- "build:min": "esbuild src/index.js --bundle --format=iife --outfile=dist/waveform-player.min.js --minify",
18
- "build:esm": "esbuild src/index.js --bundle --format=esm --outfile=dist/waveform-player.esm.js --minify",
19
- "dev": "esbuild src/index.js --bundle --format=iife --outfile=dist/waveform-player.js --watch",
20
- "size": "npm run build:min && gzip -c dist/waveform-player.min.js | wc -c",
15
+ "build": "npm run build:css && npm run build:iife && npm run build:esm && npm run build:min && npm run build:demo",
16
+ "build:css": "esbuild src/css/waveform-player.css --minify --outfile=dist/waveform-player.css",
17
+ "build:iife": "esbuild src/js/index.js --bundle --format=iife --outfile=dist/waveform-player.js",
18
+ "build:min": "esbuild src/js/index.js --bundle --format=iife --outfile=dist/waveform-player.min.js --minify",
19
+ "build:esm": "esbuild src/js/index.js --bundle --format=esm --outfile=dist/waveform-player.esm.js --minify",
20
+ "build:demo": "mkdir -p demo/dist && cp dist/*.js demo/dist/ && cp dist/*.css demo/dist/",
21
+ "dev": "npm run build:css && esbuild src/js/index.js --bundle --format=iife --outfile=dist/waveform-player.js --watch",
22
+ "dev:css": "esbuild src/css/waveform-player.css --outfile=dist/waveform-player.css --watch",
23
+ "dev:all": "npm run build:css && npm run build:demo && concurrently \"npm run dev\" \"npm run dev:css\"",
24
+ "size": "npm run build:min && npm run build:css && echo 'JS:' && gzip -c dist/waveform-player.min.js | wc -c && echo 'CSS:' && gzip -c dist/waveform-player.css | wc -c",
21
25
  "prepublishOnly": "npm run build"
22
26
  },
23
27
  "keywords": [
@@ -41,6 +45,7 @@
41
45
  },
42
46
  "homepage": "https://github.com/arraypress/waveform-player#readme",
43
47
  "devDependencies": {
48
+ "concurrently": "^7.6.0",
44
49
  "esbuild": "^0.25.0"
45
50
  }
46
- }
51
+ }
@@ -0,0 +1,180 @@
1
+ /* Base structure */
2
+ .waveform-player {
3
+ font-family: inherit;
4
+ color: inherit;
5
+ line-height: 1.4;
6
+ }
7
+
8
+ .waveform-player * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ .waveform-player-inner {
13
+ padding: 12px;
14
+ border-radius: 4px;
15
+ }
16
+
17
+ /* Body container */
18
+ .waveform-body {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 8px;
22
+ }
23
+
24
+ /* Track row */
25
+ .waveform-track {
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 12px;
29
+ position: relative;
30
+ }
31
+
32
+ /* Play button */
33
+ .waveform-btn {
34
+ width: 36px;
35
+ height: 36px;
36
+ min-width: 36px;
37
+ border-radius: 50%;
38
+ border: 2px solid currentColor;
39
+ background: transparent;
40
+ color: inherit;
41
+ cursor: pointer;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ transition: all 0.2s ease;
46
+ padding: 0;
47
+ opacity: 0.9;
48
+ flex-shrink: 0;
49
+ }
50
+
51
+ .waveform-btn:hover:not(:disabled) {
52
+ opacity: 1;
53
+ transform: scale(1.05);
54
+ }
55
+
56
+ .waveform-btn:disabled {
57
+ cursor: not-allowed;
58
+ opacity: 0.3;
59
+ }
60
+
61
+ /* Icons */
62
+ .waveform-btn > * {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ width: 100%;
67
+ height: 100%;
68
+ }
69
+
70
+ .waveform-btn svg {
71
+ width: 16px;
72
+ height: 16px;
73
+ fill: currentColor;
74
+ display: block;
75
+ }
76
+
77
+ .waveform-icon-play svg {
78
+ margin-left: 1px;
79
+ }
80
+
81
+ /* Waveform container */
82
+ .waveform-container {
83
+ flex: 1;
84
+ position: relative;
85
+ min-height: 60px;
86
+ cursor: pointer;
87
+ overflow: hidden;
88
+ min-width: 0;
89
+ width: 100%;
90
+ }
91
+
92
+ .waveform-container canvas {
93
+ display: block;
94
+ width: 100%;
95
+ height: 100%;
96
+ max-width: 100%;
97
+ transition: opacity 0.3s ease;
98
+ }
99
+
100
+ /* Info section */
101
+ .waveform-info {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 8px;
105
+ font-size: 13px;
106
+ min-height: 20px;
107
+ }
108
+
109
+ .waveform-text {
110
+ flex: 1;
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 2px;
114
+ min-width: 0;
115
+ }
116
+
117
+ .waveform-title {
118
+ white-space: nowrap;
119
+ overflow: hidden;
120
+ text-overflow: ellipsis;
121
+ font-weight: 500;
122
+ }
123
+
124
+ .waveform-subtitle {
125
+ font-size: 11px;
126
+ white-space: nowrap;
127
+ overflow: hidden;
128
+ text-overflow: ellipsis;
129
+ }
130
+
131
+ .waveform-time {
132
+ font-size: 11px;
133
+ white-space: nowrap;
134
+ flex-shrink: 0;
135
+ }
136
+
137
+ /* Loading state - simplified */
138
+ .waveform-loading {
139
+ position: absolute;
140
+ inset: 0;
141
+ background: rgba(0, 0, 0, 0.1);
142
+ z-index: 1;
143
+ }
144
+
145
+ /* Error state */
146
+ .waveform-error {
147
+ position: absolute;
148
+ inset: 0;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ background: rgba(0, 0, 0, 0.2);
153
+ z-index: 1;
154
+ }
155
+
156
+ .waveform-error-text {
157
+ font-size: 12px;
158
+ opacity: 0.7;
159
+ text-align: center;
160
+ padding: 0 20px;
161
+ }
162
+
163
+ /* Minimal responsive */
164
+ @media (max-width: 480px) {
165
+ .waveform-btn {
166
+ width: 32px;
167
+ height: 32px;
168
+ min-width: 32px;
169
+ }
170
+
171
+ .waveform-container {
172
+ min-height: 50px;
173
+ }
174
+ }
175
+
176
+ /* Accessibility */
177
+ .waveform-btn:focus-visible {
178
+ outline: 2px solid currentColor;
179
+ outline-offset: 2px;
180
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes