diff --git a/README.md b/README.md
index fd6a7e3..3a49def 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,8 @@ Detailed playbooks (research-grade, with API curls, failure modes, recovery):
| [`docs/02-metadata-and-titles.md`](docs/02-metadata-and-titles.md) | Filename parsing, Identify flow, locking the right show, language cascade, multi-episode files |
| [`docs/03-subtitles.md`](docs/03-subtitles.md) | OpenSubtitles plugin (.com), sidecar naming, ffmpeg/mkvextract extraction, per-user prefs |
| [`docs/04-theming-and-users.md`](docs/04-theming-and-users.md) | ElegantFin theme, branding API, multi-user policies, SyncPlay, friend account playbook |
+| [`docs/05-file-structure-rules.md`](docs/05-file-structure-rules.md) | Authoritative folder/filename rules for movies, TV, anime, stand-up, concerts, docs, extras, NFO, artwork overrides |
+| [`docs/06-per-library-themes.md`](docs/06-per-library-themes.md) | Per-library theming research: JS-injector plugin shim + scoped CSS for Movies/Anime/Music looks |
## State as of 2026-05-08
diff --git a/docs/05-file-structure-rules.md b/docs/05-file-structure-rules.md
new file mode 100644
index 0000000..d4a5541
--- /dev/null
+++ b/docs/05-file-structure-rules.md
@@ -0,0 +1,1165 @@
+# 05 — File & Folder Structure Rules (tv.s8n.ru)
+
+Last updated: 2026-05-08
+Server: Jellyfin 10.10.3 on nullstone, container `jellyfin`
+Library root inside container: `/media`
+Library root on host: `/home/user/media`
+
+This document is the authoritative ruleset for laying media out on disk so
+Jellyfin scrapes it correctly the first time, every time. Cross-linked to:
+
+- [`01-artwork-and-images.md`](01-artwork-and-images.md) — image scrapers and on-disk override files
+- [`02-metadata-and-titles.md`](02-metadata-and-titles.md) — filename parsing, `RemoteSearch/Apply`, the lock-the-series flow
+- [`03-subtitles.md`](03-subtitles.md) — sidecar `.srt` / `.ass` naming and the OpenSubtitles plugin
+- [`04-theming-and-users.md`](04-theming-and-users.md) — multi-user policies and library access
+
+Sources of truth (check these BEFORE this doc — they update):
+
+-
+-
+-
+-
+-
+-
+- Source: `Emby.Naming.dll` ships in the container at `/jellyfin/Emby.Naming.dll`. Rules below match the Emby.Naming regex chain referenced by the docs.
+- `CollectionType.cs` (master): `unknown, movies, tvshows, music, musicvideos, trailers, homevideos, boxsets, books, photos, livetv, playlists, folders` (enum int values 0–12).
+
+---
+
+## 0. Top-level rules (apply to everything)
+
+These are non-negotiable. Most "Jellyfin won't match my file" tickets are
+caused by violating one of these:
+
+1. **One library = one `CollectionType`.** Never mix Movies and TV in the same
+ library. Mixed libraries technically exist (`mixed`) but lose half the
+ scrapers and most edge-case parsing — do not use.
+2. **One folder per item.** A movie lives in its own folder. A series lives in
+ its own folder. A music album lives in its own folder. Loose files in the
+ library root will scrape, but extras / NFO / artwork sidecars cannot attach
+ to a loose file.
+3. **Forbidden filename characters:** `< > : " / \ | ? *`
+ These are illegal on Windows and Jellyfin's parser refuses to canonicalise
+ them. Use `--` for `:`, drop quotes entirely.
+4. **No accents/non-ASCII in folder names** unless you are sure the underlying
+ filesystem (`ext4` here) and SMB/NFS clients all support UTF-8. We're on
+ `ext4` + LAN-only HTTP, so accents are safe — but avoid them where the
+ ASCII title is well-known (e.g. `Amelie (2001)` not `Amélie (2001)`).
+5. **Always include the year** for movies/series whose title is not unique:
+ `The Office (2005)` vs `The Office (2001)` (UK), `It (2017)` vs `It (1990)`.
+ Year goes in parentheses immediately after the title with a single space.
+6. **Year is parsed only when in `( )`** — `Movie 2005.mkv` does NOT bind 2005
+ as the year, it becomes part of the title. `Movie (2005).mkv` does.
+7. **Provider-ID overrides win over filename guessing.** If a title is
+ ambiguous or the scraper repeatedly picks the wrong show, embed the ID:
+ `Series Name (2023) [tmdbid-12345]/`. Doc 02 covers the `RemoteSearch/Apply`
+ path for fixing this after-the-fact via API.
+8. **`SeriesName/Season XX/SeriesName SXXEYY.ext` is the canonical TV layout.**
+ Anything flatter or deeper has corner cases. Stick to it.
+9. **Dots, dashes, underscores, and spaces are interchangeable** between tokens
+ for the parser. `Futurama.s01e01.pl.mkv` parses identically to
+ `Futurama - S01E01 - pl.mkv`. Pick one and be consistent inside a library.
+10. **Refresh after a rename.** Renaming a file on disk does NOT auto-refresh
+ the existing Jellyfin item — it creates a new "missing" record. Either
+ rename BEFORE first scan, or `POST /Library/Refresh` after.
+
+---
+
+## 1. Movies
+
+### 1.1 Folder structure
+
+```
+/media/movies/
+├── Blade Runner (1982)/
+│ └── Blade Runner (1982).mkv
+├── Blade Runner 2049 (2017)/
+│ ├── Blade Runner 2049 (2017) - 2160p.mkv
+│ ├── Blade Runner 2049 (2017) - 1080p.mkv
+│ └── Blade Runner 2049 (2017) - Theatrical.mkv
+├── Dune (1984)/
+│ └── Dune (1984) [imdbid-tt0087182].mkv
+├── Dune (2021)/
+│ └── Dune (2021).mkv
+└── Lord of the Rings - Fellowship (2001)/
+ ├── Lord of the Rings - Fellowship (2001) - cd1.mkv
+ └── Lord of the Rings - Fellowship (2001) - cd2.mkv
+```
+
+One folder per movie. Folder name = movie name.
+
+### 1.2 Filename pattern
+
+- **Pattern:** `^
\((?\d{4})\)( \[(imdbid|tmdbid|tvdbid)-[^\]]+\])?( - )?\.$`
+- **Title** is whatever you put — but it must **byte-for-byte match** the
+ parent folder name when using multi-version naming.
+- **Year** in `(YYYY)` is technically optional but **required for this deploy**.
+- **Provider-ID block** `[imdbid-ttNNNNNNN]` / `[tmdbid-NNNN]` / `[tvdbid-NNNN]`
+ is optional; use it when the title is ambiguous or scraper picks wrong.
+- **Label** for multi-version movies: ` - ` or ` - []`.
+ Resolution labels ending in `p` or `i` (`2160p`, `1080p`, `720i`) sort
+ descending by resolution; everything else sorts alphabetically.
+
+#### Examples that WORK
+
+```
+Blade Runner (1982).mkv
+Blade Runner (1982) [imdbid-tt0083658].mkv
+Blade Runner 2049 (2017) - 2160p.mkv
+Blade Runner 2049 (2017) - Directors Cut.mkv
+Blade Runner 2049 (2017) - [Extended].mkv
+Lord of the Rings - Fellowship (2001) - cd1.mkv
+Lord of the Rings - Fellowship (2001) - part 1.mkv
+Lord of the Rings - Fellowship (2001).part.1.mkv
+```
+
+#### Examples that BREAK
+
+```
+Blade Runner.1982.mkv ← year not in parens; title becomes "Blade Runner 1982"
+Blade.Runner.(1982).mkv ← parses but folder/file mismatch will void multi-version
+BR (1982).mkv ← title too cryptic, scraper guesses wrong
+Blade Runner (1982) - Directors Cut.mkv ← in folder "BladeRunner1982" → mismatch
+LOTR: Fellowship (2001).mkv ← `:` is illegal
+Movies/Blade Runner (1982).mkv ← no per-movie folder; extras/NFO can't attach
+```
+
+### 1.3 Multi-disc / multi-part rips
+
+Use part separators `cd|dvd|part|pt|disc|disk` followed by a number, optionally
+preceded by space/`.`/`-`/`_`:
+
+```
+Lord of the Rings - Fellowship (2001) - cd1.mkv
+Lord of the Rings - Fellowship (2001) - cd2.mkv
+```
+
+**Limitation (from upstream docs, verbatim):** "This does not work with
+multiple versions or merging." → if you have a 2-disc 2160p AND a 2-disc 1080p
+of the same movie, you must remux into single files; the parser cannot encode
+both axes.
+
+### 1.4 Foreign-language audio dubs
+
+Jellyfin matches on the original title (English/release-language) regardless
+of audio. Polish-dubbed `Futurama` would be:
+
+```
+/media/movies/Futurama - Bender's Big Score (2007)/
+└── Futurama - Bender's Big Score (2007).mkv ← Polish audio inside
+```
+
+Do NOT put `.pl` in the filename — the audio language tag is a track-level
+attribute (read from the mkv stream), not a filename token. If you must
+distinguish two language rips of the same film, use the multi-version pattern:
+
+```
+Movie (2020) - PL Dub.mkv
+Movie (2020) - Original.mkv
+```
+
+### 1.5 Year disambiguation
+
+When two films share a title, year alone is what the scraper uses. If both
+are 1980, fall back to provider IDs:
+
+```
+/media/movies/Bad Movie (1980) [imdbid-tt0080000]/
+/media/movies/Bad Movie (1980) [imdbid-tt0080001]/
+```
+
+### 1.6 Scrapers
+
+- **Primary:** TheMovieDb (TMDb) — bundled, on by default.
+- **Fallback / cross-reference:** OMDb (IMDb-backed; ships with Jellyfin core).
+- **Image-only:** TheMovieDb covers most posters/backdrops. Add the
+ **Fanart.tv** plugin if you need clearart, disc, logo overrides — see
+ `01-artwork-and-images.md` § 4.
+- **Trailers:** TheMovieDb attaches YouTube trailer links automatically; the
+ **AniList** / **TheTVDB** plugins do not apply here.
+
+### 1.7 Edge cases
+
+- **`VIDEO_TS` / `BDMV` rips** are supported but **lose multi-version, multi-part,
+ and external subtitles**. Avoid for new rips; remux to mkv.
+- **Pre-release / unofficial cuts** (Snyder Cut, Final Cut Pro, etc.) → use the
+ multi-version label, not a separate folder.
+- **Movies that became series** (e.g. Fargo) — the original film goes in
+ `/media/movies/`, the show in `/media/tv/`. Provider IDs prevent cross-match.
+- **Anime films that are part of a TV show** (One Piece: Stampede) — see § 3.6.
+
+---
+
+## 2. TV shows
+
+### 2.1 Folder structure (canonical)
+
+```
+/media/tv/
+├── Futurama (1999)/
+│ ├── Season 00/ ← specials live here
+│ │ └── Futurama (1999) S00E01 - Christmas Special.mkv
+│ ├── Season 01/
+│ │ ├── Futurama (1999) S01E01.mkv
+│ │ ├── Futurama (1999) S01E02.mkv
+│ │ └── Futurama (1999) S01E03-E04.mkv ← multi-episode file
+│ ├── Season 02/
+│ └── tvshow.nfo ← optional, doc § 11
+└── The Office (2005)/
+ └── Season 01/
+ └── The Office (2005) S01E01.mkv
+```
+
+**Per-season folders are mandatory** for this deploy. Flat (no season folders)
+parses but loses the per-season-poster override path (§ 10) and breaks for
+shows >2 seasons.
+
+### 2.2 Filename pattern
+
+- **Pattern:** `^.*?[Ss](?\d{1,2})[Ee](?\d{1,3})(-[Ee]?(?\d{1,3}))?(\s.*)?\.$`
+- Season + episode tokens recognised by the parser (case-insensitive, dots
+ and dashes equivalent to spaces — verified in doc 02 § 2):
+
+| Pattern | Example | Result |
+|---|---|---|
+| `S##E##` | `Futurama S01E01.mkv` | s1e1 |
+| `s##e##` | `Futurama.s01e01.pl.mkv` | s1e1 (current Futurama) |
+| `Season ## Episode ##` | `Futurama Season 1 Episode 1.mkv` | s1e1 |
+| `##x##` | `Futurama 1x01.mkv` | s1e1 |
+| `S##E##-E##` | `Futurama S01E01-E02.mkv` | one file, eps 1+2 |
+| `S##E## - S##E##` | `Futurama S01E01 - S01E02.mkv` | multi range |
+
+Series name comes from the **parent folder** (preferred) or the filename
+prefix before the `S##E##` token. If both are present, folder wins.
+
+#### Examples that WORK
+
+```
+Futurama (1999)/Season 01/Futurama (1999) S01E01.mkv
+Futurama (1999)/Season 01/Futurama.s01e01.pl.mkv
+Futurama (1999)/Season 01/Futurama 1x01 Space Pilot 3000.mkv
+Futurama (1999)/Season 01/Futurama S01E01-E02.mkv
+Futurama (1999)/Season 00/Futurama (1999) S00E01 - Bender Big Score.mkv
+```
+
+#### Examples that BREAK
+
+```
+Futurama/Futurama-Pilot.mkv ← no S##E## token, ungrabbable as episode
+Futurama/Season1/... ← "Season1" — needs space: "Season 1" or "Season 01"
+Futurama/Specials/... ← "Specials" doesn't match; use "Season 00"
+Futurama/Season 01/01.mkv ← parser sees no season+episode token, only "01"
+Futurama/S01/Futurama_S01E01.mkv ← top-level folder is "S01", series name = "S01"
+Futurama (1999) S01E01.mkv ← in /media/tv/ root; no series folder
+```
+
+### 2.3 Specials (Season 0)
+
+- Folder: `Season 00` (zero-padded). `Specials/`, `Season 0/`, `Season Specials/`
+ do **not** match the parser.
+- Filename: `Series (year) S00E01 - Title.mkv` — `S00` is required; without
+ it the file falls into "no season" and is ignored.
+- For specials that should appear inside a regular season (e.g. between S03E04
+ and S03E05), use NFO `` / `` /
+ `` tags AND enable "Display specials within their
+ series" in library settings.
+
+### 2.4 Multi-episode files
+
+Two formats accepted:
+
+```
+Futurama (1999) S01E01-E02.mkv ← preferred
+Futurama (1999) S01E01 - S01E02.mkv ← also accepted
+```
+
+Both tag the file as "stacked" — Jellyfin shows it as one entry on the
+episode list and plays the entire file when either episode is clicked.
+
+### 2.5 Date-based / daily shows
+
+The official docs do not define a date-based pattern as of 2026-05. The
+practical workaround for daily shows (talk shows, news) is to fake them
+into seasonal numbering by year:
+
+```
+The Daily Show/
+├── Season 2024/
+│ ├── The Daily Show S2024E001 - 2024-01-02.mkv
+│ └── The Daily Show S2024E002 - 2024-01-03.mkv
+```
+
+Episode number = day-of-year (001–366). Ugly but parser-clean. If the
+metadata provider (TVDB) supports the date-based show, NFO sidecars can
+override the episode title to the actual airdate.
+
+### 2.6 Scrapers
+
+- **Primary:** TheTVDB.
+- **Secondary:** TheMovieDb (TMDb has good TV coverage too).
+- **Order matters:** Library options → Metadata Fetchers → drag the order.
+ For Futurama on this deploy we used TMDB primary because TVDB had stale
+ episode-still URLs.
+- **Image:** TVDB ships posters and episode stills; TMDB has higher-resolution
+ backdrops; Fanart.tv has clearart + clearlogo.
+
+### 2.7 Edge cases
+
+- **Series whose name STARTS with a year** (e.g. "1923") — wrap in folder
+ `1923 (2022)/` so the parser doesn't confuse the series-name year with
+ the disambiguation year.
+- **Shows that re-run/reboot** (`Doctor Who`, `Battlestar Galactica`) — keep
+ reboots in separate folders, year disambiguation is mandatory:
+ `Doctor Who (1963)/` and `Doctor Who (2005)/`.
+- **Mini-series / limited series** — treat as TV, single season is fine
+ (`Chernobyl (2019)/Season 01/...`).
+- **Episode title inside filename is ignored** once the series is identified;
+ TMDB/TVDB title overwrites it (see doc 02 § 4).
+- **`(year)` is required only at the series level**, not on every episode.
+ Including it on every episode is harmless but verbose.
+
+---
+
+## 3. Anime
+
+Anime is the area with the highest "scraper picks the wrong thing" risk
+because TVDB / TMDB / AniDB / AniList disagree on how to slice multi-cours
+shows into seasons. Two distinct strategies — pick **one per show**, never
+mix.
+
+### 3.1 Strategy A — TVDB seasonal numbering (default for this deploy)
+
+Use this when:
+- Show has ≤ 100 episodes.
+- TVDB's season split matches the official Blu-ray / streaming split.
+- You're not running Shoko.
+
+#### Folder structure
+
+```
+/media/anime/
+├── Cowboy Bebop (1998)/
+│ └── Season 01/
+│ ├── Cowboy Bebop (1998) S01E01.mkv
+│ └── Cowboy Bebop (1998) S01E02.mkv
+└── Mushishi (2005)/
+ ├── Season 01/
+ └── Season 02/ ← Mushishi Zoku Shou maps to S02 on TVDB
+```
+
+#### Filename pattern
+
+Identical to TV shows § 2.2. Include `(year)` of first broadcast.
+
+#### Edge case — episodes >99 in a season
+
+`S01E100` works for the parser. `S01E001` (3-digit) also works — see [Issue
+#17 in jellyfin-plugin-anime](https://github.com/jellyfin-archive/jellyfin-plugin-anime/issues/17).
+But **absolute numbering across multiple seasons** (where the show has 1099
+episodes spanning many "seasons" on disk) breaks the seasonal model. Use
+Strategy B.
+
+### 3.2 Strategy B — Absolute numbering with Shoko Server
+
+Use this when:
+- Show has > 100 episodes (One Piece, Naruto, Detective Conan).
+- You want AniDB matching, MAL/AniList sync, exact tag accuracy.
+- You don't mind running an extra container.
+
+Shoko hashes files by content (ED2K) and identifies them regardless of
+filename. With Shoko + the Jellyfin Shoko plugin, **filenames don't matter**.
+
+```
+/media/anime-shoko/
+└── (any layout you like; Shoko walks the tree and hashes everything)
+```
+
+This deploy does **not currently run Shoko**. If/when added, it lives at
+`/opt/docker/shoko/` and exposes `/media/anime-shoko/` to Jellyfin via the
+plugin, separate from `/media/anime/`.
+
+### 3.3 Strategy C — Absolute numbering with naive Jellyfin (avoid)
+
+Naming files `One Piece - 1099.mkv` with no season folders works for the
+**very first 99 episodes** then breaks: the parser sees ep 100+ as "ep 1
+of S00 (Specials)" and shuffles. Documented in upstream issue #17. Don't.
+
+### 3.4 Sub vs Dub
+
+Two acceptable patterns:
+
+**Pattern 1 — separate libraries** (recommended, clean):
+
+```
+/media/anime/Death Note (2006)/Season 01/Death Note (2006) S01E01.mkv ← original Japanese w/ subs
+/media/anime-dub/Death Note (2006)/Season 01/Death Note (2006) S01E01.mkv ← English dub
+```
+
+Two libraries; user picks which to browse.
+
+**Pattern 2 — multi-version filenames** (single library, may confuse scraper):
+
+```
+/media/anime/Death Note (2006)/Season 01/
+├── Death Note (2006) S01E01.mkv ← default (sub)
+└── Death Note (2006) S01E01 - Dub.mkv ← extra version, label "Dub"
+```
+
+Beware: multi-version on TV episodes (vs movies) is partial in Jellyfin
+10.10 — the dub version may not be selectable from all clients. Pattern 1
+is safer.
+
+### 3.5 OVAs / OADs / specials
+
+OVAs go in `Season 00` of the parent series, with descriptive titles:
+
+```
+Mushishi (2005)/
+├── Season 00/
+│ ├── Mushishi (2005) S00E01 - Hihamukage OVA.mkv
+│ └── Mushishi (2005) S00E02 - Bell of Stillness OVA.mkv
+└── Season 01/
+```
+
+### 3.6 Anime films that are "part of" a show
+
+Two camps:
+
+- **Canon to plot, watch-order matters** (e.g. *Code Geass: Lelouch of the
+ Re;surrection*) → put in `Season 00` of the show as a special.
+- **Standalone film**, parallel universe (most One Piece movies, Pokémon
+ films) → put in `/media/movies/` with year. Provider IDs prevent
+ cross-matching with the parent series.
+
+### 3.7 Japanese vs English titles
+
+Folder name uses the title that matches your **primary metadata provider's
+preferred display language**. On this deploy, library `MetadataLanguage` is
+`pl` for Futurama; if you add an Anime library set it to `en` or `ja-JP`.
+
+Practical rule: use **the romaji or English title that the show's English
+Wikipedia article uses as its primary heading**. That's what TVDB/TMDB
+search will resolve. Set provider ID to lock if both titles match
+something:
+
+```
+/media/anime/Steins;Gate (2011) [tvdbid-244061]/
+```
+
+(Note `;` is illegal on Windows but allowed on `ext4`. Avoid for portability.)
+
+### 3.8 Scrapers
+
+- **Primary (sub library):** TheTVDB (anime detection enabled).
+- **Optional plugin:** **AniDB** + **AniList** plugins — install via
+ Dashboard → Plugins → Catalog. Enable per-library. AniDB has better
+ episode-level metadata for older shows; AniList has better
+ current-airing data.
+- **With Shoko:** Shoko replaces all of the above; AniDB IDs are canonical.
+
+### 3.9 Library type
+
+For this deploy, the Anime library uses `CollectionType: tvshows` (NOT a
+separate "anime" type — Jellyfin doesn't have one). Set
+`PreferredMetadataLanguage` and the metadata-provider order at library
+creation. See § 12 for the API call.
+
+---
+
+## 4. Stand-up comedy specials
+
+### 4.1 Folder structure
+
+Treat as **movies** (one folder per special). Each comedian's specials are
+peers — do not nest by performer.
+
+```
+/media/movies/
+├── Bo Burnham - Inside (2021)/
+│ └── Bo Burnham - Inside (2021).mkv
+├── Bo Burnham - Make Happy (2016)/
+│ └── Bo Burnham - Make Happy (2016).mkv
+└── Norm Macdonald - Nothing Special (2022)/
+ └── Norm Macdonald - Nothing Special (2022).mkv
+```
+
+### 4.2 Filename pattern
+
+Same as Movies (§ 1.2). Convention: ` - (year)`.
+
+#### Examples that WORK
+
+```
+Bo Burnham - Inside (2021).mkv
+Hannah Gadsby - Nanette (2018) [imdbid-tt8465676].mkv
+```
+
+#### Examples that BREAK
+
+```
+Bo Burnham/Inside (2021).mkv ← no per-movie folder
+Inside (2021).mkv ← title too generic; TMDB picks horror film "Inside"
+Bo Burnham: Inside (2021).mkv ← `:` is illegal
+```
+
+### 4.3 Scrapers
+
+- **Primary:** TheMovieDb (TMDb has stand-up special listings under "Movies").
+- TVDB has a Stand-Up category but the Jellyfin TVDB integration treats
+ everything in a movies library as a movie — leave it.
+
+### 4.4 Edge cases
+
+- **Specials that aren't on TMDB** (small comedians, festival recordings) →
+ write a `movie.nfo` (§ 11) and let it stand alone. Jellyfin won't fetch
+ remote data without an ID.
+- **Optional separate library:** if you want stand-up out of the main movies
+ grid, create a second library with `CollectionType: movies` rooted at
+ `/media/standup/` — same scrapers, just a different shelf.
+- **Tagging:** add `Stand-up ` to the NFO or use a Jellyfin
+ Collection (BoxSet) called "Stand-up Specials" to group them.
+
+---
+
+## 5. Concerts / music videos
+
+### 5.1 Folder structure
+
+```
+/media/musicvideos/
+├── Daft Punk/
+│ ├── Get Lucky/
+│ │ └── Daft Punk - Get Lucky.mp4
+│ └── Around the World/
+│ └── Daft Punk - Around the World.mp4
+└── Pink Floyd/
+ └── Pulse Concert (1995)/
+ ├── Pink Floyd - Pulse (1995).mkv
+ └── poster.jpg
+```
+
+The library can nest as deep as you like — verbatim from upstream:
+"The folders and video files can be named however you want, since no
+metadata fetching is performed."
+
+### 5.2 Filename pattern
+
+- **Pattern:** anything. Free-form. The display name is the literal filename
+ minus extension.
+- **Convention for this deploy:** ` - .` (or
+ ` - (year).` for full concerts).
+
+#### Examples that WORK
+
+Anything not containing `< > : " / \ | ? *`.
+
+#### Examples that BREAK (parser-wise — none, but UX-wise)
+
+```
+01.mp4 ← display name "01", useless
+videoplayback (1).mp4 ← yt-dlp default; rename before scan
+```
+
+### 5.3 Scrapers
+
+- **None by default.** `musicvideos` library type has no built-in remote
+ metadata fetcher — Jellyfin uses filenames + folder structure.
+- Embedded ID3-style tags in `mp4` (artist, title) ARE read.
+- Plugins: there is no first-party music-video scraper. Some users use
+ the **MusicBrainz** plugin to cross-reference, but coverage is poor.
+
+### 5.4 Edge cases
+
+- **Full live concerts** are sometimes better as `tvshows` (one episode per
+ song) or `movies` (single file). For this deploy use **movies** for
+ full concert recordings, **musicvideos** for individual song clips.
+- **Fan-made / unofficial videos** — fine here, since no scraper to
+ mismatch.
+- **Music VIDEOS attached to a music album** — Jellyfin doesn't link a
+ music-video item to a music-album item natively. Live with the
+ separation.
+
+---
+
+## 6. Documentaries
+
+Documentaries split on form:
+
+### 6.1 Single-film documentaries → Movies library
+
+```
+/media/movies/
+└── Free Solo (2018)/
+ └── Free Solo (2018).mkv
+```
+
+Same rules as § 1. TMDB classifies most documentary films as "Movies".
+
+### 6.2 Multi-episode documentary series → TV library
+
+```
+/media/tv/
+└── Planet Earth II (2016)/
+ └── Season 01/
+ ├── Planet Earth II (2016) S01E01.mkv
+ └── Planet Earth II (2016) S01E02.mkv
+```
+
+Same rules as § 2. TVDB classifies most documentary series as "Series".
+
+### 6.3 Optional separate libraries
+
+For users who want documentaries off the main Movies/TV shelves:
+
+- `/media/docs-movies/` with `CollectionType: movies`
+- `/media/docs-tv/` with `CollectionType: tvshows`
+
+Same scrapers as the parent type — the tag is purely UI.
+
+### 6.4 Scrapers
+
+- **Films:** TMDb (primary), OMDb (fallback).
+- **Series:** TVDB (primary), TMDb (secondary).
+- The same NFO override rules apply (§ 11) for obscure docs that aren't on
+ any provider.
+
+### 6.5 Edge cases
+
+- **Mini-series in 1 long file** (e.g. *The Vietnam War* PBS, 18 hours, one
+ rip) — treat as a movie or split into episodes. Jellyfin does not chapter-
+ split a single file into N episode entries.
+- **Lecture series** (Crash Course, Khan Academy) — treat as TV. Use
+ `Crash Course (2011)/Season 01/` etc.
+
+---
+
+## 7. Home videos / personal media
+
+The goal: keep Jellyfin from "fixing" your wedding videos by pulling the
+poster of an unrelated 2015 movie called *Wedding*.
+
+### 7.1 Folder structure
+
+```
+/media/home/
+├── 2024/
+│ ├── 2024-06-15 Berlin Trip/
+│ │ ├── 2024-06-15 Berlin Trip - clip01.mp4
+│ │ └── 2024-06-15 Berlin Trip - clip02.mp4
+│ └── 2024-12-25 Christmas/
+│ └── 2024-12-25 Christmas.mp4
+└── 2025/
+ └── 2025-08-30 Wedding/
+ └── 2025-08-30 Wedding.mp4
+```
+
+### 7.2 Filename pattern
+
+Free-form. Date-prefix recommended (`YYYY-MM-DD `) — Jellyfin will
+parse the date and use it as the item's date.
+
+### 7.3 Library type
+
+`CollectionType: homevideos` — **critical**. This is the only collection
+type that disables all remote metadata fetchers. With `homevideos`:
+
+- No TMDB / TVDB / OMDb scrapers run.
+- Image fetchers are off (use sidecar `.jpg` if you want a thumb).
+- The library shows up under "Photos & home videos" in the UI sidebar.
+
+### 7.4 Scrapers
+
+**None.** Local-only. NFO sidecars (§ 11) work if you want to label
+individual clips, but no remote lookups.
+
+### 7.5 Edge cases
+
+- **Photos in the same folder as videos** — `homevideos` library accepts
+ both. Use `.jpg`/`.png`/`.heic` sidecars; they appear in the slideshow.
+- **Don't drop home videos in `/media/movies/`.** Even with no provider IDs,
+ Jellyfin will scan and try to match titles against TMDB. The
+ `homevideos` library is the only safe place.
+- **Smart phone clips** named `IMG_1234.MOV` are fine; they display by
+ filename. Bulk-rename to `2024-06-15 - IMG_1234.mov` if you want them
+ sorted by event date.
+
+---
+
+## 8. Extras / special features
+
+Extras attach to a parent item (movie or series) via two mechanisms:
+filename suffix, or named subfolder. Either works; mixing is fine.
+
+### 8.1 Suffix method
+
+Append one of these tokens to the filename **before** the extension:
+
+| Suffix | Type | Example |
+|---|---|---|
+| `-trailer` `.trailer` `_trailer` ` trailer` | Trailer | `Blade Runner (1982) - 1982 Theatrical-trailer.mp4` |
+| `-sample` `.sample` `_sample` ` sample` | Sample | `Movie-sample.mp4` |
+| `-scene` | Deleted scene / vignette | `Inception (2010) - Hallway-scene.mp4` |
+| `-clip` | Promo clip | `Movie - TV Spot-clip.mp4` |
+| `-interview` | Interview | `Movie - Director Interview-interview.mp4` |
+| `-behindthescenes` | BTS featurette | `Movie - VFX Breakdown-behindthescenes.mp4` |
+| `-deleted` `-deletedscene` | Deleted scene | `Movie - Cut Diner Scene-deleted.mp4` |
+| `-featurette` | Featurette | `Movie - Anatomy of a Stunt-featurette.mp4` |
+| `-short` | Short film | `Movie - Prequel Short-short.mp4` |
+| `-other` `-extra` | Catch-all | `Movie - Ephemera-other.mp4` |
+
+A lone trailer/sample file can also be named just `trailer.mp4` or
+`sample.mp4` and dropped in the parent item folder.
+
+### 8.2 Folder method
+
+Inside the parent item folder, any of these named subfolders are picked up
+and the files inside are tagged with the matching extra type:
+
+```
+Inception (2010)/
+├── Inception (2010).mkv
+├── behind the scenes/
+│ └── VFX Breakdown.mp4
+├── deleted scenes/
+│ ├── Diner Cut.mp4
+│ └── Hotel Hallway Cut.mp4
+├── featurettes/
+│ └── Dreams Within Dreams.mp4
+├── interviews/
+│ └── Christopher Nolan.mp4
+├── scenes/
+├── shorts/
+├── samples/
+├── trailers/
+│ └── Theatrical Trailer.mp4
+├── clips/
+├── theme-music/
+│ └── theme.mp3 ← see § 8.3
+├── backdrops/
+│ └── 2.mp4 ← rotating video backdrops
+├── other/
+└── extras/ ← generic catch-all
+```
+
+Subfolder names **must match exactly** (case-insensitive on `ext4`+Jellyfin):
+`Behind the Scenes/` works; `BTS/` does not; `behind-the-scenes/` does not.
+
+### 8.3 Theme music
+
+`theme-music/theme.mp3` plays a track on hover/auto in supported clients
+(Swiftfin, JellyfinMediaPlayer). One file per item.
+
+### 8.4 Backdrops
+
+Video backdrops (rotating background loops) go in `backdrops/` as numbered
+mp4s. Falls back to image backdrops if not present.
+
+### 8.5 Edge cases
+
+- **Extras attached to a series vs a season vs an episode** — folder method
+ works at any level: drop `behind the scenes/` inside `Futurama (1999)/`
+ for series-wide extras, inside `Season 01/` for season extras, or
+ use suffix on a sibling file for episode extras.
+- **Show-level trailers** — Jellyfin's TV scraper auto-attaches trailer
+ YouTube links from TMDB. You don't need to download them.
+
+---
+
+## 9. Subtitles (sidecar)
+
+See [`03-subtitles.md`](03-subtitles.md) for the full rules. Quick reference:
+
+```
+.[.flag].
+```
+
+- `` = the video filename minus extension.
+- `` = ISO-639-1 (`en`, `pl`) or ISO-639-2 (`eng`, `pol`).
+- `` = optional, any combination of `forced`, `default`, `sdh`, `cc`.
+- `` = `srt`, `ass`, `ssa`, `vtt`, `sub` (+ `.idx` for VobSub).
+
+Examples next to `Futurama.s01e01.pl.mkv`:
+
+```
+Futurama.s01e01.pl.eng.srt ← English regular
+Futurama.s01e01.pl.en.forced.srt ← English forced (foreign-scene captions)
+Futurama.s01e01.pl.en.sdh.srt ← English SDH
+Futurama.s01e01.pl.en.default.srt ← marked default, auto-selects
+Futurama.s01e01.pl.pl.srt ← Polish (matches the language of the audio)
+```
+
+After dropping subs on disk, run `POST /Library/Refresh` (or wait for the
+nightly scan) — Jellyfin discovers them and attaches.
+
+---
+
+## 10. Artwork override files
+
+Jellyfin scrapes artwork from TMDB/TVDB/Fanart by default (see doc 01).
+Override per-item by dropping a sidecar image with one of these recognised
+filenames in the item's folder.
+
+### 10.1 Movie / generic item folder
+
+| Filename | ImageType | Notes |
+|---|---|---|
+| `poster.jpg` / `poster.png` | Primary | Main poster (vertical 2:3). |
+| `folder.jpg` | Primary | Alias of poster (Windows / Plex compat). |
+| `cover.jpg` | Primary | Alias of poster (also used in music). |
+| `default.jpg` | Primary | Alias. |
+| `movie.jpg` | Primary | Alias. |
+| `backdrop.jpg` | Backdrop | Hero image (16:9 fanart). |
+| `backdrop1.jpg`, `backdrop2.jpg`, ... | Backdrop | Multiple backdrops, numbered. |
+| `fanart.jpg` | Backdrop | Plex/Kodi compat alias. |
+| `logo.png` | Logo | Transparent text-logo overlay. |
+| `clearlogo.png` | Logo | Alias. |
+| `banner.jpg` | Banner | Wide ~758×140 strip. |
+| `thumb.jpg` | Thumb | 16:9 still. Used as episode thumbnail at item level. |
+| `landscape.jpg` | Thumb | Alias. |
+| `disc.png` | Disc | DVD/Blu-ray hub icon. |
+| `clearart.png` | Art | Transparent character cutout. |
+
+### 10.2 TV series folder (additional)
+
+```
+Futurama (1999)/
+├── poster.jpg
+├── backdrop.jpg
+├── logo.png
+├── banner.jpg
+├── season-all-poster.jpg ← shared across all seasons
+├── season01-poster.jpg ← Season 01 specific
+├── season02-poster.jpg
+├── season-specials-poster.jpg ← Season 00
+├── Season 01/
+│ ├── Futurama (1999) S01E01.mkv
+│ ├── Futurama (1999) S01E01.jpg ← episode thumb (basename match)
+│ └── poster.jpg ← also valid as season poster
+└── tvshow.nfo
+```
+
+The two season-poster paths are equivalent; pick one style. Episode-level
+thumbs use `.jpg` (i.e. drop a `.jpg` next to the `.mkv` with
+matching name).
+
+### 10.3 When to override
+
+- Use sidecar files when the scraper's choice is wrong AND you don't want
+ to upload via the Web UI (which writes into `/config/metadata/library/...`
+ — wiped on container rebuild).
+- Sidecars in the media folder **survive `docker rm`** because they live on
+ the user's data volume.
+- Sidecars take precedence over remote scraper images on next refresh
+ ONLY if "Save artwork into media folders" is enabled in Dashboard →
+ Libraries → (each library) → Library options. This deploy has it ON.
+
+### 10.4 Edge cases
+
+- `.png` and `.jpg` are both accepted; `.webp` works for backdrops but
+ not all clients render it — prefer `.jpg`.
+- Image larger than 4K is downscaled by the API on serve. Don't bother with
+ >2160p source images.
+- **Don't put `poster.jpg` in `/media/movies/`** (the library root) — it
+ becomes the library's primary image, often unwanted.
+
+---
+
+## 11. NFO sidecars
+
+NFO files are XML metadata, written next to the media. They override remote
+scrapers entirely. From upstream: "Local metadata will always be fetched and
+has priority over remote metadata providers like TMDb."
+
+### 11.1 Filenames
+
+| Item type | Required filename |
+|---|---|
+| Movie | `movie.nfo` (in the movie folder), OR `.nfo` next to the file, OR `VIDEO_TS.nfo` for DVD rips |
+| TV series | `tvshow.nfo` in the series folder |
+| TV season | `season.nfo` in the season folder |
+| TV episode | `.nfo` (e.g. `Futurama (1999) S01E01.nfo`) |
+| Music artist | `artist.nfo` |
+| Music album | `album.nfo` |
+
+### 11.2 When to write one
+
+- Obscure indie film not on TMDB / IMDB → `movie.nfo` lets you fill the
+ metadata yourself.
+- Show whose IDs scrape wrong every time → `tvshow.nfo` with locked
+ `` / `` is faster than the API `RemoteSearch/Apply`
+ workflow (doc 02 § 5).
+- Home videos / personal — usually not needed (homevideos lib doesn't
+ scrape) but useful for nice titles.
+
+### 11.3 Minimal `movie.nfo` example
+
+```xml
+
+
+ The Obscure Film
+ Niejasny film
+ 2014
+ A short description that overrides whatever TMDB returns.
+ Drama
+ 97
+ Jane Director
+
+ Lead Actor
+ Protagonist
+
+ tt12345678
+
+```
+
+### 11.4 Minimal `tvshow.nfo` example
+
+```xml
+
+
+ Futurama
+ 1999
+ ...
+ 615
+ 73871
+
+```
+
+### 11.5 NFO Saver (write-back)
+
+Enable Dashboard → Libraries → (each) → Metadata Savers → "Nfo". Jellyfin
+will write `*.nfo` next to media files whenever metadata changes. This is
+how you survive container rebuilds without losing manual fixes — the NFO
+on disk is canonical, the SQLite DB is regenerable.
+
+### 11.6 Edge cases
+
+- **Empty/malformed NFO** stops the scraper from running on that item AT
+ ALL. Either write valid XML or delete the file.
+- **NFO + provider ID conflict** — local always wins. If you set
+ `615 ` and the filename has `[tmdbid-9999]`, NFO wins.
+- **Episode `.nfo` per file is verbose.** Most people only write `tvshow.nfo`
+ and let the episode metadata come from the provider.
+
+---
+
+## 12. CollectionType-to-scraper mapping (library creation)
+
+Verbatim values from `Jellyfin.Data/Enums/CollectionType.cs` (master,
+verified 2026-05). These are the strings to pass when creating a library
+via API. (`unknown` is reserved; `tvshowseries`+ are virtual aggregates not
+used at library creation.)
+
+| `CollectionType` | UI name | Default scrapers (10.10.x) | Use for |
+|---|---|---|---|
+| `movies` | Movies | TMDb, OMDb, Fanart.tv (plugin) | Films, stand-up, doc films |
+| `tvshows` | Shows | TVDB, TMDb, Fanart.tv, OMDb | TV, anime, doc series, kids' shows |
+| `music` | Music | MusicBrainz, AudioDB | Albums, tracks |
+| `musicvideos` | Music Videos | none (filename only) | Music videos, short concerts |
+| `homevideos` | Home Videos & Photos | none | Personal recordings, photo albums |
+| `boxsets` | Collections | TMDb collections | Manually-curated cross-library box sets |
+| `books` | Books | requires "Bookshelf" plugin | epub, mobi, pdf, comics |
+| `photos` | Photos | none | Photo-only library |
+| `livetv` | Live TV | tuner-driven | Real-time TV (HDHomeRun etc.) |
+| `trailers` | Trailers | bundled | Standalone trailers library (rare) |
+| `playlists` | Playlists | n/a | Internal Jellyfin construct |
+| `folders` | Folders | n/a | Internal Jellyfin construct |
+| `mixed` (no enum, legacy) | Mixed | TMDb + TVDB | Don't use — drops most parsing rules |
+
+### 12.1 Creating libraries via API
+
+```bash
+TOKEN=*redacted*
+H="-H \"X-Emby-Token: ${TOKEN}\""
+B="https://tv.s8n.ru"
+
+# Movies library
+curl -s -X POST $H "$B/Library/VirtualFolders?name=Movies&collectionType=movies" \
+ -H "Content-Type: application/json" \
+ -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/movies"}],"EnableInternetProviders":true,"PreferredMetadataLanguage":"en","MetadataCountryCode":"US","SaveLocalMetadata":true,"SubtitleDownloadLanguages":["eng"]}}'
+
+# TV library
+curl -s -X POST $H "$B/Library/VirtualFolders?name=Shows&collectionType=tvshows" \
+ -H "Content-Type: application/json" \
+ -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/tv"}],"EnableInternetProviders":true,"PreferredMetadataLanguage":"en","SaveLocalMetadata":true,"SubtitleDownloadLanguages":["eng"]}}'
+
+# Anime library (still tvshows type)
+curl -s -X POST $H "$B/Library/VirtualFolders?name=Anime&collectionType=tvshows" \
+ -H "Content-Type: application/json" \
+ -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/anime"}],"EnableInternetProviders":true,"PreferredMetadataLanguage":"en","SaveLocalMetadata":true,"SubtitleDownloadLanguages":["eng"]}}'
+
+# Music videos
+curl -s -X POST $H "$B/Library/VirtualFolders?name=Music%20Videos&collectionType=musicvideos" \
+ -H "Content-Type: application/json" \
+ -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/musicvideos"}]}}'
+
+# Home videos
+curl -s -X POST $H "$B/Library/VirtualFolders?name=Home%20Videos&collectionType=homevideos" \
+ -H "Content-Type: application/json" \
+ -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/home"}],"EnableInternetProviders":false}}'
+```
+
+After creation, trigger an initial scan: `POST /Library/Refresh`.
+
+---
+
+## 13. Canonical layout for THIS deploy
+
+### 13.1 Architecture decision
+
+**Adopted: Architecture A** — flat by category at `/home/user/media/`, one
+Jellyfin library per category.
+
+```
+/home/user/media/
+├── movies/ ← collectionType: movies
+├── tv/ ← collectionType: tvshows
+├── anime/ ← collectionType: tvshows (separate library)
+├── musicvideos/ ← collectionType: musicvideos
+├── music/ ← collectionType: music (future)
+├── docs-movies/ ← collectionType: movies (future, optional)
+├── docs-tv/ ← collectionType: tvshows (future, optional)
+└── home/ ← collectionType: homevideos
+```
+
+### 13.2 Why Architecture A (not B or C)
+
+**B (nested, `media/video/{movies,tv,anime}/...`)** — rejected. Adds a
+useless directory level. Jellyfin's library config takes a path; nesting
+buys nothing on the client side. Increases the chance of a typo in the
+container's bind-mount.
+
+**C (split disks: `/media-fast/` NVMe + `/media-slow/` HDD)** — rejected
+**for now**. Nullstone has a single 2 TB NVMe and 4 TB HDD. Total media
+today is ~50 GB (Futurama + future). When the library exceeds 1 TB, we'll
+revisit and migrate cold catalogue (older movies, finished anime) to the
+HDD, mounted as a second library path:
+
+```
+LibraryOptions.PathInfos = [
+ {"Path": "/media/movies"}, ← /home/user/media/movies on NVMe
+ {"Path": "/media-archive/movies"} ← /mnt/hdd/media/movies
+]
+```
+
+Jellyfin natively merges multiple paths into one logical library. No URL
+or client-facing change needed at migration time.
+
+**A wins because:**
+
+- One library per `collectionType` is the simplest correct mapping.
+- Anime as its own library lets us set provider order and language
+ preference independently from `tv/`.
+- `home/` MUST be a separate library to get `homevideos` collection type
+ (the only way to disable scrapers for personal media).
+
+### 13.3 Concrete mkdir commands
+
+Run on nullstone as `user` (not root — the existing tree is already owned
+by `user:user`):
+
+```bash
+ssh user@192.168.0.100 'mkdir -p \
+ /home/user/media/movies \
+ /home/user/media/tv \
+ /home/user/media/anime \
+ /home/user/media/musicvideos \
+ /home/user/media/music \
+ /home/user/media/home'
+```
+
+Verify:
+
+```bash
+ssh user@192.168.0.100 'ls -la /home/user/media/'
+```
+
+### 13.4 Container bind mounts
+
+`/opt/docker/jellyfin/docker-compose.yml` should mount each as read-only
+under `/media/`:
+
+```yaml
+volumes:
+ - /home/user/media/movies:/media/movies:ro
+ - /home/user/media/tv:/media/tv:ro
+ - /home/user/media/anime:/media/anime:ro
+ - /home/user/media/musicvideos:/media/musicvideos:ro
+ - /home/user/media/music:/media/music:ro
+ - /home/user/media/home:/media/home:ro
+```
+
+The current compose only mounts `movies` and `tv`; extend it before adding
+the new libraries via API.
+
+### 13.5 Initial state after applying
+
+```
+Movies library → /media/movies (empty, ready)
+TV Shows library → /media/tv (Futurama 1999, S01-S03, 44 eps)
+Anime library → /media/anime (empty, ready)
+Music Videos lib → /media/musicvideos (empty, ready)
+Music library → /media/music (empty, ready)
+Home Videos lib → /media/home (empty, ready)
+```
+
+---
+
+## 14. Verification checklist
+
+Before declaring a new addition "done":
+
+1. Filename matches the regex anchor for the category (§ 1–7).
+2. Year is in `(YYYY)` and matches the actual release year.
+3. Folder name byte-for-byte matches the filename prefix (movies multi-version).
+4. No forbidden chars (`< > : " / \ | ? *`).
+5. Per-item folder exists (no loose files in library root, except music videos).
+6. `tvshow.nfo` / `movie.nfo` exists IFF you needed to override the scraper.
+7. Subtitles use `..srt` (doc 03).
+8. Scan: `curl -s -X POST -H "X-Emby-Token: $TOKEN" https://tv.s8n.ru/Library/Refresh`.
+9. Wait ~30 s, check item via `/Items?searchTerm=...` — verify `ProviderIds`
+ is populated. Empty `ProviderIds` = filename didn't disambiguate; doc 02
+ § 5 has the manual-lock recipe.
+10. Refresh (`Items/{id}/Refresh?ReplaceAllMetadata=true`) AFTER fixing the
+ provider ID — otherwise the wrong cached metadata sticks.
+
+---
+
+## 15. Quick reference card
+
+| Category | Folder | Filename | CollectionType | Scraper |
+|---|---|---|---|---|
+| Movie | `movies/Title (year)/` | `Title (year).mkv` | `movies` | TMDb |
+| Movie multi-version | `movies/Title (year)/` | `Title (year) - 1080p.mkv` | `movies` | TMDb |
+| Movie multi-disc | `movies/Title (year)/` | `Title (year) - cd1.mkv` | `movies` | TMDb |
+| TV episode | `tv/Show (year)/Season 01/` | `Show (year) S01E01.mkv` | `tvshows` | TVDB |
+| TV multi-ep | `tv/Show (year)/Season 01/` | `Show (year) S01E01-E02.mkv` | `tvshows` | TVDB |
+| TV special | `tv/Show (year)/Season 00/` | `Show (year) S00E01.mkv` | `tvshows` | TVDB |
+| Anime (seasonal) | `anime/Show (year)/Season 01/` | `Show (year) S01E01.mkv` | `tvshows` | TVDB+AniDB plugin |
+| Anime (Shoko) | `anime-shoko/` | any | `tvshows` | Shoko |
+| Stand-up | `movies/Comedian - Title (year)/` | `Comedian - Title (year).mkv` | `movies` | TMDb |
+| Music video | `musicvideos/Artist/Track/` | `Artist - Track.mp4` | `musicvideos` | none |
+| Doc film | `movies/Title (year)/` | `Title (year).mkv` | `movies` | TMDb |
+| Doc series | `tv/Show (year)/Season 01/` | `Show (year) S01E01.mkv` | `tvshows` | TVDB |
+| Home video | `home/YYYY/YYYY-MM-DD Event/` | any | `homevideos` | none |
+| Extra (suffix) | `movies/Title (year)/` | `Anything-behindthescenes.mp4` | (parent) | n/a |
+| Extra (folder) | `movies/Title (year)/behind the scenes/` | any | (parent) | n/a |
+
+---
+
+## 16. Top three gotchas (in order of frequency)
+
+1. **No per-item folder.** Loose `Movie (2020).mkv` directly in `/media/movies/`
+ parses, but extras / NFO / artwork sidecars cannot attach. Always make a
+ folder.
+2. **Year not in parens.** `Movie 2020.mkv` → year is part of the title;
+ scraper search for "Movie 2020" returns wrong results. Always
+ `Movie (2020).mkv`.
+3. **Anime absolute numbering > 99 episodes** without Shoko, mixed with
+ season folders → episodes shuffle into Season 0. Either split by
+ TVDB seasons OR run Shoko. Never half-and-half.
+
+---
+
+End of doc 05. For questions about parsing edge cases not covered here,
+read `Emby.Naming.xml` inside the container (`docker exec jellyfin cat
+/jellyfin/Emby.Naming.xml`) — it has the canonical regex chain.
diff --git a/docs/06-per-library-themes.md b/docs/06-per-library-themes.md
new file mode 100644
index 0000000..aa67314
--- /dev/null
+++ b/docs/06-per-library-themes.md
@@ -0,0 +1,319 @@
+# 06 — Per-Library Themes (Movies = Netflix, Anime = Crunchyroll, Music = Spotify)
+
+> **Scope of this doc:** research only. No live changes. Targets Jellyfin **10.10.3** at https://tv.s8n.ru
+> with the current global theme **ElegantFin v25.12.31** in `/System/Configuration/branding` `CustomCss`.
+
+---
+
+## TL;DR — Verdict
+
+**Partially feasible.** Jellyfin 10.10 has **no native** per-library theming. CustomCss is a single
+site-wide blob; LibraryOptions has no CustomCss field; the web client never sets a body class or
+data attribute reflecting the current library or `collectionType`. **However**, the URL hash *does*
+encode both (`#/movies.html?topParentId=&collectionType=movies` etc.), which means a tiny JS
+shim that mirrors that URL state onto the body class is enough to let one CustomCss blob carry
+multiple scoped sub-themes (`body.lib-movies { … } body.lib-anime { … } body.lib-music { … }`).
+
+**Recommended path:** approach **#2 — JS-shim + scoped CSS**, delivered via the
+`Jellyfin-JavaScript-Injector` plugin (or, if no plugin is acceptable, a bind-mounted patched
+`index.html`). Visual fidelity is *good but not pixel-perfect Netflix/Crunchyroll/Spotify* — those
+brands have unique fonts, layouts and animations a CSS-only override cannot fully reproduce.
+Expect "tinted, branded, recognisable" rather than "indistinguishable".
+
+If true brand-grade fidelity is required, only **approach #5 (subdomain split into separate
+Jellyfin instances)** delivers it — at the cost of running 3 servers and either duplicating libraries
+or using user-policy library hiding.
+
+---
+
+## 1. Five approaches at a glance
+
+| # | Approach | Feasibility 10.10.3 | Maintenance | Fidelity | UX cost |
+|---|----------|---------------------|-------------|----------|---------|
+| 1 | Pure CSS scoping by route (no JS) | **No** — Jellyfin web sets no body/HTML attributes that reflect library or collectionType. URL hash is invisible to CSS. | n/a | n/a | n/a |
+| 2 | **JS shim → body class → scoped CSS** | **Yes** — URL hash includes `topParentId`+`collectionType`, easy to mirror onto body | **Low** — ~30 lines of JS, stable across upgrades because it consumes URL params, not DOM internals | **Good** (8/10) — full CSS variable + layout override per library; falls short of perfect brand mimicry (fonts, motion design) | Sub-100ms class flip on hashchange; no flicker if rules use the right specificity |
+| 3 | Per-library `Branding`/CustomCss via API | **No** — `LibraryOptions` schema has no CustomCss / theme field. Confirmed against `/Library/VirtualFolders` response. | n/a | n/a | n/a |
+| 4 | Existing community plugin promising per-library theming | **No** — none exists. `Skin Manager`, `JellySkin`, `ElegantFin`, `Jellyfish`, `JellyFlix`, `DarkFlix` are all server-wide. Closest building block: `Jellyfin-JavaScript-Injector` (plugin route to deliver approach #2). | Low if used as injector for #2 | Same as #2 | Same as #2 |
+| 5 | Subdomain split — `movies.tv.s8n.ru`, `anime.tv.s8n.ru`, `music.tv.s8n.ru` (3 Jellyfin containers) | **Yes** — straightforward Traefik + 3 stacks | **High** — 3× DBs, 3× scans, 3× upgrades, user accounts to sync | **Perfect (10/10)** — each instance is just a normal Jellyfin with one global theme | Users must bookmark/jump between subdomains; no unified library |
+
+### Why approach #1 fails
+
+The bundled JS only ever calls `body.classList.add/remove` with these strings:
+
+```
+bodyWithPopupOpen, dashboardDocument, force-scroll, hide-scroll,
+noScroll, screensaver-noScroll, withSectionTabs
+```
+
+(Verified by `grep` of `/jellyfin/jellyfin-web/*.js` on the running container.) None of them encode
+library, collection type, item type, or route. CSS selectors like `body[data-libraryid="…"]` or
+`body.collectionType-music` therefore match **zero** elements.
+
+CSS cannot read `window.location` or the URL hash on its own (no `:url()` selector;
+`:has()`/`[href*=…]` operate on element attributes, not the address bar). So without JS, the
+information needed to scope styles is simply not in the DOM.
+
+### Why approach #3 fails
+
+`GET /Library/VirtualFolders` (auth `X-Emby-Token: *redacted*`) returns
+`LibraryOptions` containing only metadata/scan/subtitle settings. No `CustomCss`, no `Theme`, no
+`Branding` per library. The single global CustomCss field at `/System/Configuration/branding` is the
+only knob the server exposes.
+
+### Why approach #4 — note on Skin Manager
+
+`Jellyfin-plugin-skin-manager` and `JellyWatch`'s "Skin Manager" only swap which **single**
+server-wide theme is active. None of the catalogued community themes
+([awesome-jellyfin/THEMES.md](https://github.com/awesome-jellyfin/awesome-jellyfin/blob/main/THEMES.md))
+ship per-library scoping. A handful (e.g. `Kaleidochrome`) auto-tint based on currently-viewed
+artwork but that is colour-only, not layout.
+
+The only useful plugin in this space is **`n00bcodr/Jellyfin-JavaScript-Injector`** (the maintained
+fork of the deprecated `johnpc/jellyfin-plugin-custom-javascript`, MIT, last release 2025-12-08).
+It patches `index.html` server-side to inject arbitrary JS — perfect delivery vehicle for the shim
+in approach #2.
+
+---
+
+## 2. Recommended approach — #2 in detail
+
+### 2.1 Mechanism
+
+1. A small JS payload runs on every page load and on every `hashchange`.
+2. It reads `window.location.hash`, parses out `topParentId` and `collectionType`, and writes them
+ to `` as both a class and a `data-` attribute.
+3. CustomCss carries three scoped style blocks keyed off those classes.
+
+The URL→body-class mapping is the **stable contract**: it consumes URL parameters that the SPA
+itself constructs from server-supplied data (verified — bundled JS contains literal templates
+`#/movies.html?topParentId=…&collectionType=…`). It does **not** depend on internal React state,
+private DOM structure, MUI class hashes, or webpack chunk names — all of which churn between
+Jellyfin upgrades. This is what makes the maintenance cost low.
+
+### 2.2 Delivery options (pick one)
+
+**A. Plugin route (preferred — no file mounts)**
+- Install `n00bcodr/Jellyfin-JavaScript-Injector` via repo URL
+ `https://raw.githubusercontent.com/n00bcodr/Jellyfin-JavaScript-Injector/main/manifest.json`.
+- Paste the shim (below) into the plugin's textarea.
+- Survives container rebuilds; no bind-mounts to maintain.
+- Caveat: plugin patches `index.html` once at install. Jellyfin upgrades that ship a new
+ `jellyfin-web` package re-extract `index.html` and the plugin re-patches on next start. Works
+ the same way as `Custom CSS Branding`.
+
+**B. Bind-mount patched `index.html` (no plugin)**
+- Add `` before ``.
+- Mount `custom-shim.js` into `/jellyfin/jellyfin-web/custom-shim.js`.
+- Mount the patched `index.html` over `/jellyfin/jellyfin-web/index.html`.
+- Pin `jellyfin/jellyfin` image tag — every minor upgrade may rotate the hashed bundle filenames
+ in `index.html`, breaking your patch. Approach A avoids this because the plugin re-applies the
+ patch against the upgraded file.
+
+### 2.3 The JS shim (~30 lines)
+
+```js
+// Per-library body-class shim — applies on initial load and every hashchange.
+// Stable contract: consumes URL query params (topParentId, collectionType) that
+// the Jellyfin web SPA constructs from server data — not DOM internals.
+(function () {
+ const KNOWN = new Set(['movies', 'tvshows', 'music', 'homevideos',
+ 'boxsets', 'livetv', 'books', 'playlists']);
+ // Optional: per-libraryId override (use IDs from /Library/VirtualFolders).
+ // Lets you treat one tvshows library as "anime" while leaving the other as "tv".
+ const LIB_OVERRIDES = {
+ // Example: '': 'anime',
+ };
+
+ function apply() {
+ const h = window.location.hash || '';
+ const q = h.includes('?') ? h.slice(h.indexOf('?') + 1) : '';
+ const params = new URLSearchParams(q);
+ const ct = (params.get('collectionType') || '').toLowerCase();
+ const tpi = params.get('topParentId') || '';
+
+ const body = document.body;
+ // Strip any prior lib-* / ct-* classes to avoid stacking on hashchange.
+ body.className = body.className
+ .split(/\s+/)
+ .filter(c => !c.startsWith('lib-') && !c.startsWith('ct-'))
+ .join(' ');
+
+ let label = LIB_OVERRIDES[tpi] || (KNOWN.has(ct) ? ct : '');
+ if (label) {
+ body.classList.add('lib-' + label);
+ body.classList.add('ct-' + label);
+ body.dataset.libraryId = tpi;
+ body.dataset.collectionType = label;
+ } else {
+ delete body.dataset.libraryId;
+ delete body.dataset.collectionType;
+ }
+ }
+
+ apply();
+ window.addEventListener('hashchange', apply);
+ // Some early-load races: re-apply once DOM is ready and once after route settles.
+ document.addEventListener('DOMContentLoaded', apply);
+ setTimeout(apply, 250);
+})();
+```
+
+### 2.4 CustomCss skeleton
+
+Keep ElegantFin as the global base, then append three scoped blocks. This is appended **after** the
+existing ElegantFin import so cascade order favours the per-library overrides.
+
+```css
+/* === BASE: ElegantFin v25.12.31 (already present) === */
+@import url("https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css");
+
+/* === Movies → Netflix-tinted === */
+body.lib-movies {
+ --primary-accent-color: #e50914; /* Netflix red */
+ --primary-bg-color: #141414;
+ --secondary-bg-color: #181818;
+ --card-bg-color: #232323;
+ font-family: "Netflix Sans", "Helvetica Neue", Arial, sans-serif;
+}
+body.lib-movies .skinHeader { background: linear-gradient(180deg, rgba(0,0,0,.85), transparent) !important; }
+body.lib-movies .button-submit,
+body.lib-movies .raised.button-submit { background-color: #e50914 !important; color: #fff !important; }
+body.lib-movies .cardOverlayContainer { background: rgba(20,20,20,.92) !important; }
+body.lib-movies .itemBackdrop { filter: brightness(.55) saturate(1.15); }
+
+/* Optional: pull JellyFlix's Netflix layout overrides on top */
+@supports selector(body.lib-movies) {
+ body.lib-movies { --jf-import-marker: 1; }
+}
+
+/* === Anime libraries → Crunchyroll-tinted === */
+/* Use LIB_OVERRIDES in the shim to flag *which* tvshows libraries are "anime" */
+body.lib-anime {
+ --primary-accent-color: #f47521; /* Crunchyroll orange */
+ --primary-bg-color: #0b0b0b;
+ --secondary-bg-color: #1a1a1a;
+ font-family: "Lato", "Helvetica Neue", Arial, sans-serif;
+}
+body.lib-anime .skinHeader { background: #0b0b0b !important; border-bottom: 2px solid #f47521; }
+body.lib-anime .button-submit { background-color: #f47521 !important; color: #fff !important; }
+body.lib-anime .cardText-first { font-weight: 700; letter-spacing: .02em; }
+body.lib-anime .itemBackdrop { filter: saturate(1.25) contrast(1.05); }
+
+/* === Music → Spotify-tinted === */
+body.lib-music {
+ --primary-accent-color: #1db954; /* Spotify green */
+ --primary-bg-color: #121212;
+ --secondary-bg-color: #181818;
+ --card-bg-color: #282828;
+ font-family: "Circular", "Helvetica Neue", Arial, sans-serif;
+}
+body.lib-music .skinHeader { background: #000 !important; }
+body.lib-music .button-submit,
+body.lib-music .raised.button-submit { background-color: #1db954 !important; border-radius: 999px !important; padding: .6em 1.6em !important; }
+body.lib-music .cardImageContainer { border-radius: 4px !important; } /* square-ish covers */
+body.lib-music .listItem { border-radius: 6px; transition: background-color .15s ease; }
+body.lib-music .listItem:hover { background-color: rgba(255,255,255,.07); }
+```
+
+### 2.5 Source CSS shopping list
+
+Don't write Netflix/Crunchyroll/Spotify-grade CSS from scratch. Lift from existing themes and
+re-scope each rule under `body.lib-`:
+
+| Source | URL | Style | License | Last update | Compat note |
+|---|---|---|---|---|---|
+| **JellyFlix** (prayag17) | `https://github.com/prayag17/JellyFlix` | Netflix | Not stated | 2022-03-20, "development halted" | Old; harvest selectors only, expect to fix-up for 10.10 DOM |
+| **JellyFlix fork** (Automationxperts) | `https://github.com/Automationxperts/jellyflix` | Netflix | Not stated | Active-ish (11 commits) | Newer than upstream; uses `Netflix Sans` font |
+| **DarkFlix** (DevilsDesigns) | `https://github.com/DevilsDesigns/Jellyfin-DarkFlix-Theme` | Dark Netflix | Not stated | Active | Built on JellyFlix base |
+| **JellyFlixCustomCSS** (xenoncolt) | `https://github.com/xenoncolt/JellyFlixCustomCSS` | Netflix-ish | Not stated | Recent | One-line import |
+| **JellyfinCSS** (jackheteng) | `https://github.com/jackheteng/JellyfinCSS` | Netflix font + hover | Not stated | — | Small surface; useful font/hover snippets |
+| **ElegantFin** (in use) | `https://github.com/lscambo13/ElegantFin` | Elegant base | GPL-2.0 | v25.12.31, tested 10.11.5 | Keep as global base |
+| **Jellyfish** (n00bcodr) | `https://github.com/n00bcodr/Jellyfish` | Modern, multi-colour | LICENSE.md | 10.10.7-targeted | 11 colour schemes — useful palette donor |
+| **JellySkin** (prayag17) | `https://github.com/prayag17/JellySkin` | Vibrant minimal | — | Active | Custom icon set worth scavenging |
+| **Crunchyroll subtitle style** | https://forum.jellyfin.org/t-crunchyroll-subtitle-style | Subtitles only | Forum post | — | Pair with anime block for full effect |
+
+For Crunchyroll and Spotify there is **no off-the-shelf Jellyfin theme**. Build those from:
+- official brand colours (above),
+- Crunchyroll: Lato/Open Sans, orange accents, dark slate background, square-ish poster cards.
+- Spotify: Circular/Helvetica Neue, pill-shaped buttons, green accents, black backdrop, rounded
+ but small `border-radius` on covers, hover-lighten on rows.
+
+### 2.6 Failure modes & rollback
+
+| Failure | Symptom | Rollback |
+|---|---|---|
+| Shim hits a route before DOM ready | First navigation after refresh shows base theme for ~250 ms | Already mitigated by `setTimeout(apply, 250)` + `DOMContentLoaded` listener; if still flaky, call `apply()` from `MutationObserver` watching `#mainAnimatedPages` |
+| Jellyfin upgrade rotates URL param names (`topParentId` → other) | Body class never sets, all libraries fall back to ElegantFin | Plugin disable + remove the appended CSS blocks; UI returns to vanilla ElegantFin instantly |
+| CustomCss specificity loses to ElegantFin's `!important` rules | Per-library tints not visible on some elements | Increase specificity (`html body.lib-movies …`) or add `!important`; harvest the selector list from JellyFlix CSS for accurate targets |
+| Anime library mis-classified as plain `tvshows` | Tinted as TV instead of Crunchyroll-orange | Populate `LIB_OVERRIDES` with the anime library `ItemId` from `GET /Library/VirtualFolders` |
+| Plugin update breaks injection (deprecated upstream) | Shim no longer loads | Switch to bind-mount delivery (option B in §2.2) — same shim, different vehicle |
+
+**Hard rollback (any failure):** clear CustomCss back to the original ElegantFin `@import` line in
+Dashboard → Branding, disable the JavaScript-Injector plugin. Site returns to current state in one
+page refresh. No DB or filesystem state is touched.
+
+### 2.7 Maintenance burden estimate
+
+- **Per Jellyfin minor upgrade** (~quarterly): smoke-test that `topParentId`/`collectionType` URL
+ params still appear on hash transitions. ~5 minutes. Has been stable since 10.7.
+- **Per ElegantFin upgrade**: re-test that scoped overrides still win the cascade. ~10 minutes.
+- **New library added**: zero work if its `collectionType` is one of the eight known types. If it's
+ a tvshows library you want to brand differently (anime), add one line to `LIB_OVERRIDES` with
+ the library `ItemId`.
+- **Plugin replacement**: if `Jellyfin-JavaScript-Injector` is itself deprecated, switch to bind-mount
+ delivery (option B). One-time ~30-minute migration.
+
+Total ongoing burden: ~1 hour/year. Compared with running 3 separate Jellyfin instances
+(approach #5), that's roughly two orders of magnitude less work.
+
+---
+
+## 3. When to pick approach #5 instead
+
+Choose subdomain split if **any** of these are true:
+- You want true Netflix UX (autoplay trailers on hover, exact card geometry, top-10 row, "skip
+ intro" branded affordances) — CSS alone cannot deliver these regardless of approach.
+- You want fully isolated user accounts per "service" (e.g. kid account on `anime.tv.s8n.ru` cannot
+ see movies subdomain at all).
+- You're prepared to either (a) duplicate libraries (3× disk metadata, 3× scans) or (b) maintain a
+ per-user library policy on a single backend that mirrors content into 3 frontend instances —
+ Jellyfin doesn't support multi-frontend-one-backend natively, so (b) means 3 full Jellyfin
+ containers each pointing at the same `/media` mounts but with different libraries enabled.
+
+Otherwise approach #2 wins on every other axis.
+
+---
+
+## 4. Open questions / things to verify before implementing
+
+1. Whether the CustomCss field has a length cap that will fit ElegantFin (~120 KB minified) +
+ three sub-themes (~10–20 KB each). Worth confirming via API GET on
+ `/System/Configuration/branding` before committing.
+2. Whether per-user CustomCss exists in 10.10 (admin-only?) — affects whether kid-vs-adult users
+ could see different sub-themes. Last checked: 10.10 still has only the global field.
+3. ElegantFin v25.12.31 is tested on 10.11.5. We're on 10.10.3. Spot-check that the import URL
+ resolves and renders correctly before adding library scopes on top — the global `CustomCss` is
+ already running this version, so this is presumably already verified.
+4. The `Jellyfin-JavaScript-Injector` plugin deprecation chain (`johnpc → n00bcodr`) has happened
+ once already. Plan for the possibility of a future re-fork; keep the shim source under version
+ control somewhere outside the plugin so it's portable.
+
+---
+
+## 5. Sources
+
+- [Jellyfin CSS Customization (official)](https://jellyfin.org/docs/general/clients/css-customization/) — confirms global-only scope.
+- [awesome-jellyfin THEMES.md](https://github.com/awesome-jellyfin/awesome-jellyfin/blob/main/THEMES.md) — full theme catalogue.
+- [ElegantFin (lscambo13)](https://github.com/lscambo18/ElegantFin) — current global theme.
+- [JellyFlix (prayag17)](https://github.com/prayag17/JellyFlix) — Netflix harvest source (archived).
+- [JellyFlix (Automationxperts)](https://github.com/Automationxperts/jellyflix) — active Netflix fork.
+- [DarkFlix (DevilsDesigns)](https://github.com/DevilsDesigns/Jellyfin-DarkFlix-Theme) — Netflix dark variant.
+- [Jellyfish (n00bcodr)](https://github.com/n00bcodr/Jellyfish) — multi-palette donor.
+- [JellySkin (prayag17)](https://github.com/prayag17/JellySkin) — icon donor.
+- [Jellyfin-JavaScript-Injector (n00bcodr)](https://github.com/n00bcodr/Jellyfin-JavaScript-Injector) — recommended JS delivery plugin.
+- [johnpc/jellyfin-plugin-custom-javascript](https://github.com/johnpc/jellyfin-plugin-custom-javascript) — deprecated, fork above.
+- [JellyWatch — Best Jellyfin Themes 2026](https://jellywatch.app/blog/best-jellyfin-themes-skin-manager-2026) — Skin Manager overview.
+- [BobHasNoSoul/jellyfin-mods](https://github.com/BobHasNoSoul/jellyfin-mods) — patching patterns for `index.html`.
+- [Crunchyroll subtitle style (Jellyfin forum)](https://forum.jellyfin.org/t-crunchyroll-subtitle-style) — pairs with anime block.
+- Live verification: `grep` of `/jellyfin/jellyfin-web/*.js` on the running 10.10.3 container — confirmed body.classList strings, `topParentId`/`collectionType` URL templates, and absence of any per-library DOM hook.
+- Live verification: `GET /Library/VirtualFolders` — confirmed LibraryOptions has no CustomCss field.