Stock Jellyfin paints .listItem.selected and .focused in cyan
(#00a4dc) on actionSheet/selectionList/dialogContainer dropdowns.
Clashes with Cineplex red on audio + subtitle pickers.
Pick: variant 04 "Hairline ring" — 1px #E50914 outline (offset -1px
inside row) + dark near-black bg + white text. Architectural,
quietly on-brand. Applied via body.arrflix-themed scope.
Saved alternative: web-overrides/skins/selector-variant-02-red-
underline.css — variant 02 "Red underline" (matches search-input
focus). Future skin/swap option per owner. Not currently active.
Reconciled prod drift: prod overlay had been externally modified
since last snapshot (md5 2da61583 -> c62898). Pulled prod's current
overlay as new repo baseline + snapshot. Then applied variant 04 on
top -> dev md5 0ab8b258 (= prod + v4). Prod still c62898 untouched.
Image-12 incident: I'd set <video> z-index:9999, which covers the OSD
scrubber + buttons (Jellyfin's stock OSD controls live at z-index
1100-2000, above the 1000 of .videoPlayerContainer but BELOW our
9999). Drop the lift entirely. Stock z-index hierarchy already has
controls floating on top. The fix for black-screen was always
transparent ancestor backgrounds (L1+L2), never z-index.
Reorganized inject-middle-theme.py CSS string from one-line dense
concat into a triple-quoted multi-line block with header comments
explaining each section + the layer model + DO-NOT rules. Same
output bytes (verified md5 deterministic). Added long-form comment
header to JS too.
Doc 31 (new): "Theme layer model + edit guide" — comprehensive
checklist for any future CSS edit. Covers:
- Stacking order layer 0..7 with stock Jellyfin z-indexes
- The two body classes (.arrflix-themed, .arrflix-video-active)
- Specificity tiers + cascade order (L1 vs L2)
- CSS load order (inline < bundle < branding.xml)
- Recurring bug list (6 incidents now, all same anti-pattern)
- DO NOT DO foot-gun list
- 4-step smoke verify procedure
- CI gates still TODO
Snapshot bumped to md5 2da61583. Prod+dev byte-identical.
Two bugs in prior video-isolation fix (452ce68):
1. .htmlVideoPlayer wrong class — Jellyfin actually uses
.videoPlayerContainer + <video class="htmlvideoplayer"> (lowercase).
:has(.htmlVideoPlayer) never matched, so L1 :not(:has) was always
true — body never excluded from #000 paint, L2 (lower specificity
0,2,1) lost to L1 (0,3,1). Replaced :has() gate with :not(.arrflix
-video-active) — JS body class is reliable signal.
2. html background-color stayed transparent on details/video pages
despite 5 stylesheet rules saying #000 !important. Headless Chrome
reported computed bg as rgba(0,0,0,0) — root canvas propagation
bug or cascade quirk. Fix: JS shim sets inline style on
<html> with !important. Inline styles win against any stylesheet.
Result: html paints #000 (so letterbox bars are black not white),
body transparent during playback (L2 wins on equal specificity via
source order), videoPlayerContainer + <video> transparent → frames
visible. Off-video: body opaque #000 (L1 fires).
Verified DOM probe: html bg rgb(0,0,0), body rgba(0,0,0,0) when
arrflix-video-active. Both flip when class removed.
Two-layer defense for the recurring "black screen during playback"
bug class (5+ occurrences in 24h per doc 26/28/30):
L1 (prevention): scope every black-bg rule with
:not(:has(.htmlVideoPlayer)):not(:has(#videoOsdPage)) so the rules
self-disable while a player is in the DOM. Covers body,
#reactRoot, .skinBody, .backgroundContainer, .mainAnimatedPage,
.mainAnimatedPages, .pageContainer.
L2 (override): when JS-toggled body.arrflix-video-active is set,
high-specificity (0,3,2 + tag) transparent rule wins against any
ancestor opaque-bg rule (including future regressions someone adds
without scoping). Covers all known wrappers, the
videoPlayerContainer + videoPlayerContainer-onTop, #videoOsdPage,
.libraryPage, .htmlVideoPlayer.
L3 (z-index lift): force .htmlVideoPlayer + child <video> to
z-index:9999 + isolation:isolate during playback so it floats above
any opaque ancestor that still leaks through.
Verified in playwright: with arrflix-video-active + .htmlVideoPlayer
mounted, all 7 ancestors return rgba(0,0,0,0). Without — all 7
return rgb(0,0,0). Self-disabling works.
Lesson reinforced (doc 30 roadmap open): add darkPct assertion to
bin/headless-test-v2.py + xmllint CI gate. Five recurrences without
those gates says we keep relearning this. TODO next.
Grey #101010 stripe at bottom of pages: jellyfin-web theme.css:44-50
sets .backgroundContainer + html to #101010, neither Cineplex nor
prior overlay overrode it. Added rule forcing #000 across html, body,
.backgroundContainer, .skinBody, .mainAnimatedPages, .pageContainer,
#reactRoot under body.arrflix-themed.
Search input: stock cyan focus ring (#00a4dc, theme.css:262-272)
swapped for borderless slab + 2px red bottom on focus + soft red
box-shadow halo. Cineplex/Netflix-faithful.
Movies/Series nav: variant E "cinematic glow" picked from preview.
Active state = red text + text-shadow halo + font-weight 700. JS
hash matcher toggles .arrflix-nav.active when location.hash matches
#/movies.html or #/tv.html (and short forms). hashchange listener +
existing setInterval keep state in sync.
My Media row: .homePage .homeSectionsContainer .verticalSection.section0
hidden — matches prod which had it hidden via different mechanism.
Snapshot at snapshots/2026-05-09-v6-stable/index.html bumped to
md5 2c8f5d5f7c99611fa93d15c34fbe35d1; same as prod and dev.
Stock Jellyfin shows .headerBackButton on library pages
(Movies/Series). With our nav links already in headerLeft, the back
arrow is redundant clutter and confuses the home button intent.
Add .headerBackButton to the existing display:none rule under
body.arrflix-themed. Verified visually on dev (md5 c99aca0f), then
shipped to prod with overlay swap (md5 364cc890 -> c99aca0f). Both
sides byte-identical.
Snapshot at snapshots/2026-05-09-v6-stable/index.html updated.
Owner pronounced "near perfect". Save current state as the rollback
target. Replace older 2026-05-08-pre-elegantfin snapshot.
Snapshot md5 364cc890c58f02d07cf50b43b31a48f0 — matches both prod
and dev deployed overlay.
Doc 30 lists every file/path-of-record + rollback procedure +
remaining roadmap items.
Tag this commit v6-stable-2026-05-09 after push.