# 04 — Theming and Users Status: re-themed 2026-05-08 against `https://arrflix.s8n.ru` (Jellyfin 10.10.3 on nullstone). Active theme: **Cineplex v1.0.6** (Netflix-faithful). Replaced ElegantFin v25.12.31 the same day after a Netflix-fidelity-driven survey. Scope: visual theme, server-side branding, multi-user UX prep, SyncPlay, maintenance/revert. LAN-only constraints preserved (no public-facing changes). --- ## 1. Theme decision: ElegantFin v25.12.31 + ARRFLIX recolor (current) **As of 2026-05-08 (later in the day), the active theme is ElegantFin v25.12.31 with the Netflix-red `#E50914` accent recolored over the default Jellyseerr-blue/violet palette and the ARRFLIX wordmark logo preserved.** See §3e for the migration details and §1.x ("Previous themes") below for the Cineplex history that preceded it. ### Candidates surveyed (2026-05-08) | Theme | Type | Last commit | License | Netflix-fidelity | JF 10.10.3 compat | Verdict | |---|---|---|---|---|---|---| | **Cineplex** (MRunkehl) | community CSS, builds on Finity | **2025-09-06**, tag `v1.0.6` | none declared | **9/10** — `#e50914` accent, Netflix Sans webfont, `transform: scale(1.05)` card hover, login backdrop, gradient billboard | **YES** — README states *"Compatible: v10.10.7 and higher"*; renders on 10.10.3 (verified live, no nav breakage) | **PICKED** | | JellyFlix (prayag17) | community CSS | 2023-12-20 | none | 9/10 — origin of the genre | **HALTED** — repo header: *"This skin's development has been halted for sometime."* Confirmed broken post-10.11. Risky on 10.10.3 too. | rejected | | DarkFlix v5.1 (DevilsDesigns) | fork of JellyFlix | 2024-06 | GPL-3.0 | 8/10 | only states 10.8.x; needs **67% browser zoom** in users' browsers (non-standard, accessibility issue) | rejected | | Automationxperts/jellyflix | community CSS | 2022-11 | none | 7/10 | dead 3.5y, untested on 10.10 | rejected | | **ElegantFin v25.12.31** (lscambo13, previous) | community CSS | 2026-04-30 | GPL-2.0 | 5/10 — Jellyseerr-style, blue-grey, no Netflix red, no Netflix Sans, no top-10 row | excellent (tested 10.11.5) | de-themed — wrong aesthetic for this brief | | Theme Park (jellyfin pack) | multi-app CSS | active | n/a | n/a — no `netflix` preset for jellyfin (only dracula/nord/hotline/plex) | n/a | not applicable | | zombB / NetfliFin / Finetwo | mostly fork-style replacement of jellyfin-web | varies | varies | n/a | requires image swap or JS injector | DQ — violates "pure CSS, no image swap, no plugins" constraint | | Ultrachromic (CTalvio) | community CSS | "selectively maintained" | varies | 6/10 — accent-tunable but no Netflix preset | unknown | not Netflix enough | ### Previous themes The two sub-sections below ("Why Cineplex won", "Tradeoffs", "What it looks like", "Theme history") are kept verbatim from when Cineplex was the active theme (earlier on 2026-05-08, before the ElegantFin migration documented in §3e). They remain useful as the reasoning trail for the final brand brief — Netflix-faithful was the goal, Cineplex was the purest expression of that, and the current ElegantFin + recolor stack is a deliberate tradeoff toward "more polished browsing UI" while keeping the Netflix-red accent. #### Why Cineplex won (historical) 1. **It is actually Netflix.** The CSS literally embeds Netflix Sans (`https://assets.nflxext.com/ffe/siteui/fonts/netflix-sans/v3/...`) and uses Netflix's exact `#E50914` for `--accent` / `--focus-color`. Card hover applies `transform: scale(1.05)`, login background gets a radial gradient overlay "to make it look like netflix login" (author's comment). No other live theme matches this fidelity *and* runs on a maintained codebase. 2. **It targets our Jellyfin series.** Header line of `cineplex.css` reads `Compatible: v10.10.7 and higher`. We're on 10.10.3 — same minor series, ABI-compatible for selectors. Verified live: page loads, navigation works, no broken layouts. 3. **Single `@import` line.** Zero ops overhead. The CSS imports two transitive deps internally (`finity-complete.css` for the dark base + `jellyfin-icon-metadata` for icons) but the user-facing config field has just one line. 4. **Pinned to immutable tag** `@v1.0.6`. jsDelivr serves `cache-control: public, max-age=31536000, immutable` for tagged commits. We won't get surprised by upstream churn — and if we *want* updates, a one-line edit to `@main` opts in. 5. **Companion `cineplex.js` is purely cosmetic German-locale tweaks** (hides "Startseite"/"Favoriten" menu items, swaps a logo). Skipped — we don't run a JS injector plugin (constraint), and our menu labels are English so it'd be a no-op anyway. Theme works fine without it. 6. **Cast/crew hide rule still appended** at the bottom of `CustomCss`, exactly as before. #### Tradeoffs (honest list, Cineplex era) - **License: none.** Cineplex doesn't declare one. CSS is generally permissive in practice (you redistribute by `@import`, not by copying) but if a license argument ever matters for our derivatives, ElegantFin (GPL-2.0) is cleaner. - **Bus factor: 1.** Single author (Maverick Runkehl), 0 stars, last commit Sep 2025. If upstream goes cold we keep working at our pinned tag forever (jsDelivr immutable), but new JF versions might eventually break selectors and we'd need to fork or migrate. - **Netflix Sans license note.** The font files are loaded from Netflix's own CDN, not bundled. If Netflix changes that path or rate- limits non-netflix.com referers, we'd fall back to system-ui (also declared in the stack). Acceptable. - **Theme footer.** Cineplex doesn't add a brand stamp, so users see no "Cineplex" tag — cleaner than ElegantFin's footer label was. #### What Cineplex looked like (live, post-apply) - **Background:** `#181818` (Finity base) — Netflix-black. - **Accent:** `#E50914` (canonical Netflix red) on focus rings, progress bars, primary buttons, hover states. - **Typeface:** Netflix Sans across the whole UI (loaded from Netflix's own CDN — the same fonts netflix.com itself serves). Subtitles also use Netflix Sans Medium. - **Cards:** rounded ~6px, hover scales to 1.05× with subtle shadow lift. - **Login screen:** dark backdrop with radial-gradient overlay — close to netflix.com's sign-in page. - **No theme-brand footer label** any more. #### Theme history | Date | Theme | Version | Why changed | |---|---|---|---| | 2026-05-08 (earlier today) | ElegantFin | v25.12.31 | Initial Jellyfin theming pass. Picked for activity + safety (most actively maintained CSS in the ecosystem). | | 2026-05-08 (mid-day) | **Cineplex** | **v1.0.6** | Owner asked for the most Netflix-faithful theme available. ElegantFin's Jellyseerr aesthetic (blue-grey, no red) is too far from Netflix; Cineplex is purpose-built for this look and explicitly targets the 10.10 series we're on. JellyFlix (the genre's elder) is halted. | | 2026-05-08 (later, current) | **ElegantFin + ARRFLIX recolor** | **v25.12.31** + `#E50914` accent overrides | Owner liked Cineplex's Netflix accent but preferred ElegantFin's polished browsing UI. Best of both: ElegantFin's layout/typography + ARRFLIX brand red overrides. Snapshot tag for rollback: `snapshot-2026-05-08-pre-elegantfin`. See §3e. | Rollback paths: - To Cineplex (Netflix-faithful): apply `snapshots/2026-05-08-pre-elegantfin/branding.json` per `snapshots/2026-05-08-pre-elegantfin/RESTORE.md`. - To plain ElegantFin (no recolor): see §6b. - To vanilla Jellyfin: see §6b. --- ## 2. How it was applied ### Branding API (Cineplex, applied 2026-05-08) ```bash TOKEN= cat > /tmp/branding.json <<'EOF' { "LoginDisclaimer": "Welcome to arrflix.s8n.ru — LAN-only. Be kind, rewind.", "CustomCss": "/* Cineplex v1.0.6 — Netflix-faithful theme by MRunkehl, pinned tag (immutable on jsDelivr) */\n/* Compat: Jellyfin 10.10.7+ ; we run 10.10.3 — verified rendering 2026-05-08 */\n@import url(\"https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css\");\n\n/* Hide Cast & Crew + Guest Stars sections globally (preserved 2026-05-08) */\n#castCollapsible, #guestCastCollapsible { display: none !important; }\n", "SplashscreenEnabled": true } EOF # Note: arrflix.s8n.ru didn't have a Traefik SNI cert at apply-time, so # we sent the request to the legacy SNI arrflix.s8n.ru and pinned its address # with --resolve. Either form is fine once both names have certs. curl -sS --resolve arrflix.s8n.ru:443:192.168.0.100 \ -X POST \ -H "X-Emby-Token: $TOKEN" \ -H "Content-Type: application/json" \ --data-binary @/tmp/branding.json \ https://arrflix.s8n.ru/System/Configuration/branding # expect: HTTP 204 (got HTTP 204 — applied) ``` ### Verification (executed 2026-05-08) ```bash # 1. Admin endpoint — confirms the new CustomCss is stored. curl -sS --resolve arrflix.s8n.ru:443:192.168.0.100 \ -H "X-Emby-Token: $TOKEN" \ https://arrflix.s8n.ru/System/Configuration/branding | python3 -m json.tool # Result: HTTP 200, contains the Cineplex @import + cast/crew hide rule. # 2. Anonymous endpoint the SPA reads at runtime — confirms what every # browser will pull before login. curl -sS --resolve arrflix.s8n.ru:443:192.168.0.100 \ https://arrflix.s8n.ru/Branding/Configuration | python3 -m json.tool # Result: HTTP 200, identical CustomCss to admin endpoint. ✓ # 3. The CSS asset itself on jsDelivr (sanity-check the network path). curl -sSI "https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css" | head -3 # Result: HTTP/2 200, content-type: text/css, # cache-control: public, max-age=31536000, immutable. ✓ # 4. SPA shell still routes (nav not broken). curl -sSI --resolve arrflix.s8n.ru:443:192.168.0.100 https://arrflix.s8n.ru/ | head -1 # Result: HTTP/2 302 → /web/. ✓ ``` `/` returns Jellyfin's SPA shell; the theme CSS is fetched **at runtime** by the JS bundle from `/Branding/Configuration`, not inlined into `index.html`. So `curl /` won't grep-match. The valid JSON at `/Branding/Configuration` is the API-level confirmation. Final visual check is a hard browser reload (Ctrl-Shift-R) on `https://arrflix.s8n.ru` (or `https://arrflix.s8n.ru` once its cert is up) from the LAN — owner will do this. ### Cache clear Jellyfin web caches aggressively in browsers. After applying: - Users: hard reload (Ctrl-Shift-R / Cmd-Shift-R) once. - Server: no restart needed; CustomCss change is live on next page load. --- ## 3. Server-side branding state (as of 2026-05-08, post-Cineplex) | Field | Value | |---|---| | `LoginDisclaimer` | "Welcome to ARRFLIX - Private invite only service" | | `CustomCss` | `@import` of **Cineplex v1.0.6** from jsDelivr — pinned tag `@v1.0.6` (immutable). Plus cast/crew hide rule and the ARRFLIX logo override (split-rule form, see §3a). | | `SplashscreenEnabled` | `true` | `SplashscreenEnabled: true` makes Jellyfin auto-pick a backdrop from the library and serve it at `/Branding/Splashscreen`. The web client doesn't itself surface this; mobile/TV clients do. Harmless to leave on. ### 3a. ARRFLIX logo override — fix for triple-stacked wordmark (2026-05-08) Initial override copied Cineplex's three-selector group verbatim and combined `content: url(...)` with `background-image: url(...)` on every selector. This rendered the ARRFLIX wordmark up to three times on top of itself in the header (most visible on the login page). Two root causes, verified against the live `/jellyfin/jellyfin-web/` bundle: 1. **`.imgLogoIcon` is a phantom selector.** A grep of every `*.js`, `*.html` and `*.css` asset shipped with Jellyfin 10.10.3 returns zero matches. Cineplex's upstream rule (`imgLogoIcon { content: url(...) }` — note the missing leading dot) is itself a no-op typo. Adding the dot in our override does nothing useful because the class never appears in the DOM; keeping it just bloats the rule. 2. **`content: url(...)` on a `
` renders the image inside the element.** `.pageTitleWithLogo` is a `
` (set by `setDefaultTitle` in `73233.*.chunk.js` — see `c.classList.add("pageTitleWithLogo")`). Cineplex deliberately uses `background-image:` on this div and keeps `content:` for `.adminDrawerLogo img` (an ``, where `content:` replaces the source). Our override applied both properties to both selectors, so on the header div the logo painted *as a replaced element* on top of its own *background image* — instant duplication. Fix: split the rule by element type, drop the phantom selector entirely. ```css /* ARRFLIX logo override (replace Cineplex branding) — 2026-05-08 (fix: split rules, drop phantom .imgLogoIcon) */ .adminDrawerLogo img { /* in admin sidebar drawer — content: replaces src */ content: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important; } .pageTitleWithLogo { /*
masthead on dashboard + login — bg image only, no content: */ background-image: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important; } ``` Verified live (HTTP 204 on POST, then `GET /Branding/Configuration`): single ARRFLIX wordmark on login, dashboard header, and admin sidebar. Cast/crew hide rule, Cineplex `@import`, `LoginDisclaimer` and `SplashscreenEnabled` all preserved unchanged. The bind-mounted `/web/index.html` (favicon, `ARRFLIX`, `splashLogo`) was not touched — that asset is owned by the index-patcher. ### 3b. ARRFLIX logo override fix re-applied 2026-05-08 (second time) Re-curling `/System/Configuration/branding` after §3a was applied showed the broken three-selector / dual-property rule was back: `.adminDrawerLogo img, .imgLogoIcon, .pageTitleWithLogo { content: url(...); background-image: url(...); }`. The §3a fix had been silently overwritten by a sibling agent that POSTed a full branding payload (LoginDisclaimer + SplashscreenEnabled + its own stale CustomCss) without first GETting the latest CustomCss. Because the Jellyfin branding endpoint takes a complete object on every POST and has no field-level merge, whichever POST lands last wins — there is no locking, no ETag, no patch semantics. Fix re-applied surgically (split selectors, drop `.imgLogoIcon`, preserve the data-URL bytes verbatim, preserve the cast/crew hide, Cineplex `@import`, Quick Connect hide, and both LoginDisclaimer/SplashscreenEnabled values) and re-verified via `/Branding/Configuration`. **Operational rule:** any agent or script touching `/System/Configuration/branding` must (a) GET first, (b) edit only the fields it owns, (c) POST the full object, and the branding-CSS POST must be the **last** POST in any sequence that touches this endpoint — otherwise a later sibling POST will silently re-stack the logo. If you find yourself about to POST branding for any reason, GET `/System/Configuration/branding` first and confirm the override block matches the §3a skeleton before sending. ### 3c. Header icon hide (2026-05-08): keep search, drop SyncPlay/Cast/User-menu Top-right header had four buttons: SyncPlay (Create Group people-pair), Cast (Chromecast triangle), Search (magnifying glass), User (account menu). Goal: hide everything except Search. Selectors confirmed by grep against the live JF 10.10.3 web bundle (`73233.*.chunk.js`): `headerSyncButton`, `headerCastButton`, `headerSearchButton`, `headerUserButton` (also `headerAudioPlayerButton` for the now-playing badge). Each is a class on the `