legacy-arrflix/web-overrides/theater-design/tv_theater_port.py
s8n 034dbe68c0
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions
docs(theater): import design picker assets from /tmp
4-variant ARRFLIX login picker served at localhost:666 (variants 01 The
Theater · 02 The Marquee · 03 The Cinema · 04 The Noir). Variant 01 was
selected. Includes theater-fullsize.html (1920x1080 mockup), 470x170
landscape arrflix-logo (PNG + b64), poster-bg backdrop, and tv_theater_port.py
CSS-block patcher reference for the later prod deploy.

Persisted from /tmp to survive reboot; iteration continues in this dir
under a 4-agent crew (Designer / Coder / Chromium / Inspector).
2026-05-12 17:17:38 +01:00

268 lines
9.6 KiB
Python

#!/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'(<style id="arrflix-precss">.*?)(</style>)', 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 = "</CustomCss>"
if CLOSE not in s:
sys.exit(f"ERROR: </CustomCss> 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)