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.
261 lines
18 KiB
Markdown
261 lines
18 KiB
Markdown
# 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.
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
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 #1–3 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":
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:** 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.
|