diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 4113613..394e2ae 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -42,13 +42,13 @@ --posts-meta-bg: #000000b8; --x2154: #215483; --bg-canvas-opacity: 0.81; - --bg-canvas-blur: 151px; + --bg-canvas-blur: 30px; /* runtime value set by applyBackgroundOpacitySettings() */ --bg-canvas-zoom: 1.38; --bg-canvas-color: rgba(0, 0, 0, 0.09); --bg-canvas-color-opacity: 0.09; - --onara-bg-opacity: 0.4; + --onara-bg-opacity: 0.6; --onara-backdrop-blur: 5px; - --onara-grid-dim-opacity: 0.38; + --onara-grid-dim-opacity: 1.0; } html:not([theme]), html[theme=""] { @@ -1844,7 +1844,7 @@ html[theme="f0ck95"] #next { html[theme="4d"] { --accent: #f2ef0b; - --accent-rgb: 31, 178, 176; + --accent-rgb: 242, 239, 11; --bg: #242424; --black: #000; --white: #fff; @@ -3215,7 +3215,7 @@ body.sidebar-right-hidden #sidebar-drag-zone { display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, rgba(var(--accent-rgb, 153, 255, 0), 0.12), rgba(0, 0, 0, 0.45)), var(--nav-bg, #1e1e1e); + background: linear-gradient(135deg, rgba(var(--accent-rgb, 128, 128, 128), 0.12), rgba(0, 0, 0, 0.45)), var(--nav-bg, #1e1e1e); color: var(--accent); font-size: 2em; } @@ -3233,6 +3233,23 @@ body.sidebar-right-hidden #sidebar-drag-zone { background: #000000; } +.audio-cover-backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 0; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + border-radius: inherit; + transition: opacity 0.25s ease, filter 0.25s ease; + will-change: opacity, transform, filter; + overflow: hidden; +} + .audio-reactive-bg { position: absolute; top: 0; @@ -3242,8 +3259,9 @@ body.sidebar-right-hidden #sidebar-drag-zone { pointer-events: none; z-index: 0; opacity: 0; - transition: opacity 0.3s ease; - will-change: background, opacity; + transition: opacity 0.35s ease; + will-change: opacity, transform; + overflow: hidden; } .audio-cover-circle { @@ -3256,8 +3274,8 @@ body.sidebar-right-hidden #sidebar-drag-zone { transform: translate(-50%, -50%); width: 125px; height: 125px; - max-width: calc(100% - 40px); - max-height: calc(100% - 40px); + max-width: none; + max-height: none; aspect-ratio: 1 / 1; border-radius: 50%; background-size: cover; @@ -3270,29 +3288,13 @@ body.sidebar-right-hidden #sidebar-drag-zone { outline: none !important; overflow: visible; pointer-events: none; - z-index: 1; + z-index: 2; will-change: transform, box-shadow, border-color; } -/* Motion trail ghost — faint blurred echo of the eye that smears on fast movement */ +/* Motion trail ghost — disabled so cover art is not projected around the eye */ .eye-trail-ghost { - position: absolute; - top: 50%; - left: 50%; - width: clamp(300px, 72vmin, 700px); - height: clamp(300px, 72vmin, 700px); - max-width: calc(100% - 60px); - max-height: calc(100% - 80px); - border-radius: 50%; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - background-color: transparent; - pointer-events: none; - z-index: 0; - opacity: 0; - will-change: transform, opacity, filter; - mix-blend-mode: screen; + display: none !important; } .audio-eye-inner-canvas { @@ -3345,6 +3347,21 @@ body.sidebar-right-hidden #sidebar-drag-zone { display: none !important; } +.thumb > .sidebar-media-placeholder .audio-cover-backdrop, +.album-thumb-item > .sidebar-media-placeholder .audio-cover-backdrop, +.item-preview .sidebar-media-placeholder .audio-cover-backdrop, +.sidebar-activity-item .sidebar-media-placeholder .audio-cover-backdrop, +.recent-comment-media .sidebar-media-placeholder .audio-cover-backdrop, +.posts .sidebar-media-placeholder .audio-cover-backdrop, +.thumb > .sidebar-media-placeholder .audio-reactive-bg, +.album-thumb-item > .sidebar-media-placeholder .audio-reactive-bg, +.item-preview .sidebar-media-placeholder .audio-reactive-bg, +.sidebar-activity-item .sidebar-media-placeholder .audio-reactive-bg, +.recent-comment-media .sidebar-media-placeholder .audio-reactive-bg, +.posts .sidebar-media-placeholder .audio-reactive-bg { + display: none !important; +} + .thumb > .sidebar-media-placeholder > i, .album-thumb-item > .sidebar-media-placeholder > i, .item-preview .sidebar-media-placeholder > i { @@ -6098,6 +6115,7 @@ html[theme="f0ck95"] .admin-search button { /* f0ck95dARK */ html[theme='f0ck95d'] { --accent: #fff; + --accent-rgb: 255, 255, 255; --bg: #0e0f0f; --black: #000; --white: #fff; @@ -6524,6 +6542,9 @@ div.posts>a:not(.notif-item) { background-repeat: no-repeat; background-size: cover; background-position: center; + transition: box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1), + border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1), + outline-color 0.25s cubic-bezier(0.16, 1, 0.3, 1); } div.posts>a:not(.notif-item)::before { @@ -7119,11 +7140,7 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid { .content.fade-in-zoom, ._204863.fade-in-zoom, .item_title.fade-in-zoom, -.item-title.fade-in-zoom, -#onara-modal .content.fade-in-zoom, -#onara-modal ._204863.fade-in-zoom, -#onara-modal .item_title.fade-in-zoom, -#onara-modal .item-title.fade-in-zoom { +.item-title.fade-in-zoom { animation: contentFadeInZoom 0.35s cubic-bezier(0.16, 1, 0.3, 1) both !important; will-change: transform, opacity; } @@ -7131,11 +7148,7 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid { .content.fade-out-zoom, ._204863.fade-out-zoom, .item_title.fade-out-zoom, -.item-title.fade-out-zoom, -#onara-modal .content.fade-out-zoom, -#onara-modal ._204863.fade-out-zoom, -#onara-modal .item_title.fade-out-zoom, -#onara-modal .item-title.fade-out-zoom { +.item-title.fade-out-zoom { animation: contentFadeOutZoom 0.18s cubic-bezier(0.3, 0, 0.8, 1) forwards !important; pointer-events: none; will-change: transform, opacity; @@ -10486,10 +10499,11 @@ button#togglebg { #bg.fader-out, .fader-out { opacity: 0 !important; + transition: opacity 0.22s cubic-bezier(0.25, 0, 0.4, 1) !important; } .fast-fade { - transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; + transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; } .grid-transition { @@ -10780,7 +10794,6 @@ input#s_avatar { .tag-name { color: var(--white, #fff); - font-weight: bold; font-size: 1.1em; font-family: var(--font, monospace); word-break: break-word; @@ -20818,19 +20831,22 @@ a.lazy-thumb.filtered-upload-ghost:hover::after { ONARA ALTERNATIVE FLAVOR: Fullscreen See-Through Modal Wrapper ========================================================================== */ -/* Prevent scrolling on background document when Onara modal is active */ -body.onara-modal-open { +/* Prevent scrolling on background document when Onara modal is active or closing */ +body.onara-modal-open, +body.onara-modal-closing { overflow: hidden !important; } /* Override html's scrollbar-gutter: stable so the page fills full viewport width */ -html:has(body.onara-modal-open) { +html:has(body.onara-modal-open), +html:has(body.onara-modal-closing) { overflow: hidden !important; scrollbar-gutter: auto !important; } /* Background main page elements and containers are transparent so #bg canvas and grid shine through */ -body.onara-modal-open .pagewrapper { +body.onara-modal-open .pagewrapper, +body.onara-modal-closing .pagewrapper { pointer-events: none !important; user-select: none !important; margin-top: var(--navbar-h, 50px) !important; @@ -20838,21 +20854,32 @@ body.onara-modal-open .pagewrapper { } body.onara-modal-open .index-container, -body.onara-modal-open div.posts { +body.onara-modal-closing .index-container, +body.onara-modal-open div.posts, +body.onara-modal-closing div.posts { background: transparent !important; } -body.onara-modal-open .pagewrapper > *:not(.index-layout-wrapper):not(#main), -body.onara-modal-open .index-container > *:not(.posts), -body.onara-modal-open #footbar, -body.onara-modal-open .pagination-container-fluid { +body.onara-modal-open:not(.onara-modal-closing) .pagewrapper > *:not(.index-layout-wrapper):not(#main), +body.onara-modal-open:not(.onara-modal-closing) .index-container > *:not(.posts), +body.onara-modal-open:not(.onara-modal-closing) #footbar, +body.onara-modal-open:not(.onara-modal-closing) .pagination-container-fluid { opacity: var(--onara-grid-dim-opacity, 1) !important; filter: none !important; transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important; } +body.onara-modal-closing .pagewrapper > *:not(.index-layout-wrapper):not(#main), +body.onara-modal-closing .index-container > *:not(.posts), +body.onara-modal-closing #footbar, +body.onara-modal-closing .pagination-container-fluid { + opacity: 1 !important; + filter: none !important; + transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; +} + /* All non-active background thumbnails shine through with the configured grid opacity */ -body.onara-modal-open .posts > a.thumb:not(.onara-active) { +body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb:not(.onara-active) { opacity: var(--onara-grid-dim-opacity, 1) !important; filter: none !important; box-shadow: none !important; @@ -20860,36 +20887,71 @@ body.onara-modal-open .posts > a.thumb:not(.onara-active) { transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important; } -body.onara-modal-open .posts > a.thumb:not(.onara-active)::after { +body.onara-modal-closing .posts > a.thumb:not(.onara-active) { + opacity: 1 !important; + filter: none !important; + box-shadow: none !important; + outline: none !important; + transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; +} + +body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb:not(.onara-active)::after { + visibility: hidden !important; + opacity: 0 !important; +} + +body.onara-modal-closing .posts > a.thumb:not(.onara-active)::after { visibility: hidden !important; opacity: 0 !important; } /* ONLY the single currently active thumbnail in the background shines through with prominent glow */ -body.onara-modal-open .posts > a.thumb.onara-active { +body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active { opacity: 1 !important; filter: none !important; box-shadow: 0 0 0 3px var(--accent, #9f0), 0 0 20px var(--accent, #9f0), 0 0 35px rgba(255, 255, 255, 0.9) !important; - z-index: 50 !important; + z-index: 52 !important; position: relative !important; + transition: box-shadow 0.28s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease !important; } -body.onara-modal-open .posts > a.thumb.onara-active::after { +body.onara-modal-open:not(.onara-modal-closing) .posts > a.thumb.onara-active::after { visibility: visible !important; opacity: 1 !important; + transition: opacity 0.25s ease !important; } -body.onara-modal-open .posts > a.thumb.loaded { +/* Smooth fade-out of active thumbnail border / glow when Onara modal closes */ +body.onara-modal-closing .posts > a.thumb.onara-active, +body.onara-modal-closing .posts > a.thumb.loaded.onara-active { + opacity: 1 !important; + filter: none !important; + box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent, 0 0 0 0 transparent !important; + outline-color: transparent !important; + border-color: transparent !important; + z-index: 50 !important; + position: relative !important; + transition: box-shadow 0.2s cubic-bezier(0.25, 0, 0.4, 1), + outline-color 0.2s cubic-bezier(0.25, 0, 0.4, 1), + border-color 0.2s cubic-bezier(0.25, 0, 0.4, 1), + opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; +} + +body.onara-modal-closing .posts > a.thumb.onara-active::after { + opacity: 0 !important; + transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; +} + +body.onara-modal-open .posts > a.thumb.loaded, +body.onara-modal-closing .posts > a.thumb.loaded { opacity: 1 !important; filter: none !important; z-index: 50 !important; position: relative !important; } -body.onara-modal-open #bg, -body.onara-modal-open #bg.fader-in, -body.onara-modal-open #bg.fader-out, -body.onara-modal-open #bg.fast-fade { +body.onara-modal-open:not(.onara-modal-closing) #bg, +body.onara-modal-open:not(.onara-modal-closing) #bg.fader-in { z-index: 51 !important; cursor: pointer !important; transition: width 0.3s ease-in-out !important; @@ -20897,6 +20959,18 @@ body.onara-modal-open #bg.fast-fade { opacity: var(--bg-canvas-opacity, 1.0) !important; } +body.onara-modal-closing #bg, +body.onara-modal-closing #bg.fader-in, +body.onara-modal-closing #bg.fader-out, +body.onara-modal-closing #bg.fast-fade, +body.onara-modal-open #bg.fader-out { + z-index: 51 !important; + cursor: default !important; + pointer-events: none !important; + opacity: 0 !important; + transition: opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1) !important; +} + /* Navbar and right sidebar stay fully visible, sharp, and interactive */ body.onara-modal-open nav.navbar { opacity: 1 !important; @@ -20962,33 +21036,34 @@ body.onara-modal-open .admin-bar.open { overscroll-behavior-x: contain !important; box-sizing: border-box !important; opacity: 0; - transform: scale(0.99); - transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.28s cubic-bezier(0.16, 1, 0.3, 1), - right 0.3s ease-in-out !important; cursor: pointer; + will-change: opacity, backdrop-filter; + transition: right 0.3s ease-in-out !important; } .onara-modal-wrapper::-webkit-scrollbar { display: none; } -body.onara-modal-open .onara-modal-wrapper, -.onara-modal-wrapper.onara-fade-in { +/* Static server-load fallback when page is directly visited on an onara item URL */ +body.onara-modal-open .onara-modal-wrapper:not(.onara-fade-out):not(.onara-fade-in) { display: flex !important; opacity: 1 !important; - transform: scale(1) !important; pointer-events: auto !important; } +/* Silky animated opening */ +.onara-modal-wrapper.onara-fade-in { + display: flex !important; + animation: onaraModalFadeIn 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards !important; + pointer-events: auto !important; +} + +/* Silky animated closing */ .onara-modal-wrapper.onara-fade-out { display: flex !important; - opacity: 0 !important; - transform: scale(0.99) !important; + animation: onaraModalFadeOut 0.22s cubic-bezier(0.25, 0, 0.4, 1) forwards !important; pointer-events: none !important; - transition: opacity 0.24s cubic-bezier(0.3, 0, 0.8, 1), - transform 0.24s cubic-bezier(0.3, 0, 0.8, 1), - right 0.3s ease-in-out !important; } /* Sidebar hidden: modal expands to right edge */ @@ -21013,25 +21088,33 @@ body.sidebar-right-hidden .onara-modal-wrapper { } } -@keyframes onaraFadeIn { - from { +@keyframes onaraModalFadeIn { + 0% { opacity: 0; - transform: scale(0.99); + backdrop-filter: blur(0px); + -webkit-backdrop-filter: blur(0px); + background: rgba(0, 0, 0, 0); } - to { + 100% { opacity: 1; - transform: scale(1); + backdrop-filter: blur(var(--onara-backdrop-blur, 5px)) saturate(130%); + -webkit-backdrop-filter: blur(var(--onara-backdrop-blur, 5px)) saturate(130%); + background: rgba(0, 0, 0, var(--onara-bg-opacity, 0.6)); } } -@keyframes onaraFadeOut { - from { +@keyframes onaraModalFadeOut { + 0% { opacity: 1; - transform: scale(1); + backdrop-filter: blur(var(--onara-backdrop-blur, 5px)) saturate(130%); + -webkit-backdrop-filter: blur(var(--onara-backdrop-blur, 5px)) saturate(130%); + background: rgba(0, 0, 0, var(--onara-bg-opacity, 0.6)); } - to { + 100% { opacity: 0; - transform: scale(0.99); + backdrop-filter: blur(0px); + -webkit-backdrop-filter: blur(0px); + background: rgba(0, 0, 0, 0); } } @@ -21059,6 +21142,78 @@ body.sidebar-right-hidden .onara-modal-wrapper { padding: 20px 0 80px 0 !important; box-sizing: border-box !important; cursor: pointer; + will-change: transform, opacity; +} + +/* Smooth modal content elevation during entrance & exit */ +.onara-modal-wrapper.onara-fade-in .onara-modal-content { + animation: onaraContentScaleIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards !important; +} + +.onara-modal-wrapper.onara-fade-out .onara-modal-content { + animation: onaraContentScaleOut 0.2s cubic-bezier(0.25, 0, 0.4, 1) forwards !important; +} + +@keyframes onaraContentScaleIn { + 0% { + opacity: 0; + transform: translateY(16px) scale(0.985); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes onaraContentScaleOut { + 0% { + opacity: 1; + transform: translateY(0) scale(1); + } + 100% { + opacity: 0; + transform: translateY(10px) scale(0.985); + } +} + +/* Keep .blahlol completely static on new item load — never fade in or animate */ +.blahlol, +.blahlol *, +#onara-item-mount .blahlol, +#onara-item-mount .blahlol * { + animation: none !important; + transition: none !important; +} + +.blahlol, +#onara-item-mount .blahlol { + opacity: 1 !important; + transform: none !important; + visibility: visible !important; +} + +#onara-item-mount .item-layout-container { + animation: none !important; + transform: none !important; + opacity: 1 !important; +} + +/* Modern loading shimmer spinner inside mount */ +.onara-mount-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: 360px; + width: 100%; + color: var(--accent, #99ff00); + font-size: 2rem; + opacity: 0; + animation: onaraMountSpinnerFade 0.25s 0.12s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes onaraMountSpinnerFade { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 0.75; transform: scale(1); } } #onara-item-mount { @@ -22036,6 +22191,11 @@ div#flash, bottom: 58px; } +/* YouTube controls occupy ~56px at the bottom — lift strip above them */ +.album-gallery-container.is-youtube-active .album-thumbnails-strip { + bottom: 62px; +} + .album-gallery-container.is-video-active .image-modal-btn, .album-gallery-container.is-audio-active .image-modal-btn { display: none !important; @@ -22046,6 +22206,9 @@ div#flash, .album-gallery-container.is-audio-active .album-thumbnails-strip { bottom: 48px; } + .album-gallery-container.is-youtube-active .album-thumbnails-strip { + bottom: 52px; + } } .album-thumb-item { diff --git a/public/s/js/admin.js b/public/s/js/admin.js index 618d5c2..3ab8972 100644 --- a/public/s/js/admin.js +++ b/public/s/js/admin.js @@ -151,7 +151,7 @@ if (activePostId) untaggedSpan.dataset.itemId = activePostId; const lbl = document.createElement("span"); lbl.className = "rating-label"; - lbl.textContent = "untagged"; + lbl.textContent = "unrated"; untaggedSpan.appendChild(lbl); inner.insertAdjacentElement("afterbegin", untaggedSpan); } diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index fa08223..b66acfb 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -118,6 +118,9 @@ window.cancelAnimFrame = (function () { window._randomPool = null; window._randomPoolContext = null; window._randomPoolCursor = 0; + window._randomPoolNsfpVersion = null; + window._randomPoolFetchedAt = 0; + const RANDOM_POOL_TTL_MS = 5 * 60 * 1000; // 5 minutes // Fisher-Yates (Durstenfeld) in-place shuffle const _shuffleArray = (arr) => { @@ -188,17 +191,27 @@ window.cancelAnimFrame = (function () { if (isStrict) params.append('strict', '1'); const contextKey = params.toString() + '|mode=' + (window.activeMode ?? 0); - if (!forceRefresh && window._randomPoolContext === contextKey && window._randomPool) return; + const now = Date.now(); + const isStale = (now - window._randomPoolFetchedAt) > RANDOM_POOL_TTL_MS; + if (!forceRefresh && window._randomPoolContext === contextKey && window._randomPool && !isStale) return; const url = '/api/v2/random-pool' + ([...params].length > 0 ? '?' + params.toString() : ''); fetch(url, { credentials: 'include' }) .then(r => r.json()) .then(data => { if (data && Array.isArray(data.items)) { + // Detect nsfp list changes — if version differs, reset cursor to avoid serving stale items + const newNsfpVersion = data.nsfp_version ?? null; + const nsfpChanged = newNsfpVersion !== null && newNsfpVersion !== window._randomPoolNsfpVersion; + if (nsfpChanged) { + console.log(`[RANDOM-POOL] nsfp_version changed (${window._randomPoolNsfpVersion} → ${newNsfpVersion}), invalidating pool`); + } _shuffleArray(data.items); window._randomPool = { items: data.items, total: data.total, sampled: data.sampled }; window._randomPoolContext = contextKey; window._randomPoolCursor = 0; + window._randomPoolNsfpVersion = newNsfpVersion; + window._randomPoolFetchedAt = Date.now(); console.log(`[RANDOM-POOL] Cached ${data.items.length} items (shuffled) for context: ${contextKey}`); } }) @@ -209,6 +222,7 @@ window.cancelAnimFrame = (function () { document.addEventListener('f0ck:modeChanged', () => { window._randomPool = null; window._randomPoolContext = null; + window._randomPoolFetchedAt = 0; setTimeout(() => window.fetchRandomPool(true), 200); }); @@ -519,6 +533,7 @@ window.cancelAnimFrame = (function () { else coverCircle.insertAdjacentHTML('beforeend', ''); } } + coverCircle._origBgImage = `url('${coverUrl}')`; coverCircle.style.backgroundImage = `url('${coverUrl}')`; ph.classList.add('has-cover'); ph.style.display = ''; @@ -543,6 +558,7 @@ window.cancelAnimFrame = (function () { if (existingI) coverCircle.appendChild(existingI); else coverCircle.insertAdjacentHTML('beforeend', ''); } + coverCircle._origBgImage = null; coverCircle.style.backgroundImage = 'none'; coverCircle.style.backgroundColor = '#000000'; } @@ -1769,10 +1785,25 @@ window.cancelAnimFrame = (function () { // ── Onara Alternative Flavor: Fullscreen Modal Wrapper ───────────────────── const isOnaraActive = () => { const stored = (() => { try { return localStorage.getItem('f0ck_onara_enabled'); } catch { return null; } })(); - if (stored !== null) return stored === 'true'; + if (stored !== null) { + const active = stored === 'true'; + try { + const cookieVal = active ? '1' : '0'; + if (!document.cookie.includes(`f0ck_onara=${cookieVal}`)) { + document.cookie = `f0ck_onara=${cookieVal}; path=/; max-age=31536000; SameSite=Lax`; + } + } catch {} + return active; + } return !!(window.f0ckSession?.onara || window.onara); }; + const isCurrentlyInOnara = () => { + return document.body.classList.contains('onara-modal-open') || + (typeof isOnaraActive === 'function' && isOnaraActive() && !!document.querySelector('#onara-item-mount > *:not(.onara-mount-loading)')); + }; + window.isCurrentlyInOnara = isCurrentlyInOnara; + const getOrCreateOnaraModal = () => { let modal = document.getElementById('onara-modal'); if (!modal) { @@ -1800,17 +1831,29 @@ window.cancelAnimFrame = (function () { } modal._isClosing = false; const wasAlreadyOpen = document.body.classList.contains('onara-modal-open'); + document.body.classList.remove('onara-modal-closing'); document.body.classList.add('onara-modal-open'); if (typeof window.applyBackgroundOpacitySettings === 'function') { - window.applyBackgroundOpacitySettings(null, true); + window.applyBackgroundOpacitySettings(window.onaraTuning || null, true); } + window._bgModeAutoSwitch = true; + const onaraModeBtn = document.querySelector('#bg-mode-btn-onara'); + if (onaraModeBtn) onaraModeBtn.click(); + window._bgModeAutoSwitch = false; if (document.querySelector('.index-container') && window.scrollY !== 0) { window.scrollTo(0, 0); } + + // If mount is currently empty, show subtle loading indicator until content arrives + const mount = document.getElementById('onara-item-mount'); + if (mount && !mount.hasChildNodes()) { + mount.innerHTML = '
'; + } + modal.style.display = 'flex'; modal.setAttribute('aria-hidden', 'false'); modal.classList.remove('onara-fade-out'); - void modal.offsetWidth; // Force reflow so transition starts reliably + void modal.offsetWidth; // Force reflow so keyframe / transition starts reliably modal.classList.add('onara-fade-in'); const canvas = document.getElementById('bg'); @@ -1819,7 +1862,8 @@ window.cancelAnimFrame = (function () { canvas.classList.remove('fader-out', 'fast-fade'); canvas.classList.add('fader-in'); canvas.style.transition = 'none'; - canvas.style.opacity = 'var(--bg-canvas-opacity, 0.95)'; + // opacity driven by --bg-canvas-opacity CSS var (set by applyBackgroundOpacitySettings) + .fader-in + canvas.style.removeProperty('opacity'); } if (!wasAlreadyOpen && typeof window.paintImmediateBgThumb === 'function') { window.paintImmediateBgThumb(); @@ -1886,11 +1930,14 @@ window.cancelAnimFrame = (function () { const syncOnaraBackgroundGrid = async (itemid, url, knownPage = null, slug = null) => { if (!isOnaraActive()) return; - // Fast path: if target item is already in current background DOM, just highlight and ensure visibility + // Fast path: if target item is already in current background DOM (and not a temporary placeholder), just highlight and ensure visibility const existingThumb = updateOnaraActiveItem(itemid, url, false, slug); - if (existingThumb) { + if (existingThumb && !existingThumb.classList.contains('onara-synth-thumb')) { return; } + if (existingThumb && existingThumb.classList.contains('onara-synth-thumb')) { + existingThumb.remove(); + } const currentSeq = ++_onaraSyncSeq; const urlObj = new URL(url || window.location.href, window.location.origin); @@ -1920,6 +1967,12 @@ window.cancelAnimFrame = (function () { } else if (feedBasePath === '/favs') { pageParams.set('fav', 'true'); } + if (urlObj.searchParams.get('mime')) { + pageParams.set('mime', urlObj.searchParams.get('mime')); + } + if (urlObj.searchParams.get('tagger')) { + pageParams.set('tagger', urlObj.searchParams.get('tagger')); + } if (typeof window.activeMode !== 'undefined') { pageParams.set('mode', window.activeMode); } @@ -1962,6 +2015,7 @@ window.cancelAnimFrame = (function () { const oldPosts = document.querySelector('.posts'); if (newPosts && oldPosts) { + oldPosts.querySelectorAll('.onara-synth-thumb').forEach(el => el.remove()); oldPosts.innerHTML = newPosts.innerHTML; oldPosts.dataset.currentPage = targetPage; if (newPosts.dataset.hasMore) { @@ -1970,6 +2024,20 @@ window.cancelAnimFrame = (function () { if (typeof window.initLazyLoading === 'function') { window.initLazyLoading(); } + if (typeof window.initThumbnailHover === 'function') { + window.initThumbnailHover(); + } + } else if (newPosts && !oldPosts) { + const main = document.getElementById('main'); + if (main) { + const gridContent = doc.querySelector('#main') || doc.body; + if (gridContent) { + main.className = ''; + main.innerHTML = gridContent.innerHTML; + if (typeof window.initLazyLoading === 'function') window.initLazyLoading(); + if (typeof window.initThumbnailHover === 'function') window.initThumbnailHover(); + } + } } const newPag = doc.querySelector('.pagination-wrapper'); @@ -1985,20 +2053,30 @@ window.cancelAnimFrame = (function () { } window._onaraCurrentGridUrl = pageUrl; - window._onaraReturnUrl = pageUrl; - if (doc.title) { - window._onaraReturnTitle = doc.title; + const originWasGrid = window._onaraReturnUrl && ( + window._onaraReturnUrl.match(/\/p\/\d+/) || + new URL(window._onaraReturnUrl, window.location.origin).pathname === '/' || + window._onaraReturnUrl.includes('/tag/') || + window._onaraReturnUrl.includes('/h/') || + window._onaraReturnUrl.includes('/user/') + ); + if (originWasGrid || !window._onaraReturnUrl) { + window._onaraReturnUrl = pageUrl; + if (doc.title) { + window._onaraReturnTitle = doc.title; + } } const thumb = updateOnaraActiveItem(itemid, url, true, slug); - if (!thumb && oldPosts) { + const currentPosts = document.querySelector('.posts'); + if (!thumb && currentPosts) { const synthThumb = document.createElement('a'); synthThumb.href = url; - synthThumb.className = 'thumb lazy-thumb onara-active'; + synthThumb.className = 'thumb lazy-thumb onara-active onara-synth-thumb'; synthThumb.dataset.bg = `/t/${itemid}.webp`; synthThumb.dataset.size = '1'; synthThumb.innerHTML = '

'; - oldPosts.prepend(synthThumb); + currentPosts.prepend(synthThumb); if (typeof window.initLazyLoading === 'function') window.initLazyLoading(); scrollOnaraThumbIntoView(synthThumb, true); } @@ -2092,30 +2170,23 @@ window.cancelAnimFrame = (function () { if (typeof window.closeImageModal === 'function') window.closeImageModal(); document.body.classList.remove('modal-open'); - // Pause any playing media immediately + // Pause any playing media immediately so audio/video stops cleanly without popping modal.querySelectorAll('video, audio').forEach(el => { try { el.pause(); } catch {} }); - // Smooth fade out + // Smooth fade out: immediately trigger background canvas fast fade-out modal.classList.remove('onara-fade-in'); modal.classList.add('onara-fade-out'); - document.body.classList.remove('onara-modal-open'); - if (typeof window.applyBackgroundOpacitySettings === 'function') { - window.applyBackgroundOpacitySettings(null, false); - } + document.body.classList.add('onara-modal-closing'); - // Destroy background instance when Onara modal closes so it doesn't linger on the index grid - if (window.destroyBackgroundInstance) { - window.destroyBackgroundInstance(); + const bgCanvas = document.getElementById('bg'); + if (bgCanvas) { + bgCanvas._bgFadingOut = true; + bgCanvas.classList.remove('fader-in'); + bgCanvas.classList.add('fader-out', 'fast-fade'); + bgCanvas.style.setProperty('transition', 'opacity 0.2s cubic-bezier(0.25, 0, 0.4, 1)', 'important'); + bgCanvas.style.setProperty('opacity', '0', 'important'); } - // Restore pagination container visibility on background grid - document.querySelectorAll('.pagination-container-fluid').forEach(el => { - const pagWrapper = el.querySelector('.pagination-wrapper'); - if (!pagWrapper || pagWrapper.innerHTML.trim().length > 0) { - el.style.display = ''; - } - }); - if (!options.skipHistory) { const returnUrl = window._onaraReturnUrl || getOnaraBaseUrl(); const returnTitle = window._onaraReturnTitle || window.f0ckDomain || ''; @@ -2126,13 +2197,17 @@ window.cancelAnimFrame = (function () { if (returnTitle) { document.title = returnTitle; } + const returnPath = new URL(returnUrl, window.location.origin).pathname; + const isStaticReturn = returnPath.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes)/); + if (isStaticReturn && typeof window.loadPageAjax === 'function') { + window.loadPageAjax(returnUrl, true, { skipPush: true }); + } } window._onaraReturnUrl = null; window._onaraReturnTitle = null; // Maintain focus on active item so user TAB navigation seamlessly resumes from current progress const activeThumb = document.querySelector('.posts > a.thumb.onara-active'); - document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active')); if (activeThumb) { try { activeThumb.focus({ preventScroll: true }); @@ -2141,12 +2216,34 @@ window.cancelAnimFrame = (function () { if (modal._closeTimeout) clearTimeout(modal._closeTimeout); if (options.immediate) { + if (bgCanvas) { + bgCanvas.style.setProperty('transition', 'none', 'important'); + bgCanvas.style.setProperty('opacity', '0', 'important'); + } stopMedia(); const mount = document.getElementById('onara-item-mount'); if (mount) mount.innerHTML = ''; modal.style.display = 'none'; modal.setAttribute('aria-hidden', 'true'); modal.classList.remove('onara-fade-out', 'onara-fade-in'); + document.body.classList.remove('onara-modal-open', 'onara-modal-closing'); + document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active')); + if (typeof window.applyBackgroundOpacitySettings === 'function') { + window.applyBackgroundOpacitySettings(null, false); + } + window._bgModeAutoSwitch = true; + const targetModeBtn = document.querySelector((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? '#bg-mode-btn-audio' : '#bg-mode-btn-standard'); + if (targetModeBtn) targetModeBtn.click(); + window._bgModeAutoSwitch = false; + if (window.destroyBackgroundInstance) { + window.destroyBackgroundInstance(); + } + document.querySelectorAll('.pagination-container-fluid').forEach(el => { + const pagWrapper = el.querySelector('.pagination-wrapper'); + if (!pagWrapper || pagWrapper.innerHTML.trim().length > 0) { + el.style.display = ''; + } + }); modal._isClosing = false; modal._closeTimeout = null; return; @@ -2160,9 +2257,29 @@ window.cancelAnimFrame = (function () { modal.style.display = 'none'; modal.setAttribute('aria-hidden', 'true'); modal.classList.remove('onara-fade-out'); + + // Now that modal is completely invisible, smoothly restore background state + document.body.classList.remove('onara-modal-open', 'onara-modal-closing'); + document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active')); + if (typeof window.applyBackgroundOpacitySettings === 'function') { + window.applyBackgroundOpacitySettings(null, false); + } + window._bgModeAutoSwitch = true; + const targetModeBtn = document.querySelector((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? '#bg-mode-btn-audio' : '#bg-mode-btn-standard'); + if (targetModeBtn) targetModeBtn.click(); + window._bgModeAutoSwitch = false; + if (window.destroyBackgroundInstance) { + window.destroyBackgroundInstance(); + } + document.querySelectorAll('.pagination-container-fluid').forEach(el => { + const pagWrapper = el.querySelector('.pagination-wrapper'); + if (!pagWrapper || pagWrapper.innerHTML.trim().length > 0) { + el.style.display = ''; + } + }); modal._isClosing = false; modal._closeTimeout = null; - }, 240); + }, 220); }; window.closeOnaraModal = closeOnaraModal; window.isOnaraActive = isOnaraActive; @@ -2540,7 +2657,6 @@ window.cancelAnimFrame = (function () { status.className = 'flash-success'; } registerForm.reset(); - // Optional: switch to login view after a delay setTimeout(() => { const loginToRegister = document.getElementById('login-to-register'); if (loginToRegister) { @@ -2737,6 +2853,78 @@ window.cancelAnimFrame = (function () { let bgRafId = null; let lastBgElem = null; + // ── Central Background State ──────────────────────────────────────────────── + // Single source of truth for all background-canvas state. + // bgRafId / lastBgElem remain as direct references for backward-compat with the + // existing code inside initBackground() — they are kept in sync via the setters. + const BgState = { + // ── Readable state ──────────────────────────────────────────────────────── + get enabled() { return background; }, + get rafId() { return bgRafId; }, + get elem() { return lastBgElem; }, + + // ── Setters (keep legacy vars in sync) ───────────────────────────────── + set rafId(v) { bgRafId = v; }, + set elem(v) { lastBgElem = v; }, + + // ── Tuning cache (moved from module-level _bgTuningCache/_bgTuningFrame) ─ + _cache: null, + _cacheFrame: -1, + + // Compute effective tuning for the current context. + // forceMode: undefined = auto-detect | 'audio' | 'onara' | true (= onara) + getTuning(forceMode) { + const defAudio = (typeof DEFAULT_AUDIO_TUNING !== 'undefined' ? DEFAULT_AUDIO_TUNING : (window.DEFAULT_AUDIO_TUNING || {})); + const defOnara = (typeof DEFAULT_ONARA_TUNING !== 'undefined' ? DEFAULT_ONARA_TUNING : (window.DEFAULT_ONARA_TUNING || {})); + const defAudioBg = (typeof DEFAULT_AUDIO_BG_TUNING !== 'undefined' ? DEFAULT_AUDIO_BG_TUNING : (window.DEFAULT_AUDIO_BG_TUNING || {})); + if (forceMode === undefined) { + const frameId = window._rafFrameId || 0; + if (this._cache && this._cacheFrame === frameId) return this._cache; + const isAudio = (typeof isCurrentItemAudio === 'function') ? isCurrentItemAudio() : false; + const useOnara = (typeof isCurrentlyInOnara === 'function') ? isCurrentlyInOnara() : false; + let base = Object.assign({}, defAudio, window.audioVisualizerTuning || {}); + if (isAudio && !useOnara) { + Object.assign(base, defAudioBg, window.audioBgTuning || {}); + } + if (useOnara) { + Object.assign(base, defOnara, window.onaraTuning || {}); + } + this._cache = base; + this._cacheFrame = frameId; + return base; + } + // Explicit forceMode — not cached (called from tuner UI only, never RAF) + const isAudio = (forceMode === 'audio'); + const useOnara = (forceMode === 'onara' || forceMode === true); + let base = Object.assign({}, defAudio, window.audioVisualizerTuning || {}); + if (isAudio && !useOnara) { + Object.assign(base, defAudioBg, window.audioBgTuning || {}); + } + if (useOnara) { + Object.assign(base, defOnara, window.onaraTuning || {}); + } + return base; + }, + + // Invalidate the tuning cache (call after any tuning override changes) + invalidateTuning() { + this._cache = null; + this._cacheFrame = -1; + }, + + // Cancel the running rAF loop, if any + stopRaf() { + if (bgRafId) { window.cancelAnimFrame(bgRafId); bgRafId = null; } + }, + + // Set the active media element; cancels the loop if the element changed + setElem(el) { + if (lastBgElem !== el) { this.stopRaf(); lastBgElem = el; } + }, + }; + + window.BgState = BgState; + // Apply initial visual state var initialCanvas = document.getElementById('bg'); @@ -3163,7 +3351,34 @@ window.cancelAnimFrame = (function () { try { audioEl.pause(); } catch {} } - if (mime.startsWith('video/')) { + if (mime === 'video/youtube') { + container.classList.add('is-video-active', 'is-youtube-active'); + container.classList.remove('is-audio-active'); + if (imgEl) imgEl.style.display = 'none'; + if (linkEl) linkEl.style.display = 'none'; + if (audioWrapper) audioWrapper.style.display = 'none'; + if (videoEl) { try { videoEl.pause(); } catch {} videoEl.style.display = 'none'; } + if (videoWrapper) videoWrapper.style.display = 'block'; + + // Reuse or create an iframe for YouTube + let ytIframe = videoWrapper.querySelector('.album-yt-iframe'); + if (!ytIframe) { + ytIframe = document.createElement('iframe'); + ytIframe.className = 'album-yt-iframe embed-responsive-item'; + ytIframe.setAttribute('frameborder', '0'); + ytIframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'); + ytIframe.setAttribute('allowfullscreen', ''); + ytIframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none;'; + videoWrapper.appendChild(ytIframe); + } + // Extract video ID from dest (yt:VIDEO_ID) or src URL + const ytDest = item.filename || item.dest || ''; + const ytId = ytDest.startsWith('yt:') ? ytDest.replace('yt:', '') : (ytDest.match(/(?:youtu\.be\/|v=)([A-Za-z0-9_-]{11})/)?.[1] || ''); + const newYtSrc = ytId ? `https://www.youtube.com/embed/${ytId}?enablejsapi=1` : newSrc; + if (ytIframe.src !== newYtSrc) ytIframe.src = newYtSrc; + ytIframe.style.display = 'block'; + } else if (mime.startsWith('video/')) { + container.classList.remove('is-youtube-active'); container.classList.add('is-video-active'); container.classList.remove('is-audio-active'); const badge = container.querySelector('.audio-track-info-badge'); @@ -3171,6 +3386,9 @@ window.cancelAnimFrame = (function () { if (imgEl) imgEl.style.display = 'none'; if (linkEl) linkEl.style.display = 'none'; if (audioWrapper) audioWrapper.style.display = 'none'; + // Hide any YouTube iframe that may be present from a previous sub-item + const prevYtIframe = videoWrapper?.querySelector('.album-yt-iframe'); + if (prevYtIframe) { prevYtIframe.src = ''; prevYtIframe.style.display = 'none'; } if (videoWrapper) videoWrapper.style.display = 'block'; if (videoEl) { @@ -3241,9 +3459,11 @@ window.cancelAnimFrame = (function () { audioWrapper.style.backgroundImage = 'none'; audioWrapper.style.backgroundColor = '#000000'; if (coverUrl) { + coverCircle._origBgImage = `url('${coverUrl}')`; coverCircle.style.backgroundImage = `url('${coverUrl}')`; ph.classList.add('has-cover'); } else { + coverCircle._origBgImage = null; coverCircle.style.backgroundImage = 'none'; coverCircle.style.backgroundColor = '#000000'; ph.classList.remove('has-cover'); @@ -3831,6 +4051,65 @@ window.cancelAnimFrame = (function () { syncLocationSubf0ck(); }); + const transformStandaloneItemToOnara = async () => { + const main = document.getElementById('main'); + if (!main) return; + + // Find the item content inside #main + const itemContainer = main.querySelector('.item-layout-container') || main.querySelector('.container') || main.firstElementChild; + if (!itemContainer) return; + + // Ensure Onara modal exists in DOM + const modal = getOrCreateOnaraModal(); + const mount = document.getElementById('onara-item-mount'); + if (mount) { + mount.innerHTML = ''; + mount.appendChild(itemContainer); + } + + // Open Onara modal immediately + openOnaraModal(); + + const returnUrl = getOnaraBaseUrl() || '/'; + window._onaraReturnUrl = returnUrl; + window._onaraReturnTitle = window.f0ckDomain || 'f0ck'; + window._onaraCurrentGridUrl = returnUrl; + + const pathSegments = window.location.pathname.split('/'); + const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s)); + const activeKey = keySegments.length ? keySegments[keySegments.length - 1] : null; + + try { + const resp = await fetch(returnUrl, { + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }); + if (resp.ok) { + const html = await resp.text(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + const gridContent = doc.querySelector('#main') || doc.body; + if (gridContent) { + main.className = ''; + main.innerHTML = gridContent.innerHTML; + if (typeof window.initLazyLoading === 'function') { + window.initLazyLoading(); + } + if (typeof window.initThumbnailHover === 'function') { + window.initThumbnailHover(); + } + } + } + } catch (err) { + console.warn('[ONARA] Failed to load background grid for direct item:', err); + } + + if (activeKey) { + updateOnaraActiveItem(activeKey, window.location.href); + if (typeof window.trackVisit === 'function') { + window.trackVisit(activeKey); + } + } + }; + const initOnaraInitialState = () => { if (!isOnaraActive()) return; promoteOnaraModals(); @@ -3851,6 +4130,13 @@ window.cancelAnimFrame = (function () { } } } else { + // Check if this is a direct item visit rendered as standalone item view (e.g. before cookie reached server) + const isDirectStandaloneItem = !document.getElementById('posts') && + (document.querySelector('.item-container, .media-object, #main.item-view') !== null); + if (isDirectStandaloneItem) { + transformStandaloneItemToOnara(); + return; + } window._onaraCurrentGridUrl = window.location.pathname + window.location.search; } }; @@ -4145,6 +4431,8 @@ window.cancelAnimFrame = (function () { canvas._bgFadingOut = false; canvas.classList.remove('fader-in'); canvas.classList.add('fader-out'); + canvas.style.removeProperty('opacity'); + canvas.style.removeProperty('transition'); const ctx = canvas.getContext('2d'); if (ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -4402,6 +4690,29 @@ window.cancelAnimFrame = (function () { ambientTransitionRaf = window.requestAnimFrame(step); }; + // ── Background canvas filter string — stateless, module-level ────────────── + // Method 0 = Canvas 2D blur (applied via ctx.filter before drawImage) + // Method 1 = GPU CSS only (canvas drawn sharp; CSS --bg-canvas-filter blurs the element) + // Method 2 = Hybrid (CSS blur + canvas sat/contrast, no canvas-level blur) + const _computeBgCanvasFilter = (scale) => { + const cfg = (typeof window.getEffectiveBackgroundTuning === 'function' + ? window.getEffectiveBackgroundTuning() : null) + || window.audioVisualizerTuning + || DEFAULT_AUDIO_TUNING; + const method = Number(cfg.bgBlurMethod ?? 0); + const bSetting = Number(cfg.bgCanvasBlur ?? 31); + const bPx = (method === 1 || method === 2) ? 0 : Math.round(bSetting * (scale ?? 0.5)); + const bVal = Number(cfg.bgCanvasBrightness ?? 0.6); + const sat = Number(cfg.bgCanvasSaturate ?? 1.85); + const con = Number(cfg.bgCanvasContrast ?? 1.15); + const parts = []; + if (bPx > 0) parts.push(`blur(${bPx}px)`); + if (bVal !== 1) parts.push(`brightness(${bVal})`); + if (sat !== 1) parts.push(`saturate(${sat})`); + if (con !== 1) parts.push(`contrast(${con})`); + return parts.length > 0 ? parts.join(' ') : 'none'; + }; + // Paint background canvas immediately from thumbnail without network or animation delay window.paintImmediateBgThumb = (thumbOrElem) => { if (typeof background !== 'undefined' && !background) return; @@ -4432,7 +4743,8 @@ window.cancelAnimFrame = (function () { canvas.classList.remove('fader-out', 'fast-fade'); canvas.classList.add('fader-in'); canvas.style.transition = 'none'; - canvas.style.opacity = document.body.classList.contains('onara-modal-open') ? 'var(--bg-canvas-opacity, 0.95)' : 'var(--bg-canvas-opacity, 0.81)'; + // opacity driven by --bg-canvas-opacity CSS var (set by applyBackgroundOpacitySettings) + .fader-in + canvas.style.removeProperty('opacity'); const ctx = canvas.getContext('2d'); if (!ctx) return; @@ -4552,13 +4864,45 @@ window.cancelAnimFrame = (function () { const bgCfg = (typeof window.getEffectiveBackgroundTuning === 'function' ? window.getEffectiveBackgroundTuning() : null) || window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; const feedAudio = Number(bgCfg.feedAudioToBg !== undefined ? bgCfg.feedAudioToBg : 1) === 1; + // Auto-switch background settings profile + update the mode bar button highlight + // Use a flag to suppress the toast notification on auto-switches (not user-initiated) + window._bgModeAutoSwitch = true; + const inOnara = (typeof isCurrentlyInOnara === 'function') ? isCurrentlyInOnara() : document.body.classList.contains('onara-modal-open'); + if (inOnara) { + const btn = document.querySelector('#bg-mode-btn-onara'); + if (btn) btn.click(); + else if (typeof window.applyBackgroundOpacitySettings === 'function') { + window.applyBackgroundOpacitySettings(window.onaraTuning, true); + } + } else if (elem.tagName === 'AUDIO') { + const btn = document.querySelector('#bg-mode-btn-audio'); + if (btn) btn.click(); + else if (typeof window.applyBackgroundOpacitySettings === 'function') { + window.applyBackgroundOpacitySettings(window.audioBgTuning, 'audio'); + } + } else { + // Reset stale cover-art ambient colors so they don't bleed through on the next item + mediaAmbientState.top = [25, 25, 35]; + mediaAmbientState.targetTop = [25, 25, 35]; + mediaAmbientState.bottom = [25, 25, 35]; + mediaAmbientState.targetBottom = [25, 25, 35]; + mediaAmbientState.hasSampled = false; + const btn = document.querySelector('#bg-mode-btn-standard'); + if (btn) btn.click(); + else if (typeof window.applyBackgroundOpacitySettings === 'function') { + window.applyBackgroundOpacitySettings(window.audioVisualizerTuning, false); + } + } + window._bgModeAutoSwitch = false; + if (background && (elem.tagName !== 'AUDIO' || feedAudio)) { canvas._bgFadingOut = false; canvas.classList.add('fader-in'); canvas.classList.remove('fader-out', 'fast-fade'); if (document.body.classList.contains('onara-modal-open')) { canvas.style.transition = 'none'; - canvas.style.opacity = 'var(--bg-canvas-opacity, 1.0)'; + // opacity driven by --bg-canvas-opacity CSS var (applyBackgroundOpacitySettings already called above) + canvas.style.removeProperty('opacity'); } } else { // Don't clear the canvas here — let the existing content fade out. @@ -4588,9 +4932,16 @@ window.cancelAnimFrame = (function () { const { w: cw, h: ch } = syncCanvasDimensions(); - // Capture previous canvas snapshot for buttery-smooth crossfade transition + // Capture previous canvas snapshot for buttery-smooth crossfade transition. + // IMPORTANT: Skip snapshot when the canvas was previously driven by the audio visualizer + // (feedAudioToBg). In that state the canvas contains transient visualizer output + // (accent-coloured bars / waveforms) — using it as a crossfade source causes a + // green (or otherwise accent-coloured) full-page flash while the new thumbnail loads. + // canvas._wasVisualizerSource is set to true each time the visualizer feeds to #bg, + // and cleared when the non-visualizer (image/video) background path takes over. + const isVisualizerDriven = !!canvas._wasVisualizerSource; let prevSnapshot = null; - if (canvas.width > 0 && canvas.height > 0) { + if (!isVisualizerDriven && canvas.width > 0 && canvas.height > 0) { try { prevSnapshot = document.createElement('canvas'); prevSnapshot.width = canvas.width; @@ -4601,6 +4952,15 @@ window.cancelAnimFrame = (function () { prevSnapshot = null; } } + // Track whether the canvas is now going to be visualizer-driven + canvas._wasVisualizerSource = (elem.tagName === 'AUDIO' && feedAudio); + // Reset the thumbnail reference for the ambient sampler — will be repopulated when drawOnce() loads the new thumb + canvas._bgThumbImg = null; + // Clear the canvas immediately so no stale visualizer frame bleeds through + if (isVisualizerDriven) { + const ctx0 = canvas.getContext('2d'); + if (ctx0) ctx0.clearRect(0, 0, canvas.width, canvas.height); + } const context = canvas.getContext('2d'); const cfgCurrent = (typeof window.getEffectiveBackgroundTuning === 'function' ? window.getEffectiveBackgroundTuning() : null) || window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; @@ -4609,23 +4969,8 @@ window.cancelAnimFrame = (function () { const blurPx = (methodSetting === 1 || methodSetting === 2) ? 0 : Math.round(blurSetting * SCALE); const brightnessVal = cfgCurrent.bgCanvasBrightness !== undefined ? Number(cfgCurrent.bgCanvasBrightness) : 0.6; - const getCanvasFilter = () => { - const cfg = (typeof window.getEffectiveBackgroundTuning === 'function' ? window.getEffectiveBackgroundTuning() : null) || window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; - const method = cfg.bgBlurMethod !== undefined ? Number(cfg.bgBlurMethod) : 0; - const bSetting = cfg.bgCanvasBlur !== undefined ? Number(cfg.bgCanvasBlur) : 31; - // Method 1 = GPU CSS filter only (no canvas blur, handled by CSS --bg-canvas-filter) - // Method 2 = Hybrid (CSS blur + canvas sat/contrast) - const bPx = (method === 1 || method === 2) ? 0 : Math.round(bSetting * SCALE); - const bVal = cfg.bgCanvasBrightness !== undefined ? Number(cfg.bgCanvasBrightness) : 0.6; - const sat = cfg.bgCanvasSaturate !== undefined ? Number(cfg.bgCanvasSaturate) : 1.85; - const con = cfg.bgCanvasContrast !== undefined ? Number(cfg.bgCanvasContrast) : 1.15; - const parts = []; - if (bPx > 0) parts.push(`blur(${bPx}px)`); - if (bVal !== 1) parts.push(`brightness(${bVal})`); - if (sat !== 1) parts.push(`saturate(${sat})`); - if (con !== 1) parts.push(`contrast(${con})`); - return parts.length > 0 ? parts.join(' ') : 'none'; - }; + // Thin wrapper — passes the current SCALE from this initBackground() call + const getCanvasFilter = () => _computeBgCanvasFilter(SCALE); const drawCoverImage = (ctx, source) => { if (!source) return; @@ -4676,7 +5021,7 @@ window.cancelAnimFrame = (function () { syncCanvasDimensions(); const targetW = canvas.width; const targetH = canvas.height; - const useAmbient = Number(cfgCurrent.bgAmbientLighting) === 1; + const useAmbient = Number(getEffectiveBackgroundTuning().bgAmbientLighting) === 1; if (useAmbient) { const sampled = sampleMediaSpatialColors(sourceImg) || { top: [30, 30, 45], bottom: [30, 30, 45] }; @@ -4771,7 +5116,7 @@ window.cancelAnimFrame = (function () { } const thumb = new Image(); - thumb.onload = () => onLoaded(thumb); + thumb.onload = () => { canvas._bgThumbImg = thumb; onLoaded(thumb); }; thumb.onerror = () => { if (isDrawable && elem.complete && elem.naturalWidth > 0) { onLoaded(elem); @@ -4791,18 +5136,35 @@ window.cancelAnimFrame = (function () { window.redrawBgCanvas = drawOnce; let videoFadeStartTime = prevSnapshot ? performance.now() : 0; - const animationLoop = () => { + let _bgLastTs = 0; + const animationLoop = (ts) => { + // Increment the per-frame ID so getEffectiveBackgroundTuning() cache invalidates correctly + window._rafFrameId = ((window._rafFrameId || 0) + 1) & 0xFFFFFF; + if (!elem || elem.tagName === 'AUDIO' || elem.paused || elem.ended || (!background && !canvas._bgFadingOut)) { bgRafId = null; return; } + + // Read tuning live every frame so slider changes apply immediately. + const cfgLive = getEffectiveBackgroundTuning(); + // Throttle non-ambient bg to user-configured fps (default 30). + // Ambient mode stays at full fps because the colour lerp must be buttery-smooth. + const _useAmbientThrottle = Number(cfgLive.bgAmbientLighting) === 1; + const _bgFps = Number(cfgLive.bgCanvasFps ?? 30); + const _bgFrameMs = _bgFps > 0 ? 1000 / _bgFps : 0; + if (!_useAmbientThrottle && !prevSnapshot && _bgFrameMs > 0 && ts - _bgLastTs < _bgFrameMs) { + bgRafId = window.requestAnimFrame(animationLoop); + return; + } + _bgLastTs = ts; try { syncCanvasDimensions(); const curCw = canvas.width; const curCh = canvas.height; context.clearRect(0, 0, curCw, curCh); - const useAmbient = Number(cfgCurrent.bgAmbientLighting) === 1; + const useAmbient = Number(cfgLive.bgAmbientLighting) === 1; if (useAmbient) { if (!elem._ambientFrameCount) elem._ambientFrameCount = 0; elem._ambientFrameCount++; @@ -4873,10 +5235,10 @@ window.cancelAnimFrame = (function () { }; // Singleton: Ensure only one listener and one loop per element + // BgState.setElem() cancels any running rAF if the element changed. const isAlbumVideo = elem.id === 'f0ck-album-video'; if (lastBgElem !== elem || isAlbumVideo) { - if (bgRafId) window.cancelAnimFrame(bgRafId); - lastBgElem = elem; + BgState.setElem(elem); // cancels bgRafId + sets lastBgElem if (prevSnapshot) videoFadeStartTime = performance.now(); if (elem.tagName === 'VIDEO') { @@ -5056,6 +5418,36 @@ window.cancelAnimFrame = (function () { }; + const getAudioCoverUrl = (coverCircle, audioElement) => { + if (coverCircle) { + if (coverCircle._origBgImage && coverCircle._origBgImage !== 'none') { + const m = coverCircle._origBgImage.match(/url\(["']?([^"')]+)["']?\)/); + if (m && m[1] && !m[1].includes('200.gif') && !m[1].includes('audio.webp')) return m[1]; + } + if (coverCircle.style && coverCircle.style.backgroundImage && coverCircle.style.backgroundImage !== 'none') { + const m = coverCircle.style.backgroundImage.match(/url\(["']?([^"')]+)["']?\)/); + if (m && m[1] && !m[1].includes('200.gif') && !m[1].includes('audio.webp')) return m[1]; + } + } + const coverEl = audioElement?.closest('.v0ck, .album-audio-wrapper, .embed-responsive, #onara-item-mount, .item-view')?.querySelector("#f0ck-audio-cover") + || document.querySelector("#onara-item-mount #f0ck-audio-cover, .item-view #f0ck-audio-cover, #f0ck-audio-cover"); + if (coverEl && coverEl.src && !coverEl.src.endsWith('#') && !coverEl.src.includes('200.gif') && !coverEl.src.includes('audio.webp')) { + return coverEl.src; + } + if (audioElement && audioElement.getAttribute) { + const poster = audioElement.getAttribute('poster'); + if (poster && !poster.includes('200.gif') && !poster.includes('audio.webp')) return poster; + } + const ph = coverCircle ? coverCircle.closest('.sidebar-media-placeholder.audio') : null; + const parent = ph ? ph.parentElement : null; + const imgEl = parent ? parent.querySelector('#f0ck-audio-cover, img[src*="/ca/"], img[src*="cover"]') : null; + if (imgEl && imgEl.src && !imgEl.src.includes('200.gif') && !imgEl.src.includes('audio.webp')) { + return imgEl.src; + } + return null; + }; + window.getAudioCoverUrl = getAudioCoverUrl; + // Audio Visualizer Reactivity Tuner const DEFAULT_AUDIO_TUNING = { useCustomColor: 0, @@ -5073,7 +5465,20 @@ window.cancelAnimFrame = (function () { glowSensitivity: 1, glowDynamism: 0.85, glowSmoothness: 0.95, + showCoverInEye: 0, + showCoverInBg: 0, + showCoverArt: 0, solidCover: 1, + coverPosX: 0, + coverPosY: 0, + coverImageScale: 100, + coverImagePosX: 0, + coverImagePosY: 0, + bgCoverOpacity: 0.45, + bgCoverBlur: 0, + bgCoverScale: 100, + bgCoverPosX: 50, + bgCoverPosY: 50, coverColor: "#000000", coverOpacity: 0, bassGain: 0.5, @@ -5174,51 +5579,67 @@ window.cancelAnimFrame = (function () { onaraBgOpacity: 0.4, onaraBackdropBlur: 5, bgCanvasColor: "#000000", - bgCanvasColorOpacity: 0.09, - bgCanvasOpacity: 0.81, + bgCanvasColorOpacity: 1, + bgCanvasOpacity: 0.4, bgBlurMethod: 0, - bgCanvasBlur: 151, - bgCanvasBrightness: 0.6, + bgCanvasBlur: 30, + bgCanvasBrightness: 0.75, bgCanvasSaturate: 1.9, bgCanvasContrast: 1.15, bgCanvasZoom: 1.38, bgCanvasScale: 2.15, onaraGridDim: 0.38, feedAudioToBg: 1, - bgAmbientLighting: 1, - bgAmbientIntensity: 1.9, - bgAmbientSpread: 0.5, - bgAmbientSaturation: 1.5, - bgAmbientSmoothness: 0.06, - bgAudioReactivity: 1.50 + bgAmbientLighting: 0, + bgAmbientIntensity: 1.8, + bgAmbientSpread: 0.85, + bgAmbientSaturation: 2.1, + bgAmbientSmoothness: 0.11, + bgBlurMethod: 1, + bgCanvasFps: 60, + bgCanvasBlur: 60, + bgCanvasBrightness: 0.6, + bgCanvasSaturate: 1.9, + bgCanvasContrast: 1.15, + bgCanvasZoom: 1, + bgCanvasScale: 2.15, + bgCanvasColor: "#000000", + bgCanvasColorOpacity: 0.15, + bgCanvasOpacity: 0.43, + onaraGridDim: 0.38, + feedAudioToBg: 0, + bgAudioReactivity: 1.5, + onaraBgOpacity: 0.4, + onaraBackdropBlur: 5 }; - const TUNING_CONFIG_VERSION = '2026-09-16_tuner_v12'; + const TUNING_CONFIG_VERSION = '2026-09-17_tuner_v17'; const DEFAULT_ONARA_TUNING = { + bgCanvasOpacity: 0, + bgCanvasBlur: 0, + bgCanvasBrightness: 0.1, + bgCanvasFps: 0, + feedAudioToBg: 0, bgCanvasColor: "#000000", - bgCanvasColorOpacity: 0.8, - bgCanvasOpacity: 0.95, + bgCanvasColorOpacity: 0, + bgAmbientLighting: 0, + bgAmbientIntensity: 0.1, + bgAmbientSpread: 0.75, + bgAmbientSaturation: 0.9, + bgAmbientSmoothness: 0.05, bgBlurMethod: 2, - bgCanvasBlur: 31, - bgCanvasBrightness: 0.6, bgCanvasSaturate: 1.85, bgCanvasContrast: 1.15, bgCanvasZoom: 1.38, bgCanvasScale: 2.15, - feedAudioToBg: 1, - onaraBgOpacity: 0, - onaraBackdropBlur: 10, - onaraGridDim: 0, - bgAmbientLighting: 0, - bgAmbientIntensity: 1.0, - bgAmbientSpread: 0.85, - bgAmbientSaturation: 1.35, - bgAmbientSmoothness: 0.05, - bgAudioReactivity: 1.50 + bgAudioReactivity: 0, + onaraBgOpacity: 0.6, + onaraBackdropBlur: 5, + onaraGridDim: 0 }; - const ONARA_TUNING_CONFIG_VERSION = '2026-09-16_onara_v3'; + const ONARA_TUNING_CONFIG_VERSION = '2026-09-17_onara_v5'; let savedOnaraTuning = null; try { const appliedOnaraVer = localStorage.getItem('f0ck_onara_tuning_ver'); @@ -5238,20 +5659,21 @@ window.cancelAnimFrame = (function () { const DEFAULT_AUDIO_BG_TUNING = { bgCanvasColor: "#000000", bgCanvasColorOpacity: 0.15, - bgCanvasOpacity: 0.90, + bgCanvasOpacity: 0.43, bgBlurMethod: 0, + bgCanvasFps: 30, bgCanvasBlur: 60, - bgCanvasBrightness: 0.75, + bgCanvasBrightness: 0.6, bgCanvasSaturate: 1.9, bgCanvasContrast: 1.15, - bgCanvasZoom: 1.38, + bgCanvasZoom: 1, bgCanvasScale: 2.15, feedAudioToBg: 1, - bgAmbientLighting: 1, - bgAmbientIntensity: 1.45, - bgAmbientSpread: 0.85, - bgAmbientSaturation: 1.65, - bgAmbientSmoothness: 0.08, + bgAmbientLighting: 0, + bgAmbientIntensity: 0.1, + bgAmbientSpread: 0.3, + bgAmbientSaturation: 0.5, + bgAmbientSmoothness: 0.01, bgAudioReactivity: 1.50, onaraBgOpacity: 0.4, onaraBackdropBlur: 5, @@ -5275,40 +5697,32 @@ window.cancelAnimFrame = (function () { window.DEFAULT_AUDIO_BG_TUNING = DEFAULT_AUDIO_BG_TUNING; window.audioBgTuning = Object.assign({}, DEFAULT_AUDIO_BG_TUNING, savedAudioBgTuning || {}); - const isCurrentlyInOnara = () => { - return document.body.classList.contains('onara-modal-open') || - (typeof isOnaraActive === 'function' && isOnaraActive() && !!document.getElementById('onara-item-mount')); - }; + window.isCurrentlyInOnara = isCurrentlyInOnara; + let _isAudioItemCache = { result: false, ts: 0 }; const isCurrentItemAudio = () => { + const now = Date.now(); + if (now - _isAudioItemCache.ts < 250) return _isAudioItemCache.result; const mount = document.getElementById('onara-item-mount'); if (mount) { const a = mount.querySelector('#f0ck-album-audio, audio#my-video, audio'); - if (a && a.isConnected) return true; + if (a && a.isConnected) { _isAudioItemCache = { result: true, ts: now }; return true; } } const aud = document.querySelector('#f0ck-album-audio, audio#my-video, audio'); + let result = false; if (aud && aud.isConnected) { const albumAudioWrap = document.getElementById('f0ck-album-audio-wrapper'); - if (albumAudioWrap && albumAudioWrap.style.display !== 'none') return true; - if (aud.tagName === 'AUDIO') return true; + if (albumAudioWrap && albumAudioWrap.style.display !== 'none') result = true; + if (aud.tagName === 'AUDIO') result = true; } - return false; + _isAudioItemCache = { result, ts: now }; + return result; }; window.isCurrentItemAudio = isCurrentItemAudio; - const getEffectiveBackgroundTuning = (forceMode) => { - const isAudio = forceMode !== undefined ? (forceMode === 'audio') : isCurrentItemAudio(); - const useOnara = forceMode !== undefined ? (forceMode === 'onara' || forceMode === true) : isCurrentlyInOnara(); - - let base = Object.assign({}, DEFAULT_AUDIO_TUNING, window.audioVisualizerTuning || {}); - if (useOnara) { - base = Object.assign(base, DEFAULT_ONARA_TUNING, window.onaraTuning || {}); - } - if (isAudio) { - base = Object.assign(base, DEFAULT_AUDIO_BG_TUNING, window.audioBgTuning || {}); - } - return base; - }; + // getEffectiveBackgroundTuning — thin wrapper around BgState.getTuning() + // Cache lives in BgState; _bgTuningCache/_bgTuningFrame kept only for any external accesses. + const getEffectiveBackgroundTuning = (forceMode) => BgState.getTuning(forceMode); window.getEffectiveBackgroundTuning = getEffectiveBackgroundTuning; const getCustomDefaultTuning = () => { @@ -5348,20 +5762,32 @@ window.cancelAnimFrame = (function () { } catch (e) {} window.audioVisualizerTuning = Object.assign({}, getEffectiveDefaultTuning(), savedTuning || {}); + if (window.audioVisualizerTuning.showCoverArt === undefined) { + window.audioVisualizerTuning.showCoverArt = (window.audioVisualizerTuning.solidCover !== undefined && Number(window.audioVisualizerTuning.solidCover) === 0) ? 1 : 0; + } + if (window.audioVisualizerTuning.showCoverInEye === undefined) { + window.audioVisualizerTuning.showCoverInEye = Number(window.audioVisualizerTuning.showCoverArt) === 1 ? 1 : 0; + } + if (window.audioVisualizerTuning.showCoverInBg === undefined) { + window.audioVisualizerTuning.showCoverInBg = 0; + } + if (window.audioVisualizerTuning.solidCover === undefined) { + window.audioVisualizerTuning.solidCover = (Number(window.audioVisualizerTuning.showCoverInEye) === 1 || Number(window.audioVisualizerTuning.showCoverInBg) === 1) ? 0 : 1; + } const applyBackgroundOpacitySettings = (cfg, forceMode) => { - const isAudio = forceMode !== undefined ? (forceMode === 'audio') : isCurrentItemAudio(); const inOnara = forceMode !== undefined ? (forceMode === 'onara' || forceMode === true) : isCurrentlyInOnara(); - const c = cfg || getEffectiveBackgroundTuning(forceMode); - const onaraOp = c.onaraBgOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.onaraBgOpacity))) : (inOnara ? 0.0 : 0.40); - const bgOp = c.bgCanvasOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.bgCanvasOpacity))) : (isAudio ? 0.90 : (inOnara ? 0.95 : 0.81)); - const gridThumbOp = c.onaraGridDim !== undefined ? Math.min(1, Math.max(0, Number(c.onaraGridDim))) : (inOnara ? 0.0 : 0.38); - const onaraBlur = c.onaraBackdropBlur !== undefined ? Math.max(0, Number(c.onaraBackdropBlur)) : (inOnara ? 10 : 5); - const bgBlur = c.bgCanvasBlur !== undefined ? Math.max(0, Number(c.bgCanvasBlur)) : (isAudio ? 60 : (inOnara ? 31 : 151)); + const isAudio = forceMode !== undefined ? (forceMode === 'audio') : (!inOnara && isCurrentItemAudio()); + const c = cfg || getEffectiveBackgroundTuning(forceMode !== undefined ? forceMode : (inOnara ? 'onara' : (isAudio ? 'audio' : 'standard'))); + const onaraOp = c.onaraBgOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.onaraBgOpacity))) : (inOnara ? 0.6 : 0.40); + const bgOp = c.bgCanvasOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.bgCanvasOpacity))) : (inOnara ? 0.0 : (isAudio ? 0.90 : 0.81)); + const gridThumbOp = c.onaraGridDim !== undefined ? (1 - Math.min(1, Math.max(0, Number(c.onaraGridDim)))) : 1.0; + const onaraBlur = c.onaraBackdropBlur !== undefined ? Math.max(0, Number(c.onaraBackdropBlur)) : (inOnara ? 5 : 5); + const bgBlur = c.bgCanvasBlur !== undefined ? Math.max(0, Number(c.bgCanvasBlur)) : (inOnara ? 0 : (isAudio ? 60 : 151)); const method = c.bgBlurMethod !== undefined ? Number(c.bgBlurMethod) : (inOnara ? 2 : 0); const zoom = c.bgCanvasZoom !== undefined ? Math.max(1.0, Number(c.bgCanvasZoom)) : 1.38; const bgColor = c.bgCanvasColor || '#000000'; - const bgColOp = c.bgCanvasColorOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.bgCanvasColorOpacity))) : (isAudio ? 0.15 : (inOnara ? 0.8 : 0.09)); + const bgColOp = c.bgCanvasColorOpacity !== undefined ? Math.min(1, Math.max(0, Number(c.bgCanvasColorOpacity))) : (inOnara ? 0.0 : (isAudio ? 0.15 : 0.09)); const computedBgCol = bgColOp <= 0.001 ? 'transparent' : (bgColOp >= 0.999 ? bgColor : `color-mix(in srgb, ${bgColor} ${Math.round(bgColOp * 100)}%, transparent)`); document.documentElement.style.setProperty('--onara-bg-opacity', onaraOp.toFixed(3)); @@ -5389,7 +5815,9 @@ window.cancelAnimFrame = (function () { bgEl.style.removeProperty('opacity'); } - if (typeof window.redrawBgCanvas === 'function') { + // Don't call redrawBgCanvas for audio items where the visualizer loop owns #bg + const feedAudioActive = isAudio && Number((c.feedAudioToBg !== undefined ? c.feedAudioToBg : 1)) === 1; + if (!feedAudioActive && typeof window.redrawBgCanvas === 'function') { window.redrawBgCanvas(); } }; @@ -5403,7 +5831,7 @@ window.cancelAnimFrame = (function () { const isCursorNearV0ck = (e) => { const margin = 10; - const players = document.querySelectorAll('.v0ck, .sidebar-media-placeholder.audio'); + const players = document.querySelectorAll('.v0ck, .album-audio-wrapper, .embed-responsive:has(.sidebar-media-placeholder.audio), #onara-item-mount .sidebar-media-placeholder.audio'); for (const p of players) { const r = p.getBoundingClientRect(); if (r.width > 0 && r.height > 0 && @@ -5524,10 +5952,13 @@ window.cancelAnimFrame = (function () { // If audio visualizer is NOT actively driving the transform this frame, update it here if (!circle._visualizerDriving) { - const curX = circle._eyeX.toFixed(1); - const curY = circle._eyeY.toFixed(1); - const curTiltX = circle._eyeTiltX.toFixed(1); - const curTiltY = circle._eyeTiltY.toFixed(1); + const cfg = window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; + const offX = Number(cfg.coverPosX || 0); + const offY = Number(cfg.coverPosY || 0); + const curX = ((circle._eyeX || 0) + offX).toFixed(1); + const curY = ((circle._eyeY || 0) + offY).toFixed(1); + const curTiltX = (circle._eyeTiltX || 0).toFixed(1); + const curTiltY = (circle._eyeTiltY || 0).toFixed(1); circle.style.transform = `translate(calc(-50% + ${curX}px), calc(-50% + ${curY}px)) perspective(600px) rotateX(${curTiltX}deg) rotateY(${curTiltY}deg) scale(1)`; } }); @@ -5557,8 +5988,19 @@ window.cancelAnimFrame = (function () { { section: 'Visualizer Color & Theme', key: 'beatHueCooldown', label: 'Min Time Between Jumps', min: 50, max: 800, step: 25, unit: 'ms' }, // Cover Art & Glow Section - { section: 'Cover Art & Glow Reactivity', key: 'coverSize', label: 'Cover Art Size (Diameter)', min: 40, max: 600, step: 5, unit: 'px' }, - { section: 'Cover Art & Glow Reactivity', key: 'solidCover', label: 'Solid Color Cover (1=On, 0=Off)', min: 0, max: 1, step: 1, unit: '' }, + { section: 'Cover Art & Glow Reactivity', key: 'showCoverInEye', label: 'Show Cover in Eye / Disc (1=On, 0=Off)', min: 0, max: 1, step: 1, unit: '' }, + { section: 'Cover Art & Glow Reactivity', key: 'showCoverInBg', label: 'Show Cover in Background (1=On, 0=Off)', min: 0, max: 1, step: 1, unit: '' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverSize', label: 'Eye / Disc Diameter', min: 40, max: 600, step: 5, unit: 'px' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverPosX', label: 'Eye Position Offset X', min: -400, max: 400, step: 5, unit: 'px' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverPosY', label: 'Eye Position Offset Y', min: -300, max: 300, step: 5, unit: 'px' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverImageScale', label: 'Eye Cover Zoom / Scale', min: 50, max: 250, step: 5, unit: '%' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverImagePosX', label: 'Eye Cover Pan X', min: -100, max: 100, step: 2, unit: '%' }, + { section: 'Cover Art & Glow Reactivity', key: 'coverImagePosY', label: 'Eye Cover Pan Y', min: -100, max: 100, step: 2, unit: '%' }, + { section: 'Cover Art & Glow Reactivity', key: 'bgCoverOpacity', label: 'Background Cover Opacity', min: 0.00, max: 1.00, step: 0.02, unit: '' }, + { section: 'Cover Art & Glow Reactivity', key: 'bgCoverBlur', label: 'Background Cover Blur', min: 0, max: 60, step: 1, unit: 'px' }, + { section: 'Cover Art & Glow Reactivity', key: 'bgCoverScale', label: 'Background Cover Scale', min: 20, max: 300, step: 5, unit: '%' }, + { section: 'Cover Art & Glow Reactivity', key: 'bgCoverPosX', label: 'Background Cover Position X', min: 0, max: 100, step: 1, unit: '%' }, + { section: 'Cover Art & Glow Reactivity', key: 'bgCoverPosY', label: 'Background Cover Position Y', min: 0, max: 100, step: 1, unit: '%' }, { section: 'Cover Art & Glow Reactivity', key: 'coverColor', label: 'Solid Cover Color', type: 'color' }, { section: 'Cover Art & Glow Reactivity', key: 'coverOpacity', label: 'Cover / Disc Opacity', min: 0.00, max: 1.00, step: 0.01, unit: '' }, { section: 'Cover Art & Glow Reactivity', key: 'glowIntensity', label: 'Cover Art Glow Size (Dynamic)', min: 0, max: 500, step: 5, unit: 'px' }, @@ -5670,17 +6112,19 @@ window.cancelAnimFrame = (function () { const backgroundSliders = [ // Video & Ambient Background Canvas Section + { section: 'Video & Ambient Background Canvas', key: 'bgCanvasOpacity', label: 'Background Opacity', min: 0.00, max: 1.00, step: 0.01, unit: '', basic: true }, + { section: 'Video & Ambient Background Canvas', key: 'bgCanvasBlur', label: 'Blur Radius', min: 0, max: 300, step: 1, unit: 'px', basic: true }, + { section: 'Video & Ambient Background Canvas', key: 'bgCanvasBrightness', label: 'Brightness', min: 0.10, max: 3.00, step: 0.05, unit: 'x', basic: true }, + { section: 'Video & Ambient Background Canvas', key: 'bgCanvasFps', label: 'Background FPS (0=uncapped)', min: 0, max: 60, step: 1, unit: 'fps', basic: true }, + { section: 'Video & Ambient Background Canvas', key: 'feedAudioToBg', label: 'Show Visualizer in Background (1=On)', min: 0, max: 1, step: 1, unit: '', basic: true }, { section: 'Video & Ambient Background Canvas', key: 'bgCanvasColor', label: 'Background Color', type: 'color' }, { section: 'Video & Ambient Background Canvas', key: 'bgCanvasColorOpacity', label: 'Background Color Opacity', min: 0.00, max: 1.00, step: 0.01, unit: '' }, - { section: 'Video & Ambient Background Canvas', key: 'bgCanvasOpacity', label: 'Background Canvas Opacity', min: 0.00, max: 1.00, step: 0.01, unit: '' }, { section: 'Video & Ambient Background Canvas', key: 'bgAmbientLighting', label: 'Ambient Lighting (1=YouTube Glow, 0=Filter)', min: 0, max: 1, step: 1, unit: '' }, { section: 'Video & Ambient Background Canvas', key: 'bgAmbientIntensity', label: 'Ambient Glow Brightness', min: 0.10, max: 2.50, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgAmbientSpread', label: 'Ambient Glow Spread / Size', min: 0.30, max: 2.00, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgAmbientSaturation', label: 'Ambient Glow Saturation', min: 0.50, max: 3.50, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgAmbientSmoothness', label: 'Ambient Video Transition Speed', min: 0.01, max: 0.25, step: 0.01, unit: '' }, { section: 'Video & Ambient Background Canvas', key: 'bgBlurMethod', label: 'Blur Engine (0=Canvas 2D, 1=GPU CSS, 2=Hybrid)', min: 0, max: 2, step: 1, unit: '' }, - { section: 'Video & Ambient Background Canvas', key: 'bgCanvasBlur', label: 'Blur Radius', min: 0, max: 300, step: 1, unit: 'px' }, - { section: 'Video & Ambient Background Canvas', key: 'bgCanvasBrightness', label: 'Brightness', min: 0.10, max: 3.00, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgCanvasSaturate', label: 'Color Saturation', min: 0.50, max: 4.00, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgCanvasContrast', label: 'Contrast Depth', min: 0.50, max: 3.00, step: 0.05, unit: 'x' }, { section: 'Video & Ambient Background Canvas', key: 'bgCanvasZoom', label: 'Edge Overscan Zoom', min: 1.00, max: 1.80, step: 0.01, unit: 'x' }, @@ -5691,7 +6135,7 @@ window.cancelAnimFrame = (function () { // Onara Mode Background Section { section: 'Onara Mode Background', key: 'onaraBgOpacity', label: 'Onara Modal Backdrop Darkness', min: 0.00, max: 1.00, step: 0.01, unit: '' }, { section: 'Onara Mode Background', key: 'onaraBackdropBlur', label: 'Onara Modal Backdrop Blur', min: 0, max: 50, step: 1, unit: 'px' }, - { section: 'Onara Mode Background', key: 'onaraGridDim', label: 'Onara Background Grid Opacity', min: 0.00, max: 1.00, step: 0.01, unit: '' } + { section: 'Onara Mode Background', key: 'onaraGridDim', label: 'Onara Background Grid Dimming (0=None, 1=Hidden)', min: 0.00, max: 1.00, step: 0.01, unit: '' } ]; const danmakuSliders = [ @@ -5719,29 +6163,105 @@ window.cancelAnimFrame = (function () { const updateCoverArtSolidMode = () => { const cfg = window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; - const isSolid = Number(cfg.solidCover) === 1; + const showEye = cfg.showCoverInEye !== undefined + ? Number(cfg.showCoverInEye) === 1 + : (cfg.showCoverArt !== undefined ? Number(cfg.showCoverArt) === 1 : Number(cfg.solidCover) === 0); + const showBg = Number(cfg.showCoverInBg) === 1; + const color = cfg.coverColor || '#000000'; const coverOp = cfg.coverOpacity !== undefined ? Math.min(1, Math.max(0, Number(cfg.coverOpacity))) : 1.0; const bgCol = coverOp <= 0.001 ? 'transparent' : (coverOp >= 0.999 ? color : `color-mix(in srgb, ${color} ${Math.round(coverOp * 100)}%, transparent)`); - document.querySelectorAll('.audio-cover-circle').forEach(c => { - if (isSolid) { - if (c.style.backgroundImage && c.style.backgroundImage !== 'none' && !c._origBgImage) { - c._origBgImage = c.style.backgroundImage; + const imgScale = cfg.coverImageScale !== undefined ? Number(cfg.coverImageScale) : 100; + const imgPanX = cfg.coverImagePosX !== undefined ? Number(cfg.coverImagePosX) : 0; + const imgPanY = cfg.coverImagePosY !== undefined ? Number(cfg.coverImagePosY) : 0; + + const bgOpacity = cfg.bgCoverOpacity !== undefined ? Math.min(1, Math.max(0, Number(cfg.bgCoverOpacity))) : 0.45; + const bgBlur = cfg.bgCoverBlur !== undefined ? Math.max(0, Number(cfg.bgCoverBlur)) : 0; + const bgScale = cfg.bgCoverScale !== undefined ? Number(cfg.bgCoverScale) : 100; + const bgPosX = cfg.bgCoverPosX !== undefined ? Number(cfg.bgCoverPosX) : 50; + const bgPosY = cfg.bgCoverPosY !== undefined ? Number(cfg.bgCoverPosY) : 50; + + // Clean up any stray backdrops or reactive backgrounds on thumbnail/preview placeholders + document.querySelectorAll('.thumb .audio-cover-backdrop, .album-thumb-item .audio-cover-backdrop, .item-preview .audio-cover-backdrop, .sidebar-activity-item .audio-cover-backdrop, .recent-comment-media .audio-cover-backdrop, .posts .audio-cover-backdrop, .thumb .audio-reactive-bg, .album-thumb-item .audio-reactive-bg, .item-preview .audio-reactive-bg, .sidebar-activity-item .audio-reactive-bg, .recent-comment-media .audio-reactive-bg, .posts .audio-reactive-bg').forEach(el => el.remove()); + + // Only operate on active audio player placeholders, strictly ignoring thumbnails and preview grids + const playerPlaceholders = Array.from(document.querySelectorAll('.sidebar-media-placeholder.audio')).filter(ph => { + return !ph.closest('.thumb, .album-thumb-item, .item-preview, .sidebar-activity-item, .recent-comment-media, .recent-comment-item, .posts, .user-posts-container, .media-container-cards'); + }); + + playerPlaceholders.forEach(ph => { + let backdrop = ph.querySelector('.audio-cover-backdrop'); + const c = ph.querySelector('.audio-cover-circle'); + const note = c ? c.querySelector('i') : ph.querySelector(':scope > i'); + const audioEl = ph.closest('.v0ck, .album-audio-wrapper, .album-gallery-container, .embed-responsive, #onara-item-mount, .item-view')?.querySelector('audio') + || document.querySelector('#onara-item-mount audio, .item-view audio, audio#my-video'); + + const rawUrl = getAudioCoverUrl(c, audioEl); + const targetCoverCss = rawUrl ? `url("${rawUrl}")` : null; + + if (c && targetCoverCss && !c._origBgImage) { + c._origBgImage = targetCoverCss; + } + + // Background Cover Art + if (showBg && targetCoverCss) { + if (!backdrop) { + backdrop = document.createElement('div'); + backdrop.className = 'audio-cover-backdrop'; + ph.insertBefore(backdrop, ph.firstChild); } - c.style.backgroundImage = 'none'; - c.style.backgroundColor = bgCol; - } else { - if (c._origBgImage) { - c.style.backgroundImage = c._origBgImage; + backdrop.style.backgroundImage = targetCoverCss; + backdrop.style.opacity = String(bgOpacity); + backdrop.style.filter = bgBlur > 0 ? `blur(${bgBlur}px)` : 'none'; + backdrop.style.backgroundSize = bgScale === 100 ? 'cover' : `${bgScale}%`; + backdrop.style.backgroundPosition = `${bgPosX}% ${bgPosY}%`; + backdrop.style.display = 'block'; + } else if (backdrop) { + backdrop.style.backgroundImage = 'none'; + backdrop.style.opacity = '0'; + backdrop.style.display = 'none'; + } + + // Clean up any lingering trail ghosts + ph.querySelectorAll('.eye-trail-ghost').forEach(g => g.remove()); + + // Eye / Disc Cover Art + if (c) { + if (cfg.coverSize) { + c.style.width = cfg.coverSize + 'px'; + c.style.height = cfg.coverSize + 'px'; + if (note) note.style.fontSize = Math.round(cfg.coverSize * 0.35) + 'px'; + } + + if (showEye && targetCoverCss) { + c.style.backgroundImage = targetCoverCss; c.style.backgroundColor = bgCol; + c.style.backgroundSize = imgScale === 100 ? 'cover' : `${imgScale}%`; + c.style.backgroundPosition = `calc(50% + ${imgPanX}%) calc(50% + ${imgPanY}%)`; + ph.classList.add('has-cover'); + if (note) note.style.display = 'none'; } else { + if (c.style.backgroundImage && c.style.backgroundImage !== 'none' && !c._origBgImage) { + c._origBgImage = c.style.backgroundImage; + } c.style.backgroundImage = 'none'; c.style.backgroundColor = bgCol; + c.style.backgroundSize = 'cover'; + c.style.backgroundPosition = 'center'; + if (!showBg) { + ph.classList.remove('has-cover'); + } else { + ph.classList.add('has-cover'); + } + if (note) { + note.style.display = Number(cfg.innerHideNote) === 1 ? 'none' : ''; + } } } }); }; + window.updateCoverArtSolidMode = updateCoverArtSolidMode; let audioRowsHtml = ''; let currentAudioSection = ''; @@ -5775,34 +6295,34 @@ window.cancelAnimFrame = (function () { }); let bgRowsHtml = ''; + let bgRowsAdvancedHtml = ''; let currentBgSection = ''; backgroundSliders.forEach(s => { - if (s.section && s.section !== currentBgSection) { - currentBgSection = s.section; - bgRowsHtml += `
${currentBgSection}
`; - } const val = window.audioVisualizerTuning[s.key] !== undefined ? window.audioVisualizerTuning[s.key] : DEFAULT_AUDIO_TUNING[s.key]; - if (s.type === 'color') { - bgRowsHtml += ` + // Basic rows use prefix 'bgb-', advanced rows use 'bga-' to avoid duplicate IDs + const mkRow = (pfx) => { + if (s.type === 'color') { + return ` +
+
+ ${s.label} + ${val} +
+ +
`; + } + return `
${s.label} - ${val} + ${val}${s.unit || ''}
- -
- `; - } else { - bgRowsHtml += ` -
-
- ${s.label} - ${val}${s.unit || ''} -
- -
- `; - } + + `; + }; + if (s.section && s.section !== currentBgSection) currentBgSection = s.section; + if (s.basic) bgRowsHtml += mkRow('bgb-'); + else bgRowsAdvancedHtml += mkRow('bga-'); }); const danmakuCfg = window.danmakuTuning || (window.DEFAULT_DANMAKU_TUNING || {}); @@ -5963,7 +6483,14 @@ window.cancelAnimFrame = (function () { - ${bgRowsHtml} + +
+ Ansicht: + + +
+
${bgRowsHtml}
+ `; @@ -5996,6 +6523,25 @@ window.cancelAnimFrame = (function () { switchSubtab(savedSubtab); } + // ── Einfach / Erweitert Toggle (Background Tab) ──────────────────────── + const bgBasicRows = panel.querySelector('#f0ck-bg-basic-rows'); + const bgAdvancedRows = panel.querySelector('#f0ck-bg-advanced-rows'); + const bgViewBasicBtn = panel.querySelector('#bg-view-basic'); + const bgViewAdvBtn = panel.querySelector('#bg-view-advanced'); + const switchBgView = (mode) => { + const isAdv = mode === 'advanced'; + if (bgBasicRows) bgBasicRows.style.display = isAdv ? 'none' : ''; + if (bgAdvancedRows) bgAdvancedRows.style.display = isAdv ? '' : 'none'; + if (bgViewBasicBtn) bgViewBasicBtn.classList.toggle('active', !isAdv); + if (bgViewAdvBtn) bgViewAdvBtn.classList.toggle('active', isAdv); + try { localStorage.setItem('f0ck_bg_view_mode', mode); } catch (e) {} + }; + bgViewBasicBtn && bgViewBasicBtn.addEventListener('click', () => switchBgView('basic')); + bgViewAdvBtn && bgViewAdvBtn.addEventListener('click', () => switchBgView('advanced')); + // Restore last chosen view + const savedBgView = localStorage.getItem('f0ck_bg_view_mode'); + if (savedBgView === 'advanced') switchBgView('advanced'); + // ── Audio Presets & Events ────────────────────────────────────────────── const BUILTIN_PRESETS = { 'Default': getEffectiveDefaultTuning(), @@ -6016,6 +6562,7 @@ window.cancelAnimFrame = (function () { glowSensitivity: 1.8, glowDynamism: 0.85, glowSmoothness: 0.95, + showCoverArt: 0, solidCover: 1, coverColor: "#000000", coverOpacity: 1, @@ -6075,6 +6622,7 @@ window.cancelAnimFrame = (function () { glowSensitivity: 0.1, glowDynamism: 0, glowSmoothness: 1, + showCoverArt: 0, solidCover: 1, coverColor: "#000000", coverOpacity: 0, @@ -6136,6 +6684,7 @@ window.cancelAnimFrame = (function () { glowSensitivity: 1.2, glowDynamism: 2.2, glowSmoothness: 0.65, + showCoverArt: 0, solidCover: 1, coverColor: '#000000', coverOpacity: 1, @@ -6181,6 +6730,7 @@ window.cancelAnimFrame = (function () { glowSensitivity: 1.5, glowDynamism: 2.4, glowSmoothness: 0.75, + showCoverArt: 0, solidCover: 1, coverColor: '#000000', coverOpacity: 1, @@ -6222,6 +6772,7 @@ window.cancelAnimFrame = (function () { glowSensitivity: 1.3, glowDynamism: 2.0, glowSmoothness: 0.50, + showCoverArt: 0, solidCover: 1, coverColor: '#000000', coverOpacity: 1, @@ -6254,6 +6805,7 @@ window.cancelAnimFrame = (function () { glowBrightness: 1.2, glowSensitivity: 1.0, glowDynamism: 1.5, + showCoverArt: 1, solidCover: 0, coverOpacity: 1, scaleBounce: 0.50, @@ -6325,6 +6877,18 @@ window.cancelAnimFrame = (function () { const applyTuningConfig = (newCfg, presetName) => { Object.assign(window.audioVisualizerTuning, newCfg); + if (newCfg.showCoverArt === undefined && newCfg.solidCover !== undefined) { + window.audioVisualizerTuning.showCoverArt = Number(newCfg.solidCover) === 0 ? 1 : 0; + } + if (newCfg.solidCover === undefined && newCfg.showCoverArt !== undefined) { + window.audioVisualizerTuning.solidCover = Number(newCfg.showCoverArt) === 1 ? 0 : 1; + } + if (newCfg.showCoverInEye === undefined) { + window.audioVisualizerTuning.showCoverInEye = Number(window.audioVisualizerTuning.showCoverArt) === 1 ? 1 : 0; + } + if (newCfg.showCoverInBg === undefined) { + window.audioVisualizerTuning.showCoverInBg = 0; + } try { localStorage.setItem('f0ck_audio_tuning', JSON.stringify(window.audioVisualizerTuning)); } catch (e) {} @@ -6345,8 +6909,9 @@ window.cancelAnimFrame = (function () { }); backgroundSliders.forEach(s => { - const input = panel.querySelector(`#input-bg-${s.key}`); - const valEl = panel.querySelector(`#val-bg-${s.key}`); + const pfx = s.basic ? 'bgb-' : 'bga-'; + const input = panel.querySelector(`#input-${pfx}${s.key}`); + const valEl = panel.querySelector(`#val-${pfx}${s.key}`); if (!input || !valEl) return; const val = window.audioVisualizerTuning[s.key] !== undefined ? window.audioVisualizerTuning[s.key] : DEFAULT_AUDIO_TUNING[s.key]; input.value = val; @@ -6374,6 +6939,15 @@ window.cancelAnimFrame = (function () { } const note = c.querySelector('i'); if (note && cfg.coverSize) note.style.fontSize = Math.round(cfg.coverSize * 0.35) + 'px'; + if (!c._visualizerDriving) { + const offX = Number(cfg.coverPosX || 0); + const offY = Number(cfg.coverPosY || 0); + const curX = ((c._eyeX || 0) + offX).toFixed(1); + const curY = ((c._eyeY || 0) + offY).toFixed(1); + const curTiltX = (c._eyeTiltX || 0).toFixed(1); + const curTiltY = (c._eyeTiltY || 0).toFixed(1); + c.style.transform = `translate(calc(-50% + ${curX}px), calc(-50% + ${curY}px)) perspective(600px) rotateX(${curTiltX}deg) rotateY(${curTiltY}deg) scale(1)`; + } }); updateCoverArtSolidMode(); applyBackgroundOpacitySettings(window.audioVisualizerTuning); @@ -6537,8 +7111,45 @@ window.cancelAnimFrame = (function () { note.style.color = activeColor; note.style.textShadow = activeColor ? `0 0 16px color-mix(in srgb, ${activeColor} 75%, transparent), 0 0 35px color-mix(in srgb, ${activeColor} 40%, transparent)` : ''; }); - } else if (s.key === 'solidCover' || s.key === 'coverColor' || s.key === 'coverOpacity') { + } else if (s.key === 'showCoverInEye') { + const isShow = Number(input.value) === 1; + window.audioVisualizerTuning.showCoverInEye = isShow ? 1 : 0; + window.audioVisualizerTuning.showCoverArt = (isShow || Number(window.audioVisualizerTuning.showCoverInBg) === 1) ? 1 : 0; + window.audioVisualizerTuning.solidCover = (window.audioVisualizerTuning.showCoverArt === 1) ? 0 : 1; updateCoverArtSolidMode(); + } else if (s.key === 'showCoverInBg') { + const isShow = Number(input.value) === 1; + window.audioVisualizerTuning.showCoverInBg = isShow ? 1 : 0; + window.audioVisualizerTuning.showCoverArt = (isShow || Number(window.audioVisualizerTuning.showCoverInEye) === 1) ? 1 : 0; + window.audioVisualizerTuning.solidCover = (window.audioVisualizerTuning.showCoverArt === 1) ? 0 : 1; + updateCoverArtSolidMode(); + } else if (s.key === 'showCoverArt') { + const isShow = Number(input.value) === 1; + window.audioVisualizerTuning.showCoverArt = isShow ? 1 : 0; + window.audioVisualizerTuning.solidCover = isShow ? 0 : 1; + window.audioVisualizerTuning.showCoverInEye = isShow ? 1 : 0; + updateCoverArtSolidMode(); + } else if (s.key === 'solidCover') { + const isSolid = Number(input.value) === 1; + window.audioVisualizerTuning.solidCover = isSolid ? 1 : 0; + window.audioVisualizerTuning.showCoverArt = isSolid ? 0 : 1; + window.audioVisualizerTuning.showCoverInEye = isSolid ? 0 : 1; + updateCoverArtSolidMode(); + } else if (s.key.startsWith('cover') || s.key.startsWith('bgCover')) { + updateCoverArtSolidMode(); + if (s.key === 'coverPosX' || s.key === 'coverPosY') { + document.querySelectorAll('.audio-cover-circle').forEach(circle => { + if (!circle._visualizerDriving) { + const offX = Number(window.audioVisualizerTuning.coverPosX || 0); + const offY = Number(window.audioVisualizerTuning.coverPosY || 0); + const curX = ((circle._eyeX || 0) + offX).toFixed(1); + const curY = ((circle._eyeY || 0) + offY).toFixed(1); + const curTiltX = (circle._eyeTiltX || 0).toFixed(1); + const curTiltY = (circle._eyeTiltY || 0).toFixed(1); + circle.style.transform = `translate(calc(-50% + ${curX}px), calc(-50% + ${curY}px)) perspective(600px) rotateX(${curTiltX}deg) rotateY(${curTiltY}deg) scale(1)`; + } + }); + } } else if (s.key === 'enableBars') { if (Number(input.value) === 0) { document.querySelectorAll('.v0ck-audio-visualizer-canvas, .audio-visualizer-canvas').forEach(c => { @@ -6588,7 +7199,7 @@ window.cancelAnimFrame = (function () { }); // ── Background Sliders Event Listeners (Standard vs Onara vs Audio mode) ──────── - let activeBgTunerMode = isCurrentItemAudio() ? 'audio' : (isCurrentlyInOnara() ? 'onara' : 'standard'); + let activeBgTunerMode = isCurrentlyInOnara() ? 'onara' : (isCurrentItemAudio() ? 'audio' : 'standard'); const syncBgSlidersDisplay = () => { const mode = activeBgTunerMode; @@ -6612,18 +7223,22 @@ window.cancelAnimFrame = (function () { if (audioBtn) audioBtn.classList.toggle('active', mode === 'audio'); backgroundSliders.forEach(s => { - const input = panel.querySelector(`#input-bg-${s.key}`); - const valEl = panel.querySelector(`#val-bg-${s.key}`); - if (!input || !valEl) return; - const val = sourceObj[s.key] !== undefined ? sourceObj[s.key] : defaultObj[s.key]; - input.value = val; - if (s.type === 'color') { - valEl.textContent = val; - } else { - const num = Number(val); - const displayVal = Number.isInteger(num) ? num : parseFloat(num.toFixed(3)); - valEl.textContent = `${displayVal}${s.unit || ''}`; - } + // Update both basic (bgb-) and advanced (bga-) DOM elements + const pfxs = s.basic ? ['bgb-'] : ['bga-']; + pfxs.forEach(pfx => { + const input = panel.querySelector(`#input-${pfx}${s.key}`); + const valEl = panel.querySelector(`#val-${pfx}${s.key}`); + if (!input || !valEl) return; + const val = sourceObj[s.key] !== undefined ? sourceObj[s.key] : defaultObj[s.key]; + input.value = val; + if (s.type === 'color') { + valEl.textContent = val; + } else { + const num = Number(val); + const displayVal = Number.isInteger(num) ? num : parseFloat(num.toFixed(3)); + valEl.textContent = `${displayVal}${s.unit || ''}`; + } + }); }); }; @@ -6631,7 +7246,7 @@ window.cancelAnimFrame = (function () { activeBgTunerMode = 'standard'; syncBgSlidersDisplay(); applyBackgroundOpacitySettings(window.audioVisualizerTuning, false); - if (typeof window.flashMessage === 'function') { + if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Standard Background', 1500, 'info'); } }); @@ -6640,7 +7255,7 @@ window.cancelAnimFrame = (function () { activeBgTunerMode = 'onara'; syncBgSlidersDisplay(); applyBackgroundOpacitySettings(window.onaraTuning, true); - if (typeof window.flashMessage === 'function') { + if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Onara Mode Background', 1500, 'info'); } }); @@ -6649,14 +7264,16 @@ window.cancelAnimFrame = (function () { activeBgTunerMode = 'audio'; syncBgSlidersDisplay(); applyBackgroundOpacitySettings(window.audioBgTuning, 'audio'); - if (typeof window.flashMessage === 'function') { + if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Audio Background', 1500, 'info'); } }); backgroundSliders.forEach(s => { - const input = panel.querySelector(`#input-bg-${s.key}`); - const valEl = panel.querySelector(`#val-bg-${s.key}`); + // Bind event to whichever prefix this slider lives in + const pfx = s.basic ? 'bgb-' : 'bga-'; + const input = panel.querySelector(`#input-${pfx}${s.key}`); + const valEl = panel.querySelector(`#val-${pfx}${s.key}`); if (!input || !valEl) return; input.addEventListener('input', () => { const mode = activeBgTunerMode; @@ -6682,6 +7299,7 @@ window.cancelAnimFrame = (function () { valEl.textContent = `${displayVal}${s.unit || ''}`; } + BgState.invalidateTuning(); applyBackgroundOpacitySettings(targetObj, mode === 'audio' ? 'audio' : (mode === 'onara')); try { @@ -7275,6 +7893,16 @@ window.cancelAnimFrame = (function () { const tunerTab = e.target.closest('#sidebar-tab-tuner, [data-tab="tuner"]'); if (tunerTab) { initAudioTunerUI(); + const inOnara = (typeof isCurrentlyInOnara === 'function') ? isCurrentlyInOnara() : document.body.classList.contains('onara-modal-open'); + const expectedBtnId = inOnara + ? '#bg-mode-btn-onara' + : ((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? '#bg-mode-btn-audio' : '#bg-mode-btn-standard'); + const btn = document.querySelector(expectedBtnId); + if (btn && !btn.classList.contains('active')) { + window._bgModeAutoSwitch = true; + btn.click(); + window._bgModeAutoSwitch = false; + } } }); @@ -7561,7 +8189,7 @@ window.cancelAnimFrame = (function () { } // Cleanup existing visualizer - if (visualizerRafId) window.cancelAnimFrame(visualizerRafId); + if (visualizerRafId) { window.cancelAnimFrame(visualizerRafId); visualizerRafId = null; } document.querySelectorAll("canvas.audio-visualizer").forEach(c => c.remove()); const canvas = document.createElement("canvas"); @@ -7639,11 +8267,13 @@ window.cancelAnimFrame = (function () { let audioCoverImg = null; const setupCoverImage = () => { - const coverEl = document.querySelector("#f0ck-audio-cover"); - if (coverEl && coverEl.src && !coverEl.src.endsWith('#') && !coverEl.src.includes('audio.webp') && !coverEl.src.includes('200.gif')) { + const v0ckContainer = audioElement ? (audioElement.closest('.v0ck, .album-audio-wrapper, .embed-responsive') || audioElement.parentElement) : null; + const currentCircle = v0ckContainer ? v0ckContainer.querySelector('.audio-cover-circle') : document.querySelector('.audio-cover-circle'); + const coverUrl = getAudioCoverUrl(currentCircle, audioElement); + if (coverUrl) { audioCoverImg = new Image(); audioCoverImg.crossOrigin = 'anonymous'; - audioCoverImg.src = coverEl.src; + audioCoverImg.src = coverUrl; } else { audioCoverImg = null; } @@ -7660,6 +8290,7 @@ window.cancelAnimFrame = (function () { let lastBeatJumpTime = 0; const draw = (data) => { + window._rafFrameId = (window._rafFrameId || 0) + 1; ctx.clearRect(0, 0, canvas.width, canvas.height); const count = analyser.frequencyBinCount; const cfg = (typeof window.getEffectiveBackgroundTuning === 'function' ? window.getEffectiveBackgroundTuning() : null) || window.audioVisualizerTuning || DEFAULT_AUDIO_TUNING; @@ -7778,7 +8409,12 @@ window.cancelAnimFrame = (function () { // Animate the music note and/or cover art circle to react dynamically to the music const v0ckContainer = audioElement.closest('.v0ck, .album-audio-wrapper, .embed-responsive') || audioElement.parentElement; - const ph = v0ckContainer ? v0ckContainer.querySelector('.sidebar-media-placeholder.audio') : document.querySelector('.album-gallery-container.is-audio-active .sidebar-media-placeholder.audio, .sidebar-media-placeholder.audio'); + let ph = v0ckContainer + ? v0ckContainer.querySelector(':scope > .sidebar-media-placeholder.audio, .sidebar-media-placeholder.audio') + : document.querySelector('.album-gallery-container.is-audio-active .sidebar-media-placeholder.audio, #onara-item-mount .sidebar-media-placeholder.audio, .item-view .sidebar-media-placeholder.audio, .v0ck .sidebar-media-placeholder.audio'); + if (ph && ph.closest('.thumb, .album-thumb-item, .item-preview, .sidebar-activity-item, .recent-comment-media, .recent-comment-item, .posts, .user-posts-container')) { + ph = null; + } if (!noteIcon || !noteIcon.isConnected) { noteIcon = ph ? ph.querySelector('i') : null; } @@ -7802,7 +8438,67 @@ window.cancelAnimFrame = (function () { const solidCol = cfg.coverColor || '#000000'; const bgCol = coverOp <= 0.001 ? 'transparent' : (coverOp >= 0.999 ? solidCol : `color-mix(in srgb, ${solidCol} ${Math.round(coverOp * 100)}%, transparent)`); - if (Number(cfg.solidCover) === 1) { + const showEye = cfg.showCoverInEye !== undefined + ? Number(cfg.showCoverInEye) === 1 + : (cfg.showCoverArt !== undefined ? Number(cfg.showCoverArt) === 1 : Number(cfg.solidCover) === 0); + const showBg = Number(cfg.showCoverInBg) === 1; + + let targetCoverUrl = null; + if (coverCircle._origBgImage && coverCircle._origBgImage !== 'none') { + targetCoverUrl = coverCircle._origBgImage; + } else { + const url = getAudioCoverUrl(coverCircle, audioElement); + if (url) { + targetCoverUrl = `url("${url}")`; + coverCircle._origBgImage = targetCoverUrl; + } + } + + // Background Cover Art + let backdrop = ph ? ph.querySelector('.audio-cover-backdrop') : null; + if (showBg && targetCoverUrl) { + if (ph && !backdrop) { + backdrop = document.createElement('div'); + backdrop.className = 'audio-cover-backdrop'; + ph.insertBefore(backdrop, ph.firstChild); + } + if (backdrop) { + if (backdrop.style.backgroundImage !== targetCoverUrl) { + backdrop.style.backgroundImage = targetCoverUrl; + } + const bgOp = cfg.bgCoverOpacity !== undefined ? Math.min(1, Math.max(0, Number(cfg.bgCoverOpacity))) : 0.45; + const bgBlur = cfg.bgCoverBlur !== undefined ? Math.max(0, Number(cfg.bgCoverBlur)) : 0; + const bgScale = cfg.bgCoverScale !== undefined ? Number(cfg.bgCoverScale) : 100; + const bgPosX = cfg.bgCoverPosX !== undefined ? Number(cfg.bgCoverPosX) : 50; + const bgPosY = cfg.bgCoverPosY !== undefined ? Number(cfg.bgCoverPosY) : 50; + + backdrop.style.opacity = String(bgOp); + backdrop.style.filter = bgBlur > 0 ? `blur(${bgBlur}px)` : 'none'; + backdrop.style.backgroundSize = bgScale === 100 ? 'cover' : `${bgScale}%`; + backdrop.style.backgroundPosition = `${bgPosX}% ${bgPosY}%`; + backdrop.style.display = 'block'; + } + } else if (backdrop && backdrop.style.display !== 'none') { + backdrop.style.backgroundImage = 'none'; + backdrop.style.opacity = '0'; + backdrop.style.display = 'none'; + } + + // Eye / Disc Cover Art + const imgScale = cfg.coverImageScale !== undefined ? Number(cfg.coverImageScale) : 100; + const imgPanX = cfg.coverImagePosX !== undefined ? Number(cfg.coverImagePosX) : 0; + const imgPanY = cfg.coverImagePosY !== undefined ? Number(cfg.coverImagePosY) : 0; + + if (showEye && targetCoverUrl) { + if (coverCircle.style.backgroundImage !== targetCoverUrl) { + coverCircle.style.backgroundImage = targetCoverUrl; + } + coverCircle.style.backgroundColor = bgCol; + coverCircle.style.backgroundSize = imgScale === 100 ? 'cover' : `${imgScale}%`; + coverCircle.style.backgroundPosition = `calc(50% + ${imgPanX}%) calc(50% + ${imgPanY}%)`; + if (ph && !ph.classList.contains('has-cover')) ph.classList.add('has-cover'); + if (noteIcon && noteIcon.style.display !== 'none') noteIcon.style.display = 'none'; + } else { if (coverCircle.style.backgroundImage && coverCircle.style.backgroundImage !== 'none' && !coverCircle._origBgImage) { coverCircle._origBgImage = coverCircle.style.backgroundImage; } @@ -7810,12 +8506,12 @@ window.cancelAnimFrame = (function () { coverCircle.style.backgroundImage = 'none'; } coverCircle.style.backgroundColor = bgCol; - } else if (coverCircle._origBgImage && coverCircle.style.backgroundImage === 'none') { - coverCircle.style.backgroundImage = coverCircle._origBgImage; - coverCircle.style.backgroundColor = bgCol; - } else if (!coverCircle._origBgImage && (!coverCircle.style.backgroundImage || coverCircle.style.backgroundImage === 'none')) { - coverCircle.style.backgroundImage = 'none'; - coverCircle.style.backgroundColor = bgCol; + coverCircle.style.backgroundSize = 'cover'; + coverCircle.style.backgroundPosition = 'center'; + if (ph && !showBg && ph.classList.contains('has-cover')) ph.classList.remove('has-cover'); + if (noteIcon) { + noteIcon.style.display = Number(cfg.innerHideNote) === 1 ? 'none' : ''; + } } let innerCanvas = coverCircle.querySelector('.audio-eye-inner-canvas'); @@ -8063,13 +8759,12 @@ window.cancelAnimFrame = (function () { let sqImg = sqCanvas._sqImg; let sqImgSrc = ''; if (coverCircle) { - const bi = coverCircle.style.backgroundImage || ''; + const bi = coverCircle.style.backgroundImage || coverCircle._origBgImage || ''; const bm = bi.match(/url\(["']?([^"')]+)["']?\)/); if (bm) sqImgSrc = bm[1]; } - if (!sqImgSrc && ph) { - const imgEl = ph.querySelector('img:not([src$=".svg"])'); - if (imgEl && imgEl.src) sqImgSrc = imgEl.src; + if (!sqImgSrc) { + sqImgSrc = getAudioCoverUrl(coverCircle, audioElement) || ''; } if (sqImgSrc && sqCanvas._sqImgSrc !== sqImgSrc) { const ni = new Image(); @@ -8410,8 +9105,10 @@ window.cancelAnimFrame = (function () { coverCircle._kickLunge *= 0.78; // fast decay const lungeBurst = Math.min(0.12, coverCircle._kickLunge); - const curX = (curEyeX + driftOffX).toFixed(1); - const curY = (curEyeY + driftOffY + bobY).toFixed(1); + const offX = Number(cfg.coverPosX || 0); + const offY = Number(cfg.coverPosY || 0); + const curX = (curEyeX + driftOffX + offX).toFixed(1); + const curY = (curEyeY + driftOffY + bobY + offY).toFixed(1); const curTiltX = (coverCircle._eyeTiltX || 0).toFixed(1); const curTiltY = (coverCircle._eyeTiltY || 0).toFixed(1); const eyeFov = Math.round(cfg.eyeFOV !== undefined ? cfg.eyeFOV : 600); @@ -8419,38 +9116,14 @@ window.cancelAnimFrame = (function () { const eyeTransform = `translate(calc(-50% + ${curX}px), calc(-50% + ${curY}px)) perspective(${eyeFov}px) translateZ(${eyeZPos}px) rotateX(${curTiltX}deg) rotateY(${curTiltY}deg) rotateZ(${bankDeg.toFixed(2)}deg) scale(${(circleScale + lungeBurst).toFixed(3)})`; coverCircle.style.transform = eyeTransform; - // ── Motion trail ghosts ─────────────────────────────────────────────────── - // Ring buffer of past transforms; ghosts replay them with opacity + blur - const TRAIL_N = 5; - if (!coverCircle._trailBuf) coverCircle._trailBuf = []; - if (!coverCircle._ghosts) coverCircle._ghosts = []; - // Push current state onto the front - coverCircle._trailBuf.unshift(eyeTransform); - if (coverCircle._trailBuf.length > TRAIL_N + 1) coverCircle._trailBuf.pop(); - // Speed factor: how intense is the motion this frame (0..1) - const speedFactor = Math.min(1, Math.abs(bankDeg) / 10 + lungeBurst / 0.06 + Math.abs(vpVelX) * 0.04); - const coverBg = coverCircle.style.backgroundImage || ''; - // Per-ghost opacity and blur ramps - const ghostOpacity = [0.22, 0.14, 0.08, 0.04, 0.02]; - const ghostBlur = [3, 5, 8, 11, 15 ]; - for (let gi = 0; gi < TRAIL_N; gi++) { - // Create ghost element on first encounter - if (!coverCircle._ghosts[gi]) { - const g = document.createElement('div'); - g.className = 'eye-trail-ghost'; - ph.insertBefore(g, coverCircle); - coverCircle._ghosts[gi] = g; - } - const ghost = coverCircle._ghosts[gi]; - const pastTf = coverCircle._trailBuf[gi + 1]; - if (pastTf && speedFactor > 0.02) { - ghost.style.transform = pastTf; - ghost.style.backgroundImage = coverBg; - ghost.style.opacity = (speedFactor * ghostOpacity[gi]).toFixed(3); - ghost.style.filter = `blur(${ghostBlur[gi]}px)`; - } else { - ghost.style.opacity = '0'; - } + // Ensure no cover art is projected into the surrounding glow/halo + if (coverCircle._ghosts && coverCircle._ghosts.length) { + coverCircle._ghosts.forEach(g => { if (g) g.remove(); }); + coverCircle._ghosts = []; + } + if (ph) { + const oldGhosts = ph.querySelectorAll('.eye-trail-ghost'); + if (oldGhosts.length) oldGhosts.forEach(g => g.remove()); } const eyeNoGlow = Number(cfg.eyeDisableGlow !== undefined ? cfg.eyeDisableGlow : 0) === 1; @@ -8486,8 +9159,10 @@ window.cancelAnimFrame = (function () { const dynamicOp = Math.min(1.0, Math.max(0.0, baseOp + pulse * (1.0 - baseOp * 0.4))); bgEl.style.opacity = dynamicOp.toFixed(3); - const cX = coverCircle && coverCircle._eyeX ? Number(coverCircle._eyeX) : 0; - const cY = coverCircle && coverCircle._eyeY ? Number(coverCircle._eyeY) : 0; + const offX = Number(cfg.coverPosX || 0); + const offY = Number(cfg.coverPosY || 0); + const cX = (coverCircle && coverCircle._eyeX ? Number(coverCircle._eyeX) : 0) + offX; + const cY = (coverCircle && coverCircle._eyeY ? Number(coverCircle._eyeY) : 0) + offY; // Spirit drift: shift gradient focal point with the tunnel VP const bgDriftX = (sqCanvas && sqCanvas._vpX) ? sqCanvas._vpX * 0.5 : 0; const bgDriftY = (sqCanvas && sqCanvas._vpY) ? sqCanvas._vpY * 0.5 : 0; @@ -9272,8 +9947,10 @@ window.cancelAnimFrame = (function () { const _sqCv2 = ph && ph.querySelector('.audio-sq-tunnel'); const driftOffX2 = (_sqCv2 && _sqCv2._vpX) ? _sqCv2._vpX * 0.5 : 0; const driftOffY2 = (_sqCv2 && _sqCv2._vpY) ? _sqCv2._vpY * 0.5 : 0; - const curX = (curEyeX2 + driftOffX2).toFixed(1); - const curY = (curEyeY2 + driftOffY2).toFixed(1); + const offX = Number(cfg.coverPosX || 0); + const offY = Number(cfg.coverPosY || 0); + const curX = (curEyeX2 + driftOffX2 + offX).toFixed(1); + const curY = (curEyeY2 + driftOffY2 + offY).toFixed(1); const curTiltX = (coverCircle._eyeTiltX || 0).toFixed(1); const curTiltY = (coverCircle._eyeTiltY || 0).toFixed(1); const eyeFov2 = Math.round(cfg.eyeFOV !== undefined ? cfg.eyeFOV : 600); @@ -9430,7 +10107,7 @@ window.cancelAnimFrame = (function () { const pulseVal = Math.min(1.0, aEnv.pulse * reactScale); const reactiveOpts = { pulse: isPaused ? 0 : pulseVal, - intensityMult: isPaused ? 0.40 : (0.75 + (aEnv.pulse * 0.85 + aEnv.kick * 0.55) * reactScale), + intensityMult: isPaused ? 0.40 : Math.min(1.4, 0.75 + (aEnv.pulse * 0.85 + aEnv.kick * 0.55) * reactScale), spreadMult: isPaused ? 0.75 : (0.85 + aEnv.pulse * 0.45 * reactScale), coreColor: null }; @@ -9438,14 +10115,17 @@ window.cancelAnimFrame = (function () { if (!bgCanvas._audioAmbientCount) bgCanvas._audioAmbientCount = 0; bgCanvas._audioAmbientCount++; if (bgCanvas._audioAmbientCount % 2 === 0 || !mediaAmbientState.hasSampled) { + // Use cover art if available; otherwise fall back to accent color to match the visualizer. let sampled = null; - if (audioCoverImg && audioCoverImg.complete && audioCoverImg.naturalWidth > 0) { + const allowBgCoverSampling = Number(cfg.showCoverInBg) === 1; + if (allowBgCoverSampling && audioCoverImg && audioCoverImg.complete && audioCoverImg.naturalWidth > 0) { sampled = sampleMediaSpatialColors(audioCoverImg); } - if (!sampled && sourceCanvas) { - sampled = sampleMediaSpatialColors(sourceCanvas); + if (!sampled) { + const accentRgb = parseCssColorToRgb(accent); + if (accentRgb) sampled = { top: accentRgb, bottom: accentRgb }; } - if (sampled && (sampled.top[0] > 30 || sampled.top[1] > 30 || sampled.top[2] > 45 || sampled.bottom[0] > 30 || sampled.bottom[1] > 30 || sampled.bottom[2] > 45)) { + if (sampled) { // Modulate sampled colors dynamically with real-time frequency energy const modTop = [ Math.min(255, Math.max(0, Math.round(sampled.top[0] * (1.0 + (aEnv.mid + aEnv.high) * 0.50 * reactScale)))), @@ -9459,16 +10139,8 @@ window.cancelAnimFrame = (function () { ]; setAmbientTargetColors({ top: modTop, bottom: modBot }); mediaAmbientState.hasSampled = true; - } else { - // Synthesize harmonic audio colors from beat hue or accent - const baseHue = (Number(cfg.enableBeatHue) === 1 ? currentBeatHue : (currentBeatHue || 110)) % 360; - const topHue = (baseHue + 45) % 360; - const botHue = baseHue; - const modTop = parseCssColorToRgb(`hsl(${Math.round(topHue)}, 100%, ${Math.round(50 + (aEnv.mid + aEnv.high) * 20 * reactScale)}%)`); - const modBot = parseCssColorToRgb(`hsl(${Math.round(botHue)}, 100%, ${Math.round(45 + aEnv.kick * 25 * reactScale)}%)`); - setAmbientTargetColors({ top: modTop, bottom: modBot }); - mediaAmbientState.hasSampled = true; } + // If sampled is null or too dark, canvas stays dark — no green flash. } const current = updateAmbientLerp(0.08); @@ -11107,7 +11779,7 @@ window.cancelAnimFrame = (function () { const isGridPage = !!document.querySelector('.index-layout-wrapper, .index-container, .posts'); const isModalOpen = document.body.classList.contains('onara-modal-open'); - const useOnara = isOnaraActive() && (isGridPage || isModalOpen); + const useOnara = isOnaraActive(); if (useOnara && !isModalOpen) { if (options.skipPush || isItemPath(window.location.href)) { @@ -12057,9 +12729,9 @@ window.cancelAnimFrame = (function () { loadItemAjax(targetUrl, true, { transition: 'fade-zoom' }); - // Background grid sync + // Background grid sync: pass targetUrl and null so actual page is looked up and synced if (isOnaraActive()) { - syncOnaraBackgroundGrid(pick, `/${pick}`, 1, pick); + syncOnaraBackgroundGrid(pick, targetUrl, null, pick); } return; } @@ -12130,7 +12802,7 @@ window.cancelAnimFrame = (function () { } loadItemAjax(targetUrl, true, { transition: 'fade-zoom', prefetchedData: data }); if (isOnaraActive() && data.id) { - syncOnaraBackgroundGrid(data.id, `/${targetKey}`, data.page || 1, targetKey); + syncOnaraBackgroundGrid(data.id, targetUrl, data.page || null, targetKey); } } else { // No items found — restore UI, don't redirect @@ -12158,7 +12830,7 @@ window.cancelAnimFrame = (function () { const isModalOpen = document.body.classList.contains('onara-modal-open'); const isGridPage = !!document.querySelector('.index-layout-wrapper, .index-container, .posts'); - const useOnara = isOnaraActive() && (isGridPage || isModalOpen); + const useOnara = isOnaraActive(); if (direction) { const outEls = document.querySelectorAll('.content, ._204863, .item_title'); @@ -12596,7 +13268,7 @@ window.cancelAnimFrame = (function () { sfw: { label: 'SFW', text: 'sfw', badgeCls: 'badge-success', isCls: 'is-sfw', toast: '🛡 SFW' }, nsfw: { label: 'NSFW', text: 'nsfw', badgeCls: 'badge-danger', isCls: 'is-nsfw', toast: '🔥 NSFW' }, nsfl: { label: 'NSFL', text: 'nsfl', badgeCls: 'badge-nsfl', isCls: 'is-nsfl', toast: '💀 NSFL' }, - untagged: { label: '?', text: 'untagged', badgeCls: 'badge-untagged', isCls: 'is-untagged', toast: '❓ Untagged' } + untagged: { label: '?', text: 'untagged', badgeCls: 'badge-untagged', isCls: 'is-untagged', toast: '❓ Unrated' } }; const info = mapping[nextRating]; const nextLabel = info.label; @@ -17522,7 +18194,7 @@ class NotificationSystem { untaggedSpan.dataset.rating = 'untagged'; const lbl = document.createElement('span'); lbl.className = 'rating-label'; - lbl.textContent = 'untagged'; + lbl.textContent = 'unrated'; untaggedSpan.appendChild(lbl); fragment.appendChild(untaggedSpan); } @@ -17918,7 +18590,7 @@ class NotificationSystem { if (window.activeMode === 0 && data.tag_id !== 1) return; // SFW mode, item is not SFW if (window.activeMode === 1 && data.tag_id !== 2) return; // NSFW mode, item is not NSFW if (window.activeMode === 4 && data.tag_id != window.f0ckSession?.nsfl_tag_id) return; // NSFL mode, item is not NSFL - if (window.activeMode === 2) return; // Untagged mode, new uploads are never untagged + if (window.activeMode === 2) return; // Unrated mode, new uploads are never unrated } // 2. Dispatch for external listeners (e.g. sidebar-activity) @@ -21161,7 +21833,7 @@ document.addEventListener('click', (e) => { if (window.hideAllModals) window.hideAllModals(); }); - // Global click interceptor for dynamically blurred thumbnails (NSFW, NSFL, SFW, Untagged) + // Global click interceptor for dynamically blurred thumbnails (NSFW, NSFL, SFW, Unrated) document.addEventListener('click', (e) => { // If no blur options are active, skip entirely const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index 97303c5..6eaec10 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -556,7 +556,7 @@ } // ── Filter Presets CRUD ─────────────────────────────────────────────────── - const PRESETS_LABELS = { mode: { 0: 'SFW', 1: 'NSFW', 2: 'Untagged', 3: 'All', 4: 'NSFL' } }; + const PRESETS_LABELS = { mode: { 0: 'SFW', 1: 'NSFW', 2: 'Unrated', 3: 'All', 4: 'NSFL' } }; function getPresets() { try { return JSON.parse(localStorage.getItem(PRESETS_KEY) || '[]'); } catch { return []; } } function savePresets(arr) { localStorage.setItem(PRESETS_KEY, JSON.stringify(arr)); } @@ -3060,7 +3060,7 @@ if (addTagSendBtn) addTagSendBtn.addEventListener('click', submitTag); // ── Filter panel ────────────────────────────────────────────────────────── - const modeLabels = { 0: 'SFW', 1: 'NSFW', 2: 'Untagged', 3: 'All', 4: 'NSFL' }; + const modeLabels = { 0: 'SFW', 1: 'NSFW', 2: 'Unrated', 3: 'All', 4: 'NSFL' }; filterOpenBtn.addEventListener('click', () => { pending = { ...applied, tags: [...applied.tags] }; syncPanelUI(); diff --git a/public/s/js/settings.js b/public/s/js/settings.js index 1240e4b..e0eab84 100644 --- a/public/s/js/settings.js +++ b/public/s/js/settings.js @@ -1498,7 +1498,7 @@ } else { document.documentElement.classList.remove('blur-untagged-active'); } - showStatus(enabled ? 'Untagged blurring enabled!' : 'Untagged blurring disabled!', 'success'); + showStatus(enabled ? 'Unrated blurring enabled!' : 'Unrated blurring disabled!', 'success'); }); } const blurDetailToggle = document.getElementById('blur_detail_toggle'); @@ -1596,10 +1596,16 @@ onaraEnabledToggle.checked = stored !== null ? stored === 'true' : sessionOnara; // Reflect current state into window.onara so isOnaraActive() sees it window.onara = onaraEnabledToggle.checked; + try { + document.cookie = `f0ck_onara=${onaraEnabledToggle.checked ? '1' : '0'}; path=/; max-age=31536000; SameSite=Lax`; + } catch {} onaraEnabledToggle.addEventListener('change', () => { const enabled = onaraEnabledToggle.checked; localStorage.setItem(LS_KEY, String(enabled)); + try { + document.cookie = `f0ck_onara=${enabled ? '1' : '0'}; path=/; max-age=31536000; SameSite=Lax`; + } catch {} window.onara = enabled; showStatus('Onara viewer ' + (enabled ? 'enabled' : 'disabled') + '.', 'success'); }); diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js index f438618..87c681a 100644 --- a/public/s/js/sidebar-activity.js +++ b/public/s/js/sidebar-activity.js @@ -1272,14 +1272,19 @@ const isTagFeed = options.isTagFeed || false; const tagContext = options.tag || null; + const isFavsFeed = options.isFavsFeed || false; + const favsUser = options.favsUser || null; const isStrict = isStrictMode(); const strictSuffix = (isTagFeed && isStrict) ? '?strict=1' : ''; const targetSubHash = video.target_subf0ck_slug ? `#${video.target_subf0ck_slug}` : ''; - const targetHref = (isTagFeed && tagContext) - ? `/tag/${encodeURIComponent(tagContext).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${videoKey}${strictSuffix}${targetSubHash}` - : `/${videoKey}${targetSubHash}`; + const targetHref = (isFavsFeed && favsUser) + ? `/user/${encodeURIComponent(favsUser)}/favs/${videoKey}${targetSubHash}` + : (isTagFeed && tagContext) + ? `/tag/${encodeURIComponent(tagContext).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${videoKey}${strictSuffix}${targetSubHash}` + : `/${videoKey}${targetSubHash}`; const activeClass = options.isActive ? ' active-tag-item' : ''; + return ` @endif -@if(onara) +@if(onara || is_onara_item) +