Refine mobile layout and PWA paths
This commit is contained in:
@@ -8,6 +8,7 @@ const CACHE_NAME = 'radioplayer-pwa-v4';
|
|||||||
const CORE_ASSETS = [
|
const CORE_ASSETS = [
|
||||||
'./',
|
'./',
|
||||||
'index.html',
|
'index.html',
|
||||||
|
'data/radio-stations.json',
|
||||||
'stations.json',
|
'stations.json',
|
||||||
'manifest.json',
|
'manifest.json',
|
||||||
'assets/radioplayer-logo-192.png',
|
'assets/radioplayer-logo-192.png',
|
||||||
|
|||||||
135
src/player.js
135
src/player.js
@@ -34,10 +34,14 @@ let stationCatalogState = 'idle';
|
|||||||
let stationCatalogError = '';
|
let stationCatalogError = '';
|
||||||
let playbackError = '';
|
let playbackError = '';
|
||||||
let stationCountryFilterOpen = false;
|
let stationCountryFilterOpen = false;
|
||||||
|
let artworkShineTimeoutId = null;
|
||||||
|
let artworkShineClearTimeoutId = null;
|
||||||
|
let stationTitleRafId = null;
|
||||||
|
|
||||||
const STATION_LIBRARY_PAGE_SIZE = 12;
|
const STATION_LIBRARY_PAGE_SIZE = 12;
|
||||||
|
const ARTWORK_SHINE_EFFECTS = ['shine-sweep', 'shine-glint', 'shine-flare'];
|
||||||
|
|
||||||
const RADIO_PLACEHOLDER_LOGO = '/images/radio-placeholder.svg';
|
const RADIO_PLACEHOLDER_LOGO = `${import.meta.env.BASE_URL}images/radio-placeholder.svg`;
|
||||||
|
|
||||||
// UI Elements
|
// UI Elements
|
||||||
const stationNameEl = document.getElementById('station-name');
|
const stationNameEl = document.getElementById('station-name');
|
||||||
@@ -112,6 +116,54 @@ const castOutputText = document.getElementById('cast-output-text');
|
|||||||
|
|
||||||
// ── Utilities ────────────────────────────────────────────────────────────────
|
// ── Utilities ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function prefersReducedMotion() {
|
||||||
|
try {
|
||||||
|
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearArtworkShineTimers() {
|
||||||
|
if (artworkShineTimeoutId) {
|
||||||
|
clearTimeout(artworkShineTimeoutId);
|
||||||
|
artworkShineTimeoutId = null;
|
||||||
|
}
|
||||||
|
if (artworkShineClearTimeoutId) {
|
||||||
|
clearTimeout(artworkShineClearTimeoutId);
|
||||||
|
artworkShineClearTimeoutId = null;
|
||||||
|
}
|
||||||
|
if (artworkPlaceholder) {
|
||||||
|
artworkPlaceholder.classList.remove(...ARTWORK_SHINE_EFFECTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleArtworkShine() {
|
||||||
|
if (!artworkPlaceholder || prefersReducedMotion()) return;
|
||||||
|
|
||||||
|
const trigger = () => {
|
||||||
|
artworkShineTimeoutId = null;
|
||||||
|
clearArtworkShineTimers();
|
||||||
|
|
||||||
|
if (!artworkPlaceholder || prefersReducedMotion()) return;
|
||||||
|
|
||||||
|
const effect = ARTWORK_SHINE_EFFECTS[Math.floor(Math.random() * ARTWORK_SHINE_EFFECTS.length)];
|
||||||
|
const effectDuration = 1800 + Math.floor(Math.random() * 900);
|
||||||
|
artworkPlaceholder.classList.add(effect);
|
||||||
|
|
||||||
|
artworkShineClearTimeoutId = window.setTimeout(() => {
|
||||||
|
artworkPlaceholder.classList.remove(effect);
|
||||||
|
artworkShineClearTimeoutId = null;
|
||||||
|
}, effectDuration);
|
||||||
|
|
||||||
|
const nextDelay = 7000 + Math.floor(Math.random() * 15000);
|
||||||
|
artworkShineTimeoutId = window.setTimeout(trigger, nextDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
clearArtworkShineTimers();
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
|
||||||
const STATION_THEMES = [
|
const STATION_THEMES = [
|
||||||
{ accent: '#4dd7c8', accent2: '#ffb45c', accent3: '#8fb3ff', page: '#171b22', panel: '#111821' },
|
{ accent: '#4dd7c8', accent2: '#ffb45c', accent3: '#8fb3ff', page: '#171b22', panel: '#111821' },
|
||||||
{ accent: '#ff7aa8', accent2: '#ffd166', accent3: '#8fb3ff', page: '#22151d', panel: '#1b131a' },
|
{ accent: '#ff7aa8', accent2: '#ffd166', accent3: '#8fb3ff', page: '#22151d', panel: '#1b131a' },
|
||||||
@@ -255,7 +307,18 @@ function getMetadataFetchUrl(url) {
|
|||||||
if (!url || typeof url !== 'string') return '';
|
if (!url || typeof url !== 'string') return '';
|
||||||
|
|
||||||
const safeUrl = toHttpsIfHttp(url) || url;
|
const safeUrl = toHttpsIfHttp(url) || url;
|
||||||
if (!import.meta.env.DEV) return safeUrl;
|
if (!import.meta.env.DEV) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(safeUrl);
|
||||||
|
if (parsed.hostname === 'data.radio.si') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return safeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(safeUrl);
|
const parsed = new URL(safeUrl);
|
||||||
@@ -538,6 +601,64 @@ function getStationTitle(station) {
|
|||||||
return station?.name || station?.title || station?.id || 'Station';
|
return station?.name || station?.title || station?.id || 'Station';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMobileTitleViewport() {
|
||||||
|
return window.matchMedia('(max-width: 760px)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStationTitle(title, shouldScroll = false) {
|
||||||
|
if (!stationNameEl) return;
|
||||||
|
|
||||||
|
stationNameEl.replaceChildren();
|
||||||
|
|
||||||
|
if (!shouldScroll) {
|
||||||
|
stationNameEl.classList.remove('station-title-marquee');
|
||||||
|
const plainTitle = document.createElement('span');
|
||||||
|
plainTitle.className = 'station-title-text';
|
||||||
|
plainTitle.textContent = title;
|
||||||
|
stationNameEl.appendChild(plainTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stationNameEl.classList.add('station-title-marquee');
|
||||||
|
const track = document.createElement('span');
|
||||||
|
track.className = 'station-title-track';
|
||||||
|
|
||||||
|
const firstCopy = document.createElement('span');
|
||||||
|
firstCopy.className = 'station-title-copy';
|
||||||
|
firstCopy.textContent = title;
|
||||||
|
|
||||||
|
const secondCopy = document.createElement('span');
|
||||||
|
secondCopy.className = 'station-title-copy';
|
||||||
|
secondCopy.setAttribute('aria-hidden', 'true');
|
||||||
|
secondCopy.textContent = title;
|
||||||
|
|
||||||
|
track.append(firstCopy, secondCopy);
|
||||||
|
stationNameEl.appendChild(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStationTitleLayout(title) {
|
||||||
|
if (!stationNameEl) return;
|
||||||
|
|
||||||
|
const titleText = String(title || '').trim() || 'Station';
|
||||||
|
|
||||||
|
if (stationTitleRafId) {
|
||||||
|
cancelAnimationFrame(stationTitleRafId);
|
||||||
|
stationTitleRafId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStationTitle(titleText, false);
|
||||||
|
|
||||||
|
if (!isMobileTitleViewport()) return;
|
||||||
|
|
||||||
|
stationTitleRafId = window.requestAnimationFrame(() => {
|
||||||
|
stationTitleRafId = null;
|
||||||
|
if (!stationNameEl || !isMobileTitleViewport()) return;
|
||||||
|
if (stationNameEl.scrollWidth > stationNameEl.clientWidth + 2) {
|
||||||
|
renderStationTitle(titleText, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getStationCountry(station) {
|
function getStationCountry(station) {
|
||||||
return station?.country || station?.countryCode || station?.region || '';
|
return station?.country || station?.countryCode || station?.region || '';
|
||||||
}
|
}
|
||||||
@@ -1560,7 +1681,7 @@ function loadStation(index) {
|
|||||||
|
|
||||||
applyStationTheme(station);
|
applyStationTheme(station);
|
||||||
|
|
||||||
if (stationNameEl) stationNameEl.textContent = station.name;
|
updateStationTitleLayout(station.name);
|
||||||
if (stationSubtitleEl) stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
|
if (stationSubtitleEl) stationSubtitleEl.textContent = getStationDetails(station) || 'Live stream';
|
||||||
if (nowPlayingEl) nowPlayingEl.classList.add('hidden');
|
if (nowPlayingEl) nowPlayingEl.classList.add('hidden');
|
||||||
if (nowArtistEl) nowArtistEl.textContent = '';
|
if (nowArtistEl) nowArtistEl.textContent = '';
|
||||||
@@ -2131,7 +2252,10 @@ function setupEventListeners() {
|
|||||||
|
|
||||||
artworkPlaceholder?.addEventListener('click', openStationLibrary);
|
artworkPlaceholder?.addEventListener('click', openStationLibrary);
|
||||||
castOutputBtn?.addEventListener('click', toggleCastBothMode);
|
castOutputBtn?.addEventListener('click', toggleCastBothMode);
|
||||||
window.addEventListener('resize', updateCoverflowTransforms);
|
window.addEventListener('resize', () => {
|
||||||
|
updateCoverflowTransforms();
|
||||||
|
updateStationTitleLayout(getStationTitle(stations[currentIndex]));
|
||||||
|
});
|
||||||
document.addEventListener('click', (ev) => {
|
document.addEventListener('click', (ev) => {
|
||||||
if (!stationCountryFilterOpen) return;
|
if (!stationCountryFilterOpen) return;
|
||||||
if (stationCountryFilterWrapEl?.contains(ev.target)) return;
|
if (stationCountryFilterWrapEl?.contains(ev.target)) return;
|
||||||
@@ -2202,6 +2326,7 @@ async function init() {
|
|||||||
await loadStations();
|
await loadStations();
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
ensureArtworkPointerFallback();
|
ensureArtworkPointerFallback();
|
||||||
|
scheduleArtworkShine();
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
||||||
// Update Media Session when station or song changes
|
// Update Media Session when station or song changes
|
||||||
@@ -2228,7 +2353,7 @@ if ('serviceWorker' in navigator && import.meta.env.DEV) {
|
|||||||
});
|
});
|
||||||
} else if ('serviceWorker' in navigator) {
|
} else if ('serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker.register('sw.js')
|
navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`)
|
||||||
.then((reg) => console.log('ServiceWorker registered:', reg.scope))
|
.then((reg) => console.log('ServiceWorker registered:', reg.scope))
|
||||||
.catch((err) => console.debug('ServiceWorker registration failed:', err));
|
.catch((err) => console.debug('ServiceWorker registration failed:', err));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export async function loadManagedStations(): Promise<unknown[]> {
|
export async function loadManagedStations(): Promise<unknown[]> {
|
||||||
const response = await fetch('/stations.json');
|
const response = await fetch(`${import.meta.env.BASE_URL}stations.json`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load managed stations: ${response.status}`);
|
throw new Error(`Failed to load managed stations: ${response.status}`);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { RadioStation } from './radioTypes.js';
|
import type { RadioStation } from './radioTypes.js';
|
||||||
|
|
||||||
export async function loadRadioStations(): Promise<RadioStation[]> {
|
export async function loadRadioStations(): Promise<RadioStation[]> {
|
||||||
const response = await fetch('/data/radio-stations.json');
|
const response = await fetch(`${import.meta.env.BASE_URL}data/radio-stations.json`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load radio stations: ${response.status}`);
|
throw new Error(`Failed to load radio stations: ${response.status}`);
|
||||||
|
|||||||
314
src/styles.css
314
src/styles.css
@@ -55,45 +55,50 @@ body::before {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
|
linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
|
||||||
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
||||||
background-size: 42px 42px;
|
radial-gradient(circle, rgba(255, 255, 255, 0.18) 0 1px, transparent 1.6px),
|
||||||
|
radial-gradient(circle, rgba(var(--accent-3-rgb), 0.14) 0 1px, transparent 1.8px),
|
||||||
|
radial-gradient(circle, rgba(255, 255, 255, 0.12) 0 1px, transparent 1.7px);
|
||||||
|
background-size: 42px 42px, 42px 42px, 180px 180px, 240px 240px, 300px 300px;
|
||||||
|
background-position: 0 0, 0 0, 20px 28px, 80px 120px, 120px 56px;
|
||||||
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.62), transparent 78%);
|
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.62), transparent 78%);
|
||||||
animation: grid-drift 18s linear infinite;
|
opacity: 0.62;
|
||||||
|
animation: grid-drift 72s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::after {
|
body::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: -28vmax;
|
inset: -24vmax;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 24% 32%, rgba(var(--accent-rgb), 0.22), transparent 22vmax),
|
radial-gradient(circle at 24% 32%, rgba(var(--accent-rgb), 0.16), transparent 22vmax),
|
||||||
radial-gradient(circle at 78% 64%, rgba(var(--accent-2-rgb), 0.18), transparent 24vmax),
|
radial-gradient(circle at 78% 64%, rgba(var(--accent-2-rgb), 0.12), transparent 24vmax),
|
||||||
radial-gradient(circle at 52% 82%, rgba(var(--accent-3-rgb), 0.16), transparent 20vmax);
|
radial-gradient(circle at 52% 82%, rgba(var(--accent-3-rgb), 0.1), transparent 20vmax);
|
||||||
filter: blur(28px);
|
filter: blur(36px);
|
||||||
opacity: 0.88;
|
opacity: 0.42;
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
animation: ambient-drift 22s ease-in-out infinite alternate;
|
animation: ambient-drift 84s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes grid-drift {
|
@keyframes grid-drift {
|
||||||
from {
|
from {
|
||||||
background-position: 0 0, 0 0;
|
background-position: 0 0, 0 0, 20px 28px, 80px 120px, 120px 56px;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
background-position: 42px 42px, -42px 42px;
|
background-position: 12px 12px, -12px 12px, 32px 18px, 92px 128px, 108px 66px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes ambient-drift {
|
@keyframes ambient-drift {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(-1.5%, -1%, 0) scale(1);
|
transform: translate3d(-0.6%, -0.4%, 0) scale(1);
|
||||||
}
|
}
|
||||||
45% {
|
50% {
|
||||||
transform: translate3d(2%, 1.5%, 0) scale(1.035);
|
transform: translate3d(0.8%, 0.6%, 0) scale(1.02);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translate3d(-0.5%, 2%, 0) scale(1.07);
|
transform: translate3d(0.2%, 0.9%, 0) scale(1.03);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,19 +753,7 @@ input:focus-visible,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.starfield {
|
.starfield {
|
||||||
position: absolute;
|
display: none;
|
||||||
inset: 0;
|
|
||||||
z-index: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
perspective: 760px;
|
|
||||||
perspective-origin: 50% 48%;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: inherit;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 50% 48%, rgba(var(--accent-rgb), 0.13), transparent 34%),
|
|
||||||
radial-gradient(circle at 76% 68%, rgba(var(--accent-2-rgb), 0.10), transparent 28%);
|
|
||||||
mask-image: radial-gradient(ellipse at center, rgba(0,0,0,0.98), rgba(0,0,0,0.88) 64%, transparent 98%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.starfield-plane {
|
.starfield-plane {
|
||||||
@@ -1036,6 +1029,7 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
isolation: isolate;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background:
|
background:
|
||||||
linear-gradient(135deg, rgba(var(--accent-rgb), 0.9), rgba(var(--accent-3-rgb), 0.72) 48%, rgba(var(--accent-2-rgb), 0.86));
|
linear-gradient(135deg, rgba(var(--accent-rgb), 0.9), rgba(var(--accent-3-rgb), 0.72) 48%, rgba(var(--accent-2-rgb), 0.86));
|
||||||
@@ -1047,6 +1041,7 @@ header {
|
|||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
background:
|
background:
|
||||||
linear-gradient(120deg, rgba(255,255,255,0.26), transparent 34%),
|
linear-gradient(120deg, rgba(255,255,255,0.26), transparent 34%),
|
||||||
repeating-linear-gradient(135deg, rgba(255,255,255,0.05) 0 1px, transparent 1px 12px);
|
repeating-linear-gradient(135deg, rgba(255,255,255,0.05) 0 1px, transparent 1px 12px);
|
||||||
@@ -1054,6 +1049,95 @@ header {
|
|||||||
animation: artwork-sheen 9s ease-in-out infinite alternate;
|
animation: artwork-sheen 9s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.artwork-placeholder::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -18%;
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
|
opacity: 0;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
transform: translate3d(-10%, -10%, 0) rotate(0deg);
|
||||||
|
filter: blur(0.5px) drop-shadow(0 0 16px rgba(255,255,255,0.26));
|
||||||
|
background:
|
||||||
|
linear-gradient(120deg, transparent 28%, rgba(255,255,255,0.24) 42%, rgba(255,255,255,0.86) 50%, rgba(255,255,255,0.26) 58%, transparent 72%),
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(255,255,255,0.74), transparent 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.artwork-placeholder.shine-sweep::after {
|
||||||
|
opacity: 1;
|
||||||
|
animation: artwork-shine-sweep 2.6s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artwork-placeholder.shine-glint::after {
|
||||||
|
opacity: 1;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 22% 28%, rgba(255,255,255,0.92) 0 8%, transparent 13%),
|
||||||
|
radial-gradient(circle at 72% 34%, rgba(var(--accent-3-rgb), 0.56) 0 10%, transparent 16%),
|
||||||
|
radial-gradient(circle at 52% 58%, rgba(255,255,255,0.24), transparent 36%);
|
||||||
|
animation: artwork-shine-glint 2.2s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artwork-placeholder.shine-flare::after {
|
||||||
|
opacity: 1;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 48% 42%, rgba(255,255,255,0.95) 0 4%, transparent 12%),
|
||||||
|
radial-gradient(circle at 52% 48%, rgba(255,255,255,0.30) 0 18%, transparent 42%),
|
||||||
|
linear-gradient(135deg, transparent 35%, rgba(var(--accent-rgb), 0.34) 50%, transparent 66%);
|
||||||
|
animation: artwork-shine-flare 2.4s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes artwork-shine-sweep {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(-26%, -10%, 0) rotate(11deg);
|
||||||
|
}
|
||||||
|
12% {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
58% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(26%, 10%, 0) rotate(11deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes artwork-shine-glint {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
18% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes artwork-shine-flare {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.84);
|
||||||
|
}
|
||||||
|
18% {
|
||||||
|
opacity: 0.95;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes artwork-sheen {
|
@keyframes artwork-sheen {
|
||||||
from {
|
from {
|
||||||
transform: translateX(-3%) translateY(-2%);
|
transform: translateX(-3%) translateY(-2%);
|
||||||
@@ -1797,21 +1881,28 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
align-items: start;
|
align-items: stretch;
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-layout {
|
.player-layout {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass-card {
|
.glass-card {
|
||||||
height: auto;
|
height: 100dvh;
|
||||||
min-height: calc(100vh - 20px);
|
min-height: 100dvh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr) auto auto auto auto auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"header"
|
"header"
|
||||||
"artwork"
|
"artwork"
|
||||||
@@ -1820,9 +1911,45 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
"controls"
|
"controls"
|
||||||
"volume"
|
"volume"
|
||||||
"quickpick";
|
"quickpick";
|
||||||
gap: 13px;
|
gap: 9px;
|
||||||
padding: 16px;
|
padding: 10px 12px 12px;
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-top-row {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-block {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex-basis: 36px;
|
||||||
|
border-radius: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
font-size: 0.96rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-time {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-date {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icons-left {
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-tabs {
|
.library-tabs {
|
||||||
@@ -1930,6 +2057,11 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#edit-stations-btn,
|
||||||
|
#install-app-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -1949,27 +2081,37 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.artwork-section {
|
.artwork-section {
|
||||||
align-self: auto;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-stack {
|
.artwork-stack {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-container {
|
.artwork-container {
|
||||||
width: min(72vw, 280px);
|
width: min(34vw, 132px);
|
||||||
padding: 8px;
|
padding: 4px;
|
||||||
border-radius: 26px;
|
border-radius: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-placeholder {
|
.artwork-placeholder {
|
||||||
border-radius: 20px;
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-logo-img {
|
||||||
|
width: 72%;
|
||||||
|
height: 72%;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-logo-text {
|
||||||
|
font-size: clamp(1rem, 4.8vw, 1.55rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-coverflow {
|
.artwork-coverflow {
|
||||||
width: min(100%, calc(100vw - 42px));
|
width: min(100%, calc(100vw - 42px));
|
||||||
height: 92px;
|
height: 72px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1978,35 +2120,59 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.coverflow-item {
|
.coverflow-item {
|
||||||
width: 72px;
|
width: 60px;
|
||||||
height: 64px;
|
height: 52px;
|
||||||
border-radius: 16px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.coverflow-item.fallback {
|
.coverflow-item.fallback {
|
||||||
font-size: 0.74rem;
|
font-size: 0.66rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.coverflow-arrow {
|
.coverflow-arrow {
|
||||||
width: 36px;
|
width: 32px;
|
||||||
height: 36px;
|
height: 32px;
|
||||||
background: rgba(12, 15, 20, 0.78);
|
background: rgba(12, 15, 20, 0.78);
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-info {
|
.track-info {
|
||||||
min-height: 150px;
|
min-height: 108px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-info h2 {
|
.track-info h2 {
|
||||||
font-size: clamp(1.75rem, 10vw, 2.8rem);
|
font-size: clamp(1.45rem, 8vw, 2.25rem);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info h2.station-title-marquee {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info h2.station-title-marquee .station-title-track {
|
||||||
|
display: inline-flex;
|
||||||
|
width: max-content;
|
||||||
|
animation: station-title-scroll 10s linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info h2 .station-title-text,
|
||||||
|
.track-info h2 .station-title-copy {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info p {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#now-playing {
|
#now-playing {
|
||||||
min-height: 48px;
|
min-height: 34px;
|
||||||
margin-top: 12px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator-wrap,
|
.status-indicator-wrap,
|
||||||
@@ -2015,26 +2181,39 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.controls-section {
|
.controls-section {
|
||||||
grid-template-columns: 56px 82px 56px;
|
grid-template-columns: 50px 72px 50px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 16px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn.secondary {
|
.control-btn.secondary {
|
||||||
width: 56px;
|
width: 50px;
|
||||||
height: 56px;
|
height: 50px;
|
||||||
border-radius: 17px;
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn.primary {
|
.control-btn.primary {
|
||||||
width: 82px;
|
width: 72px;
|
||||||
height: 82px;
|
height: 72px;
|
||||||
border-radius: 24px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-section {
|
.volume-section {
|
||||||
grid-template-columns: 40px minmax(0, 1fr) 44px;
|
grid-template-columns: 36px minmax(0, 1fr) 38px;
|
||||||
padding-bottom: 4px;
|
gap: 8px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-section #volume-value {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickpick-section {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickpick-section .artwork-coverflow {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -2072,6 +2251,15 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes station-title-scroll {
|
||||||
|
from {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 761px) and (max-width: 980px) {
|
@media (min-width: 761px) and (max-width: 980px) {
|
||||||
.glass-card {
|
.glass-card {
|
||||||
grid-template-columns: minmax(260px, 0.9fr) minmax(300px, 1.1fr);
|
grid-template-columns: minmax(260px, 0.9fr) minmax(300px, 1.1fr);
|
||||||
@@ -2093,7 +2281,7 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.artwork-container {
|
.artwork-container {
|
||||||
width: min(74vw, 238px);
|
width: min(47vw, 238px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-coverflow {
|
.artwork-coverflow {
|
||||||
|
|||||||
Reference in New Issue
Block a user