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.
# 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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
danklivingroomruns 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-onlyVolume=binding that file over the image's Media Center theme slot (/jellyfin/jellyfin-web/themes/wmc/theme.css).Why that shape:
@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.zoomon 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.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.pymaps onlydankserver/<svc>/quadlet/**to a service, so a latertheme/**-only commit plansnothing — 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
zoomon the root element goes1 → 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)