diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..5ae09e9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+# syntax=docker/dockerfile:1
+FROM nginx:1.22.0-alpine
+WORKDIR /var/www/
+COPY nginx/nginx.conf /etc/nginx/nginx.conf
+RUN mkdir -p /var/www/website
+COPY src /var/www/website
+
+EXPOSE 80
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cab5ff3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+login:
+ docker login gitea.bjornmossa.net
+
+start:
+ npx live-server ./src
+
+build:
+ docker build . -t gitea.bjornmossa.net/radioiceberg/website
+
+push: login
+ docker push gitea.bjornmossa.net/radioiceberg/website
+
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 0000000..c2bb3c3
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,50 @@
+include /etc/nginx/modules_enabled/*.conf;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ server {
+ listen 80;
+ server_name radioiceberg.net www.radioiceberg.net localhost 127.0.0.1;
+ rewrite ^/(.*) https://radioiceberg.net/$1 permanent;
+ }
+
+ server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ include /etc/nginx/mime.types;
+ server_name radioiceberg.net www.radioiceberg.net localhost 127.0.0.1;
+
+ #SSL
+ #ssl_certificate /etc/letsencrypt/live/radioiceberg.net/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/radioiceberg.net/privkey.pem;
+ ssl_trusted_certificate /etc/letsencrypt/live/radioiceberg.net/chain.pem;
+
+ location / {
+ root /var/www/website;
+ index index.html;
+ }
+
+ location /stream {
+ proxy_read_timeout 3000;
+ proxy_connect_timeout 3000;
+ proxy_redirect off;
+ proxy_pass http://icecast:8000/iceberg.ogg;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Server $host;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ }
+
+ location /meta {
+ proxy_pass http://metadata:7000;
+ proxy_http_version 1.1;
+ proxy_connect_timeout 1d;
+ proxy_send_timeout 1d;
+ proxy_read_timeout 1d;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "Upgrade";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/fonts/lato-lightitalic.woff b/src/fonts/lato-lightitalic.woff
new file mode 100644
index 0000000..86b3600
Binary files /dev/null and b/src/fonts/lato-lightitalic.woff differ
diff --git a/src/fonts/lato-lightitalic.woff2 b/src/fonts/lato-lightitalic.woff2
new file mode 100644
index 0000000..77aabad
Binary files /dev/null and b/src/fonts/lato-lightitalic.woff2 differ
diff --git a/src/fonts/lato-regular-webfont.woff2 b/src/fonts/lato-regular-webfont.woff2
new file mode 100644
index 0000000..495cb9e
Binary files /dev/null and b/src/fonts/lato-regular-webfont.woff2 differ
diff --git a/src/icons/android-chrome-192x192.png b/src/icons/android-chrome-192x192.png
new file mode 100644
index 0000000..937d2e0
Binary files /dev/null and b/src/icons/android-chrome-192x192.png differ
diff --git a/src/icons/android-chrome-512x512.png b/src/icons/android-chrome-512x512.png
new file mode 100644
index 0000000..939b049
Binary files /dev/null and b/src/icons/android-chrome-512x512.png differ
diff --git a/src/icons/apple-touch-icon.png b/src/icons/apple-touch-icon.png
new file mode 100644
index 0000000..dbbb103
Binary files /dev/null and b/src/icons/apple-touch-icon.png differ
diff --git a/src/icons/favicon-16x16.png b/src/icons/favicon-16x16.png
new file mode 100644
index 0000000..ebd0022
Binary files /dev/null and b/src/icons/favicon-16x16.png differ
diff --git a/src/icons/favicon-32x32.png b/src/icons/favicon-32x32.png
new file mode 100644
index 0000000..32673ff
Binary files /dev/null and b/src/icons/favicon-32x32.png differ
diff --git a/src/icons/favicon.ico b/src/icons/favicon.ico
new file mode 100644
index 0000000..c9acbc6
Binary files /dev/null and b/src/icons/favicon.ico differ
diff --git a/src/icons/site.webmanifest b/src/icons/site.webmanifest
new file mode 100644
index 0000000..cd8a657
--- /dev/null
+++ b/src/icons/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "Radio Iceberg",
+ "short_name": "Iceberg",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ded8d8",
+ "background_color": "#252525",
+ "display": "standalone"
+}
diff --git a/src/images/social-preview.jpg b/src/images/social-preview.jpg
new file mode 100644
index 0000000..06be136
Binary files /dev/null and b/src/images/social-preview.jpg differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..d0d8a32
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,94 @@
+
+
+
+
+ Radio . Iceberg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Radio . Iceberg
+
+
+
now playing:
+
We don't know'
+
yet...
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..e608cd7
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,115 @@
+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`;
+ }
+};
+
+const main = () => {
+ hideAudio();
+ addPlayerInteractions();
+ connectToServer();
+};
+
+document.addEventListener("DOMContentLoaded", main);
diff --git a/src/scripts/dom.js b/src/scripts/dom.js
new file mode 100644
index 0000000..fe89522
--- /dev/null
+++ b/src/scripts/dom.js
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..8ffb66c
--- /dev/null
+++ b/src/scripts/interactions.js
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000..e69de29
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..e588888
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,70 @@
+@import url("styles/global.css");
+@import url("styles/system.css");
+
+body {
+ background-color: var(--color-primary-0);
+ color: var(--color-primary-1);
+ height: 100vh;
+ max-height: 100vh;
+ overflow: hidden;
+ position: relative;
+ line-height: 1.75;
+ padding: 1rem;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5 {
+ margin: 3rem 0 1.38rem;
+ font-family: "Lato", sans-serif;
+ font-weight: 400;
+ line-height: 1.3;
+}
+
+h1 {
+ text-transform: uppercase;
+}
+
+h1 {
+ margin-top: 0;
+ font-size: 5.063rem;
+}
+
+.play-stream {
+ background: transparent;
+ border: none;
+ padding: 0;
+ width: 300px;
+ cursor: pointer;
+}
+
+.now-playing {
+ font-family: "Lato Light Italic";
+ font-size: 1.5rem;
+}
+
+.icebergs {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ rotate: -45deg;
+ translate: 10% 50%;
+ max-height: 70vh;
+}
+
+#iceberg1,
+#iceberg2,
+#iceberg3 {
+ transition: transform 2s ease-in-out;
+ transform: translate(0);
+}
+
+#iceberg1.active {
+ transform: translate(0, calc(100px * sin(var(--time))));
+}
+
+#iceberg2.active {
+ transform: translate(0, calc(50px * cos(var(--time))));
+}
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 0000000..688a5f7
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,9 @@
+html {
+ font-size: 100%;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
diff --git a/src/styles/system.css b/src/styles/system.css
new file mode 100644
index 0000000..9d83bb9
--- /dev/null
+++ b/src/styles/system.css
@@ -0,0 +1,36 @@
+/* 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)));
+}