diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection.css b/Jellyfin.Plugin.Seasonals/Web/resurrection.css new file mode 100644 index 0000000..adc365b --- /dev/null +++ b/Jellyfin.Plugin.Seasonals/Web/resurrection.css @@ -0,0 +1,59 @@ +.ressurection-container { + display: block; + position: fixed; + overflow: hidden; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; +} + +.ressurection-symbol { + position: fixed; + z-index: 15; + top: -15%; + user-select: none; + -webkit-user-select: none; + cursor: default; + animation-name: ressurection-fall, ressurection-sway; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; + will-change: transform, top; +} + +.ressurection-symbol img { + z-index: 15; + height: auto; + width: 56px; + opacity: 0.95; + filter: drop-shadow(0 0 8px rgba(255, 215, 130, 0.5)); +} + +@media (max-width: 768px) { + .ressurection-symbol img { + width: 42px; + } +} + +@keyframes ressurection-fall { + 0% { + top: -15%; + } + + 100% { + top: 105%; + } +} + +@keyframes ressurection-sway { + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(65px); + } +} diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection.js b/Jellyfin.Plugin.Seasonals/Web/resurrection.js new file mode 100644 index 0000000..374f6e9 --- /dev/null +++ b/Jellyfin.Plugin.Seasonals/Web/resurrection.js @@ -0,0 +1,113 @@ +const config = window.SeasonalsPluginConfig?.Resurrection || {}; + +const enableResurrection = config.EnableResurrection !== undefined ? config.EnableResurrection : true; +const enableRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; +const enableRandomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; +const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; +const symbolCount = config.SymbolCount || 12; + +let animationEnabled = true; +let statusLogged = false; + +const images = [ + '../Seasonals/Resources/resurrection_images/crosses.png', + '../Seasonals/Resources/resurrection_images/palm-branch.png', + '../Seasonals/Resources/resurrection_images/draped-cross.png', + '../Seasonals/Resources/resurrection_images/empty-tomb.png', + '../Seasonals/Resources/resurrection_images/he-is-risen.png', + '../Seasonals/Resources/resurrection_images/crown-of-thorns.png', + '../Seasonals/Resources/resurrection_images/risen-lord.png', + '../Seasonals/Resources/resurrection_images/dove.png' +]; + +function toggleResurrection() { + const container = document.querySelector('.resurrection-container'); + if (!container) return; + + const videoPlayer = document.querySelector('.videoPlayerContainer'); + const trailerPlayer = document.querySelector('.youtubePlayerContainer'); + const isDashboard = document.body.classList.contains('dashboardDocument'); + const hasUserMenu = document.querySelector('#app-user-menu'); + + animationEnabled = !(videoPlayer || trailerPlayer || isDashboard || hasUserMenu); + container.style.display = animationEnabled ? 'block' : 'none'; + + if (!animationEnabled && !statusLogged) { + console.log('Resurrection hidden'); + statusLogged = true; + } else if (animationEnabled && statusLogged) { + console.log('Resurrection visible'); + statusLogged = false; + } +} + +const observer = new MutationObserver(toggleResurrection); +observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true +}); + +function createSymbol(imageSrc, leftPercent, delaySeconds) { + const symbol = document.createElement('div'); + symbol.className = 'resurrection-symbol'; + + const img = document.createElement('img'); + img.src = imageSrc; + img.alt = ''; + + symbol.style.left = `${leftPercent}%`; + symbol.style.animationDelay = `${delaySeconds}s, ${Math.random() * 3}s`; + + if (enableDifferentDuration) { + const fallDuration = Math.random() * 7 + 7; + const swayDuration = Math.random() * 4 + 2; + symbol.style.animationDuration = `${fallDuration}s, ${swayDuration}s`; + } + + symbol.appendChild(img); + return symbol; +} + +function addSymbols(count) { + const container = document.querySelector('.resurrection-container'); + if (!container || !enableRandomSymbols) return; + + const isDesktop = window.innerWidth > 768; + if (!isDesktop && !enableRandomSymbolsMobile) return; + + for (let i = 0; i < count; i++) { + const imageSrc = images[Math.floor(Math.random() * images.length)]; + const left = Math.random() * 100; + const delay = Math.random() * 12; + container.appendChild(createSymbol(imageSrc, left, delay)); + } +} + +function initResurrection() { + let container = document.querySelector('.resurrection-container'); + if (!container) { + container = document.createElement('div'); + container.className = 'resurrection-container'; + container.setAttribute('aria-hidden', 'true'); + document.body.appendChild(container); + } + + // Place one of each of the 8 provided resurrection images first. + images.forEach((imageSrc, index) => { + const left = (index + 1) * (100 / (images.length + 1)); + const delay = Math.random() * 8; + container.appendChild(createSymbol(imageSrc, left, delay)); + }); + + const extraCount = Math.max(symbolCount - images.length, 0); + addSymbols(extraCount); +} + +function initializeResurrection() { + if (!enableResurrection) return; + initResurrection(); + toggleResurrection(); +} + +initializeResurrection(); diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crosses.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crosses.png new file mode 100644 index 0000000..7496f64 Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crosses.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crown-of-thorns.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crown-of-thorns.png new file mode 100644 index 0000000..5ce7765 Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/crown-of-thorns.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/dove.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/dove.png new file mode 100644 index 0000000..89726df Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/dove.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/draped-cross.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/draped-cross.png new file mode 100644 index 0000000..8e6cab2 Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/draped-cross.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/empty-tomb.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/empty-tomb.png new file mode 100644 index 0000000..f4d7b5b Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/empty-tomb.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/he-is-risen.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/he-is-risen.png new file mode 100644 index 0000000..9043f61 Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/he-is-risen.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/palm-branch.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/palm-branch.png new file mode 100644 index 0000000..211c97b Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/palm-branch.png differ diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection_images/risen-lord.png b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/risen-lord.png new file mode 100644 index 0000000..b3e92c7 Binary files /dev/null and b/Jellyfin.Plugin.Seasonals/Web/resurrection_images/risen-lord.png differ