Token 76858153...f8b1 was committed across 9 docs + 1 snapshot RESTORE.md and exposed via the brief public window of this repo. Replaced with `<JELLYFIN_API_TOKEN>` placeholder. WARNING: this commit only redacts HEAD — the token remains in git history. Anyone who cloned during the public window has the full value. Treat the old token as compromised and rotate at Jellyfin Dashboard > API Keys. Original value backed up to private s8n/secrets/ARRFLIX/.
866 lines
41 KiB
Markdown
866 lines
41 KiB
Markdown
# 04 — Theming and Users
|
||
|
||
Status: re-themed 2026-05-08 against `https://arrflix.s8n.ru` (Jellyfin 10.10.3
|
||
on nullstone). Active theme: **Cineplex v1.0.6** (Netflix-faithful). Replaced
|
||
ElegantFin v25.12.31 the same day after a Netflix-fidelity-driven survey.
|
||
Scope: visual theme, server-side branding, multi-user UX prep, SyncPlay,
|
||
maintenance/revert. LAN-only constraints preserved (no public-facing changes).
|
||
|
||
> Hostname note: this site is being renamed `tv.s8n.ru` → `arrflix.s8n.ru`
|
||
> in the same session. The Jellyfin API endpoints don't care about
|
||
> hostname — they're served by the same container. All `curl` examples
|
||
> below are reachable as either `https://tv.s8n.ru/...` (legacy) or
|
||
> `https://arrflix.s8n.ru/...` (new), as long as Traefik has a SNI cert
|
||
> for the name. Internal pin: both names should resolve to `192.168.0.100`
|
||
> (see CLAUDE.md memory `feedback_s8n_hosts_override.md`). If a
|
||
> hostname's DNS or cert isn't up yet, use
|
||
> `--resolve tv.s8n.ru:443:192.168.0.100` on curl — that's how this
|
||
> re-theming was applied while `arrflix.s8n.ru` was still missing a cert.
|
||
|
||
---
|
||
|
||
## 1. Theme decision: ElegantFin v25.12.31 + ARRFLIX recolor (current)
|
||
|
||
**As of 2026-05-08 (later in the day), the active theme is ElegantFin
|
||
v25.12.31 with the Netflix-red `#E50914` accent recolored over the
|
||
default Jellyseerr-blue/violet palette and the ARRFLIX wordmark logo
|
||
preserved.** See §3e for the migration details and §1.x ("Previous
|
||
themes") below for the Cineplex history that preceded it.
|
||
|
||
### Candidates surveyed (2026-05-08)
|
||
|
||
| Theme | Type | Last commit | License | Netflix-fidelity | JF 10.10.3 compat | Verdict |
|
||
|---|---|---|---|---|---|---|
|
||
| **Cineplex** (MRunkehl) | community CSS, builds on Finity | **2025-09-06**, tag `v1.0.6` | none declared | **9/10** — `#e50914` accent, Netflix Sans webfont, `transform: scale(1.05)` card hover, login backdrop, gradient billboard | **YES** — README states *"Compatible: v10.10.7 and higher"*; renders on 10.10.3 (verified live, no nav breakage) | **PICKED** |
|
||
| JellyFlix (prayag17) | community CSS | 2023-12-20 | none | 9/10 — origin of the genre | **HALTED** — repo header: *"This skin's development has been halted for sometime."* Confirmed broken post-10.11. Risky on 10.10.3 too. | rejected |
|
||
| DarkFlix v5.1 (DevilsDesigns) | fork of JellyFlix | 2024-06 | GPL-3.0 | 8/10 | only states 10.8.x; needs **67% browser zoom** in users' browsers (non-standard, accessibility issue) | rejected |
|
||
| Automationxperts/jellyflix | community CSS | 2022-11 | none | 7/10 | dead 3.5y, untested on 10.10 | rejected |
|
||
| **ElegantFin v25.12.31** (lscambo13, previous) | community CSS | 2026-04-30 | GPL-2.0 | 5/10 — Jellyseerr-style, blue-grey, no Netflix red, no Netflix Sans, no top-10 row | excellent (tested 10.11.5) | de-themed — wrong aesthetic for this brief |
|
||
| Theme Park (jellyfin pack) | multi-app CSS | active | n/a | n/a — no `netflix` preset for jellyfin (only dracula/nord/hotline/plex) | n/a | not applicable |
|
||
| zombB / NetfliFin / Finetwo | mostly fork-style replacement of jellyfin-web | varies | varies | n/a | requires image swap or JS injector | DQ — violates "pure CSS, no image swap, no plugins" constraint |
|
||
| Ultrachromic (CTalvio) | community CSS | "selectively maintained" | varies | 6/10 — accent-tunable but no Netflix preset | unknown | not Netflix enough |
|
||
|
||
### Previous themes
|
||
|
||
The two sub-sections below ("Why Cineplex won", "Tradeoffs", "What it
|
||
looks like", "Theme history") are kept verbatim from when Cineplex was
|
||
the active theme (earlier on 2026-05-08, before the ElegantFin migration
|
||
documented in §3e). They remain useful as the reasoning trail for the
|
||
final brand brief — Netflix-faithful was the goal, Cineplex was the
|
||
purest expression of that, and the current ElegantFin + recolor stack
|
||
is a deliberate tradeoff toward "more polished browsing UI" while
|
||
keeping the Netflix-red accent.
|
||
|
||
#### Why Cineplex won (historical)
|
||
|
||
1. **It is actually Netflix.** The CSS literally embeds Netflix Sans
|
||
(`https://assets.nflxext.com/ffe/siteui/fonts/netflix-sans/v3/...`) and
|
||
uses Netflix's exact `#E50914` for `--accent` / `--focus-color`. Card
|
||
hover applies `transform: scale(1.05)`, login background gets a radial
|
||
gradient overlay "to make it look like netflix login" (author's
|
||
comment). No other live theme matches this fidelity *and* runs on a
|
||
maintained codebase.
|
||
2. **It targets our Jellyfin series.** Header line of `cineplex.css`
|
||
reads `Compatible: v10.10.7 and higher`. We're on 10.10.3 — same
|
||
minor series, ABI-compatible for selectors. Verified live: page
|
||
loads, navigation works, no broken layouts.
|
||
3. **Single `@import` line.** Zero ops overhead. The CSS imports two
|
||
transitive deps internally (`finity-complete.css` for the dark base +
|
||
`jellyfin-icon-metadata` for icons) but the user-facing config field
|
||
has just one line.
|
||
4. **Pinned to immutable tag** `@v1.0.6`. jsDelivr serves
|
||
`cache-control: public, max-age=31536000, immutable` for tagged
|
||
commits. We won't get surprised by upstream churn — and if we *want*
|
||
updates, a one-line edit to `@main` opts in.
|
||
5. **Companion `cineplex.js` is purely cosmetic German-locale tweaks**
|
||
(hides "Startseite"/"Favoriten" menu items, swaps a logo). Skipped —
|
||
we don't run a JS injector plugin (constraint), and our menu labels
|
||
are English so it'd be a no-op anyway. Theme works fine without it.
|
||
6. **Cast/crew hide rule still appended** at the bottom of `CustomCss`,
|
||
exactly as before.
|
||
|
||
#### Tradeoffs (honest list, Cineplex era)
|
||
|
||
- **License: none.** Cineplex doesn't declare one. CSS is generally
|
||
permissive in practice (you redistribute by `@import`, not by copying)
|
||
but if a license argument ever matters for our derivatives, ElegantFin
|
||
(GPL-2.0) is cleaner.
|
||
- **Bus factor: 1.** Single author (Maverick Runkehl), 0 stars, last
|
||
commit Sep 2025. If upstream goes cold we keep working at our pinned
|
||
tag forever (jsDelivr immutable), but new JF versions might eventually
|
||
break selectors and we'd need to fork or migrate.
|
||
- **Netflix Sans license note.** The font files are loaded from
|
||
Netflix's own CDN, not bundled. If Netflix changes that path or rate-
|
||
limits non-netflix.com referers, we'd fall back to system-ui (also
|
||
declared in the stack). Acceptable.
|
||
- **Theme footer.** Cineplex doesn't add a brand stamp, so users see no
|
||
"Cineplex" tag — cleaner than ElegantFin's footer label was.
|
||
|
||
#### What Cineplex looked like (live, post-apply)
|
||
|
||
- **Background:** `#181818` (Finity base) — Netflix-black.
|
||
- **Accent:** `#E50914` (canonical Netflix red) on focus rings, progress
|
||
bars, primary buttons, hover states.
|
||
- **Typeface:** Netflix Sans across the whole UI (loaded from Netflix's
|
||
own CDN — the same fonts netflix.com itself serves). Subtitles also
|
||
use Netflix Sans Medium.
|
||
- **Cards:** rounded ~6px, hover scales to 1.05× with subtle shadow lift.
|
||
- **Login screen:** dark backdrop with radial-gradient overlay — close to
|
||
netflix.com's sign-in page.
|
||
- **No theme-brand footer label** any more.
|
||
|
||
#### Theme history
|
||
|
||
| Date | Theme | Version | Why changed |
|
||
|---|---|---|---|
|
||
| 2026-05-08 (earlier today) | ElegantFin | v25.12.31 | Initial Jellyfin theming pass. Picked for activity + safety (most actively maintained CSS in the ecosystem). |
|
||
| 2026-05-08 (mid-day) | **Cineplex** | **v1.0.6** | Owner asked for the most Netflix-faithful theme available. ElegantFin's Jellyseerr aesthetic (blue-grey, no red) is too far from Netflix; Cineplex is purpose-built for this look and explicitly targets the 10.10 series we're on. JellyFlix (the genre's elder) is halted. |
|
||
| 2026-05-08 (later, current) | **ElegantFin + ARRFLIX recolor** | **v25.12.31** + `#E50914` accent overrides | Owner liked Cineplex's Netflix accent but preferred ElegantFin's polished browsing UI. Best of both: ElegantFin's layout/typography + ARRFLIX brand red overrides. Snapshot tag for rollback: `snapshot-2026-05-08-pre-elegantfin`. See §3e. |
|
||
|
||
Rollback paths:
|
||
- To Cineplex (Netflix-faithful): apply `snapshots/2026-05-08-pre-elegantfin/branding.json` per `snapshots/2026-05-08-pre-elegantfin/RESTORE.md`.
|
||
- To plain ElegantFin (no recolor): see §6b.
|
||
- To vanilla Jellyfin: see §6b.
|
||
|
||
---
|
||
|
||
## 2. How it was applied
|
||
|
||
### Branding API (Cineplex, applied 2026-05-08)
|
||
|
||
```bash
|
||
TOKEN=<JELLYFIN_API_TOKEN>
|
||
|
||
cat > /tmp/branding.json <<'EOF'
|
||
{
|
||
"LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.",
|
||
"CustomCss": "/* Cineplex v1.0.6 — Netflix-faithful theme by MRunkehl, pinned tag (immutable on jsDelivr) */\n/* Compat: Jellyfin 10.10.7+ ; we run 10.10.3 — verified rendering 2026-05-08 */\n@import url(\"https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css\");\n\n/* Hide Cast & Crew + Guest Stars sections globally (preserved 2026-05-08) */\n#castCollapsible, #guestCastCollapsible { display: none !important; }\n",
|
||
"SplashscreenEnabled": true
|
||
}
|
||
EOF
|
||
|
||
# Note: arrflix.s8n.ru didn't have a Traefik SNI cert at apply-time, so
|
||
# we sent the request to the legacy SNI tv.s8n.ru and pinned its address
|
||
# with --resolve. Either form is fine once both names have certs.
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
-X POST \
|
||
-H "X-Emby-Token: $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
--data-binary @/tmp/branding.json \
|
||
https://tv.s8n.ru/System/Configuration/branding
|
||
# expect: HTTP 204 (got HTTP 204 — applied)
|
||
```
|
||
|
||
### Verification (executed 2026-05-08)
|
||
|
||
```bash
|
||
# 1. Admin endpoint — confirms the new CustomCss is stored.
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
-H "X-Emby-Token: $TOKEN" \
|
||
https://tv.s8n.ru/System/Configuration/branding | python3 -m json.tool
|
||
# Result: HTTP 200, contains the Cineplex @import + cast/crew hide rule.
|
||
|
||
# 2. Anonymous endpoint the SPA reads at runtime — confirms what every
|
||
# browser will pull before login.
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
https://tv.s8n.ru/Branding/Configuration | python3 -m json.tool
|
||
# Result: HTTP 200, identical CustomCss to admin endpoint. ✓
|
||
|
||
# 3. The CSS asset itself on jsDelivr (sanity-check the network path).
|
||
curl -sSI "https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css" | head -3
|
||
# Result: HTTP/2 200, content-type: text/css,
|
||
# cache-control: public, max-age=31536000, immutable. ✓
|
||
|
||
# 4. SPA shell still routes (nav not broken).
|
||
curl -sSI --resolve tv.s8n.ru:443:192.168.0.100 https://tv.s8n.ru/ | head -1
|
||
# Result: HTTP/2 302 → /web/. ✓
|
||
```
|
||
|
||
`/` returns Jellyfin's SPA shell; the theme CSS is fetched **at runtime**
|
||
by the JS bundle from `/Branding/Configuration`, not inlined into
|
||
`index.html`. So `curl /` won't grep-match. The valid JSON at
|
||
`/Branding/Configuration` is the API-level confirmation. Final visual
|
||
check is a hard browser reload (Ctrl-Shift-R) on `https://tv.s8n.ru`
|
||
(or `https://arrflix.s8n.ru` once its cert is up) from the LAN — owner
|
||
will do this.
|
||
|
||
### Cache clear
|
||
|
||
Jellyfin web caches aggressively in browsers. After applying:
|
||
|
||
- Users: hard reload (Ctrl-Shift-R / Cmd-Shift-R) once.
|
||
- Server: no restart needed; CustomCss change is live on next page load.
|
||
|
||
---
|
||
|
||
## 3. Server-side branding state (as of 2026-05-08, post-Cineplex)
|
||
|
||
| Field | Value |
|
||
|---|---|
|
||
| `LoginDisclaimer` | "Welcome to ARRFLIX - Private invite only service" |
|
||
| `CustomCss` | `@import` of **Cineplex v1.0.6** from jsDelivr — pinned tag `@v1.0.6` (immutable). Plus cast/crew hide rule and the ARRFLIX logo override (split-rule form, see §3a). |
|
||
| `SplashscreenEnabled` | `true` |
|
||
|
||
`SplashscreenEnabled: true` makes Jellyfin auto-pick a backdrop from the
|
||
library and serve it at `/Branding/Splashscreen`. The web client doesn't
|
||
itself surface this; mobile/TV clients do. Harmless to leave on.
|
||
|
||
### 3a. ARRFLIX logo override — fix for triple-stacked wordmark (2026-05-08)
|
||
|
||
Initial override copied Cineplex's three-selector group verbatim and
|
||
combined `content: url(...)` with `background-image: url(...)` on every
|
||
selector. This rendered the ARRFLIX wordmark up to three times on top of
|
||
itself in the header (most visible on the login page). Two root causes,
|
||
verified against the live `/jellyfin/jellyfin-web/` bundle:
|
||
|
||
1. **`.imgLogoIcon` is a phantom selector.** A grep of every `*.js`,
|
||
`*.html` and `*.css` asset shipped with Jellyfin 10.10.3 returns zero
|
||
matches. Cineplex's upstream rule (`imgLogoIcon { content: url(...) }`
|
||
— note the missing leading dot) is itself a no-op typo. Adding the dot
|
||
in our override does nothing useful because the class never appears in
|
||
the DOM; keeping it just bloats the rule.
|
||
2. **`content: url(...)` on a `<div>` renders the image inside the
|
||
element.** `.pageTitleWithLogo` is a `<div>` (set by `setDefaultTitle`
|
||
in `73233.*.chunk.js` — see `c.classList.add("pageTitleWithLogo")`).
|
||
Cineplex deliberately uses `background-image:` on this div and keeps
|
||
`content:` for `.adminDrawerLogo img` (an `<img>`, where `content:`
|
||
replaces the source). Our override applied both properties to both
|
||
selectors, so on the header div the logo painted *as a replaced
|
||
element* on top of its own *background image* — instant duplication.
|
||
|
||
Fix: split the rule by element type, drop the phantom selector entirely.
|
||
|
||
```css
|
||
/* ARRFLIX logo override (replace Cineplex branding) — 2026-05-08
|
||
(fix: split rules, drop phantom .imgLogoIcon) */
|
||
.adminDrawerLogo img {
|
||
/* <img> in admin sidebar drawer — content: replaces src */
|
||
content: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important;
|
||
}
|
||
.pageTitleWithLogo {
|
||
/* <div> masthead on dashboard + login — bg image only, no content: */
|
||
background-image: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important;
|
||
}
|
||
```
|
||
|
||
Verified live (HTTP 204 on POST, then `GET /Branding/Configuration`):
|
||
single ARRFLIX wordmark on login, dashboard header, and admin sidebar.
|
||
Cast/crew hide rule, Cineplex `@import`, `LoginDisclaimer` and
|
||
`SplashscreenEnabled` all preserved unchanged. The bind-mounted
|
||
`/web/index.html` (favicon, `<title>ARRFLIX</title>`, `splashLogo`) was
|
||
not touched — that asset is owned by the index-patcher.
|
||
|
||
### 3b. ARRFLIX logo override fix re-applied 2026-05-08 (second time)
|
||
|
||
Re-curling `/System/Configuration/branding` after §3a was applied showed
|
||
the broken three-selector / dual-property rule was back: `.adminDrawerLogo
|
||
img, .imgLogoIcon, .pageTitleWithLogo { content: url(...); background-image:
|
||
url(...); }`. The §3a fix had been silently overwritten by a sibling agent
|
||
that POSTed a full branding payload (LoginDisclaimer + SplashscreenEnabled +
|
||
its own stale CustomCss) without first GETting the latest CustomCss. Because
|
||
the Jellyfin branding endpoint takes a complete object on every POST and
|
||
has no field-level merge, whichever POST lands last wins — there is no
|
||
locking, no ETag, no patch semantics. Fix re-applied surgically (split
|
||
selectors, drop `.imgLogoIcon`, preserve the data-URL bytes verbatim,
|
||
preserve the cast/crew hide, Cineplex `@import`, Quick Connect hide, and
|
||
both LoginDisclaimer/SplashscreenEnabled values) and re-verified via
|
||
`/Branding/Configuration`. **Operational rule:** any agent or script touching
|
||
`/System/Configuration/branding` must (a) GET first, (b) edit only the
|
||
fields it owns, (c) POST the full object, and the branding-CSS POST must
|
||
be the **last** POST in any sequence that touches this endpoint — otherwise
|
||
a later sibling POST will silently re-stack the logo. If you find yourself
|
||
about to POST branding for any reason, GET `/System/Configuration/branding`
|
||
first and confirm the override block matches the §3a skeleton before
|
||
sending.
|
||
|
||
### 3c. Header icon hide (2026-05-08): keep search, drop SyncPlay/Cast/User-menu
|
||
|
||
Top-right header had four buttons: SyncPlay (Create Group people-pair),
|
||
Cast (Chromecast triangle), Search (magnifying glass), User (account menu).
|
||
Goal: hide everything except Search. Selectors confirmed by grep against
|
||
the live JF 10.10.3 web bundle (`73233.*.chunk.js`): `headerSyncButton`,
|
||
`headerCastButton`, `headerSearchButton`, `headerUserButton` (also
|
||
`headerAudioPlayerButton` for the now-playing badge). Each is a class on
|
||
the `<button>` element, not an id, so the hide rules target the class
|
||
directly. Pure CSS appended to existing `CustomCss` via the standard
|
||
GET → edit → POST flow (no container restart, no `index.html` edit):
|
||
|
||
```css
|
||
/* Hide top-right header icons (keep Search) — 2026-05-08 */
|
||
.headerSyncButton { display: none !important; }
|
||
.headerCastButton { display: none !important; }
|
||
.headerUserButton { display: none !important; }
|
||
```
|
||
|
||
Rationale per button:
|
||
- **headerSyncButton** — SyncPlay group creation. Single-user / family box,
|
||
no co-watching workflow yet (see §5b for the full SyncPlay context).
|
||
- **headerCastButton** — Chromecast picker. No Cast targets on this LAN,
|
||
the button just opened an empty device list.
|
||
- **headerUserButton** — account/sign-out drop-down. Non-admins lose almost
|
||
every entry in this menu after §4 lockdown anyway, and admins use the
|
||
Dashboard wrench. Removing the icon eliminates the dead-end click.
|
||
- **headerSearchButton** — kept. Only navigation affordance left in the
|
||
top-right. The CSS does not touch `.headerSearchButton` so its event
|
||
handler is untouched and the icon still triggers the search drawer.
|
||
- The hamburger drawer toggle on the LEFT (`.headerHomeButton` /
|
||
`.mainDrawerButton`) is also untouched.
|
||
|
||
Verified post-write via `GET /Branding/Configuration` that all earlier
|
||
rules are still present (Cineplex `@import`, cast/crew hide, ARRFLIX
|
||
logo override, Quick Connect hide, Settings drawer hide, LoginDisclaimer,
|
||
SplashscreenEnabled). POST returned 204; same operational rule from §3b
|
||
applies — this was the last branding POST in the sequence.
|
||
|
||
### 3d. Slider thumb white + pure-black background (2026-05-08)
|
||
|
||
Two small visual nits remained after §3c. (1) The OSD scrubber and volume
|
||
slider thumbs in the video player were rendered with the Material-UI
|
||
default primary tint (blue-ish circle), clashing against the otherwise
|
||
red/white/black Cineplex palette; we want pure white circles so the thumb
|
||
reads as a neutral "where am I" indicator and not a brand colour.
|
||
(2) The page surface throughout the SPA was a near-black grey
|
||
(roughly `#181818`, inherited from Jellyfin / Cineplex defaults) rather
|
||
than true `#000000`; on OLED displays and against the Netflix-style
|
||
artwork-edge fades this looked dusty. Both fixed with pure CSS appended
|
||
to `CustomCss`, no `index.html` edit, no container restart.
|
||
|
||
**Selectors verified against the live bundle.** `osdPositionSlider` and
|
||
`osdVolumeSlider` are visible in `playback-video-index-html.*.chunk.js`
|
||
and `90742.*.chunk.js` (grep on `/jellyfin/jellyfin-web/*.js`). The
|
||
inner `.MuiSlider-thumb` class is added at runtime by MUI's React
|
||
component, so it doesn't appear as a literal in the bundle but is the
|
||
documented MUI public API surface — overriding it directly (rather than
|
||
the `--mui-palette-primary-main` CSS variable) keeps the rest of the
|
||
button/control palette unchanged. Older Jellyfin builds used
|
||
`emby-slider .sliderThumb`, included as a belt-and-braces fallback.
|
||
|
||
```css
|
||
/* Tweak: white thumbs (2026-05-08) */
|
||
.MuiSlider-thumb,
|
||
.osdPositionSlider .MuiSlider-thumb,
|
||
.osdVolumeSlider .MuiSlider-thumb,
|
||
emby-slider .sliderThumb {
|
||
color: #ffffff !important;
|
||
background-color: #ffffff !important;
|
||
border-color: #ffffff !important;
|
||
}
|
||
.MuiSlider-thumb:hover,
|
||
.MuiSlider-thumb.Mui-focusVisible,
|
||
.MuiSlider-thumb:active {
|
||
box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.16) !important;
|
||
}
|
||
|
||
/* Tweak: pure black bg (2026-05-08) */
|
||
:root {
|
||
--primary-background-color: #000000 !important;
|
||
--background-color: #000000 !important;
|
||
}
|
||
html, body, .preload, .skinBody, .mainDrawerHandle {
|
||
background-color: #000000 !important;
|
||
}
|
||
.skinHeader,
|
||
.skinHeader.semiTransparent,
|
||
.skinHeader.skinHeader-withBackground,
|
||
.mainAnimatedPages,
|
||
#reactRoot,
|
||
.dashboardDocument {
|
||
background-color: #000000 !important;
|
||
}
|
||
```
|
||
|
||
Rationale notes:
|
||
- We deliberately did **not** override `--mui-palette-primary-main` at
|
||
`:root` — that variable re-tints buttons, focus rings, and a handful
|
||
of other accents globally. Scoping to `.MuiSlider-thumb` keeps the
|
||
fix surgical.
|
||
- We kept the red track (`.MuiSlider-track`) and grey rail
|
||
(`.MuiSlider-rail`) untouched; only the draggable thumb changed.
|
||
- For the background, both the Jellyfin shell variables
|
||
(`--primary-background-color`, `--background-color`) and the concrete
|
||
wrapper elements (`html`, `body`, `.skinHeader`, `.mainAnimatedPages`,
|
||
`#reactRoot`, `.dashboardDocument`) are forced to `#000`. Belt and
|
||
braces — different views render through different wrappers and the
|
||
Cineplex import sometimes redeclares the variables.
|
||
- Screenshots: capture player chrome (scrubber + volume) and home/lib
|
||
pages on next visual sweep; if poster cards visibly lose contrast
|
||
against pure black, soften the card surface to `#0a0a0a` in a
|
||
follow-up tweak rather than raising the page surface again.
|
||
|
||
POST returned 204. Verified via `GET /Branding/Configuration`: both
|
||
new blocks present, all prior blocks (Cineplex `@import`, cast/crew
|
||
hide, ARRFLIX logo override, Quick Connect hide, Settings drawer hide,
|
||
header icon hide) preserved verbatim. Same race rule applies — this is
|
||
the last branding POST in the sequence.
|
||
|
||
### 3e. ElegantFin migration with ARRFLIX recolor (2026-05-08, current)
|
||
|
||
Later on 2026-05-08, the active theme was migrated **from Cineplex to
|
||
ElegantFin v25.12.31** while preserving the ARRFLIX brand: Netflix-red
|
||
`#E50914` accent overrides over ElegantFin's default Jellyseerr-blue/
|
||
violet palette, plus the existing ARRFLIX wordmark logo. The owner had
|
||
seen the demo at <https://lscambo13.github.io/ElegantFin/>, liked
|
||
ElegantFin's polished browsing UI more than Cineplex's purer Netflix
|
||
fidelity, and asked for the swap with the brand colour kept intact.
|
||
|
||
**Snapshot tag for rollback (committed and pushed before any change):**
|
||
`snapshot-2026-05-08-pre-elegantfin`. Captures `branding.json`,
|
||
`index.html`, `docker-compose.yml`, all per-user `displayprefs-*.json`,
|
||
`users.json`, `libraries.json`, plus `RESTORE.md` with three concrete
|
||
rollback commands. Located at `snapshots/2026-05-08-pre-elegantfin/`.
|
||
|
||
**ElegantFin tag pinned: `v25.12.31`** (latest tag at migration time;
|
||
list resolved via `git ls-remote --tags https://github.com/lscambo13/ElegantFin.git`).
|
||
jsDelivr serves tagged refs immutably with year-long cache TTL — same
|
||
no-surprise-update guarantee we had on `cineplex@v1.0.6`. To opt into
|
||
upstream churn, edit the URL to `@main`; to pin a different tag, edit
|
||
the version segment.
|
||
|
||
**ElegantFin import:**
|
||
|
||
```css
|
||
@import url("https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@v25.12.31/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css");
|
||
```
|
||
|
||
**Accent variables overridden (ARRFLIX recolor block).** ElegantFin
|
||
declares its accent palette through CSS custom properties at `:root`.
|
||
Eight variables were identified by grepping the minified theme for
|
||
`--[a-z]*` definitions and inspecting their default values; all eight
|
||
are remapped to `#E50914` (or its `rgba()` form for alpha variants):
|
||
|
||
| Variable | ElegantFin default | ARRFLIX value | What it controls |
|
||
|---|---|---|---|
|
||
| `--uiAccentColor` | `rgb(117 111 226)` (violet) | `#E50914` | Primary UI accent — most surfaces |
|
||
| `--activeColor` | `rgb(119,91,244)` (violet) | `#E50914` | Active / focused state highlights |
|
||
| `--activeColorAlpha` | `rgba(119,91,244,.9)` | `rgba(229, 9, 20, 0.9)` | Same with alpha — hover overlays |
|
||
| `--osdSeekBarPlayedColor` | `var(--textColor)` (white) | `#E50914` | Played portion of the video scrubber |
|
||
| `--checkboxCheckedBgColor` | `rgb(79,70,229)` (indigo) | `#E50914` | Checked checkboxes (settings, lib pickers) |
|
||
| `--highlightOutlineColor` | `rgb(37,99,235)` (blue) | `#E50914` | Focus / highlight outlines on cards |
|
||
| `--btnSubmitColor` | `rgb(61,54,178)` (indigo) | `#E50914` | "Submit" button background |
|
||
| `--btnSubmitBorderColor` | `rgb(117 111 226)` (violet) | `#E50914` | "Submit" button border |
|
||
|
||
Override block:
|
||
|
||
```css
|
||
:root {
|
||
--uiAccentColor: #E50914 !important;
|
||
--activeColor: #E50914 !important;
|
||
--activeColorAlpha: rgba(229, 9, 20, 0.9) !important;
|
||
--osdSeekBarPlayedColor: #E50914 !important;
|
||
--checkboxCheckedBgColor: #E50914 !important;
|
||
--highlightOutlineColor: #E50914 !important;
|
||
--btnSubmitColor: #E50914 !important;
|
||
--btnSubmitBorderColor: #E50914 !important;
|
||
}
|
||
```
|
||
|
||
Variables deliberately NOT changed:
|
||
- `--osdSeekBarThumbColor: white` — kept the explicit white-thumb rule
|
||
from §3d (white thumbs read as a neutral position indicator, not as
|
||
brand colour). The slider-thumb override in this doc's §3d still
|
||
applies.
|
||
- `--drawerColor`, `--headerColor` — kept ElegantFin's translucent
|
||
blur over its dark-blue surface; these are structural, not accent.
|
||
- `--borderColor`, `--textColor` — typography / structure, not accent.
|
||
|
||
**Logo selectors used.** ElegantFin does NOT define rules for the two
|
||
ARRFLIX logo selectors (verified by grepping the minified theme for
|
||
`adminDrawerLogo` and `pageTitleWithLogo` — zero matches), so the same
|
||
override skeleton from §3a/§3b is re-applied verbatim against the
|
||
ElegantFin base:
|
||
|
||
```css
|
||
.adminDrawerLogo img {
|
||
/* <img> in admin sidebar drawer — content: replaces src */
|
||
content: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important;
|
||
}
|
||
.pageTitleWithLogo {
|
||
/* <div> masthead on dashboard + login — bg image only, no content: */
|
||
background-image: url("data:image/png;base64,<...ARRFLIX wordmark...>") !important;
|
||
}
|
||
```
|
||
|
||
The data-URL bytes are byte-for-byte identical to the Cineplex-era
|
||
override (extracted from the snapshot's `branding.json` and re-inlined
|
||
into the new `CustomCss` payload). Both selectors are still split-rule
|
||
form (per the §3a/§3b lesson — never combine `content:` and
|
||
`background-image:` on the same selector).
|
||
|
||
**Preserved blocks** (every custom rule from the Cineplex era was
|
||
re-applied on top of ElegantFin):
|
||
- `#castCollapsible, #guestCastCollapsible { display: none }` — cast/crew sections hidden
|
||
- `.btnQuick { display: none }` — Quick Connect login button hidden
|
||
- `.headerSyncButton`, `.headerCastButton`, `.headerUserButton` — top-right header icons hidden (§3c)
|
||
- `.MuiSlider-thumb` + variants — white scrubber/volume thumbs (§3d)
|
||
- `:root { --primary-background-color: #000000; --background-color: #000000; }` and the wrapper-element rules — pure black bg (§3d)
|
||
- `mypreferencesmenu` selectors — Settings drawer entry hidden
|
||
- `.countIndicator { display: none }` — unwatched-episode count badges hidden
|
||
- `.adminDrawerLogo img` / `.pageTitleWithLogo` — ARRFLIX wordmark override
|
||
- `LoginDisclaimer` — `"Welcome to ARRFLIX - Private invite only service"` preserved
|
||
- `SplashscreenEnabled: true` — preserved
|
||
|
||
**Verification (executed 2026-05-08):**
|
||
- POST to `/System/Configuration/branding` → HTTP 204
|
||
- GET on `/Branding/Configuration` → no Cineplex `@import`, ElegantFin
|
||
`@import` present and pinned to `v25.12.31`, ARRFLIX logo data URL
|
||
intact on both selectors, all preserved blocks intact, all eight
|
||
accent variable overrides present
|
||
- HEAD on `https://arrflix.s8n.ru/` → HTTP 302 (Traefik redirect to
|
||
`web/`, baseline behaviour — proxy still serving)
|
||
|
||
**Operational notes:**
|
||
- The bind-mounted `/web/index.html` was NOT touched (sibling work owns
|
||
that file via the index-patcher). All visual changes ride on
|
||
`CustomCss` via the public `/Branding/Configuration` consumer + the
|
||
authenticated `/System/Configuration/branding` writer.
|
||
- No container restart, no `docker compose` action, no Traefik change.
|
||
- Same race rule from §3b applies — the branding POST in this migration
|
||
was the **last** POST in the sequence.
|
||
|
||
**Rollback** — see `snapshots/2026-05-08-pre-elegantfin/RESTORE.md`,
|
||
or in one shot: `git checkout snapshot-2026-05-08-pre-elegantfin --
|
||
snapshots/2026-05-08-pre-elegantfin/branding.json` then POST it back
|
||
to `/System/Configuration/branding`.
|
||
|
||
---
|
||
|
||
## 4. Multi-user UX prep
|
||
|
||
### 4a. Library inventory
|
||
|
||
| Library | Type | ItemId |
|
||
|---|---|---|
|
||
| Movies | movies | `f137a2dd21bbc1b99aa5c0f6bf02a805` |
|
||
| TV Shows | tvshows | `767bffe4f11c93ef34b805451a696a4e` |
|
||
| Playlists | playlists | `1071671e7bffa0532e930debee501d2e` |
|
||
|
||
### 4b. Existing users
|
||
|
||
| Name | UserId | Admin? |
|
||
|---|---|---|
|
||
| s8n | `2be0f0d3fe3a45dc9298138a15a01925` | yes |
|
||
|
||
### 4c. Creating a new user (UI)
|
||
|
||
1. Dashboard → Users → "+ Add User".
|
||
2. Username + initial password. Tick "User can manage server" OFF.
|
||
3. After creation, click the user → tabs:
|
||
- **Profile**: language, audio default, subtitle default. Set per
|
||
user; doesn't have to match server defaults.
|
||
- **Library Access**: untick "Enable access to all libraries", tick
|
||
only the libraries this user should see.
|
||
- **Parental Control**: max rating, blocked tags, access schedule.
|
||
- **Password**: set / reset.
|
||
|
||
### 4d. Creating a new user (API) — playbook
|
||
|
||
> **Do not run this without explicit user request.** Documented for the
|
||
> friend account that will exist later.
|
||
|
||
```bash
|
||
TOKEN=<JELLYFIN_API_TOKEN>
|
||
TVSHOWS_ID=767bffe4f11c93ef34b805451a696a4e
|
||
|
||
# 1. Create the user (auth header REQUIRED — admin token).
|
||
NEW_USER=$(curl -sS -X POST \
|
||
-H "X-Emby-Token: $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"Name":"friend","Password":"<initial-password>"}' \
|
||
https://arrflix.s8n.ru/Users/New)
|
||
echo "$NEW_USER" | python3 -m json.tool
|
||
NEW_ID=$(echo "$NEW_USER" | python3 -c "import sys,json; print(json.load(sys.stdin)['Id'])")
|
||
echo "NEW_ID=$NEW_ID"
|
||
|
||
# 2. Tighten the policy: TV-only, non-admin, can change own prefs,
|
||
# no content deletion, SyncPlay enabled (so we can co-watch).
|
||
cat > /tmp/policy.json <<EOF
|
||
{
|
||
"IsAdministrator": false,
|
||
"IsHidden": false,
|
||
"IsDisabled": false,
|
||
"EnableContentDeletion": false,
|
||
"EnableUserPreferenceAccess": true,
|
||
"EnableRemoteAccess": true,
|
||
"EnableSharedDeviceControl": false,
|
||
"EnableLiveTvAccess": false,
|
||
"EnableLiveTvManagement": false,
|
||
"EnableMediaPlayback": true,
|
||
"EnableAudioPlaybackTranscoding": true,
|
||
"EnableVideoPlaybackTranscoding": true,
|
||
"EnablePlaybackRemuxing": true,
|
||
"EnableAllFolders": false,
|
||
"EnabledFolders": ["$TVSHOWS_ID"],
|
||
"EnableAllChannels": false,
|
||
"EnabledChannels": [],
|
||
"EnableAllDevices": true,
|
||
"BlockedTags": [],
|
||
"BlockedMediaFolders": [],
|
||
"MaxParentalRating": null,
|
||
"AccessSchedules": [],
|
||
"SyncPlayAccess": "CreateAndJoinGroups",
|
||
"InvalidLoginAttemptCount": 0,
|
||
"LoginAttemptsBeforeLockout": 5,
|
||
"MaxActiveSessions": 0
|
||
}
|
||
EOF
|
||
|
||
curl -sS -X POST \
|
||
-H "X-Emby-Token: $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
--data-binary @/tmp/policy.json \
|
||
"https://arrflix.s8n.ru/Users/$NEW_ID/Policy"
|
||
# expect: HTTP 204
|
||
|
||
# 3. Verify
|
||
curl -sS -H "X-Emby-Token: $TOKEN" \
|
||
"https://arrflix.s8n.ru/Users/$NEW_ID" | python3 -m json.tool
|
||
```
|
||
|
||
### 4e. Policy field cheat-sheet
|
||
|
||
| Field | What it does | Recommended for `friend` |
|
||
|---|---|---|
|
||
| `IsAdministrator` | Full server admin | **false** |
|
||
| `EnableContentDeletion` | Can delete media items via UI | **false** |
|
||
| `EnableUserPreferenceAccess` | Change own profile/audio/sub prefs | **true** |
|
||
| `EnableAllFolders` | Master switch for library ACL | **false** |
|
||
| `EnabledFolders` | Whitelist of CollectionFolder Ids | `[TVShows]` (only) |
|
||
| `BlockedTags` | Skip items tagged with these | optional; e.g. `["adult","unrated"]` |
|
||
| `MaxParentalRating` | Hide above this rating | `null` for friend (adult). Set `15` for a kid. |
|
||
| `AccessSchedules` | Day-of-week + time windows | `[]` (no restriction) |
|
||
| `SyncPlayAccess` | `CreateAndJoinGroups` / `JoinGroups` / `None` | `CreateAndJoinGroups` |
|
||
| `MaxActiveSessions` | Concurrent sessions cap; 0 = unlimited | `2` if you want to throttle |
|
||
| `LoginAttemptsBeforeLockout` | Brute-force protection | `5` |
|
||
| `EnableLiveTvAccess` / `Management` | Live TV / DVR | **false** (we don't run it) |
|
||
|
||
### 4f. Password reset flow
|
||
|
||
The friend forgot their password. Two routes:
|
||
|
||
- **Self-serve (only if SMTP is configured — we don't currently)**: login
|
||
page → "Forgot Password". Jellyfin emits a PIN file at
|
||
`/config/data/passwordreset-*.json` valid 30 minutes. Without SMTP, the
|
||
admin reads the PIN out of the container and gives it to the friend.
|
||
- **Admin reset (what we'll do)**:
|
||
|
||
```bash
|
||
curl -sS -X POST \
|
||
-H "X-Emby-Token: $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"NewPw":"<new-password>","ResetPassword":false}' \
|
||
"https://arrflix.s8n.ru/Users/$USER_ID/Password"
|
||
```
|
||
|
||
To clear the password entirely (forces friend to set one on next login):
|
||
same call with `"ResetPassword": true` and no `NewPw`.
|
||
|
||
### 4g. Quick Connect — disabled (2026-05-08)
|
||
|
||
Quick Connect is Jellyfin's 6-digit-code device pairing flow. ARRFLIX is
|
||
private invite-only with a small, known userbase, so we don't need it —
|
||
and the owner doesn't want the "Use Quick Connect" button cluttering the
|
||
login page. Disabled at both the server-config and CSS layers:
|
||
|
||
**1. Server-side disable (the canonical fix).** In 10.10.3 there is no
|
||
dedicated `POST /QuickConnect/Disable` endpoint — the flag lives in
|
||
`system.xml` as `QuickConnectAvailable` and is toggled via the system
|
||
config API:
|
||
|
||
```bash
|
||
# Pull current config, flip the flag, push back.
|
||
curl -s -H "Authorization: MediaBrowser Token=\"$TOKEN\"" \
|
||
https://arrflix.s8n.ru/System/Configuration > /tmp/cfg.json
|
||
jq '.QuickConnectAvailable = false' /tmp/cfg.json > /tmp/cfg.new.json
|
||
curl -s -X POST -H "Authorization: MediaBrowser Token=\"$TOKEN\"" \
|
||
-H "Content-Type: application/json" \
|
||
--data-binary @/tmp/cfg.new.json \
|
||
https://arrflix.s8n.ru/System/Configuration
|
||
# expect: HTTP 204
|
||
|
||
# Verify
|
||
curl -s -H "Authorization: MediaBrowser Token=\"$TOKEN\"" \
|
||
https://arrflix.s8n.ru/QuickConnect/Enabled
|
||
# expect: false
|
||
```
|
||
|
||
**2. CSS hide as belt-and-braces.** The login button in the web bundle
|
||
has class `.btnQuick` (verified in
|
||
`session-login-index-html.c73c6453a153f384f752.chunk.js`). Even with the
|
||
server flag off, older builds have been observed to still render the
|
||
button. Appended to CustomCss:
|
||
|
||
```css
|
||
/* Hide Quick Connect button on login page (server-side disabled too) */
|
||
.btnQuick { display: none !important; }
|
||
```
|
||
|
||
Pushed via `POST /System/Configuration/branding` (the `/Branding/...`
|
||
namespace is read-only — write goes through `/System/Configuration/<key>`).
|
||
Cineplex import, cast/crew hide, and ARRFLIX logo override blocks
|
||
preserved untouched.
|
||
|
||
**Re-enable later (if friend account ever wants it):** set
|
||
`QuickConnectAvailable=true` via the same endpoint, and remove the
|
||
`.btnQuick` rule from CustomCss.
|
||
|
||
### 4h. Per-user defaults (profile UI)
|
||
|
||
Set on each user's profile page (or via `/Users/{id}/Configuration` API):
|
||
|
||
- `AudioLanguagePreference`: `eng`
|
||
- `SubtitleLanguagePreference`: `eng`
|
||
- `SubtitleMode`: `Smart` (only show when audio differs) or `Always`.
|
||
- `PlayDefaultAudioTrack`: true.
|
||
- Display language: pick on first login.
|
||
|
||
---
|
||
|
||
## 5. Watching together / continue-watching
|
||
|
||
### 5a. Resume / Next Up / Up Next — how Jellyfin builds them
|
||
|
||
- **Continue Watching** ("Resume" row): items where `UserData.PlaybackPositionTicks > 0`
|
||
and not yet `Played: true`. Threshold for "watched" is server-side
|
||
~90% by default. Per-user.
|
||
- **Next Up**: for series the user has started, Jellyfin walks the
|
||
next unwatched episode in season/episode order. Configurable in
|
||
Dashboard → Display → Next Up (max age, rewatching toggle).
|
||
- **Up Next** (the in-player auto-advance card): client-side feature in
|
||
the web/mobile players, fed by the same Next Up logic.
|
||
|
||
No action needed — these light up automatically once a user has played
|
||
something. Futurama is loaded, so as soon as anyone plays an episode,
|
||
the homepage gets populated.
|
||
|
||
### 5b. SyncPlay (synchronised group playback)
|
||
|
||
Server-side: nothing to enable, ships on. Per-user permission lives in
|
||
`Policy.SyncPlayAccess`:
|
||
|
||
| Value | Meaning |
|
||
|---|---|
|
||
| `CreateAndJoinGroups` | Can start a SyncPlay group + invite | (s8n + recommended for friend) |
|
||
| `JoinGroups` | Can only join existing groups | useful for kid accounts |
|
||
| `None` | Disabled | — |
|
||
|
||
Verified current state: `s8n.SyncPlayAccess = CreateAndJoinGroups` ✓.
|
||
|
||
**How to use**:
|
||
|
||
1. s8n opens a series episode and starts playing.
|
||
2. Player overlay → top-right people-icon ("SyncPlay") → "Create group".
|
||
3. Friend logs in (any device — same `arrflix.s8n.ru`), opens the same item
|
||
or the SyncPlay menu → "Join {s8n}'s group".
|
||
4. Anyone in the group's play/pause/seek is mirrored within ~1 second.
|
||
5. Voice chat is up to you — Jellyfin doesn't bundle one (Matrix room
|
||
on `txt.s8n.ru` works fine; or just a phone call).
|
||
|
||
Caveat: SyncPlay uses WebSockets. Our reverse proxy (`traefik`) handles
|
||
WS by default, no changes needed.
|
||
|
||
---
|
||
|
||
## 6. Maintenance
|
||
|
||
### 6a. Updating the theme
|
||
|
||
We currently pin Cineplex to `@v1.0.6` (immutable) — no auto-updates,
|
||
no surprise breakage. To opt into upstream changes:
|
||
|
||
```bash
|
||
# Move from immutable tag to floating @main (pulls future commits;
|
||
# jsDelivr cache TTL is up to 7d for floating refs).
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
-X POST -H "X-Emby-Token: $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"CustomCss": "@import url(\"https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@main/cineplex.css\");\n#castCollapsible, #guestCastCollapsible { display: none !important; }", "LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \
|
||
https://tv.s8n.ru/System/Configuration/branding
|
||
```
|
||
|
||
Or just ask each user to hard-reload — their browser cache is the common
|
||
bottleneck, not jsDelivr.
|
||
|
||
When upgrading Jellyfin (e.g. 10.10.3 → 10.10.7+ → 10.11.x), check the
|
||
[Cineplex commits](https://github.com/MRunkehl/cineplex/commits/main)
|
||
and the README compatibility line. Cineplex's stated floor is 10.10.7,
|
||
so going forward in the 10.10 series is safe; jumping to 10.11 needs a
|
||
re-test (selectors changed in some 10.11 release notes). If something
|
||
regresses, pin back to `@v1.0.6`.
|
||
|
||
### 6b. Reverting to ElegantFin (or vanilla)
|
||
|
||
Replace the `@import` line:
|
||
|
||
```bash
|
||
# Back to ElegantFin (Jellyseerr-style):
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
-X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
|
||
-d '{"CustomCss": "@import url(\"https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@v25.12.31/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css\");\n#castCollapsible, #guestCastCollapsible { display: none !important; }", "LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \
|
||
https://tv.s8n.ru/System/Configuration/branding
|
||
|
||
# To vanilla Jellyfin (clear everything):
|
||
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
|
||
-X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
|
||
-d '{"CustomCss": "", "LoginDisclaimer": "", "SplashscreenEnabled": false}' \
|
||
https://tv.s8n.ru/System/Configuration/branding
|
||
```
|
||
|
||
Or in the UI: Dashboard → General → edit / clear "Custom CSS code" →
|
||
Save. Hard-reload browsers afterward.
|
||
|
||
### 6c. Pinning a known-good revision
|
||
|
||
Cineplex is already pinned to `@v1.0.6`. If a future tag (e.g. `v1.0.7`)
|
||
ships and is good, bump the URL. jsDelivr serves `@<tag>` immutably and
|
||
forever. Tag list: <https://github.com/MRunkehl/cineplex/tags>.
|
||
|
||
---
|
||
|
||
## 7. First-30-minutes UX checklist (new user)
|
||
|
||
When the friend gets their account, walk them through this **once**:
|
||
|
||
1. **Login** → see the LAN-only disclaimer; that's the right server.
|
||
2. **Profile picture** → set one (just helps SyncPlay group UX).
|
||
3. **Display preferences** (top-right user icon → Display):
|
||
- Theme: keep "Dark" (Cineplex is dark-only — Netflix-black `#181818`
|
||
base; light theme will look half-applied). Don't switch.
|
||
- Landing screen: Home.
|
||
4. **Playback preferences**:
|
||
- Default audio language: `English`.
|
||
- Default subtitle language: `English`.
|
||
- Subtitle mode: `Smart` (auto-show on foreign audio).
|
||
- "Play next episode automatically": on (this is what enables Up Next).
|
||
5. **Quality** — first-time playback test on Futurama:
|
||
- Pick S01E01, play. Click the gear → quality. If it stutters on
|
||
1080p, drop to 720p; transcoder is CPU-only on nullstone today
|
||
(GTX 1660 Ti driver still broken — see `README.md`).
|
||
- Once confirmed playing, that quality is remembered per device.
|
||
6. **SyncPlay test**: friend in one tab, s8n in another, friend joins
|
||
s8n's group, confirm play/pause syncs. (Drops the "do you have it
|
||
running" question forever.)
|
||
7. **Mobile/TV**: install Jellyfin app, server URL `https://arrflix.s8n.ru`
|
||
(must be on LAN or Tailscale), Quick Connect or password.
|
||
8. **Bookmarks/RSS**: there isn't one — Jellyfin's "Latest" row is the
|
||
substitute. Friend can favourite shows (heart icon) to pin.
|
||
|
||
---
|
||
|
||
## 8. Open items / future work
|
||
|
||
- [ ] Enable Quick Connect when friend account is created (Dashboard →
|
||
General → Quick Connect).
|
||
- [ ] Configure SMTP for self-serve password reset (currently admin-only).
|
||
- [ ] Get Traefik to issue a SNI cert for `arrflix.s8n.ru` so the curl
|
||
examples don't need `--resolve tv.s8n.ru:443:192.168.0.100`. Until
|
||
then, both names point to the same backend on `192.168.0.100` but
|
||
only `tv.s8n.ru` has a valid cert.
|
||
- [ ] Watch [Cineplex commits](https://github.com/MRunkehl/cineplex/commits/main)
|
||
monthly; if a `v1.0.7` lands and looks safe, bump the pin.
|
||
- [ ] Add a 2nd library (movies are mounted but the server may have an
|
||
empty Movies folder — confirm with friend's first ask).
|
||
- [ ] After GPU driver fix on nullstone, NVENC transcode → 1080p HEVC
|
||
will stop being CPU-bound; revisit per-user quality defaults.
|
||
- [ ] Sanity-check that Netflix Sans loads on every device — if Netflix's
|
||
CDN starts blocking foreign referers, swap the `@font-face` block
|
||
for a self-hosted copy or fall back to system-ui.
|