diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
index 1ec4755..3e75b62 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
@@ -49,5 +49,9 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public bool IncludeWatchedContent { get; set; } = false;
public string SortBy { get; set; } = "Random";
public string SortOrder { get; set; } = "Ascending";
+
+ public bool EnableCustomOverlay { get; set; } = false;
+ public string CustomOverlayText { get; set; } = "";
+ public string CustomOverlayImageUrl { get; set; } = "";
}
}
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
index 8604d0a..a450d08 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
@@ -212,6 +212,29 @@
+
Custom Slideshow Overlay
+
Inject a custom text or floating image over the slideshow. This can be overridden by specific Seasonal Sections.
+
+
+
+
If enabled, the text or image below will hover over the slideshow globally.
+
+
+
+
+
+
+
+
Features
' +
+ '
' +
+ '
';
div.querySelector('.btn-remove').addEventListener('click', function () {
@@ -786,7 +821,9 @@
StartMonth: parseInt(el.querySelector('.start-month').value),
EndDay: parseInt(el.querySelector('.end-day').value),
EndMonth: parseInt(el.querySelector('.end-month').value),
- MediaIds: el.querySelector('.section-ids').value
+ MediaIds: el.querySelector('.section-ids').value,
+ OverlayText: el.querySelector('.section-overlay-text').value,
+ OverlayImageUrl: el.querySelector('.section-overlay-image').value
});
});
return sections;
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
index f643472..76af5f9 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
@@ -1031,3 +1031,75 @@
-webkit-backdrop-filter: none;
}
}
+/* Floating Custom Overlay Styling */
+.custom-overlay-container {
+ position: absolute;
+ top: 8vh;
+ left: 4vw;
+ z-index: 15;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ pointer-events: none; /* Let clicks pass through to the slider */
+ animation: fadeInOverlay 1.5s ease-in-out forwards;
+}
+
+.custom-overlay-text {
+ font-family: "Archivo Narrow", sans-serif;
+ color: #fff;
+ font-size: 2.5rem;
+ font-weight: 700;
+ text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.8), -1px -1px 4px rgba(0, 0, 0, 0.5);
+ margin: 0;
+ letter-spacing: 1px;
+}
+
+.custom-overlay-image {
+ max-width: 300px;
+ max-height: 120px;
+ object-fit: contain;
+ filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.5));
+}
+
+@keyframes fadeInOverlay {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Make it smaller on mobile portrait */
+@media only screen and (max-width: 767px) and (orientation: portrait) {
+ .custom-overlay-container {
+ top: 5vh;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 90%;
+ justify-content: center;
+ text-align: center;
+ }
+
+ .custom-overlay-text {
+ font-size: 1.8rem;
+ }
+
+ .custom-overlay-image {
+ max-width: 200px;
+ max-height: 80px;
+ }
+
+ @keyframes fadeInOverlay {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -10px);
+ }
+ to {
+ opacity: 1;
+ transform: translate(-50%, 0);
+ }
+ }
+}
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
index e378448..89f246b 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
@@ -58,6 +58,9 @@ const CONFIG = {
enableKeyboardControls: true,
alwaysShowArrows: false,
hideArrowsOnMobile: true,
+ enableCustomOverlay: false,
+ customOverlayText: "",
+ customOverlayImageUrl: "",
enableCustomMediaIds: true,
enableSeasonalContent: false,
customMediaIds: "",
@@ -3777,6 +3780,88 @@ const slidesInit = async () => {
return;
}
+ const renderCustomOverlay = () => {
+ let activeOverlayText = CONFIG.customOverlayText;
+ let activeOverlayImage = CONFIG.customOverlayImageUrl;
+ let isSeasonOverride = false;
+
+ if (CONFIG.enableSeasonalContent && CONFIG.seasonalSections) {
+ try {
+ const sections = JSON.parse(CONFIG.seasonalSections || "[]");
+ const now = new Date();
+ const currentMonth = now.getMonth() + 1;
+ const currentDay = now.getDate();
+
+ for (const section of sections) {
+ const startMonth = parseInt(section.StartMonth);
+ const startDay = parseInt(section.StartDay);
+ const endMonth = parseInt(section.EndMonth);
+ const endDay = parseInt(section.EndDay);
+
+ let isActive = false;
+ if (startMonth === endMonth) {
+ if (currentMonth === startMonth && currentDay >= startDay && currentDay <= endDay) {
+ isActive = true;
+ }
+ } else if (startMonth < endMonth) {
+ if (currentMonth > startMonth && currentMonth < endMonth) {
+ isActive = true;
+ } else if (currentMonth === startMonth && currentDay >= startDay) {
+ isActive = true;
+ } else if (currentMonth === endMonth && currentDay <= endDay) {
+ isActive = true;
+ }
+ } else { // Wraps around year
+ if (currentMonth > startMonth || currentMonth < endMonth) {
+ isActive = true;
+ } else if (currentMonth === startMonth && currentDay >= startDay) {
+ isActive = true;
+ } else if (currentMonth === endMonth && currentDay <= endDay) {
+ isActive = true;
+ }
+ }
+
+ if (isActive) {
+ if (section.OverlayText || section.OverlayImageUrl) {
+ isSeasonOverride = true;
+ if (section.OverlayText) activeOverlayText = section.OverlayText;
+ if (section.OverlayImageUrl) activeOverlayImage = section.OverlayImageUrl;
+ }
+ break;
+ }
+ }
+ } catch (e) {
+ console.error("🎬 Media Bar:", "Error parsing seasonal sections for overlay:", e);
+ }
+ }
+
+ if (!CONFIG.enableCustomOverlay && !isSeasonOverride) {
+ return;
+ }
+
+ if (!activeOverlayText && !activeOverlayImage) return;
+
+ const overlayContainer = document.createElement("div");
+ overlayContainer.className = "custom-overlay-container";
+
+ if (activeOverlayImage) {
+ const img = document.createElement("img");
+ img.className = "custom-overlay-image";
+ img.src = activeOverlayImage;
+ overlayContainer.appendChild(img);
+ } else if (activeOverlayText) {
+ const p = document.createElement("p");
+ p.className = "custom-overlay-text";
+ p.textContent = activeOverlayText;
+ overlayContainer.appendChild(p);
+ }
+
+ const slidesContainer = document.getElementById("slides-container");
+ if (slidesContainer) {
+ slidesContainer.appendChild(overlayContainer);
+ }
+ };
+
if (CONFIG.enableClientSideSettings) {
MediaBarEnhancedSettingsManager.init();
const isClientSideEnabled = MediaBarEnhancedSettingsManager.getSetting('enabled', true);
@@ -3875,6 +3960,8 @@ const slidesInit = async () => {
initArrowNavigation();
+ renderCustomOverlay();
+
await SlideshowManager.loadSlideshowData();
SlideshowManager.initTouchEvents();