SkateSpots/NOGOOD_RADIO_PROPOSAL.md
shakespeare.diy 27e14994b5 Import files from skate-spots project
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 20:04:15 +07:00

781 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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?