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

26 KiB
Raw Blame History

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)

┌──────────────────────────────────────────────────────┐
│  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:

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

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

// 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

{
  "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

┌─────────────────────────────────────────────────────────────┐
│ ┌───┐ 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)

  1. Add branding (NoGood logo/colors)
  2. Smooth animations
  3. Responsive sizing
  4. Loading states

Step 3: State Persistence (1 hour)

  1. localStorage integration
  2. Remember user preferences
  3. Auto-restore on page load

Step 4: Integration (1 hour)

  1. Add to Layout component
  2. Desktop-only logic
  3. 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

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?