39417-vm/index.php
2026-03-31 15:38:55 +00:00

399 lines
19 KiB
PHP
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.

<?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();
$logoPath = project_logo_path();
$heroLogoPath = '/assets/pasted-20260331-153158-abeb4b0b.png';
$logoVersion = (string) (@filemtime(__DIR__ . $logoPath) ?: time());
$heroLogoVersion = (string) (@filemtime(__DIR__ . $heroLogoPath) ?: time());
$assetVersion = (string) max(@filemtime(__DIR__ . '/assets/css/custom.css') ?: time(), @filemtime(__DIR__ . '/assets/js/main.js') ?: time(), @filemtime(__DIR__ . $logoPath) ?: time(), @filemtime(__DIR__ . $heroLogoPath) ?: 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="icon" type="image/png" sizes="512x512" href="<?= h($logoPath) ?>?v=<?= h($logoVersion) ?>">
<link rel="apple-touch-icon" href="<?= h($logoPath) ?>?v=<?= h($logoVersion) ?>">
<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-logo">
<img src="<?= h($logoPath) ?>" alt="Logo de Lili Records Radio" width="44" height="44">
</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>
<div class="hero-wordmark-wrap mb-4">
<img class="hero-wordmark" src="<?= h($heroLogoPath) ?>?v=<?= h($heroLogoVersion) ?>" alt="Logo horizontal de Lili Records Radio" width="168" height="95">
</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>