From 814bbbff851dc36e5d65eb6fbc25e07e452f84ad Mon Sep 17 00:00:00 2001 From: codey Date: Thu, 17 Jul 2025 11:30:14 -0400 Subject: [PATCH] radio styling changes (add/fix mobile responsivity); WIP, player still does not function on iOS --- src/assets/styles/player.css | 421 +++++++++++++++++---------------- src/components/AudioPlayer.jsx | 135 +++++++---- 2 files changed, 300 insertions(+), 256 deletions(-) diff --git a/src/assets/styles/player.css b/src/assets/styles/player.css index 49b900c..7901c55 100644 --- a/src/assets/styles/player.css +++ b/src/assets/styles/player.css @@ -1,263 +1,268 @@ -div > img { - transition: 300ms; -} -div > img:hover { - opacity: 0.7; - cursor: pointer; -} - -div { - box-sizing: border-box; -} - -img { - width: 100%; - height: 100%; -} - -.album-cover > img { - height: 100% !important; - width: 100% !important; - object-fit: cover; - aspect-ratio: 1; -} - -p { - margin-top: 0; - margin-bottom: 0.3em; +/* Universal box-sizing for consistency */ +*, +*::before, +*::after { + box-sizing: border-box; } body { - font-family: "Mukta", sans-serif; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - min-width: 100vw; - min-height: 100vh; - /* background: linear-gradient(-45deg, #FFCDD2 50%, #B2EBF2 50%); */ -} - -div, -section { - box-sizing: border-box; -} - -.c-containter { - display: flex; - flex-flow: column nowrap; - justify-content: center; - align-items: center; - width: 100%; - height: 30vh; - padding: 1em; -} - -.station-tabs { - top: 4rem; + font-family: "Mukta", sans-serif; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + min-width: 100vw; + min-height: 100vh; + /* background: linear-gradient(-45deg, #FFCDD2 50%, #B2EBF2 50%); */ } +/* Container for the player and album cover */ .music-container { - display: flex; - justify-content: center; - align-items: center; - position: absolute; - /* top: 20%; - left: 30%; */ - width: 40%; - max-width: 700px; - box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.3); - max-height: 290px; - margin-bottom: 50px; + width: 800px; /* fixed desktop width */ + max-width: 90vw; /* prevent overflow on smaller screens */ + height: 290px; + margin: 0 auto 120px auto; /* increased bottom margin */ + overflow-x: visible; /* allow horizontal overflow if needed */ + overflow-y: hidden; + position: relative; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; + padding: 1rem; + box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.3); } +/* Album cover section */ .album-cover { - flex: 1 0 30%; -} -.album-cover img { - width: 100%; - height: 100%; + flex: 0 0 30%; /* Fixed ~30% width */ + max-width: 30%; + height: 100%; + overflow: hidden; /* Prevent image spill */ + min-width: 0; /* Fix flex overflow */ } -.arrow { - position: absolute; - top: calc(50% - 2.5em); - background: rgba(255, 255, 255, 0.3); - border: 0; - width: 5em; - height: 5em; - cursor: pointer; -} -.arrow:hover { - background: rgba(255, 255, 255, 0.5); -} -.arrow img { - display: block; - width: 20px; - margin: 0 auto; -} -.arrow.left { - left: -5em; -} -.arrow.right { - right: -5em; +/* Album cover image */ +.album-cover > img { + width: 100%; + height: 100%; + object-fit: cover; + aspect-ratio: 1; + transition: 300ms; } +.album-cover > img:hover { + opacity: 0.7; + cursor: pointer; +} + +/* Player info and controls */ .music-player { - display: flex; - flex-flow: column wrap; - justify-content: center; - background: inherit; - padding: 1em; - text-align: center; - width: 500px; - max-width: 500px; + flex: 1 1 70%; /* Take remaining ~70% */ + max-width: 70%; + width: auto; + height: 100%; /* Match container height */ + padding: 1em; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + background: inherit; + box-sizing: border-box; + min-width: 0; /* Fix flex overflow */ + flex-shrink: 0; /* Prevent shrinking that hides content */ } + +/* Text styling */ .music-player__title { - color: inherit; - font-size: 0.9rem; - margin: 0 0 0.1em 0; + color: inherit; + font-size: 0.9rem; + margin: 0 0 0.1em 0; } + .music-player__header { - color: inherit; - font-size: 1rem; - margin: 1 0 0.1em 0; + color: inherit; + font-size: 1rem; + margin: 1 0 0.1em 0; } + .music-player__author { - color: inherit; - font-size: 0.9rem; - margin: 0 0 0.5em 0; + color: inherit; + font-size: 0.9rem; + margin: 0 0 0.5em 0; } .music-player__genre { - color: inherit; - font-size: 0.9rem; - margin: 0 0 0.5em 0; + color: inherit; + font-size: 0.9rem; + margin: 0 0 0.5em 0; } .music-player__genre:before { - font-weight: bold; - content: "Genre: " + font-weight: bold; + content: "Genre: "; } +.station-tabs { + padding-top: 2%; +} +/* Music bar and progress */ .music-bar { - background: #efefef; - stroke-width: 1; - height: 8px; - width: 100%; -} -.music-bar:hover { - cursor: pointer; + background: #efefef; + stroke-width: 1; + height: 8px; + width: 100%; + cursor: pointer; } + .music-bar #length { - width: 0%; - background: #2196F3; - height: 100%; - transition: width linear 200ms; + width: 0%; + background: #2196F3; + height: 100%; + transition: width linear 200ms; } +/* Time display */ .music-time { - display: flex; - flex-flow: row wrap; + display: flex; + flex-flow: row wrap; } + .music-time__last { - margin-left: auto; + margin-left: auto; } +/* Playback order icons */ .music-order { - display: flex; - flex-flow: row wrap; + display: flex; + flex-flow: row wrap; } -.music-order__shuffle, .music-order__loop { - width: 1.2em; - height: 1.2em; - opacity: 0.2; - margin: 0.3em 0; + +.music-order__shuffle, +.music-order__loop { + width: 1.2em; + height: 1.2em; + opacity: 0.2; + margin: 0.3em 0; } -.music-order__shuffle.is-loop, .music-order__loop.is-loop { - opacity: 1 !important; -} -.music-order__shuffle.is-loop-one, .music-order__loop.is-loop-one { - opacity: 1 !important; + +.music-order__shuffle.is-loop, +.music-order__loop.is-loop, +.music-order__shuffle.is-loop-one, +.music-order__loop.is-loop-one { + opacity: 1 !important; } + .music-order__shuffle { - margin-left: auto; + margin-left: auto; } +/* Controls container */ .music-control { - display: flex; - flex-flow: row wrap; - justify-content: center; - align-items: center; - height: 2em; + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: center; + height: 2em; } + .music-control__play { - width: 3em; - height: 3em; - margin: 0 1em; -} -.music-control__backward, .music-control__forward { - width: 1.5em; - height: 1.5em; + width: 3em; + height: 3em; + margin: 0 1em; } +.music-control__backward, +.music-control__forward { + width: 1.5em; + height: 1.5em; +} + +/* Arrows for navigation */ +.arrow { + position: absolute; + top: calc(50% - 2.5em); + background: rgba(255, 255, 255, 0.3); + border: 0; + width: 5em; + height: 5em; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + transition: background 0.3s ease; +} + +.arrow:hover { + background: rgba(255, 255, 255, 0.5); +} + +.arrow img { + display: block; + width: 20px; + margin: 0 auto; +} + +.arrow.left { + left: -5em; +} + +.arrow.right { + right: -5em; +} + +/* Mobile override */ @media all and (max-width: 960px) { - .c-containter { - display: block; - overflow: auto; - max-height: inherit; - max-width: 90%; - } - - .music-container { - display: flex; - justify-content: center; - flex-flow: column wrap; - margin: 0 auto; - padding-left: 25%; - overflow: auto; - } + .music-container { + flex-direction: column !important; + width: 100% !important; + max-width: 100% !important; + height: auto !important; + margin-bottom: 50px !important; + overflow: visible !important; + padding: 1rem !important; + } - .music-player__author { - font-size: 0.4rem; - } + .album-cover { + flex: none !important; + width: 100% !important; + max-width: 100% !important; + height: auto !important; + margin-bottom: 1rem !important; + min-width: 0 !important; + } - .music-player__title { - font-size: 0.6rem; - } + .album-cover > img { + aspect-ratio: unset !important; + height: auto !important; + width: 100% !important; + } - .music-player__genre { - display: none; - } - - .music-player { - width: 100%; - max-width: 100%; - } - - .album-cover { - position: relative; - flex: 1 1 100%; - max-width: 200px; - max-height: 200px; - } - - .arrow { - position: absolute; - top: calc(50% - 1.5em); - width: 3em; - height: 3em; - } - .arrow.left { - left: 0; - } - .arrow.right { - right: 0; - } - - .music-control__play { - width: 2.2em; - height: 2.2em; - } -} \ No newline at end of file + .music-player { + flex: none !important; + width: 100% !important; + max-width: 100% !important; + height: auto !important; + padding: 0 !important; + min-width: 0 !important; + flex-shrink: 0 !important; + } + + .arrow.left, + .arrow.right { + position: relative !important; + top: auto !important; + left: auto !important; + right: auto !important; + width: auto !important; + height: auto !important; + background: transparent !important; + cursor: default !important; + margin: 0 1em !important; + } + + .arrow img { + width: 24px !important; + } +} diff --git a/src/components/AudioPlayer.jsx b/src/components/AudioPlayer.jsx index b4f4e49..d48a621 100644 --- a/src/components/AudioPlayer.jsx +++ b/src/components/AudioPlayer.jsx @@ -16,7 +16,12 @@ const STATIONS = { pop: { label: "Pop", streamPath: "/pop.ogg" }, }; -// Global interval tracking (required for Astro + client:only) +// Detect iOS user agent (can refine if needed) +const isIOS = (() => { + if (typeof window === "undefined") return false; + return /iP(ad|hone|od)/.test(navigator.userAgent); +})(); + let activeInterval = null; let currentStationForInterval = null; @@ -50,13 +55,19 @@ export function Player() { const progress = duration > 0 ? (elapsed / duration) * 100 : 0; - // Create Howl instance on activeStation change + // UseEffect: create Howl on station change (as before) useEffect(() => { if (soundRef.current) { soundRef.current.unload(); soundRef.current = null; } + // On iOS we defer creation until user plays, so skip creating Howl here + if (isIOS) { + setIsPlaying(false); + return; + } + const streamUrl = "https://stream.codey.lol" + STATIONS[activeStation].streamPath; const howl = new Howl({ @@ -92,47 +103,80 @@ export function Player() { }; }, [activeStation]); -const togglePlayback = () => { - if (isPlaying) { - if (soundRef.current) { - soundRef.current.pause(); - } - setIsPlaying(false); - } else { - // If a previous Howl exists, unload it - if (soundRef.current) { - soundRef.current.stop(); - soundRef.current.unload(); + // Toggle playback handler + const togglePlayback = () => { + if (isPlaying) { + if (soundRef.current) { + soundRef.current.pause(); + } + setIsPlaying(false); + } else { + if (isIOS) { + // On iOS, defer creation until user explicitly plays + if (soundRef.current) { + soundRef.current.stop(); + soundRef.current.unload(); + soundRef.current = null; + } + + const streamUrl = + "https://stream.codey.lol" + + STATIONS[activeStation].streamPath + + `?t=${Date.now()}`; // Cache-busting param + + const newHowl = new Howl({ + src: [streamUrl], + html5: true, + onend: () => newHowl.play(), + onplay: () => { + setIsPlaying(true); + }, + onpause: () => setIsPlaying(false), + onstop: () => setIsPlaying(false), + onloaderror: (_, err) => console.error("Load error", err), + onplayerror: (_, err) => { + console.error("Play error", err); + setTimeout(() => newHowl.play(), 1000); + }, + }); + + soundRef.current = newHowl; + newHowl.play(); + } else { + // Desktop browsers: current logic + if (soundRef.current) { + soundRef.current.stop(); + soundRef.current.unload(); + } + + const streamUrl = + "https://stream.codey.lol" + + STATIONS[activeStation].streamPath + + `?t=${Date.now()}`; // Cache-busting param + + const newHowl = new Howl({ + src: [streamUrl], + html5: true, + onend: () => newHowl.play(), + onplay: () => { + setIsPlaying(true); + }, + onpause: () => setIsPlaying(false), + onstop: () => setIsPlaying(false), + onloaderror: (_, err) => console.error("Load error", err), + onplayerror: (_, err) => { + console.error("Play error", err); + setTimeout(() => newHowl.play(), 1000); + }, + }); + + soundRef.current = newHowl; + newHowl.play(); + } } + }; - const streamUrl = - "https://stream.codey.lol" + - STATIONS[activeStation].streamPath + - `?t=${Date.now()}`; // Cache-busting param - - const newHowl = new Howl({ - src: [streamUrl], - html5: true, - onend: () => newHowl.play(), - onplay: () => { - setIsPlaying(true); - }, - onpause: () => setIsPlaying(false), - onstop: () => setIsPlaying(false), - onloaderror: (_, err) => console.error("Load error", err), - onplayerror: (_, err) => { - console.error("Play error", err); - setTimeout(() => newHowl.play(), 1000); - }, - }); - - soundRef.current = newHowl; - newHowl.play(); - } -}; - - - // Metadata fetcher: global-safe + // Metadata fetcher (unchanged) useEffect(() => { clearGlobalMetadataInterval(); @@ -145,7 +189,6 @@ const togglePlayback = () => { }); const data = await response.json(); - // Ignore stale interval calls if (currentStationForInterval !== activeStation) return; if (data.artist === "N/A" && data.song === "N/A") { @@ -227,12 +270,8 @@ const togglePlayback = () => {
-
- {!isPlaying ? ( - - ) : ( - - )} +
+ {!isPlaying ? : }