Анализ сайта radio420.net
Основное Готовность: 55%
Домен
radio420.net
Состояние доменного имени
?
Проверяем корректность доменного имени и наличие технических проблем на уровне домена.
Домен второго уровня идеален для продвижения.
Отличный запоминающийся домен.
Ответ сервера
200 Успешный ответ
HTTP-код ответа и цепочка редиректов
?
Код 200 — страница доступна. Коды 3xx — редиректы (цепочки замедляют загрузку и размывают ссылочный вес). Коды 4xx/5xx — ошибки, поисковик не сможет проиндексировать страницу.
Сервер настроен корректно.
Цепочка редиректов:
http://radio420.net
301 MovedPermanently
http://www.radio420.net/
200 OK
Безопасность
Сайт небезопасен
Использование HTTPS и SSL-сертификат
?
HTTPS — обязательный стандарт. Google и Яндекс отдают предпочтение защищённым сайтам. Отсутствие SSL или просроченный сертификат ведут к предупреждениям в браузере и снижению позиций.
На сайте не найден защищенный протокол ssl и сайт не открывается по https!
Не настроен HSTS (Strict-Transport-Security) — рекомендуется включить.
HTTP автоматически перенаправляется на HTTPS.
Поздравляем! Сайт не содержится в реестре РКН.
Кодировка
utf-8
Кодировка символов страницы
?
Стандарт — UTF-8. Неправильная кодировка вызывает нечитаемые символы и мешает поисковику корректно распознать текст страницы.
Указана кодировка на странице utf-8.
Язык
Атрибут lang в HTML-теге
?
Атрибут lang (<html lang="ru">) сообщает поисковикам и браузерам, на каком языке написана страница. Помогает при ранжировании в региональном поиске.
Язык страницы не указан. Рекомендуется явно указать язык документа!
Скорость загрузки
~0,62сек
Время отклика сервера (TTFB)
?
Time To First Byte — время до получения первого байта от сервера. Норма до 200 мс. Медленный отклик ухудшает пользовательский опыт и ранжирование: Яндекс и Google учитывают скорость страниц.
Скорость загрузки сайта 0,62сек оптимальна.
Объем документа
45Кб
Размер HTML-кода страницы
?
Слишком большой HTML замедляет парсинг браузером и сканирование поисковым роботом. Рекомендуется не более 200 Кб.
Объем html-документа 45Кб оптимален.
Структура html-документа корректна.
Ресурсы
Ресурсы: 2
Внешние ресурсы страницы (CSS, JS, изображения)
?
Количество и тип подключённых ресурсов влияют на скорость загрузки. Большое число запросов увеличивает время рендеринга страницы.
Кол-во файлов ресурсов 2 достаточно.
Показать полный список ресурсов
| Тип | Название | Значение |
|---|---|---|
| stylesheet | text/css | https://www.blogger.com/static/v1/widgets/2872013778-css_bundle_v2.css |
| js | text/javascript | https://www.blogger.com/static/v1/widgets/4290755334-widgets.js |
Серверные заголовки
Кол-во: 8
HTTP-заголовки ответа сервера
?
Заголовки сервера передают браузеру и поисковику служебную информацию: кеширование, безопасность (CSP, HSTS), сжатие (gzip). Правильная настройка ускоряет загрузку и повышает защищённость.
Найдены серверные заголовки 8шт. Подробнее про серверные заголовки.
Показать полный список серверных заголовков
| Ключ | Значение |
|---|---|
| Date | Sat, 22 Aug 2026 23:21:39 GMT |
| Cache-Control | max-age=0, private |
| X-Content-Type-Options | nosniff |
| X-XSS-Protection | 1; mode=block |
| Server | GSE |
| Accept-Ranges | none |
| Vary | Accept-Encoding |
| Transfer-Encoding | chunked |
CMS
blogger
Система управления сайтом (движок)
?
CMS — это движок, на котором работает сайт (WordPress, 1C-Bitrix, Tilda и др.). Знание CMS помогает понять возможности SEO-оптимизации и подобрать подходящие инструменты. «Не определена» — вероятно, самописный сайт или нестандартная сборка.
В мета-теге generator указано: blogger.
Веб-сервер
Программное обеспечение сервера
?
Веб-сервер — это ПО, которое отдаёт страницы посетителям (nginx, Apache, IIS, LiteSpeed и др.). Определяется по серверным заголовкам ответа (Server, X-Powered-By и т.п.). «Не определён» — сервер намеренно скрывает эти заголовки, это нормальная практика безопасности.
Сайт работает на веб-сервере Google Frontend.
Мета-теги Готовность: 48%
Title
Radio420™
Заголовок страницы в браузере и поисковой выдаче
?
Title — главный SEO-заголовок страницы. Влияет на CTR в поиске и ранжирование. Оптимальная длина: 50–70 символов. Ключевые слова — ближе к началу.
Необходимо увеличить число символов в title (текущее значение мало: 15, минимум: 25, оптимально: от 40 до 45)
Дублей словоформ в title не найдено.
Description
Описание страницы в поисковой выдаче (сниппет)
?
Meta Description — текст под заголовком в выдаче. Напрямую на позиции не влияет, но влияет на CTR. Оптимальная длина: 120–160 символов.
Установите мета-тег description!
Keywords
Список ключевых слов страницы (устаревший тег)
?
Meta Keywords не учитывается Яндексом и Google для ранжирования с 2009–2012 годов. Заполнение не обязательно, но не вредит. Конкурент может использовать содержимое для анализа.
Установите мета-тег keywords!
Канонический Url
http://www.radio420.net/
Указывает поисковику основную версию страницы
?
Canonical (rel=canonical) предотвращает проблему дублей страниц. Должен точно совпадать с URL проверяемой страницы. Неправильный canonical может передать ссылочный вес на другую страницу.
Канонический Url прописан корректно.
Robots
Ошибок нет
Директивы для поисковых роботов на уровне страницы
?
Meta Robots управляет индексацией конкретной страницы: index/noindex — индексировать ли, follow/nofollow — следовать ли по ссылкам. Noindex полностью исключает страницу из поиска.
Meta-тег robots не указан. Страница свободна для индексации.
Адаптивность
width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0
Настройка масштабирования на мобильных устройствах
?
Тег viewport (<meta name="viewport">) сообщает браузеру, как масштабировать страницу на мобильных. Стандарт: width=device-width, initial-scale=1. Отсутствие — признак отсутствия мобильной версии.
Meta-тег viewport со значением-константой width=device-width задаёт ширину страницы в соответствии с размером экрана.
Meta-тег viewport со значением initial-scale=1.0 определяет масштаб 1:1, т.е. «не масштабировать».
Meta-тег viewport со значением minimum-scale определяет минимальный масштаб области просмотра.
Meta-тег viewport со значением maximum-scale определяет максимальный масштаб области просмотра.
Разметка OpenGraph
Кол-во: 3
Мета-теги для красивых превью в соцсетях
?
OpenGraph (og:title, og:description, og:image) управляет тем, как страница выглядит при репосте в социальных сетях и мессенджерах. Отсутствие OG-тегов — невзрачный превью при шеринге.
Разметка OpenGraph задана. Страница оптимизирована под социальные сети.
Показать полный список og мета-тегов
| Тип | Значение |
|---|---|
| og:url | http://www.radio420.net/ |
| og:title | Radio420™ |
| og:description | BLAZiN ALL THE HiTS |
Все мета-теги
Кол-во: 8
Полный список мета-тегов страницы
?
Таблица всех meta-тегов, включая нестандартные. Позволяет найти опечатки, дубли и лишние теги.
Найдены мета-теги 8шт. Мета-теги не видимы для человека и предназначены для обмена информацией между веб-страницей и поисковыми системами, браузерами и другими веб-службами. С ними роботы 🤖 и устройства ведут себя более ожидаемо.
Показать полный список мета-тегов
| Тип | Название | Значение |
|---|---|---|
| name | viewport | width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0 |
| name | generator | blogger |
| name | description | |
| name | google-adsense-platform-account | ca-host-pub-1556223355139109 |
| name | google-adsense-platform-domain | blogspot.com |
| property | og:url | http://www.radio420.net/ |
| property | og:title | Radio420™ |
| property | og:description | BLAZiN ALL THE HiTS |
Оптимизация Готовность: 37%
Структура
Ошибок нет
Семантические HTML-элементы страницы
?
Проверяет наличие основных структурных элементов: nav, header, footer, main. Корректная семантическая структура помогает поисковику понять архитектуру страницы.
Структура документа корректна (теги <html> и <body> присутствуют в единственном экземпляре).
Контент
Есть ошибки
Объём и качество текстового содержимого
?
Анализирует объём полезного текста на странице. Слишком мало — страница может считаться малополезной. Слишком много — ухудшается читаемость и восприятие.
Слова из title 0 встречаются в тексте редко. Добавьте в контент страницы слова из тега <title>!
Абзацев с текстом 7 слишком мало. Добавьте больше абзацев с текстом (тег <p>)!
Кол-во знаков контента 870 слишком мало. Добавьте больше текста!
Кол-во слов 136 слишком мало. Добавьте больше текста (минимум 400 слов)!
Среднее число слов в абзаце 17 достаточно.
Заголовки
Ошибок нет
Иерархия заголовков H1–H6
?
H1 должен быть один и содержать ключевой запрос. H2–H6 описывают подразделы. Пропуск уровней (H1 → H3) и несколько H1 — типичные ошибки, снижающие понятность страницы для поисковика.
На странице присутствуют заголовки <h2> 1. Это хорошо.
На странице присутствуют заголовки <h3> 2.
Тошнота
1,41
Насколько одно слово доминирует в тексте
?
Классическая тошнота = √(частота самого повторяющегося слова). Норма до 7–8: текст воспринимается естественно. Выше — поисковик может счесть страницу переспамленной.
Тошнота страницы в пределах нормы 3.
Академич. тошнота
22,79%
Насколько текст перенасыщен ключевыми словами
?
Академическая тошнота = (частота слова / общее количество слов) × 100%. Показывает долю конкретного слова в тексте. Норма 5–15%.
Академическая тошнота превышает норму 5-15%. Измените текст страницы!
Семантическое ядро
20
Наиболее часто встречающиеся слова на странице
?
Топ слов по частоте использования. Показывает, какие слова доминируют в тексте с точки зрения поисковика.
Контент страницы содержит осмысленный текст и слова.
Показать список слов
| Слово | Кол-во | Частота |
|---|---|---|
| loading | 2 | 1,47% |
| station | 2 | 1,47% |
| dedicated | 2 | 1,47% |
| streams | 2 | 1,47% |
| stonerway | 1 | 0,74% |
| stoniest | 1 | 0,74% |
| unique | 1 | 0,74% |
| designs | 1 | 0,74% |
| youtube | 1 | 0,74% |
| spotify | 1 | 0,74% |
| stations | 1 | 0,74% |
| select | 1 | 0,74% |
| advertise | 1 | 0,74% |
| audience | 1 | 0,74% |
| lovers | 1 | 0,74% |
| cannabis | 1 | 0,74% |
| enthusiasts? | 1 | 0,74% |
| competitive | 1 | 0,74% |
| placements | 1 | 0,74% |
| within | 1 | 0,74% |
Индексация Готовность: 30%
Индексирование
Ошибок нет
Разрешено ли индексирование страницы
?
Проверяет, не закрыта ли страница от индексации через robots.txt, meta robots или X-Robots-Tag. Страница, закрытая от индексации, не появится в поисковой выдаче.
Анкоров на странице 7 оптимально. Поисковые роботы обязательно проиндексируют сайт.
Robots.txt
Найден корректный robots.txt
Файл управления сканированием сайта роботами
?
Robots.txt указывает поисковым роботам, какие страницы сканировать, а какие — нет. Ошибки в файле могут случайно закрыть важные разделы от индексации.
Robots.txt настроен корректно. Размер файла: 46551 байт. Загружен за: 0сек.
Проверяемая страница не запрещена в robots.txt.
Robots.txt доступен по постоянному адресу
Цепочка редиректов для файла robots.txt:
http://radio420.net/robots.txt
301 MovedPermanently
http://www.radio420.net/
200 OK
Показать содержимое robots.txt
<!DOCTYPE html>
<html class='v2' dir='ltr' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
<head>
<link href='https://www.blogger.com/static/v1/widgets/2872013778-css_bundle_v2.css' rel='stylesheet' type='text/css'/>
<meta content='IE=EmulateIE7' http-equiv='X-UA-Compatible'/>
<meta content='width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0' name='viewport'/>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
<meta content='blogger' name='generator'/>
<link href='http://www.radio420.net/favicon.ico' rel='icon' type='image/x-icon'/>
<link href='http://www.radio420.net/' rel='canonical'/>
<link rel="alternate" type="application/atom+xml" title="Radio420™ - Atom" href="http://www.radio420.net/feeds/posts/default" />
<link rel="alternate" type="application/rss+xml" title="Radio420™ - RSS" href="http://www.radio420.net/feeds/posts/default?alt=rss" />
<link rel="service.post" type="application/atom+xml" title="Radio420™ - Atom" href="https://www.blogger.com/feeds/6097148778129698866/posts/default" />
<!--Can't find substitution for tag [blog.ieCssRetrofitLinks]-->
<meta content='http://www.radio420.net/' property='og:url'/>
<meta content='Radio420™' property='og:title'/>
<meta content='BLAZiN ALL THE HiTS' property='og:description'/>
<title>Radio420™</title>
<meta content='' name='description'/>
<style id='page-skin-1' type='text/css'><!--
/* --- Global Styles --- */
*, *:before, *:after { box-sizing: border-box; }
html, body {
background: #000000 url(https://themes.googleusercontent.com/image?id=1_UPSuFGxHEaIUme6AnAeu5ZvDnijFNnoLXkl6NuCpTfWQN5BRuDJV_NkibASw-94tFfA) no-repeat scroll top center !important;
background-size: cover !important;
color: #d1d5db; margin: 0; padding: 0;
font-family: sans-serif; overflow: hidden;
}
/* --- Main App Layout --- */
#radio420-app {
max-width: 1420px; margin: 0 auto; display: flex;
flex-direction: column; gap: 1rem; padding: 0;
height: 100vh; padding-top: 0.5rem;
}
.app-panel {
background-color: rgba(0, 0, 0, 0.5); border-radius: 0.5rem;
border: 1px solid #374151; padding: 1.5rem;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
display: flex; flex-direction: column; overflow: hidden;
}
/* --- Header Styles --- */
#header-container img {
width: 100%; height: auto; object-fit: contain;
filter: drop-shadow(0 0 10px rgba(16,185,129,0.5));
}
/* --- Promo Banner Styles --- */
#promo-banner-container {
display: grid; grid-template-columns: auto 1fr auto;
align-items: center; width: 100%; flex-shrink: 0;
padding: 0.5rem 1rem;
}
.promo-banner-section { display: flex; align-items: center; gap: 0.75rem; }
#promo-banner-left { justify-content: flex-start; }
#promo-banner-right { justify-content: flex-end; }
#promo-banner-center { text-align: center; padding: 0 1rem; gap: 0.5rem; justify-content: center; }
#promo-banner-center a { text-decoration: none; color: #d1d5db; font-size: 1rem; transition: color 0.3s ease; }
#promo-banner-center a:hover { color: #ffffff; }
#promo-banner-center a span { font-weight: bold; color: #34d399; transition: color 0.3s ease; }
#promo-banner-center a:hover span { color: #6ee7b7; }
.promo-banner-icon { color: #9ca3af; background: none; border: none; padding: 0; cursor: pointer; transition: color 0.2s ease-in-out; }
.promo-banner-icon:hover { color: #34d399; }
.promo-banner-icon svg { width: 1.25rem; height: 1.25rem; fill: currentColor; }
.promo-button {
background-color: #10b981;
color: #ffffff;
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: bold;
cursor: pointer;
text-transform: uppercase;
transition: background-color 0.2s ease;
}
.promo-button:hover { background-color: #059669; }
/* --- Main Content Grid --- */
#main-content-container { display: grid; grid-template-columns: 1fr; gap: 1rem; min-height: 0; flex-grow: 1; padding-bottom: 0.5rem; }
/* --- Station List Styles --- */
#station-list-container { padding: 1rem; }
.station-tabs {
display: flex;
margin-bottom: 1rem;
border-bottom: 1px solid #374151; /* gray-700 */
}
.tab-button {
flex: 1; padding: 0.5rem; text-align: center; font-weight: bold;
cursor: pointer; border: none; background: none; color: #9ca3af; /* gray-400 */
border-bottom: 4px solid transparent; transition: all 0.3s ease;
display: flex; align-items: center; justify-content: center; gap: 0.5rem;
}
.tab-button.active { color: #8b5cf6; /* violet-500 */ border-bottom-color: #8b5cf6; }
.tab-button .tab-count { font-size: 0.75rem; opacity: 0.75; }
.tab-panels { flex-grow: 1; position: relative; }
.tab-panel {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
display: none; /* Hidden by default */
flex-direction: column;
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.tab-panel.active { display: flex; opacity: 1; }
.list-scroll-container { flex-grow: 1; overflow-y: auto; padding-right: 0.5rem; }
/* Station Item Styles */
.station-item {
display: flex; align-items: center; gap: 1rem; padding: 0.75rem;
margin-bottom: 0.25rem; border-radius: 0.5rem; background-color: #1f2937; /* gray-800 */
cursor: pointer; transition: background-color 0.2s;
}
.station-item:hover { background-color: #374151; }
.station-item.active { background-color: #16a34a; color: #111827; }
.station-logo { width: 3rem; height: 3rem; border-radius: 0.375rem; border: 2px solid #4b5563; object-fit: cover; flex-shrink: 0; }
.station-info { flex-grow: 1; min-width: 0; }
.station-name { font-weight: bold; color: #6ee7b7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.station-item.active .station-name { color: #ffffff; }
.station-genre { font-size: 0.875rem; color: #9ca3af; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.station-item.active .station-genre { color: #d1d5db; }
.station-extras { display: flex; align-items: center; gap: 0.75rem; flex-shrink: 0; }
.station-extras svg { width: 1.25rem; height: 1.25rem; }
.station-extras .flag-icon { width: 1.25rem; border-radius: 0.25rem; }
/* News Item Styles */
.news-item {
display: flex; align-items: flex-start; gap: 0.75rem; padding: 0.75rem;
margin-bottom: 0.25rem; border-radius: 0.5rem; background-color: #1f2937;
text-decoration: none; transition: background-color 0.2s;
}
.news-item:hover { background-color: #374151; }
.news-item img { width: 4rem; height: 4rem; object-fit: cover; border-radius: 0.375rem; border: 2px solid #4b5563; flex-shrink: 0; }
.news-info { flex-grow: 1; min-width: 0; }
.news-title { font-weight: bold; color: #6ee7b7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 0.25rem; }
.news-date { font-size: 0.75rem; color: #6b7280; }
/* Chat Panel Styles */
#chat-panel iframe {
width: 100%;
height: 100%;
border: none;
border-radius: 0.5rem;
}
/* --- Player Styles --- */
#player-container {
justify-content: center;
align-items: center;
text-align: center;
gap: 1.5rem; /* 24px */
position: relative; /* for expand toggle positioning */
}
.player-art-wrapper {
position: relative;
width: 16rem; /* 256px */
height: 16rem;
}
#player-art {
width: 100%;
height: 100%;
border-radius: 9999px; /* rounded-full */
border: 4px solid #059669; /* green-700 */
box-shadow: 0 0 20px rgba(16, 185, 129, 0.6);
object-fit: cover;
}
.spinning { animation: spin 10s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.player-glow {
position: absolute;
inset: -1rem;
border-radius: 9999px;
background-image: linear-gradient(to right, #ef4444, #3b82f6, #10b981);
filter: blur(24px);
animation: rgb-glow 4s linear infinite;
z-index: -1;
display: none; /* Hidden by default, shown when playing */
}
@keyframes rgb-glow { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.player-info h2 {
font-size: 1.875rem; /* 30px */
font-weight: bold;
color: #6ee7b7; /* green-300 */
margin: 0;
height: 2.5rem; /* prevent layout shifts */
}
.player-info p {
color: #9ca3af; /* gray-400 */
margin: 0.25rem 0 0 0;
height: 1.25rem; /* Fixed height */
}
.player-controls { display: flex; align-items: center; justify-content: center; gap: 1.5rem; }
.player-button { background: none; border: none; color: #d1d5db; cursor: pointer; padding: 0.75rem; transition: color 0.2s; }
.player-button:hover { color: #ffffff; }
.player-button:disabled { color: #4b5563; cursor: not-allowed; }
.player-button svg { width: 2rem; height: 2rem; }
#play-pause-btn {
background-color: #10b981; /* green-600 */
color: #111827; /* gray-900 */
border-radius: 9999px;
padding: 1rem;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.7);
transition: all 0.3s ease;
}
#play-pause-btn:hover { background-color: #059669; transform: scale(1.1); }
.volume-controls { display: flex; align-items: center; gap: 0.75rem; width: 100%; max-width: 16rem; }
#volume-slider { width: 100%; cursor: pointer; }
/* Expand toggle: hidden by default (desktop) */
.player-expand-toggle { display: none; }
/* --- Popup / Modal Styles --- */
.popup-overlay {
position: fixed; inset: 0; z-index: 100; display: none;
align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
animation: fadeIn 0.3s ease-out;
}
.popup-content {
position: relative; background-color: rgba(17, 24, 39, 0.8);
border: 2px solid #34d399; border-radius: 0.5rem;
box-shadow: 0 0 20px rgba(16, 185, 129, 0.5);
padding: 2rem; max-width: 32rem; width: 100%;
text-align: left; display: flex; flex-direction: column; gap: 1rem;
}
.popup-content h3 { color: #6ee7b7; margin-top: 0; text-align: center; }
.popup-content p { margin: 0; line-height: 1.5; }
.popup-close-btn { position: absolute; top: 0.5rem; right: 0.5rem; color: #9ca3af; background: none; border: none; padding: 0.5rem; cursor: pointer; transition: color 0.2s ease-in-out; }
.popup-close-btn:hover { color: #ffffff; }
.popup-close-btn svg { width: 1.5rem; height: 1.5rem; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
/* --- Responsive & Blogger Overrides --- */
@media (min-width: 1024px) { #main-content-container { grid-template-columns: 5fr 3fr; } }
@media (max-width: 768px) { #promo-banner-center { display: none; } }
#navbar-iframe { height:0px; visibility:hidden; display:none; }
/* === Mini Player on Mobile (sticky bottom bar) === */
:root { --banner-h: 64px; } /* tweak if you want a shorter/taller bar */
#promo-banner-container { min-height: var(--banner-h); }
@media (max-width: 768px) {
/* Reserve space so the fixed bar doesn't cover content */
#radio420-app { padding-bottom: calc(var(--banner-h) + 8px); }
/* Sticky mini player */
#player-container {
position: fixed;
bottom: 0; left: 0; right: 0;
height: var(--banner-h);
padding: 0 .75rem;
background-color: rgba(0, 0, 0, 0.85);
border-top: 1px solid #374151;
box-shadow: 0 -4px 20px rgba(16, 185, 129, 0.15);
z-index: 999;
/* compact horizontal layout */
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: .75rem;
}
/* Tiny round art; hide glow & spin to avoid jitter in tight space */
.player-art-wrapper { width: 40px; height: 40px; flex: 0 0 40px; }
#player-art { border-width: 2px; }
.player-glow { display: none !important; }
.spinning { animation: none; }
/* Tighter text */
.player-info { min-width: 0; text-align: left; }
.player-info h2 { font-size: .95rem; line-height: 1.2; height: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.player-info p { font-size: .75rem; line-height: 1.2; height: auto; color: #9ca3af; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Compact controls */
.player-controls { gap: .5rem; }
.player-button { padding: .5rem; }
.player-button svg { width: 1.5rem; height: 1.5rem; }
#play-pause-btn { padding: .5rem .6rem; box-shadow: 0 0 10px rgba(16, 185, 129, 0.5); }
/* Slim volume */
.volume-controls { max-width: 120px; gap: .5rem; }
#volume-slider { height: 4px; }
/* Expand toggle visible on mobile */
.player-expand-toggle {
display: block;
background: none; border: none; color: #9ca3af; cursor: pointer; padding: .5rem;
position: absolute; top: .25rem; right: .25rem; z-index: 1000;
}
.player-expand-toggle:hover { color: #34d399; }
.player-expand-toggle svg { width: 1.25rem; height: 1.25rem; transition: transform .2s ease; }
/* Expanded mode */
#player-container.expanded {
height: 60vh; /* taller, almost full screen */
flex-direction: column; justify-content: flex-start; align-items: center; padding: 1rem; overflow-y: auto; z-index: 1000;
background-color: rgba(0, 0, 0, 0.92);
}
#player-container.expanded .player-art-wrapper { width: 160px; height: 160px; }
#player-container.expanded .player-glow { display: block !important; }
#player-container.expanded .player-info h2 { font-size: 1.25rem; white-space: normal; height: auto; }
#player-container.expanded .player-info p { font-size: 1rem; white-space: normal; height: auto; }
#player-container.expanded .player-expand-toggle svg { transform: rotate(180deg); }
}
--></style>
<meta name='google-adsense-platform-account' content='ca-host-pub-1556223355139109'/>
<meta name='google-adsense-platform-domain' content='blogspot.com'/>
<!-- data-ad-client=ca-pub-9725009255532012 -->
</head>
<body>
<div id='radio420-app'>
<div id='header-container'>
<a href='/'><img alt='Radio 420' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_-k_nlc42V6o482364MUyHLvtoK6Piiu5IR_VLbyZmXwpdy-gXQMYy3Vpd9AugGDO7Mou_FnNPMmoQWpwjjZDOGSURTm1VTuKcjHtSOPo3JMkUDE4xl0EU3rpuTffJVhDUSnaOVmD-Yp4/s1600/420bar.png'/></a>
</div>
<div class='app-panel' id='promo-banner-container'>
<div class='promo-banner-section' id='promo-banner-left'>
<a class='promo-banner-icon' href='https://www.facebook.com/TheRadio420' target='_blank' title='Facebook'><svg viewBox='0 0 24 24'><path d='M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95z'></path></svg></a>
<a class='promo-banner-icon' href='https://www.instagram.com/TheRadio420' target='_blank' title='Instagram'><svg viewBox='0 0 24 24'><path d='M7.8 2h8.4C19.4 2 22 4.6 22 7.8v8.4a5.8 5.8 0 0 1-5.8 5.8H7.8C4.6 22 2 19.4 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2m-.2 2A3.6 3.6 0 0 0 4 7.6v8.8C4 18.39 5.61 20 7.6 20h8.8a3.6 3.6 0 0 0 3.6-3.6V7.6C20 5.61 18.39 4 16.4 4H7.6m9.65 1.5a1.25 1.25 0 0 1 1.25 1.25A1.25 1.25 0 1 1 16 6.75a1.25 1.25 0 0 1 1.25-1.25M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3z'></path></svg></a>
<a class='promo-banner-icon' href='https://x.com/radio420' target='_blank' title='X'><svg viewBox='0 0 24 24'><path d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'></path></svg></a>
<a class='promo-banner-icon' href='https://bsky.app/profile/radio420.bsky.social' target='_blank' title='BlueSky'><svg aria-hidden='true' viewBox='0 0 64 64'>
<path d='M32 29.7c4.4-7.9 12-14.6 19.1-17.6 5.1-2.2 9.9-2.5 10.1 3.4.2 4.7-3.1 10.8-5.4 13.6-3.6 4.4-9 7.5-14.5 8.5 6.5.9 12.1 4.3 15.5 9 2.1 2.9 4.8 8.6 3.7 12-1.1 3.2-5.9 3.2-10.9 1.2-7.1-3-14.6-10.3-17.6-15.7-3 5.4-10.5 12.7-17.6 15.7-5 2.1-9.8 2-10.9-1.2-1.1-3.4 1.6-9.1 3.7-12 3.4-4.7 9-8.1 15.5-9-5.5-1-10.9-4.1-14.5-8.5C5.9 26.3 2.6 20.2 2.8 15.5c.2-5.9 5-5.6 10.1-3.4 7.1 3 14.7 9.7 19.1 17.6z' fill='currentColor'></path>
</svg></a>
<a class='promo-banner-icon' href='https://youtube.com/RadioFourTwenty' target='_blank' title='YouTube'><svg viewBox='0 0 24 24'><path d='M10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83-.25.9-.83 1.48-1.73 1.73-.47.13-1.33.22-2.65.28-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44-.9-.25-1.48-.83-1.73-1.73-.13-.47-.22-1.1-.28-1.9-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83.25-.9.83-1.48 1.73-1.73z'></path></svg></a>
</div>
<div class='promo-banner-section' id='promo-banner-center'>
<a href='https://stonerway.net' rel='noopener noreferrer' target='_blank'><p><span>StonerWay.net:</span> The stoniest place to get unique merch designs!</p></a>
<button class='promo-button' id='promo-button' title='Advertise Here'>Ad</button>
</div>
<div class='promo-banner-section' id='promo-banner-right'>
<button class='promo-banner-icon' id='share-button' title='Share'><svg fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'><path d='M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8'></path><polyline points='16 6 12 2 8 6'></polyline><line x1='12' x2='12' y1='2' y2='15'></line></svg></button>
<button class='promo-banner-icon' id='info-button' title='More Info'><svg viewBox='0 0 24 24'><path d='M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z'></path></svg></button>
</div>
</div>
<div id='main-content-container'>
<div class='app-panel' id='station-list-container'>
<div class='station-tabs'>
<button class='tab-button active' data-tab-target='#stations-panel' data-tab-type='live'>Live</button>
<button class='tab-button' data-tab-target='#stations-panel' data-tab-type='youtube'>YouTube</button>
<button class='tab-button' data-tab-target='#stations-panel' data-tab-type='spotify'>Spotify</button>
<button class='tab-button' data-tab-target='#news-panel'>News</button>
<button class='tab-button' data-tab-target='#chat-panel'>Chat</button>
</div>
<div class='tab-panels'>
<div class='tab-panel active' id='stations-panel'>
<div class='list-scroll-container'>
<p style='text-align:center; padding: 2rem;'>Loading Stations...</p>
</div>
</div>
<div class='tab-panel' id='news-panel'>
<div class='list-scroll-container'>
<p style='text-align:center; padding: 2rem;'>Loading News...</p>
</div>
</div>
<div class='tab-panel' id='chat-panel'>
<iframe allow='camera;microphone;display-capture;fullscreen' src='https://chitchatter.im/public/Radio420?embed=1' title='Radio 420 Chat'></iframe>
</div>
</div>
</div>
<div class='app-panel' id='player-container'>
<button class='player-expand-toggle' id='player-expand-toggle' title='Expand Player'>
<svg aria-hidden='true' fill='currentColor' viewBox='0 0 24 24'><path d='M12 15.5l-6-6h12l-6 6z'></path></svg>
</button>
<div class='player-art-wrapper'>
<div class='player-glow' id='player-glow'></div>
<img alt='Album Art' id='player-art' src='https://4.bp.blogspot.com/-HmQibrxJmaA/TxPBIWrhxWI/AAAAAAAABWw/a0IYAEdN02U/s6000/radio420-600x600-04.jpg'/>
</div>
<div class='player-info'>
<h2 id='player-station-name'>Select a station</h2>
<p id='player-station-genre'></p>
</div>
<div class='player-controls'>
<button class='player-button' id='prev-btn' title='Previous Station'>
<svg fill='currentColor' viewBox='0 0 20 20'><path d='M8.445 14.832A1 1 0 0010 14.118V5.882a1 1 0 00-1.555-.832L3.387 9.168a1 1 0 000 1.664l5.058 4.001zM11.555 5.05A1 1 0 0113 5.882v8.236a1 1 0 01-1.445.894l-5.058-4.001a1 1 0 010-1.788l5.058-4.001z'></path></svg>
</button>
<button class='player-button' id='play-pause-btn' title='Play'></button>
<button class='player-button' id='next-btn' title='Next Station'>
<svg fill='currentColor' viewBox='0 0 20 20'><path d='M11.555 5.168A1 1 0 0010 5.882v8.236a1 1 0 001.555.832l5.058-4.001a1 1 0 000-1.664l-5.058-4.001zM8.445 14.95A1 1 0 017 14.118V5.882a1 1 0 011.445-.894l5.058 4.001a1 1 0 010 1.788l-5.058 4.001z'></path></svg>
</button>
</div>
<div class='volume-controls'>
<svg aria-hidden='true' style='width:1.5rem;height:1.5rem;color:#9ca3af;' viewBox='0 0 24 24'>
<path d='M3 10v4a1 1 0 001 1h3l4.293 3.293A1 1 0 0013 17V7a1 1 0 00-1.707-.707L7 9H4a1 1 0 00-1 1z' fill='currentColor'></path>
</svg>
<input id='volume-slider' max='1' min='0' step='0.01' title='Volume' type='range' value='1'/>
</div>
</div>
</div>
</div>
<div class='popup-overlay' id='promo-popup'>
<div class='popup-content'>
<button class='popup-close-btn'>
<svg fill='currentColor' viewBox='0 0 20 20'><path clip-rule='evenodd' d='M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z' fill-rule='evenodd'></path></svg>
</button>
<h3>Advertise on Radio 420</h3>
<p>Want to reach a dedicated audience of music lovers and cannabis enthusiasts? We offer competitive rates for ad placements within our app and on our streams.</p>
<p>Please contact our marketing team at <strong>advertise@radio420.net</strong> for more information on our packages and rates. We'd love to partner with you!</p>
</div>
</div>
<div class='popup-overlay' id='info-popup'>
<div class='popup-content'>
<button class='popup-close-btn'>
<svg fill='currentColor' viewBox='0 0 20 20'><path clip-rule='evenodd' d='M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z' fill-rule='evenodd'></path></svg>
</button>
<h3>About Radio 420</h3>
<p>Radio 420 is a community-driven, online radio station dedicated to providing the best tunes for your session. We curate a wide variety of genres, from chill lo-fi beats to heavy metal, ensuring there's always something for every mood.</p>
<p>This application was developed to bring all of our content—live streams, playlists, news, and community chat—into one convenient place. Enjoy the vibes!</p>
</div>
</div>
<div class='navbar no-items section' id='navbar'></div>
<footer style='display:none;'></footer>
<script type='text/javascript'>
// <![CDATA[
document.addEventListener('DOMContentLoaded', () => {
// --- GLOBAL APP STATE & DOM REFERENCES ---
const AppState = {
stations: [],
news: [],
activeStationTab: 'live',
currentStation: null,
isPlaying: false,
isLoading: false,
loadingTimeout: null
};
const DOM = {
// Main App
allTabs: document.querySelectorAll('.tab-button'),
allPanels: document.querySelectorAll('.tab-panel'),
stationListContainer: document.querySelector('#stations-panel .list-scroll-container'),
newsListContainer: document.querySelector('#news-panel .list-scroll-container'),
// Player
audioPlayer: document.getElementById('audio-player'),
playerArt: document.getElementById('player-art'),
playerGlow: document.getElementById('player-glow'),
stationName: document.getElementById('player-station-name'),
stationGenre: document.getElementById('player-station-genre'),
playPauseBtn: document.getElementById('play-pause-btn'),
volumeSlider: document.getElementById('volume-slider'),
prevBtn: document.getElementById('prev-btn'),
nextBtn: document.getElementById('next-btn'),
playerContainer: document.getElementById('player-container'),
expandToggle: document.getElementById('player-expand-toggle'),
// Popups & Banner Buttons
infoButton: document.getElementById('info-button'),
infoPopup: document.getElementById('info-popup'),
adButton: document.getElementById('promo-button'),
adPopup: document.getElementById('promo-popup'),
shareButton: document.getElementById('share-button'),
popupCloseBtns: document.querySelectorAll('.popup-close-btn'),
popupOverlays: document.querySelectorAll('.popup-overlay')
};
// Ensure a container exists for YouTube IFrame (we keep it hidden)
let ytContainer = document.getElementById('yt-player');
if (!ytContainer) {
ytContainer = document.createElement('div');
ytContainer.id = 'yt-player';
ytContainer.style.display = 'none';
document.body.appendChild(ytContainer);
}
// --- ICONS ---
const playIcon = `<svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.14v13.72a1 1 0 001.52.85l10.01-6.86a1 1 0 000-1.7L9.52 4.29A1 1 0 008 5.14z"/></svg>`;
const pauseIcon = `<svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 5a1 1 0 011-1h2a1 1 0 011 1v14a1 1 0 01-1 1H7a1 1 0 01-1-1V5zm8 0a1 1 0 011-1h2a1 1 0 011 1v14a1 1 0 01-1 1h-2a1 1 0 01-1-1V5z"/></svg>`;
const loadingSpinner = `<svg style="width:2rem;height:2rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" style="opacity:0.25;"></circle><path d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" fill="currentColor" style="opacity:0.75;"></path></svg>`;
// --- YOUTUBE BACKEND ---
let ytPlayer = null;
let ytAPIReadyPromise = null;
function loadYouTubeAPIOnce() {
if (ytAPIReadyPromise) return ytAPIReadyPromise;
ytAPIReadyPromise = new Promise((resolve) => {
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
window.onYouTubeIframeAPIReady = () => resolve();
document.head.appendChild(tag);
});
return ytAPIReadyPromise;
}
function ensureYTPlayer() {
return loadYouTubeAPIOnce().then(() => {
if (ytPlayer) return ytPlayer;
ytPlayer = new YT.Player('yt-player', {
width: 300,
height: 150,
playerVars: {
autoplay: 0,
controls: 0,
modestbranding: 1,
rel: 0
},
events: {
onReady: () => { /* sync initial volume */ setYTVolumeFromSlider(); },
onStateChange: (e) => {
// -1 unstarted, 0 ended, 1 playing, 2 paused, 3 buffering, 5 cued
if (e.data === YT.PlayerState.PLAYING) updatePlayPauseUI('playing');
else if (e.data === YT.PlayerState.PAUSED) updatePlayPauseUI('paused');
else if (e.data === YT.PlayerState.BUFFERING || e.data === YT.PlayerState.UNSTARTED) updatePlayPauseUI('loading');
},
onError: () => updatePlayPauseUI('error')
}
});
return ytPlayer;
});
}
function parseYouTube(url) {
// Returns {playlistId?, videoId?}
try {
const u = new URL(url);
const list = u.searchParams.get('list');
const v = u.searchParams.get('v');
if (list) return { playlistId: list };
if (v) return { videoId: v };
// youtu.be/<id> or shorts
const path = u.pathname.replace(/^\/+/, '');
if (u.hostname.includes('youtu.be') && path) return { videoId: path };
if (u.hostname.includes('youtube.com') && path.startsWith('shorts/')) {
return { videoId: path.split('/')[1] || '' };
}
} catch (e) {
// if not a full URL, maybe it's already an id
if (/^[\w-]{8,}$/.test(url)) return { videoId: url };
}
return {};
}
async function playYouTube(url) {
updatePlayPauseUI('loading');
const player = await ensureYTPlayer();
// pause/clear HTML audio backend
try { DOM.audioPlayer.pause(); } catch (e) {}
const { playlistId, videoId } = parseYouTube(url);
if (playlistId) {
player.loadPlaylist({ list: playlistId });
player.playVideo();
} else if (videoId) {
player.loadVideoById(videoId);
} else {
updatePlayPauseUI('error');
console.error('Unrecognized YouTube URL:', url);
return;
}
// show container (still visually hidden via CSS; we don't render it)
ytContainer.style.display = 'block';
}
function pauseYouTube() {
if (ytPlayer && ytPlayer.pauseVideo) ytPlayer.pauseVideo();
}
function stopYouTube() {
if (ytPlayer && ytPlayer.stopVideo) ytPlayer.stopVideo();
}
function isYouTubePlaying() {
return ytPlayer && ytPlayer.getPlayerState && ytPlayer.getPlayerState() === YT.PlayerState.PLAYING;
}
function setYTVolumeFromSlider() {
if (!ytPlayer || !ytPlayer.setVolume) return;
const v = parseFloat(DOM.volumeSlider.value || '1');
ytPlayer.setVolume(Math.round(v * 100)); // YT is 0–100
}
// --- DATA FETCHING ---
async function fetchDataFromSheet(sheetName) {
const sheetId = '1nc2e54Xok9sHxEX8m5D3B9N9H8xsP29yCw4CXuIVcrg';
const url = `https://docs.google.com/spreadsheets/d/${sheetId}/gviz/tq?tqx=out:json&sheet=${encodeURIComponent(sheetName)}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`Network error fetching '${sheetName}'`);
let text = await response.text();
const match = text.match(/google\.visualization\.Query\.setResponse\((.*)\)/s);
const jsonString = match ? match[1] : '{}';
const data = JSON.parse(jsonString);
if (!data.table || !data.table.rows) return [];
const headers = data.table.cols.map(col => (col.label || '').toLowerCase().replace(/\s/g, ''));
return data.table.rows.map(row => {
const item = {};
(row.c || []).forEach((cell, index) => {
const header = headers[index];
if (header) item[header] = cell ? cell.v : '';
});
if (Object.prototype.hasOwnProperty.call(item, 'onair')) {
item.onair = String(item.onair).toLowerCase() === 'true';
}
return item;
});
} catch (error) {
console.error(`Error processing sheet "${sheetName}":`, error);
DOM.stationListContainer.innerHTML = `<p style="text-align:center;color:#f87171;">Error: Could not load station data.<br/>Sheet name might be incorrect or not public.</p>`;
return [];
}
}
async function fetchNewsFromBlog() {
return new Promise((resolve, reject) => {
const callbackName = 'onNewsFeedLoaded';
window[callbackName] = function(json) {
try {
const newsItems = ((json.feed || {}).entry || []).map(entry => {
const postContent = entry.content.$t;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = postContent;
const linkTag = tempDiv.querySelector('a');
const imageTag = tempDiv.querySelector('img');
return {
title: entry.title.$t,
link: linkTag ? linkTag.href : ((entry.link || []).find(l => l.rel === 'alternate') || {}).href || '#',
pubDate: entry.published.$t,
imageUrl: imageTag ? imageTag.src : null
};
});
resolve(newsItems);
} catch (e) {
reject(e);
} finally {
delete window[callbackName];
const scriptTag = document.getElementById('news-fetch-script');
if (scriptTag) scriptTag.parentNode.removeChild(scriptTag);
}
};
const script = document.createElement('script');
script.id = 'news-fetch-script';
script.src = `/feeds/posts/default/-/News?alt=json-in-script&callback=${callbackName}&max-results=20`;
script.onerror = reject;
document.body.appendChild(script);
});
}
// --- UI RENDERING ---
function renderStationList(tabType) {
const stationsForTab = AppState.stations.filter(s => s.stationtype === tabType);
DOM.stationListContainer.innerHTML = stationsForTab.length === 0
? '<p style="text-align:center; padding: 2rem;">No stations in this category.</p>'
: stationsForTab.map(station => `
<div class="station-item" data-station-id="${station.id}">
<img class="station-logo" src="${station.logo}" alt="" onerror="this.src='https://4.bp.blogspot.com/-HmQibrxJmaA/TxPBIWrhxWI/AAAAAAAABWw/a0IYAEdN02U/s6000/radio420-600x600-04.jpg'"/>
<div class="station-info">
<div class="station-name">${station.name}</div>
<div class="station-genre">${station.genre}</div>
</div>
<div class="station-extras">
${station.website ? `<a href="${station.website}" target="_blank" class="promo-banner-icon" onclick="event.stopPropagation()" title="Visit Website"><svg fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg></a>` : ''}
${station.countrycode ? `<img class="flag-icon" src="https://flagcdn.com/w20/${String(station.countrycode).toLowerCase().trim()}.png" alt="${station.countrycode}"/>` : ''}
</div>
</div>`).join('');
}
function renderNewsList() {
if (!AppState.news || AppState.news.length === 0) {
DOM.newsListContainer.innerHTML = '<p style="text-align:center; padding: 2 rem;">No posts found with the "News" label.</p>';
return;
}
DOM.newsListContainer.innerHTML = AppState.news.map(item => `
<a class="news-item" href="${item.link}" target="_blank" rel="noopener noreferrer">
<img src="${item.imageUrl || 'https://4.bp.blogspot.com/-HmQibrxJmaA/TxPBIWrhxWI/AAAAAAAABWw/a0IYAEdN02U/s6000/radio420-600x600-04.jpg'}" alt="" onerror="this.src='https://4.bp.blogspot.com/-HmQibrxJmaA/TxPBIWrhxWI/AAAAAAAABWw/a0IYAEdN02U/s6000/radio420-600x600-04.jpg'"/>
<div class="news-info">
<div class="news-title">${item.title}</div>
<div class="news-date">${new Date(item.pubDate).toLocaleString()}</div>
</div>
</a>
`).join('');
}
function renderPlayer() {
const station = AppState.currentStation;
if (!station) return;
DOM.playerArt.src = station.logo || 'https://4.bp.blogspot.com/-HmQibrxJmaA/TxPBIWrhxWI/AAAAAAAABWw/a0IYAEdN02U/s6000/radio420-600x600-04.jpg';
DOM.stationName.textContent = station.name;
DOM.stationGenre.textContent = station.genre;
const currentActive = document.querySelector('.station-item.active');
if (currentActive) currentActive.classList.remove('active');
const newActive = document.querySelector(`.station-item[data-station-id="${station.id}"]`);
if (newActive) newActive.classList.add('active');
}
function updatePlayPauseUI(state) {
clearTimeout(AppState.loadingTimeout);
AppState.isPlaying = (state === 'playing');
AppState.isLoading = (state === 'loading');
if (state === 'playing') {
DOM.playPauseBtn.innerHTML = pauseIcon;
DOM.playerArt.classList.add('spinning');
DOM.playerGlow.style.display = 'block';
} else if (state === 'paused') {
DOM.playPauseBtn.innerHTML = playIcon;
DOM.playerArt.classList.remove('spinning');
DOM.playerGlow.style.display = 'none';
} else if (state === 'loading') {
DOM.playPauseBtn.innerHTML = loadingSpinner;
} else if (state === 'error') {
DOM.playPauseBtn.innerHTML = playIcon;
DOM.stationGenre.textContent = 'Stream Error or Offline';
DOM.stationGenre.style.color = '#f87171';
}
}
// --- PLAYER LOGIC ---
function getStationsInActiveTab() { return AppState.stations.filter(s => s.stationtype === AppState.activeStationTab); }
function selectStationByIndex(index) {
const list = getStationsInActiveTab();
if (list.length === 0) return;
const safeIndex = ((typeof index === 'number' ? index : 0) + list.length) % list.length;
AppState.currentStation = list[safeIndex];
DOM.stationGenre.style.color = '';
renderPlayer();
playStation(AppState.currentStation);
}
function goNextStation() {
const list = getStationsInActiveTab();
if (list.length === 0) return;
let currentIndex = -1;
if (AppState.currentStation) currentIndex = list.findIndex(s => s.id === AppState.currentStation.id);
const nextIndex = (currentIndex + 1 + list.length) % list.length;
selectStationByIndex(nextIndex);
}
function goPrevStation() {
const list = getStationsInActiveTab();
if (list.length === 0) return;
let currentIndex = -1;
if (AppState.currentStation) currentIndex = list.findIndex(s => s.id === AppState.currentStation.id);
const prevIndex = (currentIndex - 1 + list.length) % list.length;
selectStationByIndex(prevIndex);
}
function handleStationSelect(stationId) {
const station = AppState.stations.find(s => s.id === stationId);
if (!station) return;
if (AppState.currentStation && AppState.currentStation.id === stationId) {
togglePlayPause();
return;
}
AppState.currentStation = station;
DOM.stationGenre.style.color = '';
playStation(station);
renderPlayer();
}
function stopOtherBackends(currentType) {
if (currentType !== 'live' && currentType !== 'test') {
try { DOM.audioPlayer.pause(); } catch (e) {}
}
if (currentType !== 'youtube') {
stopYouTube();
}
}
function playStation(station) {
stopOtherBackends(station.stationtype);
if (station.stationtype === 'live' || station.stationtype === 'test') {
updatePlayPauseUI('loading');
DOM.audioPlayer.src = station.url;
DOM.audioPlayer.play().catch(() => updatePlayPauseUI('error'));
AppState.loadingTimeout = setTimeout(() => {
if (AppState.isLoading) updatePlayPauseUI('error');
}, 10000);
} else if (station.stationtype === 'youtube') {
playYouTube(station.url);
} else if (station.stationtype === 'spotify') {
alert('Spotify playback not implemented yet.');
updatePlayPauseUI('paused');
} else {
alert(station.stationtype + ' player not implemented yet.');
updatePlayPauseUI('paused');
}
}
function togglePlayPause() {
if (!AppState.currentStation) {
const list = getStationsInActiveTab();
if (list.length > 0) {
AppState.currentStation = list[0];
renderPlayer();
playStation(AppState.currentStation);
}
return;
}
const type = AppState.currentStation.stationtype;
if (type === 'live' || type === 'test') {
if (DOM.audioPlayer.paused) {
DOM.audioPlayer.play().catch(() => updatePlayPauseUI('error'));
} else {
DOM.audioPlayer.pause();
}
} else if (type === 'youtube') {
ensureYTPlayer().then(() => {
if (isYouTubePlaying()) ytPlayer.pauseVideo();
else ytPlayer.playVideo();
});
}
}
// --- INITIALIZATION & EVENT LISTENERS ---
async function initializeApp() {
// Tabs
DOM.allTabs.forEach(tab => {
tab.addEventListener('click', () => {
DOM.allTabs.forEach(t => t.classList.remove('active'));
DOM.allPanels.forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.querySelector(tab.dataset.tabTarget).classList.add('active');
if (tab.dataset.tabType) {
AppState.activeStationTab = tab.dataset.tabType;
renderStationList(AppState.activeStationTab);
const list = getStationsInActiveTab();
if (!AppState.currentStation || !list.some(s => s.id === AppState.currentStation.id)) {
AppState.currentStation = null;
DOM.stationName.textContent = 'Select a station';
DOM.stationGenre.textContent = '';
updatePlayPauseUI('paused');
}
}
});
});
// Player controls
DOM.playPauseBtn.addEventListener('click', togglePlayPause);
DOM.prevBtn.addEventListener('click', goPrevStation);
DOM.nextBtn.addEventListener('click', goNextStation);
// Expand/unexpand
if (DOM.expandToggle) {
DOM.expandToggle.addEventListener('click', () => {
DOM.playerContainer.classList.toggle('expanded');
});
}
// Volume (map to both backends)
DOM.volumeSlider.addEventListener('input', e => {
const val = parseFloat(e.target.value || '1');
DOM.audioPlayer.volume = val;
setYTVolumeFromSlider();
});
// Audio events
DOM.audioPlayer.addEventListener('playing', () => updatePlayPauseUI('playing'));
DOM.audioPlayer.addEventListener('pause', () => updatePlayPauseUI('paused'));
DOM.audioPlayer.addEventListener('waiting', () => updatePlayPauseUI('loading'));
DOM.audioPlayer.addEventListener('error', () => updatePlayPauseUI('error'));
// Popups & share
if (DOM.infoButton) DOM.infoButton.addEventListener('click', () => { DOM.infoPopup.style.display = 'flex'; });
if (DOM.adButton) DOM.adButton.addEventListener('click', () => { DOM.adPopup.style.display = 'flex'; });
if (DOM.shareButton) {
DOM.shareButton.addEventListener('click', async () => {
const shareData = { title: 'Radio 420', text: 'Tune in to Radio 420 for the best vibes!', url: window.location.href };
if (navigator.share) {
try { await navigator.share(shareData); } catch (err) { console.error('Share failed:', err); }
} else {
try { await navigator.clipboard.writeText(window.location.href); alert('Link copied to clipboard!'); }
catch (err) { console.error('Failed to copy: ', err); alert('Could not copy link.'); }
}
});
}
// Close popups
DOM.popupCloseBtns.forEach(btn => {
btn.addEventListener('click', (e) => { e.target.closest('.popup-overlay').style.display = 'none'; });
});
DOM.popupOverlays.forEach(overlay => {
overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.style.display = 'none'; } });
});
// Station list delegation
DOM.stationListContainer.addEventListener('click', (event) => {
const stationItem = event.target.closest('.station-item');
if (stationItem) handleStationSelect(stationItem.dataset.stationId);
});
// Fetch data
const [stationsData, spotifyData, youtubeData, newsData] = await Promise.all([
fetchDataFromSheet('stations'),
fetchDataFromSheet('spotify'),
fetchDataFromSheet('youtube'),
fetchNewsFromBlog()
]);
AppState.stations = [...stationsData, ...spotifyData, ...youtubeData];
AppState.news = newsData;
// Initial render
renderStationList(AppState.activeStationTab);
renderNewsList();
updatePlayPauseUI('paused');
if (DOM.playPauseBtn && DOM.playPauseBtn.innerHTML.trim() === '') DOM.playPauseBtn.innerHTML = playIcon;
}
initializeApp();
});
// ]]>
</script>
<audio crossorigin='anonymous' id='audio-player' style='display:none;'></audio>
<script type="text/javascript" src="https://www.blogger.com/static/v1/widgets/4290755334-widgets.js"></script>
<script type='text/javascript'>
window['__wavt'] = 'ACvIOeAiIwoBpJE-0RnSC3CU-5l8:1787418645224';_WidgetManager._Init('//www.blogger.com/rearrange?blogID\x3d6097148778129698866','//www.radio420.net/','6097148778129698866');
_WidgetManager._SetDataContext([{'name': 'blog', 'data': {'blogId': '6097148778129698866', 'title': 'Radio420\u2122', 'url': 'http://www.radio420.net/', 'canonicalUrl': 'http://www.radio420.net/', 'homepageUrl': 'http://www.radio420.net/', 'searchUrl': 'http://www.radio420.net/search', 'canonicalHomepageUrl': 'http://www.radio420.net/', 'blogspotFaviconUrl': 'http://www.radio420.net/favicon.ico', 'bloggerUrl': 'https://www.blogger.com', 'hasCustomDomain': true, 'httpsEnabled': true, 'enabledCommentProfileImages': true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber': '', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en', 'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest': false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable': true, 'feedLinks': '\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Radio420\u2122 - Atom\x22 href\x3d\x22http://www.radio420.net/feeds/posts/default\x22 /\x3e\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/rss+xml\x22 title\x3d\x22Radio420\u2122 - RSS\x22 href\x3d\x22http://www.radio420.net/feeds/posts/default?alt\x3drss\x22 /\x3e\n\x3clink rel\x3d\x22service.post\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Radio420\u2122 - Atom\x22 href\x3d\x22https://www.blogger.com/feeds/6097148778129698866/posts/default\x22 /\x3e\n', 'meTag': '', 'adsenseClientId': 'ca-pub-9725009255532012', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds': false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam': '', 'isGoogleEverywhereLinkTooltipEnabled': true, 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js', 'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/24810df02073da6c', 'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments': true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name': 'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target': 'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!', 'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage': 'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email', 'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth': 0, 'googlePlusBootstrap': '\x3cscript type\x3d\x22text/javascript\x22\x3ewindow.___gcfg \x3d {\x27lang\x27: \x27en\x27};\x3c/script\x3e'}, 'hasCustomJumpLinkMessage': true, 'jumpLinkMessage': 'READ MORE AND GET LINK', 'pageType': 'index', 'pageName': '', 'pageTitle': 'Radio420\u2122'}}, {'name': 'features', 'data': {}}, {'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template', 'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false, 'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data': {'classic': {'name': 'classic', 'url': '?view\x3dclassic'}, 'flipcard': {'name': 'flipcard', 'url': '?view\x3dflipcard'}, 'magazine': {'name': 'magazine', 'url': '?view\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\x3dmosaic'}, 'sidebar': {'name': 'sidebar', 'url': '?view\x3dsidebar'}, 'snapshot': {'name': 'snapshot', 'url': '?view\x3dsnapshot'}, 'timeslide': {'name': 'timeslide', 'url': '?view\x3dtimeslide'}, 'isMobile': false, 'title': 'Radio420\u2122', 'description': 'BLAZiN ALL THE HiTS', 'url': 'http://www.radio420.net/', 'type': 'feed', 'isSingleItem': false, 'isMultipleItems': true, 'isError': false, 'isPage': false, 'isPost': false, 'isHomepage': true, 'isArchive': false, 'isLabelSearch': false}}]);
</script>
</body>
</html>
Sitemap
Кол-во: 0
XML-карта сайта для поисковиков
?
Sitemap.xml помогает поисковику быстрее находить и индексировать страницы. Особенно важен для крупных сайтов и новых страниц, на которые ещё нет входящих ссылок.
Robots.txt не содержит ссылку на карту сайта. Рекомендуется добавить карту сайта и указать ссылку на нее в robots.txt.
Внутренние ссылки
Не найдено
Ссылки на другие страницы своего сайта
?
Внутренние ссылки распределяют ссылочный вес между страницами и помогают поисковику обходить сайт. Пустые анкоры и ссылки на запрещённые robots.txt страницы — типичные ошибки.
Внутренних ссылок на странице 0 оптимально.
На странице присутствуют изображения 1.
Внешние ссылки
Кол-во: 6
Ссылки на сторонние сайты
?
Исходящие внешние ссылки передают часть ссылочного веса на чужие сайты. Ссылки на авторитетные ресурсы безопасны; ссылки на мусорные сайты могут навредить репутации страницы.
Внешних ссылок на странице 6 слишком много. Спрячьте лишние ссылки в тег noindex или атрибут rel='nofollow'!
Показать внешние ссылки
| Url | Анкор | Анализировать |
|---|---|---|
| facebook.com |
<svg viewbox='0 0 24 24'><path d='M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95z'></path></svg>
|
|
| instagram.com |
<svg viewbox='0 0 24 24'><path d='M7.8 2h8.4C19.4 2 22 4.6 22 7.8v8.4a5.8 5.8 0 0 1-5.8 5.8H7.8C4.6 22 2 19.4 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2m-.2 2A3.6 3.6 0 0 0 4 7.6v8.8C4 18.39 5.61 20 7.6 20h8.8a3.6 3.6 0 0 0 3.6-3.6V7.6C20 5.61 18.39 4 16.4 4H7.6m9.65 1.5a1.25 1.25 0 0 1 1.25 1.25A1.25 1.25 0 1 1 16 6.75a1.25 1.25 0 0 1 1.25-1.25M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3z'></path></svg>
|
|
| x.com |
<svg viewbox='0 0 24 24'><path d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'></path></svg>
|
Анализировать url |
| bsky.app |
<svg aria-hidden='true' viewbox='0 0 64 64'>
<path d='M32 29.7c4.4-7.9 12-14.6 19.1-17.6 5.1-2.2 9.9-2.5 10.1 3.4.2 4.7-3.1 10.8-5.4 13.6-3.6 4.4-9 7.5-14.5 8.5 6.5.9 12.1 4.3 15.5 9 2.1 2.9 4.8 8.6 3.7 12-1.1 3.2-5.9 3.2-10.9 1.2-7.1-3-14.6-10.3-17.6-15.7-3 5.4-10.5 12.7-17.6 15.7-5 2.1-9.8 2-10.9-1.2-1.1-3.4 1.6-9.1 3.7-12 3.4-4.7 9-8.1 15.5-9-5.5-1-10.9-4.1-14.5-8.5C5.9 26.3 2.6 20.2 2.8 15.5c.2-5.9 5-5.6 10.1-3.4 7.1 3 14.7 9.7 19.1 17.6z' fill='currentColor'></path>
</svg>
|
Анализировать url |
| youtube.com |
<svg viewbox='0 0 24 24'><path d='M10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83-.25.9-.83 1.48-1.73 1.73-.47.13-1.33.22-2.65.28-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44-.9-.25-1.48-.83-1.73-1.73-.13-.47-.22-1.1-.28-1.9-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83.25-.9.83-1.48 1.73-1.73z'></path></svg>
|
Анализировать url |
| stonerway.net |
<p><span>StonerWay.net:</span> The stoniest place to get unique merch designs!</p>
|
Анализировать url |
Конкуренты Готовность: 0%
Конкуренты в Яндексе
Кол-во: 0
Топ сайтов-конкурентов в Яндексе
?
Сайты, чаще всего появляющиеся в ТОПе Яндекса по запросам из семантического ядра этой страницы.
Мы не нашли у вас конкурентов в Яндексе. Сайт или очень молодой или плохо продвигается.
Конкурентов в ТОП-10 Яндекса не нашлось.
Конкуренты в Google
Кол-во: 0
Топ сайтов-конкурентов в Google
?
Сайты, чаще всего появляющиеся в ТОПе Google по запросам из семантического ядра этой страницы.
Конкуренты в Google тоже не найдены. Займитесь продвижением сайта!
Конкурентов в ТОП-10 Google не нашлось.
ЗоЗПП: права потребителей Готовность: 100%
Нарушения
Не выявлены
Признаков дистанционной продажи товаров (интернет-магазина) не обнаружено — требования ЗоЗПП о раскрытии информации продавца к сайту не применяются. Нарушений нет.
ФЗ-149: рекомендательные технологии Готовность: 100%
Нарушения
Не выявлены
Рекомендательные блоки («с этим покупают», «похожие товары» и т.п.) на сайте не обнаружены — требования ст. 10.7 ФЗ-149 к сайту не применяются. Нарушений нет.
ФЗ-38: реклама Готовность: 100%
Нарушения
Не выявлены
Рекламных тематик с обязательными оговорками (медицина, БАД, кредиты и займы, новостройки) на сайте не обнаружено. Нарушений нет.
ФЗ-436: защита детей Готовность: 100%
Нарушения
Не выявлены
Признаков информационной продукции (новости, видео, книги, игры, курсы) не обнаружено — обязательная возрастная маркировка по ФЗ-436 сайту не требуется. Нарушений нет.
Вердикт
Сайт radio420.net не совсем готов к продвижению (процент готовности лишь 42%). Для попадания в ТОПы поисковых систем нужно:
Постарайтесь исправить общие ошибки.
Исправьте ошибки в мета-тегах.
Исправьте ошибки оптимизации.
Исправьте ошибки индексации.
Поделитесь с друзьями: