feat(jellyfin): big-screen theme for the TV - repo CSS in the Media Center slot #138

Merged
dank merged 1 commits from feat/jellyfin-big-screen-theme into main 2026-11-03 03:40:04 -08:00
Owner

Why

The living-room screen renders Jellyfin's web UI from three metres and everything on it is too small. The fix has to be server-side, and it has to survive a Jellyfin image bump — which rules out the obvious approaches.

Who is on that TV (census, not guesswork): the server's own device ledger says danklivingroom runs Jellyfin Desktop 2.0.0, last active 2026-11-03 09:46, and there is no TV-app and no TV-browser record in Jellyfin's history at all. Jellyfin Desktop 2.x loads the server-served web app, so server-side CSS reaches it; there is no client scale knob in that app to reach for instead.

What changes

dankserver/jellyfin/theme/wmc/theme.css — a repo-owned theme: one @import, a zoom variable, a focus-ring rule, hidden scrollbars.

dankserver/jellyfin/quadlet/jellyfin.container — one read-only Volume= binding that file over the image's Media Center theme slot (/jellyfin/jellyfin-web/themes/wmc/theme.css).

Why that shape:

  • The theme's first line is @import url("../dark/theme.css") — the live upstream dark palette. The repo stores no vendored copy of Jellyfin's CSS, so an image bump cannot strand a stale snapshot; the worst a drift can do is slightly wrong colors, never a dead web app. This works because jellyfin-web applies a theme as <link rel="stylesheet" href="themes/<id>/theme.css"> (main.jellyfin.bundle.js: l = e => "themes/".concat(e, "/theme.css") → jsx("link", …)) — a relative URL resolved against the CSS file, not the document.
  • The slot is one nobody would pick by accident, and the label stays "Media Center": the display names are compiled into the bundle, so no "Big Screen" entry will ever appear in the dropdown. The page says so, because that is exactly where someone gets lost.
  • zoom on the root element rather than a font-size bump: the card-grid code measures layout pixels, so the grid re-computes fewer, larger columns instead of overflowing.
  • Adoption is per device (Display Settings → Theme → Media Center), because the theme comes from the client's own settings and a phone browser on this same server must not get a 1.75× interface.

Size is one number: --dank-big-screen-zoom (1.5 mild, 1.75 = three metres, 2.0 projector).

Evidence

One consequence worth having in writing: scripts/deploy-plan.py maps only
dankserver/<svc>/quadlet/** to a service, so a later theme/**-only commit plans
nothing — and it should, because the bind serves live bytes and a zoom tweak must
not restart a media server. Measured: these three paths plan exactly jellyfin.

Rendered the live web app in a headless browser with the theme's rules injected: computed zoom on the root element goes 1 → 1.75, the login screen's headings, inputs and buttons scale with it, the layout re-flows without clipping or overflow. Before/after screenshots are in the session record.

No permanent test is added, deliberately: asserting that a mounted file contains a marker is a configuration assertion, not behavior. The behavior is a visual result and the knob is a single CSS number; the page records the rollback (delete the Volume=, redeploy) instead.

Deploy

scripts/deploy.sh dankserver --service jellyfin. The restart discards any in-flight library scan, so the refresh has to be re-triggered afterwards — see Media Metadata View and Jellyfin (Triggering a scan). No LLM endpoint is in the blast radius.


cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac9963f6f8cb (build worktree: /tmp/wt-jftheme)

# Why The living-room screen renders Jellyfin's web UI from three metres and everything on it is too small. The fix has to be server-side, and it has to survive a Jellyfin image bump — which rules out the obvious approaches. **Who is on that TV (census, not guesswork):** the server's own device ledger says `danklivingroom` runs **Jellyfin Desktop 2.0.0**, last active 2026-11-03 09:46, and there is **no TV-app and no TV-browser record in Jellyfin's history at all**. Jellyfin Desktop 2.x loads the *server-served* web app, so server-side CSS reaches it; there is no client scale knob in that app to reach for instead. # What changes **`dankserver/jellyfin/theme/wmc/theme.css`** — a repo-owned theme: one `@import`, a zoom variable, a focus-ring rule, hidden scrollbars. **`dankserver/jellyfin/quadlet/jellyfin.container`** — one read-only `Volume=` binding that file over the image's **Media Center** theme slot (`/jellyfin/jellyfin-web/themes/wmc/theme.css`). Why that shape: - The theme's first line is `@import url("../dark/theme.css")` — the **live upstream** dark palette. The repo stores no vendored copy of Jellyfin's CSS, so an image bump cannot strand a stale snapshot; the worst a drift can do is slightly wrong colors, never a dead web app. This works because jellyfin-web applies a theme as `<link rel="stylesheet" href="themes/<id>/theme.css">` (`main.jellyfin.bundle.js`: `l = e => "themes/".concat(e, "/theme.css")` → `jsx("link", …)`) — a relative URL resolved against the CSS file, not the document. - The slot is one nobody would pick by accident, and the label stays **"Media Center"**: the display names are compiled into the bundle, so no "Big Screen" entry will ever appear in the dropdown. The page says so, because that is exactly where someone gets lost. - `zoom` on the root element rather than a font-size bump: the card-grid code measures layout pixels, so the grid re-computes fewer, larger columns instead of overflowing. - **Adoption is per device** (`Display Settings → Theme → Media Center`), because the theme comes from the client's own settings and a phone browser on this same server must not get a 1.75× interface. Size is one number: `--dank-big-screen-zoom` (1.5 mild, **1.75** = three metres, 2.0 projector). # Evidence One consequence worth having in writing: `scripts/deploy-plan.py` maps only `dankserver/<svc>/quadlet/**` to a service, so a later `theme/**`-only commit plans nothing — and it should, because the bind serves live bytes and a zoom tweak must not restart a media server. Measured: these three paths plan exactly `jellyfin`. Rendered the live web app in a headless browser with the theme's rules injected: computed `zoom` on the root element goes `1 → 1.75`, the login screen's headings, inputs and buttons scale with it, the layout re-flows without clipping or overflow. Before/after screenshots are in the session record. No permanent test is added, deliberately: asserting that a mounted file contains a marker is a configuration assertion, not behavior. The behavior is a visual result and the knob is a single CSS number; the page records the rollback (delete the `Volume=`, redeploy) instead. # Deploy `scripts/deploy.sh dankserver --service jellyfin`. The restart **discards any in-flight library scan**, so the refresh has to be re-triggered afterwards — see [[Media Metadata View]] and [[Jellyfin]] (*Triggering a scan*). No LLM endpoint is in the blast radius. --- cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac9963f6f8cb (build worktree: /tmp/wt-jftheme)
dank added 1 commit 2026-11-03 03:38:15 -08:00
dank merged commit b875256500 into main 2026-11-03 03:40:04 -08:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#138