ARRFLIX/docs/11-neutralfin-audit.md
s8n 7a0eb93a0a doc 11: NeutralFin render audit (read-only)
Live render audited at https://arrflix.s8n.ru. Owner believed NeutralFin
was applied; live /Branding/Configuration shows Cineplex v1.0.6 — a
sibling POST won the race per §3b operational rule. Audit covers visual
contract, drift table for every CustomCss + critical-path index.html
rule, NeutralFin variable conflicts, logo aspect ratio (235x85, fits),
ranked fix list. No state mutated; recommendation pending owner sign-off.
2026-05-08 04:18:07 +01:00

18 KiB
Raw Blame History

11 — NeutralFin Render Audit

Status: read-only audit, executed 2026-05-08 against https://arrflix.s8n.ru (Jellyfin 10.10.3 on nullstone). Owner reported the live render "doesn't look as good as it should" relative to the NeutralFin demo screenshots. Scope: identify why the current CustomCss

  • inline critical-path <style> block fail to deliver the polished NeutralFin aesthetic. No fixes applied. No state mutated.

Headline finding (must read first). The audit was commissioned against the live NeutralFin render, but at audit time /Branding/Configuration returned a CustomCss whose @import is MRunkehl/cineplex@v1.0.6 and whose accompanying personal-tweak blocks reference Cineplex / Netflix-red, not NeutralFin. A single earlier curl in the audit session momentarily showed a NeutralFin import block, then three follow-up cache-busted curls reverted to Cineplex. This matches the §3b race rule in 04-theming-and-users.md: the branding endpoint takes a complete object on every POST; whichever POST lands last wins. A NeutralFin payload was applied and then overwritten by a sibling Cineplex POST. The render the owner is seeing is therefore not NeutralFin — it is Cineplex with a stale set of personal tweaks layered on top, plus a critical-path <style> in index.html that pre-paints the page in Netflix red. That mismatch alone is the single highest-impact root cause; everything else below is secondary.


1. Visual contract — what NeutralFin should look like

Sourced from https://github.com/KartoffelChipss/NeutralFin README and the upstream minified CSS at https://cdn.jsdelivr.net/gh/KartoffelChipss/NeutralFin@1.3.0/theme/neutralfin-minified.css.

Aspect NeutralFin contract
Tagline "a sleek black and grey color scheme for a more neutral and modern look"
Lineage Built on ElegantFin (GPL-2.0). Bundles Jellyfin Lucide icons for the modern icon set.
Page background Gradient between --darkerGradientPoint #131313 and --lighterGradientPoint #1e1e1e (0deg). NOT pure #000.
Card background --cardBackgroundGradient (same two-stop dark gradient). NOT pure #000.
Header / drawer surface --headerColor rgba(40,40,40,0.5) and --drawerColor rgba(40,40,40,0.9) — translucent over a blurred backdrop.
Accent (UI) --uiAccentColor rgb(130,130,130)mid-grey, not coloured.
Active / focus tint --activeColor rgb(100,100,100) — slightly darker grey.
Borders --borderColor rgb(71,71,71) (mid), --darkerBorderColor rgb(51,51,51), --lighterBorderColor rgba(255,255,255,0.2) — subtle hierarchy across cards/sections.
Selector bg --selectorBackgroundColor rgb(60,60,60).
Text --textColor rgb(209,213,219) (off-white, not pure white). --dimTextColor rgb(156,163,175).
Play button --btnMiniPlayColor rgb(41,154,93) — the only saturated colour, used on play CTAs.
Delete button --btnDeleteColor rgb(169,29,29) — saturated red, but ONLY for destructive confirms.
Recommended pairing Owner enables backdrops in Jellyfin (Display → Show backdrops). The translucent header/drawer relies on having something to blur.
Minimum JF Not stated. Demos shown on JF 10.11+. We're on 10.10.3 — selectors should largely match (Lucide icon refs may degrade gracefully).

Net visual impression: subdued monochrome, soft gradients, mid-grey accents, restrained borders, off-white text. The whole thing is a texture of dark greys, not a flat black.

NeutralFin defines none of --primary-background-color, --background-color, --background-color-alpha, --card-background-color, or --mui-palette-primary-main. Those are Jellyfin's own variables; NeutralFin lets Jellyfin's defaults pass through and skins via its own --darkerGradientPoint / --lighterGradientPoint / --headerColor / --drawerColor set.


2. Live state at audit time

/Branding/Configuration (anon) and /System/Configuration/branding (authed) both return identical payload, 25 225 chars of CustomCss. Theme banner comments name Cineplex v1.0.6. Sole @import is https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css.

!important count in CustomCss: 17.

#E50914 occurrences in CustomCss: 0.

Inline critical-path <style> block in /jellyfin/jellyfin-web/index.html (bind-mounted from web-overrides/index.html, 93 lines total): forces #000000 on shell wrappers AND #E50914 on .raised, .button-submit, .emby-button[type=submit], button[type=submit]. 1 occurrence of #E50914, 8 occurrences of ARRFLIX (title, shim, comments).

ARRFLIX wordmark PNG embedded in CustomCss: 235 × 85 px, aspect ratio 2.765.


3. Drift table — every rule in current CustomCss + index.html

For each block, classify as KEEP (compatible with NeutralFin), DROP (legacy / harmful), or MODIFY (needs adjustment for NeutralFin). Assumes the owner's intent was to be on NeutralFin.

# Source Block Classification Reason
1 CustomCss @import cineplex@v1.0.6 DROP Wrong theme entirely. Owner wants NeutralFin. Replace with @import KartoffelChipss/NeutralFin@1.3.0/theme/neutralfin-minified.css.
2 CustomCss #castCollapsible, #guestCastCollapsible { display:none } KEEP Personal preference, theme-agnostic. NeutralFin doesn't redefine these.
3 CustomCss .adminDrawerLogo img { content: url(<ARRFLIX 235×85 PNG>) } KEEP NeutralFin defines no rule for this selector (verified). Override stands. Split-rule form (per §3a) preserved.
4 CustomCss .pageTitleWithLogo { background-image: url(<same PNG>) } KEEP Same; NeutralFin doesn't touch this selector.
5 CustomCss .btnQuick { display:none } KEEP Server-side disable in §4g still in effect. CSS belt-and-braces is fine on any theme.
6 CustomCss .headerSyncButton/.headerCastButton/.headerUserButton { display:none } KEEP NeutralFin sets width/height/border on .headerUserButton — those rules become moot under display:none, no conflict.
7 CustomCss .MuiSlider-thumb { color/bg/border:#fff } (+ hover halo) KEEP, but reconsider NeutralFin doesn't theme MUI sliders. White thumbs work, but they're a Cineplex-era decision when the rest of the chrome was Netflix-red/white/black. Against a monochrome grey theme, mid-grey thumbs would read more native. Low priority.
8 CustomCss :root { --primary-background-color:#000 !important; --background-color:#000 !important } DROP High-impact harm. NeutralFin's whole aesthetic depends on the page background showing the gradient between #131313 and #1e1e1e. Forcing #000 flattens that gradient to a single pure black, killing the depth NeutralFin was designed to deliver. Owner literally cannot see NeutralFin's intent while these vars are clamped.
9 CustomCss html, body, .preload, .skinBody, .mainDrawerHandle { background-color:#000 !important } DROP Same — clobbers NeutralFin's gradient surface. NeutralFin paints page bg via the gradient applied at body / wrapper level; this rule forces solid black underneath.
10 CustomCss .skinHeader/.skinHeader.semiTransparent/.skinHeader-withBackground/.mainAnimatedPages/#reactRoot/.dashboardDocument { background:#000 !important } DROP Same. Notably .skinHeader.semiTransparent is the surface NeutralFin's --headerColor rgba(40,40,40,0.5) translucency renders OVER. Forcing #000 underneath defeats the blur/translucency effect — the header becomes a flat black bar instead of a glassy panel.
11 CustomCss mypreferencesmenu :has() block KEEP Personal tweak, theme-agnostic. JF 10.10.3 supports :has() in modern browsers; if a user is on Firefox <121 they'll see the link, but no harm to NeutralFin.
12 CustomCss .countIndicator { display:none } KEEP, but note NeutralFin sets background:#1f50bd; border:var(--defaultLighterBorder) on this selector. Hiding it is fine and is what owner asked for; the NeutralFin styling becomes irrelevant under display:none.
13 index.html <style> :root { --primary-background-color:#000; --background-color:#000 } (no !important) MODIFY (DROP the var lines) Same harm as row 8 but in a sneakier place: it's pre-bundle, paints before CustomCss arrives, then CustomCss row 8 keeps it pinned post-bundle. For NeutralFin to look right, both need to go.
14 index.html <style> html, body, .preload, .skinBody, .skinHeader, #reactRoot, .mainAnimatedPages { background:#000 !important; color:#fff !important } MODIFY Drop .skinHeader from the selector list (so NeutralFin's translucent header isn't pre-painted black) and consider dropping the wrapper bg overrides entirely. The color:#fff is also more saturated than NeutralFin's off-white rgb(209,213,219) — fine for pre-bundle anti-flash but needs to NOT be !important post-bundle. The !important here outranks NeutralFin's inherited text colour.
15 index.html <style> .raised, .button-submit, .emby-button[type=submit], button[type=submit] { background:#E50914 !important; color:#fff !important } DROP Critical. NeutralFin is monochrome — the play CTA is green (--btnMiniPlayColor), submits use grey accent, only --btnDeleteColor is red and only on destructive confirms. This block paints every submit button Netflix-red, including login → Sign In, settings → Save, library → Add. Owner did not ask for that on NeutralFin. This is the most jarring single visual conflict.
16 index.html <script> nukeSettings() MutationObserver + setInterval(...,1000) KEEP Targets mypreferencesmenu only; doesn't mutate styles or layout. Does fire on every DOM mutation (could be tens per second on rich pages) but the work is one querySelectorAll scoped to a narrow attribute selector. No measurable layout thrash on a non-loaded page; on heavy lists it's the sort of thing to profile but not a "looks bad" cause.
17 index.html <script> lockTitle/lockFavicon head observer + interval KEEP Cosmetic, unrelated to render quality.

4. Variable conflict report

NeutralFin variable Default Overridden by us? Effect
--darkerGradientPoint #131313 no Gradient bottom intact … but masked by row 9 body{bg:#000}
--lighterGradientPoint #1e1e1e no Gradient top intact … masked same way
--headerColor rgba(40,40,40,0.5) no Translucent header colour intact … but row 10 paints .skinHeader{bg:#000} underneath, so the alpha composes against pure black instead of the gradient. Header reads flatter than NeutralFin intends.
--drawerColor rgba(40,40,40,0.9) no OK — drawer bg unaffected by the wrapper-element rules.
--borderColor rgb(71,71,71) no OK
--uiAccentColor rgb(130,130,130) no in CustomCss; YES indirectly via index.html row 15 (every submit button forced red) Submit buttons should be grey-accented; instead they are #E50914.
--activeColor rgb(100,100,100) no OK
--textColor rgb(209,213,219) partially — index.html row 14 sets color:#fff !important on body Text is full white instead of the off-white NeutralFin uses. Subtle but cumulative.
--btnMiniPlayColor rgb(41,154,93) no Play CTA still green, OK.
--btnDeleteColor rgb(169,29,29) no Delete confirms still red, OK.
Jellyfin --primary-background-color (Jellyfin default #101010-ish) YES — row 8 + row 13 → #000 NeutralFin doesn't override this var; NeutralFin paints the gradient directly on body. Forcing --primary-background-color:#000 doesn't break NeutralFin's body gradient (NeutralFin doesn't read this var) BUT the body{bg:#000 !important} rule that lives next to it DOES, because it sets the body bg directly and beats NeutralFin's lower-specificity body rule.
Jellyfin --background-color Jellyfin default YES — row 8 + row 13 → #000 Same — variable override harmless on its own; the wrapper rule next door is the real damage.
--mui-palette-primary-main (MUI default) no OK; sliders/checkboxes keep MUI palette.

5. Logo aspect ratio

ARRFLIX wordmark PNG: 235 × 85 px, aspect 2.765 : 1.

NeutralFin (and Cineplex) target three logo containers:

  • .adminDrawerLogo img — admin sidebar drawer. Inherits sidebar width (~240 px on desktop). 235 × 85 fits naturally; replaces <img> source via content:. Match: YES.
  • .pageTitleWithLogo — masthead <div> on dashboard / login pages. In NeutralFin this <div> is sized by var(--appBarHeight) 5em (header height) and the background-image is laid out with background-size: contain (NeutralFin / ElegantFin convention). At 5 em ≈ 80 px header height a 235 × 85 image will render at ~221 × 80 — fits the header band cleanly. Match: YES, no squish, no clip.
  • .detailLogo — clear-logo on item detail pages (movies / shows). NeutralFin sizes this at width:40%; height:25vh; background-position:bottom with background-size:contain — designed for tall, near-square clear logos. A 2.765:1 wordmark will render small (height-limited by the 25vh box only at very narrow viewports; at 1080p it's width-limited at 40% = 768 px and height settles at ~278 px, well under 25vh = 270 px). Acceptable, no distortion. Match: YES.

Verdict: Logo aspect ratio is fine. Not a render-quality root cause. A 235 × 85 wordmark is on the wide end of typical Jellyfin custom logos but fits every container cleanly because both NeutralFin and Cineplex use background-size: contain on the masthead.


Read-only audit. None of these have been applied. Owner sign-off required before any branding POST.

  1. Apply NeutralFin (currently NOT applied). Replace the @import line in CustomCss to point at https://cdn.jsdelivr.net/gh/KartoffelChipss/NeutralFin@1.3.0/theme/neutralfin-minified.css. (Verify the live Branding/Configuration reflects this after the POST, and that no sibling agent is racing the endpoint — see §3b operational rule. Make this POST the LAST POST in the sequence.)
  2. Drop the pure-black background overrides in CustomCss (drift-table rows 8, 9, 10). NeutralFin's whole texture is the #131313 → #1e1e1e gradient; clamping it to #000 flattens it and is the single biggest cause of the "not as good as it should" feel.
  3. Drop #E50914 from the index.html critical-path <style> (drift-table row 15). On NeutralFin, every submit button suddenly being Netflix-red is the single most jarring visual conflict. Also drop .skinHeader from the wrapper bg list (row 14) and the --primary-background-color/--background-color #000 declarations (row 13). What stays in the critical-path <style> should be: html, body { background:#0e0e0e } (close enough to NeutralFin's gradient midpoint to avoid pre-bundle flash without clamping the gradient post-bundle) and color:#d1d5db (the off-white NeutralFin uses) — both WITHOUT !important so the theme can take over once it loads.
  4. Reconsider the white slider thumbs (row 7) once #13 land. If the owner still finds them too "Netflix" against a grey theme, change to currentColor or var(--uiAccentColor). Low priority, purely taste.
  5. Audit the !important count post-fix. Currently 17; once the black-bg wrapper rules drop, the count falls to ~10, all of which are legitimate (display:none overrides, logo content: replacements, slider thumb forces). NeutralFin's hover/focus states will then fire correctly because no !important rule is masking them.

7. Rollback note

If owner says "revert everything I had before the audit-driven fixes":

git checkout snapshot-2026-05-08-pre-elegantfin -- \
  snapshots/2026-05-08-pre-elegantfin/branding.json
# then POST that file's contents to /System/Configuration/branding
# (full restore command in snapshots/2026-05-08-pre-elegantfin/RESTORE.md)

That snapshot captures the Cineplex era state — the CustomCss in it is the same Cineplex import that's live RIGHT NOW (modulo personal-tweak appendices that were added after the snapshot). It does NOT contain a NeutralFin import, because NeutralFin was never persisted long enough to enter the canonical history; the §3e ElegantFin migration block in 04-theming-and-users.md documents an intended state that the owner had asked for but which a sibling Cineplex POST has since silently reverted.

For a clean reset to vanilla Jellyfin (no theme at all) before re-trying NeutralFin:

curl -sS -X POST -H "X-Emby-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"CustomCss":"","LoginDisclaimer":"Welcome to ARRFLIX - Private invite only service","SplashscreenEnabled":true}' \
  https://arrflix.s8n.ru/System/Configuration/branding

8. 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.
  • Read-only over SSH; only docker exec jellyfin sh -c '...' shell invocations, all bounded to wc -l / head / grep -c.

9. Sign-off

  • Auditor: Claude (audit pass, 2026-05-08)
  • Live theme at audit time: Cineplex v1.0.6 (despite doc 04 §3e claiming ElegantFin + ARRFLIX recolor; despite owner believing the state is NeutralFin)
  • Doc 04 §3e accuracy: stale — needs an §3f addendum after fixes documenting the NeutralFin migration and the race-loss that hid it.
  • Next step: owner reviews this doc, decides whether to apply the fix list in §6. No work to be done on the live server until that review.