Files
2026-05-09 17:32:17 +02:00

265 lines
12 KiB
PHP
Executable File

@extends('layouts.app')
@section('title', __('radio.music') . ' - ' . config('app.name'))
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/inter@4.x/400-700.css">
<style>
:root {
--radio-primary: {{ $primaryColor ?? '#eeb425' }};
--radio-secondary: {{ $secondaryColor ?? '#1a1a2e' }};
--radio-text: {{ $textColor ?? '#ffffff' }};
--radio-accent: {{ $accentColor ?? '#ff6b6b' }};
}
.radio-container {
background: linear-gradient(135deg, var(--radio-secondary) 0%, #0f0f1a 100%);
min-height: 100vh;
}
.radio-player {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.now-playing-card {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.animate-pulse-slow {
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.live-badge {
background: linear-gradient(90deg, #ef4444, #f97316);
animation: livePulse 2s infinite;
}
@keyframes livePulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
50% { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}
</style>
@endpush
@section('content')
<div class="radio-container text-white">
@if(!$enabled)
<!-- Radio Disabled State -->
<div class="min-h-screen flex items-center justify-center p-6">
<div class="text-center max-w-md">
<div class="mb-6">
<svg class="w-24 h-24 mx-auto text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<h1 class="text-3xl font-bold mb-4">{{ __('radio.offline') }}</h1>
<p class="text-gray-400 mb-6">{{ __('radio.offline_message') }}</p>
@if($offlineMessage)
<div class="bg-gray-800 rounded-lg p-4">
<p class="text-gray-300">{{ $offlineMessage }}</p>
</div>
@endif
</div>
</div>
@else
<!-- Radio Player Section -->
<div class="max-w-6xl mx-auto px-4 py-8">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold flex items-center gap-3">
<span class="live-badge inline-flex items-center px-3 py-1 rounded-full text-xs font-bold">
<span class="w-2 h-2 bg-white rounded-full mr-2"></span>
{{ __('radio.live') }}
</span>
{{ __('radio.music') }}
</h1>
<p class="text-gray-400 mt-1">{{ __('radio.music_desc') }}</p>
</div>
<!-- Stats -->
<div class="flex gap-6">
@if($showListeners)
<div class="stat-card rounded-lg px-4 py-2 flex items-center gap-2">
<svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span id="listenerCount" class="font-semibold">--</span>
<span class="text-gray-400 text-sm">{{ __('radio.listeners') }}</span>
</div>
@endif
<a href="{{ route('radio.leaderboard') }}" class="stat-card rounded-lg px-4 py-2 flex items-center gap-2 hover:bg-white/10 transition">
<svg class="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span class="font-semibold">{{ __('radio.top_100') }}</span>
</a>
</div>
</div>
<!-- Main Player -->
<div class="radio-player rounded-2xl p-6 mb-8">
<div class="grid md:grid-cols-3 gap-6 items-center">
<!-- Album Art / DJ Avatar -->
<div class="flex justify-center">
<div id="djAvatar" class="w-32 h-32 rounded-full bg-gradient-to-br from-amber-400 to-amber-600 flex items-center justify-center text-4xl">
@if($currentDJ && $currentDJ->user)
<img src="{{ $currentDJ->user->avatar_url ?? asset('images/default-avatar.png') }}" alt="{{ $currentDJ->user->username }}" class="w-full h-full rounded-full object-cover">
@else
🎵
@endif
</div>
</div>
<!-- Track Info -->
<div class="text-center">
<div id="nowPlayingInfo">
<p class="text-gray-400 text-sm mb-1">{{ __('radio.now_playing') }}</p>
<h2 id="trackTitle" class="text-xl font-bold mb-2">
@if($nowPlaying && $nowPlaying['title'])
{{ $nowPlaying['title'] }}
@else
--
@endif
</h2>
@if($nowPlaying && $nowPlaying['artist'])
<p id="trackArtist" class="text-gray-400">{{ $nowPlaying['artist'] }}</p>
@endif
</div>
<!-- DJ Info -->
@if($showCurrentDJ && $currentDJ && $currentDJ->user)
<div class="mt-4 pt-4 border-t border-white/10">
<p class="text-gray-400 text-sm mb-1">{{ __('radio.host') }}</p>
<p class="font-semibold text-amber-400">{{ $currentDJ->user->username }}</p>
</div>
@endif
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const player = document.getElementById('radioPlayer');
const playBtn = document.getElementById('playPauseBtn');
const playIcon = document.getElementById('playIcon');
const pauseIcon = document.getElementById('pauseIcon');
const volumeSlider = document.getElementById('volumeSlider');
const listenerCount = document.getElementById('listenerCount');
// Play/Pause
playBtn.addEventListener('click', function() {
if (player.paused) {
player.play().catch(function(e) {
console.log('Autoplay blocked:', e);
});
playIcon.classList.add('hidden');
pauseIcon.classList.remove('hidden');
} else {
player.pause();
playIcon.classList.remove('hidden');
pauseIcon.classList.add('hidden');
}
});
// Volume
volumeSlider.addEventListener('input', function() {
player.volume = this.value / 100;
});
// Initial volume
player.volume = volumeSlider.value / 100;
// Update listeners count
function updateListeners() {
fetch('/api/radio/listeners')
.then(r => r.json())
.then(data => {
if (listenerCount) {
listenerCount.textContent = data.count.toLocaleString();
}
})
.catch(() => {
if (listenerCount) listenerCount.textContent = '--';
});
}
// Update now playing
function updateNowPlaying() {
fetch('/api/radio/now-playing')
.then(r => r.json())
.then(data => {
const titleEl = document.getElementById('trackTitle');
const artistEl = document.getElementById('trackArtist');
if (titleEl && data.title) titleEl.textContent = data.title;
if (artistEl && data.artist) artistEl.textContent = data.artist;
})
.catch(() => {});
}
// Auto-update listeners every 30 seconds
updateListeners();
setInterval(updateListeners, 30000);
// Auto-update now playing every 15 seconds
updateNowPlaying();
setInterval(updateNowPlaying, 15000);
// Load user points if logged in
if (document.body.classList.contains('authenticated')) {
loadUserPoints();
setInterval(loadUserPoints, 60000); // Update every minute
}
});
function loadUserPoints() {
fetch('/api/radio/points/user')
.then(response => {
if (!response.ok) {
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
const pointsCount = document.getElementById('userPointsCount');
const userRank = document.getElementById('userRank');
const pointsHistory = document.getElementById('pointsHistory');
if (pointsCount) pointsCount.textContent = data.points || 0;
if (userRank) userRank.textContent = data.rank || '--';
if (pointsHistory && data.history && data.history.length > 0) {
pointsHistory.innerHTML = data.history.map(item => `
<div class="flex justify-between items-center py-1 border-b border-gray-700 text-xs">
<span class="text-gray-300">${item.reason}</span>
<span class="${item.points > 0 ? 'text-green-400' : 'text-red-400'}">
${item.points > 0 ? '+' : ''}${item.points}
</span>
</div>
`).join('');
} else if (pointsHistory) {
pointsHistory.innerHTML = '<p class="text-xs text-gray-500">{{ __("radio.no_history") }}</p>';
}
})
.catch(error => {
console.log('User points not available:', error.message);
// Silently fail if not authenticated or other error
});
}
</script>
@endpush