Files
website-collection/radiowebview.html
T
2026-08-22 16:45:58 +00:00

519 lines
23 KiB
HTML
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.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="#171717">
<title>Tempo Radio</title>
<link rel="icon" type="image/x-icon" href="YOUR_FAVICON_URL_HERE">
<style>
/* =========================================================
THEME & GLOBAL
========================================================= */
:root {
--bg: #f5f5f7;
--glass: rgba(255,255,255,.75);
--glass-strong: rgba(255,255,255,.85);
--glass-hover: rgba(255,255,255,.95);
--text: #1d1d1f;
--muted: #86868b;
--accent: #1d1d1f;
--accent-text: #ffffff;
--line: rgba(0,0,0,.08);
--shadow: rgba(0,0,0,.08);
--overlay: rgba(255,255,255,.20);
--bg-image: url('YOUR_BACKGROUND_URL_HERE');
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #000000;
--glass: rgba(28,28,30,.75);
--glass-strong: rgba(44,44,46,.85);
--glass-hover: rgba(58,58,60,.95);
--text: #f5f5f7;
--muted: #86868b;
--accent: #f5f5f7;
--accent-text: #000000;
--line: rgba(255,255,255,.1);
--shadow: rgba(0,0,0,.4);
--overlay: rgba(0,0,0,.6);
}
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { scroll-behavior: smooth; }
body {
margin: 0; min-height: 100vh; color: var(--text);
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(var(--overlay), var(--overlay)), var(--bg-image) no-repeat center center fixed;
background-size: cover; background-color: var(--bg);
user-select: none; -webkit-user-select: none; overscroll-behavior-y: none;
transition: background-color .3s ease, color .3s ease;
}
::-webkit-scrollbar { display: none; }
button { font: inherit; outline: none; }
/* =========================================================
APP & TOP BAR
========================================================= */
.app { width: min(100%, 980px); min-height: 100vh; margin: auto; overflow-x: hidden; }
.topbar {
position: sticky; top: 0; z-index: 50; display: flex; align-items: center;
min-height: 72px; padding: max(16px, env(safe-area-inset-top)) 24px 16px;
background: var(--glass); border-bottom: 1px solid var(--line);
backdrop-filter: blur(30px) saturate(180%); -webkit-backdrop-filter: blur(30px) saturate(180%);
}
.brand { display: flex; align-items: center; gap: 14px; font-size: 19px; font-weight: 700; letter-spacing: -0.3px; }
.brand-logo { width: auto; height: 42px; object-fit: contain; border-radius: 8px; }
.brand-text small { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; font-weight: 500; letter-spacing: normal; }
/* =========================================================
TABS
========================================================= */
.tabs {
position: sticky; top: 72px; z-index: 40; display: flex; justify-content: center; gap: 8px;
padding: 12px 24px; background: var(--glass); border-bottom: 1px solid var(--line);
backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px);
}
.tab {
padding: 8px 20px; border: 0; border-radius: 100px; background: transparent;
color: var(--muted); cursor: pointer; font-size: 14px; font-weight: 600;
transition: all 0.2s ease;
}
.tab:hover { color: var(--text); background: rgba(128,128,128,.1); }
.tab.active { color: var(--accent-text); background: var(--accent); }
.tab:active { transform: scale(.96); }
/* =========================================================
MAIN
========================================================= */
main { padding: 24px 24px calc(140px + env(safe-area-inset-bottom)); }
.panel { display: none; animation: fadeIn 0.3s ease; }
.panel.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
/* =========================================================
NOW PLAYING (HERO)
========================================================= */
.hero {
position: relative; min-height: 220px; display: flex; align-items: center;
padding: 32px; overflow: hidden; border: 1px solid var(--line); margin-bottom: 24px;
background: linear-gradient(135deg, var(--glass-strong), var(--glass));
border-radius: 24px;
backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%);
box-shadow: 0 20px 40px var(--shadow);
}
.hero::before {
content: ""; position: absolute; inset: 0; pointer-events: none;
background: radial-gradient(circle at 100% 0%, rgba(255,255,255,.12), transparent 50%);
}
.hero-content { position: relative; z-index: 2; display: flex; align-items: center; gap: 24px; width: 100%; }
.hero-art {
width: 120px; height: 120px; flex: 0 0 120px; object-fit: cover;
background: rgba(128,128,128,.10); border: 1px solid var(--line);
border-radius: 20px; box-shadow: 0 16px 32px var(--shadow);
}
.hero h1 { margin: 0 0 6px; font-size: 32px; font-weight: 800; letter-spacing: -1.2px; line-height: 1.1; }
.hero p { margin: 0; color: var(--muted); font-size: 15px; font-weight: 500; }
.playing {
display: inline-block; margin-top: 12px; padding: 6px 14px; color: var(--text);
background: rgba(128,128,128,.12); border: 1px solid var(--line);
border-radius: 100px; font-size: 12px; font-weight: 600;
}
.hero-controls { display: flex; margin-top: 20px; }
.play {
width: 54px; height: 54px; border: 0; border-radius: 50%; background: var(--accent);
color: var(--accent-text); cursor: pointer; font-size: 20px;
box-shadow: 0 10px 24px var(--shadow); transition: transform 0.2s ease, filter 0.2s ease;
display: flex; align-items: center; justify-content: center;
}
.play:hover { filter: brightness(1.1); }
.play:active { transform: scale(.92); }
/* =========================================================
STATIONS HEADER & VIEW MANAGER
========================================================= */
.stations-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin: 32px 0 20px; }
.section-head h2 { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -0.6px; }
.view-controls {
display: flex; align-items: center; gap: 4px; padding: 4px;
background: rgba(128,128,128,.08); border: 1px solid var(--line);
border-radius: 10px; backdrop-filter: blur(20px);
}
.view-btn {
width: 32px; height: 32px; display: flex; justify-content: center; align-items: center;
gap: 3px; padding: 5px; border: 0; border-radius: 6px; background: transparent; cursor: pointer;
transition: all 0.2s ease;
}
.view-btn span { display: block; width: 3px; height: 14px; background: var(--muted); border-radius: 2px; }
.view-btn.active { background: var(--glass-strong); box-shadow: 0 2px 8px var(--shadow); }
.view-btn.active span { background: var(--text); }
.view-btn:active { transform: scale(.9); }
/* =========================================================
RADIO GRID
========================================================= */
.grid { display: grid; gap: 20px; grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid[data-columns="2"] { grid-template-columns: repeat(2, minmax(0,1fr)); }
.grid[data-columns="3"] { grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid[data-columns="4"] { grid-template-columns: repeat(4, minmax(0,1fr)); }
/* =========================================================
STATION ITEM
========================================================= */
.station { min-width: 0; cursor: pointer; border-radius: 16px; transition: transform .2s ease; }
@media (hover: hover) { .station:hover { transform: translateY(-4px); } }
.station-art {
display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
background: rgba(128,128,128,.08); border: 1px solid var(--line); border-radius: 16px;
box-shadow: 0 10px 24px var(--shadow); transition: transform .2s ease, filter .2s ease;
}
.station:active .station-art { transform: scale(.95); filter: brightness(.85); }
.station-info { padding: 12px 6px 4px; }
.station-name { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 700; letter-spacing: -0.3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.station-desc { margin-top: 4px; color: var(--muted); font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.onair { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: #32d74b; } /* Статичный индикатор, без пульсации */
/* =========================================================
BOTTOM PLAYER
========================================================= */
.bottom-player {
position: fixed; left: 50%; bottom: max(24px, env(safe-area-inset-bottom)); z-index: 100;
width: min(calc(100% - 48px), 940px); min-height: 76px; transform: translateX(-50%);
display: flex; align-items: center; gap: 16px; padding: 12px 16px;
background: var(--glass-strong); border: 1px solid var(--line); border-radius: 24px;
backdrop-filter: blur(40px) saturate(180%); -webkit-backdrop-filter: blur(40px) saturate(180%);
box-shadow: 0 24px 48px rgba(0,0,0,0.15); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.bottom-player:active { transform: translateX(-50%) scale(0.98); }
.mini-art { width: 52px; height: 52px; flex-shrink: 0; object-fit: cover; border-radius: 12px; border: 1px solid var(--line); }
.mini-info { min-width: 0; flex: 1; }
.mini-title { font-size: 15px; font-weight: 700; letter-spacing: -0.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mini-sub { margin-top: 3px; color: var(--muted); font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.progress { width: 100%; height: 4px; margin-top: 8px; overflow: hidden; background: rgba(128,128,128,.15); border-radius: 4px; }
.progress i { display: block; width: 37%; height: 100%; background: var(--accent); border-radius: 4px; }
.bottom-controls { display: flex; align-items: center; gap: 4px; padding-right: 4px; }
.small-btn {
width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; padding: 0; border: 0;
border-radius: 50%; background: transparent; color: var(--text); cursor: pointer; font-size: 20px;
transition: all 0.2s ease;
}
.small-btn:hover { background: rgba(128,128,128,.10); }
.small-btn:active { background: rgba(128,128,128,.16); transform: scale(.9); }
.main-play { background: var(--accent); color: var(--accent-text); box-shadow: 0 8px 16px var(--shadow); }
.main-play:hover { background: var(--accent); filter: brightness(1.1); }
/* =========================================================
ABOUT & REPOSITORY CARD
========================================================= */
.about-card {
margin-top: 12px; padding: 32px; background: var(--glass-strong); border: 1px solid var(--line);
backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px); box-shadow: 0 20px 40px var(--shadow);
border-radius: 24px;
}
.about-card.repo-container {
display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px;
}
.about-card h3 { margin: 0 0 24px 0; font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }
.repo-link { display: inline-block; transition: transform 0.2s ease, filter 0.2s ease; }
.repo-link:hover { filter: brightness(1.05); }
.repo-link:active { transform: scale(0.92); }
.repo-icon {
width: 88px; height: 88px; object-fit: contain; border-radius: 20px;
background: var(--bg); border: 1px solid var(--line);
box-shadow: 0 12px 28px var(--shadow); padding: 12px;
}
/* =========================================================
MOBILE RESPONSIVENESS
========================================================= */
@media(max-width:600px) {
.topbar { padding: 12px 16px; min-height: 64px; }
.brand-text small { display: none; }
.tabs { top: 64px; padding: 10px 16px; }
.tab { padding: 8px 16px; font-size: 13px; }
main { padding: 16px 16px calc(130px + env(safe-area-inset-bottom)); }
.hero { min-height: 190px; padding: 20px; border-radius: 20px; }
.hero-content { gap: 16px; }
.hero-art { width: 90px; height: 90px; flex-basis: 90px; border-radius: 16px; }
.hero h1 { font-size: 24px; letter-spacing: -0.6px; }
.hero p { font-size: 13px; }
.stations-toolbar { flex-direction: column; align-items: stretch; gap: 16px; margin: 24px 0 16px; }
.grid { gap: 16px; }
.station-art { border-radius: 12px; }
.bottom-player { width: calc(100% - 24px); bottom: max(12px, env(safe-area-inset-bottom)); padding: 8px 12px; min-height: 70px; border-radius: 20px; }
.mini-art { width: 46px; height: 46px; border-radius: 10px; }
.small-btn { width: 40px; height: 40px; font-size: 18px; }
}
@media(max-width:400px) {
.hero-art { width: 80px; height: 80px; flex-basis: 80px; }
.hero h1 { font-size: 22px; }
.station-name { font-size: 14px; }
.station-desc { font-size: 11px; }
}
</style>
</head>
<body>
<div class="app"> <!-- HEADER -->
<header class="topbar">
<div class="brand">
<img src="YOUR_LOGO_URL_HERE" alt="Tempo Radio" class="brand-logo">
<div class="brand-text">Tempo Radio <small>Internet Radio</small></div>
</div>
</header>
<!-- NAVIGATION -->
<nav class="tabs">
<button class="tab active" data-tab="#radio">Radio</button>
<button class="tab" data-tab="#about">О программе</button>
</nav>
<!-- MAIN -->
<main>
<!-- RADIO -->
<section id="radio" class="panel active">
<!-- NOW PLAYING -->
<section class="hero">
<div class="hero-content">
<img id="heroArt" class="hero-art" src="" alt="Station">
<div>
<h1 id="heroTitle">ZIP FM</h1>
<p id="heroDesc">Hit music • Lithuania</p>
<div id="heroStatus" class="playing">Готово к воспроизведению</div>
<div class="hero-controls">
<button id="heroPlay" class="play" onclick="togglePlay()"></button>
</div>
</div>
</div>
</section>
<!-- TOOLBAR -->
<div class="stations-toolbar">
<div class="section-head"><h2>Radio stations</h2></div>
<div class="view-controls">
<button class="view-btn" data-columns="2" title="2 в ряд">
<span></span><span></span>
</button>
<button class="view-btn" data-columns="3" title="3 в ряд">
<span></span><span></span><span></span>
</button>
<button class="view-btn" data-columns="4" title="4 в ряд">
<span></span><span></span><span></span><span></span>
</button>
</div>
</div>
<!-- STATIONS -->
<div id="stationGrid" class="grid" data-columns="3"></div>
</section>
<!-- ABOUT -->
<section id="about" class="panel">
<div class="about-card repo-container">
<h3>Мой репозиторий</h3>
<!-- Замените YOUR_REPOSITORY_URL_HERE на ссылку на ваш репозиторий (например GitHub) -->
<!-- Замените YOUR_REPO_ICON_URL_HERE на ссылку с вашей иконкой или картинкой -->
<a href="YOUR_REPOSITORY_URL_HERE" target="_blank" rel="noopener noreferrer" class="repo-link" title="Перейти в репозиторий">
<img src="YOUR_REPO_ICON_URL_HERE" alt="Repository" class="repo-icon">
</a>
</div>
</section>
</main>
</div>
<!-- BOTTOM PLAYER -->
<div id="bottomPlayer" class="bottom-player" onclick="window.scrollTo(0,0)">
<img id="miniArt" class="mini-art" src="" alt="Station">
<div class="mini-info">
<div id="miniTitle" class="mini-title">ZIP FM</div>
<div id="miniSub" class="mini-sub">Готово к воспроизведению</div>
<div class="progress"><i></i></div>
</div>
<div class="bottom-controls">
<button class="small-btn" onclick="event.stopPropagation(); previousStation();"></button>
<button id="miniPlay" class="small-btn main-play" onclick="event.stopPropagation(); togglePlay();"></button>
<button class="small-btn" onclick="event.stopPropagation(); nextStation();"></button>
</div>
</div>
<audio id="audio" preload="none"></audio>
<script>
/* =========================================================
RADIO STATIONS
========================================================= */
const stations = [
{ id: "zip", name: "ZIP FM", description: "Hit music • Lithuania", stream: "https://stream1.zipfm.lt/zipfm128.mp3", icon: "YOUR_SVG_URL_HERE_1" },
{ id: "powerhit", name: "Power Hit Radio", description: "Dance • Pop • Top Hits • Lithuania", stream: "https://stream.powerhitradio.lt/PHR_AAC", icon: "YOUR_POWERHIT_SVG_URL_HERE" },
{ id: "rock", name: "Rock FM", description: "Rock music • Lithuania", stream: "https://stream2.rockfm.lt/crf128.mp3", icon: "YOUR_SVG_URL_HERE_2" },
{ id: "relax", name: "Relax FM", description: "Relax & easy listening", stream: "https://stream1.relaxfm.lt/relaxfm128.mp3", icon: "YOUR_SVG_URL_HERE_3" },
{ id: "lietus", name: "Lietus", description: "Lithuanian music", stream: "https://radio.m-1.fm/LIETUS", icon: "YOUR_SVG_URL_HERE_5" },
{ id: "m1", name: "M-1", description: "Popular music", stream: "https://n02a-eu.rcs.revma.com/f31w7e0fveuvv", icon: "YOUR_SVG_URL_HERE_6" },
{ id: "plus", name: "M-1 Plius", description: "Music & classics", stream: "https://radio.m-1.fm/m1plius/mp3", icon: "YOUR_SVG_URL_HERE_7" }
];
/* =========================================================
APP LOGIC
========================================================= */
const audio = document.getElementById("audio");
let currentIndex = 0;
let isPlaying = false;
const $ = id => document.getElementById(id);
const current = () => stations[currentIndex];
function renderStations() {
const grid = $("stationGrid");
let columns = localStorage.getItem("tempoRadioColumns") || "3";
if(columns === "5" || columns === "6") columns = "4";
grid.dataset.columns = columns;
grid.innerHTML = stations.map((station, index) => {
const active = index === currentIndex;
return `
<article class="station" onclick="selectStation(${index})">
<img class="station-art" src="${station.icon}" alt="${station.name}">
<div class="station-info">
<div class="station-name">${active && isPlaying ? '<i class="onair"></i>' : ''}${station.name}</div>
<div class="station-desc">${station.description}</div>
</div>
</article>
`;
}).join("");
}
function updateMediaSession() {
if ("mediaSession" in navigator) {
try {
const station = current();
navigator.mediaSession.metadata = new MediaMetadata({
title: station.name, artist: station.description, album: "Tempo Radio",
artwork: [{ src: station.icon, sizes: "512x512", type: "image/png" }]
});
} catch (error) {}
}
}
if ("mediaSession" in navigator) {
try {
navigator.mediaSession.setActionHandler("play", togglePlay);
navigator.mediaSession.setActionHandler("pause", togglePlay);
navigator.mediaSession.setActionHandler("previoustrack", previousStation);
navigator.mediaSession.setActionHandler("nexttrack", nextStation);
} catch (error) {}
}
function updateUI() {
const station = current();
$("heroArt").src = station.icon;
$("heroTitle").textContent = station.name;
$("heroDesc").textContent = station.description;
$("miniArt").src = station.icon;
$("miniTitle").textContent = station.name;
const statusTxt = isPlaying ? "Сейчас играет • LIVE" : "Готово к воспроизведению";
$("miniSub").textContent = statusTxt;
$("heroStatus").textContent = statusTxt;
$("heroPlay").textContent = isPlaying ? "Ⅱ" : "▶";
$("miniPlay").textContent = isPlaying ? "Ⅱ" : "▶";
renderStations();
updateMediaSession();
}
async function selectStation(index, autoplay = true) {
currentIndex = index;
isPlaying = false;
$("heroStatus").textContent = "Подключение…";
updateUI();
audio.src = current().stream;
audio.load();
if (autoplay) {
try {
await audio.play();
isPlaying = true;
} catch (error) {
isPlaying = false;
$("heroStatus").textContent = "Нажмите Play для запуска";
}
}
updateUI();
}
async function togglePlay() {
if (!audio.src) return selectStation(currentIndex, true);
if (audio.paused) {
try {
await audio.play();
isPlaying = true;
} catch (error) {
isPlaying = false;
$("heroStatus").textContent = "Нажмите Play для запуска";
}
} else {
audio.pause();
isPlaying = false;
}
updateUI();
}
function previousStation() {
currentIndex = (currentIndex - 1 + stations.length) % stations.length;
selectStation(currentIndex, true);
}
function nextStation() {
currentIndex = (currentIndex + 1) % stations.length;
selectStation(currentIndex, true);
}
audio.addEventListener("playing", () => { isPlaying = true; $("heroStatus").textContent = "Сейчас играет • LIVE"; updateUI(); });
audio.addEventListener("pause", () => { isPlaying = false; $("heroStatus").textContent = "Готово к воспроизведению"; updateUI(); });
audio.addEventListener("waiting", () => { if (!audio.paused) $("heroStatus").textContent = "Подключение…"; });
audio.addEventListener("error", () => { isPlaying = false; $("heroStatus").textContent = "Поток недоступен"; updateUI(); });
/* =========================================================
GRID VIEW
========================================================= */
const stationGrid = $("stationGrid");
const viewButtons = document.querySelectorAll(".view-btn");
let savedColumns = localStorage.getItem("tempoRadioColumns") || "3";
if(savedColumns === "5" || savedColumns === "6") savedColumns = "4";
stationGrid.dataset.columns = savedColumns;
viewButtons.forEach(button => {
if (button.dataset.columns === savedColumns) button.classList.add("active");
button.addEventListener("click", () => {
const cols = button.dataset.columns;
stationGrid.dataset.columns = cols;
localStorage.setItem("tempoRadioColumns", cols);
viewButtons.forEach(item => item.classList.remove("active"));
button.classList.add("active");
});
});
/* =========================================================
TABS
========================================================= */
document.querySelectorAll(".tab").forEach(tab => {
tab.addEventListener("click", () => {
document.querySelectorAll(".tab, .panel").forEach(el => el.classList.remove("active"));
tab.classList.add("active");
document.querySelector(tab.dataset.tab).classList.add("active");
window.scrollTo(0, 0);
});
});
/* =========================================================
START
========================================================= */
updateUI();
</script>
</body>
</html>