#!/usr/bin/env python3 """Port "The Theater" login design (variant 01 from picker R3) to tv.s8n.ru jellyfin-stock. Edits: 1. /opt/docker/jellyfin-stock/web-overrides/index.html (precss) 2. /home/docker/jellyfin-stock/config/config/branding.xml (CustomCss) Theater spec: - Backdrop: Cineplex poster-bg.jpg with vignette + linear shadow - Top bar: 88px tall, transparent gradient fade, ARRFLIX logo CENTERED (156px wide) via .skinHeader::before - Hide .pageTitleWithLogo top-left (we paint centered instead) - "Sign In" heading: keep stock h1.sectionTitle "Please sign in", re-style large + centered (the visible text stays "Please sign in" -- short of bundle JS rewrite that's the JF i18n string; matches arrflix.s8n.ru behavior) - Form card: .padded-left.padded-right.padded-bottom-page styled as 540px wide dark glass card, padding, border, blur - Inputs: underline-only, red focus - Sign In: full-width red square button, 4px radius - Remember Me: red square checkbox (already wired in earlier round) - Disclaimer: small white-50% centered """ import re, sys THEATER_PRECSS = """ /* === ARRFLIX 2026-05-12 -- THE THEATER login port ==================== Applied AFTER previous "ARRFLIX login-parity" + selectserver-extend + hide-visualform + polish-flash blocks. !important + late cascade beats earlier rules. Sources: picker variant 01 at /tmp/picker_assets/picker.html (.v1.*) User pick 2026-05-12: "the theater sign in looks the best". -------------------------------------------------------------------- */ /* Top bar: 88px tall transparent fade. ARRFLIX wordmark centered. */ .skinHeader, .skinHeader.semiTransparent, .skinHeader.focuscontainer-x, .skinHeader.focuscontainer-x.skinHeader-withBackground, .skinHeader.focuscontainer-x.skinHeader-withBackground.skinHeader-blurred { height: 88px !important; background: linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 100%) !important; border: none !important; contain: none !important; overflow: visible !important; } /* Centered wordmark in top bar -- override the earlier .skinHeader::before { bg: none } from selectserver-extend block. */ .skinHeader::before { content: "" !important; position: fixed !important; top: 16px !important; left: 50% !important; transform: translateX(-50%) !important; width: 156px !important; height: 56px !important; background-image: var(--arrflix-logo) !important; background-repeat: no-repeat !important; background-position: center !important; background-size: contain !important; z-index: 1001 !important; pointer-events: none !important; } /* Kill the top-left .pageTitleWithLogo entirely -- centered ::before above replaces it. */ .pageTitleWithLogo, .pageTitleWithDefaultLogo, h3.pageTitle.pageTitleWithLogo, h3.pageTitle.pageTitleWithDefaultLogo { display: none !important; } /* #loginPage backdrop already handled by earlier #loginPage block (poster-bg + vignette). Add a stronger vignette on top so the card has more contrast. */ #loginPage::before { content: "" !important; position: absolute !important; inset: 0 !important; background: radial-gradient(ellipse 70% 60% at center, rgba(0,0,0,0) 0%, rgba(0,0,0,0.55) 55%, rgba(0,0,0,0.92) 100%), linear-gradient(180deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 75%, rgba(0,0,0,0.7) 100%) !important; pointer-events: none !important; z-index: 0 !important; /* override the earlier blur+filter from cineplex.css #loginPage::before */ filter: none !important; transform: none !important; } /* UN-hide the "Please sign in" heading (was hidden by hide-visualform block). Re-style as Theater Sign In heading -- big, white, centered. */ #loginPage h1.sectionTitle, #loginPage .formDialogHeader, #loginPage .padded-left.padded-right.padded-bottom-page > h1.sectionTitle { display: block !important; font-family: "Bebas Neue", "Anton", Impact, "Haettenschweiler", "Liberation Sans Narrow", "Arial Narrow Bold", sans-serif !important; font-size: 48px !important; color: #fff !important; letter-spacing: 0.03em !important; margin: 0 0 32px !important; padding: 0 !important; line-height: 1 !important; text-align: center !important; text-transform: none !important; font-weight: 400 !important; } /* Form card: style the padded-left wrapper as a 540px-wide centered dark-glass card. Override hide-visualform's flex layout to keep center-vertical positioning. */ #loginPage .padded-left.padded-right.padded-bottom-page { max-width: 540px !important; width: 540px !important; margin: 0 auto !important; padding: 56px 56px 44px !important; background: rgba(0, 0, 0, 0.78) !important; border: 1px solid rgba(255,255,255,0.06) !important; border-radius: 0 !important; backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important; min-height: 0 !important; align-items: stretch !important; box-shadow: 0 30px 80px rgba(0,0,0,0.6) !important; } /* Vertical center the card on the page (account for 88px top bar). */ #loginPage { display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; padding-top: 88px !important; } /* Form-internal: manualLoginForm + visualLoginForm full width inside card. */ #loginPage .manualLoginForm, #loginPage form.manualLoginForm { width: 100% !important; max-width: 100% !important; margin: 0 !important; } /* Inputs: underline-only, transparent bg, red focus. */ #loginPage .inputContainer { margin-bottom: 24px !important; } #loginPage .inputContainer label, #loginPage .inputLabelFocused, #loginPage label.inputLabel { font-family: "SF Mono", "Monaco", "Cascadia Code", "JetBrains Mono", "Roboto Mono", "DejaVu Sans Mono", "Liberation Mono", Consolas, monospace !important; font-size: 11px !important; color: rgba(255,255,255,0.55) !important; text-transform: uppercase !important; letter-spacing: 0.22em !important; margin-bottom: 10px !important; font-weight: 600 !important; display: block !important; } #loginPage .emby-input { width: 100% !important; background: transparent !important; border: none !important; border-bottom: 1px solid rgba(255,255,255,0.18) !important; border-radius: 0 !important; padding: 12px 0 14px !important; font-size: 18px !important; color: #fff !important; outline: none !important; box-shadow: none !important; } #loginPage .emby-input:focus, #loginPage .inputContainer.focused .emby-input { border-bottom-color: #E50914 !important; box-shadow: none !important; } /* Remember Me row centered. (Checkbox red already from earlier polish.) */ #loginPage .checkboxContainer, #loginPage label.emby-checkbox-label { display: flex !important; align-items: center !important; justify-content: center !important; margin: 28px 0 30px !important; padding: 0 !important; } /* Sign In button: full-width red, 4px radius, square label. */ #loginPage .raised.button-submit, #loginPage button.raised.button-submit, #loginPage .raised.block.emby-button[type="submit"] { width: 100% !important; background: #E50914 !important; color: #fff !important; font-size: 17px !important; font-weight: 700 !important; padding: 18px !important; border: none !important; border-radius: 4px !important; letter-spacing: 0.04em !important; text-transform: none !important; margin: 0 0 0 !important; box-shadow: none !important; } /* Disclaimer: centered, light grey, small. */ #loginPage .loginDisclaimer, #loginPage .disclaimerContainer, #loginPage .loginDisclaimerContainer { margin-top: 28px !important; font-size: 12px !important; color: rgba(255,255,255,0.5) !important; text-align: center !important; letter-spacing: 0.04em !important; width: 100% !important; display: block !important; } """ # Branding mirror: same selectors + rules, copied. Doesn't include the # var(--arrflix-logo) reference issue since branding.xml already # defines --arrflix-logo at L64. THEATER_BRANDING = THEATER_PRECSS # identical CSS body def patch_precss(path, marker, block): with open(path, "r", encoding="utf-8") as f: s = f.read() if marker in s: print(f" {path}: already patched") return m = re.search(r'()', s, flags=re.DOTALL) if not m: sys.exit(f"ERROR: arrflix-precss block not found in {path}") s = s[:m.end(1)] + block + s[m.end(1):] with open(path, "w", encoding="utf-8") as f: f.write(s) print(f" {path}: patched (+{len(block)})") def patch_branding(path, marker, block): with open(path, "r", encoding="utf-8") as f: s = f.read() if marker in s: print(f" {path}: already patched") return CLOSE = "" if CLOSE not in s: sys.exit(f"ERROR: not found in {path}") s = s.replace(CLOSE, block + CLOSE, 1) with open(path, "w", encoding="utf-8") as f: f.write(s) print(f" {path}: patched (+{len(block)})") MARKER = "THE THEATER login port" patch_precss( "/opt/docker/jellyfin-stock/web-overrides/index.html", MARKER, THEATER_PRECSS) patch_branding( "/home/docker/jellyfin-stock/config/config/branding.xml", MARKER, THEATER_BRANDING)