52 Commits
Author SHA1 Message Date
admin01 408e50db6c Update chess/server.js 2026-08-23 18:39:12 +00:00
admin01 2aaf442bd2 Update chess/package.json 2026-08-23 18:38:50 +00:00
admin01 6f079faa48 Update chess/package-lock.json 2026-08-23 18:38:25 +00:00
admin01 76de66ca6c Update chess/public/js/ai.js 2026-08-23 07:42:19 +00:00
admin01 eef730e030 Update chess/public/index.html 2026-08-23 07:37:29 +00:00
admin01 adca2464f8 Update chess/public/styles.css 2026-08-23 07:36:35 +00:00
admin01 a5d1608741 Update package.json 2026-08-23 07:21:04 +00:00
admin01 81edf106ed Update server.js 2026-08-23 07:20:17 +00:00
admin01 7f6bfa34e2 Update chess/public/js/app.js 2026-08-23 07:19:12 +00:00
admin01 1b46099218 Update chess/shared/chess-engine.js 2026-08-23 07:17:16 +00:00
admin01 cd0c0f30ba Update chess/public/index.html 2026-08-23 07:15:23 +00:00
admin01 d67496fb19 Update chess/public/js/ai.js 2026-08-23 06:53:40 +00:00
admin01 e852d0eea4 Update chess/shared/chess-engine.js 2026-08-23 06:50:00 +00:00
admin01 e8e55e5aae Update package.json 2026-08-23 06:46:18 +00:00
admin01 6c2cb35a2a Delete chess_party_fixed.zip 2026-08-23 06:43:27 +00:00
admin01 069cae780c Update server.js 2026-08-23 06:43:20 +00:00
admin01 bbcd70d00f Update chess/public/js/app.js 2026-08-23 06:42:17 +00:00
admin01 7e469bd660 Update chess/public/styles.css 2026-08-23 06:41:16 +00:00
admin01 786620c168 Update chess/public/index.html 2026-08-23 06:39:51 +00:00
admin01 52edd18b3e Update chess/public/index.html 2026-08-22 19:00:53 +00:00
admin01 299f47b1b3 Update chess/public/styles.css 2026-08-22 19:00:17 +00:00
admin01 d0e0e3274a Update chess/public/js/app.js 2026-08-22 18:59:37 +00:00
admin01 d38ae7cbe6 Update chess/shared/chess-engine.js 2026-08-22 18:57:39 +00:00
admin01 7c94426848 Update package.json 2026-08-22 18:56:58 +00:00
admin01 1fbdd1317c Update server.js 2026-08-22 18:56:03 +00:00
admin01 1f41362338 Upload files to "/" 2026-08-22 18:54:41 +00:00
admin01 2dfea24f42 Update chess/public/styles.css 2026-08-22 18:21:04 +00:00
admin01 039b64f9e5 Update chess/public/js/app.js 2026-08-22 18:20:15 +00:00
admin01 6ed96533b0 Update chess/public/js/app.js 2026-08-22 18:14:17 +00:00
admin01 8dcc964d04 Update chess/public/js/app.js 2026-08-22 18:08:35 +00:00
admin01 226ad23f7b Update chess/public/js/app.js 2026-08-22 17:51:45 +00:00
admin01 5f75752b26 Update server.js 2026-08-22 17:49:23 +00:00
admin01 5e57feb66f Update chess/public/index.html 2026-08-22 17:47:08 +00:00
admin01 4e6e2fc9e2 Update chess/public/styles.css 2026-08-22 17:46:20 +00:00
admin01 e6e57a3cc0 Add server.js 2026-08-22 17:41:22 +00:00
admin01 0720bcc229 Add package.json 2026-08-22 17:40:44 +00:00
admin01 093b4ac46f Add package-lock.json 2026-08-22 17:40:02 +00:00
admin01 3b02071fe7 Add chess/shared/chess-engine.js 2026-08-22 17:39:10 +00:00
admin01 728da46b10 Add chess/public/js/app.js 2026-08-22 17:37:25 +00:00
admin01 328fe6b417 Add chess/public/js/ai.js 2026-08-22 17:36:51 +00:00
admin01 0aba79015d Delete chess/public/js 2026-08-22 17:36:17 +00:00
admin01 3d7bfd1797 Add chess/public/js 2026-08-22 17:36:04 +00:00
admin01 debf3bbc68 Add chess/public/styles.css 2026-08-22 17:35:26 +00:00
admin01 1d87e4539b Add chess/public/index.html 2026-08-22 17:34:34 +00:00
admin01 11a1b0cb41 Delete chess/public 2026-08-22 17:32:52 +00:00
admin01 83500d2d86 Add chess/public 2026-08-22 17:30:18 +00:00
admin01 da311edec3 Delete batch.zip 2026-08-22 17:03:06 +00:00
admin01 34606b50cc Upload files to "/" 2026-08-22 17:02:45 +00:00
admin01 0a8475d245 Delete directory 'public' 2026-08-22 17:00:37 +00:00
admin01 52ce4ab42a Add public/404.html 2026-08-22 16:55:44 +00:00
admin01 ff02d54bab Delete radiowebview.html 2026-08-22 16:53:45 +00:00
admin01 555d0b2005 Update radiowebview.html 2026-08-22 16:45:58 +00:00
9 changed files with 9076 additions and 518 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"name": "college-chess-party",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "college-chess-party",
"version": "1.0.0"
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "chess-party",
"version": "2.0.0",
"private": true,
"type": "module",
"description": "Chess Party — AI and multiplayer chess",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"express": "^4.21.2"
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1313
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-518
View File
@@ -1,518 +0,0 @@
<!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>