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.
18 KiB
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/Configurationreturned aCustomCsswhose@importisMRunkehl/cineplex@v1.0.6and 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 in04-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>inindex.htmlthat 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 viacontent:. Match: YES..pageTitleWithLogo— masthead<div>on dashboard / login pages. In NeutralFin this<div>is sized byvar(--appBarHeight) 5em(header height) and thebackground-imageis laid out withbackground-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 atwidth:40%; height:25vh; background-position:bottomwithbackground-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.
6. Recommended fix list (impact-ranked, top = biggest visual win)
Read-only audit. None of these have been applied. Owner sign-off required before any branding POST.
- Apply NeutralFin (currently NOT applied). Replace the
@importline in CustomCss to point athttps://cdn.jsdelivr.net/gh/KartoffelChipss/NeutralFin@1.3.0/theme/neutralfin-minified.css. (Verify the liveBranding/Configurationreflects 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.) - Drop the pure-black background overrides in CustomCss
(drift-table rows 8, 9, 10). NeutralFin's whole texture is the
#131313 → #1e1e1egradient; clamping it to#000flattens it and is the single biggest cause of the "not as good as it should" feel. - Drop
#E50914from 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.skinHeaderfrom the wrapper bg list (row 14) and the--primary-background-color/--background-color #000declarations (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) andcolor:#d1d5db(the off-white NeutralFin uses) — both WITHOUT!importantso the theme can take over once it loads. - Reconsider the white slider thumbs (row 7) once #1–3 land. If
the owner still finds them too "Netflix" against a grey theme,
change to
currentColororvar(--uiAccentColor). Low priority, purely taste. - Audit the
!importantcount 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!importantrule 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.htmlor the bind-mounted/jellyfin/jellyfin-web/index.html. - No
docker composeaction, 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 towc -l/head/grep -c.
9. Sign-off
- Auditor: s8n (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.