fix(jellyfin): scope logo bocken.org link to home header only

The previous wrap-in-anchor approach leaked the bocken.org link onto
unrelated page titles because Jellyfin reuses the same <h3> across
navigations and only swaps its class between .pageTitleWithDefaultLogo
and .pageTitle. Switch to click delegation so the redirect fires only
when the clicked element currently carries the logo class. Also unwrap
any legacy anchor wrappers on first mutation, and bump cursor/filter
hover styles to !important so they survive Jellyfin's own h3 rules.
This commit is contained in:
2026-05-02 13:10:24 +02:00
parent 2e8685d02b
commit ccca1a7959
2 changed files with 46 additions and 38 deletions
+5 -6
View File
@@ -233,15 +233,14 @@ progress[value]::-moz-progress-bar {
background-image: url(https://bocken.org/static/css/logos/logo_full_light.png);
}
/* Bocken logo link */
.bocken-logo-link {
display: flex;
text-decoration: none;
/* Bocken logo (clickable via JS click delegation) */
.skinHeader:not(.osdHeader) .pageTitleWithDefaultLogo {
cursor: pointer !important;
transition: filter 150ms;
}
.bocken-logo-link:hover .pageTitleWithDefaultLogo {
.skinHeader:not(.osdHeader) .pageTitleWithDefaultLogo:hover {
/* Tint white logo to Nord lightblue (#81A1C1) */
filter: brightness(0) saturate(100%) invert(61%) sepia(52%) saturate(212%) hue-rotate(169deg) brightness(92%) contrast(94%);
filter: brightness(0) saturate(100%) invert(61%) sepia(52%) saturate(212%) hue-rotate(169deg) brightness(92%) contrast(94%) !important;
}
/* Toast notification */
+41 -32
View File
@@ -136,52 +136,61 @@
/* ═══════════════════════════════════════════
2. Make Bocken logo link to bocken.org
Uses click delegation so we don't wrap the
DOM — Jellyfin reuses the same <h3> across
navigations and swaps its class between
.pageTitleWithDefaultLogo (home) and
.pageTitle (collection pages), which would
leak the wrapper onto unrelated titles.
═══════════════════════════════════════════ */
(function () {
function wrapLogo() {
var logo = document.querySelector('.pageTitleWithDefaultLogo');
if (!logo || logo.dataset.bockenLinked) return;
logo.dataset.bockenLinked = '1';
var isMobileApp = /wv\)|Jellyfin Mobile/.test(navigator.userAgent);
var isMobileApp = /wv\)|Jellyfin Mobile/.test(navigator.userAgent);
/* One-time cleanup of legacy <a.bocken-logo-link> wrappers
left behind by older versions of this script. */
function unwrapLegacy() {
document.querySelectorAll('a.bocken-logo-link').forEach(function (a) {
while (a.firstChild) a.parentNode.insertBefore(a.firstChild, a);
a.remove();
});
document.querySelectorAll('[data-bocken-linked]').forEach(function (el) {
delete el.dataset.bockenLinked;
});
}
var link = document.createElement('a');
link.href = 'https://bocken.org';
link.className = 'bocken-logo-link';
link.title = 'bocken.org';
function openBocken() {
if (isMobileApp) {
link.addEventListener('click', function (e) {
e.preventDefault();
navigator.clipboard.writeText('https://bocken.org').then(function () {
var toast = document.createElement('div');
toast.className = 'bocken-toast';
toast.textContent = 'Link copied — open in browser';
document.body.appendChild(toast);
setTimeout(function () { toast.classList.add('bocken-toast-hide'); }, 2000);
setTimeout(function () { toast.remove(); }, 2500);
});
navigator.clipboard.writeText('https://bocken.org').then(function () {
var toast = document.createElement('div');
toast.className = 'bocken-toast';
toast.textContent = 'Link copied — open in browser';
document.body.appendChild(toast);
setTimeout(function () { toast.classList.add('bocken-toast-hide'); }, 2000);
setTimeout(function () { toast.remove(); }, 2500);
});
} else {
window.location.href = 'https://bocken.org';
}
logo.parentNode.insertBefore(link, logo);
link.appendChild(logo);
}
var pending = null;
function schedule() {
if (pending) return;
pending = setTimeout(function () {
pending = null;
wrapLogo();
}, 300);
}
document.addEventListener('click', function (e) {
var logo = e.target.closest(
'.skinHeader:not(.osdHeader) .pageTitleWithDefaultLogo'
);
if (!logo) return;
e.preventDefault();
e.stopPropagation();
openBocken();
}, true);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', schedule);
document.addEventListener('DOMContentLoaded', unwrapLegacy);
} else {
schedule();
unwrapLegacy();
}
new MutationObserver(schedule).observe(document.body, {
/* Catch wrappers that re-appear from cached SPA state */
new MutationObserver(unwrapLegacy).observe(document.body, {
childList: true,
subtree: true,
});