diff options
| -rw-r--r-- | .hintrc | 15 | ||||
| -rw-r--r-- | apps/stream/__init__.py | 0 | ||||
| -rw-r--r-- | apps/stream/admin.py | 3 | ||||
| -rw-r--r-- | apps/stream/apps.py | 6 | ||||
| -rw-r--r-- | apps/stream/migrations/__init__.py | 0 | ||||
| -rw-r--r-- | apps/stream/models.py | 3 | ||||
| -rw-r--r-- | apps/stream/tests.py | 3 | ||||
| -rw-r--r-- | apps/stream/urls.py | 9 | ||||
| -rw-r--r-- | apps/stream/views.py | 101 | ||||
| -rw-r--r-- | static/css/en/login-area.css | 31 | ||||
| -rw-r--r-- | static/css/ja/login-area.css | 31 | ||||
| -rw-r--r-- | static/css/shared/core.css | 372 | ||||
| -rw-r--r-- | static/css/shared/kawaiibeats.css | 106 | ||||
| -rw-r--r-- | static/css/shared/login-area.css | 96 | ||||
| -rw-r--r-- | static/css/sidebar.css | 10 | ||||
| -rw-r--r-- | static/extra/player_skin.wsz | bin | 210107 -> 0 bytes | |||
| -rw-r--r-- | static/images/kawaiibeats/kawaii_beats.gif | bin | 0 -> 893194 bytes | |||
| -rw-r--r-- | static/js/musicPlayer.js | 540 | ||||
| -rw-r--r-- | static/js/shared/resolutionScaling.js | 136 | ||||
| -rw-r--r-- | static/js/youdontdare.js | 2 | ||||
| -rw-r--r-- | templates/shared/base.html | 18 | ||||
| -rw-r--r-- | templates/shared/header.html | 8 | ||||
| -rw-r--r-- | templates/shared/right_sidebar.html | 27 | ||||
| -rw-r--r-- | thatcomputerscientist/urls.py | 1 |
24 files changed, 1064 insertions, 454 deletions
diff --git a/.hintrc b/.hintrc new file mode 100644 index 00000000..e7fa2a23 --- /dev/null +++ b/.hintrc @@ -0,0 +1,15 @@ +{ + "extends": [ + "development" + ], + "hints": { + "compat-api/css": [ + "default", + { + "ignore": [ + "image-rendering: crisp-edges" + ] + } + ] + } +}
\ No newline at end of file diff --git a/apps/stream/__init__.py b/apps/stream/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/apps/stream/__init__.py diff --git a/apps/stream/admin.py b/apps/stream/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/stream/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/stream/apps.py b/apps/stream/apps.py new file mode 100644 index 00000000..da754011 --- /dev/null +++ b/apps/stream/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StreamConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.stream" diff --git a/apps/stream/migrations/__init__.py b/apps/stream/migrations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/apps/stream/migrations/__init__.py diff --git a/apps/stream/models.py b/apps/stream/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/apps/stream/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/stream/tests.py b/apps/stream/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/stream/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/stream/urls.py b/apps/stream/urls.py new file mode 100644 index 00000000..326c83e6 --- /dev/null +++ b/apps/stream/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "stream" +urlpatterns = [ + path("random-song", views.random_song, name="random_song"), + path("song/<str:song_id>", views.stream_song, name="stream_song"), +] diff --git a/apps/stream/views.py b/apps/stream/views.py new file mode 100644 index 00000000..820792c2 --- /dev/null +++ b/apps/stream/views.py @@ -0,0 +1,101 @@ +# views.py +import os +import random +from hashlib import md5 +from typing import Dict, Union + +import requests +from django.http import HttpResponse, JsonResponse +from django.conf import settings +from django.core.cache import cache + +# Constants from environment variables +API_CONFIG = { + "BASE_URL": os.getenv("MUSIC_API_BASE_URL", "http://music.shi.foo/rest"), + "USERNAME": os.getenv("MUSIC_API_USERNAME"), + "PASSWORD": os.getenv("MUSIC_API_PASSWORD"), + "VERSION": os.getenv("MUSIC_API_VERSION", "1.16.1"), + "CLIENT": os.getenv("MUSIC_API_CLIENT", "Shifoo"), +} + + +def get_auth_params() -> Dict[str, str]: + """Generate authentication parameters for API requests.""" + salt = "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=6)) + token = md5((API_CONFIG["PASSWORD"] + salt).encode()).hexdigest() + + return { + "u": API_CONFIG["USERNAME"], + "t": token, + "s": salt, + "v": API_CONFIG["VERSION"], + "c": API_CONFIG["CLIENT"], + } + + +def make_api_request( + endpoint: str, params: Dict = None, stream: bool = False +) -> Union[requests.Response, Dict]: + """Make an API request with error handling.""" + auth_params = get_auth_params() + if params: + auth_params.update(params) + + url = f"{API_CONFIG['BASE_URL']}/{endpoint}" + + try: + response = requests.get(url, params=auth_params, stream=stream) + response.raise_for_status() + return response if stream else response.json() + except requests.RequestException as e: + # Log error here if you have logging configured + return {"error": str(e)} + + +def random_song(request) -> JsonResponse: + """Get a random song from the API.""" + # Try to get cached response first + cache_key = "random_song_response" + cached_response = cache.get(cache_key) + if cached_response: + return JsonResponse(cached_response) + + response = make_api_request("getRandomSongs", {"size": 1, "f": "json"}) + + if "error" in response: + return JsonResponse({"error": response["error"]}, status=500) + + try: + subsonic_response = response.get("subsonic-response", {}) + song = subsonic_response.get("randomSongs", {}).get("song", [{}])[0] + + if not song: + return JsonResponse({"error": "No song found"}, status=404) + + # Construct response data + response_data = { + "song": song, + "coverURL": f"{API_CONFIG['BASE_URL']}/getCoverArt", + "coverParams": {"id": song.get("coverArt"), **get_auth_params()}, + } + + # Cache the response for a short period + cache.set(cache_key, response_data, 30) # Cache for 30 seconds + + return JsonResponse(response_data) + except Exception as e: + # Log error here if you have logging configured + return JsonResponse({"error": str(e)}, status=500) + + +def stream_song(request, song_id: str) -> HttpResponse: + """Stream a specific song by ID.""" + response = make_api_request("stream", {"id": song_id}, stream=True) + + if isinstance(response, dict) and "error" in response: + return JsonResponse({"error": response["error"]}, status=500) + + return HttpResponse( + response.raw.read(), + content_type=response.headers.get("Content-Type", "audio/mpeg"), + ) diff --git a/static/css/en/login-area.css b/static/css/en/login-area.css index 29024e12..c339605f 100644 --- a/static/css/en/login-area.css +++ b/static/css/en/login-area.css @@ -1,31 +1,18 @@ #login-area { - background-image: image-set( - url("../../images/core/sidebar/[email protected]") 1x, - url("../../images/core/sidebar/[email protected]") 2x, - url("../../images/core/sidebar/[email protected]") 3x - ); + background-image: -webkit-image-set(url("../../images/core/sidebar/[email protected]") 1x, + url("../../images/core/sidebar/[email protected]") 2x, + url("../../images/core/sidebar/[email protected]") 3x); + background-image: image-set(url("../../images/core/sidebar/[email protected]") 1x, + url("../../images/core/sidebar/[email protected]") 2x, + url("../../images/core/sidebar/[email protected]") 3x); } -#login-area > #register-now-button { - display: block; +#login-area>#register-now-button { width: 50px; - height: 12px; - cursor: pointer; - border-radius: 2px; - position: absolute; - bottom: 23px; right: 32px; - background: transparent; } -#login-area > #forgot-password-button { - display: block; +#login-area>#forgot-password-button { width: 64px; - height: 12px; - cursor: pointer; - border-radius: 2px; - position: absolute; - bottom: 85px; right: 21px; - background: transparent; -} +}
\ No newline at end of file diff --git a/static/css/ja/login-area.css b/static/css/ja/login-area.css index c05a350b..4404eada 100644 --- a/static/css/ja/login-area.css +++ b/static/css/ja/login-area.css @@ -1,31 +1,18 @@ #login-area { - background-image: image-set( - url("../../images/core/sidebar/[email protected]") 1x, - url("../../images/core/sidebar/[email protected]") 2x, - url("../../images/core/sidebar/[email protected]") 3x - ); + background-image: -webkit-image-set(url("../../images/core/sidebar/[email protected]") 1x, + url("../../images/core/sidebar/[email protected]") 2x, + url("../../images/core/sidebar/[email protected]") 3x); + background-image: image-set(url("../../images/core/sidebar/[email protected]") 1x, + url("../../images/core/sidebar/[email protected]") 2x, + url("../../images/core/sidebar/[email protected]") 3x); } -#login-area > #register-now-button { - display: block; +#login-area>#register-now-button { width: 76px; - height: 12px; - cursor: pointer; - border-radius: 2px; - position: absolute; - bottom: 23px; right: 27px; - background: transparent; } -#login-area > #forgot-password-button { - display: block; +#login-area>#forgot-password-button { width: 75px; - height: 12px; - cursor: pointer; - border-radius: 2px; - position: absolute; - bottom: 85px; right: 14px; - background: transparent; -} +}
\ No newline at end of file diff --git a/static/css/shared/core.css b/static/css/shared/core.css index 06fcb57c..884ddbbf 100644 --- a/static/css/shared/core.css +++ b/static/css/shared/core.css @@ -1,42 +1,52 @@ -/* Shared CSS File */ +/* Reset and Base Styles */ * { margin: 0; padding: 0; box-sizing: border-box; -} - -*, -input, -button { + font-family: "Mali", sans-serif; + font-size: 11px; outline: none; border: none; } -hr { - border: 0; - height: 1px; - background: #fff; +/* Root Variables */ +:root { + --resolution-multiplier: 1; } +/* HTML and Body Base */ html { background-color: #000; color: #fff; + min-width: 1024px; +} + +body { + min-height: 100vh; + overflow-x: hidden; + position: relative; +} + +/* Element Styles */ +hr { + height: 1px; + background: #fff; } a:visited { color: #fff; } -#video-background { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - object-fit: cover; - z-index: -100; +img { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; } +/* Video Overlay */ #video-overlay { position: fixed; top: 0; @@ -48,6 +58,7 @@ a:visited { z-index: -99; } +/* Layout Structure */ #body-wrapper { position: relative; z-index: 1; @@ -59,6 +70,7 @@ a:visited { display: flex; } +/* Sidebar and Main Content */ #left-sidebar { width: 200px; border-top: 1px solid #fff; @@ -74,328 +86,4 @@ a:visited { #right-sidebar { width: 200px; border-top: 1px solid #fff; -} - -.flip-container { - perspective: 1000px; -} - -.flipper { - position: relative; - transform-style: preserve-3d; - transition: transform 0.6s; -} - -.front, -.back { - position: absolute; - width: 100%; - height: 100%; - backface-visibility: hidden; - display: flex; - align-items: center; - justify-content: start; -} - -.back { - transform: rotateX(180deg); -} - -.glitch, -.glow { - color: #dfbfbf; - position: relative; - font-size: 18px; - font-weight: bold; - animation: glitch 5s 5s infinite; -} - -.glitch::before, -.glow::before { - content: attr(data-text); - position: absolute; - left: -2px; - text-shadow: -5px 0 magenta; - background: black; - overflow: hidden; - top: 0; - animation: noise-1 3s linear infinite alternate-reverse, - glitch 5s 5.05s infinite; -} - -.glitch::after, -.glow::after { - content: attr(data-text); - position: absolute; - left: 2px; - text-shadow: -5px 0 lightgreen; - background: black; - overflow: hidden; - top: 0; - animation: noise-2 3s linear infinite alternate-reverse, glitch 5s 5s infinite; -} - -@keyframes glitch { - 1% { - transform: rotateX(10deg) skewX(90deg); - } - - 2% { - transform: rotateX(0deg) skewX(0deg); - } -} - -@keyframes noise-1 { - 3.3333333333% { - clip-path: inset(9px 0 46px 0); - } - - 6.6666666667% { - clip-path: inset(54px 0 5px 0); - } - - 10% { - clip-path: inset(13px 0 46px 0); - } - - 13.3333333333% { - clip-path: inset(54px 0 31px 0); - } - - 16.6666666667% { - clip-path: inset(80px 0 1px 0); - } - - 20% { - clip-path: inset(61px 0 29px 0); - } - - 23.3333333333% { - clip-path: inset(39px 0 32px 0); - } - - 26.6666666667% { - clip-path: inset(25px 0 43px 0); - } - - 30% { - clip-path: inset(68px 0 5px 0); - } - - 33.3333333333% { - clip-path: inset(4px 0 42px 0); - } - - 36.6666666667% { - clip-path: inset(90px 0 4px 0); - } - - 40% { - clip-path: inset(75px 0 25px 0); - } - - 43.3333333333% { - clip-path: inset(56px 0 17px 0); - } - - 46.6666666667% { - clip-path: inset(24px 0 26px 0); - } - - 50% { - clip-path: inset(90px 0 8px 0); - } - - 53.3333333333% { - clip-path: inset(22px 0 65px 0); - } - - 56.6666666667% { - clip-path: inset(45px 0 29px 0); - } - - 60% { - clip-path: inset(93px 0 3px 0); - } - - 63.3333333333% { - clip-path: inset(80px 0 12px 0); - } - - 66.6666666667% { - clip-path: inset(93px 0 4px 0); - } - - 70% { - clip-path: inset(75px 0 17px 0); - } - - 73.3333333333% { - clip-path: inset(96px 0 1px 0); - } - - 76.6666666667% { - clip-path: inset(68px 0 5px 0); - } - - 80% { - clip-path: inset(10px 0 64px 0); - } - - 83.3333333333% { - clip-path: inset(78px 0 18px 0); - } - - 86.6666666667% { - clip-path: inset(14px 0 36px 0); - } - - 90% { - clip-path: inset(54px 0 4px 0); - } - - 93.3333333333% { - clip-path: inset(18px 0 65px 0); - } - - 96.6666666667% { - clip-path: inset(79px 0 14px 0); - } - - 100% { - clip-path: inset(82px 0 11px 0); - } -} - -@keyframes noise-2 { - 0% { - clip-path: inset(45px 0 42px 0); - } - - 3.3333333333% { - clip-path: inset(97px 0 2px 0); - } - - 6.6666666667% { - clip-path: inset(98px 0 2px 0); - } - - 10% { - clip-path: inset(67px 0 19px 0); - } - - 13.3333333333% { - clip-path: inset(28px 0 8px 0); - } - - 16.6666666667% { - clip-path: inset(85px 0 2px 0); - } - - 20% { - clip-path: inset(94px 0 2px 0); - } - - 23.3333333333% { - clip-path: inset(75px 0 3px 0); - } - - 26.6666666667% { - clip-path: inset(47px 0 42px 0); - } - - 30% { - clip-path: inset(69px 0 9px 0); - } - - 33.3333333333% { - clip-path: inset(79px 0 22px 0); - } - - 36.6666666667% { - clip-path: inset(50px 0 5px 0); - } - - 40% { - clip-path: inset(2px 0 53px 0); - } - - 43.3333333333% { - clip-path: inset(74px 0 19px 0); - } - - 46.6666666667% { - clip-path: inset(8px 0 57px 0); - } - - 50% { - clip-path: inset(96px 0 3px 0); - } - - 53.3333333333% { - clip-path: inset(19px 0 36px 0); - } - - 56.6666666667% { - clip-path: inset(18px 0 43px 0); - } - - 60% { - clip-path: inset(28px 0 50px 0); - } - - 63.3333333333% { - clip-path: inset(17px 0 3px 0); - } - - 66.6666666667% { - clip-path: inset(77px 0 4px 0); - } - - 70% { - clip-path: inset(64px 0 29px 0); - } - - 73.3333333333% { - clip-path: inset(57px 0 29px 0); - } - - 76.6666666667% { - clip-path: inset(7px 0 62px 0); - } - - 80% { - clip-path: inset(27px 0 10px 0); - } - - 83.3333333333% { - clip-path: inset(44px 0 5px 0); - } - - 86.6666666667% { - clip-path: inset(18px 0 34px 0); - } - - 90% { - clip-path: inset(61px 0 39px 0); - } - - 93.3333333333% { - clip-path: inset(3px 0 51px 0); - } - - 96.6666666667% { - clip-path: inset(28px 0 22px 0); - } - - 100% { - clip-path: inset(48px 0 37px 0); - } -} - -.glow { - text-shadow: 0 0 1000px #dfbfbf; - color: transparent; - position: absolute; - top: 0; }
\ No newline at end of file diff --git a/static/css/shared/kawaiibeats.css b/static/css/shared/kawaiibeats.css new file mode 100644 index 00000000..ca4ce155 --- /dev/null +++ b/static/css/shared/kawaiibeats.css @@ -0,0 +1,106 @@ +/* Kawaii Beats Player Container */ +.kawaiibeats { + width: 200px; + height: 320px; + position: relative; + left: 20px; + top: 6px; + background-image: url("../../images/kawaiibeats/kawaii_beats.gif"); + background-size: 200px 320px; + background-repeat: no-repeat; + -webkit-user-select: none; + user-select: none; +} + +.kawaiibeats::after { + content: ""; + position: absolute; + top: 20px; + left: 20px; + width: 160px; + height: 280px; + background-color: black; + opacity: 0.75; + z-index: -1; +} + +/* Track Information */ +#song-title { + position: absolute; + top: 25px; + left: 20px; + width: 160px; + font-weight: bold; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#song-artist-album { + position: absolute; + top: 40px; + left: 25px; + width: 150px; + font-size: 10px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Player Elements */ +#song-cover { + position: absolute; + top: 65px; + left: 30px; + width: 140px; + height: 140px; +} + +#song-visualizer { + position: absolute; + top: 190px; + left: 30px; +} + +#custom-seekbar { + position: absolute; + top: 220px; + left: 30px; + cursor: pointer; +} + +#song-time { + position: absolute; + top: 240px; + left: 30px; + width: 140px; + font-size: 12px; + display: flex; + justify-content: space-between; +} + +/* Player Controls */ +#song-controls { + position: absolute; + top: 260px; + left: 30px; + width: 140px; + display: flex; + justify-content: space-around; + align-items: center; +} + +#song-controls button { + color: white; + font-size: 16px; + cursor: pointer; + background: none; + border: none; +} + +#song-controls button:disabled { + opacity: 0.5; + cursor: not-allowed; +}
\ No newline at end of file diff --git a/static/css/shared/login-area.css b/static/css/shared/login-area.css index 4fd5331c..680e95c3 100644 --- a/static/css/shared/login-area.css +++ b/static/css/shared/login-area.css @@ -1,15 +1,17 @@ +/* Login Area Container */ #login-area { width: 200px; height: 280px; - background-repeat: no-repeat; - background-size: 200px 280px; - margin: auto; - padding: 0px; - border: 0px; position: relative; top: -65px; + margin: auto; + background-repeat: no-repeat; + background-size: 200px 280px; + padding: 0; + border: 0; } +/* Login Form */ #login-form { display: block; position: relative; @@ -19,22 +21,14 @@ #login-form input[type="text"], #login-form input[type="password"] { display: block; - margin: 10px auto; width: 144px; - font-size: 11px; + margin: 10px auto; padding: 4px 8px; - background: transparent; + font-size: 11px; + font-weight: bold; color: #4a2e6f; + background: transparent; border-radius: 4px; - font-weight: bold; -} - -/* Reset auto fill */ -#login-form input:-webkit-autofill, -#login-form input:-webkit-autofill:hover, -#login-form input:-webkit-autofill:focus, -#login-form input:-webkit-autofill:active { - transition: background-color 1s ease-in 2000s; } #login-form input::placeholder { @@ -43,52 +37,78 @@ } #login-form input[type="submit"] { - width: 106px; - height: 22px; - cursor: pointer; position: absolute; top: 96px; left: 48px; - border-radius: 2px; + width: 106px; + height: 22px; background: transparent; + border-radius: 2px; + cursor: pointer; } +/* Autofill Override */ +#login-form input:-webkit-autofill, +#login-form input:-webkit-autofill:hover, +#login-form input:-webkit-autofill:focus, +#login-form input:-webkit-autofill:active { + transition: background-color 1s ease-in 2000s; +} + +/* Error Messages */ #login-error { position: relative; } -#login-error > .RFEERR { +#login-error>.messageBox { + position: absolute; + top: -100px; + left: -140px; + z-index: 2; + width: 250px; + height: 166px; + background-size: 250px 166px; +} + +#login-error>.RFEERR { background: url("../images/backgrounds/login-messages/RFEERR.png") no-repeat; } -#login-error > .IUOPERR { +#login-error>.IUOPERR { background: url("../images/backgrounds/login-messages/IUOPERR.png") no-repeat; } -#login-error > .ENVERR { +#login-error>.ENVERR { background: url("../images/backgrounds/login-messages/ENVERR.png") no-repeat; } -#login-error > .VESENDERR { - background: url("../images/backgrounds/login-messages/VESENDERR.png") - no-repeat; +#login-error>.VESENDERR { + background: url("../images/backgrounds/login-messages/VESENDERR.png") no-repeat; } -#login-error > .VESENT { +#login-error>.VESENT { background: url("../images/backgrounds/login-messages/VESENT.png") no-repeat; } -#login-error > .VESUCCESS { - background: url("../images/backgrounds/login-messages/VESUCCESS.png") - no-repeat; +#login-error>.VESUCCESS { + background: url("../images/backgrounds/login-messages/VESUCCESS.png") no-repeat; } -#login-error > .messageBox { +/* Login Area Button Styles */ +#login-area>#register-now-button, +#login-area>#forgot-password-button { + display: block; + height: 12px; + cursor: pointer; + border-radius: 2px; position: absolute; - background-size: 250px 166px; - width: 250px; - height: 166px; - top: -100px; - left: -140px; - z-index: 2; + background: transparent; } + +#login-area>#register-now-button { + bottom: 23px; +} + +#login-area>#forgot-password-button { + bottom: 85px; +}
\ No newline at end of file diff --git a/static/css/sidebar.css b/static/css/sidebar.css index d9ac75ae..07d2c6ef 100644 --- a/static/css/sidebar.css +++ b/static/css/sidebar.css @@ -75,7 +75,7 @@ height: 150px; } -#user-area > h2 { +#user-area>h2 { margin: 0; padding: 0; border: none; @@ -84,7 +84,7 @@ top: 15px; } -#user-area > ul { +#user-area>ul { position: absolute; left: 110px; top: 58px; @@ -121,7 +121,7 @@ border-bottom-right-radius: 8px; } -#online-users div > ul { +#online-users div>ul { padding: 0 0 0 20px; } @@ -143,8 +143,8 @@ top: 0px; height: 100px; width: 200px; - mask: url("https://upload.wikimedia.org/wikipedia/commons/9/9b/DVD_logo.svg"); -webkit-mask: url("https://upload.wikimedia.org/wikipedia/commons/9/9b/DVD_logo.svg"); + mask: url("https://upload.wikimedia.org/wikipedia/commons/9/9b/DVD_logo.svg"); background-repeat: no-repeat; background-size: 150px; background-position: center; @@ -156,4 +156,4 @@ font-size: 16px; font-weight: bold; background: linear-gradient(0deg, #8663e570 60%, #8663e595 60%); -} +}
\ No newline at end of file diff --git a/static/extra/player_skin.wsz b/static/extra/player_skin.wsz Binary files differdeleted file mode 100644 index 23a10c59..00000000 --- a/static/extra/player_skin.wsz +++ /dev/null diff --git a/static/images/kawaiibeats/kawaii_beats.gif b/static/images/kawaiibeats/kawaii_beats.gif Binary files differnew file mode 100644 index 00000000..8ac5e3e6 --- /dev/null +++ b/static/images/kawaiibeats/kawaii_beats.gif diff --git a/static/js/musicPlayer.js b/static/js/musicPlayer.js new file mode 100644 index 00000000..bf1586b9 --- /dev/null +++ b/static/js/musicPlayer.js @@ -0,0 +1,540 @@ +// Collection of random anime artwork +const artworkCollection = [ + 'https://i.pinimg.com/enabled/564x/e2/5d/31/e25d3199f73c9453035727f8c7a70170.jpg', + 'https://i.pinimg.com/enabled/564x/5f/ed/28/5fed282cff8d22ac857e2a489031d05a.jpg', + 'https://i.pinimg.com/736x/05/a8/71/05a87162a78e2cad2ffe0a9eac6b4e2c.jpg', + 'https://i.pinimg.com/736x/c6/ac/13/c6ac139ed02c9accd34dbb16d7466025.jpg', + 'https://i.pinimg.com/736x/72/69/c3/7269c3d939764b024da9a6869dc59a0f.jpg', + 'https://i.pinimg.com/enabled/564x/cc/5c/6f/cc5c6f1c8e053d791ae2b4300ef5c9fe.jpg', + 'https://i.pinimg.com/enabled/564x/fa/0a/c2/fa0ac2b7145af1205c87350f7c735683.jpg', + 'https://i.pinimg.com/enabled/564x/94/84/57/9484579fcbf7e768d6206b07fa44c2b9.jpg', + 'https://i.pinimg.com/enabled/564x/fd/30/bf/fd30bf62f6409129ce6538f1b9ed7b8b.jpg', + 'https://i.pinimg.com/enabled/564x/44/b2/11/44b21104b4e41736c99ee183127aab3d.jpg', + 'https://i.pinimg.com/enabled/564x/f7/ce/56/f7ce5629aa91866020a559ef7e249f1c.jpg', + 'https://i.pinimg.com/enabled/564x/7b/ac/36/7bac368ff9b5f702d9b727491f8d4ef0.jpg', + 'https://i.pinimg.com/enabled/564x/39/1a/51/391a514a013f62ca9f25f47b4cbd7776.jpg', + 'https://i.pinimg.com/enabled/564x/03/d2/96/03d2967de5d249f88155cab461e69f3a.jpg' +]; + +// Constants +const SEEKBAR_CONFIG = { + HEIGHT: 4, + THUMB_RADIUS: 6, + HOVER_RADIUS: 8, + COLORS: { + BASE: 'rgba(255, 255, 255, 0.5)', + PROGRESS: 'rgba(255, 255, 255, 1)', + HOVER: 'rgba(255, 255, 255, 0.8)' + } +}; + +const STORE_LIMIT = artworkCollection.length; +const MIN_SCREEN_WIDTH = 1600; + +// Audio Context and Core Variables +let audioContext; +let sourceNode; +let analyzerNode; +let audioBuffer; +let startTime; +let pauseTime = 0; +let isPlaying = false; +let currentSong = null; +let isLoading = true; +let isDragging = false; + +// DOM Elements +const elements = { + playButton: document.getElementById('song-play'), + prevButton: document.getElementById('song-prev'), + nextButton: document.getElementById('song-next'), + timeElapsed: document.getElementById('song-time-elapsed'), + timeTotal: document.getElementById('song-time-total'), + songCover: document.getElementById('song-cover'), + songTitle: document.getElementById('song-title'), + songArtistAlbum: document.getElementById('song-artist-album'), + visualizer: document.getElementById('song-visualizer') +}; + +// Create and setup seekbar +const seekbarCanvas = document.createElement('canvas'); +seekbarCanvas.id = 'custom-seekbar'; +seekbarCanvas.width = 140; +seekbarCanvas.height = 20; +seekbarCanvas.style.cssText = 'position: absolute; left: 30px; top: 220px; cursor: pointer; z-index: 1;'; +document.getElementById('song-time').parentNode.insertBefore(seekbarCanvas, document.getElementById('song-time')); + +class ArtworkManager { + constructor() { + this.usedArtwork = new Set(); + this.availableArtwork = [...artworkCollection]; + } + + getRandomArtwork() { + // If all artwork has been used, reset the pool + if (this.availableArtwork.length === 0) { + this.resetArtworkPool(); + } + + // Get random artwork from available pool + const randomIndex = Math.floor(Math.random() * this.availableArtwork.length); + const artwork = this.availableArtwork[randomIndex]; + + // Remove from available pool and add to used set + this.availableArtwork.splice(randomIndex, 1); + this.usedArtwork.add(artwork); + + return artwork; + } + + resetArtworkPool() { + this.availableArtwork = [...artworkCollection]; + this.usedArtwork.clear(); + } + + releaseArtwork(artwork) { + if (this.usedArtwork.has(artwork)) { + this.usedArtwork.delete(artwork); + this.availableArtwork.push(artwork); + } + } +} + +// Song Store Management +class SongStore { + constructor() { + this.songs = JSON.parse(localStorage.getItem('songStore')) || []; + this.currentIndex = parseInt(localStorage.getItem('currentSongIndex')) || -1; + this.artworkCache = JSON.parse(localStorage.getItem('artworkCache')) || {}; + this.artworkManager = new ArtworkManager(); + + // Restore artwork state + Object.values(this.artworkCache).forEach(artwork => { + this.artworkManager.usedArtwork.add(artwork); + }); + + // Remove any artwork from availableArtwork that's already in use + this.artworkManager.availableArtwork = this.artworkManager.availableArtwork + .filter(artwork => !this.artworkManager.usedArtwork.has(artwork)); + } + + async addSong(song) { + if (!song) return null; + + // Generate unique artwork for the song + const artwork = this.artworkManager.getRandomArtwork(); + this.artworkCache[song.id] = artwork; + + // Always add the song, even if it's a duplicate + this.songs.push({ + id: song.id, + title: song.title, + artist: song.artist, + album: song.album + }); + + // Maintain the store limit + if (this.songs.length > STORE_LIMIT) { + const removedSong = this.songs.shift(); + // Release the artwork back to the pool + const removedArtwork = this.artworkCache[removedSong.id]; + this.artworkManager.releaseArtwork(removedArtwork); + delete this.artworkCache[removedSong.id]; + if (this.currentIndex > -1) this.currentIndex--; + } + + this.currentIndex = this.songs.length - 1; + this.save(); + + return { + ...this.songs[this.currentIndex], + artwork: this.artworkCache[song.id] + }; + } + + async getNext() { + if (this.currentIndex < this.songs.length - 1) { + this.currentIndex++; + this.save(); + return { + ...this.songs[this.currentIndex], + artwork: this.artworkCache[this.songs[this.currentIndex].id] + }; + } + // Get new song if we're at the end + const newSong = await this.fetchNewSong(); + return this.addSong(newSong); + } + + async getPrevious() { + if (this.currentIndex > 0) { + this.currentIndex--; + this.save(); + return { + ...this.songs[this.currentIndex], + artwork: this.artworkCache[this.songs[this.currentIndex].id] + }; + } + return null; + } + + async fetchNewSong() { + try { + const response = await fetch('/stream/random-song'); + const data = await response.json(); + return data.song; + } catch (error) { + console.error('Error fetching new song:', error); + return null; + } + } + + save() { + localStorage.setItem('songStore', JSON.stringify(this.songs)); + localStorage.setItem('currentSongIndex', this.currentIndex.toString()); + localStorage.setItem('artworkCache', JSON.stringify(this.artworkCache)); + } + + getCurrentSong() { + if (this.currentIndex >= 0) { + const song = this.songs[this.currentIndex]; + return { + ...song, + artwork: this.artworkCache[song.id] + }; + } + return null; + } + + getArtwork(songId) { + return this.artworkCache[songId]; + } +} + +const songStore = new SongStore(); + +// Audio Control Functions +async function initAudio() { + audioContext = new (window.AudioContext || window.webkitAudioContext)(); + analyzerNode = audioContext.createAnalyser(); + analyzerNode.fftSize = 256; + analyzerNode.connect(audioContext.destination); +} + +function formatTime(time) { + const minutes = Math.floor(time / 60); + const seconds = Math.floor(time % 60).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; +} + +async function fetchAudio(url) { + isLoading = true; + updateControls(); + try { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + elements.timeTotal.textContent = formatTime(audioBuffer.duration); + isLoading = false; + updateControls(); + } catch (error) { + console.error('Error loading audio:', error); + isLoading = false; + updateControls(); + } +} + +function playAudio(offset = 0) { + if (!audioBuffer) return; + if (isPlaying) stopAudio(); + + offset = Math.min(Math.max(0, offset), audioBuffer.duration); + + sourceNode = audioContext.createBufferSource(); + sourceNode.buffer = audioBuffer; + sourceNode.connect(analyzerNode); + + sourceNode.onended = async () => { + const currentTime = audioContext.currentTime - startTime; + if (currentTime >= audioBuffer.duration - 0.1) { + await loadNewSong(true, 'next'); + } + }; + + sourceNode.start(0, offset); + startTime = audioContext.currentTime - offset; + isPlaying = true; + updateUI(); +} + +function stopAudio() { + if (sourceNode) { + sourceNode.stop(); + sourceNode = null; + isPlaying = false; + updateUI(); + } +} + +function seekAudio(time) { + if (!audioBuffer) return; + const wasPlaying = isPlaying; + stopAudio(); + pauseTime = time; + if (wasPlaying) { + playAudio(pauseTime); + } + drawSeekbar(); + savePlaybackState(); +} + +// UI Update Functions +function updateUI() { + elements.playButton.innerHTML = isPlaying ? "❚❚" : "►"; + drawVisualizer(); + updateTimeDisplay(); + drawSeekbar(); +} + +function updateControls() { + elements.playButton.disabled = isLoading; + elements.prevButton.disabled = isLoading; + elements.nextButton.disabled = isLoading; + if (isLoading) stopAudio(); +} + +function updateSongInfo() { + if (currentSong) { + elements.songTitle.textContent = currentSong.title; + elements.songArtistAlbum.textContent = `${currentSong.artist} - ${currentSong.album}`; + // Always update artwork with the cached version or generate new + elements.songCover.src = currentSong.artwork || songStore.getArtwork(currentSong.id); + } +} + +// Canvas Drawing Functions +function drawSeekbar() { + if (!audioBuffer) return; + + const ctx = seekbarCanvas.getContext('2d'); + const { width, height } = seekbarCanvas; + const centerY = height / 2; + + ctx.clearRect(0, 0, width, height); + + // Background bar + ctx.fillStyle = SEEKBAR_CONFIG.COLORS.BASE; + ctx.fillRect(0, centerY - SEEKBAR_CONFIG.HEIGHT / 2, width, SEEKBAR_CONFIG.HEIGHT); + + // Progress bar + const currentTime = isPlaying ? audioContext.currentTime - startTime : pauseTime; + const progress = (currentTime / audioBuffer.duration) * width; + + ctx.fillStyle = SEEKBAR_CONFIG.COLORS.PROGRESS; + ctx.fillRect(0, centerY - SEEKBAR_CONFIG.HEIGHT / 2, progress, SEEKBAR_CONFIG.HEIGHT); + + // Thumb + ctx.beginPath(); + ctx.arc(progress, centerY, SEEKBAR_CONFIG.THUMB_RADIUS, 0, Math.PI * 2); + ctx.fill(); +} + +function drawVisualizer() { + if (!isPlaying) return; + + const ctx = elements.visualizer.getContext('2d'); + const bufferLength = analyzerNode.frequencyBinCount; + const dataArray = new Uint8Array(bufferLength); + + function animate() { + if (!isPlaying) return; + requestAnimationFrame(animate); + + analyzerNode.getByteFrequencyData(dataArray); + ctx.clearRect(0, 0, elements.visualizer.width, elements.visualizer.height); + + const barWidth = (elements.visualizer.width / bufferLength) * 2.5; + let x = 0; + + for (let i = 0; i < bufferLength; i++) { + const barHeight = (dataArray[i] / 255) * elements.visualizer.height; + ctx.fillStyle = `rgb(${dataArray[i]}, 50, 255)`; + ctx.fillRect(x, elements.visualizer.height - barHeight, barWidth, barHeight); + x += barWidth + 1; + } + } + + animate(); +} + +// State Management Functions +function savePlaybackState() { + if (!currentSong) return; + + const currentTime = isPlaying ? audioContext.currentTime - startTime : pauseTime; + const state = { + songId: currentSong.id, + timeStamp: Math.min(currentTime, audioBuffer?.duration || 0), + isPlaying, + artwork: elements.songCover.src, + songTitle: currentSong.title, + songArtist: currentSong.artist, + songAlbum: currentSong.album + }; + + localStorage.setItem('playbackState', JSON.stringify(state)); +} + +// Song Loading and Navigation +async function loadNewSong(autoplay = false, direction = 'next') { + try { + const wasPlaying = isPlaying || autoplay; + stopAudio(); + + let nextSong; + if (direction === 'next') { + nextSong = await songStore.getNext(); + } else { + nextSong = await songStore.getPrevious(); + if (!nextSong) return; // Don't proceed if no previous song + } + + currentSong = nextSong; + updateSongInfo(); // This will now use the cached artwork + + await fetchAudio(`/stream/song/${currentSong.id}`); + pauseTime = 0; // Reset seek position for new song + + if (wasPlaying) { + playAudio(0); + } + + savePlaybackState(); + } catch (error) { + console.error('Error loading song:', error); + } +} + +function getCurrentScale() { + const screenWidth = window.innerWidth; + return screenWidth <= MIN_SCREEN_WIDTH ? 1 : screenWidth / MIN_SCREEN_WIDTH; +} + +// Event Listeners +function setupEventListeners() { + seekbarCanvas.addEventListener('mousedown', (e) => { + isDragging = true; + const scale = getCurrentScale(); + const rect = seekbarCanvas.getBoundingClientRect(); + // Adjust position calculation for scale + const position = Math.max(0, Math.min((e.clientX - rect.left) / scale, seekbarCanvas.width)); + handleSeek(position); + }); + + seekbarCanvas.addEventListener('mousemove', (e) => { + if (isDragging) { + const scale = getCurrentScale(); + const rect = seekbarCanvas.getBoundingClientRect(); + // Adjust position calculation for scale + const position = Math.max(0, Math.min((e.clientX - rect.left) / scale, seekbarCanvas.width)); + handleSeek(position); + } + }); + + seekbarCanvas.addEventListener('mouseup', () => isDragging = false); + seekbarCanvas.addEventListener('mouseleave', () => isDragging = false); + document.addEventListener('mouseup', () => isDragging = false); + + // Rest of the event listeners remain the same... + elements.playButton.addEventListener('click', () => { + if (isLoading) return; + if (isPlaying) { + stopAudio(); + pauseTime = audioContext.currentTime - startTime; + } else { + playAudio(pauseTime); + } + savePlaybackState(); + }); + + elements.prevButton.addEventListener('click', () => loadNewSong(isPlaying, 'previous')); + elements.nextButton.addEventListener('click', () => loadNewSong(isPlaying, 'next')); + + elements.songCover.addEventListener('error', () => { + elements.songCover.src = songStore.getArtwork(currentSong.id); + savePlaybackState(); + }); + + document.addEventListener('visibilitychange', savePlaybackState); + window.addEventListener('beforeunload', savePlaybackState); +} + +function handleSeek(position) { + if (!audioBuffer) return; + const seekTime = (position / seekbarCanvas.width) * audioBuffer.duration; + seekAudio(seekTime); +} + +function updateTimeDisplay() { + if (!audioBuffer) return; + const currentTime = isPlaying ? audioContext.currentTime - startTime : pauseTime; + elements.timeElapsed.textContent = formatTime(currentTime); + elements.timeTotal.textContent = formatTime(audioBuffer.duration); +} + +// Main update loop +function update() { + if (audioBuffer && isPlaying) { + const currentTime = audioContext.currentTime - startTime; + if (currentTime >= audioBuffer.duration) { + loadNewSong(true, 'next'); + return; + } + } + + updateTimeDisplay(); + drawSeekbar(); + requestAnimationFrame(update); +} + +// Initialization +async function init() { + await initAudio(); + setupEventListeners(); + + try { + const savedState = localStorage.getItem('playbackState'); + if (savedState) { + const state = JSON.parse(savedState); + currentSong = { + id: state.songId, + title: state.songTitle, + artist: state.songArtist, + album: state.songAlbum, + artwork: state.artwork + }; + + updateSongInfo(); + await fetchAudio(`/stream/song/${state.songId}`); + pauseTime = state.timeStamp || 0; + + if (state.isPlaying) { + setTimeout(() => playAudio(pauseTime), 100); + } else { + drawSeekbar(); + updateTimeDisplay(); + } + } else { + await loadNewSong(false); + } + } catch (error) { + console.error('Error restoring state:', error); + await loadNewSong(false); + } + + setInterval(savePlaybackState, 500); + update(); +} + +init(); + diff --git a/static/js/shared/resolutionScaling.js b/static/js/shared/resolutionScaling.js new file mode 100644 index 00000000..f11bc9a8 --- /dev/null +++ b/static/js/shared/resolutionScaling.js @@ -0,0 +1,136 @@ +(function () { + // Core configuration constants + const SITE_BASE_WIDTH = 1000; + const MIN_SCALE_WIDTH = 1600; // Threshold for starting scale adjustments + const HI_RES_WIDTH = 1920; // Threshold for forcing maximum resolution + const MAX_DENSITY = 3; // Maximum supported image density + + // Calculate appropriate image density based on screen size and device + function getTargetDensity() { + const screenWidth = window.innerWidth; + const devicePixelRatio = window.devicePixelRatio || 1; + + if (screenWidth < MIN_SCALE_WIDTH) return 1; + if (screenWidth >= HI_RES_WIDTH) return MAX_DENSITY; + + const scaleRatio = screenWidth / MIN_SCALE_WIDTH; + const effectiveScale = scaleRatio * devicePixelRatio; + + return effectiveScale > 2 ? 2 : 1; + } + + // Update single image to appropriate resolution + function forceHighResImage(img) { + const srcset = img.getAttribute('srcset'); + if (!srcset) return; + + const sources = srcset.split(',').map(src => { + const [url, size] = src.trim().split(' '); + return { + url: url, + density: parseFloat(size.replace('x', '')) || 1 + }; + }).sort((a, b) => b.density - a.density); + + const targetDensity = getTargetDensity(); + const targetSource = sources.reduce((prev, curr) => { + if (curr.density <= targetDensity && curr.density > prev.density) { + return curr; + } + return prev; + }, { density: 0 }); + + if (img.src !== targetSource.url) { + const newImg = document.createElement('img'); + Array.from(img.attributes).forEach(attr => { + if (attr.name !== 'src' && attr.name !== 'srcset') { + newImg.setAttribute(attr.name, attr.value); + } + }); + newImg.src = targetSource.url; + img.parentNode.replaceChild(newImg, img); + } + } + + // Update all images on the page + function updateAllImages() { + document.querySelectorAll('img[srcset]').forEach(forceHighResImage); + } + + // Calculate and apply scaling based on screen width + function calculateScale() { + const screenWidth = window.innerWidth; + const wrapper = document.getElementById('body-wrapper'); + if (!wrapper) return; + + if (screenWidth <= MIN_SCALE_WIDTH) { + wrapper.style.transform = 'scale(1)'; + wrapper.style.transformOrigin = 'left top'; + wrapper.style.left = '50%'; + wrapper.style.marginLeft = `-${SITE_BASE_WIDTH / 2}px`; + return; + } + + const scaleRatio = screenWidth / MIN_SCALE_WIDTH; + const scaledWidth = SITE_BASE_WIDTH * scaleRatio; + const leftPosition = (screenWidth - scaledWidth) / 2; + + wrapper.style.transform = `scale(${scaleRatio})`; + wrapper.style.transformOrigin = 'left top'; + wrapper.style.left = `${leftPosition}px`; + wrapper.style.position = 'absolute'; + wrapper.style.marginLeft = '0'; + } + + // Initialize wrapper positioning and initial calculations + function init() { + const wrapper = document.getElementById('body-wrapper'); + if (wrapper) { + wrapper.style.position = 'absolute'; + wrapper.style.width = `${SITE_BASE_WIDTH}px`; + wrapper.style.top = '0'; + } + + calculateScale(); + updateAllImages(); + } + + // Handle dynamically added images + const observer = new MutationObserver((mutations) => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.nodeType === 1) { + if (node.tagName === 'IMG' && node.hasAttribute('srcset')) { + forceHighResImage(node); + } + node.querySelectorAll('img[srcset]').forEach(forceHighResImage); + } + }); + }); + }); + + // Debounced resize handler + let resizeTimeout; + function handleResize() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + calculateScale(); + updateAllImages(); + }, 100); + } + + // Event listeners and initialization + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + init(); + observer.observe(document.body, { childList: true, subtree: true }); + }); + } else { + init(); + observer.observe(document.body, { childList: true, subtree: true }); + } + + window.addEventListener('load', init); + window.addEventListener('resize', handleResize); + window.addEventListener('orientationchange', handleResize); +})();
\ No newline at end of file diff --git a/static/js/youdontdare.js b/static/js/youdontdare.js index 8e0c8aa4..c2a27ac1 100644 --- a/static/js/youdontdare.js +++ b/static/js/youdontdare.js @@ -73,7 +73,7 @@ function applyStyles(x, y, container) { container.style.position = 'absolute' container.style.top = `${y}px` container.style.left = `${x}px` - container.style.fontSize = '8px' + container.style.fontSize = '11px' container.style.userSelect = 'none' container.style.transition = 'opacity 0.5s ease-in-out' container.style.textShadow = '0 0 2px #e014df, 0 0 4px #e014df, 0 0 6px #e014df, 0 0 10px #ff00ff, 0 0 14px #ff00ff' diff --git a/templates/shared/base.html b/templates/shared/base.html index 8d04b64b..32e495c1 100644 --- a/templates/shared/base.html +++ b/templates/shared/base.html @@ -22,24 +22,8 @@ <link type="text/css" rel="stylesheet" href="{% static 'css/shared/core.css' %}" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> - {% if request.LANGUAGE_CODE == 'ja' %} - <link href="https://fonts.googleapis.com/css2?family=Yuji+Boku&display=swap" rel="stylesheet"> - <style> - * { - font-family: "Yuji Boku", sans-serif; - font-size: 11px; - letter-spacing: 0cap; - } - </style> - {% else %} <link href="https://fonts.googleapis.com/css2?family=Mali:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet" /> - <style> - * { - font-family: "Mali", sans-serif; - font-size: 11px; - } - </style> - {% endif %} + <script src="{% static 'js/shared/resolutionScaling.js' %}"></script> {% block head %} {% endblock %} diff --git a/templates/shared/header.html b/templates/shared/header.html index 361a7a70..f30b7790 100644 --- a/templates/shared/header.html +++ b/templates/shared/header.html @@ -2,16 +2,16 @@ <div style="position: relative; padding: 20px 0;"> {% if request.LANGUAGE_CODE == 'ja' %} {% with banner_image='images/core/shared/title_banner_ja' %} - <img draggable="false" src="{% static banner_image|add:'@2x.png' %}" srcset=" - {% static banner_image|add:'@2x.png' %} 1x, + <img draggable="false" src="{% static banner_image|add:'.png' %}" srcset=" + {% static banner_image|add:'.png' %} 1x, {% static banner_image|add:'@2x.png' %} 2x, {% static banner_image|add:'@3x.png' %} 3x" alt="Shifoo Title Banner" class="title-banner" width="1000" height="333" loading="lazy" /> {% endwith %} {% else %} {% with banner_image='images/core/shared/title_banner' %} - <img draggable="false" src="{% static banner_image|add:'@2x.png' %}" srcset=" - {% static banner_image|add:'@2x.png' %} 1x, + <img draggable="false" src="{% static banner_image|add:'.png' %}" srcset=" + {% static banner_image|add:'.png' %} 1x, {% static banner_image|add:'@2x.png' %} 2x, {% static banner_image|add:'@3x.png' %} 3x" alt="Shifoo Title Banner" class="title-banner" width="1000" height="333" loading="lazy" /> diff --git a/templates/shared/right_sidebar.html b/templates/shared/right_sidebar.html index fb2ede6d..653e9f66 100644 --- a/templates/shared/right_sidebar.html +++ b/templates/shared/right_sidebar.html @@ -1,4 +1,25 @@ -<div style="margin: 18px 0 12px 0;"> - <div class="glitch" data-text="KAWAII BEATS">KAWAII BEATS</div> - <div class="glow">KAWAII BEATS</div> +{% load static %} + +{% block head %} +<link rel="stylesheet" href="{% static 'css/shared/kawaiibeats.css' %}"> +{% endblock head %} + +<div class="kawaiibeats"> + <p id="song-title">Loading...</p> + <p id="song-artist-album">Loading...</p> + <img onerror="this.src=getRandomArtwork()" id="song-cover" alt="Song Cover"> + <canvas id="song-visualizer" width="140" height="30"></canvas> + <div id="song-time"> + <span id="song-time-elapsed">0:00</span> + <span id="song-time-total">0:00</span> + </div> + <div id="song-controls"> + <button id="song-prev"><</button> + <button id="song-play">►</button> + <button id="song-next">></button> + </div> </div> + +{% block scripts %} +<script src="{% static 'js/musicPlayer.js' %}"></script> +{% endblock scripts %}
\ No newline at end of file diff --git a/thatcomputerscientist/urls.py b/thatcomputerscientist/urls.py index c2d442a3..d13d6674 100644 --- a/thatcomputerscientist/urls.py +++ b/thatcomputerscientist/urls.py @@ -40,6 +40,7 @@ handler404 = "thatcomputerscientist.error_handler.custom_404" urlpatterns = [ path("", include("apps.core.urls", namespace="core")), + path("stream/", include("apps.stream.urls", namespace="stream")), # path('users', include('users.urls', namespace='users')), # path('blog-admin', include('blog_admin.urls', namespace='blog-admin')), # path('repositories', include(('dev_status.urls', 'dev_status'), namespace='dev_status')), |
