diff --git a/src/animations.js b/src/animations.js new file mode 100644 index 0000000..e410db1 --- /dev/null +++ b/src/animations.js @@ -0,0 +1,23 @@ +const STOP_STATE_POINTS = "650,0, 1350,0, 1350,500, 650,500"; +const PLAY_STATE_POINTS = "0,0, 2000,250, 2000,250, 0,500"; + +const startIcebergsAnimation = () => { + [1, 2, 3].map((i) => { + const polygon = document.getElementById(`iceberg${i}`); + polygon.classList.add("active"); + }); +}; + +const stopIcebergsAnimation = () => { + [1, 2, 3].map((i) => { + const polygon = document.getElementById(`iceberg${i}`); + polygon.classList.remove("active"); + }); +}; + +export { + startIcebergsAnimation, + stopIcebergsAnimation, + STOP_STATE_POINTS, + PLAY_STATE_POINTS, +}; diff --git a/src/dom.js b/src/dom.js new file mode 100644 index 0000000..398575a --- /dev/null +++ b/src/dom.js @@ -0,0 +1,15 @@ +const audio = document.querySelector("audio"); +const playButton = document.querySelector(".play-stream"); +const playButtonAnimation = document.getElementById("play-button-animation"); +const artistBlock = document.querySelector(".now-playing_artist"); +const titleBlock = document.querySelector(".now-playing_song"); +const logoDot = document.querySelector(".logo_dot"); + +export { + audio, + playButton, + playButtonAnimation, + artistBlock, + titleBlock, + logoDot, +}; diff --git a/src/fonts/Inter.ttf b/src/fonts/Inter.ttf new file mode 100644 index 0000000..e724708 Binary files /dev/null and b/src/fonts/Inter.ttf differ diff --git a/src/fonts/lato-lightitalic.woff b/src/fonts/lato-lightitalic.woff deleted file mode 100644 index 86b3600..0000000 Binary files a/src/fonts/lato-lightitalic.woff and /dev/null differ diff --git a/src/fonts/lato-lightitalic.woff2 b/src/fonts/lato-lightitalic.woff2 deleted file mode 100644 index 77aabad..0000000 Binary files a/src/fonts/lato-lightitalic.woff2 and /dev/null differ diff --git a/src/fonts/lato-regular-webfont.woff2 b/src/fonts/lato-regular-webfont.woff2 deleted file mode 100644 index 495cb9e..0000000 Binary files a/src/fonts/lato-regular-webfont.woff2 and /dev/null differ diff --git a/src/index.html b/src/index.html index d0d8a32..ba4902e 100644 --- a/src/index.html +++ b/src/index.html @@ -43,14 +43,20 @@ -

Radio . Iceberg

+
now playing:
@@ -75,20 +81,20 @@
- + diff --git a/src/interactions.js b/src/interactions.js new file mode 100644 index 0000000..f2198e1 --- /dev/null +++ b/src/interactions.js @@ -0,0 +1,51 @@ +import { audio, playButton, playButtonAnimation } from "./dom.js"; +import { + STOP_STATE_POINTS, + PLAY_STATE_POINTS, + startIcebergsAnimation, + stopIcebergsAnimation, +} from "./animations.js"; + +const hideAudio = () => { + audio.hidden = true; +}; + +const playAudio = () => { + audio?.play(); +}; + +const pauseAudio = () => { + audio?.pause(); +}; + +const setButtonPlayState = () => { + playButtonAnimation.setAttribute("from", PLAY_STATE_POINTS); + playButtonAnimation.setAttribute("to", STOP_STATE_POINTS); + playButtonAnimation.beginElement(); +}; + +const setButtonStopState = () => { + playButtonAnimation.setAttribute("from", STOP_STATE_POINTS); + playButtonAnimation.setAttribute("to", PLAY_STATE_POINTS); + playButtonAnimation.beginElement(); +}; + +const handleAudioPlay = () => { + playAudio(); + setButtonPlayState(); + startIcebergsAnimation(); +}; + +const handleAudioStop = () => { + pauseAudio(); + setButtonStopState(); + stopIcebergsAnimation(); +}; + +const addPlayerInteractions = () => { + playButton.addEventListener("click", () => { + audio?.paused ? handleAudioPlay() : handleAudioStop(); + }); +}; + +export { hideAudio, addPlayerInteractions }; diff --git a/src/main.js b/src/main.js index e608cd7..3e5f170 100644 --- a/src/main.js +++ b/src/main.js @@ -1,110 +1,5 @@ -const STOP_STATE_POINTS = "650,0, 1350,0, 1350,500, 650,500"; -const PLAY_STATE_POINTS = "0,0, 2000,250, 2000,250, 0,500"; - -const getAudio = () => document.querySelector("audio"); -const getPlayButton = () => document.querySelector(".play-stream"); -const getPlayButtonAnimation = () => - document.getElementById("play-button-animation"); - -const hideAudio = () => { - const a = getAudio(); - a.hidden = true; -}; - -const playAudio = () => { - getAudio()?.play(); -}; - -const pauseAudio = () => { - getAudio()?.pause(); -}; - -const setButtonPlayState = () => { - const animationNode = getPlayButtonAnimation(); - animationNode.setAttribute("from", PLAY_STATE_POINTS); - animationNode.setAttribute("to", STOP_STATE_POINTS); - animationNode.beginElement(); -}; - -const setButtonStopState = () => { - const animationNode = getPlayButtonAnimation(); - animationNode.setAttribute("from", STOP_STATE_POINTS); - animationNode.setAttribute("to", PLAY_STATE_POINTS); - animationNode.beginElement(); -}; - -const startIcebergsAnimation = () => { - [1, 2, 3].map((i) => { - const polygon = document.getElementById(`iceberg${i}`); - polygon.classList.add("active"); - }); -}; - -const stopIcebergsAnimation = () => { - [1, 2, 3].map((i) => { - const polygon = document.getElementById(`iceberg${i}`); - polygon.classList.remove("active"); - }); -}; - -const handleAudioPlay = () => { - playAudio(); - setButtonPlayState(); - startIcebergsAnimation(); -}; - -const handleAudioStop = () => { - pauseAudio(); - setButtonStopState(); - stopIcebergsAnimation(); -}; - -const addPlayerInteractions = () => { - const playerButton = getPlayButton(); - const audio = getAudio(); - - playerButton.addEventListener("click", () => { - audio.paused ? handleAudioPlay() : handleAudioStop(); - }); - - audio.addEventListener("timeupdate", () => { - window.requestAnimationFrame(() => { - document.documentElement.style.setProperty( - "--time", - `${audio.currentTime * 100}deg` - ); - }); - }); -}; - -async function connectToServer() { - const ws = new WebSocket(`ws://${location.host}/meta`); - ws.addEventListener("message", changeMetadata); -} - -const changeMetadata = (metadataEvent) => { - const metaObject = JSON.parse(metadataEvent.data); - const { artist, title } = metaObject; - const artistBlock = document.querySelector(".now-playing_artist"); - const titleBlock = document.querySelector(".now-playing_song"); - - if (artist) { - artistBlock.innerHTML = artist; - } else { - artistBlock.innerHTML = ""; - } - - if (title) { - titleBlock.innerHTML = title; - document.title = `${title} on Radioiceberg`; - } else { - titleBlock.innerHTML = ""; - } - - if (artist && title) { - document.title = `${artist} - ${title} on Radioiceberg`; - } -}; +import { addPlayerInteractions, hideAudio } from "./interactions.js"; +import { connectToServer } from "./websocket.js"; const main = () => { hideAudio(); diff --git a/src/messagesProcessor.js b/src/messagesProcessor.js new file mode 100644 index 0000000..99f817a --- /dev/null +++ b/src/messagesProcessor.js @@ -0,0 +1,58 @@ +import { artistBlock, titleBlock, logoDot } from "./dom.js"; + +const changeTitles = ({ artist, title }) => { + if (artist) { + artistBlock.innerHTML = artist; + } else { + artistBlock.innerHTML = ""; + } + + if (title) { + titleBlock.innerHTML = title; + document.title = `${title} on Radioiceberg`; + } else { + titleBlock.innerHTML = ""; + } + + if (artist && title) { + document.title = `${artist} - ${title} on Radioiceberg`; + } +}; + +const metadataChange = ({ data }) => { + changeTitles(data); +}; + +const liveStarted = () => { + logoDot.classList.add("live"); +}; + +const liveEnded = () => { + logoDot.classList.remove("live"); +}; + +const setState = ({ data }) => { + const { lastPlayed } = data; + changeTitles(lastPlayed); +}; + +const actions = { + metadataChange, + liveStarted, + liveEnded, + setState, +}; + +export const processMessage = ({ data }) => { + try { + const message = JSON.parse(data); + const action = actions[message.command]; + if (action) { + action(message); + } else { + console.log("no action for", message); + } + } catch (error) { + console.error("Error while process websocket message", error); + } +}; diff --git a/src/scripts/dom.js b/src/scripts/dom.js deleted file mode 100644 index fe89522..0000000 --- a/src/scripts/dom.js +++ /dev/null @@ -1,10 +0,0 @@ -const getAudio = () => document.querySelector("audio"); -const getPlayButton = () => document.querySelector(".play-stream"); -const getPlayButtonAnimation = () => - document.getElementById("play-button-animation"); - -export { - getAudio, - getPlayButton, - getPlayButtonAnimation -}; diff --git a/src/scripts/interactions.js b/src/scripts/interactions.js deleted file mode 100644 index 8ffb66c..0000000 --- a/src/scripts/interactions.js +++ /dev/null @@ -1,14 +0,0 @@ -import { getAudio } from "./dom.js"; - -const hideAudio = () => { - const a = getAudio(); - a.hidden = true; -}; - -const playAudio = () => { - getAudio()?.play(); -}; - -const pauseAudio = () => { - getAudio()?.pause(); -}; diff --git a/src/scripts/now-playing.js b/src/scripts/now-playing.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/styles.css b/src/styles.css index e588888..e494e3c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,15 +1,29 @@ -@import url("styles/global.css"); -@import url("styles/system.css"); +@import url("styles/fonts.css"); +@import url("styles/variables.css"); +@import url("styles/animations.css"); + +html { + font-size: 16pt; +} + +html, +body { + margin: 0; + padding: 0; +} body { - background-color: var(--color-primary-0); - color: var(--color-primary-1); + background-color: var(--color-colors-base); + color: var(--color-colors-additional-light); height: 100vh; + font-family: var(--typography-font-family), sans-serif; max-height: 100vh; overflow: hidden; position: relative; line-height: 1.75; - padding: 1rem; + padding-left: 1rem; + display: grid; + align-content: center; } h1, @@ -17,8 +31,7 @@ h2, h3, h4, h5 { - margin: 3rem 0 1.38rem; - font-family: "Lato", sans-serif; + margin: 0; font-weight: 400; line-height: 1.3; } @@ -29,7 +42,7 @@ h1 { h1 { margin-top: 0; - font-size: 5.063rem; + font-size: var(--size-font-large); } .play-stream { @@ -41,8 +54,13 @@ h1 { } .now-playing { - font-family: "Lato Light Italic"; - font-size: 1.5rem; + font-size: var(--size-font-medium); +} + +.now-playing_artist, +.now-playing_song { + font-weight: 300; + font-variation-settings: "slnt" -10; } .icebergs { @@ -54,17 +72,63 @@ h1 { max-height: 70vh; } -#iceberg1, -#iceberg2, -#iceberg3 { - transition: transform 2s ease-in-out; - transform: translate(0); +#iceberg1.active, +#iceberg2.active { + animation-name: slide; + animation-iteration-count: infinite; + animation-direction: alternate; } #iceberg1.active { - transform: translate(0, calc(100px * sin(var(--time)))); + animation-duration: 3s; } #iceberg2.active { - transform: translate(0, calc(50px * cos(var(--time)))); + animation-duration: 2s; +} + +.logo { + display: flex; + align-items: baseline; +} + +.logo_dot { + width: 0.25rem; + height: 0.25rem; + background: white; + border-radius: 100%; + margin: 0 0.25rem; + display: grid; + align-items: center; + justify-items: center; + transition: all 0.25s; +} + +.logo_dot > .logo_dot_inner { + display: none; +} + +.logo_dot.live { + width: 0.5rem; + height: 0.5rem; + background: var(--color-colors-warning); + box-sizing: border-box; +} + +.logo_dot.live > .logo_dot_inner { + display: initial; +} + +.logo_dot_inner { + width: 100%; + height: 100%; + border: 1px solid var(--color-colors-warning); + transform: scale(1); + border-radius: 100%; + display: flex; + box-sizing: border-box; + animation-name: grow; + animation-iteration-count: infinite; + animation-direction: normal; + animation-duration: 3s; } diff --git a/src/styles/animations.css b/src/styles/animations.css new file mode 100644 index 0000000..52dcb7e --- /dev/null +++ b/src/styles/animations.css @@ -0,0 +1,19 @@ +@keyframes slide { + from { + transform: translate(0, 0); + } + to { + transform: translate(0, 25%); + } +} + +@keyframes grow { + from { + border-color: var(--color-colors-warning); + transform: scale(0); + } + to { + border-color: transparent; + transform: scale(3); + } +} diff --git a/src/styles/fonts.css b/src/styles/fonts.css new file mode 100644 index 0000000..e760338 --- /dev/null +++ b/src/styles/fonts.css @@ -0,0 +1,4 @@ +@font-face { + font-family: "Inter"; + src: url("/fonts/Inter.ttf") format("truetype"); +} diff --git a/src/styles/global.css b/src/styles/global.css deleted file mode 100644 index 688a5f7..0000000 --- a/src/styles/global.css +++ /dev/null @@ -1,9 +0,0 @@ -html { - font-size: 100%; -} - -html, -body { - margin: 0; - padding: 0; -} diff --git a/src/styles/system.css b/src/styles/system.css deleted file mode 100644 index 9d83bb9..0000000 --- a/src/styles/system.css +++ /dev/null @@ -1,36 +0,0 @@ -/* Typography */ - -@font-face { - font-family: "Lato"; - src: url("fonts/lato-regular-webfont.woff2") format("woff2"), - url("fonts/lato-regular-webfont.woff") format("woff"); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: "Lato Light Italic"; - src: url("fonts/lato-lightitalic.woff2") format("woff2"), - url("fonts/lato-lightitalic.woff") format("woff"); - font-weight: normal; - font-style: normal; -} - -/* Colors */ - -:root { - --color-primary-0: #252525; - --color-primary-1: #ded8d8; - --color-primary-2: #7d7d7d; - --color-primary-3: #010101; - --color-primary-4: #0e0404; - - --color-complement-0: #1e1e1e; - --color-complement-1: #adb2ad; - --color-complement-2: #646464; - --color-complement-3: #010101; - --color-complement-4: #030b03; - - --time: 0; - --lol: calc(100px * sin(var(--time))); -} diff --git a/src/styles/variables.css b/src/styles/variables.css new file mode 100644 index 0000000..930e645 --- /dev/null +++ b/src/styles/variables.css @@ -0,0 +1,14 @@ +:root { + --color-colors-base: #252525; + --color-colors-warning: #ff0000; + --color-colors-additional-dark: #bebebe; + --color-colors-additional-medium: #cbcaca; + --color-colors-additional-light: #d9d9d9; + + --size-font-small: 0.75rem; + --size-font-medium: 1rem; + --size-font-large: 2rem; + --size-font-base: 1rem; + + --typography-font-family: Inter; +} diff --git a/src/websocket.js b/src/websocket.js new file mode 100644 index 0000000..2b00504 --- /dev/null +++ b/src/websocket.js @@ -0,0 +1,8 @@ +import { processMessage } from "./messagesProcessor.js"; + +const socketProtocol = location.protocol === "http:" ? "ws:" : "wss:"; + +export async function connectToServer() { + const ws = new WebSocket(`${socketProtocol}//${location.host}/meta`); + ws.addEventListener("message", processMessage); +}