388 lines
18 KiB
PHP
388 lines
18 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
require_once __DIR__ . '/app.php';
|
||
|
||
$playerUrl = 'https://player.foxradios.com/demo/1/index.html';
|
||
$counts = get_entry_counts();
|
||
$errors = [];
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'fan_message') {
|
||
$name = trim((string) ($_POST['name'] ?? ''));
|
||
$email = trim((string) ($_POST['email'] ?? ''));
|
||
$song = trim((string) ($_POST['song_request'] ?? ''));
|
||
$message = trim((string) ($_POST['message'] ?? ''));
|
||
|
||
if ($name === '') {
|
||
$errors[] = 'Escribe tu nombre.';
|
||
}
|
||
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||
$errors[] = 'Indica un correo válido para responderte.';
|
||
}
|
||
if ($message === '') {
|
||
$errors[] = 'Cuéntanos tu saludo o pedido musical.';
|
||
}
|
||
if (strlen($message) > 1200) {
|
||
$errors[] = 'El mensaje debe tener menos de 1200 caracteres.';
|
||
}
|
||
|
||
if ($errors === []) {
|
||
save_entry([
|
||
'entry_type' => 'message',
|
||
'title' => $name,
|
||
'subtitle' => $email,
|
||
'body' => $message,
|
||
'meta_value' => $song,
|
||
'status' => 'new',
|
||
'sort_order' => 0,
|
||
]);
|
||
set_flash('success', 'Tu mensaje ya quedó en cabina. El equipo de Lili Records Radio lo verá en el panel admin.');
|
||
header('Location: /#mensajes');
|
||
exit;
|
||
}
|
||
|
||
set_flash('danger', implode(' ', $errors));
|
||
header('Location: /#mensajes');
|
||
exit;
|
||
}
|
||
|
||
$flash = pull_flash();
|
||
$programs = get_entries('program', ['published']);
|
||
$djs = get_entries('dj', ['published']);
|
||
$socials = get_entries('social', ['published']);
|
||
$currentShow = current_program($programs);
|
||
$nextShow = next_program($programs);
|
||
$groupedPrograms = group_programs_by_day($programs);
|
||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? project_description();
|
||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||
$title = project_name();
|
||
$metaDescription = project_description();
|
||
$assetVersion = (string) max(@filemtime(__DIR__ . '/assets/css/custom.css') ?: time(), @filemtime(__DIR__ . '/assets/js/main.js') ?: time());
|
||
?>
|
||
<!doctype html>
|
||
<html lang="es">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title><?= h($title) ?></title>
|
||
<meta name="description" content="<?= h($metaDescription) ?>">
|
||
<meta name="author" content="Lili Records Radio">
|
||
<?php if ($projectDescription): ?>
|
||
<meta property="og:description" content="<?= h($projectDescription) ?>">
|
||
<meta property="twitter:description" content="<?= h($projectDescription) ?>">
|
||
<?php endif; ?>
|
||
<?php if ($projectImageUrl): ?>
|
||
<meta property="og:image" content="<?= h($projectImageUrl) ?>">
|
||
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>">
|
||
<?php endif; ?>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||
<link rel="stylesheet" href="/assets/css/custom.css?v=<?= h($assetVersion) ?>">
|
||
</head>
|
||
<body>
|
||
<nav class="navbar navbar-expand-lg navbar-dark sticky-top radio-nav border-bottom border-secondary-subtle">
|
||
<div class="container">
|
||
<a class="navbar-brand d-flex align-items-center gap-2" href="/#top">
|
||
<span class="brand-mark">LR</span>
|
||
<span>
|
||
<span class="d-block fw-semibold brand-title">Lili Records Radio</span>
|
||
<span class="brand-subtitle">Online station</span>
|
||
</span>
|
||
</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Abrir navegación">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="mainNav">
|
||
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
|
||
<li class="nav-item"><a class="nav-link" href="#escuchar">Escuchar</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#programacion">Programación</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#djs">DJs</a></li>
|
||
<li class="nav-item"><a class="nav-link" href="#mensajes">Mensajes</a></li>
|
||
<li class="nav-item ms-lg-2"><a class="btn btn-sm btn-outline-light" href="/admin.php">Admin</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<header class="hero-section" id="top">
|
||
<div class="container">
|
||
<div class="row g-4 align-items-stretch">
|
||
<div class="col-lg-7">
|
||
<div class="hero-panel panel p-4 p-lg-5 h-100">
|
||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||
<span class="eyebrow-pill">Señal online activa</span>
|
||
<span class="eyebrow-pill muted">Web app inicial MVP</span>
|
||
</div>
|
||
<h1 class="display-5 fw-semibold text-white mb-3">Tu radio lista para reproducir, descubrir shows y recibir pedidos musicales.</h1>
|
||
<p class="lead text-secondary-emphasis mb-4">Lili Records Radio abre con play inmediato, estado de “Ahora suena”, parrilla semanal, perfiles de DJs y un flujo real para que los oyentes envíen mensajes que el admin puede revisar y actualizar.</p>
|
||
<div class="d-flex flex-wrap gap-3 mb-4">
|
||
<a class="btn btn-light btn-lg" href="#escuchar">Escuchar ahora</a>
|
||
<a class="btn btn-outline-light btn-lg" href="/admin.php">Entrar al panel</a>
|
||
</div>
|
||
<div class="row g-3 stats-row">
|
||
<div class="col-6 col-xl-3">
|
||
<div class="mini-stat">
|
||
<span class="mini-label">Estado</span>
|
||
<strong>En línea</strong>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-xl-3">
|
||
<div class="mini-stat">
|
||
<span class="mini-label">Shows</span>
|
||
<strong><?= h((string) $counts['program']) ?></strong>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-xl-3">
|
||
<div class="mini-stat">
|
||
<span class="mini-label">DJs</span>
|
||
<strong><?= h((string) $counts['dj']) ?></strong>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-xl-3">
|
||
<div class="mini-stat">
|
||
<span class="mini-label">Mensajes</span>
|
||
<strong><?= h((string) $counts['message']) ?></strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-5">
|
||
<aside class="panel hero-side p-4 h-100">
|
||
<div class="panel-header d-flex justify-content-between align-items-start mb-3">
|
||
<div>
|
||
<p class="panel-kicker">Ahora suena</p>
|
||
<h2 class="h4 text-white mb-1"><?= h($currentShow['title'] ?? 'Lili Records Live Mix') ?></h2>
|
||
<p class="text-secondary mb-0"><?= h($currentShow['subtitle'] ?? 'Playlist continua · sin show en vivo detectado') ?></p>
|
||
</div>
|
||
<span class="live-dot">LIVE</span>
|
||
</div>
|
||
<div class="schedule-chip mb-3">
|
||
<span><?= h($currentShow ? weekday_name((int) $currentShow['weekday']) : 'Disponible 24/7') ?></span>
|
||
<strong><?= h($currentShow ? time_label($currentShow['start_time']) . ' – ' . time_label($currentShow['end_time']) : 'Streaming abierto') ?></strong>
|
||
</div>
|
||
<p class="text-secondary small mb-4"><?= h($currentShow['body'] ?? 'Usando el player embebido confirmado por el usuario. Puedes reemplazar la programación demo desde el panel admin.') ?></p>
|
||
<div class="stack-card mb-3">
|
||
<span class="stack-label">Siguiente bloque</span>
|
||
<strong><?= h($nextShow['title'] ?? 'Sin programación próxima') ?></strong>
|
||
<span><?= h($nextShow ? weekday_name((int) $nextShow['weekday']) . ' · ' . time_label($nextShow['start_time']) : 'Agrega shows en admin') ?></span>
|
||
</div>
|
||
<div class="stack-card muted">
|
||
<span class="stack-label">Operación</span>
|
||
<strong>Mensajes conectados</strong>
|
||
<span>Los pedidos entran al panel admin con estado “new”.</span>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="section-block" id="escuchar">
|
||
<div class="container">
|
||
<div class="section-heading d-flex flex-wrap align-items-end justify-content-between gap-3 mb-4">
|
||
<div>
|
||
<span class="section-label">Escuchar</span>
|
||
<h2 class="section-title">Play inmediato desde móvil o desktop</h2>
|
||
</div>
|
||
<p class="section-copy">El reproductor queda visible de inmediato y el contexto editorial acompaña la escucha.</p>
|
||
</div>
|
||
<div class="row g-4">
|
||
<div class="col-xl-8">
|
||
<div class="panel p-3 p-md-4">
|
||
<div class="ratio ratio-16x9 radio-player-shell">
|
||
<iframe src="<?= h($playerUrl) ?>" title="Reproductor de Lili Records Radio" loading="lazy" allow="autoplay; encrypted-media" referrerpolicy="strict-origin-when-cross-origin"></iframe>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-4">
|
||
<div class="panel p-4 h-100 d-flex flex-column justify-content-between">
|
||
<div>
|
||
<span class="section-label">Cabina</span>
|
||
<h3 class="h4 text-white mt-2 mb-3">Información de emisión</h3>
|
||
<ul class="list-unstyled detail-list mb-0">
|
||
<li>
|
||
<span>Señal</span>
|
||
<strong>Player embebido activo</strong>
|
||
</li>
|
||
<li>
|
||
<span>Horario visible</span>
|
||
<strong>UTC · editable</strong>
|
||
</li>
|
||
<li>
|
||
<span>Contacto fan</span>
|
||
<strong>Formulario conectado</strong>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="note-card mt-4">
|
||
<strong>Consejo</strong>
|
||
<p class="mb-0">Si quieres, en la siguiente iteración puedo conectar metadata real del stream para reemplazar el “Ahora suena” basado en parrilla.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section-block" id="programacion">
|
||
<div class="container">
|
||
<div class="section-heading d-flex flex-wrap align-items-end justify-content-between gap-3 mb-4">
|
||
<div>
|
||
<span class="section-label">Programación</span>
|
||
<h2 class="section-title">Parrilla semanal lista para editar</h2>
|
||
</div>
|
||
<p class="section-copy">Los bloques publicados alimentan la vista pública y el estado editorial de la estación.</p>
|
||
</div>
|
||
<div class="row g-3">
|
||
<?php foreach ($groupedPrograms as $weekday => $items): ?>
|
||
<div class="col-md-6 col-xl-4">
|
||
<article class="panel p-4 h-100">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h3 class="h5 text-white mb-0"><?= h(weekday_name((int) $weekday)) ?></h3>
|
||
<span class="soft-badge"><?= h((string) count($items)) ?> show<?= count($items) === 1 ? '' : 's' ?></span>
|
||
</div>
|
||
<div class="timeline-list">
|
||
<?php foreach ($items as $item): ?>
|
||
<div class="timeline-item">
|
||
<div class="timeline-hour"><?= h(time_label($item['start_time'])) ?></div>
|
||
<div>
|
||
<strong class="d-block text-white"><?= h($item['title']) ?></strong>
|
||
<span class="text-secondary d-block small mb-1"><?= h($item['subtitle']) ?> · <?= h(time_label($item['end_time'])) ?></span>
|
||
<p class="small text-secondary-emphasis mb-0"><?= h($item['body']) ?></p>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section-block" id="djs">
|
||
<div class="container">
|
||
<div class="section-heading d-flex flex-wrap align-items-end justify-content-between gap-3 mb-4">
|
||
<div>
|
||
<span class="section-label">DJs & shows</span>
|
||
<h2 class="section-title">Caras visibles de la emisora</h2>
|
||
</div>
|
||
<p class="section-copy">Cada perfil puede enlazar a redes o biografía corta desde el mismo backend.</p>
|
||
</div>
|
||
<div class="row g-3">
|
||
<?php foreach ($djs as $dj): ?>
|
||
<div class="col-md-6 col-xl-4">
|
||
<article class="panel p-4 h-100 dj-card">
|
||
<div class="avatar-mark mb-4"><?= h(strtoupper(substr($dj['title'], 0, 1))) ?></div>
|
||
<h3 class="h4 text-white mb-1"><?= h($dj['title']) ?></h3>
|
||
<p class="text-secondary mb-3"><?= h($dj['subtitle']) ?></p>
|
||
<p class="text-secondary-emphasis mb-4"><?= h($dj['body']) ?></p>
|
||
<?php if (!empty($dj['meta_url'])): ?>
|
||
<a class="text-link" href="<?= h($dj['meta_url']) ?>" target="_blank" rel="noreferrer">Ver enlace principal</a>
|
||
<?php endif; ?>
|
||
</article>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section-block" id="mensajes">
|
||
<div class="container">
|
||
<div class="section-heading d-flex flex-wrap align-items-end justify-content-between gap-3 mb-4">
|
||
<div>
|
||
<span class="section-label">Mensajes</span>
|
||
<h2 class="section-title">Pide una canción o manda un saludo</h2>
|
||
</div>
|
||
<p class="section-copy">El formulario guarda el pedido en base de datos y lo deja listo para revisión desde el panel admin.</p>
|
||
</div>
|
||
<div class="row g-4">
|
||
<div class="col-lg-7">
|
||
<div class="panel p-4 p-lg-5">
|
||
<form method="post" action="/#mensajes" class="row g-3 needs-validation" novalidate>
|
||
<input type="hidden" name="action" value="fan_message">
|
||
<div class="col-md-6">
|
||
<label class="form-label" for="name">Nombre</label>
|
||
<input class="form-control" id="name" name="name" maxlength="120" required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label" for="email">Email</label>
|
||
<input class="form-control" id="email" name="email" type="email" maxlength="160" required>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label" for="song_request">Pedido musical</label>
|
||
<input class="form-control" id="song_request" name="song_request" maxlength="160" placeholder="Ej. Un tema de Lili Records Sessions">
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label" for="message">Mensaje para cabina</label>
|
||
<textarea class="form-control" id="message" name="message" rows="5" maxlength="1200" required placeholder="Escribe tu saludo, dedicatoria o pedido."></textarea>
|
||
</div>
|
||
<div class="col-12 d-flex flex-wrap gap-3 align-items-center pt-2">
|
||
<button class="btn btn-light" type="submit">Enviar a cabina</button>
|
||
<span class="small text-secondary">Respuesta esperada: el mensaje se guarda con estado inicial <strong class="text-white">new</strong>.</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-5">
|
||
<div class="panel p-4 h-100 d-flex flex-column gap-3">
|
||
<div>
|
||
<span class="section-label">Redes</span>
|
||
<h3 class="h4 text-white mt-2 mb-3">Canales activos</h3>
|
||
</div>
|
||
<?php foreach ($socials as $social): ?>
|
||
<a class="social-row" href="<?= h($social['meta_url'] ?: '#') ?>" target="_blank" rel="noreferrer">
|
||
<span>
|
||
<strong class="d-block text-white"><?= h($social['title']) ?></strong>
|
||
<span class="small text-secondary"><?= h($social['subtitle']) ?></span>
|
||
</span>
|
||
<span class="social-copy"><?= h($social['body']) ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<div class="note-card mt-auto">
|
||
<strong>Admin simple incluido</strong>
|
||
<p class="mb-0">Desde el panel puedes crear programas, DJs y enlaces, además de revisar el detalle de cada mensaje recibido.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="footer-block border-top border-secondary-subtle">
|
||
<div class="container d-flex flex-column flex-lg-row justify-content-between gap-3 py-4">
|
||
<div>
|
||
<strong class="d-block text-white mb-1">Lili Records Radio</strong>
|
||
<span class="text-secondary">Web app inicial con streaming, parrilla, DJs, formulario y panel admin.</span>
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||
<a class="btn btn-sm btn-outline-light" href="/#escuchar">Ir al player</a>
|
||
<a class="btn btn-sm btn-outline-light" href="/admin.php">Panel admin</a>
|
||
<a class="btn btn-sm btn-outline-light" href="/healthz.php">Health</a>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<?php if ($flash): ?>
|
||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||
<div class="toast radio-toast align-items-center text-bg-dark border-0" id="statusToast" role="status" aria-live="polite" aria-atomic="true" data-autoshow="true">
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<span class="toast-indicator <?= h($flash['type']) ?>"></span>
|
||
<?= h($flash['message']) ?>
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Cerrar"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||
<script src="/assets/js/main.js?v=<?= h($assetVersion) ?>"></script>
|
||
</body>
|
||
</html>
|