ARRFLIX/web-overrides/index.html

241 lines
64 KiB
HTML
Raw Normal View History

<!doctype html><html class="preload" dir="ltr"><head><style>/* ARRFLIX critical-path styles render first to avoid pre-bundle flash */
:root {
--primary-background-color: #000000;
--background-color: #000000;
}
html, body, .preload, .skinBody, .skinHeader, #reactRoot, .mainAnimatedPages {
background-color: #000000 !important;
color: #ffffff !important;
}
/* Login form's pre-bundle layout uses MUI default blue button — pre-paint Netflix red */
.raised, .button-submit, .emby-button[type=submit], button[type=submit] {
background-color: #E50914 !important;
color: #ffffff !important;
}
/* Hide pre-bundle Jellyfin logo + replace with .splashLogo (already swapped to ARRFLIX in this index.html) */
/* ARRFLIX dev V2 (2026-05-08) — hide drawer Settings entry from non-admins.
Drawer Settings link is .btnSettings / [data-itemid="settings"] with href="#".
Old href*="mypreferencesmenu" rules in CustomCss never matched. */
a.btnSettings,
.navMenuOption.btnSettings,
[data-itemid="settings"] {
display: none !important;
}
/* ARRFLIX english-lockdown (2026-05-08) — hide every language-switcher in the UI.
The shim pins UICulture to en-US in JS; these rules remove the controls so the
user can't even see they exist. !important survives Jellyfin upgrades that
re-skin the prefs page. Belt; the JS user-config save hook is the suspenders. */
/* User profile prefs — Display Language dropdown */
select[name="language"],
select#selectLanguage,
select[is="emby-select"][name="language"],
.languageSelector,
[data-role="language"],
.fldDisplayLanguage,
.fieldDisplayLanguage,
.displayLanguageField,
/* Login form locale picker (some Jellyfin builds expose a langSelect on /web/#/login.html) */
#divLoginLanguage,
.loginLanguage,
select[name="loginLanguage"],
/* Header / userMenu locale flag-or-text widget (Cineplex theme adds a .localeButton on some builds) */
.headerLanguageButton,
.localeButton,
.localeFlag,
button.btnLanguage,
[data-action="language"],
/* Belt: any <select> that contains a non-English option (de/fr/es/...) — hides
future Jellyfin builds that rename the field but keep the option list */
.formSection select option[value="de"],
.formSection select option[value="fr"],
.formSection select option[value="es"],
.formSection select option[value="ru"] {
display: none !important;
}
/* Hide the parent <select> + its label when its option list contains de-DE.
:has() is supported in Chromium 105+ / Firefox 121+ / Safari 15.4+ — same
support floor as the existing drawer-Settings :has() selectors. */
.fldSelectContainer:has(select option[value="de-DE"]),
.formSection:has(> label[for="selectLanguage"]),
label[for="selectLanguage"],
label[for="language"] {
display: none !important;
}
</style><script>/* ARRFLIX-SHIM-BEGIN */
(function(){
var TITLE = 'ARRFLIX';
var BARE_RE = /^Jellyfin$/i;
/* === English-lockdown (synchronous, runs before Jellyfin bundle) ===
Pins UI locale to en-US so the SPA never reads navigator.language
or the user's stored preference. Belt-and-braces against:
- localStorage keys the SPA reads on boot
- navigator.language / navigator.languages getters
- fetch / XHR Accept-Language header (best-effort; most browsers
block JS from setting it, but Jellyfin sometimes does)
- user-config save round-trip (rewrite UICulture → en-US before send) */
try {
var LS_KEYS = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
for (var i=0;i<LS_KEYS.length;i++){
try { localStorage.setItem(LS_KEYS[i], 'en-US'); } catch(e){}
}
} catch(e){}
try {
var EN = ['en-US','en'];
Object.defineProperty(Navigator.prototype, 'language', { get:function(){return 'en-US';}, configurable:true });
Object.defineProperty(Navigator.prototype, 'languages', { get:function(){return EN.slice();}, configurable:true });
} catch(e){
/* fallback for engines that won't let us redefine on the prototype */
try { Object.defineProperty(navigator, 'language', { get:function(){return 'en-US';}, configurable:true }); } catch(e2){}
try { Object.defineProperty(navigator, 'languages', { get:function(){return ['en-US','en'];}, configurable:true }); } catch(e2){}
}
/* fetch wrapper: strip Accept-Language on outbound requests, and rewrite
any user-config save body so UICulture is pinned to en-US. */
try {
if (window.fetch) {
var _origFetch = window.fetch;
window.fetch = function(input, init){
try {
init = init || {};
/* strip Accept-Language if present on a plain object headers init */
if (init.headers) {
if (init.headers instanceof Headers) {
try { init.headers.delete('Accept-Language'); } catch(e){}
} else if (typeof init.headers === 'object') {
for (var k in init.headers){ if (k && k.toLowerCase() === 'accept-language') { try { delete init.headers[k]; } catch(e){} } }
}
}
/* rewrite user-config save: POST /Users/{id}/Configuration */
var url = (typeof input === 'string') ? input : (input && input.url) || '';
var method = (init.method || (input && input.method) || 'GET').toUpperCase();
if (url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(url) && method === 'POST' && init.body) {
try {
var body = init.body;
if (typeof body === 'string') {
var obj = JSON.parse(body);
if (obj && typeof obj === 'object') {
obj.UICulture = 'en-US';
init.body = JSON.stringify(obj);
}
}
} catch(e){}
}
} catch(e){}
return _origFetch.call(this, input, init);
};
}
} catch(e){}
/* XHR wrapper: strip Accept-Language; rewrite user-config save body. */
try {
if (window.XMLHttpRequest) {
var _open = XMLHttpRequest.prototype.open;
var _setHeader = XMLHttpRequest.prototype.setRequestHeader;
var _send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url){
this.__arrflix_method = (method || 'GET').toUpperCase();
this.__arrflix_url = url || '';
return _open.apply(this, arguments);
};
XMLHttpRequest.prototype.setRequestHeader = function(name, value){
if (name && String(name).toLowerCase() === 'accept-language') return;
return _setHeader.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body){
try {
if (this.__arrflix_url && /\/Users\/[^/]+\/Configuration(\?|$)/.test(this.__arrflix_url) && this.__arrflix_method === 'POST' && typeof body === 'string') {
try {
var obj = JSON.parse(body);
if (obj && typeof obj === 'object') {
obj.UICulture = 'en-US';
body = JSON.stringify(obj);
}
} catch(e){}
}
} catch(e){}
return _send.call(this, body);
};
}
} catch(e){}
/* Re-pin localStorage on every visibility change (SPA may rewrite on user save) */
function pinLocale(){
try {
var L = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture'];
for (var i=0;i<L.length;i++){ try { if (localStorage.getItem(L[i]) !== 'en-US') localStorage.setItem(L[i], 'en-US'); } catch(e){} }
} catch(e){}
}
/* === end english-lockdown synchronous block === */
function getFavicon(){
var l = document.querySelector('link[rel="shortcut icon"], link[rel="icon"]');
return l && l.href ? l.href : null;
}
function lockTitle(){
try {
var t = document.title || '';
if (BARE_RE.test(t)) { document.title = TITLE; return; }
if (/Jellyfin/i.test(t)) {
var cleaned = t.replace(/\s*[-|]\s*Jellyfin\s*$/i, '').replace(/Jellyfin/gi, TITLE);
if (!cleaned) { document.title = TITLE; }
else if (!/ARRFLIX/i.test(cleaned)) { document.title = cleaned + ' - ' + TITLE; }
else { document.title = cleaned; }
}
} catch(e){}
}
function lockFavicon(){
try {
var fav = getFavicon();
if (!fav || fav.indexOf('data:image') !== 0) return;
var icons = document.querySelectorAll('link[rel*="icon"]');
for (var i=0;i<icons.length;i++){ if (icons[i].href !== fav) icons[i].href = fav; }
} catch(e){}
}
function nukeSettings(){
try {
var nodes = document.querySelectorAll('a[href*="mypreferencesmenu"], [to*="mypreferencesmenu"]');
for (var i=0;i<nodes.length;i++){
var el = nodes[i];
var p = el.closest && (el.closest('li, .MuiListItem-root, [role="menuitem"]')) || el;
if (p && p.style && p.style.display !== 'none') p.style.display = 'none';
}
} catch(e){}
}
function start(){
lockTitle(); lockFavicon(); nukeSettings(); pinLocale();
try {
var head = document.head || document.querySelector('head');
if (head && window.MutationObserver) {
new MutationObserver(function(){ lockTitle(); lockFavicon(); }).observe(head, { childList:true, subtree:true, characterData:true, attributes:true, attributeFilter:['href'] });
}
} catch(e){}
try {
if (document.body && window.MutationObserver) {
new MutationObserver(function(){ nukeSettings(); }).observe(document.body, { childList:true, subtree:true });
}
} catch(e){}
setInterval(function(){
var t = document.title || '';
if (BARE_RE.test(t) || /Jellyfin/i.test(t)) lockTitle();
var fav = getFavicon();
if (fav && fav.indexOf('data:image') !== 0) lockFavicon();
nukeSettings();
pinLocale();
}, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start, { once:true });
} else { start(); }
if ('serviceWorker' in navigator) {
try {
navigator.serviceWorker.getRegistrations().then(function(regs){
regs.forEach(function(r){
try {
var url = (r.active && r.active.scriptURL) || '';
if (url.indexOf('serviceworker.js') !== -1) { r.unregister(); }
} catch(e){}
});
}).catch(function(){});
if (window.caches && caches.keys) {
caches.keys().then(function(keys){ keys.forEach(function(k){ caches.delete(k); }); }).catch(function(){});
}
} catch(e){}
}
})();
/* ARRFLIX-SHIM-END */</script><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><link rel="manifest" href="fd4301fdc170fd202474.json"><meta name="format-detection" content="telephone=no"><meta name="msapplication-tap-highlight" content="no"><meta http-equiv="X-UA-Compatibility" content="IE=Edge"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="mobile-web-app-capable" content="yes"><meta name="application-name" content="ARRFLIX"><meta name="robots" content="noindex, nofollow, noarchive"><meta name="referrer" content="no-referrer"><meta id="themeColor" name="theme-color" content="#202020"><link rel="apple-touch-icon" sizes="180x180" href="data:image/png;base64,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