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}
+ ${bgRowsAdvancedHtml}
`;
@@ -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)
@@ -131,6 +131,9 @@
+
@@ -165,6 +168,21 @@
+