1004 lines
35 KiB
PHP
1004 lines
35 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . "/includes/tracker.php";
|
|
track_visitor();
|
|
|
|
$projectDescription = $_SERVER["PROJECT_DESCRIPTION"] ?? "Lili Records Radio - La mejor música en vivo.";
|
|
$projectImageUrl = $_SERVER["PROJECT_IMAGE_URL"] ?? "assets/images/featured.jpg";
|
|
|
|
// WhatsApp info
|
|
$whatsapp_link = "https://chat.whatsapp.com/DkG96pTzAFO3hvLqmzwmTY";
|
|
|
|
// Program Schedule
|
|
$schedule = [
|
|
[
|
|
"time" => "14:00",
|
|
"name" => "Amanecer Techno",
|
|
"desc" => "Comienza el día con los ritmos más puros y energéticos del techno underground.",
|
|
"image" => "assets/images/programs/techno_sunrise.jpg"
|
|
],
|
|
[
|
|
"time" => "16:30",
|
|
"name" => "Sesiones de House Vocal",
|
|
"desc" => "Una selección exquisita de house melódico con las voces más cautivadoras.",
|
|
"image" => "assets/images/programs/vocal_house.jpg"
|
|
],
|
|
[
|
|
"time" => "19:00",
|
|
"name" => "Invitado Especial Lili",
|
|
"desc" => "Cada semana, un invitado especial nos trae su visión única de la pista de baile.",
|
|
"image" => "assets/images/programs/lili_guest.jpg"
|
|
],
|
|
[
|
|
"time" => "21:00",
|
|
"name" => "Vibras de la Noche",
|
|
"desc" => "Sonidos profundos y envolventes para acompañar la calma de la noche.",
|
|
"image" => "assets/images/programs/deep_night.jpg"
|
|
],
|
|
];
|
|
|
|
$defaultStudioImage = "assets/pasted-20260215-020116-2dc16355.jpg";
|
|
|
|
function get_live_index($schedule) {
|
|
$current = date("H:i");
|
|
$count = count($schedule);
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$start = $schedule[$i]["time"];
|
|
$next = ($i < $count - 1) ? $schedule[$i+1]["time"] : "23:59";
|
|
if ($current >= $start && $current < $next) return $i;
|
|
}
|
|
// Handling night transition
|
|
if ($current >= $schedule[$count-1]["time"] || $current < $schedule[0]["time"]) return $count - 1;
|
|
return -1;
|
|
}
|
|
|
|
$liveIndex = get_live_index($schedule);
|
|
?>
|
|
<!doctype html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Lili Records Radio</title>
|
|
|
|
<!-- Meta tags SEO -->
|
|
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
<meta property="og:title" content="Lili Records Radio" />
|
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
<meta property="twitter:card" content="summary_large_image" />
|
|
|
|
<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@300;400;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
|
|
<style>
|
|
:root {
|
|
--accent-color: #00e676;
|
|
--primary-color: #38bdf8;
|
|
--secondary-color: #f472b6;
|
|
--bg-dark: #0f172a;
|
|
--glass-bg: rgba(255, 255, 255, 0.03);
|
|
--glass-border: rgba(255, 255, 255, 0.08);
|
|
--flash-color: #ffffff;
|
|
}
|
|
|
|
.flash-effect {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 9999;
|
|
background-color: var(--flash-color);
|
|
opacity: 0;
|
|
mix-blend-mode: overlay;
|
|
}
|
|
|
|
body, html {
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
font-family: "Inter", sans-serif;
|
|
color: #ffffff;
|
|
background-color: #0f172a;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
@keyframes shake {
|
|
0% { transform: translate(0, 0); }
|
|
25% { transform: translate(-8px, 8px); }
|
|
50% { transform: translate(8px, -8px); }
|
|
75% { transform: translate(-8px, -8px); }
|
|
100% { transform: translate(0, 0); }
|
|
}
|
|
|
|
.shake {
|
|
animation: shake 0.15s cubic-bezier(.36,.07,.19,.97) both;
|
|
}
|
|
|
|
.background-container {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -2;
|
|
background-image: url("assets/pasted-20260215-011439-25779668.jpg?v=<?php echo time(); ?>");
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
animation: ken-burns 60s ease-in-out infinite alternate;
|
|
transition: background-image 2s ease-in-out, transform 0.1s ease-out;
|
|
transform: scale(var(--pulse-intensity, 1));
|
|
}
|
|
|
|
@keyframes ken-burns {
|
|
0% { transform: scale(var(--pulse-intensity, 1)); }
|
|
100% { transform: scale(calc(var(--pulse-intensity, 1) * 1.1)); }
|
|
}
|
|
|
|
.background-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -1;
|
|
background: radial-gradient(circle at top right, rgba(56, 189, 248, 0.1), transparent 40%),
|
|
radial-gradient(circle at bottom left, rgba(244, 114, 182, 0.1), transparent 40%),
|
|
linear-gradient(135deg, rgba(15, 23, 42, 0.4) 0%, rgba(15, 23, 42, 0.7) 100%);
|
|
transition: opacity 0.1s ease-out;
|
|
opacity: calc(0.7 + (var(--pulse-intensity, 1) - 1) * 2);
|
|
}
|
|
|
|
/* Floating Microphones Effect */
|
|
.floating-mic {
|
|
position: absolute;
|
|
color: var(--mic-color, rgba(255, 255, 255, 0.1));
|
|
font-size: 3.5rem;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
animation: float-and-rotate 25s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
filter: drop-shadow(0 0 15px var(--mic-color));
|
|
}
|
|
|
|
@keyframes float-and-rotate {
|
|
0% {
|
|
transform: translate(0, 0) rotate(0deg) scale(0.8);
|
|
opacity: 0;
|
|
}
|
|
15% { opacity: 0.4; }
|
|
85% { opacity: 0.4; }
|
|
100% {
|
|
transform: translate(var(--move-x), var(--move-y)) rotate(360deg) scale(1.4);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.app-container {
|
|
display: flex;
|
|
width: 100%;
|
|
max-width: 1800px;
|
|
height: auto;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 2rem;
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.side-image-section {
|
|
width: 100%;
|
|
max-width: 320px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.side-image-card {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 40px;
|
|
padding: 1rem;
|
|
box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.4);
|
|
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.side-image-card:hover {
|
|
transform: translateY(-5px) rotate(1deg);
|
|
border-color: var(--secondary-color);
|
|
}
|
|
|
|
.side-img {
|
|
width: 100%;
|
|
height: auto;
|
|
border-radius: 32px;
|
|
display: block;
|
|
}
|
|
|
|
.app-content {
|
|
display: flex;
|
|
gap: 3rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
width: 100%;
|
|
}
|
|
|
|
.studio-section {
|
|
width: 100%;
|
|
max-width: 480px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.studio-card {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 40px;
|
|
padding: 1rem;
|
|
box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.4);
|
|
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.studio-tag {
|
|
position: absolute;
|
|
top: 2.2rem;
|
|
left: 2.2rem;
|
|
background: rgba(15, 23, 42, 0.5);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
padding: 0.6rem 1.2rem;
|
|
border-radius: 100px;
|
|
font-size: 0.65rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: #fff;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
z-index: 5;
|
|
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
|
transition: all 0.4s ease;
|
|
}
|
|
|
|
.studio-card:hover {
|
|
transform: translateY(-5px) rotate(-1deg);
|
|
border-color: rgba(255, 255, 255, 0.15);
|
|
background: rgba(255, 255, 255, 0.04);
|
|
}
|
|
|
|
.studio-card:hover .studio-tag {
|
|
transform: scale(1.05);
|
|
background: rgba(15, 23, 42, 0.7);
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.studio-photo {
|
|
width: 100%;
|
|
height: auto;
|
|
border-radius: 32px;
|
|
display: block;
|
|
filter: saturate(1.1) brightness(1.05);
|
|
}
|
|
|
|
.player-section {
|
|
width: 100%;
|
|
max-width: 420px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
perspective: 1000px;
|
|
}
|
|
|
|
/* Upcoming Programs Styles */
|
|
.upcoming-section {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.small-glass {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
border-radius: 36px;
|
|
padding: 2rem;
|
|
box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.3);
|
|
transition: all 0.5s ease;
|
|
}
|
|
|
|
.upcoming-section h2 {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin: 0 0 1.5rem;
|
|
color: var(--primary-color);
|
|
letter-spacing: 0.05em;
|
|
text-transform: uppercase;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.8rem;
|
|
}
|
|
|
|
.program-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.2rem;
|
|
}
|
|
|
|
.program-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.2rem;
|
|
padding-bottom: 1.2rem;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.program-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.program-description {
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
opacity: 0;
|
|
font-size: 0.75rem;
|
|
color: rgba(255, 255, 255, 0.45);
|
|
line-height: 1.5;
|
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
padding-left: 4.1rem;
|
|
}
|
|
|
|
.program-item.is-expanded .program-description {
|
|
max-height: 100px;
|
|
opacity: 1;
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.program-item.is-live {
|
|
background: rgba(0, 230, 118, 0.08);
|
|
border-bottom: 1px solid rgba(0, 230, 118, 0.2);
|
|
padding: 1rem;
|
|
margin: 0 -1rem;
|
|
border-radius: 16px;
|
|
position: relative;
|
|
}
|
|
|
|
.program-item.is-live::after {
|
|
content: "VIVO";
|
|
position: absolute;
|
|
right: 1rem;
|
|
top: 1.2rem;
|
|
font-size: 0.5rem;
|
|
font-weight: 800;
|
|
color: var(--accent-color);
|
|
border: 1px solid var(--accent-color);
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
letter-spacing: 0.1em;
|
|
animation: pulse-glow 2s infinite;
|
|
}
|
|
|
|
.program-time {
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
color: rgba(255, 255, 255, 0.4);
|
|
min-width: 50px;
|
|
}
|
|
|
|
.program-name {
|
|
font-size: 0.9rem;
|
|
font-weight: 400;
|
|
color: rgba(255, 255, 255, 0.85);
|
|
letter-spacing: 0.01em;
|
|
}
|
|
|
|
.glass-card {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
backdrop-filter: blur(40px);
|
|
-webkit-backdrop-filter: blur(40px);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
border-radius: 60px;
|
|
padding: 4rem 3rem;
|
|
box-shadow: 0 60px 120px -30px rgba(0, 0, 0, 0.6);
|
|
text-align: center;
|
|
transition: all 0.8s ease;
|
|
}
|
|
|
|
.brand h1 {
|
|
font-size: 2.8rem;
|
|
font-weight: 700;
|
|
margin: 0 0 0.5rem;
|
|
color: #fff;
|
|
letter-spacing: -0.04em;
|
|
text-transform: lowercase;
|
|
}
|
|
|
|
.brand-logo {
|
|
width: 110px;
|
|
height: auto;
|
|
margin: 0 auto 2rem;
|
|
display: block;
|
|
filter: drop-shadow(0 10px 20px rgba(0,0,0,0.3));
|
|
}
|
|
|
|
.brand p {
|
|
font-size: 0.95rem;
|
|
font-weight: 300;
|
|
opacity: 0.6;
|
|
margin-bottom: 3rem;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.now-playing-container {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border-radius: 32px;
|
|
padding: 3rem 2rem;
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
margin-bottom: 2.5rem;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.5s ease;
|
|
}
|
|
|
|
.track-title {
|
|
font-weight: 300;
|
|
font-size: 1.2rem;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
letter-spacing: 0.02em;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.play-btn {
|
|
width: 90px;
|
|
height: 90px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
border: none;
|
|
color: var(--bg-dark);
|
|
font-size: 3rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 30px 60px -15px rgba(0, 0, 0, 0.5);
|
|
transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.play-btn:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 0 40px rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.play-btn.is-playing {
|
|
background: #ff3d00;
|
|
color: #fff;
|
|
box-shadow: 0 0 50px rgba(255, 61, 0, 0.4);
|
|
animation: pulse-red 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse-red {
|
|
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 61, 0, 0.7); }
|
|
70% { transform: scale(1.05); box-shadow: 0 0 0 20px rgba(255, 61, 0, 0); }
|
|
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 61, 0, 0); }
|
|
}
|
|
|
|
.volume-container {
|
|
width: 100%;
|
|
max-width: 180px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.2rem;
|
|
margin: 2.5rem auto 0;
|
|
opacity: 0.5;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.volume-container:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
.volume-slider {
|
|
flex: 1;
|
|
height: 3px;
|
|
-webkit-appearance: none;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.volume-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 12px;
|
|
height: 12px;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
#visualizer {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 80px;
|
|
opacity: 0.8;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.track-status {
|
|
font-size: 0.6rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3em;
|
|
color: var(--primary-color);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.6rem;
|
|
background: rgba(56, 189, 248, 0.08);
|
|
padding: 0.5rem 1.2rem;
|
|
border-radius: 100px;
|
|
width: fit-content;
|
|
margin: 0 auto 1.2rem;
|
|
border: 1px solid rgba(56, 189, 248, 0.15);
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.live-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
background-color: var(--primary-color);
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 10px var(--primary-color);
|
|
animation: pulse-glow 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse-glow {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.4); opacity: 0.8; }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
|
|
.payment-section {
|
|
margin-top: 2rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.qr-container img {
|
|
width: 90px;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.whatsapp-float {
|
|
position: fixed;
|
|
bottom: 2rem;
|
|
right: 2rem;
|
|
width: 60px;
|
|
height: 60px;
|
|
background: var(--accent-color);
|
|
border-radius: 50%;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 2rem;
|
|
z-index: 100;
|
|
}
|
|
|
|
@media (max-width: 992px) {
|
|
body, html { overflow-y: auto; height: auto; }
|
|
.app-container { padding: 1rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="flash-effect"></div>
|
|
<div class="background-container"></div>
|
|
<div class="background-overlay"></div>
|
|
<div id="mics-background"></div>
|
|
|
|
<div class="app-container">
|
|
<div class="app-content">
|
|
<div class="side-image-section">
|
|
<div class="side-image-card">
|
|
<img src="./assets/pasted-20260215-024434-5730389a.jpg" alt="Side Image" class="side-img">
|
|
</div>
|
|
</div>
|
|
<section class="studio-section">
|
|
<div class="studio-card">
|
|
<div class="studio-tag">
|
|
<span class="live-dot"></span> <span id="studio-program-name"><?= ($liveIndex !== -1) ? htmlspecialchars($schedule[$liveIndex]["name"]) : "Directo desde el estudio" ?></span>
|
|
</div>
|
|
<img id="studio-photo" src="<?= ($liveIndex !== -1 && !empty($schedule[$liveIndex]["image"])) ? $schedule[$liveIndex]["image"] : $defaultStudioImage ?>?v=<?php echo time(); ?>" alt="Studio Live" class="studio-photo">
|
|
</div>
|
|
</section>
|
|
|
|
<section class="player-section">
|
|
<div class="glass-card">
|
|
<header class="brand">
|
|
<img src="assets/pasted-20260214-203540-699a2e6a.png" alt="Logo" class="brand-logo">
|
|
<h1>Lili Records</h1>
|
|
<p>La sintonía que eleva tus sentidos.</p>
|
|
</header>
|
|
<div class="now-playing-container">
|
|
<canvas id="visualizer"></canvas>
|
|
<div class="track-status"><span class="live-dot"></span> AL AIRE</div>
|
|
<div id="track-title" class="track-title">Conectando...</div>
|
|
</div>
|
|
<button class="play-btn" onclick="togglePlay()"><i id="play-icon" class="bi bi-play-fill"></i></button>
|
|
<div class="volume-container">
|
|
<i class="bi bi-volume-down"></i>
|
|
<input type="range" class="volume-slider" min="0" max="1" step="0.01" value="1" oninput="changeVolume(this.value)">
|
|
<i class="bi bi-volume-up"></i>
|
|
</div>
|
|
<div class="payment-section">
|
|
<p style="font-size:0.7rem; margin-bottom:1rem;">APOYA NUESTRA VIBRA</p>
|
|
<div class="qr-container"><img src="assets/pasted-20260214-203505-f7d808c3.jpg" alt="QR"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="upcoming-section">
|
|
<div class="small-glass">
|
|
<h2>Próximos Programas</h2>
|
|
<ul class="program-list">
|
|
<?php foreach ($schedule as $index => $item): ?>
|
|
<li class="program-item <?= ($index === $liveIndex) ? "is-live" : "" ?>" onclick="toggleDescription(this)">
|
|
<div class="program-header">
|
|
<span class="program-time"><?= $item["time"] ?></span>
|
|
<span class="program-name"><?= $item["name"] ?></span>
|
|
</div>
|
|
<div class="program-description"><?= htmlspecialchars($item["desc"]) ?></div>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<a href="<?= $whatsapp_link ?>" target="_blank" class="whatsapp-float"><i class="bi bi-whatsapp"></i></a>
|
|
<audio id="radio-audio" src="https://listen.radioking.com/radio/828046/stream/897251" preload="none" crossorigin="anonymous"></audio>
|
|
|
|
<script>
|
|
// Mics background with vibrant colors
|
|
const micBg = document.getElementById("mics-background");
|
|
const micColors = ["#00e676", "#38bdf8", "#f472b6", "#fbbf24", "#fb7185", "#2dd4bf"];
|
|
for (let i = 0; i < 18; i++) {
|
|
const mic = document.createElement("i");
|
|
mic.className = "bi bi-mic-fill floating-mic";
|
|
mic.style.left = Math.random() * 100 + "vw";
|
|
mic.style.top = Math.random() * 100 + "vh";
|
|
mic.style.setProperty("--mic-color", micColors[Math.floor(Math.random() * micColors.length)]);
|
|
mic.style.setProperty("--move-x", (Math.random() - 0.5) * 600 + "px");
|
|
mic.style.setProperty("--move-y", (Math.random() - 0.5) * 600 + "px");
|
|
mic.style.animationDelay = Math.random() * -25 + "s";
|
|
micBg.appendChild(mic);
|
|
floatingMics.push(mic);
|
|
}
|
|
|
|
const audio = document.getElementById("radio-audio");
|
|
const playIcon = document.getElementById("play-icon");
|
|
const trackTitle = document.getElementById("track-title");
|
|
const bgContainer = document.querySelector(".background-container");
|
|
const appContainer = document.querySelector(".app-container");
|
|
const canvas = document.getElementById("visualizer");
|
|
const canvasCtx = canvas.getContext("2d");
|
|
|
|
let currentTrackTitle = "";
|
|
let audioCtx;
|
|
let analyser;
|
|
let source;
|
|
let dataArray;
|
|
let freqDataArray;
|
|
let animationId;
|
|
let floatingMics = [];
|
|
let particles = [];
|
|
let waveColor = "rgba(56, 189, 248, 0.8)"; // Default primary
|
|
let waveGlow = "rgba(56, 189, 248, 0.4)";
|
|
|
|
function hexToRgb(hex) {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16)
|
|
} : null;
|
|
}
|
|
|
|
function getComplementaryColor(rgbaStr) {
|
|
// Extract r, g, b from rgba(r, g, b, a)
|
|
const match = rgbaStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
if (!match) return "#ffffff";
|
|
const r = 255 - parseInt(match[1]);
|
|
const g = 255 - parseInt(match[2]);
|
|
const b = 255 - parseInt(match[3]);
|
|
return `rgb(${r}, ${g}, ${b})`;
|
|
}
|
|
|
|
const programColors = {
|
|
"Amanecer Techno": { main: "rgba(0, 230, 118, 0.8)", glow: "rgba(0, 230, 118, 0.4)" },
|
|
"Sesiones de House Vocal": { main: "rgba(244, 114, 182, 0.8)", glow: "rgba(244, 114, 182, 0.4)" },
|
|
"Invitado Especial Lili": { main: "rgba(251, 191, 36, 0.8)", glow: "rgba(251, 191, 36, 0.4)" },
|
|
"Vibras de la Noche": { main: "rgba(129, 140, 248, 0.8)", glow: "rgba(129, 140, 248, 0.4)" }
|
|
};
|
|
|
|
function initAudio() {
|
|
if (!audioCtx) {
|
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
analyser = audioCtx.createAnalyser();
|
|
source = audioCtx.createMediaElementSource(audio);
|
|
source.connect(analyser);
|
|
analyser.connect(audioCtx.destination);
|
|
analyser.fftSize = 2048;
|
|
const bufferLength = analyser.frequencyBinCount;
|
|
dataArray = new Uint8Array(bufferLength);
|
|
freqDataArray = new Uint8Array(bufferLength);
|
|
}
|
|
}
|
|
|
|
function drawWave() {
|
|
animationId = requestAnimationFrame(drawWave);
|
|
analyser.getByteTimeDomainData(dataArray);
|
|
analyser.getByteFrequencyData(freqDataArray);
|
|
|
|
// Calculate average volume for pulse effect
|
|
let sum = 0;
|
|
for (let i = 0; i < 30; i++) { // Focus on bass frequencies
|
|
sum += freqDataArray[i];
|
|
}
|
|
const average = sum / 30;
|
|
const boost = (average / 128.0); // Factor between 0 and 2
|
|
|
|
// Flash Effect Detection (Drop detection)
|
|
const flashEl = document.querySelector(".flash-effect");
|
|
if (boost > 1.6 && Math.random() > 0.96) {
|
|
const useWhite = Math.random() > 0.5;
|
|
const flashColor = useWhite ? "#ffffff" : getComplementaryColor(waveColor);
|
|
document.documentElement.style.setProperty("--flash-color", flashColor);
|
|
flashEl.style.opacity = "0.3";
|
|
|
|
// Shake UI effect
|
|
appContainer.classList.remove("shake");
|
|
void appContainer.offsetWidth; // Trigger reflow
|
|
appContainer.classList.add("shake");
|
|
|
|
setTimeout(() => {
|
|
flashEl.style.transition = "opacity 0.4s ease-out";
|
|
flashEl.style.opacity = "0";
|
|
}, 40);
|
|
setTimeout(() => { flashEl.style.transition = ""; }, 500);
|
|
}
|
|
|
|
// Update CSS variables for background pulse
|
|
document.body.style.setProperty("--pulse-intensity", (1 + boost * 0.15).toString());
|
|
floatingMics.forEach(mic => {
|
|
mic.style.transform = `translate(var(--move-x), var(--move-y)) rotate(360deg) scale(${1 + boost * 0.5})`;
|
|
});
|
|
|
|
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// --- Particle System (Fire/Sparks with Smoke Trail) ---
|
|
if (boost > 1.2) {
|
|
const particleCount = Math.floor(boost * 2);
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const x = Math.random() * canvas.width;
|
|
const dataIdx = Math.floor((x / canvas.width) * dataArray.length);
|
|
const v = dataArray[dataIdx] / 128.0;
|
|
const amplitude = (v - 1) * (1.5 + boost * 2) + 1;
|
|
const y = amplitude * canvas.height / 2;
|
|
|
|
particles.push({
|
|
x: x,
|
|
y: y,
|
|
vx: (Math.random() - 0.5) * 4,
|
|
vy: -Math.random() * 6 - (boost * 4),
|
|
alpha: 1,
|
|
size: Math.random() * 3 + 1,
|
|
color: waveColor.replace("0.8", "1"),
|
|
trail: []
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update and draw particles
|
|
for (let i = particles.length - 1; i >= 0; i--) {
|
|
const p = particles[i];
|
|
|
|
// Add to trail
|
|
p.trail.push({ x: p.x, y: p.y, alpha: p.alpha });
|
|
if (p.trail.length > 10) p.trail.shift();
|
|
|
|
p.x += p.vx;
|
|
p.y += p.vy;
|
|
p.vy += 0.22; // Gravity
|
|
p.alpha -= 0.015; // Slower fade for smoke effect
|
|
|
|
if (p.alpha <= 0) {
|
|
particles.splice(i, 1);
|
|
continue;
|
|
}
|
|
|
|
// Draw Trail (Smoke)
|
|
p.trail.forEach((point, idx) => {
|
|
canvasCtx.beginPath();
|
|
canvasCtx.arc(point.x, point.y, p.size * (idx / p.trail.length), 0, Math.PI * 2);
|
|
canvasCtx.fillStyle = p.color.replace("1)", `${point.alpha * 0.2})`);
|
|
canvasCtx.fill();
|
|
});
|
|
|
|
canvasCtx.save();
|
|
canvasCtx.globalAlpha = p.alpha;
|
|
canvasCtx.fillStyle = p.color;
|
|
canvasCtx.shadowBlur = 5;
|
|
canvasCtx.shadowColor = p.color;
|
|
canvasCtx.beginPath();
|
|
canvasCtx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
canvasCtx.fill();
|
|
canvasCtx.restore();
|
|
}
|
|
// --- End Particle System ---
|
|
|
|
// Dynamic glow based on volume
|
|
canvasCtx.shadowBlur = 10 + (boost * 20);
|
|
canvasCtx.shadowColor = waveGlow;
|
|
|
|
canvasCtx.lineWidth = 2 + (boost * 3);
|
|
canvasCtx.strokeStyle = waveColor;
|
|
canvasCtx.beginPath();
|
|
|
|
const sliceWidth = canvas.width * 1.0 / dataArray.length;
|
|
let x = 0;
|
|
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
const v = dataArray[i] / 128.0;
|
|
// Scale amplitude by boost factor for more intensity
|
|
const amplitude = (v - 1) * (1.5 + boost * 2) + 1;
|
|
const y = amplitude * canvas.height / 2;
|
|
|
|
if (i === 0) canvasCtx.moveTo(x, y);
|
|
else canvasCtx.lineTo(x, y);
|
|
|
|
x += sliceWidth;
|
|
}
|
|
|
|
canvasCtx.lineTo(canvas.width, canvas.height / 2);
|
|
canvasCtx.stroke();
|
|
|
|
canvasCtx.shadowBlur = 0;
|
|
}
|
|
|
|
function togglePlay() {
|
|
const btn = document.querySelector(".play-btn");
|
|
initAudio();
|
|
|
|
if (audio.paused) {
|
|
if (audioCtx.state === "suspended") audioCtx.resume();
|
|
audio.play();
|
|
playIcon.className = "bi bi-pause-fill";
|
|
btn.classList.add("is-playing");
|
|
canvas.width = canvas.offsetWidth;
|
|
canvas.height = canvas.offsetHeight;
|
|
drawWave();
|
|
}
|
|
else {
|
|
audio.pause();
|
|
playIcon.className = "bi bi-play-fill";
|
|
btn.classList.remove("is-playing");
|
|
cancelAnimationFrame(animationId);
|
|
}
|
|
}
|
|
|
|
function changeVolume(val) { audio.volume = val; }
|
|
|
|
async function updateBackgroundImage(query = "abstract music") {
|
|
try {
|
|
const res = await fetch(`api/pexels.php?query=${encodeURIComponent(query)}`);
|
|
const data = await res.json();
|
|
if (data.success && data.url) {
|
|
const img = new Image();
|
|
img.src = data.url;
|
|
img.onload = () => { bgContainer.style.backgroundImage = `url("${data.url}")`; };
|
|
}
|
|
} catch (e) { console.error(e); }
|
|
}
|
|
|
|
async function updateMetadata() {
|
|
try {
|
|
const res = await fetch("https://www.radioking.com/widgets/currenttrack.php?radio=828046&format=json");
|
|
const data = await res.json();
|
|
if (data && data.title) {
|
|
let full = data.artist ? `${data.artist} - ${data.title}` : data.title;
|
|
if (currentTrackTitle !== full) {
|
|
currentTrackTitle = full;
|
|
trackTitle.textContent = full;
|
|
document.title = `▶ ${full} | Lili Records Radio`;
|
|
updateBackgroundImage(data.artist ? `${data.artist} music` : "abstract music background");
|
|
}
|
|
}
|
|
} catch (e) { console.error(e); }
|
|
}
|
|
|
|
function toggleDescription(el) {
|
|
const was = el.classList.contains("is-expanded");
|
|
document.querySelectorAll(".program-item").forEach(i => i.classList.remove("is-expanded"));
|
|
if (!was) el.classList.add("is-expanded");
|
|
}
|
|
|
|
setInterval(updateMetadata, 30000);
|
|
updateMetadata();
|
|
|
|
function updateHighlight() {
|
|
const now = new Date();
|
|
const cur = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
|
|
const schedule = <?= json_encode($schedule) ?>;
|
|
let idx = -1;
|
|
for (let i = 0; i < schedule.length; i++) {
|
|
const start = schedule[i].time;
|
|
const next = schedule[i+1] ? schedule[i+1].time : "23:59";
|
|
if (cur >= start && cur < next) { idx = i; break; }
|
|
}
|
|
if (idx === -1) idx = schedule.length - 1;
|
|
|
|
document.querySelectorAll(".program-item").forEach((item, i) => {
|
|
item.classList.toggle("is-live", i === idx);
|
|
});
|
|
|
|
if (idx !== -1) {
|
|
const programName = schedule[idx].name;
|
|
if (programColors[programName]) {
|
|
waveColor = programColors[programName].main;
|
|
waveGlow = programColors[programName].glow;
|
|
}
|
|
}
|
|
|
|
const stName = document.getElementById("studio-program-name");
|
|
const stPhoto = document.getElementById("studio-photo");
|
|
if (stName && idx !== -1) {
|
|
stName.textContent = schedule[idx].name;
|
|
if (stPhoto && !stPhoto.src.includes(schedule[idx].image)) stPhoto.src = schedule[idx].image;
|
|
}
|
|
}
|
|
setInterval(updateHighlight, 60000);
|
|
updateHighlight();
|
|
</script>
|
|
</body>
|
|
</html>
|