morille 0.1.0 → 0.4.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 (134) hide show
  1. package/README.md +15 -3
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +2 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/components/animated-subtitle.d.ts +19 -0
  6. package/dist/components/animated-subtitle.d.ts.map +1 -0
  7. package/dist/components/animated-subtitle.js +109 -0
  8. package/dist/components/animated-subtitle.js.map +1 -0
  9. package/dist/components/animated-title.d.ts +16 -0
  10. package/dist/components/animated-title.d.ts.map +1 -0
  11. package/dist/components/animated-title.js +47 -0
  12. package/dist/components/animated-title.js.map +1 -0
  13. package/dist/components/browse-detail-view.d.ts.map +1 -0
  14. package/dist/components/browse-detail-view.js +2 -1
  15. package/dist/components/browse-detail-view.js.map +1 -0
  16. package/dist/components/device-indicator.d.ts +18 -0
  17. package/dist/components/device-indicator.d.ts.map +1 -0
  18. package/dist/components/device-indicator.js +53 -0
  19. package/dist/components/device-indicator.js.map +1 -0
  20. package/dist/components/device-picker-view.d.ts +10 -0
  21. package/dist/components/device-picker-view.d.ts.map +1 -0
  22. package/dist/components/device-picker-view.js +65 -0
  23. package/dist/components/device-picker-view.js.map +1 -0
  24. package/dist/components/lyrics-panel.d.ts.map +1 -0
  25. package/dist/components/lyrics-view.d.ts.map +1 -0
  26. package/dist/components/lyrics-view.js +2 -1
  27. package/dist/components/lyrics-view.js.map +1 -0
  28. package/dist/components/panel-content.d.ts.map +1 -0
  29. package/dist/components/panel-content.js +4 -0
  30. package/dist/components/panel-content.js.map +1 -0
  31. package/dist/components/playback-status.d.ts.map +1 -0
  32. package/dist/components/player.d.ts.map +1 -1
  33. package/dist/components/player.js +46 -10
  34. package/dist/components/player.js.map +1 -1
  35. package/dist/components/progress-bar.d.ts.map +1 -0
  36. package/dist/components/progress-bar.js +2 -1
  37. package/dist/components/progress-bar.js.map +1 -0
  38. package/dist/components/queue-view.d.ts.map +1 -0
  39. package/dist/components/queue-view.js +2 -1
  40. package/dist/components/queue-view.js.map +1 -0
  41. package/dist/components/search-panel.d.ts.map +1 -0
  42. package/dist/components/search-panel.js +3 -2
  43. package/dist/components/search-panel.js.map +1 -0
  44. package/dist/components/shimmer.d.ts.map +1 -0
  45. package/dist/components/shimmer.js +2 -2
  46. package/dist/components/shimmer.js.map +1 -0
  47. package/dist/components/track-facts.d.ts +13 -0
  48. package/dist/components/track-facts.d.ts.map +1 -0
  49. package/dist/components/track-facts.js +52 -0
  50. package/dist/components/track-facts.js.map +1 -0
  51. package/dist/components/track-info-skeleton.d.ts.map +1 -0
  52. package/dist/components/track-info.d.ts.map +1 -0
  53. package/dist/components/track-info.js +2 -1
  54. package/dist/components/track-info.js.map +1 -0
  55. package/dist/config.d.ts +3 -4
  56. package/dist/config.d.ts.map +1 -1
  57. package/dist/config.js +3 -4
  58. package/dist/config.js.map +1 -1
  59. package/dist/contexts/devices-context.d.ts +28 -0
  60. package/dist/contexts/devices-context.d.ts.map +1 -0
  61. package/dist/contexts/devices-context.js +33 -0
  62. package/dist/contexts/devices-context.js.map +1 -0
  63. package/dist/contexts/lyrics-context.d.ts.map +1 -0
  64. package/dist/contexts/panel-mode-context.d.ts +2 -1
  65. package/dist/contexts/panel-mode-context.d.ts.map +1 -0
  66. package/dist/contexts/panel-mode-context.js +3 -0
  67. package/dist/contexts/panel-mode-context.js.map +1 -0
  68. package/dist/contexts/queue-context.d.ts.map +1 -0
  69. package/dist/contexts/search-context.d.ts.map +1 -0
  70. package/dist/hooks/use-album-art.d.ts.map +1 -0
  71. package/dist/hooks/use-album-art.js +3 -1
  72. package/dist/hooks/use-album-art.js.map +1 -0
  73. package/dist/hooks/use-browse.d.ts.map +1 -0
  74. package/dist/hooks/use-browse.js +7 -3
  75. package/dist/hooks/use-browse.js.map +1 -0
  76. package/dist/hooks/use-devices.d.ts +24 -0
  77. package/dist/hooks/use-devices.d.ts.map +1 -0
  78. package/dist/hooks/use-devices.js +106 -0
  79. package/dist/hooks/use-devices.js.map +1 -0
  80. package/dist/hooks/use-lyrics.d.ts.map +1 -0
  81. package/dist/hooks/use-playback.d.ts +7 -0
  82. package/dist/hooks/use-playback.d.ts.map +1 -0
  83. package/dist/hooks/use-playback.js +59 -18
  84. package/dist/hooks/use-playback.js.map +1 -0
  85. package/dist/hooks/use-player-input.d.ts.map +1 -0
  86. package/dist/hooks/use-player-input.js +31 -2
  87. package/dist/hooks/use-player-input.js.map +1 -0
  88. package/dist/hooks/use-queue.d.ts.map +1 -0
  89. package/dist/hooks/use-queue.js +8 -3
  90. package/dist/hooks/use-queue.js.map +1 -0
  91. package/dist/hooks/use-search.d.ts.map +1 -0
  92. package/dist/hooks/use-search.js +2 -1
  93. package/dist/hooks/use-search.js.map +1 -0
  94. package/dist/icons.d.ts +29 -0
  95. package/dist/icons.d.ts.map +1 -0
  96. package/dist/icons.js +43 -0
  97. package/dist/icons.js.map +1 -0
  98. package/dist/index.d.ts +1 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +4 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/logger.d.ts +9 -0
  103. package/dist/logger.d.ts.map +1 -0
  104. package/dist/logger.js +15 -0
  105. package/dist/logger.js.map +1 -0
  106. package/dist/main.d.ts.map +1 -0
  107. package/dist/spotify/auth.d.ts.map +1 -0
  108. package/dist/spotify/auth.js +3 -1
  109. package/dist/spotify/auth.js.map +1 -0
  110. package/dist/spotify/client.d.ts.map +1 -0
  111. package/dist/spotify/client.js +3 -1
  112. package/dist/spotify/client.js.map +1 -0
  113. package/dist/spotify/devices.d.ts +36 -0
  114. package/dist/spotify/devices.d.ts.map +1 -0
  115. package/dist/spotify/devices.js +72 -0
  116. package/dist/spotify/devices.js.map +1 -0
  117. package/dist/spotify/fetch-with-retry.d.ts.map +1 -0
  118. package/dist/spotify/fetch-with-retry.js +5 -4
  119. package/dist/spotify/fetch-with-retry.js.map +1 -0
  120. package/dist/spotify/lyrics.d.ts.map +1 -0
  121. package/dist/spotify/lyrics.js +5 -2
  122. package/dist/spotify/lyrics.js.map +1 -0
  123. package/dist/spotify/playback.d.ts +9 -0
  124. package/dist/spotify/playback.d.ts.map +1 -0
  125. package/dist/spotify/playback.js +40 -8
  126. package/dist/spotify/playback.js.map +1 -0
  127. package/dist/spotify/search.d.ts.map +1 -0
  128. package/dist/spotify/search.js +4 -4
  129. package/dist/spotify/search.js.map +1 -0
  130. package/dist/spotify/track-facts.d.ts +9 -0
  131. package/dist/spotify/track-facts.d.ts.map +1 -0
  132. package/dist/spotify/track-facts.js +95 -0
  133. package/dist/spotify/track-facts.js.map +1 -0
  134. package/package.json +21 -1
package/README.md CHANGED
@@ -43,6 +43,21 @@ and rebuild.
43
43
 
44
44
  [quota]: https://developer.spotify.com/documentation/web-api/concepts/quota-modes
45
45
 
46
+ ### Debug output
47
+
48
+ morille is silent on stderr by default so the terminal UI stays clean. To see
49
+ internal diagnostics (fetch traces, retry notices, auth and API errors), run
50
+ with the `--debug` flag or set the `DEBUG=1` environment variable:
51
+
52
+ ```sh
53
+ npx morille --debug
54
+ # or
55
+ DEBUG=1 npx morille
56
+ ```
57
+
58
+ Debug output is written to stderr and will visibly interfere with the ink UI —
59
+ this mode is intended for development, not regular use.
60
+
46
61
  ## Usage
47
62
 
48
63
  - **Space** - toggle play/pause
@@ -74,9 +89,6 @@ pnpm test:biome # lint/format check
74
89
 
75
90
  - Add track to queue
76
91
  - Device selection / transfer playback
77
- - ~~Search tracks, albums, artists, and playlists~~
78
- - ~~Browse and play playlists~~
79
- - ~~Browse and play albums~~
80
92
  - Show recently played tracks
81
93
  - Like / unlike tracks (save to library)
82
94
  - Keyboard shortcuts customization
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAgB,GAAG,4CAmFlB"}
package/dist/app.js CHANGED
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useApp, useInput } from 'ink';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { Player } from './components/player.js';
5
+ import { logger } from './logger.js';
5
6
  import { createSpotifyClient, getClientId } from './spotify/client.js';
6
7
  /**
7
8
  * Root application component. Handles Spotify authentication on mount,
@@ -31,6 +32,7 @@ export function App() {
31
32
  }
32
33
  }
33
34
  catch (err) {
35
+ logger.error('Spotify authentication failed:', err);
34
36
  if (!cancelled) {
35
37
  setAuthError(err instanceof Error ? err.message : 'Authentication failed');
36
38
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { PanelMode } from '../contexts/panel-mode-context.js';
2
+ type AnimatedSubtitleProps = {
3
+ hasError: boolean;
4
+ hasTrack: boolean;
5
+ isPlaying: boolean;
6
+ panelMode: PanelMode;
7
+ };
8
+ /**
9
+ * Renders a dim, context-aware funny hint line under the title.
10
+ * The hint pool changes whenever the context (playback / panel / error)
11
+ * changes; within a stable context the hint rotates every few seconds.
12
+ * Rotation pauses when nothing is playing so a frozen player has a stable line.
13
+ * @param hasError - Whether the player is currently in an error state
14
+ * @param hasTrack - Whether a track is loaded (regardless of play/pause)
15
+ * @param isPlaying - Whether playback is actively running
16
+ * @param panelMode - Currently active side panel
17
+ */
18
+ export declare function AnimatedSubtitle({ hasError, hasTrack, isPlaying, panelMode }: AnimatedSubtitleProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-subtitle.d.ts","sourceRoot":"","sources":["../../src/components/animated-subtitle.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AA8EnE,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CA8CnG"}
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ const ROTATE_INTERVAL_MS = 6000;
5
+ /**
6
+ * Returns the pool of funny hint lines that fit the current context.
7
+ * The first array entry doubles as the default if rotation is disabled.
8
+ */
9
+ function hintsFor({ hasError, hasTrack, isPlaying, panelMode }) {
10
+ if (hasError) {
11
+ return [
12
+ 'something broke. probably the vibes.',
13
+ 'spotify said no. try again, gently.',
14
+ 'the music gods are unamused.',
15
+ ];
16
+ }
17
+ if (!hasTrack) {
18
+ return [
19
+ 'silence is golden, but tunes are gold-plated. open spotify somewhere.',
20
+ 'no device detected. point one at me with `space`.',
21
+ 'the void hums quietly. give it something to drown out.',
22
+ 'press play in any spotify app to wake me up.',
23
+ ];
24
+ }
25
+ if (panelMode === 'search') {
26
+ return [
27
+ 'hunt good. type words. find bops.',
28
+ 'the catalogue awaits. enter the void.',
29
+ 'left/right to switch the haystack.',
30
+ 'enter on a track to commit. esc to retreat.',
31
+ ];
32
+ }
33
+ if (panelMode === 'queue') {
34
+ return [
35
+ 'the lineup. judge it ruthlessly.',
36
+ 'up next, then up next, then up next. enter to skip ahead.',
37
+ '↑/↓ to scroll the future. enter to fast-forward time.',
38
+ ];
39
+ }
40
+ if (panelMode === 'lyrics') {
41
+ return [
42
+ 'sing along. nobody can hear you in the terminal.',
43
+ "+/- if it's drifting. you're not imagining it.",
44
+ 'karaoke mode engaged. hairbrush optional.',
45
+ 'feel the words. or just read them. whatever.',
46
+ ];
47
+ }
48
+ if (!isPlaying) {
49
+ return [
50
+ 'frozen mid-vibe. press space to thaw.',
51
+ 'paused. the universe holds its breath.',
52
+ 'the silence is dramatic. press space to ruin it.',
53
+ 'currently doing nothing. respectfully.',
54
+ ];
55
+ }
56
+ return [
57
+ 'currently jamming. do not disturb.',
58
+ 'the vibes are immaculate.',
59
+ 'music intensifies.',
60
+ "press '/' to find more, 'd' for what's next, 'l' for the words.",
61
+ 'this slaps. according to my code.',
62
+ 'in the zone. or pretending to be.',
63
+ ];
64
+ }
65
+ /**
66
+ * Renders a dim, context-aware funny hint line under the title.
67
+ * The hint pool changes whenever the context (playback / panel / error)
68
+ * changes; within a stable context the hint rotates every few seconds.
69
+ * Rotation pauses when nothing is playing so a frozen player has a stable line.
70
+ * @param hasError - Whether the player is currently in an error state
71
+ * @param hasTrack - Whether a track is loaded (regardless of play/pause)
72
+ * @param isPlaying - Whether playback is actively running
73
+ * @param panelMode - Currently active side panel
74
+ */
75
+ export function AnimatedSubtitle({ hasError, hasTrack, isPlaying, panelMode }) {
76
+ const pool = useMemo(() => hintsFor({
77
+ hasError,
78
+ hasTrack,
79
+ isPlaying,
80
+ panelMode,
81
+ }), [
82
+ hasError,
83
+ hasTrack,
84
+ isPlaying,
85
+ panelMode,
86
+ ]);
87
+ const [index, setIndex] = useState(0);
88
+ // Reset to a random starting hint whenever the pool changes so the user
89
+ // doesn't see the same first line every time they switch panels.
90
+ useEffect(() => {
91
+ setIndex(Math.floor(Math.random() * pool.length));
92
+ }, [
93
+ pool,
94
+ ]);
95
+ useEffect(() => {
96
+ if (!isPlaying || pool.length <= 1)
97
+ return;
98
+ const id = setInterval(() => {
99
+ setIndex((i) => (i + 1) % pool.length);
100
+ }, ROTATE_INTERVAL_MS);
101
+ return () => clearInterval(id);
102
+ }, [
103
+ isPlaying,
104
+ pool,
105
+ ]);
106
+ const safeIndex = index % pool.length;
107
+ const hint = pool[safeIndex] ?? pool[0];
108
+ return (_jsx(Text, { color: "blackBright", dimColor: true, italic: true, children: hint }));
109
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-subtitle.js","sourceRoot":"","sources":["../../src/components/animated-subtitle.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ type AnimatedTitleProps = {
2
+ text: string;
3
+ isPlaying: boolean;
4
+ };
5
+ /**
6
+ * Renders the stylized header one-liner: `♪ ── <text> ── ♪ <tagline>`.
7
+ * Only the `<text>` segment animates — each letter cycles through a green
8
+ * gradient offset by its position so the brightest spot ripples left-to-right.
9
+ * The animation is paused (and re-renders stop) when `isPlaying` is false.
10
+ * On narrow terminals (< 60 columns) the trailing tagline is dropped so the
11
+ * decorated title still fits.
12
+ * @param text - The title string to animate (the ripple segment)
13
+ * @param isPlaying - Whether playback is active; the animation runs only when true
14
+ */
15
+ export declare function AnimatedTitle({ text, isPlaying }: AnimatedTitleProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-title.d.ts","sourceRoot":"","sources":["../../src/components/animated-title.tsx"],"names":[],"mappings":"AAoBA,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAgCpE"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, useWindowSize } from 'ink';
3
+ import { useEffect, useState } from 'react';
4
+ const FRAME_INTERVAL_MS = 120;
5
+ const TAGLINE = 'a spotify cli for nerds';
6
+ const MIN_WIDE_COLUMNS = 60;
7
+ // Green gradient (bright → dark) used to animate the title. The brightness
8
+ // range matches the karaoke palette in animated-line.tsx (`#0cb80c` → `#0f570f`)
9
+ // for visual consistency across the app and to keep the contrast subtle.
10
+ const COLORS = [
11
+ '#0cb80c',
12
+ '#0ca80c',
13
+ '#0c980c',
14
+ '#0c880c',
15
+ '#0c780c',
16
+ '#0c680c',
17
+ '#0c570c',
18
+ ];
19
+ /**
20
+ * Renders the stylized header one-liner: `♪ ── <text> ── ♪ <tagline>`.
21
+ * Only the `<text>` segment animates — each letter cycles through a green
22
+ * gradient offset by its position so the brightest spot ripples left-to-right.
23
+ * The animation is paused (and re-renders stop) when `isPlaying` is false.
24
+ * On narrow terminals (< 60 columns) the trailing tagline is dropped so the
25
+ * decorated title still fits.
26
+ * @param text - The title string to animate (the ripple segment)
27
+ * @param isPlaying - Whether playback is active; the animation runs only when true
28
+ */
29
+ export function AnimatedTitle({ text, isPlaying }) {
30
+ const [tick, setTick] = useState(0);
31
+ const { columns } = useWindowSize();
32
+ useEffect(() => {
33
+ if (!isPlaying)
34
+ return;
35
+ const id = setInterval(() => setTick((t) => t + 1), FRAME_INTERVAL_MS * 4);
36
+ return () => clearInterval(id);
37
+ }, [
38
+ isPlaying,
39
+ ]);
40
+ const showTagline = columns >= MIN_WIDE_COLUMNS;
41
+ return (_jsxs(Text, { bold: true, children: [_jsx(Text, { dimColor: true, children: "\u266A \u2500\u2500 " }), text.split('').map((char, idx) => {
42
+ const colorIndex = (((tick - idx) % COLORS.length) + COLORS.length) % COLORS.length;
43
+ // Modulo math guarantees a valid index, so the lookup is always defined.
44
+ const color = COLORS[colorIndex];
45
+ return (_jsx(Text, { color: color, children: char }, idx));
46
+ }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 \u266A" }), showTagline && _jsx(Text, { dimColor: true, children: ` ${TAGLINE}` })] }));
47
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-title.js","sourceRoot":"","sources":["../../src/components/animated-title.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,OAAO,GAAG,yBAAyB,CAAC;AAC1C,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,2EAA2E;AAC3E,iFAAiF;AACjF,yEAAyE;AACzE,MAAM,MAAM,GAAG;IACb,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACV,CAAC;AAOF;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAsB;IACnE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAAC;IAEpC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,GAAG,CAAC,CAAC,CAAC;QAC3E,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC,EAAE;QACD,SAAS;KACV,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,IAAI,gBAAgB,CAAC;IAEhD,OAAO,CACL,MAAC,IAAI,IAAC,IAAI,mBACR,KAAC,IAAI,IAAC,QAAQ,2CAAa,EAC1B,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBAChC,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;gBACpF,yEAAyE;gBACzE,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAE,CAAC;gBAClC,OAAO,CAEL,KAAC,IAAI,IAAW,KAAK,EAAE,KAAK,YACzB,IAAI,IADI,GAAG,CAEP,CACR,CAAC;YACJ,CAAC,CAAC,EACF,KAAC,IAAI,IAAC,QAAQ,2CAAa,EAC1B,WAAW,IAAI,KAAC,IAAI,IAAC,QAAQ,kBAAE,MAAM,OAAO,EAAE,GAAQ,IAClD,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browse-detail-view.d.ts","sourceRoot":"","sources":["../../src/components/browse-detail-view.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CA0DvG"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { ICON_LIST_GUTTER, ICON_LIST_PLAYING, ICON_LIST_SELECTED } from '../icons.js';
3
4
  /**
4
5
  * Renders album or playlist track lists with a scrolling selection window.
5
6
  * Reuses the windowing pattern from queue-view for consistent navigation.
@@ -22,7 +23,7 @@ export function BrowseDetailView({ data, selectedIndex, currentTrackUri, height
22
23
  const label = `${item.artist} - ${item.name}`;
23
24
  const isSelected = idx === selectedIndex;
24
25
  const isPlaying = currentTrackUri !== null && item.uri === currentTrackUri;
25
- const icon = isPlaying ? '\u25B6 ' : isSelected ? '> ' : ' ';
26
+ const icon = isPlaying ? ICON_LIST_PLAYING : isSelected ? ICON_LIST_SELECTED : ICON_LIST_GUTTER;
26
27
  if (isPlaying) {
27
28
  return (_jsxs(Text, { bold: true, color: "green", inverse: isSelected, children: [icon, label] }, item.uri + idx));
28
29
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browse-detail-view.js","sourceRoot":"","sources":["../../src/components/browse-detail-view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAStF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAyB;IACtG,MAAM,WAAW,GAAG,CAAC,CAAC;IACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;IAE1B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;IAChG,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAExD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAC,QAAQ,aAC3D,MAAC,GAAG,IACF,GAAG,EAAE,CAAC,EACN,WAAW,EAAC,MAAM,EAClB,WAAW,EAAC,QAAQ,EACpB,YAAY,QACZ,UAAU,EAAE,KAAK,EACjB,WAAW,EAAE,KAAK,EAClB,SAAS,EAAE,KAAK,aAEhB,KAAC,IAAI,IAAC,IAAI,kBAAE,IAAI,CAAC,KAAK,GAAQ,EAC7B,IAAI,CAAC,QAAQ,IAAI,MAAC,IAAI,IAAC,QAAQ,yBAAI,IAAI,CAAC,QAAQ,IAAQ,IACrD,EAEL,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAC,IAAI,IAAC,QAAQ,sCAAuB,EAE3D,KAAK,CAAC,IAAI,CACT;gBACE,MAAM,EAAE,YAAY;aACrB,EACD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBACP,MAAM,GAAG,GAAG,UAAU,GAAG,CAAC,CAAC;gBAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBACxB,IAAI,CAAC,IAAI;oBAAE,OAAO,KAAC,IAAI,qBAAM,GAAG,CAAU,CAAC;gBAC3C,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC9C,MAAM,UAAU,GAAG,GAAG,KAAK,aAAa,CAAC;gBACzC,MAAM,SAAS,GAAG,eAAe,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,eAAe,CAAC;gBAC3E,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,gBAAgB,CAAC;gBAEhG,IAAI,SAAS,EAAE,CAAC;oBACd,OAAO,CACL,MAAC,IAAI,IAAsB,IAAI,QAAC,KAAK,EAAC,OAAO,EAAC,OAAO,EAAE,UAAU,aAC9D,IAAI,EACJ,KAAK,KAFG,IAAI,CAAC,GAAG,GAAG,GAAG,CAGlB,CACR,CAAC;gBACJ,CAAC;gBAED,OAAO,CACL,MAAC,IAAI,IAAsB,OAAO,EAAE,UAAU,aAC3C,IAAI,EACJ,KAAK,KAFG,IAAI,CAAC,GAAG,GAAG,GAAG,CAGlB,CACR,CAAC;YACJ,CAAC,CACF,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { DeviceType } from '../spotify/devices.js';
2
+ type DeviceIndicatorProps = {
3
+ name: string | null;
4
+ type: DeviceType | null;
5
+ };
6
+ /**
7
+ * Picks a glyph for a device type. Kept as an exported helper so the
8
+ * device-picker-view can reuse it next to each row without duplicating the
9
+ * icon-to-type mapping.
10
+ */
11
+ export declare function deviceIcon(type: DeviceType | null): string;
12
+ /**
13
+ * Compact always-visible header badge showing which Spotify Connect device
14
+ * is currently playing. On narrow terminals (<60 columns) only the glyph is
15
+ * rendered so the animated title still has room.
16
+ */
17
+ export declare function DeviceIndicator({ name, type }: DeviceIndicatorProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-indicator.d.ts","sourceRoot":"","sources":["../../src/components/device-indicator.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,CA2B1D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,oBAAoB,2CAanE"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text, useWindowSize } from 'ink';
3
+ import { ICON_DEVICE_CAR, ICON_DEVICE_COMPUTER, ICON_DEVICE_GAME, ICON_DEVICE_PHONE, ICON_DEVICE_SPEAKER, ICON_DEVICE_TV, ICON_DEVICE_UNKNOWN, } from '../icons.js';
4
+ const MIN_WIDE_COLUMNS = 60;
5
+ /**
6
+ * Picks a glyph for a device type. Kept as an exported helper so the
7
+ * device-picker-view can reuse it next to each row without duplicating the
8
+ * icon-to-type mapping.
9
+ */
10
+ export function deviceIcon(type) {
11
+ switch (type) {
12
+ case 'Computer':
13
+ return ICON_DEVICE_COMPUTER;
14
+ case 'Smartphone':
15
+ return ICON_DEVICE_PHONE;
16
+ case 'Speaker':
17
+ case 'AVR':
18
+ case 'STB':
19
+ case 'AudioDongle':
20
+ case 'CastAudio':
21
+ return ICON_DEVICE_SPEAKER;
22
+ case 'TV':
23
+ case 'CastVideo':
24
+ return ICON_DEVICE_TV;
25
+ case 'Automobile':
26
+ return ICON_DEVICE_CAR;
27
+ case 'GameConsole':
28
+ return ICON_DEVICE_GAME;
29
+ case 'Unknown':
30
+ case null:
31
+ return ICON_DEVICE_UNKNOWN;
32
+ default: {
33
+ const _exhaustive = type;
34
+ return _exhaustive;
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Compact always-visible header badge showing which Spotify Connect device
40
+ * is currently playing. On narrow terminals (<60 columns) only the glyph is
41
+ * rendered so the animated title still has room.
42
+ */
43
+ export function DeviceIndicator({ name, type }) {
44
+ const { columns } = useWindowSize();
45
+ const glyph = deviceIcon(type);
46
+ if (!name) {
47
+ return _jsx(Text, { dimColor: true, children: `${glyph} no device` });
48
+ }
49
+ if (columns < MIN_WIDE_COLUMNS) {
50
+ return _jsx(Text, { dimColor: true, children: glyph });
51
+ }
52
+ return _jsx(Text, { dimColor: true, children: `${glyph} ${name}` });
53
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-indicator.js","sourceRoot":"","sources":["../../src/components/device-indicator.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAO5B;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAuB;IAChD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,UAAU;YACb,OAAO,oBAAoB,CAAC;QAC9B,KAAK,YAAY;YACf,OAAO,iBAAiB,CAAC;QAC3B,KAAK,SAAS,CAAC;QACf,KAAK,KAAK,CAAC;QACX,KAAK,KAAK,CAAC;QACX,KAAK,aAAa,CAAC;QACnB,KAAK,WAAW;YACd,OAAO,mBAAmB,CAAC;QAC7B,KAAK,IAAI,CAAC;QACV,KAAK,WAAW;YACd,OAAO,cAAc,CAAC;QACxB,KAAK,YAAY;YACf,OAAO,eAAe,CAAC;QACzB,KAAK,aAAa;YAChB,OAAO,gBAAgB,CAAC;QAC1B,KAAK,SAAS,CAAC;QACf,KAAK,IAAI;YACP,OAAO,mBAAmB,CAAC;QAC7B,SAAS,CAAC;YACR,MAAM,WAAW,GAAU,IAAI,CAAC;YAChC,OAAO,WAAW,CAAC;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAwB;IAClE,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAC,IAAI,IAAC,QAAQ,kBAAE,GAAG,KAAK,YAAY,GAAQ,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,GAAG,gBAAgB,EAAE,CAAC;QAC/B,OAAO,KAAC,IAAI,IAAC,QAAQ,kBAAE,KAAK,GAAQ,CAAC;IACvC,CAAC;IAED,OAAO,KAAC,IAAI,IAAC,QAAQ,kBAAE,GAAG,KAAK,IAAI,IAAI,EAAE,GAAQ,CAAC;AACpD,CAAC"}
@@ -0,0 +1,10 @@
1
+ type DevicePickerViewProps = {
2
+ height: number;
3
+ };
4
+ /**
5
+ * Device picker panel — lists Spotify Connect devices and lets the user
6
+ * transfer playback to one. The currently active device is highlighted in
7
+ * green; arrow keys move the cursor and enter triggers the transfer.
8
+ */
9
+ export declare function DevicePickerView({ height }: DevicePickerViewProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-picker-view.d.ts","sourceRoot":"","sources":["../../src/components/device-picker-view.tsx"],"names":[],"mappings":"AAKA,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAuCF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CAsDjE"}
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useDevicesContext } from '../contexts/devices-context.js';
4
+ import { deviceIcon } from './device-indicator.js';
5
+ function formatType(type) {
6
+ if (type === 'Unknown')
7
+ return '';
8
+ if (type === 'AVR' || type === 'STB' || type === 'TV')
9
+ return type;
10
+ // Break "GameConsole" / "CastVideo" / "CastAudio" / "AudioDongle" into two words
11
+ return type.replace(/([a-z])([A-Z])/g, '$1 $2');
12
+ }
13
+ function formatVolume(vol) {
14
+ if (vol === null)
15
+ return '';
16
+ return `${vol}%`;
17
+ }
18
+ function DeviceRow({ device, isSelected }) {
19
+ const icon = deviceIcon(device.type);
20
+ const type = formatType(device.type);
21
+ const volume = formatVolume(device.volumePercent);
22
+ const details = [
23
+ type,
24
+ volume,
25
+ ]
26
+ .filter((s) => s.length > 0)
27
+ .join(' · ');
28
+ const label = details ? `${icon} ${device.name} ${details}` : `${icon} ${device.name}`;
29
+ if (device.isActive) {
30
+ return (_jsx(Text, { bold: true, color: "green", inverse: isSelected, children: label }));
31
+ }
32
+ if (isSelected) {
33
+ return _jsx(Text, { inverse: true, children: label });
34
+ }
35
+ return _jsx(Text, { children: label });
36
+ }
37
+ /**
38
+ * Device picker panel — lists Spotify Connect devices and lets the user
39
+ * transfer playback to one. The currently active device is highlighted in
40
+ * green; arrow keys move the cursor and enter triggers the transfer.
41
+ */
42
+ export function DevicePickerView({ height }) {
43
+ const { devices, isLoading, selectedIndex, error } = useDevicesContext();
44
+ if (isLoading && devices.length === 0) {
45
+ return _jsx(Text, { dimColor: true, children: "Loading devices..." });
46
+ }
47
+ if (devices.length === 0) {
48
+ return _jsx(Text, { dimColor: true, children: "No devices available. Open Spotify on a device to connect." });
49
+ }
50
+ const activeName = devices.find((d) => d.isActive)?.name ?? null;
51
+ const headerLines = 1;
52
+ const availableHeight = Math.max(height - headerLines, 2);
53
+ const visibleCount = Math.min(availableHeight, devices.length);
54
+ const halfHeight = Math.floor(availableHeight / 2);
55
+ const startIndex = Math.max(0, Math.min(selectedIndex - halfHeight, devices.length - visibleCount));
56
+ return (_jsxs(Box, { flexDirection: "column", height: height, overflow: "hidden", children: [_jsxs(Box, { gap: 1, borderColor: "gray", borderStyle: "single", borderBottom: true, borderLeft: false, borderRight: false, borderTop: false, children: [_jsx(Text, { bold: true, children: "Devices" }), _jsx(Text, { dimColor: true, children: "-" }), _jsxs(Text, { dimColor: true, children: [devices.length, " available"] }), activeName && _jsxs(Text, { dimColor: true, children: ["\u00B7 on ", activeName] })] }), Array.from({
57
+ length: visibleCount,
58
+ }, (_, i) => {
59
+ const idx = startIndex + i;
60
+ const device = devices[idx];
61
+ if (!device)
62
+ return _jsx(Text, { children: " " }, idx);
63
+ return _jsx(DeviceRow, { device: device, isSelected: idx === selectedIndex }, device.id);
64
+ }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: error }) }))] }));
65
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-picker-view.js","sourceRoot":"","sources":["../../src/components/device-picker-view.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lyrics-panel.d.ts","sourceRoot":"","sources":["../../src/components/lyrics-panel.tsx"],"names":[],"mappings":"AAIA,KAAK,gBAAgB,GAAG;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA0BF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAgBnE"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lyrics-view.d.ts","sourceRoot":"","sources":["../../src/components/lyrics-view.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AA0BF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,eAAe,2CA2CnF"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { ICON_INSTRUMENTAL } from '../icons.js';
3
4
  import { getCurrentLineIndex } from '../spotify/lyrics.js';
4
5
  import { AnimatedLine } from './animated-line.js';
5
6
  function LyricSlot({ line, isCurrent, isPast, lineProgress }) {
6
7
  if (isCurrent) {
7
- return _jsx(AnimatedLine, { text: line.text || '\u266A', progress: lineProgress });
8
+ return _jsx(AnimatedLine, { text: line.text || ICON_INSTRUMENTAL, progress: lineProgress });
8
9
  }
9
10
  if (isPast) {
10
11
  return _jsx(Text, { dimColor: true, children: line.text || ' ' });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lyrics-view.js","sourceRoot":"","sources":["../../src/components/lyrics-view.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel-content.d.ts","sourceRoot":"","sources":["../../src/components/panel-content.tsx"],"names":[],"mappings":"AAMA,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,iBAAiB,kDAoBrE"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { usePanelMode } from '../contexts/panel-mode-context.js';
3
+ import { DevicePickerView } from './device-picker-view.js';
3
4
  import { LyricsPanel } from './lyrics-panel.js';
4
5
  import { QueueView } from './queue-view.js';
5
6
  import { SearchPanel } from './search-panel.js';
@@ -18,5 +19,8 @@ export function PanelContent({ progressMs, height }) {
18
19
  if (panelMode === 'search') {
19
20
  return _jsx(SearchPanel, { height: height });
20
21
  }
22
+ if (panelMode === 'devices') {
23
+ return _jsx(DevicePickerView, { height: height });
24
+ }
21
25
  return null;
22
26
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel-content.js","sourceRoot":"","sources":["../../src/components/panel-content.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOhD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,UAAU,EAAE,MAAM,EAAqB;IACpE,MAAM,EAAE,SAAS,EAAE,GAAG,YAAY,EAAE,CAAC;IAErC,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,KAAC,WAAW,IAAC,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAI,CAAC;IACjE,CAAC;IAED,IAAI,SAAS,KAAK,OAAO,EAAE,CAAC;QAC1B,OAAO,KAAC,SAAS,IAAC,MAAM,EAAE,MAAM,GAAI,CAAC;IACvC,CAAC;IAED,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,GAAI,CAAC;IACzC,CAAC;IAED,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,KAAC,gBAAgB,IAAC,MAAM,EAAE,MAAM,GAAI,CAAC;IAC9C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playback-status.d.ts","sourceRoot":"","sources":["../../src/components/playback-status.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAIxD,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,mBAAmB,2CAuBrG"}
@@ -1 +1 @@
1
- {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/components/player.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAiB1D,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAmLF;;GAEG;AACH,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,2CAM7C"}
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/components/player.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAuB1D,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAgNF;;GAEG;AACH,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,2CAM7C"}