commit 2c39bc9cf4f4e5932ecc6ffb565cbcaef6f72c39 Author: eldov Date: Sun Aug 30 05:16:43 2026 +0200 feat: initial import of Jellyfin Aurora Noir theme and assets diff --git a/THEME-README.md b/THEME-README.md new file mode 100644 index 0000000..b073967 --- /dev/null +++ b/THEME-README.md @@ -0,0 +1,115 @@ +# Jellyfin Container — Theme & Branding (Stand 2026-08-16) + +## Aktuelles Theme: "Aurora Noir" + +Cinematic Dark Theme (Aurora-Cyan -> Indigo auf #050810), ersetzt das alte +"Cyberpunk Neon"-Theme (das 2 MB Base64 inline im CSS transportierte). + +## Dateien + +| Pfad | Zweck | +|---|---| +| `web-assets/aurora_noir.css` | Quelldatei des Themes (SSOT zum Editieren) | +| `web-assets/custom_bg.webp` | Hintergrundbild 1920x1080, ~110 KB | +| `library/branding.xml` | Aktive Konfiguration (CSS XML-escaped eingebettet) | +| `library/branding.xml.bak-20260816` | Rollback des alten Themes | + +## Deployment-Pfad + +- docker-compose mountet `./web-assets` nach `/usr/share/jellyfin/web/custom-assets:ro` +- Assets sind erreichbar unter: `/web/custom-assets/` (nicht `/custom-assets/`!) +- CSS-Änderungen: `web-assets/aurora_noir.css` editieren -> XML-escape -> in + `` von `library/branding.xml` schreiben -> `docker restart jellyfin` + +Regeneration des branding.xml aus der CSS-Datei: + +```bash +cd /opt/containers/jellyfin +python3 - <<'PYEOF' +import xml.sax.saxutils as sx +css = open('web-assets/aurora_noir.css').read() +out = ('\n\n' + ' Private media cloud. Authorized access only.\n' + ' %s\n' + ' false\n' + '\n') % sx.escape(css) +open('library/branding.xml', 'w').write(out) +print('written', len(out)) +PYEOF +docker restart jellyfin +``` + +## Rollback auf altes Theme + +```bash +cp /opt/containers/jellyfin/library/branding.xml.bak-20260816 /opt/containers/jellyfin/library/branding.xml +docker restart jellyfin +``` + +## Verifikation (2026-08-16) + +6 UI-Pfade im Browser geprüft (Home, Bibliothek, Album-Detail, Wiedergabe, +Suche, Login): Theme aktiv, keine HTTP-Fehler, Container healthy. +Payload: 10.4 KB statt 2.02 MB (-99.5%). + +## Admin-SPA-Theme-Fix (2026-08-16, final implementiert) + +Jellyfin 10.11: Die React-Admin-Routen (#/dashboard, #/metadata) entfernen das +Custom-CSS beim Mount. Geloest via Traefik rewritebody-Plugin (router-scoped, +nur am jellyfin-Router, KEIN Eingriff in andere Services): + +### Architektur + +1. `traefik.yml`: Plugin `github.com/traefik/plugin-rewritebody v0.3.1` registriert +2. `dynamic_conf.yml`: Zwei Middlewares: + - `jf-no-compress`: stript Accept-Encoding (Plugin überspringt komprimierte Bodies) + - `jf-inject-theme`: injiziert Watcher-Script vor `` +3. `docker-compose.yml` (jellyfin): Middlewares am Router `jellyfin` (nicht am Login-Router) + +### Watcher-Script-Logik (bidirektionale Dedupe) + +- Setzt ``, wenn weder Link noch Inline-AURORA-style existieren (Admin-Routen) +- ENTFERNT den Link, sobald Legacy-Inline-style erscheint (Legacy-Routen wie Home) +- Re-Injection via MutationObserver + setInterval(1000ms) gegen React-Remounts + +### CSS-Updates (wichtig) + +Bei CSS-Aenderungen in `aurora_noir.css`: +1. Datei auf VPS aktualisieren + branding.xml regenerieren + `docker restart jellyfin` +2. Cache-Bust im Watcher erhoehen: in `dynamic_conf.yml` beim jf-inject-theme + Script `aurora_noir.css?v=2` -> `?v=3` setzen + `docker restart traefik` + (Browser-HTTP-Cache otherwise haelt alte CSS fest) + +### MUI-Admin-Styling (2026-08-16, nachgeruestet) + +Admin-SPA nutzt MUI-Komponenten - Legacy-Selektoren greifen dort nicht. +`aurora_noir.css` enthaelt eigene `.Mui*`-Override-Sektion (Papers, Cards, +Buttons, Inputs, Tables, Dialogs, Switches, Chips). Verifiziert: 25 MUI-Regeln +aktiv, Paper radius 14px + Aurora-Border, Buttons Aurora-Gradient. + +### Verifikation (2026-08-16) + +- Test A: Dashboard-Direktload -> Link aktiv, BG gestylt +- Test B: SPA Dashboard->Home -> deduped (nur Legacy-inline, 0 Links) +- Test C: SPA Home->Dashboard -> Link kehrt zurück, BG gestylt +- Test D: Login -> Legacy-Weg unberührt (blur 22px Panel) +- Link-Entfernungs-Angriff: Interval-Watcher stellt Link in <1.5s wieder her +- HTML-Overhead: +620B (Script), CSS nur auf Admin-Routen geladen +- Nachbar-Services: unberührt (gitea 200, home 302, traefik 302) + +### Browser-Cache-Hinweis + +Nach Änderungen am Watcher-Script: Browser-Cache UND Jellyfin-Service-Worker +leeren (about:reload genügt nicht immer), sonst altes HTML: +`caches.keys().then(ks=>ks.forEach(k=>caches.delete(k)))` + harte Reload-Nav +mit `?v=` Query. + +### Rollback + +```bash +# Theme-Injection entfernen: +cp /opt/containers/traefik3-crowdsec-stack/traefik/dynamic_conf.yml.bak-jftheme /opt/containers/traefik3-crowdsec-stack/traefik/dynamic_conf.yml +cp /opt/containers/traefik3-crowdsec-stack/traefik/traefik.yml.bak-jftheme /opt/containers/traefik3-crowdsec-stack/traefik/traefik.yml +# compose label zurücksetzen (middlewares-Zeile), dann: +docker restart traefik && cd /opt/containers/jellyfin && docker compose up -d +``` diff --git a/branding.xml b/branding.xml new file mode 100755 index 0000000..0765b94 --- /dev/null +++ b/branding.xml @@ -0,0 +1,588 @@ + + + Private media cloud. Authorized access only. + /* ============================================================ + AURORA NOIR - cinematic theme for Stream Xtream + Accent: aurora cyan -> indigo | Base: deep space #050810 + Perf: single external bg asset, blur only on chrome surfaces + ============================================================ */ + +:root { + --an-bg: #050810; + --an-surface: rgba(13, 18, 30, 0.62); + --an-surface-solid: #0b101c; + --an-border: rgba(148, 163, 184, 0.14); + --an-border-hover: rgba(34, 211, 238, 0.55); + --an-accent: #22d3ee; + --an-accent-2: #818cf8; + --an-text: #f1f5f9; + --an-text-dim: #94a3b8; + --an-radius: 14px; + --an-glow: 0 0 24px rgba(34, 211, 238, 0.30); + --an-shadow: 0 18px 48px rgba(0, 0, 0, 0.55); +} + +html { + color-scheme: dark; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + scrollbar-color: rgba(34, 211, 238, 0.45) #070b14; +} + +body { + background-color: var(--an-bg); + color: var(--an-text); +} + +/* ---------------- Ambient background ---------------- */ + +.backgroundContainer { + background: #050810 url(/web/custom-assets/custom_bg.webp) center / cover no-repeat fixed !important; +} + +/* Cinematic grade over fanart backdrops so text stays readable */ +.backdropImage { + filter: saturate(1.05) brightness(0.45) !important; +} + +.backgroundContainer::after { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: linear-gradient(180deg, + rgba(5, 8, 16, 0.66) 0%, + rgba(5, 8, 16, 0.18) 34%, + rgba(5, 8, 16, 0.30) 62%, + rgba(5, 8, 16, 0.78) 100%); +} + +/* ---------------- Header ---------------- */ + +.skinHeader { + background: linear-gradient(180deg, rgba(5, 8, 16, 0.92), rgba(5, 8, 16, 0.55)) !important; + backdrop-filter: blur(18px) saturate(1.25); + -webkit-backdrop-filter: blur(18px) saturate(1.25); + border-bottom: 1px solid var(--an-border) !important; +} + +.headerTop { + padding: 0.6em 0.9em; +} + +.emby-tab-button { + color: var(--an-text-dim); +} + +.emby-tab-button-active { + color: var(--an-accent) !important; + text-shadow: 0 0 18px rgba(34, 211, 238, 0.45); +} + +.paper-icon-button-light:hover:not(:disabled) { + color: var(--an-accent); + background-color: rgba(34, 211, 238, 0.10); + border-radius: 8px; +} + +.paper-icon-button-light:focus-visible { + outline: 2px solid var(--an-accent); + outline-offset: 2px; + border-radius: 8px; +} + +.headerButton { + color: var(--an-text); +} + +/* ---------------- Drawer / side menu ---------------- */ + +.mainDrawer { + background: rgba(7, 11, 20, 0.96) !important; + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-right: 1px solid var(--an-border); +} + +.navDrawerMask { + background: rgba(2, 4, 8, 0.55); +} + +.navMenuOption { + border-radius: 10px; + margin: 0.25em 0.6em; +} + +.navMenuOptionIcon { + color: var(--an-accent) !important; + text-shadow: 0 0 14px rgba(34, 211, 238, 0.55); +} + +.navMenuOption:hover, +.navMenuOption.showFocus { + background: rgba(34, 211, 238, 0.12) !important; +} + +.navMenuOptionText { + color: var(--an-text); + font-weight: 500; +} + +/* ---------------- Cards ---------------- */ + +.cardBox { + border-radius: var(--an-radius); + border: 1px solid var(--an-border); + background: var(--an-surface); + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); + transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.3, 1), + box-shadow 0.28s ease, border-color 0.28s ease; +} + +.card:hover .cardBox, +.card.showFocus .cardBox { + transform: translateY(-4px); + border-color: var(--an-border-hover); + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.60), var(--an-glow); +} + +.cardImageContainer { + border-radius: calc(var(--an-radius) - 2px) calc(var(--an-radius) - 2px) 0 0; + background: #0b101c; +} + +.cardText { + color: var(--an-text-dim); +} + +.cardText:first-child { + color: var(--an-text); +} + +.defaultCardBackground { + background: linear-gradient(135deg, #101b2e, #0a1120) !important; +} + +/* Watched / unwatched indicators */ + +.playedIndicator { + background: rgba(52, 211, 153, 0.92) !important; + color: #052015 !important; + border-radius: 8px 0 0 0; + font-weight: 700; + box-shadow: 0 2px 12px rgba(52, 211, 153, 0.5); +} + +.countIndicator { + background: linear-gradient(135deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 999px; + font-weight: 700; + box-shadow: var(--an-glow); +} + +/* Progress bars under cards */ + +.itemProgressBar { + height: 4px; + background: rgba(255, 255, 255, 0.10); + border-radius: 0 0 var(--an-radius) var(--an-radius); +} + +.itemProgressBarForeground { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 0 0 var(--an-radius) var(--an-radius); +} + +/* ---------------- Buttons ---------------- */ + +.button-submit, +.btnPlay, +.fab.primary { + background: linear-gradient(135deg, #22d3ee 0%, #818cf8 100%) !important; + color: #ffffff !important; + font-weight: 600; + border-radius: 10px !important; + border: none !important; + box-shadow: 0 6px 18px rgba(34, 211, 238, 0.25); + transition: filter 0.2s ease, box-shadow 0.2s ease; +} + +.button-submit:hover, +.button-submit:focus, +.btnPlay:hover, +.fab.primary:hover { + filter: brightness(1.1); + box-shadow: 0 8px 26px rgba(34, 211, 238, 0.45); +} + +.emby-button:not(.raised):focus-visible, +.emby-button:not(.raised):hover { + color: var(--an-accent); +} + +/* ---------------- Detail page ---------------- */ + +.itemDetailImage, +.detailImageContainer img { + border-radius: 12px; + border: 1px solid var(--an-border); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.65); +} + +h1, +h2, +h3 { + color: var(--an-text) !important; + text-shadow: 0 2px 24px rgba(0, 0, 0, 0.85); +} + +.nameContainer, +.itemName, +.itemMiscInfo { + text-shadow: 0 2px 24px rgba(0, 0, 0, 0.85); +} + +.secondaryText, +.itemMiscInfo, +.mediaInfoText { + color: var(--an-text-dim) !important; +} + +.mediaInfoText { + background: rgba(13, 18, 30, 0.72); + border: 1px solid var(--an-border); + border-radius: 8px; + padding: 0.25em 0.7em; +} + +/* ---------------- Dialogs, sheets, menus ---------------- */ + +.dialog { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border); + border-radius: 16px !important; + box-shadow: var(--an-shadow); +} + +.actionSheet { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border); + border-radius: 16px !important; + overflow: hidden; + box-shadow: var(--an-shadow); +} + +.formDialogHeader { + border-bottom: 1px solid var(--an-border); +} + +.formDialogFooter { + border-top: 1px solid var(--an-border); +} + +.actionSheetMenuItem, +.listItem { + border-radius: 10px; + color: var(--an-text) !important; +} + +.actionSheetMenuItem:hover, +.actionSheetMenuItem.showFocus, +.listItem:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.listItem:hover .listItemBody, +.listItem .secondary { + color: var(--an-text-dim); +} + +.paperList { + background: rgba(9, 13, 23, 0.72); + border: 1px solid var(--an-border); + border-radius: 12px; +} + +/* ---------------- Inputs ---------------- */ + +.emby-input, +.emby-select, +.emby-textarea { + background: rgba(10, 15, 26, 0.85) !important; + border: 1px solid var(--an-border) !important; + border-radius: 10px !important; + color: var(--an-text) !important; +} + +.emby-input:focus, +.emby-select:focus, +.emby-textarea:focus { + border-color: var(--an-accent) !important; + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.18); +} + +.emby-select option { + background: #0b101c; + color: var(--an-text); +} + +.checkboxOutline { + border-radius: 6px; + border-color: var(--an-border); +} + +.emby-checkbox:checked + .checkboxOutline { + background: linear-gradient(135deg, var(--an-accent), var(--an-accent-2)) !important; + border-color: transparent !important; +} + +/* Player sliders */ + +.mdl-slider-background-lower { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 999px; +} + +.mdl-slider-background-upper { + background: rgba(255, 255, 255, 0.15) !important; + border-radius: 999px; +} + +/* ---------------- Player OSD ---------------- */ + +.videoOsdBottom { + background: linear-gradient(0deg, rgba(3, 6, 12, 0.94), rgba(3, 6, 12, 0.0)); +} + +.osdHeader { + background: linear-gradient(180deg, rgba(3, 6, 12, 0.85), rgba(3, 6, 12, 0.0)); + padding-top: 1em; +} + +/* ---------------- Login page ---------------- */ + +#loginPage .visualLoginForm { + background: rgba(7, 11, 20, 0.78); + backdrop-filter: blur(22px) saturate(1.25); + -webkit-backdrop-filter: blur(22px) saturate(1.25); + border: 1px solid var(--an-border); + border-radius: 20px; + box-shadow: var(--an-shadow); + max-width: 480px; + margin: 0 auto; + padding: 2.6em 2.8em !important; +} + +#loginPage .visualLoginForm h1 { + text-align: center; + letter-spacing: 0.5px; +} + +#loginPage .disclaimerContainer { + text-align: center; + color: var(--an-text-dim); +} + +/* ---------------- Toasts & misc ---------------- */ + +.toast { + background: rgba(9, 13, 23, 0.95) !important; + border: 1px solid var(--an-border); + border-radius: 12px; + box-shadow: var(--an-shadow); + color: var(--an-text) !important; +} + +.itemSelectionPanel { + background: rgba(34, 211, 238, 0.85) !important; +} + +.sectionTitle, +.sectionTitleText { + color: var(--an-text) !important; + letter-spacing: 0.2px; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: #070b14; +} + +::-webkit-scrollbar-thumb { + background: rgba(34, 211, 238, 0.35); + border-radius: 8px; + border: 2px solid #070b14; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(34, 211, 238, 0.6); +} + +/* ---------------- Motion safety ---------------- */ + +@media (prefers-reduced-motion: reduce) { + + .cardBox, + .button-submit, + .btnPlay, + .fab.primary { + transition: none !important; + } + + .card:hover .cardBox, + .card.showFocus .cardBox { + transform: none !important; + } +} + +/* ---------------- Admin-SPA (MUI) Overrides ---------------- */ + +.MuiPaper-root, +.MuiCard-root, +.MuiAccordion-root, +.MuiDrawer-paper { + background-color: var(--an-surface-solid) !important; + background-image: linear-gradient(160deg, rgba(13, 18, 30, 0.92), rgba(8, 12, 22, 0.97)) !important; + border: 1px solid var(--an-border) !important; + border-radius: 14px !important; + box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5) !important; + color: var(--an-text) !important; +} + +.MuiCard-root:hover { + border-color: var(--an-border-hover); + box-shadow: 0 16px 44px rgba(0, 0, 0, 0.6), var(--an-glow) !important; +} + +.MuiAppBar-root, +.MuiToolbar-root { + background: linear-gradient(180deg, rgba(5, 8, 16, 0.95), rgba(5, 8, 16, 0.72)) !important; + backdrop-filter: blur(18px) saturate(1.25); + -webkit-backdrop-filter: blur(18px) saturate(1.25); + border-bottom: 1px solid var(--an-border) !important; + color: var(--an-text) !important; +} + +.MuiTypography-root, +.MuiListItemText-primary, +.MuiListItemText-secondary, +.MuiTableCell-root { + color: var(--an-text) !important; +} + +.MuiListItemText-secondary, +.MuiTypography-colorTextSecondary { + color: var(--an-text-dim) !important; +} + +.MuiButton-containedPrimary, +.MuiButton-contained { + background: linear-gradient(135deg, #22d3ee 0%, #818cf8 100%) !important; + color: #ffffff !important; + border-radius: 10px !important; + font-weight: 600; + box-shadow: 0 6px 18px rgba(34, 211, 238, 0.25); +} + +.MuiButton-contained:hover { + filter: brightness(1.1); + box-shadow: 0 8px 26px rgba(34, 211, 238, 0.45); +} + +.MuiButton-outlined, +.MuiButton-text { + color: var(--an-accent) !important; + border-color: var(--an-border-hover) !important; + border-radius: 10px !important; +} + +.MuiListItem-root:hover, +.MuiListItem-button:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.MuiListItemButton-root:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.Mui-selected, +.Mui-selected:hover { + background: rgba(34, 211, 238, 0.16) !important; +} + +.MuiInputBase-root, +.MuiOutlinedInput-root, +.MuiFilledInput-root { + background: rgba(10, 15, 26, 0.85) !important; + border-radius: 10px !important; +} + +.MuiOutlinedInput-notchedOutline { + border-color: var(--an-border) !important; +} + +.MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline { + border-color: var(--an-accent) !important; +} + +.MuiSwitch-switchBase.Mui-checked { + color: var(--an-accent) !important; +} + +.MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; +} + +.MuiChip-root { + background: rgba(13, 18, 30, 0.85) !important; + border: 1px solid var(--an-border) !important; + color: var(--an-text) !important; + border-radius: 999px !important; +} + +.MuiChip-root.MuiChip-colorPrimary { + background: linear-gradient(135deg, rgba(34, 211, 238, 0.2), rgba(129, 140, 248, 0.2)) !important; + color: var(--an-accent) !important; +} + +.MuiTableCell-root { + border-bottom: 1px solid var(--an-border) !important; +} + +.MuiTableHead-root .MuiTableCell-root { + color: var(--an-accent) !important; + font-weight: 600; +} + +.MuiDivider-root { + border-color: var(--an-border) !important; +} + +.MuiDialog-paper, +.MuiMenu-paper, +.MuiPopover-paper { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border) !important; + border-radius: 16px !important; + box-shadow: var(--an-shadow) !important; +} + +.MuiLinearProgress-bar { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; +} + +.MuiCircularProgress-root { + color: var(--an-accent) !important; +} + +.MuiAlert-root { + border-radius: 12px !important; + border: 1px solid var(--an-border) !important; +} + + false + diff --git a/web-assets/aurora_noir.css b/web-assets/aurora_noir.css new file mode 100644 index 0000000..6da64b1 --- /dev/null +++ b/web-assets/aurora_noir.css @@ -0,0 +1,582 @@ +/* ============================================================ + AURORA NOIR - cinematic theme for Stream Xtream + Accent: aurora cyan -> indigo | Base: deep space #050810 + Perf: single external bg asset, blur only on chrome surfaces + ============================================================ */ + +:root { + --an-bg: #050810; + --an-surface: rgba(13, 18, 30, 0.62); + --an-surface-solid: #0b101c; + --an-border: rgba(148, 163, 184, 0.14); + --an-border-hover: rgba(34, 211, 238, 0.55); + --an-accent: #22d3ee; + --an-accent-2: #818cf8; + --an-text: #f1f5f9; + --an-text-dim: #94a3b8; + --an-radius: 14px; + --an-glow: 0 0 24px rgba(34, 211, 238, 0.30); + --an-shadow: 0 18px 48px rgba(0, 0, 0, 0.55); +} + +html { + color-scheme: dark; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + scrollbar-color: rgba(34, 211, 238, 0.45) #070b14; +} + +body { + background-color: var(--an-bg); + color: var(--an-text); +} + +/* ---------------- Ambient background ---------------- */ + +.backgroundContainer { + background: #050810 url(/web/custom-assets/custom_bg.webp) center / cover no-repeat fixed !important; +} + +/* Cinematic grade over fanart backdrops so text stays readable */ +.backdropImage { + filter: saturate(1.05) brightness(0.45) !important; +} + +.backgroundContainer::after { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: linear-gradient(180deg, + rgba(5, 8, 16, 0.66) 0%, + rgba(5, 8, 16, 0.18) 34%, + rgba(5, 8, 16, 0.30) 62%, + rgba(5, 8, 16, 0.78) 100%); +} + +/* ---------------- Header ---------------- */ + +.skinHeader { + background: linear-gradient(180deg, rgba(5, 8, 16, 0.92), rgba(5, 8, 16, 0.55)) !important; + backdrop-filter: blur(18px) saturate(1.25); + -webkit-backdrop-filter: blur(18px) saturate(1.25); + border-bottom: 1px solid var(--an-border) !important; +} + +.headerTop { + padding: 0.6em 0.9em; +} + +.emby-tab-button { + color: var(--an-text-dim); +} + +.emby-tab-button-active { + color: var(--an-accent) !important; + text-shadow: 0 0 18px rgba(34, 211, 238, 0.45); +} + +.paper-icon-button-light:hover:not(:disabled) { + color: var(--an-accent); + background-color: rgba(34, 211, 238, 0.10); + border-radius: 8px; +} + +.paper-icon-button-light:focus-visible { + outline: 2px solid var(--an-accent); + outline-offset: 2px; + border-radius: 8px; +} + +.headerButton { + color: var(--an-text); +} + +/* ---------------- Drawer / side menu ---------------- */ + +.mainDrawer { + background: rgba(7, 11, 20, 0.96) !important; + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-right: 1px solid var(--an-border); +} + +.navDrawerMask { + background: rgba(2, 4, 8, 0.55); +} + +.navMenuOption { + border-radius: 10px; + margin: 0.25em 0.6em; +} + +.navMenuOptionIcon { + color: var(--an-accent) !important; + text-shadow: 0 0 14px rgba(34, 211, 238, 0.55); +} + +.navMenuOption:hover, +.navMenuOption.showFocus { + background: rgba(34, 211, 238, 0.12) !important; +} + +.navMenuOptionText { + color: var(--an-text); + font-weight: 500; +} + +/* ---------------- Cards ---------------- */ + +.cardBox { + border-radius: var(--an-radius); + border: 1px solid var(--an-border); + background: var(--an-surface); + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45); + transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.3, 1), + box-shadow 0.28s ease, border-color 0.28s ease; +} + +.card:hover .cardBox, +.card.showFocus .cardBox { + transform: translateY(-4px); + border-color: var(--an-border-hover); + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.60), var(--an-glow); +} + +.cardImageContainer { + border-radius: calc(var(--an-radius) - 2px) calc(var(--an-radius) - 2px) 0 0; + background: #0b101c; +} + +.cardText { + color: var(--an-text-dim); +} + +.cardText:first-child { + color: var(--an-text); +} + +.defaultCardBackground { + background: linear-gradient(135deg, #101b2e, #0a1120) !important; +} + +/* Watched / unwatched indicators */ + +.playedIndicator { + background: rgba(52, 211, 153, 0.92) !important; + color: #052015 !important; + border-radius: 8px 0 0 0; + font-weight: 700; + box-shadow: 0 2px 12px rgba(52, 211, 153, 0.5); +} + +.countIndicator { + background: linear-gradient(135deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 999px; + font-weight: 700; + box-shadow: var(--an-glow); +} + +/* Progress bars under cards */ + +.itemProgressBar { + height: 4px; + background: rgba(255, 255, 255, 0.10); + border-radius: 0 0 var(--an-radius) var(--an-radius); +} + +.itemProgressBarForeground { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 0 0 var(--an-radius) var(--an-radius); +} + +/* ---------------- Buttons ---------------- */ + +.button-submit, +.btnPlay, +.fab.primary { + background: linear-gradient(135deg, #22d3ee 0%, #818cf8 100%) !important; + color: #ffffff !important; + font-weight: 600; + border-radius: 10px !important; + border: none !important; + box-shadow: 0 6px 18px rgba(34, 211, 238, 0.25); + transition: filter 0.2s ease, box-shadow 0.2s ease; +} + +.button-submit:hover, +.button-submit:focus, +.btnPlay:hover, +.fab.primary:hover { + filter: brightness(1.1); + box-shadow: 0 8px 26px rgba(34, 211, 238, 0.45); +} + +.emby-button:not(.raised):focus-visible, +.emby-button:not(.raised):hover { + color: var(--an-accent); +} + +/* ---------------- Detail page ---------------- */ + +.itemDetailImage, +.detailImageContainer img { + border-radius: 12px; + border: 1px solid var(--an-border); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.65); +} + +h1, +h2, +h3 { + color: var(--an-text) !important; + text-shadow: 0 2px 24px rgba(0, 0, 0, 0.85); +} + +.nameContainer, +.itemName, +.itemMiscInfo { + text-shadow: 0 2px 24px rgba(0, 0, 0, 0.85); +} + +.secondaryText, +.itemMiscInfo, +.mediaInfoText { + color: var(--an-text-dim) !important; +} + +.mediaInfoText { + background: rgba(13, 18, 30, 0.72); + border: 1px solid var(--an-border); + border-radius: 8px; + padding: 0.25em 0.7em; +} + +/* ---------------- Dialogs, sheets, menus ---------------- */ + +.dialog { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border); + border-radius: 16px !important; + box-shadow: var(--an-shadow); +} + +.actionSheet { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border); + border-radius: 16px !important; + overflow: hidden; + box-shadow: var(--an-shadow); +} + +.formDialogHeader { + border-bottom: 1px solid var(--an-border); +} + +.formDialogFooter { + border-top: 1px solid var(--an-border); +} + +.actionSheetMenuItem, +.listItem { + border-radius: 10px; + color: var(--an-text) !important; +} + +.actionSheetMenuItem:hover, +.actionSheetMenuItem.showFocus, +.listItem:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.listItem:hover .listItemBody, +.listItem .secondary { + color: var(--an-text-dim); +} + +.paperList { + background: rgba(9, 13, 23, 0.72); + border: 1px solid var(--an-border); + border-radius: 12px; +} + +/* ---------------- Inputs ---------------- */ + +.emby-input, +.emby-select, +.emby-textarea { + background: rgba(10, 15, 26, 0.85) !important; + border: 1px solid var(--an-border) !important; + border-radius: 10px !important; + color: var(--an-text) !important; +} + +.emby-input:focus, +.emby-select:focus, +.emby-textarea:focus { + border-color: var(--an-accent) !important; + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.18); +} + +.emby-select option { + background: #0b101c; + color: var(--an-text); +} + +.checkboxOutline { + border-radius: 6px; + border-color: var(--an-border); +} + +.emby-checkbox:checked + .checkboxOutline { + background: linear-gradient(135deg, var(--an-accent), var(--an-accent-2)) !important; + border-color: transparent !important; +} + +/* Player sliders */ + +.mdl-slider-background-lower { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; + border-radius: 999px; +} + +.mdl-slider-background-upper { + background: rgba(255, 255, 255, 0.15) !important; + border-radius: 999px; +} + +/* ---------------- Player OSD ---------------- */ + +.videoOsdBottom { + background: linear-gradient(0deg, rgba(3, 6, 12, 0.94), rgba(3, 6, 12, 0.0)); +} + +.osdHeader { + background: linear-gradient(180deg, rgba(3, 6, 12, 0.85), rgba(3, 6, 12, 0.0)); + padding-top: 1em; +} + +/* ---------------- Login page ---------------- */ + +#loginPage .visualLoginForm { + background: rgba(7, 11, 20, 0.78); + backdrop-filter: blur(22px) saturate(1.25); + -webkit-backdrop-filter: blur(22px) saturate(1.25); + border: 1px solid var(--an-border); + border-radius: 20px; + box-shadow: var(--an-shadow); + max-width: 480px; + margin: 0 auto; + padding: 2.6em 2.8em !important; +} + +#loginPage .visualLoginForm h1 { + text-align: center; + letter-spacing: 0.5px; +} + +#loginPage .disclaimerContainer { + text-align: center; + color: var(--an-text-dim); +} + +/* ---------------- Toasts & misc ---------------- */ + +.toast { + background: rgba(9, 13, 23, 0.95) !important; + border: 1px solid var(--an-border); + border-radius: 12px; + box-shadow: var(--an-shadow); + color: var(--an-text) !important; +} + +.itemSelectionPanel { + background: rgba(34, 211, 238, 0.85) !important; +} + +.sectionTitle, +.sectionTitleText { + color: var(--an-text) !important; + letter-spacing: 0.2px; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: #070b14; +} + +::-webkit-scrollbar-thumb { + background: rgba(34, 211, 238, 0.35); + border-radius: 8px; + border: 2px solid #070b14; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(34, 211, 238, 0.6); +} + +/* ---------------- Motion safety ---------------- */ + +@media (prefers-reduced-motion: reduce) { + + .cardBox, + .button-submit, + .btnPlay, + .fab.primary { + transition: none !important; + } + + .card:hover .cardBox, + .card.showFocus .cardBox { + transform: none !important; + } +} + +/* ---------------- Admin-SPA (MUI) Overrides ---------------- */ + +.MuiPaper-root, +.MuiCard-root, +.MuiAccordion-root, +.MuiDrawer-paper { + background-color: var(--an-surface-solid) !important; + background-image: linear-gradient(160deg, rgba(13, 18, 30, 0.92), rgba(8, 12, 22, 0.97)) !important; + border: 1px solid var(--an-border) !important; + border-radius: 14px !important; + box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5) !important; + color: var(--an-text) !important; +} + +.MuiCard-root:hover { + border-color: var(--an-border-hover); + box-shadow: 0 16px 44px rgba(0, 0, 0, 0.6), var(--an-glow) !important; +} + +.MuiAppBar-root, +.MuiToolbar-root { + background: linear-gradient(180deg, rgba(5, 8, 16, 0.95), rgba(5, 8, 16, 0.72)) !important; + backdrop-filter: blur(18px) saturate(1.25); + -webkit-backdrop-filter: blur(18px) saturate(1.25); + border-bottom: 1px solid var(--an-border) !important; + color: var(--an-text) !important; +} + +.MuiTypography-root, +.MuiListItemText-primary, +.MuiListItemText-secondary, +.MuiTableCell-root { + color: var(--an-text) !important; +} + +.MuiListItemText-secondary, +.MuiTypography-colorTextSecondary { + color: var(--an-text-dim) !important; +} + +.MuiButton-containedPrimary, +.MuiButton-contained { + background: linear-gradient(135deg, #22d3ee 0%, #818cf8 100%) !important; + color: #ffffff !important; + border-radius: 10px !important; + font-weight: 600; + box-shadow: 0 6px 18px rgba(34, 211, 238, 0.25); +} + +.MuiButton-contained:hover { + filter: brightness(1.1); + box-shadow: 0 8px 26px rgba(34, 211, 238, 0.45); +} + +.MuiButton-outlined, +.MuiButton-text { + color: var(--an-accent) !important; + border-color: var(--an-border-hover) !important; + border-radius: 10px !important; +} + +.MuiListItem-root:hover, +.MuiListItem-button:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.MuiListItemButton-root:hover { + background: rgba(34, 211, 238, 0.10) !important; +} + +.Mui-selected, +.Mui-selected:hover { + background: rgba(34, 211, 238, 0.16) !important; +} + +.MuiInputBase-root, +.MuiOutlinedInput-root, +.MuiFilledInput-root { + background: rgba(10, 15, 26, 0.85) !important; + border-radius: 10px !important; +} + +.MuiOutlinedInput-notchedOutline { + border-color: var(--an-border) !important; +} + +.MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline { + border-color: var(--an-accent) !important; +} + +.MuiSwitch-switchBase.Mui-checked { + color: var(--an-accent) !important; +} + +.MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; +} + +.MuiChip-root { + background: rgba(13, 18, 30, 0.85) !important; + border: 1px solid var(--an-border) !important; + color: var(--an-text) !important; + border-radius: 999px !important; +} + +.MuiChip-root.MuiChip-colorPrimary { + background: linear-gradient(135deg, rgba(34, 211, 238, 0.2), rgba(129, 140, 248, 0.2)) !important; + color: var(--an-accent) !important; +} + +.MuiTableCell-root { + border-bottom: 1px solid var(--an-border) !important; +} + +.MuiTableHead-root .MuiTableCell-root { + color: var(--an-accent) !important; + font-weight: 600; +} + +.MuiDivider-root { + border-color: var(--an-border) !important; +} + +.MuiDialog-paper, +.MuiMenu-paper, +.MuiPopover-paper { + background: rgba(9, 13, 23, 0.97) !important; + border: 1px solid var(--an-border) !important; + border-radius: 16px !important; + box-shadow: var(--an-shadow) !important; +} + +.MuiLinearProgress-bar { + background: linear-gradient(90deg, var(--an-accent), var(--an-accent-2)) !important; +} + +.MuiCircularProgress-root { + color: var(--an-accent) !important; +} + +.MuiAlert-root { + border-radius: 12px !important; + border: 1px solid var(--an-border) !important; +} diff --git a/web-assets/custom_bg.webp b/web-assets/custom_bg.webp new file mode 100644 index 0000000..42fc2c4 Binary files /dev/null and b/web-assets/custom_bg.webp differ diff --git a/web-assets/custom_favicon.ico b/web-assets/custom_favicon.ico new file mode 100644 index 0000000..5ffbaae Binary files /dev/null and b/web-assets/custom_favicon.ico differ