2026-05-08 03:44:24 +01:00
|
|
|
<!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) */
|
2026-05-08 16:05:30 +01:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2026-05-08 17:04:03 +01:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2026-05-08 03:44:24 +01:00
|
|
|
</style><script>/* ARRFLIX-SHIM-BEGIN */
|
2026-05-08 03:25:16 +01:00
|
|
|
(function(){
|
|
|
|
|
var TITLE = 'ARRFLIX';
|
|
|
|
|
var BARE_RE = /^Jellyfin$/i;
|
2026-05-08 17:04:03 +01:00
|
|
|
/* === 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 === */
|
2026-05-08 03:25:16 +01:00
|
|
|
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){}
|
|
|
|
|
}
|
2026-05-08 03:51:48 +01:00
|
|
|
function nukeSettings(){
|
|
|
|
|
try {
|
2026-05-08 17:04:03 +01:00
|
|
|
var nodes = document.querySelectorAll('a[href*="mypreferencesmenu"], [to*="mypreferencesmenu"]');
|
2026-05-08 03:51:48 +01:00
|
|
|
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){}
|
|
|
|
|
}
|
2026-05-08 03:25:16 +01:00
|
|
|
function start(){
|
2026-05-08 17:04:03 +01:00
|
|
|
lockTitle(); lockFavicon(); nukeSettings(); pinLocale();
|
2026-05-08 03:25:16 +01:00
|
|
|
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){}
|
2026-05-08 03:51:48 +01:00
|
|
|
try {
|
|
|
|
|
if (document.body && window.MutationObserver) {
|
|
|
|
|
new MutationObserver(function(){ nukeSettings(); }).observe(document.body, { childList:true, subtree:true });
|
|
|
|
|
}
|
|
|
|
|
} catch(e){}
|
2026-05-08 03:25:16 +01:00
|
|
|
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();
|
2026-05-08 03:51:48 +01:00
|
|
|
nukeSettings();
|
2026-05-08 17:04:03 +01:00
|
|
|
pinLocale();
|
2026-05-08 03:25:16 +01:00
|
|
|
}, 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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
|