beeta/frontend/src/routes/+page.svelte

204 lines
5.2 KiB
Svelte
Raw Normal View History

2025-08-03 21:53:15 -04:00
<script>
import { onMount } from 'svelte';
import { browser } from '$app/environment';
let streams = [];
let interval;
let loading = true;
async function loadStreams() {
if (!browser) return;
try {
const res = await fetch('/api/realms/live');
if (res.ok) {
streams = await res.json();
}
} catch (e) {
console.error('Failed to load streams:', e);
} finally {
loading = false;
}
}
onMount(() => {
loadStreams();
// Refresh every 10 seconds
interval = setInterval(loadStreams, 10000);
return () => {
if (interval) clearInterval(interval);
};
});
</script>
<style>
.hero {
text-align: center;
padding: 4rem 0;
margin-bottom: 3rem;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
background: linear-gradient(135deg, var(--primary), #8b3a92);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
font-size: 1.25rem;
color: var(--gray);
}
.stream-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.stream-card {
background: #111;
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
text-decoration: none;
color: var(--white);
}
.stream-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(86, 29, 94, 0.3);
border-color: var(--primary);
}
.stream-thumbnail {
width: 100%;
height: 180px;
background: linear-gradient(135deg, #1a1a1a, #2a2a2a);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.live-badge {
position: absolute;
top: 1rem;
left: 1rem;
background: #ff0000;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
}
.stream-info {
padding: 1.5rem;
}
.stream-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.25rem;
}
.stream-meta {
display: flex;
align-items: center;
gap: 1rem;
color: var(--gray);
font-size: 0.9rem;
}
.streamer-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.streamer-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--gray);
}
.viewer-count {
display: flex;
align-items: center;
gap: 0.25rem;
}
.viewer-count::before {
content: '•';
width: 8px;
height: 8px;
background: #ff0000;
border-radius: 50%;
margin-right: 0.25rem;
}
.no-streams {
text-align: center;
padding: 4rem 0;
color: var(--gray);
}
.no-streams-icon {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.5;
}
</style>
<div class="container">
<div class="hero">
<h1>Live Streams</h1>
<p>Watch your favorite streamers live</p>
</div>
{#if loading}
<div style="text-align: center; padding: 2rem;">
<p>Loading streams...</p>
</div>
{:else if streams.length === 0}
<div class="no-streams">
<div class="no-streams-icon">📺</div>
<h2>No streams live right now</h2>
<p>Check back later or become a streamer yourself!</p>
</div>
{:else}
<div class="stream-grid">
{#each streams as stream}
<a href={`/${stream.name}/live`} class="stream-card">
<div class="stream-thumbnail">
<div class="live-badge">LIVE</div>
<span style="font-size: 3rem; opacity: 0.3;">🎮</span>
</div>
<div class="stream-info">
<h3>{stream.name}</h3>
<div class="stream-meta">
<div class="streamer-info">
{#if stream.avatarUrl}
<img src={stream.avatarUrl} alt={stream.username} class="streamer-avatar" />
{:else}
<div class="streamer-avatar"></div>
{/if}
<span>{stream.username}</span>
</div>
<div class="viewer-count">
{stream.viewerCount} {stream.viewerCount === 1 ? 'viewer' : 'viewers'}
</div>
</div>
</div>
</a>
{/each}
</div>
{/if}
</div>