ARRFLIX/docs/14-theme-audit.md
s8n 667694adbf strip: remove Claude attribution from ROADMAP + audit docs
ROADMAP owner column 's8n' (was 'claude'). Audit-run-by lines in
docs/{11,14,16} reattributed to s8n. Removed CLAUDE.md memory ref
from docs/04 hosts-pin note.
2026-05-08 16:44:49 +01:00

617 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 14 — Theme Audit + Detail-Page Backdrop Diagnosis
Status: **read-only audit**, executed 2026-05-08 against
`https://arrflix.s8n.ru` (Jellyfin 10.10.3 on nullstone). The owner has
just rolled back to **Cineplex v1.0.6** (the Netflix-faithful theme)
after a brief ElegantFin → NeutralFin experiment that was documented in
docs 04 §3e and 11 respectively. Reported issue: on detail pages the
**backdrop image leaves a visible vertical black band on the left** where
the title/info column sits. Owner asked for a forward plan, not a fix.
> **No state mutated.** No POST to `/System/Configuration/branding`,
> no edit to `/jellyfin/jellyfin-web/index.html`, no docker action.
> Read-only over SSH and against the public `/Branding/Configuration`
> + authenticated `/System/Configuration/branding` endpoints.
---
## 1. Current state inventory
### 1a. Active theme
`/System/Configuration/branding` returns:
| Field | Value |
|---|---|
| `LoginDisclaimer` | `"Welcome to ARRFLIX - Private invite only service"` |
| `SplashscreenEnabled` | `true` |
| `CustomCss` (size) | **25 225 chars** (most of which is the embedded ARRFLIX wordmark data-URL — twice) |
Sole `@import` line:
```css
@import url("https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css");
```
Cineplex itself transitively imports
`cineplex@v1.0.5/finity-theme/finity-complete.css` (its parent theme,
**Finity** by prism2001). This matters for the backdrop diagnosis below.
### 1b. CustomCss block inventory (every rule, in order)
`!important` declarations: **17**. `#E50914` occurrences: **0** in
CustomCss; **1** in `web-overrides/index.html` critical-path `<style>`.
ARRFLIX wordmark PNG: **235 × 85 px** (aspect 2.765 : 1), embedded
as base64 data-URL on two selectors.
| # | Block | Selectors | Purpose | `!important` count |
|---|---|---|---|---|
| 1 | Cineplex import | `@import` | Theme entry point | 0 |
| 2 | Cast/Crew hide | `#castCollapsible, #guestCastCollapsible` | Drop reviewer cruft | 1 |
| 3a | ARRFLIX logo (img) | `.adminDrawerLogo img` | `content:` replace src in admin drawer | 1 |
| 3b | ARRFLIX logo (div) | `.pageTitleWithLogo` | `background-image:` for masthead `<div>` | 1 |
| 4 | Quick Connect hide | `.btnQuick` | Belt-and-braces for the server-side disable in 04 §4g | 1 |
| 5 | Header icon hide | `.headerSyncButton`, `.headerCastButton`, `.headerUserButton` | Keep only Search top-right | 3 |
| 6a | Slider thumbs (white) | `.MuiSlider-thumb`, `.osdPositionSlider .MuiSlider-thumb`, `.osdVolumeSlider .MuiSlider-thumb`, `emby-slider .sliderThumb` | OSD scrubber + volume circles | 3 |
| 6b | Slider thumbs (focus halo) | `.MuiSlider-thumb:hover/:active/.Mui-focusVisible` | Hover ring | 1 |
| 7a | Pure-black bg (vars) | `:root { --primary-background-color/--background-color: #000 }` | Force shell vars to true black | 2 |
| 7b | Pure-black bg (wrappers) | `html, body, .preload, .skinBody, .mainDrawerHandle` | Anti-flash on shell wrappers | 1 |
| 7c | Pure-black bg (containers) | `.skinHeader, .skinHeader.semiTransparent, .skinHeader.skinHeader-withBackground, .mainAnimatedPages, #reactRoot, .dashboardDocument` | Container surfaces | 1 |
| 8 | Settings drawer hide | `a[href*="mypreferencesmenu"]`, `[to="/mypreferencesmenu.html"]` and `:has()` parent variants × 7 | Remove Settings link from drawer | 1 |
| 9 | Count-badge hide | `.countIndicator` | Drop unwatched-episode badges | 1 |
### 1c. Critical-path inline `<style>` (in `web-overrides/index.html`)
Bind-mounted at `/jellyfin/jellyfin-web/index.html`, paints **before** the
SPA bundle loads CustomCss:
| Block | Effect |
|---|---|
| `:root { --primary-background-color: #000; --background-color: #000 }` | Pre-paint shell vars (no `!important`) |
| `html, body, .preload, .skinBody, .skinHeader, #reactRoot, .mainAnimatedPages { bg:#000 !important; color:#fff !important }` | Anti-flash + force colour |
| `.raised, .button-submit, .emby-button[type=submit], button[type=submit] { bg:#E50914 !important; color:#fff !important }` | Pre-paint Netflix-red on submits (login Sign-In) |
| `.splashLogo { animation: fadein .5s; width:30%; height:30%; bg-image:<ARRFLIX wordmark data-URL>; bg-size:contain; bg-position:center; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%) }` | The pre-bundle splash screen |
| `@media (min-device-width:992px) { .splashLogo { bg-image:<same ARRFLIX wordmark, full-res copy> } }` | Desktop variant (currently identical bytes — see §6) |
Plus 78 lines of inline `<script>` (ARRFLIX-SHIM) that locks
`document.title`, the favicon, and continuously hides any
`mypreferencesmenu` drawer entry that might be rendered after navigation.
None of the JS touches detail-page layout.
---
## 2. Detail-page backdrop diagnosis
### 2a. Selector hunt against the live JF 10.10.3 web bundle
`docker exec jellyfin grep -oE` against
`/jellyfin/jellyfin-web/main.jellyfin.1ed46a7a22b550acaef3.css` and
`itemDetails-index-html.ca5f15ff794311af00a6.chunk.js` returned the
canonical detail-page selector set:
| Selector | Where defined | Stock JF 10.10.3 layout |
|---|---|---|
| `.itemBackdrop` | `main.jellyfin.<hash>.css` | `height: 40vh; width: <inherited>; background-size: cover; background-attachment: fixed; position: relative;`**only top 40vh of the page** |
| `.layout-mobile .itemBackdrop` | same | `background-attachment: scroll; background-position: top` |
| `.layout-tv .itemBackdrop` | same | `display: none` |
| `.detailPageContent` | same | `display: flex; flex-direction: column; padding-left: 32.45vw` (LTR desktop) — i.e. the content column starts 32.45% from the left |
| `.detailPagePrimaryContainer` | same | `display: flex; align-items: center; z-index: 2;` desktop adds `padding-left: 32.45vw` |
| `.detailImageContainer .card` | same | `position: absolute; top: -80%; left: 3.3%; width: 25vw` (desktop) — the poster card sits in the LEFT column |
| `.detailLogo` | same | `position: absolute; top: 10vh; right: 25vw; width: 25vw; height: 16vh; background-size: contain` |
| `.detailRibbon` | same | desktop: `height: 7.2em; margin-top: -7.2em` (the gradient fade strip below backdrop) |
| `.itemBackdropProgressBar` | same | `position: absolute; bottom:0; left:0; right:0` |
| `.detailPageWrapperContainer` | same | `border-collapse: collapse` |
There is **no** `itemBackdropFader`, no `itemHeroSection`, no
`backdropHeroSection` selector in the bundle. The owner's mental model of
"a fader covering the left column" doesn't match — the architecture is
*positional offsets*, not an overlay.
### 2b. What Cineplex/Finity overrides
`grep -nE "itemBackdrop|detailPagePrimary|detailPageContent|detailLogo|detailImageContainer|detailRibbon|detailPageWrapper" /tmp/cineplex.css /tmp/finity.css` shows:
**`cineplex.css`** — only **two** detail-page rules, both of them
mobile-only. No desktop override of `.itemBackdrop`.
```css
/* line 577 */
.layout-mobile .itemBackdrop {
margin-top: 0rem;
mask-image: linear-gradient(to top, #fff0 1%, #000 15%, #000 80%, #fff0 100%);
}
```
**`finity-complete.css`** — Finity is where the detail-page layout is
heavily redesigned. Key block:
```css
/* finity.css :root */
--detail-page-side-padding: 5%;
--detail-page-primary-width: 45%;
--detail-page-backdrop-offset: 17%; /* <-- THE BLACK BAND */
--detail-page-backdrop-width: 85vw;
--detail-page-mask-offset: 16%;
--detail-page-mask-width: 85vw;
--detail-page-content-offset: -65vh;
.layout-desktop .itemBackdrop {
background-attachment: scroll;
background-position: center;
background-size: cover;
height: 100vh; /* full viewport, NOT 40vh — Finity expands JF default */
width: 100%;
}
.backdropContainer {
height: 100vh;
left: var(--detail-page-backdrop-offset); /* 17% */
position: absolute;
top: 0;
width: var(--detail-page-backdrop-width); /* 85vw */
z-index: 0;
pointer-events: none;
}
.layout-desktop .backgroundContainer.withBackdrop {
background: url("https://raw.githubusercontent.com/prism2001/finity/main/assets/mask.png");
background-size: cover;
height: 100vh;
left: var(--detail-page-mask-offset); /* 16% */
width: var(--detail-page-mask-width); /* 85vw */
z-index: 1;
pointer-events: none;
}
.layout-desktop .detailImageContainer .card { display: none; } /* hide poster card */
```
### 2c. Root cause
The "black band on the left" is **Finity's intentional design**, not a
Cineplex bug and not a JF stock layout artefact:
- Stock Jellyfin: `.itemBackdrop` is `height: 40vh` and full-width
(`width` is inherited from the parent flow). The backdrop crops the
*top* of the page, the info column lays out below it. No left band.
- Finity: re-engineers the page so `.itemBackdrop` is `100vh` *but*
positions a separate `.backdropContainer` absolutely at `left: 17%
width: 85vw` (so the right ~98% of the viewport gets the backdrop and
the left **17vw / 17%** is left clear). On top of that, a blurred
`mask.png` is overlaid at `left: 16%` to fade the right edge of the
remaining clear band into the backdrop — making the band look like a
designed gradient sidebar, NOT a black bar.
The reason it currently reads as **a hard black band** rather than a
soft gradient fade is the combination of two of our personal tweaks
plus one Finity asset that may not be reaching the browser:
1. **`html, body, .preload, .skinBody, .mainDrawerHandle { bg:#000 !important }`**
forces the underlying surface where the band sits to pure black.
Finity's `--theme-background-color: #181818` is the intended
surface — slightly less harsh.
2. **`#reactRoot, .mainAnimatedPages, .dashboardDocument { bg:#000 !important }`**
does the same for the SPA wrappers above the body.
3. The Finity mask overlay
(`.backgroundContainer.withBackdrop`) loads its mask PNG from
`raw.githubusercontent.com/prism2001/finity/main/assets/mask.png`
on a LAN with no upstream proxy that should resolve, but if the
browser blocks third-party image loads (some ad-blockers strip
`raw.githubusercontent.com` requests) the mask never paints and the
17vw band is unmasked. Worth a DevTools network-tab check before any
CSS change.
Net: the backdrop **is** filling the right 85vw of the viewport. The
left 17vw is intentionally clear so the title/poster/info column has a
high-contrast surface to render on. Our `bg:#000 !important` rules turn
that intentionally-clear surface into a hard black band; without them
it would be `#181818` with a soft gradient fade from the mask PNG.
### 2d. Forward-plan CSS (DO NOT APPLY)
If the goal is **Netflix-style full-bleed backdrop with a left-side
gradient overlay** (info column floating over a darkened-but-visible
backdrop), the proposed rule set is:
```css
/* Detail-page backdrop: full-bleed + left gradient overlay
(proposal — not applied) */
/* 1. Stretch the backdrop container across the full viewport
instead of starting at 17vw */
.layout-desktop .backdropContainer {
left: 0 !important;
width: 100vw !important;
}
/* 2. Replace Finity's mask.png with a CSS-only linear gradient
that darkens the left 40-50vw and fades to transparent.
`.backgroundContainer.withBackdrop` is the overlay layer. */
.layout-desktop .backgroundContainer.withBackdrop {
background: linear-gradient(
90deg,
rgba(0, 0, 0, 0.95) 0%,
rgba(0, 0, 0, 0.85) 25%,
rgba(0, 0, 0, 0.55) 45%,
rgba(0, 0, 0, 0.20) 65%,
rgba(0, 0, 0, 0.00) 85%
) !important;
left: 0 !important;
width: 100vw !important;
}
/* 3. Drop the global black-bg force from the wrappers ON DETAIL
PAGES ONLY so the gradient composes against the actual
backdrop, not pure black. Scope by .itemDetailPage body class
that JF adds on detail routes. */
body.itemDetailPage,
body.itemDetailPage #reactRoot,
body.itemDetailPage .mainAnimatedPages {
background-color: transparent !important;
}
```
The `90deg, 95% → 0%` gradient is the Netflix.com detail-page recipe:
opaque on the left where the title sits, fades to transparent by ~70vw
so the right side of the backdrop is visible at full brightness. Tune
the stop percentages once live — the sweet spot depends on
`--detail-page-primary-width` (Finity ships `45%`).
**Untested side-effect to watch for:** Finity *also* hides the poster
card with `.layout-desktop .detailImageContainer .card { display:none }`.
That means we have NO poster in the left column today — the current
black band is empty space framing a clear logo + title block. The fix
above would put the title text directly over the backdrop, which is
fine on most artwork but may have legibility issues on bright/busy
backdrops. If owner wants the poster back, drop that Finity rule too.
### 2e. Screenshot reference
A capture of `https://arrflix.s8n.ru/web/#/details?id=324f75b84f394a5d9b0749c0679f23b9`
(Rick & Morty S01E01 "Pilot") with a hard browser reload would show:
- Top: ~17vw black/empty band on the left, Rick & Morty backdrop on
the right ~83vw. (Finity / current.)
- Title "Pilot" + Series logo + Play button float over the empty band.
- After fix: title floats over a darkened-but-visible portion of the
same backdrop, gradient eases into the un-darkened backdrop on the
right ~30%.
Owner has not provided a current screenshot in this audit; capture
recommended before any CSS change so before/after is documented.
---
## 3. Theme survey 2026-05
Surveyed candidates (live as of audit date), scored on Netflix
fidelity, monochrome fidelity, recency, JF 10.10.3 compatibility,
import format, license:
| Theme | Last commit | License | Netflix fidelity | Monochrome fidelity | JF 10.10.3 compat | Import | Notes |
|---|---|---|---|---|---|---|---|
| **Cineplex v1.0.6** (current) | 2025-09-06 | MIT | **9/10** — true `#E50914`, Netflix Sans webfont, scale-hover, login backdrop | 2/10 | YES (verified live) | single `@import` (transitively pulls Finity) | Bus-factor 1 (single author MRunkehl, 0 stars). Inherits Finity's left-band detail-page layout. |
| **ElegantFin v25.12.31** | 2026-04-30 | GPL-2.0 | 5/10 — Jellyseerr blue/violet by default, recolour-able to `#E50914` (eight `--var` overrides documented in 04 §3e) | 5/10 | YES (tested 10.11.5) | single `@import` | Most actively maintained CSS theme in the ecosystem. Detail-page backdrop is full-width with a gradient overlay built in — no left band. |
| **NeutralFin v1.3.0** | 2025-11-24 | GPL-2.0 | 1/10 (mid-grey accents, no red) | **9/10**`#131313 → #1e1e1e` gradient, mid-grey accents, off-white text | YES (tested implicitly via ElegantFin parent) | single `@import` | Fork of ElegantFin. The "didn't look as good" feel was caused by our `bg:#000 !important` rules clamping its `#131313→#1e1e1e` gradient flat (see doc 11). With those dropped it would render correctly. |
| **Theme Park (jellyfin pack)** | active | GPL-3.0 | n/a — **no Netflix preset** (only aquamarine/hotline/dracula/dark/organizr/space-gray/plex/nord) | varies by preset | likely | single `@import url(theme-park.dev/css/base/jellyfin/<NAME>.css)` | DQ for our brief; closest is `plex` (orange/black) but that's a different brand entirely. |
| **JellyFlix** (prayag17) | 2023-12-20 | none | 9/10 — origin of the genre | 1/10 | **HALTED** (README header) | single `@import` | DQ — explicitly halted, broken on JF 10.11, risky on 10.10.3 |
| **DarkFlix v5.1** | 2024-06 | GPL-3.0 | 8/10 | 1/10 | only declares 10.8.x; **requires 67% browser zoom** | single `@import` | DQ — accessibility issue, no 10.10 statement |
| **Ultrachromic** (CTalvio) | "selectively maintained" — 146 commits, no recent date | MIT | 6/10 (accent-tunable) — three presets: Monochromic, Kaleidochromic, Novachromic | 8/10 (Monochromic preset) | unspecified | single `@import` per preset | "Old, passively maintained." No Netflix preset, but Novachromic accepts custom accents — could be set to `#E50914`. |
| **Finity** (prism2001, Cineplex's parent) | 2026-05 (active) | none stated | 6/10 (dark, modern, no Netflix red by default) | 5/10 | unspecified | single `@import` | Fully responsible for the detail-page layout we see on Cineplex. If the backdrop fix lands, we'd be fixing Finity's `.backdropContainer` rules. |
| **abyss-jellyfin** (AumGupta) | 2026-05 | n/a | 1/10 | 7/10 | unspecified | unknown | "Minimal dark." 290 stars, growing. Not Netflix-flavoured. |
| **FossFlix** (PaleCache) | 2026-01 | n/a | 6/10 (claims Netflix UI similarity) | 1/10 | unspecified | unknown | 1 star, unproven. Worth bookmark, not migration. |
| **JellyFin** (n00bcodr) | 2026-05 | n/a | 0/10 | 6/10 | unspecified | unknown | Inspired by Flow + Zesty — neither fits the brief. |
| **JellyThemes** (kingchenc) | 2026-01 | n/a | 0/10 | varies (six dark themes with glassmorphism) | unspecified | unknown | DQ for Netflix brief. |
| **Hybrid: Cineplex + NeutralFin tweaks** | n/a | derivative | 7/10 | 4/10 | YES if grafted carefully | one `@import` + tweaks | Not actually possible to graft cleanly — Cineplex's red and NeutralFin's grey both define `--theme-accent-color` / `--uiAccentColor` at `:root`, last-write-wins. Picking the import = picking the palette. Ranges of personal-tweak overrides (e.g. `.MuiSlider-thumb:white`) DO survive across both. |
### 3a. Verdict on Theme Park
`docs.theme-park.dev/themes/jellyfin/` lists eight presets: Aquamarine,
Hotline, Dracula, Dark, Organizr, Space-gray, Plex, Nord. **No Netflix
preset.** The closest cousin (`hotline`) is a magenta/cyan synthwave
look, not Netflix-red. Theme Park is therefore not a viable migration
target for the ARRFLIX brand; ruled out.
---
## 4. Personal-tweak portability matrix
For each personal-tweak block in current `CustomCss`, classify the
selector as **theme-independent** (generic Jellyfin selector, survives
any swap) vs **theme-specific** (requires re-targeting).
| # | Block | Selector | Type | Cineplex | ElegantFin | NeutralFin | Theme-Park | Portability |
|---|---|---|---|---|---|---|---|---|
| 2 | Cast/Crew hide | `#castCollapsible, #guestCastCollapsible` | Generic JF id | works | works | works | works | **HIGH** |
| 3a | Logo (admin) | `.adminDrawerLogo img` | Generic JF class | works | works (per 04 §3e — verified 0 ElegantFin matches) | works (no NeutralFin matches) | works | **HIGH** |
| 3b | Logo (masthead) | `.pageTitleWithLogo` | Generic JF class | works (with `bg-image`, NOT `content:`) | works (verified) | works | works | **HIGH** |
| 4 | Quick Connect hide | `.btnQuick` | Generic JF class on `<button>` | works | works | works | works | **HIGH** |
| 5 | Header icons hide | `.headerSyncButton`, `.headerCastButton`, `.headerUserButton` | Generic JF classes (verified in `73233.*.chunk.js`) | works | works | works (NeutralFin sets `width/height/border` on `.headerUserButton` but `display:none` overrides those) | works | **HIGH** |
| 6 | Slider thumb white | `.MuiSlider-thumb` + variants | MUI runtime class | works | works | works (theme doesn't theme MUI sliders) | works | **HIGH** — but consider re-tinting on monochrome themes |
| 7a | Bg vars `:root` | `--primary-background-color`, `--background-color` | Jellyfin shell var | works (Cineplex defaults to `#181818` — we override to `#000`) | works | **HARMFUL on NeutralFin** — clamps the `#131313→#1e1e1e` gradient (see doc 11 row 8) | works | **MEDIUM** — survives technically, but defeats NeutralFin's intent. |
| 7b/7c | Bg wrappers (`html`, `body`, `.skinHeader`, `.mainAnimatedPages`, `#reactRoot`, `.dashboardDocument`) | Jellyfin shell wrappers | works (Cineplex doesn't theme these) | works (ElegantFin uses translucent wrappers — `#000` underneath is fine) | **HARMFUL** — clamps gradient + flattens `.skinHeader.semiTransparent` (see doc 11 row 10) | likely works | **MEDIUM** — and **harmful on detail pages for Cineplex** (this is what's making the 17vw band hard-black, see §2c above) |
| 8 | Settings drawer hide | `a[href*="mypreferencesmenu"]`, `[to="/mypreferencesmenu.html"]`, `:has()` parents | JF route + MUI ListItem classes | works | works | works | works | **HIGH** (if browser supports `:has()`) |
| 9 | Count badge hide | `.countIndicator` | Generic JF class | works | works | works (NeutralFin themes it, but `display:none` wins) | works | **HIGH** |
| index.html | Anti-flash inline | `html, body, .preload, .skinBody, .skinHeader, #reactRoot, .mainAnimatedPages` | Same wrappers as 7b/7c, but **pre-bundle** | works | works | **HARMFUL** — same issue as 7b/7c, but earlier in load (see doc 11 row 14) | likely | **LOW-MEDIUM** — needs `!important` removed and `.skinHeader` dropped from the list to be theme-portable |
| index.html | Submit-button red | `.raised, .button-submit, .emby-button[type=submit], button[type=submit]` | Generic JF + MUI button classes | works (matches Cineplex's `#E50914` accent) | requires recolour-aware ElegantFin (works since override is in our hands) | **HARMFUL** — paints every submit Netflix-red over a monochrome theme (see doc 11 row 15) | works | **LOW** — rule is brand-specific, must be removed when brand colour changes (NeutralFin would need `--btnSubmitColor` instead) |
| index.html | ARRFLIX shim (title/favicon/`mypreferencesmenu`) | inline `<script>` | Independent of theme | works | works | works | works | **HIGH** |
| index.html | Splash logo | `.splashLogo` | Pre-bundle JF class | works | works | works | works | **HIGH** |
**Summary:** 11 of 14 blocks are HIGH portability (theme-independent
generic JF selectors). The 3 problem children are all variations of
"force pure black background" — and they happen to be the same blocks
flagged in doc 11 as harmful to NeutralFin AND, per §2c above, to be
the cause of the hard-black detail-page band on Cineplex.
> **Operational rule:** when swapping themes, audit blocks 7a / 7b / 7c
> / index.html-anti-flash / index.html-submit-red FIRST. The other
> tweaks ride along automatically.
---
## 5. Logo aspect-ratio fit
ARRFLIX wordmark PNG: **235 × 85 px**, aspect **2.765 : 1**.
| Container | Selector | Sizing on Cineplex/Finity | Wordmark fit |
|---|---|---|---|
| Admin drawer | `.adminDrawerLogo img` | `<img>` element, `content:` swap, sized by sidebar (~240px wide) | natural — replacement is the displayed image | OK |
| Masthead | `.pageTitleWithLogo` | `<div>`, `bg-image` + `bg-size: contain` (Finity convention) | aspect preserved by `contain`, no squish | OK |
| Detail page logo | `.detailLogo` | `position: absolute; right: 25vw; top: 10vh; width: 25vw; height: 16vh; bg-size: contain` | per-show clear-logo box. ARRFLIX wordmark is not used here — this is the show's clear-logo (e.g. Rick & Morty title art). Not a fit concern for our wordmark. | OK |
| Splash | `.splashLogo` | `width:30%; height:30%; bg-size:contain; centered` | aspect preserved; on a 1920×1080 viewport renders ~576×324 box, wordmark settles at ~576×208 (height-limited by aspect). Looks correct. | OK |
**Verdict:** 235 × 85 fits cleanly in every container. Aspect ratio is
NOT a factor in any of the rendering complaints. The native JF
admin-drawer + masthead use `bg-size: contain`, so a 2.765:1 wordmark
displays without distortion regardless of theme.
---
## 6. Pre-bundle splash quality
Inspecting `web-overrides/index.html` (93 lines, the bind-mounted
override of the JF web shell):
| Aspect | Value | Notes |
|---|---|---|
| `body { background: #000 }` (declared in critical-path `<style>`) | YES | Anti-flash baseline |
| `.splashLogo` size | `width:30%; height:30%` | Centred via `position:fixed; top:50%; left:50%; transform:translate(-50%,-50%)` |
| `.splashLogo bg-image` | inlined data-URL of the 235 × 85 ARRFLIX wordmark | Same PNG as the masthead/admin drawer |
| `.splashLogo bg-size` | `contain` | Aspect preserved |
| Animation | `animation: fadein 0.5s` (defined as `@keyframes fadein { 0%{opacity:0} 100%{opacity:1} }`) | Half-second ease-in |
| Mobile vs desktop variant | `@media (min-device-width: 992px) { .splashLogo { bg-image: <data-URL> } }` | The desktop branch CURRENTLY uses **the same 235 × 85 PNG bytes** as the small/mobile branch — i.e. there is no higher-resolution desktop asset. This is a half-implemented split. Owner could supply a 470 × 170 (2x) or 940 × 340 (4x) PNG to bake into the desktop branch for sharper rendering on 1080p+ displays. |
| Screen reader / `<title>` | `<title>` is set + locked at runtime by `lockTitle()` to `"ARRFLIX"` | OK |
**Verdict:** splash is functional, fade-in is smooth, aspect is correct.
The only quality nit is the desktop `<media>` branch reading the same
small PNG as mobile — a 2× or 4× ARRFLIX wordmark in the desktop
branch would be sharper. Defer-able; not a complaint the owner has
raised.
---
## 7. Detail-page backdrop fix proposal (concrete CSS, NOT applied)
Re-stating §2d in implementation-ready form. Expected to drop into
`CustomCss` AFTER the Cineplex `@import`, BEFORE the existing
`bg:#000` blocks (which need to be **scoped out of detail pages** to
not clobber the gradient — see `body.itemDetailPage` selectors below).
```css
/* === Detail-page backdrop fix (proposal, 2026-05-08) === */
/* Convert Finity's 17vw black band into a Netflix-style gradient
overlay over a full-bleed backdrop. */
/* 1. Stretch backdrop container across the full viewport */
.layout-desktop .backdropContainer {
left: 0 !important;
width: 100vw !important;
}
/* 2. Replace Finity's mask.png with a CSS-only linear-gradient
that darkens the left ~50vw and fades to transparent.
`.backgroundContainer.withBackdrop` is the existing overlay
element in the Finity DOM. */
.layout-desktop .backgroundContainer.withBackdrop {
background-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0.95) 0%,
rgba(0, 0, 0, 0.85) 25%,
rgba(0, 0, 0, 0.55) 45%,
rgba(0, 0, 0, 0.20) 65%,
rgba(0, 0, 0, 0.00) 85%
) !important;
background-size: 100vw 100vh !important;
left: 0 !important;
width: 100vw !important;
}
/* 3. UN-clamp the page bg specifically on detail pages so the
gradient composes against the actual backdrop, not pure black.
`.itemDetailPage` is added to <body> by JF on every detail
route (verified in main.jellyfin.bundle.js). */
body.itemDetailPage,
body.itemDetailPage #reactRoot,
body.itemDetailPage .mainAnimatedPages,
body.itemDetailPage .skinBody {
background-color: transparent !important;
}
```
**Before/after expectation:**
- Before: 17vw band on the left of the detail page is **flat `#000`**;
poster card hidden by Finity; title + clear-logo float on a hard
black slab.
- After: backdrop fills 100vw of the viewport. Title + logo float over
a darkened-but-visible slice of the backdrop on the left, fading to
full backdrop brightness around 70-85% across. Reads as
netflix.com's title-card style.
**Stops to tune** once live (open DevTools, edit the gradient stops):
- If title text is illegible against busy artwork, push opacity stops
up: `0.95 / 0.92 / 0.75 / 0.40 / 0.10`.
- If too much of the backdrop is darkened, pull stops left: `0.95 / 0.80 / 0.40 / 0.10 / 0.00`
with the last stop at 60%.
- If the right edge of the gradient creates a visible seam against a
bright backdrop, soften the last stop: append a sixth at
`90% rgba(0,0,0,0)` for an extra 5vw fade.
**Untested side-effects to watch for:**
- Finity hides `.detailImageContainer .card` on desktop. The fix
preserves that (poster card stays hidden — title is the focus).
If owner wants the poster card visible, drop:
```css
.layout-desktop .detailImageContainer .card { display: none }
```
by adding `.layout-desktop .detailImageContainer .card { display: revert !important }`.
- The OSD scrubber (`.itemBackdropProgressBar`) sits at the very
bottom of `.itemBackdrop`. With the backdrop now full-width, it's
also full-width (was already, just visually different against a
colour-fade vs. black band).
- Library-list pages that ALSO use the `.backgroundContainer.withBackdrop`
layer (a few in JF — backdrops on library tile rows) will get the
same gradient. If they look wrong, scope rule (1) and (2) to
`body.itemDetailPage .layout-desktop .backdropContainer` etc.
---
## 8. Recommended forward path (top 3 ranked)
### #1 — STAY on Cineplex + apply the §7 detail-page backdrop fix
**Why:** Cineplex is the only Netflix-faithful theme that runs on
JF 10.10.3 with a maintained codebase. The detail-page band is a
*single rule's worth of CSS* away from being a Netflix-style gradient
overlay. We've already invested in the brand stack (ARRFLIX wordmark,
header-icon hide, slider thumbs, Quick Connect off, settings hide); 11
of 14 personal tweaks survive the change, the other 3 (`bg:#000`)
need to be **scoped to non-detail pages** by selector chain
`body:not(.itemDetailPage)` instead of being dropped.
**Risk:** low. CSS-only, additive, no `@import` change, no
`/branding` POST hot-spot. Rolls back trivially.
**Cost:** ~30 minutes to apply, screenshot, tune gradient stops live.
### #2 — Migrate to ElegantFin v25.12.31 with ARRFLIX `#E50914` recolour
**Why:** ElegantFin's detail-page is full-width-backdrop with a
gradient overlay built in — no left band — so the §7 fix becomes
unnecessary. Most actively maintained CSS theme on JF (last commit
2026-04-30, GPL-2.0). The 04 §3e migration documented this exact
config: 8 accent variables overridden, ARRFLIX logo + cast/crew + Quick
Connect + header icons + slider thumbs all preserved.
**Risk:** medium. The previous attempt was overwritten by a sibling
Cineplex POST (race rule in 04 §3b). Personal-tweak block 7c
(`.skinHeader.semiTransparent`) still risks flattening ElegantFin's
translucent header — that block needs editing on landing.
**Cost:** ~45 minutes (re-do migration, scope the bg-clamp rules,
verify all 11 personal tweaks intact post-POST).
**Aesthetic delta vs Cineplex:** ElegantFin is "polished
Jellyseerr-y", Cineplex is "Netflix-faithful". With the recolour
ElegantFin gets the brand red but keeps a non-Netflix layout
(card design, hero strip, etc.). Owner has gone back-and-forth on this
preference — explicitly chose Cineplex this morning.
### #3 — Hybrid: keep Cineplex import + graft NeutralFin's `--gradientPoint` vars
**Why:** for owners who like Cineplex's red+webfont but want
NeutralFin's depth/gradient on backgrounds. Manually copy NeutralFin's
`--darkerGradientPoint #131313 / --lighterGradientPoint #1e1e1e` into a
`:root` block, drop our `--primary-background-color: #000 !important`
overrides, and let the gradient render.
**Risk:** higher than #1 or #2. Variables don't compose perfectly
across themes — Cineplex's Finity parent doesn't read those NeutralFin
vars, it reads its own `--theme-background-color`. So you'd actually
copy the values into Finity's variable: `--theme-background-color: linear-gradient(...)`
which CSS doesn't allow on a plain `background-color`. Real grafting
needs `body { background-image: linear-gradient(180deg, #131313, #1e1e1e) }`
plus dropping the `bg:#000 !important` rules.
**Cost:** ~60 min trial-and-error. Likely lower visual reward than #1.
**Verdict:** Recommended order is **#1 first (lowest risk, biggest
backdrop win), then #2 if owner re-evaluates Netflix-fidelity vs
polish, #3 only as a fall-back if #1 doesn't read well**.
---
## 9. Risks + rollback
### Snapshot tag
`snapshot-2026-05-08-pre-elegantfin` — captured before the ElegantFin
attempt. Currently this is **also the rollback point for any further
theme work** because ElegantFin → NeutralFin → Cineplex have all been
applied (and reverted) on top of it. Located at
`snapshots/2026-05-08-pre-elegantfin/`.
If a future change wants its own snapshot, follow the pattern in
`RESTORE.md`: capture `branding.json`, `index.html`, all
`displayprefs-*.json`, `users.json`, `libraries.json`, write a new
`RESTORE.md`, tag the commit.
### Prior failed swaps (timeline 2026-05-08)
| Time | Theme attempted | Outcome |
|---|---|---|
| early today | ElegantFin v25.12.31 (initial pick — pre-Netflix-brief) | replaced by Cineplex when owner asked for Netflix-faithful |
| mid-day | **Cineplex v1.0.6** | applied, working |
| later | ElegantFin v25.12.31 + ARRFLIX recolour (04 §3e) | applied, then silently overwritten by a sibling Cineplex POST (race rule, see 04 §3b) |
| even later | NeutralFin v1.3.0 | applied, but a sibling Cineplex POST overwrote it minutes later (see doc 11 headline finding); also, our `bg:#000 !important` rules clamped its gradient flat so the brief render that DID land looked wrong |
| now | **Cineplex v1.0.6** | active (verified live this audit) |
### Race-rule reminder
`/System/Configuration/branding` takes a complete object on every
POST; whichever POST lands last wins. Per 04 §3b: any agent or script
touching this endpoint MUST `GET → edit-only-its-fields → POST` and
the branding POST must be the **last** in any sequence.
### Detail-page fix rollback
If §7's CSS lands and looks wrong, remove the three new blocks from
`CustomCss` and POST `branding`. The §7 proposal is purely additive
(no rule removal); revert is a clean delete.
---
## 10. What was NOT touched during this audit
- No POST to `/System/Configuration/branding`.
- No edit to `web-overrides/index.html` or the bind-mounted
`/jellyfin/jellyfin-web/index.html`.
- No `docker compose` action, no container restart.
- No git commit on `snapshots/`, no tag movement.
- All inspections were `curl` GET (`/Branding/Configuration` +
`/System/Configuration/branding`) and `docker exec jellyfin sh -c`
bounded to `cat`/`grep`/`wc`/`ls`.
---
## 11. Sign-off
- **Auditor:** s8n (audit pass, 2026-05-08)
- **Live theme at audit time:** Cineplex v1.0.6 (verified —
`/Branding/Configuration` returns `MRunkehl/cineplex@v1.0.6`)
- **Top likely cause of detail-page black band:** Finity (Cineplex's
parent) ships `--detail-page-backdrop-offset: 17%` by design. Our
`bg:#000 !important` rules turn that intentionally-clear 17vw band
into a hard-black slab. The Finity `mask.png` overlay would have
softened it into a gradient if it loads — worth a DevTools network
check.
- **Recommended forward path:** STAY on Cineplex + apply §7
detail-page CSS (full-bleed backdrop + linear-gradient overlay,
scoped to `body.itemDetailPage`).
- **Personal-tweak portability:** **HIGH** for 11 of 14 blocks; **MEDIUM/LOW**
for the 3 `bg:#000` blocks (must be scoped/dropped on theme swap).
- **Next step:** owner reviews this doc + screenshots the current
detail-page band, decides whether to apply §7. No work on the live
server until that review.