aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-11-03 19:00:29 -0500
committerBobby <[email protected]>2024-11-03 19:00:29 -0500
commitc0bd4e25dc6d1dbbe76cb88c36d74b621dbdb58c (patch)
treebf6f3e599df7b0e9053888b7f7dcbe9de6ac5aaf
parent609cc6264e8a9dfc69666b40f7452300ad22caef (diff)
downloadthatcomputerscientist-c0bd4e25dc6d1dbbe76cb88c36d74b621dbdb58c.tar.xz
thatcomputerscientist-c0bd4e25dc6d1dbbe76cb88c36d74b621dbdb58c.zip
stuff still all over the place; added a music player and a resolution based scaler — now in a brainfucked stated - going to sleep
-rw-r--r--.hintrc15
-rw-r--r--apps/stream/__init__.py0
-rw-r--r--apps/stream/admin.py3
-rw-r--r--apps/stream/apps.py6
-rw-r--r--apps/stream/migrations/__init__.py0
-rw-r--r--apps/stream/models.py3
-rw-r--r--apps/stream/tests.py3
-rw-r--r--apps/stream/urls.py9
-rw-r--r--apps/stream/views.py101
-rw-r--r--static/css/en/login-area.css31
-rw-r--r--static/css/ja/login-area.css31
-rw-r--r--static/css/shared/core.css372
-rw-r--r--static/css/shared/kawaiibeats.css106
-rw-r--r--static/css/shared/login-area.css96
-rw-r--r--static/css/sidebar.css10
-rw-r--r--static/extra/player_skin.wszbin210107 -> 0 bytes
-rw-r--r--static/images/kawaiibeats/kawaii_beats.gifbin0 -> 893194 bytes
-rw-r--r--static/js/musicPlayer.js540
-rw-r--r--static/js/shared/resolutionScaling.js136
-rw-r--r--static/js/youdontdare.js2
-rw-r--r--templates/shared/base.html18
-rw-r--r--templates/shared/header.html8
-rw-r--r--templates/shared/right_sidebar.html27
-rw-r--r--thatcomputerscientist/urls.py1
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
deleted file mode 100644
index 23a10c59..00000000
--- a/static/extra/player_skin.wsz
+++ /dev/null
Binary files differ
diff --git a/static/images/kawaiibeats/kawaii_beats.gif b/static/images/kawaiibeats/kawaii_beats.gif
new file mode 100644
index 00000000..8ac5e3e6
--- /dev/null
+++ b/static/images/kawaiibeats/kawaii_beats.gif
Binary files differ
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 ? "&#10074;&#10074;" : "&#9658;";
+ 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">&lt;</button>
+ <button id="song-play">&#9658;</button>
+ <button id="song-next">&gt;</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')),