From c0bd4e25dc6d1dbbe76cb88c36d74b621dbdb58c Mon Sep 17 00:00:00 2001 From: Bobby Date: Sun, 3 Nov 2024 19:00:29 -0500 Subject: =?UTF-8?q?stuff=20still=20all=20over=20the=20place;=20added=20a?= =?UTF-8?q?=20music=20player=20and=20a=20resolution=20based=20scaler=20?= =?UTF-8?q?=E2=80=94=20now=20in=20a=20brainfucked=20stated=20-=20going=20t?= =?UTF-8?q?o=20sleep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/en/login-area.css | 31 +- static/css/ja/login-area.css | 31 +- static/css/shared/core.css | 372 ++------------------ static/css/shared/kawaiibeats.css | 106 ++++++ static/css/shared/login-area.css | 96 +++-- static/css/sidebar.css | 10 +- static/extra/player_skin.wsz | Bin 210107 -> 0 bytes static/images/kawaiibeats/kawaii_beats.gif | Bin 0 -> 893194 bytes static/js/musicPlayer.js | 540 +++++++++++++++++++++++++++++ static/js/shared/resolutionScaling.js | 136 ++++++++ static/js/youdontdare.js | 2 +- 11 files changed, 894 insertions(+), 430 deletions(-) create mode 100644 static/css/shared/kawaiibeats.css delete mode 100644 static/extra/player_skin.wsz create mode 100644 static/images/kawaiibeats/kawaii_beats.gif create mode 100644 static/js/musicPlayer.js create mode 100644 static/js/shared/resolutionScaling.js (limited to 'static') 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/login-area@2x.png") 1x, - url("../../images/core/sidebar/login-area@2x.png") 2x, - url("../../images/core/sidebar/login-area@3x.png") 3x - ); + background-image: -webkit-image-set(url("../../images/core/sidebar/login-area@2x.png") 1x, + url("../../images/core/sidebar/login-area@2x.png") 2x, + url("../../images/core/sidebar/login-area@3x.png") 3x); + background-image: image-set(url("../../images/core/sidebar/login-area@2x.png") 1x, + url("../../images/core/sidebar/login-area@2x.png") 2x, + url("../../images/core/sidebar/login-area@3x.png") 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/login-area_ja@2x.png") 1x, - url("../../images/core/sidebar/login-area_ja@2x.png") 2x, - url("../../images/core/sidebar/login-area_ja@3x.png") 3x - ); + background-image: -webkit-image-set(url("../../images/core/sidebar/login-area_ja@2x.png") 1x, + url("../../images/core/sidebar/login-area_ja@2x.png") 2x, + url("../../images/core/sidebar/login-area_ja@3x.png") 3x); + background-image: image-set(url("../../images/core/sidebar/login-area_ja@2x.png") 1x, + url("../../images/core/sidebar/login-area_ja@2x.png") 2x, + url("../../images/core/sidebar/login-area_ja@3x.png") 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 Binary files a/static/extra/player_skin.wsz and /dev/null differ diff --git a/static/images/kawaiibeats/kawaii_beats.gif b/static/images/kawaiibeats/kawaii_beats.gif new file mode 100644 index 00000000..8ac5e3e6 Binary files /dev/null and b/static/images/kawaiibeats/kawaii_beats.gif 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 ? "❚❚" : "►"; + 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' -- cgit v1.2.3