781 lines
26 KiB
Markdown
781 lines
26 KiB
Markdown
# NoGood Radio Integration - Background Music Player
|
||
|
||
## Concept
|
||
|
||
Add a **persistent music player** that plays NoGood Radio while users browse the app on desktop. Think of it like Spotify's mini-player, but for skate culture radio.
|
||
|
||
---
|
||
|
||
## How It Would Look (Desktop)
|
||
|
||
### Option 1: Bottom Bar Player (Spotify-style) ✅ RECOMMENDED
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ Regular App Content │
|
||
│ (Map, Spots, Events, etc.) │
|
||
│ │
|
||
│ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────┘
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ 🎵 NoGood Radio 🔊──── ▶️ ⏸️│
|
||
│ Now Playing: Lo-Fi Skate Beats │
|
||
└──────────────────────────────────────────────────────┘
|
||
↑
|
||
Persistent player bar (always visible)
|
||
```
|
||
|
||
**Layout Details:**
|
||
```
|
||
┌───────────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio | Now Playing: Chill Skate Mix | 🔊──────── | ▶️ ⏸️ 🔇 ✕ │
|
||
└───────────────────────────────────────────────────────────┘
|
||
↑ ↑ ↑ ↑
|
||
Logo/Icon Track info Volume Controls
|
||
```
|
||
|
||
**Features:**
|
||
- Sticks to bottom of screen
|
||
- Plays while navigating between pages
|
||
- Volume slider
|
||
- Play/Pause button
|
||
- Minimize/Close button
|
||
- Shows current track/show name
|
||
- Doesn't interrupt browsing
|
||
|
||
---
|
||
|
||
### Option 2: Floating Mini Player (Draggable)
|
||
|
||
```
|
||
Regular App Content
|
||
|
||
|
||
┌──────────────────────┐
|
||
│ 🎵 NoGood Radio │
|
||
│ ▶️ Lo-Fi Beats │
|
||
│ 🔊────── [–] [✕] │
|
||
└──────────────────────┘
|
||
↑
|
||
Draggable, can be moved anywhere
|
||
```
|
||
|
||
**Features:**
|
||
- Floats above content
|
||
- User can drag to preferred position
|
||
- Minimizes to small icon
|
||
- Expands on hover
|
||
- Less obtrusive than bottom bar
|
||
|
||
---
|
||
|
||
### Option 3: Sidebar Docked Player
|
||
|
||
```
|
||
┌────────┬─────────────────────────────────┐
|
||
│ Nav │ Main Content │
|
||
│ Links │ (Spots, Events, etc.) │
|
||
│ │ │
|
||
│ ────── │ │
|
||
│ │ │
|
||
│ 🎵 ▶️ │ │
|
||
│ NoGood │ │
|
||
│ Radio │ │
|
||
│ │ │
|
||
│ 🔊──── │ │
|
||
└────────┴─────────────────────────────────┘
|
||
↑
|
||
Docked in sidebar (desktop only)
|
||
```
|
||
|
||
**Features:**
|
||
- Always visible in navigation
|
||
- Compact vertical design
|
||
- Doesn't cover content
|
||
- Desktop-only (sidebar space)
|
||
|
||
---
|
||
|
||
## Detailed UI/UX Design (Option 1 - Bottom Bar)
|
||
|
||
### Collapsed State (Default)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio • Now Playing: Sunset Session Mix 🔊──── ▶️ [–]│
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Elements:**
|
||
- **Logo**: 🎧 or NoGood brand icon
|
||
- **Station name**: "NoGood Radio"
|
||
- **Track info**: Scrolling text if too long
|
||
- **Volume slider**: 🔊──────
|
||
- **Play/Pause**: ▶️ / ⏸️
|
||
- **Minimize**: [–] (collapses to icon only)
|
||
|
||
**Colors:**
|
||
- Background: Dark gradient (matches app theme)
|
||
- Accent: `#ccff00` (brand yellow)
|
||
- Text: White/gray
|
||
|
||
---
|
||
|
||
### Minimized State (When User Clicks [–])
|
||
|
||
```
|
||
┌──────────────────┐
|
||
│ 🎧 ▶️ 🔊──── [+]│
|
||
└──────────────────┘
|
||
↑
|
||
Small bar, bottom-right
|
||
```
|
||
|
||
**Elements:**
|
||
- Just icon, play button, volume
|
||
- Expand button [+] to restore
|
||
- Stays out of the way
|
||
|
||
---
|
||
|
||
### Expanded State (When User Clicks for More Info)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio [–] [✕]│
|
||
│ │
|
||
│ Now Playing: │
|
||
│ 🎵 Sunset Session Mix - Track 04 │
|
||
│ │
|
||
│ ▶️ ⏸️ | 🔊──────────── | 12:34 / 45:00 │
|
||
│ │
|
||
│ 📻 About NoGood Radio: │
|
||
│ Curated skate culture music 24/7 │
|
||
│ │
|
||
│ ⚡ Support NoGood │
|
||
│ [Zap 100] [Zap 500] [Zap 1000] │
|
||
│ │
|
||
│ 🔗 [Visit zap.stream] [Share Radio] │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Height**: ~250px (slides up from bottom)
|
||
**Features**:
|
||
- Show more info about current track
|
||
- Zap button to support NoGood
|
||
- Links to their platform
|
||
- About section
|
||
|
||
---
|
||
|
||
## Mobile Experience
|
||
|
||
**Recommendation**: **Don't show on mobile**
|
||
|
||
**Why:**
|
||
- Mobile users often have their own music playing
|
||
- Takes up valuable screen space
|
||
- Audio conflicts with phone calls
|
||
- Data usage concerns
|
||
- Better UX to keep mobile clean
|
||
|
||
**How to detect**:
|
||
```typescript
|
||
const isDesktop = window.innerWidth > 768;
|
||
// Only render player if isDesktop === true
|
||
```
|
||
|
||
---
|
||
|
||
## Technical Implementation
|
||
|
||
### Component Structure
|
||
|
||
```
|
||
App.tsx
|
||
└─ Layout.tsx
|
||
└─ AppRouter (pages)
|
||
└─ RadioPlayer.tsx (NEW - conditional, desktop only)
|
||
```
|
||
|
||
### RadioPlayer Component
|
||
|
||
```tsx
|
||
import { useState, useRef, useEffect } from 'react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Slider } from '@/components/ui/slider';
|
||
import { Play, Pause, Volume2, VolumeX, Minimize2, Maximize2, X } from 'lucide-react';
|
||
|
||
export function RadioPlayer() {
|
||
const [isPlaying, setIsPlaying] = useState(false);
|
||
const [volume, setVolume] = useState(0.7);
|
||
const [isMinimized, setIsMinimized] = useState(false);
|
||
const [isExpanded, setIsExpanded] = useState(false);
|
||
const [isMuted, setIsMuted] = useState(false);
|
||
const audioRef = useRef<HTMLAudioElement>(null);
|
||
|
||
// Stream URL (NoGood Radio on zap.stream)
|
||
const streamUrl = "https://stream.zap.stream/nogoodradio";
|
||
|
||
// Only show on desktop
|
||
const isDesktop = window.innerWidth > 768;
|
||
if (!isDesktop) return null;
|
||
|
||
// Audio controls
|
||
const togglePlay = () => {
|
||
if (audioRef.current) {
|
||
if (isPlaying) {
|
||
audioRef.current.pause();
|
||
} else {
|
||
audioRef.current.play();
|
||
}
|
||
setIsPlaying(!isPlaying);
|
||
}
|
||
};
|
||
|
||
const handleVolumeChange = (value: number[]) => {
|
||
const newVolume = value[0];
|
||
setVolume(newVolume);
|
||
if (audioRef.current) {
|
||
audioRef.current.volume = newVolume;
|
||
}
|
||
setIsMuted(newVolume === 0);
|
||
};
|
||
|
||
const toggleMute = () => {
|
||
if (audioRef.current) {
|
||
audioRef.current.muted = !isMuted;
|
||
setIsMuted(!isMuted);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
{/* Audio element (hidden) */}
|
||
<audio ref={audioRef} src={streamUrl} preload="none" />
|
||
|
||
{/* Player UI */}
|
||
{!isMinimized ? (
|
||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-gradient-to-r from-black/95 to-gray-900/95 backdrop-blur border-t-2 border-[#ccff00]/30">
|
||
<div className="container px-4 py-3 flex items-center gap-4">
|
||
{/* Logo & Info */}
|
||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||
<div className="text-2xl">🎧</div>
|
||
<div className="min-w-0">
|
||
<div className="font-bold text-sm">NoGood Radio</div>
|
||
<div className="text-xs text-gray-400 truncate">
|
||
Now Playing: Lo-Fi Skate Beats
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Controls */}
|
||
<div className="flex items-center gap-3">
|
||
{/* Volume */}
|
||
<div className="hidden sm:flex items-center gap-2">
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8"
|
||
onClick={toggleMute}
|
||
>
|
||
{isMuted ? <VolumeX className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
|
||
</Button>
|
||
<Slider
|
||
value={[volume]}
|
||
min={0}
|
||
max={1}
|
||
step={0.01}
|
||
onValueChange={handleVolumeChange}
|
||
className="w-24"
|
||
/>
|
||
</div>
|
||
|
||
{/* Play/Pause */}
|
||
<Button
|
||
variant="outline"
|
||
size="icon"
|
||
className="h-8 w-8 bg-[#ccff00] hover:bg-[#ccff00]/90 text-black border-[#ccff00]"
|
||
onClick={togglePlay}
|
||
>
|
||
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
|
||
</Button>
|
||
|
||
{/* Minimize */}
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8"
|
||
onClick={() => setIsMinimized(true)}
|
||
>
|
||
<Minimize2 className="h-4 w-4" />
|
||
</Button>
|
||
|
||
{/* Close */}
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8"
|
||
onClick={() => {/* close player */}}
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
// Minimized version (bottom-right)
|
||
<div className="fixed bottom-4 right-4 z-50">
|
||
<Button
|
||
className="flex items-center gap-2 bg-gradient-to-r from-black/95 to-gray-900/95 backdrop-blur border-2 border-[#ccff00]/30 hover:border-[#ccff00]"
|
||
onClick={() => setIsMinimized(false)}
|
||
>
|
||
<span className="text-xl">🎧</span>
|
||
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
|
||
<span className="text-xs">Radio</span>
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Visual Mockups
|
||
|
||
### Default State (Bottom Bar)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ Regular App Content │
|
||
│ (Map, Spots, Events, etc.) │
|
||
│ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio │
|
||
│ Now Playing: Lo-Fi Skate Beats Mix 🔊──── ▶️ [–] [✕] │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
↑
|
||
Always visible at bottom
|
||
Plays while you navigate
|
||
```
|
||
|
||
**Key Features:**
|
||
- Height: ~60px (minimal intrusion)
|
||
- Dark background with yellow accent
|
||
- Sticky positioning (scrolls with page)
|
||
- Persistent across page navigation
|
||
- Desktop only (hidden on mobile)
|
||
|
||
---
|
||
|
||
### Minimized State (Small Floating Button)
|
||
|
||
```
|
||
┌──────────┐
|
||
│ 🎧 ▶️ │
|
||
│ Radio │
|
||
└──────────┘
|
||
↑
|
||
Bottom-right corner
|
||
Click to expand
|
||
```
|
||
|
||
**When to use:**
|
||
- User clicks minimize button
|
||
- Saves screen space
|
||
- Still shows play/pause
|
||
- Click to restore full bar
|
||
|
||
---
|
||
|
||
### Expanded State (More Info)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ Regular App Content │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio [–] [✕] │
|
||
│ │
|
||
│ Now Playing: Lo-Fi Skate Beats Mix - Track 04 │
|
||
│ │
|
||
│ ▶️ ⏸️ 🔊────────────── │
|
||
│ │
|
||
│ 📻 About NoGood Radio │
|
||
│ 24/7 curated music for skate culture. Beats, hip-hop, punk, │
|
||
│ and everything that makes you want to push. │
|
||
│ │
|
||
│ ⚡ Support NoGood Radio │
|
||
│ [Zap 100 sats] [Zap 500 sats] [Zap 1000 sats] │
|
||
│ │
|
||
│ 🔗 [Visit zap.stream] [Share Radio] │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
↑
|
||
Slides up to ~250px when clicked
|
||
Shows more details and zap buttons
|
||
```
|
||
|
||
**When to show:**
|
||
- User clicks on player bar
|
||
- Shows description
|
||
- Zap buttons to support NoGood
|
||
- Links to their platform
|
||
|
||
---
|
||
|
||
## User Experience Flow
|
||
|
||
### First Visit (Not Playing)
|
||
|
||
```
|
||
User opens app
|
||
↓
|
||
Sees bottom bar: "🎧 NoGood Radio [▶️ Play]"
|
||
↓
|
||
Clicks Play button
|
||
↓
|
||
Music starts streaming
|
||
↓
|
||
Continues while browsing
|
||
↓
|
||
User browses spots, map, events
|
||
↓
|
||
Music keeps playing (uninterrupted)
|
||
```
|
||
|
||
### Returning Visit (Remembers Preference)
|
||
|
||
```
|
||
User opens app
|
||
↓
|
||
If they were playing last time:
|
||
→ Auto-starts playing
|
||
|
||
If they closed it last time:
|
||
→ Shows collapsed bar
|
||
→ User can opt-in to play
|
||
```
|
||
|
||
---
|
||
|
||
## Technical Details
|
||
|
||
### Stream Source
|
||
|
||
**NoGood Radio streams on zap.stream:**
|
||
- Platform: https://zap.stream
|
||
- Profile: NoGood (@nogood.store)
|
||
- Stream format: HLS (HTTP Live Streaming) or Icecast
|
||
- Accessible via: Direct stream URL or iframe embed
|
||
|
||
**How to find stream URL:**
|
||
1. Query Nostr for NoGood's kind:30311 events (Live Streaming Event)
|
||
2. Extract `streaming` tag with URL
|
||
3. Use that URL in HTML5 audio player
|
||
|
||
**Fallback**: If live stream not available, use:
|
||
- Pre-recorded NoGood mixes from SoundCloud
|
||
- NoGood's Spotify playlists
|
||
- Or disable player gracefully
|
||
|
||
---
|
||
|
||
### Audio Player Implementation
|
||
|
||
```tsx
|
||
// Simple HTML5 audio approach
|
||
<audio
|
||
ref={audioRef}
|
||
src="https://stream.zap.stream/nogoodradio" // Example URL
|
||
preload="none"
|
||
onPlay={() => setIsPlaying(true)}
|
||
onPause={() => setIsPlaying(false)}
|
||
onError={(e) => handleError(e)}
|
||
/>
|
||
```
|
||
|
||
**Browser Support:**
|
||
- ✅ Chrome, Firefox, Safari, Edge
|
||
- ✅ HLS support (most modern browsers)
|
||
- ✅ Fallback for older browsers
|
||
|
||
---
|
||
|
||
## State Management
|
||
|
||
### What to Store in localStorage
|
||
|
||
```typescript
|
||
{
|
||
"radio-enabled": true, // User wants radio available
|
||
"radio-autoplay": false, // Auto-start on load
|
||
"radio-volume": 0.7, // Volume level (0-1)
|
||
"radio-minimized": false, // UI state
|
||
"radio-last-played": 1234567890 // Timestamp
|
||
}
|
||
```
|
||
|
||
**Behavior:**
|
||
- Remember if user closed radio permanently (don't show again)
|
||
- Remember volume preference
|
||
- Remember minimized state
|
||
- Optional: Remember last position (if draggable)
|
||
|
||
---
|
||
|
||
## Design Variants
|
||
|
||
### Variant A: Minimal (Clean & Simple)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio • Lo-Fi Mix 🔊─── ▶️ ✕ │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Pros:**
|
||
- Takes minimal space
|
||
- Clean and unobtrusive
|
||
- Fast to implement
|
||
|
||
**Cons:**
|
||
- Less engaging
|
||
- No zap integration
|
||
|
||
---
|
||
|
||
### Variant B: Branded (Full Featured) ✅ RECOMMENDED
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ┌───┐ NoGood Radio │
|
||
│ │🎧 │ Now Playing: Skate Mix Vol. 3 🔊──── ▶️ ⚡ [–] [✕]│
|
||
│ └───┘ Curated skate culture music ^^^ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
Zap button!
|
||
```
|
||
|
||
**Pros:**
|
||
- Shows branding (NoGood logo/icon)
|
||
- Integrated zap support
|
||
- Subtitle text for context
|
||
- More engaging
|
||
|
||
**Cons:**
|
||
- Slightly taller (~80px vs 60px)
|
||
|
||
---
|
||
|
||
### Variant C: Card Style (Modern)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ╔═══════════════════════════════════════════════════════╗ │
|
||
│ ║ 🎧 NoGood Radio [–] [✕] ║ │
|
||
│ ║ ║ │
|
||
│ ║ 🎵 Sunset Session Mix ║ │
|
||
│ ║ ▶️ ⏸️ 🔊────────── ⚡ Zap ║ │
|
||
│ ╚═══════════════════════════════════════════════════════╝ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Pros:**
|
||
- Looks like a component (card design)
|
||
- Polished appearance
|
||
- Clear boundaries
|
||
|
||
**Cons:**
|
||
- Takes more space (~100px)
|
||
|
||
---
|
||
|
||
## Page-Specific Behavior
|
||
|
||
### On Different Pages
|
||
|
||
**Home Page (`/`)**:
|
||
- Show player
|
||
- Auto-collapse to minimize screen clutter
|
||
|
||
**Map Page (`/map`)**:
|
||
- Show player
|
||
- Slightly transparent to not block map
|
||
|
||
**Event Pages (`/events`, `/event/:id`)**:
|
||
- Show player
|
||
- If event has its own music, allow switching
|
||
|
||
**Spot Pages (`/spot/:id`)**:
|
||
- Show player normally
|
||
- Unobtrusive
|
||
|
||
**Settings (`/settings`)**:
|
||
- Hide player (less distraction)
|
||
|
||
---
|
||
|
||
## Advanced Features (Future)
|
||
|
||
### Track Metadata (If Available)
|
||
```
|
||
🎵 Now Playing:
|
||
Artist: Unknown Brain
|
||
Track: Skate or Die
|
||
Album: Street Sessions Vol. 2
|
||
```
|
||
|
||
### Playlist Integration
|
||
```
|
||
📻 Currently playing from:
|
||
"NoGood's Weekend Mix" playlist
|
||
[View Full Playlist] →
|
||
```
|
||
|
||
### Listener Count
|
||
```
|
||
🎧 NoGood Radio • 1,234 listeners worldwide
|
||
```
|
||
|
||
### Share What You're Hearing
|
||
```
|
||
🔗 Share: "I'm listening to NoGood Radio on GoSkateSpots!"
|
||
[Share on Twitter] [Share on Nostr]
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Steps
|
||
|
||
### Step 1: Basic Player (3-4 hours)
|
||
1. Create RadioPlayer component
|
||
2. HTML5 audio element with stream URL
|
||
3. Play/pause button
|
||
4. Volume slider
|
||
5. Close button
|
||
|
||
### Step 2: UI Polish (2 hours)
|
||
6. Add branding (NoGood logo/colors)
|
||
7. Smooth animations
|
||
8. Responsive sizing
|
||
9. Loading states
|
||
|
||
### Step 3: State Persistence (1 hour)
|
||
10. localStorage integration
|
||
11. Remember user preferences
|
||
12. Auto-restore on page load
|
||
|
||
### Step 4: Integration (1 hour)
|
||
13. Add to Layout component
|
||
14. Desktop-only logic
|
||
15. Test across all pages
|
||
|
||
**Total: 6-8 hours**
|
||
|
||
---
|
||
|
||
## Questions to Answer
|
||
|
||
### 1. Stream URL
|
||
**Q**: What's the actual NoGood Radio stream URL?
|
||
**A**: Need to query Nostr for their kind:30311 event, extract `streaming` tag
|
||
|
||
### 2. Permissions
|
||
**Q**: Does NoGood Radio allow embedding?
|
||
**A**: Should check with NoGood or zap.stream ToS
|
||
|
||
### 3. Attribution
|
||
**Q**: How to credit NoGood?
|
||
**A**: Show their name, link to their profile, enable zaps
|
||
|
||
### 4. Fallback
|
||
**Q**: What if stream is offline?
|
||
**A**: Show "Radio offline" message or hide player
|
||
|
||
---
|
||
|
||
## Cost Analysis
|
||
|
||
### Development Cost
|
||
- **Time**: 6-8 hours
|
||
- **Complexity**: Low-Medium
|
||
- **Maintenance**: Minimal
|
||
|
||
### Ongoing Costs
|
||
- **Bandwidth**: $0 (stream hosted by zap.stream)
|
||
- **Storage**: $0 (no files)
|
||
- **API**: $0 (just audio streaming)
|
||
|
||
### User Bandwidth
|
||
- **Audio quality**: ~128kbps
|
||
- **Per hour**: ~60MB
|
||
- **Consideration**: Some users may have data caps
|
||
|
||
---
|
||
|
||
## Recommended Design (Final)
|
||
|
||
### Bottom Bar - Collapsed (Default)
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────────────┐
|
||
│ Main App Content │
|
||
└────────────────────────────────────────────────────────────────────┘
|
||
┌────────────────────────────────────────────────────────────────────┐
|
||
│ 🎧 NoGood Radio • Lo-Fi Skate Mix 🔊────── ▶️ ⚡ [–] [✕]│
|
||
└────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Height**: 60px
|
||
**Background**: `bg-gradient-to-r from-black/95 to-gray-900/95 backdrop-blur`
|
||
**Border**: `border-t-2 border-[#ccff00]/30`
|
||
**Position**: `fixed bottom-0 left-0 right-0 z-50`
|
||
|
||
### Elements:
|
||
1. **🎧 Icon** - Visual identifier
|
||
2. **Station Name** - "NoGood Radio"
|
||
3. **Track Info** - "Lo-Fi Skate Mix" (scrolls if long)
|
||
4. **Volume** - Slider (🔊────── )
|
||
5. **Play/Pause** - Yellow button (brand color)
|
||
6. **Zap** - Quick support button
|
||
7. **Minimize** - [–] collapses to corner
|
||
8. **Close** - [✕] hides completely
|
||
|
||
### Interactions:
|
||
- **Click bar** → Expands to show more info
|
||
- **Click Play** → Starts streaming
|
||
- **Drag volume** → Adjusts sound
|
||
- **Click Zap** → Opens ZapDialog for NoGood
|
||
- **Click Minimize** → Collapses to floating button
|
||
- **Click Close** → Hides player (remember preference)
|
||
|
||
---
|
||
|
||
## Bottom Line
|
||
|
||
### What You Get:
|
||
- 🎵 **Background music** while browsing
|
||
- 🎧 **NoGood Radio** streaming 24/7
|
||
- 🖥️ **Desktop only** (respects mobile UX)
|
||
- ⚡ **Zap integration** (support NoGood)
|
||
- 🎨 **Beautiful UI** (matches app design)
|
||
- 💾 **Remembers preferences** (volume, state)
|
||
- 🔄 **Persistent** (plays across pages)
|
||
|
||
### Implementation:
|
||
- **Time**: 6-8 hours
|
||
- **Cost**: $0
|
||
- **Complexity**: Low-Medium
|
||
- **Maintenance**: Minimal
|
||
|
||
### The Vibe:
|
||
Imagine browsing skate spots with curated beats in the background. That's the feeling. It makes the app feel **alive** and creates a **skate culture atmosphere**.
|
||
|
||
Like walking into a skate shop with music playing - it just feels right. 🎵🛹
|
||
|
||
Would you like me to implement this?
|