doc 14: theme audit + detail-page backdrop diagnosis (read-only)
This commit is contained in:
parent
6614911432
commit
c4ac896342
3 changed files with 836 additions and 0 deletions
45
compose-dev/docker-compose.yml
Normal file
45
compose-dev/docker-compose.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Jellyfin DEV — second instance for theme/branding experimentation
|
||||
# Deploy path on nullstone: /opt/docker/jellyfin-dev/
|
||||
# Domain: dev.arrflix.s8n.ru (LAN-only via Pi-hole local DNS + no-guest middleware)
|
||||
#
|
||||
# Purpose:
|
||||
# - Isolated playground for trying themes (Cineplex, ElegantFin, NeutralFin, ...)
|
||||
# without touching the live arrflix.s8n.ru that real users (marco, house, guest, 5)
|
||||
# are watching.
|
||||
# - Same media library mounted READ-ONLY so dev sees the same titles but cannot
|
||||
# mutate the on-disk library.
|
||||
# - Separate config/cache so first-run wizard, accounts and branding live here only.
|
||||
# - LAN-only: no-guest middleware on router; do NOT publish to WAN.
|
||||
#
|
||||
# Image pinned to 10.10.3 to match prod for theme parity. Bump prod first, then
|
||||
# match here, never the other way around.
|
||||
|
||||
services:
|
||||
jellyfin-dev:
|
||||
image: jellyfin/jellyfin:10.10.3
|
||||
container_name: jellyfin-dev
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
userns_mode: "host"
|
||||
environment:
|
||||
- TZ=Europe/London
|
||||
- JELLYFIN_PublishedServerUrl=https://dev.arrflix.s8n.ru
|
||||
volumes:
|
||||
- /home/docker/jellyfin-dev/config:/config
|
||||
- /home/docker/jellyfin-dev/cache:/cache
|
||||
- /home/user/media:/media:ro
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy"
|
||||
- "traefik.http.routers.jellyfin-dev.rule=Host(`dev.arrflix.s8n.ru`)"
|
||||
- "traefik.http.routers.jellyfin-dev.entrypoints=websecure"
|
||||
- "traefik.http.routers.jellyfin-dev.tls=true"
|
||||
- "traefik.http.routers.jellyfin-dev.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.jellyfin-dev.middlewares=security-headers@file,no-guest@file"
|
||||
- "traefik.http.services.jellyfin-dev.loadbalancer.server.port=8096"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
174
docs/12-dev-instance.md
Normal file
174
docs/12-dev-instance.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# 12 — Jellyfin DEV instance for theme experimentation
|
||||
|
||||
A second Jellyfin container, `jellyfin-dev`, runs alongside prod on
|
||||
nullstone. Same media library (read-only), separate config/cache/users,
|
||||
separate domain. LAN-only by design — you can break it freely without
|
||||
real users (marco, house, guest, 5) noticing.
|
||||
|
||||
---
|
||||
|
||||
## Architecture diff
|
||||
|
||||
| Aspect | Prod | Dev |
|
||||
|-------------------|-------------------------------------|-------------------------------------------|
|
||||
| Container | `jellyfin` | `jellyfin-dev` |
|
||||
| Image | `jellyfin/jellyfin:10.10.3` | `jellyfin/jellyfin:10.10.3` (must match) |
|
||||
| Compose path | `/opt/docker/jellyfin/` | `/opt/docker/jellyfin-dev/` |
|
||||
| Config dir | `/home/docker/jellyfin/{config,cache}` | `/home/docker/jellyfin-dev/{config,cache}` |
|
||||
| Media mount | `/home/user/media:/media:ro` | `/home/user/media:/media:ro` (SAME, RO) |
|
||||
| Domain | `arrflix.s8n.ru` | `dev.arrflix.s8n.ru` |
|
||||
| Pi-hole DNS | `dns.hosts` in pihole.toml | `dns.hosts` in pihole.toml (added 2026-05-08) |
|
||||
| Traefik router | `Host(arrflix.s8n.ru)` | `Host(dev.arrflix.s8n.ru)` |
|
||||
| Cert | LE DNS-01 (Gandi) | LE DNS-01 (auto-issued on first request) |
|
||||
| Middleware | `security-headers@file` only | `security-headers@file,no-guest@file` |
|
||||
| WAN exposure | Yes during WAN window (doc 09) | NEVER — LAN-only forever |
|
||||
| Internal port | `8096` | `8096` |
|
||||
| User | `1000:1000` | `1000:1000` |
|
||||
| `userns_mode` | `host` | `host` |
|
||||
| index.html shim | Bind-mounted (doc 10) | None (vanilla shell — clean theme canvas) |
|
||||
| Branding/auth | Configured | Empty — first-run wizard required |
|
||||
|
||||
The compose file lives in this repo at `compose-dev/docker-compose.yml`
|
||||
and is deployed to nullstone at `/opt/docker/jellyfin-dev/docker-compose.yml`.
|
||||
|
||||
---
|
||||
|
||||
## How to use
|
||||
|
||||
1. Open `https://dev.arrflix.s8n.ru` from any LAN/tailnet box. First visit hits the
|
||||
first-run wizard — create an admin user (use any throwaway name; nothing
|
||||
shared with prod).
|
||||
2. Add libraries pointing at the same paths prod uses:
|
||||
- `/media/movies`
|
||||
- `/media/tv`
|
||||
The library ROOTS are shared (read-only); dev will rescrape independently
|
||||
into its own `library.db`. That's intentional — dev is a clean slate.
|
||||
3. Apply a theme via Branding API or via the SPA shim (doc 10) by dropping
|
||||
files into `/opt/docker/jellyfin-dev/web-overrides/` and adding the same
|
||||
bind-mount pattern as prod (currently absent for a clean canvas).
|
||||
4. Test, watch, break. Prod remains untouched on `arrflix.s8n.ru`.
|
||||
|
||||
---
|
||||
|
||||
## Theme workflow (dev → prod)
|
||||
|
||||
When a dev theme is "shipped":
|
||||
|
||||
1. **Export branding** from dev:
|
||||
```bash
|
||||
curl -k -H "X-Emby-Token: $DEV_TOKEN" \
|
||||
https://dev.arrflix.s8n.ru/Branding/Configuration > /tmp/branding.json
|
||||
```
|
||||
2. **POST to prod**:
|
||||
```bash
|
||||
curl -k -X POST \
|
||||
-H "X-Emby-Token: *redacted*" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data @/tmp/branding.json \
|
||||
https://arrflix.s8n.ru/System/Configuration/branding
|
||||
```
|
||||
3. If the theme involves SPA-shim files (custom JS/CSS), `rsync` them from
|
||||
`dev:/opt/docker/jellyfin-dev/web-overrides/` to
|
||||
`prod:/opt/docker/jellyfin/web-overrides/` and hot-reload prod via the
|
||||
bind-mount (no container restart needed for read-only mounts on file
|
||||
change — Jellyfin will serve the new file on next request).
|
||||
|
||||
Auth tokens for dev are local to the dev instance — they'll be issued by
|
||||
the dev wizard. They DO NOT cross over.
|
||||
|
||||
---
|
||||
|
||||
## Reset / wipe dev
|
||||
|
||||
When experiments make a mess:
|
||||
|
||||
```bash
|
||||
ssh user@192.168.0.100
|
||||
cd /opt/docker/jellyfin-dev
|
||||
docker compose down
|
||||
sudo rm -rf /home/docker/jellyfin-dev/config/* /home/docker/jellyfin-dev/cache/*
|
||||
# (use the privileged-userns-host bypass if no sudo:
|
||||
# docker run --rm --privileged --userns=host -v /home/docker:/h alpine \
|
||||
# sh -c 'rm -rf /h/jellyfin-dev/config/* /h/jellyfin-dev/cache/*')
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
First-run wizard reappears. The media library is intact (read-only mount,
|
||||
unaffected).
|
||||
|
||||
---
|
||||
|
||||
## LAN-only enforcement
|
||||
|
||||
`no-guest@file` middleware (defined in `/opt/docker/traefik/config/dynamic.yml`)
|
||||
restricts source IPs to:
|
||||
- `127.0.0.0/8`
|
||||
- `192.168.0.0/24` (LAN)
|
||||
- `100.64.0.1/32` onyx, `100.64.0.2/32` nullstone, `100.64.0.4/32` office (tailnet)
|
||||
- `82.22.5.233/32` YOU500 home IP
|
||||
- `172.20.0.0/24` docker proxy gateway
|
||||
|
||||
Anyone outside that list trying `https://dev.arrflix.s8n.ru` from the WAN
|
||||
gets a Traefik 403. Even if a guest tailnet node (100.64.0.3 friend GPU)
|
||||
hits dev, no-guest blocks them — only `tag:admin` and `tag:infra` are
|
||||
allowed.
|
||||
|
||||
There is **no plan** to expose dev publicly. If you need to test something
|
||||
WAN-shaped, do it on prod inside the WAN window (doc 09) — never widen
|
||||
dev's allowlist.
|
||||
|
||||
---
|
||||
|
||||
## Risks and non-risks
|
||||
|
||||
- **Read-only media mount.** Dev cannot write to `/home/user/media`.
|
||||
Theme experiments cannot accidentally rename, delete or scramble files.
|
||||
- **Separate library.db.** Dev rescrapes from scratch. If a metadata
|
||||
experiment in dev produces bad results, it never touches prod metadata.
|
||||
- **Same Traefik instance.** Both routers share the proxy network and the
|
||||
one Traefik. A misconfigured label on dev could *theoretically* shadow
|
||||
prod's router, but the rules are `Host(dev.arrflix.s8n.ru)` vs
|
||||
`Host(arrflix.s8n.ru)` — disjoint. Sanity-check after any compose edit
|
||||
with `curl -kI https://arrflix.s8n.ru/`.
|
||||
- **Same image tag.** Bumping prod to a new Jellyfin version means
|
||||
bumping dev too; do prod first, then sync dev. Never test a version
|
||||
bump on dev and forget to mirror prod — the API surface might drift.
|
||||
- **No shared sessions.** Tokens, users, watch progress, playlists are
|
||||
100% isolated. A test admin in dev cannot act on prod, and vice versa.
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
```
|
||||
# Status
|
||||
ssh user@192.168.0.100 'docker ps --filter name=jellyfin'
|
||||
|
||||
# Logs
|
||||
ssh user@192.168.0.100 'docker logs jellyfin-dev --tail 100 -f'
|
||||
|
||||
# Restart
|
||||
ssh user@192.168.0.100 'cd /opt/docker/jellyfin-dev && docker compose restart'
|
||||
|
||||
# Stop / start
|
||||
ssh user@192.168.0.100 'cd /opt/docker/jellyfin-dev && docker compose down'
|
||||
ssh user@192.168.0.100 'cd /opt/docker/jellyfin-dev && docker compose up -d'
|
||||
|
||||
# Health check from onyx
|
||||
curl -kI https://dev.arrflix.s8n.ru
|
||||
# expect HTTP/2 302, location: web/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DNS pin path used
|
||||
|
||||
The dev hostname was added to Pi-hole's `dns.hosts` array in
|
||||
`/opt/docker/pihole/etc-pihole/pihole.toml` (alongside the existing
|
||||
LAN-only entries) and Pi-hole was restarted to pick up the change.
|
||||
The legacy `custom.list` file is still present but is no longer the
|
||||
authoritative source — `dns.hosts` in `pihole.toml` is what
|
||||
`pihole-FTL` actually consults.
|
||||
|
||||
If `dev.arrflix.s8n.ru` ever fails to resolve, restart Pi-hole and
|
||||
re-check the `dns.hosts` array.
|
||||
617
docs/14-theme-audit.md
Normal file
617
docs/14-theme-audit.md
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
# 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:** Claude (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.
|
||||
Loading…
Reference in a new issue