diff --git a/Jellyfin.Plugin.Seasonals/Web/frost.css b/Jellyfin.Plugin.Seasonals/Web/frost.css new file mode 100644 index 0000000..eeb846c --- /dev/null +++ b/Jellyfin.Plugin.Seasonals/Web/frost.css @@ -0,0 +1,74 @@ +.frost-container { + display: block; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; + overflow: hidden; + contain: strict; +} + +.frost-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + /* A glowing white-blue gradient from edges */ + background: radial-gradient(ellipse at center, transparent 60%, rgba(180, 220, 255, 0.4) 100%); + box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3); + + filter: url('#frost-filter'); + + animation: frost-creep 4s ease-out forwards; +} + +/* Subtle repeating star/crystal pattern */ +.frost-crystals { + position: absolute; + top: -5%; + left: -5%; + width: 110%; + height: 110%; + /* Use multi-layered star patterns for a random, crystalline spread */ + background-image: + url('data:image/svg+xml;utf8,'), + url('data:image/svg+xml;utf8,'), + url('data:image/svg+xml;utf8,'); + background-repeat: repeat; + background-size: 110px 110px, 60px 60px, 30px 30px; + background-position: 0 0, 15px 15px, 5px 10px; + mix-blend-mode: overlay; + + /* Mask out the center so crystals only appear strongly on the edges */ + -webkit-mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%); + mask-image: radial-gradient(ellipse at center, transparent 50%, black 100%); + + animation: frost-shimmer 6s infinite alternate ease-in-out; +} + +@keyframes frost-creep { + 0% { + opacity: 0; + box-shadow: inset 0 0 10px rgba(200, 230, 255, 0); + } + 100% { + opacity: 1; + box-shadow: inset 0 0 60px rgba(200, 230, 255, 0.5), inset 0 0 120px rgba(255, 255, 255, 0.3); + } +} + +@keyframes frost-shimmer { + 0% { + opacity: 0.4; + transform: scale(1); + } + 100% { + opacity: 0.8; + transform: scale(1.02); + } +} diff --git a/Jellyfin.Plugin.Seasonals/Web/frost.js b/Jellyfin.Plugin.Seasonals/Web/frost.js new file mode 100644 index 0000000..dd5e6f9 --- /dev/null +++ b/Jellyfin.Plugin.Seasonals/Web/frost.js @@ -0,0 +1,75 @@ +const config = window.SeasonalsPluginConfig?.Frost || {}; + +const frost = config.EnableFrost !== undefined ? config.EnableFrost : true; + +let msgPrinted = false; + +function toggleFrost() { + const container = document.querySelector('.frost-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'); + + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + container.style.display = 'none'; + if (!msgPrinted) { + console.log('Frost hidden'); + msgPrinted = true; + } + } else { + container.style.display = 'block'; + if (msgPrinted) { + console.log('Frost visible'); + msgPrinted = false; + } + } +} + +const observer = new MutationObserver(toggleFrost); +observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true +}); + +function createFrost(container) { + const frostLayer = document.createElement('div'); + frostLayer.className = 'frost-layer'; + + const frostCrystals = document.createElement('div'); + frostCrystals.className = 'frost-crystals'; + + // An SVG filter to make things look "frozen"/distorted around the edges + const svgFilter = document.createElement('div'); + svgFilter.innerHTML = ` + + + + + + + `; + + frostLayer.appendChild(frostCrystals); + container.appendChild(frostLayer); + container.appendChild(svgFilter); +} + +function initializeFrost() { + if (!frost) return; + + const container = document.querySelector('.frost-container') || document.createElement("div"); + + if (!document.querySelector('.frost-container')) { + container.className = "frost-container"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + createFrost(container); +} + +initializeFrost();