<?php // index.php β Home (Bulma + RadioMouse mascot theme) declare(strict_types=1); require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/includes/helpers.php'; require_once __DIR__ . '/includes/settings.php'; $pdo = db(); $siteName = get_setting('site_name', 'RadioMouse'); /** * β NEW: Read Tagline from settings (Admin β Site Settings β Tagline) * Fallback keeps your original default. */ $siteTagline = get_setting('site_tagline', 'Lightning-fast internet radio'); /* ----------------------------------------- Mascot slot: SVG/PNG if available ------------------------------------------ */ $mascotUrl = null; $svgPath = __DIR__ . '/assets/img/radiomouse-mascot.svg'; $pngPath = __DIR__ . '/assets/img/radiomouse-mascot.png'; if (is_file($svgPath)) { $mascotUrl = '/assets/img/radiomouse-mascot.svg'; } elseif (is_file($pngPath)) { $mascotUrl = '/assets/img/radiomouse-mascot.png'; } /* ----------------------------------------- DATA: Featured/popular + Recently added ------------------------------------------ */ // Featured & popular $st = $pdo->query(" SELECT id, slug, name, logo, country, genre, bitrate FROM stations WHERE is_active = 1 ORDER BY featured DESC, clicks DESC, name ASC LIMIT 36 "); $stationsFeatured = $st->fetchAll(PDO::FETCH_ASSOC); // Recently added $st2 = $pdo->query(" SELECT id, slug, name, logo, country, genre, bitrate FROM stations WHERE is_active = 1 ORDER BY created_at DESC LIMIT 24 "); $stationsRecent = $st2->fetchAll(PDO::FETCH_ASSOC); /* ----------------------------------------- SEO ------------------------------------------ */ $page = 'home'; /** * β UPDATED: SEO now uses settings tagline (fallback same as before) * Keeping your format: "SiteName β Tagline" */ $metaTitle = $siteName . ' β ' . $siteTagline; $metaDesc = 'Discover and play ' . $siteTagline . ' with favorites in your browser.'; /* ----------------------------------------- Helper: Render a station card ------------------------------------------ */ if (!function_exists('rm_render_station_card')) { function rm_render_station_card(array $s): void { $id = (int)($s['id'] ?? 0); $slug = isset($s['slug']) && $s['slug'] !== '' ? (string)$s['slug'] : ''; $href = $slug !== '' ? '/station/' . h($slug) : '/station/' . $id; $logoRaw = (string)($s['logo'] ?? ''); $logo = $logoRaw; if ($logo !== '' && strncasecmp($logo, 'http://', 7) === 0) { $logo = 'https://' . substr($logo, 7); } $country = trim((string)($s['country'] ?? '')); $bitrate = (int)($s['bitrate'] ?? 0); $name = (string)($s['name'] ?? ''); ?> <div class="column is-one-quarter-desktop is-one-third-tablet is-half-mobile"> <div class="rm-card rm-card-home" data-station-id="<?= $id ?>" data-name="<?= h($name) ?>" data-logo="<?= h($logo) ?>" data-url="<?= h($href) ?>"> <!-- Play button (no navigation) --> <button class="rm-play-btn" data-play="<?= $id ?>" onclick="event.preventDefault(); event.stopPropagation();"> βΆ </button> <a href="<?= $href ?>" class="rm-card-link"> <div class="rm-card-thumb rm-card-thumb-big"> <?php if (!empty($logo)): ?> <img src="<?= h($logo) ?>" alt="<?= h($name) ?>" loading="lazy"> <?php else: ?> <div class="rm-thumb-placeholder"> <?= h(mb_strtoupper(mb_substr($name, 0, 1))) ?> </div> <?php endif; ?> </div> <div class="rm-card-info"> <div class="rm-card-title-row"> <div class="rm-card-title" title="<?= h($name) ?>"> <?= h($name) ?> </div> <!-- Favorite toggle (no navigation) --> <button class="rm-fav-btn" data-fav="<?= $id ?>" onclick="event.preventDefault(); event.stopPropagation();"> β‘ </button> </div> <div class="rm-card-meta-pill"> <?php if ($country !== ''): ?> <span class="rm-pill rm-pill-country" title="<?= h($country) ?>"> <?= h($country) ?> </span> <?php endif; ?> <?php if ($bitrate > 0): ?> <span class="rm-pill rm-pill-bitrate"> <?= $bitrate ?> kbps </span> <?php endif; ?> </div> </div> </a> </div> </div> <?php } } ob_start(); ?> <style> /* ============================================================ HERO β more colorful, pro look ============================================================ */ .rm-hero.rm-hero-mouse { background: radial-gradient(circle at top left, #111827, #020617 55%, #000000 100%); border-bottom: 1px solid rgba(31,41,55,0.9); } .rm-hero.rm-hero-mouse .hero-body { padding-top: 3.2rem; padding-bottom: 3.1rem; } .rm-hero-tagline { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-bottom: 0.85rem; } .rm-mouse-chip { display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.15rem 0.7rem 0.15rem 0.2rem; border-radius: 999px; background: radial-gradient(circle at 0% 0%, #bbf7d0, #22c55e 45%, #15803d 100%); color: #022c22; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; box-shadow: 0 0 18px rgba(74,222,128,0.7); } .rm-mouse-icon { width: 24px; height: 24px; border-radius: 999px; background: #022c22; display: flex; align-items: center; justify-content: center; overflow: hidden; } .rm-mascot-chip-img { width: 100%; height: 100%; object-fit: cover; } .rm-mouse-chip-sub { font-size: 0.8rem; color: #9ca3af; } .rm-hero-title { font-size: 2.2rem; line-height: 1.15; font-weight: 800; color: #f9fafb; margin-bottom: 0.75rem; text-shadow: 0 6px 24px rgba(0,0,0,0.8); } .rm-hero-sub { max-width: 32rem; color: #d1d5db; font-size: 0.95rem; } /* Right card (mascot + copy) */ .rm-mascot-hero-card { background: radial-gradient(circle at top left, #111827, #020617); border-radius: 20px; padding: 0.9rem 1rem; display: flex; gap: 0.75rem; align-items: center; border: 1px solid rgba(55,65,81,0.9); box-shadow: 0 18px 45px rgba(15,23,42,0.9), 0 0 0 1px rgba(15,23,42,0.8); margin-bottom: 1.1rem; } .rm-mascot-hero-img-wrap { flex: 0 0 64px; width: 64px; height: 64px; border-radius: 24px; background: radial-gradient(circle at 30% 0%, #f97316, #facc15 45%, #4ade80 100%); padding: 3px; display: flex; align-items: center; justify-content: center; } .rm-mascot-img-main { width: 100%; height: 100%; border-radius: 20px; object-fit: cover; background: #020617; } .rm-mascot-fallback { font-size: 2.1rem; } .rm-mascot-hero-text { flex: 1; } .rm-mascot-badge { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.14em; color: #bbf7d0; margin-bottom: 0.2rem; } .rm-mascot-copy { font-size: 0.85rem; color: #e5e7eb; } /* Search box styling */ .rm-search-wrap { margin-top: 0.9rem; position: relative; } .rm-search-label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.16em; color: #9ca3af; margin-bottom: 0.25rem; } .rm-search-input { background: radial-gradient(circle at top, #020617, #020617); border-radius: 999px; border: 1px solid rgba(75,85,99,0.9); color: #f9fafb; padding-left: 2.5rem !important; box-shadow: 0 10px 30px rgba(0,0,0,0.65); } .rm-search-input::placeholder { color: #6b7280; } .rm-search-input:focus { border-color: #22c55e; box-shadow: 0 0 0 1px rgba(34,197,94,0.9), 0 0 35px rgba(34,197,94,0.6); } .rm-search-wrap .icon.is-left { color: #9ca3af; } .rm-search-help { margin-top: 0.35rem; font-size: 0.78rem; color: #9ca3af; } /* Suggest dropdown */ .rm-suggest-box { margin-top: 0.25rem; background: #020617; border-radius: 14px; border: 1px solid rgba(55,65,81,0.9); box-shadow: 0 24px 50px rgba(0,0,0,0.9); max-height: 260px; overflow-y: auto; display: none; position: absolute; width: 100%; z-index: 40; } .rm-suggest-item { display: flex; justify-content: space-between; align-items: center; padding: 0.45rem 0.7rem; font-size: 0.82rem; color: #e5e7eb; text-decoration: none; border-bottom: 1px solid rgba(31,41,55,0.9); } .rm-suggest-item:last-child { border-bottom: none; } .rm-suggest-item:hover { background: rgba(15,23,42,0.9); } .rm-suggest-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .rm-suggest-meta { font-size: 0.75rem; color: #9ca3af; margin-left: 0.5rem; } /* ============================================================ STATION CARD THUMBNAILS β FIX BLUR/CROP ============================================================ */ /* Big thumbs on home cards: keep aspect ratio, no cropping */ .rm-card-thumb.rm-card-thumb-big { background: radial-gradient(circle at top left, #111827, #020617); border-radius: 18px; padding: 0.35rem; display: flex; align-items: center; justify-content: center; overflow: hidden; min-height: 150px; } /* Override any global "cover" rules just for big thumbs */ .rm-card-thumb.rm-card-thumb-big img { width: auto !important; height: auto !important; max-width: 100%; max-height: 100%; object-fit: contain; image-rendering: auto; } /* Placeholder letter when there is no logo */ .rm-thumb-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 2.4rem; color: var(--rm-muted, #9ca3af); } /* ============================================================ SECTION TITLES (Featured / Recently added) ============================================================ */ .rm-page-title { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.12em; font-weight: 700; display: inline-flex; align-items: center; gap: 0.6rem; color: #e5e7eb; opacity: 0.95; margin-bottom: 0.75rem; } .rm-page-title::before { content: ""; width: 9px; height: 9px; border-radius: 999px; background: radial-gradient(circle at 30% 30%, #22c55e, #16a34a, #0f766e); box-shadow: 0 0 12px rgba(34,197,94,0.7); } .rm-section-tight { padding-top: 2.1rem; padding-bottom: 2.1rem; } .rm-section-alt { background: radial-gradient(circle at top, #020617, #020617 50%, #000 100%); border-top: 1px solid rgba(15,23,42,0.9); } .rm-pill-hero { background: rgba(34,197,94,0.12); color: #bbf7d0; border: 1px solid rgba(34,197,94,0.5); } .rm-pill-hero-alt { background: rgba(96,165,250,0.12); color: #dbeafe; border: 1px solid rgba(59,130,246,0.5); } /* ============================================================ HOME GRID β MOBILE OPTIMIZATION ============================================================ */ @media (max-width: 768px) { .rm-hero.rm-hero-mouse .hero-body { padding-top: 2.3rem; padding-bottom: 2.4rem; } .rm-hero-title { font-size: 1.7rem; } .rm-mascot-hero-card { margin-top: 1.2rem; } .rm-home-grid { display: flex; flex-wrap: wrap; margin-left: -0.35rem; margin-right: -0.35rem; } .rm-home-grid > .column.is-half-mobile { flex: 0 0 50%; max-width: 50%; padding-left: 0.35rem; padding-right: 0.35rem; margin-bottom: 0.7rem; } .rm-card.rm-card-home { padding: 0.45rem 0.5rem 0.55rem; border-radius: 12px; } /* keep same βno cropβ logic, but a bit shorter cards */ .rm-card-home .rm-card-thumb.rm-card-thumb-big { min-height: 110px; padding: 0.3rem; } .rm-card-home .rm-card-title-row { margin-top: 0.35rem; } .rm-card-home .rm-card-title { font-size: 0.78rem; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .rm-card-home .rm-card-meta-pill { margin-top: 0.25rem; display: flex; flex-wrap: wrap; gap: 4px; } .rm-card-home .rm-pill { font-size: 0.62rem; padding: 2px 6px; } .rm-card-home .rm-pill.rm-pill-country { max-width: 80%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .rm-card-home .rm-pill.rm-pill-bitrate { font-size: 0.62rem; } .rm-card-home .rm-play-btn { top: 6px; left: 6px; width: 22px; height: 22px; font-size: 10px; } .rm-card-home .rm-fav-btn { top: 6px; right: 6px; font-size: 0.95rem; } } </style> <!-- Hero with RadioMouse mascot theme --> <section class="hero is-dark rm-hero rm-hero-mouse"> <div class="hero-body"> <div class="container"> <div class="columns is-vcentered is-variable is-6"> <div class="column is-7-desktop is-12-mobile"> <div class="rm-hero-tagline"> <span class="rm-mouse-chip"> <span class="rm-mouse-icon"> <?php if ($mascotUrl): ?> <img src="<?= h($mascotUrl) ?>" alt="RadioMouse mascot" class="rm-mascot-chip-img"> <?php else: ?> π <?php endif; ?> </span> <?= h($siteName) ?> </span> <!-- β UPDATED: use Tagline from settings --> <span class="rm-mouse-chip-sub"><?= h($siteTagline) ?></span> </div> <!-- β UPDATED: use Tagline from settings --> <h1 class="title is-3 rm-hero-title"> <?= h($siteTagline) ?>, powered by a tiny DJ mouse. </h1> <p class="subtitle is-6 rm-hero-sub"> Browse stations from around the world, tap play, and keep your favorites in one sleek, dark player. No signup, no clutter β just pure audio. </p> </div> <div class="column is-5-desktop is-12-mobile"> <!-- Mascot card on the right --> <div class="rm-mascot-hero-card"> <div class="rm-mascot-hero-img-wrap"> <?php if ($mascotUrl): ?> <img src="<?= h($mascotUrl) ?>" alt="RadioMouse mascot" class="rm-mascot-img-main"> <?php else: ?> <div class="rm-mascot-fallback">π</div> <?php endif; ?> </div> <div class="rm-mascot-hero-text"> <div class="rm-mascot-badge">Tiny DJ β’ Big sound</div> <div class="rm-mascot-copy"> RadioMouse keeps your stations and favorites just a click away β optimized for speed and a clean dark UI. </div> </div> </div> <!-- Search box --> <div class="field rm-search-wrap"> <label class="label rm-search-label">Search stations</label> <div class="control has-icons-left"> <input class="input is-medium rm-search-input" type="text" id="rm-search-input" placeholder="Search by name, country, or genre..." autocomplete="off"> <span class="icon is-left"> <i class="fas fa-search"></i> </span> </div> <p class="help rm-search-help"> Try βBollywoodβ, βLoFiβ, βRockβ, βIndiaβ, βJazzββ¦ </p> <div id="rm-search-suggest" class="rm-suggest-box"></div> </div> </div> </div> </div> </div> </section> <!-- Featured & popular --> <section class="section rm-section-tight"> <div class="container"> <div class="level"> <div class="level-left"> <h2 class="title is-5 rm-page-title">Featured & popular stations</h2> </div> <div class="level-right"> <!-- Link to full featured page --> <a href="/featured" class="tag is-rounded rm-pill rm-pill-hero" data-nav> View all featured </a> </div> </div> <div class="columns is-multiline is-variable is-3 rm-home-grid"> <?php if (!empty($stationsFeatured)): ?> <?php foreach ($stationsFeatured as $s): ?> <?php rm_render_station_card($s); ?> <?php endforeach; ?> <?php else: ?> <p>No stations yet. Add some in the admin panel.</p> <?php endif; ?> </div> </div> </section> <!-- Recently added --> <section class="section rm-section-tight rm-section-alt"> <div class="container"> <div class="level"> <div class="level-left"> <h2 class="title is-5 rm-page-title">Recently added</h2> </div> <div class="level-right"> <span class="tag is-rounded rm-pill rm-pill-hero-alt">Fresh stations</span> </div> </div> <div class="columns is-multiline is-variable is-3 rm-home-grid"> <?php if (!empty($stationsRecent)): ?> <?php foreach ($stationsRecent as $s): ?> <?php rm_render_station_card($s); ?> <?php endforeach; ?> <?php else: ?> <p>No recent stations yet.</p> <?php endif; ?> </div> </div> </section> <script> // API-based autosuggest (debounced, full navigation) (function () { const input = document.getElementById('rm-search-input'); const box = document.getElementById('rm-search-suggest'); const endpoint = '/api/search_stations.php'; let timer = null; if (!input || !box) return; function hideBox() { box.style.display = 'none'; box.innerHTML = ''; } function buildHref(st) { if (st.slug && st.slug !== '') return '/station/' + encodeURIComponent(st.slug); return '/station/' + st.id; } function renderResults(items) { box.innerHTML = ''; if (!items || !items.length) { const noItem = document.createElement('div'); noItem.className = 'rm-suggest-item'; noItem.textContent = 'No stations found'; box.appendChild(noItem); box.style.display = 'block'; return; } items.forEach(st => { const href = buildHref(st); const item = document.createElement('a'); item.className = 'rm-suggest-item'; item.href = href; const left = document.createElement('div'); left.className = 'rm-suggest-name'; left.textContent = st.name || ''; const right = document.createElement('div'); right.className = 'rm-suggest-meta'; right.textContent = st.country || ''; item.appendChild(left); item.appendChild(right); item.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); window.location.href = href; }); box.appendChild(item); }); box.style.display = 'block'; } function doSearch(q) { const term = q.trim(); if (term.length < 2) { hideBox(); return; } fetch(endpoint + '?q=' + encodeURIComponent(term), { headers: { 'Accept': 'application/json' }, cache: 'no-store' }) .then(resp => resp.ok ? resp.json() : Promise.reject(resp.status)) .then(json => { if (input.value.trim() !== term) return; renderResults(json || []); }) .catch(() => hideBox()); } input.addEventListener('input', function () { if (timer) window.clearTimeout(timer); const v = input.value; if (v.trim() === '') { hideBox(); return; } timer = window.setTimeout(() => doSearch(v), 220); }); input.addEventListener('focus', function () { if (input.value.trim().length >= 2) { doSearch(input.value); } }); document.addEventListener('click', function (e) { if (!box.contains(e.target) && e.target !== input) { hideBox(); } }); })(); </script> <?php $content = ob_get_clean(); include __DIR__ . '/partials/template.php';