Hermes Agent commited on
Commit
d080af5
·
1 Parent(s): 6529dfd

feat(ui): restore original clean Jellyfin layout with pure OLED pitch black theme

Browse files
Dockerfile CHANGED
@@ -39,7 +39,11 @@ RUN python3 -m pip install --upgrade pip --break-system-packages && \
39
  COPY . .
40
  COPY apex /opt/apex
41
 
42
- RUN chmod +x /app/combined-entrypoint.sh /opt/apex/health_doctor.py /opt/apex/combined-entrypoint.sh
 
 
 
 
43
 
44
  EXPOSE 7860
45
 
 
39
  COPY . .
40
  COPY apex /opt/apex
41
 
42
+ RUN if [ -f /opt/apex/theme/pitch-black.css ] && [ -d /usr/share/jellyfin/web ]; then \
43
+ cp /opt/apex/theme/pitch-black.css /usr/share/jellyfin/web/pitch-black.css \
44
+ && sed -i 's|</head>|<link rel="stylesheet" href="pitch-black.css"></head>|' /usr/share/jellyfin/web/index.html; \
45
+ fi \
46
+ && chmod +x /app/combined-entrypoint.sh /opt/apex/health_doctor.py /opt/apex/combined-entrypoint.sh
47
 
48
  EXPOSE 7860
49
 
apex/combined-entrypoint.sh CHANGED
@@ -64,8 +64,25 @@ restore_jellyfin_local() {
64
  ln -s "$LOCAL_ROOT/data/metadata" /data/jellyfin/data/metadata 2>/dev/null || true
65
  if [ -d "$LOCAL_ROOT/data/metadata" ]; then
66
  mkdir -p /data/jellyfin/data/metadata.snapshot
67
- cp -a -f "$LOCAL_ROOT/data/metadata/." /data/jellyfin/data/metadata.snapshot/ 2>/dev/null || true
68
  fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  echo "[JELLYFIN] Local datadir ready. Metadata redirected to fast local disk."
70
  }
71
 
 
64
  ln -s "$LOCAL_ROOT/data/metadata" /data/jellyfin/data/metadata 2>/dev/null || true
65
  if [ -d "$LOCAL_ROOT/data/metadata" ]; then
66
  mkdir -p /data/jellyfin/data/metadata.snapshot
 
67
  fi
68
+
69
+ # Seed and sync Pitch Black OLED dark theme branding
70
+ if [ -f "/opt/apex/theme/branding.xml" ]; then
71
+ echo "[JELLYFIN] Applying Pitch Black OLED dark theme branding..."
72
+ cp -f /opt/apex/theme/branding.xml "$LOCAL_ROOT/config/branding.xml"
73
+ mkdir -p /data/jellyfin/config
74
+ cp -f /opt/apex/theme/branding.xml /data/jellyfin/config/branding.xml 2>/dev/null || true
75
+ fi
76
+
77
+ # Pre-inject pitch-black.css into web assets for instant loading
78
+ if [ -f "/opt/apex/theme/pitch-black.css" ] && [ -d "/usr/share/jellyfin/web" ]; then
79
+ cp -f /opt/apex/theme/pitch-black.css /usr/share/jellyfin/web/pitch-black.css 2>/dev/null || true
80
+ if [ -f "/usr/share/jellyfin/web/index.html" ] && ! grep -q "pitch-black.css" /usr/share/jellyfin/web/index.html; then
81
+ sed -i 's|</head>|<link rel="stylesheet" href="pitch-black.css"></head>|' /usr/share/jellyfin/web/index.html 2>/dev/null || true
82
+ echo "[JELLYFIN] Pitch Black OLED theme injected into web index.html."
83
+ fi
84
+ fi
85
+
86
  echo "[JELLYFIN] Local datadir ready. Metadata redirected to fast local disk."
87
  }
88
 
apex/entrypoint.sh CHANGED
@@ -49,8 +49,25 @@ restore_jellyfin_local() {
49
  ln -s "$LOCAL_ROOT/data/metadata" /data/jellyfin/data/metadata 2>/dev/null || true
50
  if [ -d "$LOCAL_ROOT/data/metadata" ]; then
51
  mkdir -p /data/jellyfin/data/metadata.snapshot
52
- cp -a -f "$LOCAL_ROOT/data/metadata/." /data/jellyfin/data/metadata.snapshot/ 2>/dev/null || true
53
  fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  echo "[JELLYFIN] Local datadir ready. Metadata redirected to fast local disk."
55
  }
56
 
 
49
  ln -s "$LOCAL_ROOT/data/metadata" /data/jellyfin/data/metadata 2>/dev/null || true
50
  if [ -d "$LOCAL_ROOT/data/metadata" ]; then
51
  mkdir -p /data/jellyfin/data/metadata.snapshot
 
52
  fi
53
+
54
+ # Seed and sync Pitch Black OLED dark theme branding
55
+ if [ -f "/opt/apex/theme/branding.xml" ]; then
56
+ echo "[JELLYFIN] Applying Pitch Black OLED dark theme branding..."
57
+ cp -f /opt/apex/theme/branding.xml "$LOCAL_ROOT/config/branding.xml"
58
+ mkdir -p /data/jellyfin/config
59
+ cp -f /opt/apex/theme/branding.xml /data/jellyfin/config/branding.xml 2>/dev/null || true
60
+ fi
61
+
62
+ # Pre-inject pitch-black.css into web assets for instant loading
63
+ if [ -f "/opt/apex/theme/pitch-black.css" ] && [ -d "/usr/share/jellyfin/web" ]; then
64
+ cp -f /opt/apex/theme/pitch-black.css /usr/share/jellyfin/web/pitch-black.css 2>/dev/null || true
65
+ if [ -f "/usr/share/jellyfin/web/index.html" ] && ! grep -q "pitch-black.css" /usr/share/jellyfin/web/index.html; then
66
+ sed -i 's|</head>|<link rel="stylesheet" href="pitch-black.css"></head>|' /usr/share/jellyfin/web/index.html 2>/dev/null || true
67
+ echo "[JELLYFIN] Pitch Black OLED theme injected into web index.html."
68
+ fi
69
+ fi
70
+
71
  echo "[JELLYFIN] Local datadir ready. Metadata redirected to fast local disk."
72
  }
73
 
apex/tests/test_pitch_black_theme.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import xml.etree.ElementTree as ET
3
+ import pytest
4
+
5
+ def test_pitch_black_theme_exists_and_valid():
6
+ theme_path = os.path.join(os.path.dirname(__file__), "..", "theme", "pitch-black.css")
7
+ assert os.path.exists(theme_path), "pitch-black.css must exist"
8
+
9
+ with open(theme_path, "r", encoding="utf-8") as f:
10
+ content = f.read()
11
+
12
+ # Assert pure pitch black and red accent tokens
13
+ assert "#000000" in content, "Must enforce pure pitch black background"
14
+ assert "#e50914" in content.lower(), "Must use Netflix red accent"
15
+
16
+ # Assert core components styled
17
+ assert "#loginPage" in content, "Must style login page in pure black"
18
+ assert "backgroundContainer" in content, "Must style background container"
19
+ assert "itemProgressBarForeground" in content, "Must style playback progress bar"
20
+
21
+ def test_branding_xml_is_valid():
22
+ xml_path = os.path.join(os.path.dirname(__file__), "..", "theme", "branding.xml")
23
+ assert os.path.exists(xml_path), "branding.xml must exist"
24
+
25
+ tree = ET.parse(xml_path)
26
+ root = tree.getroot()
27
+ assert root.tag == "BrandingOptions"
28
+ custom_css = root.find("CustomCss")
29
+ assert custom_css is not None
30
+ assert "#000000" in (custom_css.text or "")
31
+ assert "#e50914" in (custom_css.text or "").lower()
apex/theme/branding.xml ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <BrandingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3
+ <LoginDisclaimer />
4
+ <CustomCss><![CDATA[/* ==========================================================================
5
+ Apex / Jellyfin — Pitch Black OLED Dark Theme
6
+ Clean Original Layout with 100% Pure Pitch Black Surfaces & Netflix Red
7
+ ========================================================================== */
8
+
9
+ :root,
10
+ :root[data-theme] {
11
+ --accent: #E50914 !important;
12
+ --accent-hover: #b80710 !important;
13
+ --accent-glow: rgba(229, 9, 20, 0.4) !important;
14
+ --accentColor: #E50914 !important;
15
+ --accentHoverColor: #b80710 !important;
16
+ --uiAccentColor: #E50914 !important;
17
+ --activeColor: #E50914 !important;
18
+ --btnSubmitColor: #E50914 !important;
19
+ --btnMiniPlayColor: #E50914 !important;
20
+ --highlightOutlineColor: #E50914 !important;
21
+ --cardResumeProgressColor: #E50914 !important;
22
+ --osdSeekBarPlayedColor: #E50914 !important;
23
+ --osdSeekBarThumbColor: #E50914 !important;
24
+
25
+ --darkerGradientPoint: #000000 !important;
26
+ --darkerGradientPointAlpha: rgba(0, 0, 0, 0.95) !important;
27
+ --lighterGradientPoint: #050505 !important;
28
+ --lighterGradientPointAlpha: rgba(5, 5, 5, 0.95) !important;
29
+ --headerColor: rgba(0, 0, 0, 0.92) !important;
30
+ --drawerColor: rgba(0, 0, 0, 0.98) !important;
31
+ --borderColor: #222222 !important;
32
+ --darkerBorderColor: #141414 !important;
33
+ --selectorBackgroundColor: #111111 !important;
34
+ --tableBodyColor: #050505 !important;
35
+ --textareaBackgroundColor: #111111 !important;
36
+
37
+ --jf-palette-background-default: #000000 !important;
38
+ --jf-palette-background-paper: #0c0c0c !important;
39
+ --jf-palette-AppBar-defaultBg: rgba(0, 0, 0, 0.92) !important;
40
+ --jf-palette-primary-main: #E50914 !important;
41
+ --jf-palette-secondary-main: #E50914 !important;
42
+ --jf-palette-action-hover: rgba(229, 9, 20, 0.12) !important;
43
+ --jf-palette-action-selected: rgba(229, 9, 20, 0.22) !important;
44
+ --jf-palette-action-focus: rgba(229, 9, 20, 0.22) !important;
45
+ }
46
+
47
+ html,
48
+ body,
49
+ .backgroundContainer,
50
+ .backdropContainer,
51
+ .mainDrawer,
52
+ .skinHeader,
53
+ .page,
54
+ .view,
55
+ .dialog,
56
+ .actionSheet,
57
+ .cardBox,
58
+ .visualCardBox,
59
+ .paperList,
60
+ .mainDrawer-scrollContainer,
61
+ .dashboardSection,
62
+ .emby-tabs-slider,
63
+ div#itemDetailPage {
64
+ background-color: #000000 !important;
65
+ color: #FFFFFF;
66
+ }
67
+
68
+ .backgroundContainer.withBackdrop {
69
+ background-color: rgba(0, 0, 0, 0.88) !important;
70
+ }
71
+
72
+ .backdropContainer {
73
+ background-color: #000000 !important;
74
+ }
75
+
76
+ #loginPage {
77
+ background-color: #000000 !important;
78
+ }
79
+
80
+ #loginPage .readOnlyContent {
81
+ background-color: #0a0a0a !important;
82
+ border: 1px solid #222222 !important;
83
+ border-radius: 8px !important;
84
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.9) !important;
85
+ }
86
+
87
+ #loginPage .emby-input {
88
+ background: #161616 !important;
89
+ border: 1px solid #2a2a2a !important;
90
+ color: #FFFFFF !important;
91
+ }
92
+
93
+ #loginPage .emby-input:focus {
94
+ border-color: #E50914 !important;
95
+ }
96
+
97
+ #loginPage .raised.button-submit,
98
+ #loginPage .MuiButton-containedPrimary {
99
+ background-color: #E50914 !important;
100
+ color: #FFFFFF !important;
101
+ font-weight: 600 !important;
102
+ }
103
+
104
+ ::selection {
105
+ background: #E50914 !important;
106
+ color: #FFFFFF !important;
107
+ }
108
+
109
+ ::-moz-selection {
110
+ background: #E50914 !important;
111
+ color: #FFFFFF !important;
112
+ }
113
+
114
+ .emby-button.show-focus:focus,
115
+ .paper-icon-button-light.show-focus:focus {
116
+ background-color: #E50914 !important;
117
+ color: #FFFFFF !important;
118
+ }
119
+
120
+ .emby-checkbox:checked + span + .checkboxOutline {
121
+ background-color: #E50914 !important;
122
+ border-color: #E50914 !important;
123
+ }
124
+
125
+ .itemProgressBarForeground {
126
+ background-color: #E50914 !important;
127
+ }
128
+
129
+ .cardOverlayContainer > .cardOverlayFab-primary,
130
+ .listItemImageButton {
131
+ background-color: #E50914 !important;
132
+ color: #FFFFFF !important;
133
+ }
134
+
135
+ ::-webkit-scrollbar {
136
+ width: 8px;
137
+ height: 8px;
138
+ }
139
+
140
+ ::-webkit-scrollbar-track {
141
+ background: #000000;
142
+ }
143
+
144
+ ::-webkit-scrollbar-thumb {
145
+ background: #2a2a2a;
146
+ border-radius: 4px;
147
+ }
148
+
149
+ ::-webkit-scrollbar-thumb:hover {
150
+ background: #E50914;
151
+ }
152
+ ]]></CustomCss>
153
+ <SplashscreenEnabled>true</SplashscreenEnabled>
154
+ </BrandingOptions>
apex/theme/pitch-black.css ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ==========================================================================
2
+ Apex / Jellyfin — Pitch Black OLED Dark Theme
3
+ Clean Original Layout with 100% Pure Pitch Black Surfaces & Netflix Red
4
+ ========================================================================== */
5
+
6
+ :root,
7
+ :root[data-theme] {
8
+ /* Design Tokens */
9
+ --accent: #E50914 !important;
10
+ --accent-hover: #b80710 !important;
11
+ --accent-glow: rgba(229, 9, 20, 0.4) !important;
12
+ --accentColor: #E50914 !important;
13
+ --accentHoverColor: #b80710 !important;
14
+ --uiAccentColor: #E50914 !important;
15
+ --activeColor: #E50914 !important;
16
+ --btnSubmitColor: #E50914 !important;
17
+ --btnMiniPlayColor: #E50914 !important;
18
+ --highlightOutlineColor: #E50914 !important;
19
+ --cardResumeProgressColor: #E50914 !important;
20
+ --osdSeekBarPlayedColor: #E50914 !important;
21
+ --osdSeekBarThumbColor: #E50914 !important;
22
+
23
+ /* Pure OLED Pitch Black Tokens */
24
+ --darkerGradientPoint: #000000 !important;
25
+ --darkerGradientPointAlpha: rgba(0, 0, 0, 0.95) !important;
26
+ --lighterGradientPoint: #050505 !important;
27
+ --lighterGradientPointAlpha: rgba(5, 5, 5, 0.95) !important;
28
+ --headerColor: rgba(0, 0, 0, 0.92) !important;
29
+ --drawerColor: rgba(0, 0, 0, 0.98) !important;
30
+ --borderColor: #222222 !important;
31
+ --darkerBorderColor: #141414 !important;
32
+ --selectorBackgroundColor: #111111 !important;
33
+ --tableBodyColor: #050505 !important;
34
+ --textareaBackgroundColor: #111111 !important;
35
+
36
+ /* Jellyfin 12 MUI Tokens */
37
+ --jf-palette-background-default: #000000 !important;
38
+ --jf-palette-background-paper: #0c0c0c !important;
39
+ --jf-palette-AppBar-defaultBg: rgba(0, 0, 0, 0.92) !important;
40
+ --jf-palette-primary-main: #E50914 !important;
41
+ --jf-palette-secondary-main: #E50914 !important;
42
+ --jf-palette-action-hover: rgba(229, 9, 20, 0.12) !important;
43
+ --jf-palette-action-selected: rgba(229, 9, 20, 0.22) !important;
44
+ --jf-palette-action-focus: rgba(229, 9, 20, 0.22) !important;
45
+ }
46
+
47
+ /* --------------------------------------------------------------------------
48
+ 1. Pure OLED Pitch Black Backgrounds (#000000) Everywhere
49
+ -------------------------------------------------------------------------- */
50
+ html,
51
+ body,
52
+ .backgroundContainer,
53
+ .backdropContainer,
54
+ .mainDrawer,
55
+ .skinHeader,
56
+ .page,
57
+ .view,
58
+ .dialog,
59
+ .actionSheet,
60
+ .cardBox,
61
+ .visualCardBox,
62
+ .paperList,
63
+ .mainDrawer-scrollContainer,
64
+ .dashboardSection,
65
+ .emby-tabs-slider,
66
+ div#itemDetailPage {
67
+ background-color: #000000 !important;
68
+ color: #FFFFFF;
69
+ }
70
+
71
+ .backgroundContainer.withBackdrop {
72
+ background-color: rgba(0, 0, 0, 0.88) !important;
73
+ }
74
+
75
+ .backdropContainer {
76
+ background-color: #000000 !important;
77
+ }
78
+
79
+ /* --------------------------------------------------------------------------
80
+ 2. Pure Pitch Black Login Page
81
+ -------------------------------------------------------------------------- */
82
+ #loginPage {
83
+ background-color: #000000 !important;
84
+ }
85
+
86
+ #loginPage .readOnlyContent {
87
+ background-color: #0a0a0a !important;
88
+ border: 1px solid #222222 !important;
89
+ border-radius: 8px !important;
90
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.9) !important;
91
+ }
92
+
93
+ #loginPage .emby-input {
94
+ background: #161616 !important;
95
+ border: 1px solid #2a2a2a !important;
96
+ color: #FFFFFF !important;
97
+ }
98
+
99
+ #loginPage .emby-input:focus {
100
+ border-color: #E50914 !important;
101
+ }
102
+
103
+ #loginPage .raised.button-submit,
104
+ #loginPage .MuiButton-containedPrimary {
105
+ background-color: #E50914 !important;
106
+ color: #FFFFFF !important;
107
+ font-weight: 600 !important;
108
+ }
109
+
110
+ /* --------------------------------------------------------------------------
111
+ 3. Netflix Red Interactive Elements & Focus
112
+ -------------------------------------------------------------------------- */
113
+ ::selection {
114
+ background: #E50914 !important;
115
+ color: #FFFFFF !important;
116
+ }
117
+
118
+ ::-moz-selection {
119
+ background: #E50914 !important;
120
+ color: #FFFFFF !important;
121
+ }
122
+
123
+ .emby-button.show-focus:focus,
124
+ .paper-icon-button-light.show-focus:focus {
125
+ background-color: #E50914 !important;
126
+ color: #FFFFFF !important;
127
+ }
128
+
129
+ .emby-checkbox:checked + span + .checkboxOutline {
130
+ background-color: #E50914 !important;
131
+ border-color: #E50914 !important;
132
+ }
133
+
134
+ .itemProgressBarForeground {
135
+ background-color: #E50914 !important;
136
+ }
137
+
138
+ .cardOverlayContainer > .cardOverlayFab-primary,
139
+ .listItemImageButton {
140
+ background-color: #E50914 !important;
141
+ color: #FFFFFF !important;
142
+ }
143
+
144
+ /* --------------------------------------------------------------------------
145
+ 4. Custom Dark Scrollbars
146
+ -------------------------------------------------------------------------- */
147
+ ::-webkit-scrollbar {
148
+ width: 8px;
149
+ height: 8px;
150
+ }
151
+
152
+ ::-webkit-scrollbar-track {
153
+ background: #000000;
154
+ }
155
+
156
+ ::-webkit-scrollbar-thumb {
157
+ background: #2a2a2a;
158
+ border-radius: 4px;
159
+ }
160
+
161
+ ::-webkit-scrollbar-thumb:hover {
162
+ background: #E50914;
163
+ }