From d5df90a6ae3bda066748e4a964b21061010cfb17 Mon Sep 17 00:00:00 2001 From: CodeDevMLH <145071728+CodeDevMLH@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:29:49 +0100 Subject: [PATCH] Refactor animation delays in seasonal effects for improved performance and consistency [skip ci] --- Jellyfin.Plugin.Seasonals/Web/autumn.js | 2 + Jellyfin.Plugin.Seasonals/Web/christmas.css | 163 ++++++------------ Jellyfin.Plugin.Seasonals/Web/christmas.js | 62 ++----- Jellyfin.Plugin.Seasonals/Web/halloween.js | 2 + Jellyfin.Plugin.Seasonals/Web/hearts.css | 60 ------- Jellyfin.Plugin.Seasonals/Web/hearts.js | 60 ++----- Jellyfin.Plugin.Seasonals/Web/resurrection.js | 4 +- Jellyfin.Plugin.Seasonals/Web/snowflakes.js | 2 +- 8 files changed, 96 insertions(+), 259 deletions(-) diff --git a/Jellyfin.Plugin.Seasonals/Web/autumn.js b/Jellyfin.Plugin.Seasonals/Web/autumn.js index 8012951..f19c71e 100644 --- a/Jellyfin.Plugin.Seasonals/Web/autumn.js +++ b/Jellyfin.Plugin.Seasonals/Web/autumn.js @@ -91,6 +91,8 @@ function initLeaves(count) { // set random horizontal position, animation delay and size(uncomment lines to enable) const randomLeft = Math.random() * 100; // position (0% to 100%) const randomAnimationDelay = Math.random() * 12; // delay for fall (0s to 12s) + // Display directly symbols on full screen (below) or let it build up (above) + // const randomAnimationDelay = -(Math.random() * 16); // delay for fall (-16s to 0s) const randomAnimationDelay2 = -(Math.random() * 4); // delay for shake+rotate (-4s to 0s) // apply styles diff --git a/Jellyfin.Plugin.Seasonals/Web/christmas.css b/Jellyfin.Plugin.Seasonals/Web/christmas.css index 81ef3bf..f574f93 100644 --- a/Jellyfin.Plugin.Seasonals/Web/christmas.css +++ b/Jellyfin.Plugin.Seasonals/Web/christmas.css @@ -1,112 +1,53 @@ -.christmas-container { - display: block; - position: fixed; - overflow: hidden; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 10; - contain: layout paint; -} - -.christmas { - position: fixed; - z-index: 15; - top: 0; - will-change: transform; - translate: 0 -10vh; - font-size: 1em; - color: #fff; - font-family: Arial, sans-serif; - text-shadow: 0 0 5px #000; - user-select: none; - cursor: default; animation-name: christmas-fall, christmas-shake; - animation-duration: 10s, 3s; - animation-timing-function: linear, ease-in-out; - animation-iteration-count: infinite, infinite; -} - - - -@keyframes christmas-fall { - 0% { - translate: 0 -10vh; - } - - 100% { - translate: 0 110vh; - } -} - -@keyframes christmas-shake { - 0%, - 100% { - transform: translateX(0); - } - - 50% { - transform: translateX(80px); - } -} - -.christmas:nth-of-type(0) { - left: 0%; - animation-delay: 0s, 0s; -} - -.christmas:nth-of-type(1) { - left: 10%; - animation-delay: 1s, 1s; -} - -.christmas:nth-of-type(2) { - left: 20%; - animation-delay: 6s, 0.5s; -} - -.christmas:nth-of-type(3) { - left: 30%; - animation-delay: 4s, 2s; -} - -.christmas:nth-of-type(4) { - left: 40%; - animation-delay: 2s, 2s; -} - -.christmas:nth-of-type(5) { - left: 50%; - animation-delay: 8s, 3s; -} - -.christmas:nth-of-type(6) { - left: 60%; - animation-delay: 6s, 2s; -} - -.christmas:nth-of-type(7) { - left: 70%; - animation-delay: 2.5s, 1s; -} - -.christmas:nth-of-type(8) { - left: 80%; - animation-delay: 1s, 0s; -} - -.christmas:nth-of-type(9) { - left: 90%; - animation-delay: 3s, 1.5s; -} - -.christmas:nth-of-type(10) { - left: 25%; - animation-delay: 2s, 0s; -} - -.christmas:nth-of-type(11) { - left: 65%; - animation-delay: 4s, 2.5s; +.christmas-container { + display: block; + position: fixed; + overflow: hidden; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; + contain: layout paint; +} + +.christmas { + position: fixed; + z-index: 15; + top: 0; + will-change: transform; + translate: 0 -10vh; + font-size: 1em; + color: #fff; + font-family: Arial, sans-serif; + text-shadow: 0 0 5px #000; + user-select: none; + cursor: default; + animation-name: christmas-fall, christmas-shake; + animation-duration: 10s, 3s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; +} + + + +@keyframes christmas-fall { + 0% { + translate: 0 -10vh; + } + + 100% { + translate: 0 110vh; + } +} + +@keyframes christmas-shake { + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(80px); + } } \ No newline at end of file diff --git a/Jellyfin.Plugin.Seasonals/Web/christmas.js b/Jellyfin.Plugin.Seasonals/Web/christmas.js index 97df486..a14837e 100644 --- a/Jellyfin.Plugin.Seasonals/Web/christmas.js +++ b/Jellyfin.Plugin.Seasonals/Web/christmas.js @@ -1,10 +1,9 @@ const config = window.SeasonalsPluginConfig?.Christmas || {}; const christmas = config.EnableChristmas !== undefined ? config.EnableChristmas : true; // enable/disable christmas -const randomChristmas = config.EnableRandomChristmas !== undefined ? config.EnableRandomChristmas : true; // enable random christmas -const randomChristmasMobile = config.EnableRandomChristmasMobile !== undefined ? config.EnableRandomChristmasMobile : false; // enable random christmas on mobile const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations const christmasCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbol +const christmasCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbol on mobile // Array of christmas characters const christmasSymbols = ['❆', '🎁', '❄️', '🎁', '🎅', '🎊', '🎁', '🎉']; @@ -45,11 +44,16 @@ observer.observe(document.body, { attributes: true }); -function addRandomChristmas(count) { - const christmasContainer = document.querySelector('.christmas-container'); // get the christmas container - if (!christmasContainer) return; // exit if christmas container is not found +function initChristmas(count) { + let christmasContainer = document.querySelector('.christmas-container'); // get the christmas container + if (!christmasContainer) { + christmasContainer = document.createElement("div"); + christmasContainer.className = "christmas-container"; + christmasContainer.setAttribute("aria-hidden", "true"); + document.body.appendChild(christmasContainer); + } - console.log('Adding random christmas'); + console.log('Adding christmas'); for (let i = 0; i < count; i++) { // create a new christmas element @@ -61,8 +65,8 @@ function addRandomChristmas(count) { // set random horizontal position, animation delay and size(uncomment lines to enable) const randomLeft = Math.random() * 100; // position (0% to 100%) - const randomAnimationDelay = Math.random() * 12 + 8; // delay (8s to 12s) - const randomAnimationDelay2 = Math.random() * 5 + 3; // delay (0s to 5s) + const randomAnimationDelay = -(Math.random() * 16); // delay (-16s to 0s) + const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s) // apply styles christmasDiv.style.left = `${randomLeft}%`; @@ -78,46 +82,18 @@ function addRandomChristmas(count) { // add the christmas to the container christmasContainer.appendChild(christmasDiv); } - console.log('Random christmas added'); + console.log('Christmas added'); } -// initialize standard christmas -function initChristmas() { - const christmasContainer = document.querySelector('.christmas-container') || document.createElement("div"); - - if (!document.querySelector('.christmas-container')) { - christmasContainer.className = "christmas-container"; - christmasContainer.setAttribute("aria-hidden", "true"); - document.body.appendChild(christmasContainer); - } - - // create the 12 standard christmas - for (let i = 0; i < 12; i++) { - const christmasDiv = document.createElement('div'); - christmasDiv.className = 'christmas'; - christmasDiv.textContent = christmasSymbols[Math.floor(Math.random() * christmasSymbols.length)]; - - // set random animation duration - if (enableDiffrentDuration) { - const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) - const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) - christmasDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; - } - - christmasContainer.appendChild(christmasDiv); - } -} - -// initialize christmas and add random christmas symbols +// initialize christmas function initializeChristmas() { if (!christmas) return; // exit if christmas is disabled - initChristmas(); - toggleChristmas(); - const screenWidth = window.innerWidth; // get the screen width to detect mobile devices - if (randomChristmas && (screenWidth > 768 || randomChristmasMobile)) { // add random christmas only on larger screens, unless enabled for mobile devices - addRandomChristmas(christmasCount); - } + const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; + const count = !isMobile ? christmasCount : christmasCountMobile; + + initChristmas(count); + toggleChristmas(); } initializeChristmas(); \ No newline at end of file diff --git a/Jellyfin.Plugin.Seasonals/Web/halloween.js b/Jellyfin.Plugin.Seasonals/Web/halloween.js index 136cd3b..05c4f30 100644 --- a/Jellyfin.Plugin.Seasonals/Web/halloween.js +++ b/Jellyfin.Plugin.Seasonals/Web/halloween.js @@ -72,6 +72,8 @@ function initHalloween(count) { const randomLeft = Math.random() * 100; // position (0% to 100%) const randomAnimationDelay = Math.random() * 10; // delay (0s to 10s) + // Display directly symbols on full screen (below) or let it build up (above) + // const randomAnimationDelay = -(Math.random() * 10); // delay (-10s to 0s) const randomAnimationDelay2 = -(Math.random() * 3); // delay (-3s to 0s) // apply styles diff --git a/Jellyfin.Plugin.Seasonals/Web/hearts.css b/Jellyfin.Plugin.Seasonals/Web/hearts.css index e4fce52..323bb96 100644 --- a/Jellyfin.Plugin.Seasonals/Web/hearts.css +++ b/Jellyfin.Plugin.Seasonals/Web/hearts.css @@ -49,63 +49,3 @@ transform: translateX(80px) } } - -.heart:nth-of-type(0) { - left: 1%; - animation-delay: 0s, 0s -} - -.heart:nth-of-type(1) { - left: 10%; - animation-delay: 1s, 1s -} - -.heart:nth-of-type(2) { - left: 20%; - animation-delay: 6s, .5s -} - -.heart:nth-of-type(3) { - left: 30%; - animation-delay: 4s, 2s -} - -.heart:nth-of-type(4) { - left: 40%; - animation-delay: 2s, 2s -} - -.heart:nth-of-type(5) { - left: 50%; - animation-delay: 8s, 3s -} - -.heart:nth-of-type(6) { - left: 60%; - animation-delay: 6s, 2s -} - -.heart:nth-of-type(7) { - left: 70%; - animation-delay: 2.5s, 1s -} - -.heart:nth-of-type(8) { - left: 80%; - animation-delay: 1s, 0s -} - -.heart:nth-of-type(9) { - left: 90%; - animation-delay: 3s, 1.5s -} - -.heart:nth-of-type(10) { - left: 25%; - animation-delay: 2s, 0s -} - -.heart:nth-of-type(11) { - left: 65%; - animation-delay: 4s, 2.5s -} \ No newline at end of file diff --git a/Jellyfin.Plugin.Seasonals/Web/hearts.js b/Jellyfin.Plugin.Seasonals/Web/hearts.js index 28484e9..a7c80dc 100644 --- a/Jellyfin.Plugin.Seasonals/Web/hearts.js +++ b/Jellyfin.Plugin.Seasonals/Web/hearts.js @@ -1,10 +1,9 @@ const config = window.SeasonalsPluginConfig?.Hearts || {}; const hearts = config.EnableHearts !== undefined ? config.EnableHearts : true; // enable/disable hearts -const randomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; // enable random symbols -const randomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; // enable random symbols on mobile const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different durations const heartsCount = config.SymbolCount !== undefined ? config.SymbolCount : 25; // count of symbol +const heartsCountMobile = config.SymbolCountMobile !== undefined ? config.SymbolCountMobile : 10; // count of symbol on mobile // Array of hearts characters const heartSymbols = ['❤️', '💕', '💞', '💓', '💗', '💖']; @@ -46,11 +45,16 @@ observer.observe(document.body, { }); -function addRandomSymbols(count) { - const heartsContainer = document.querySelector('.hearts-container'); // get the hearts container - if (!heartsContainer) return; // exit if hearts container is not found +function initHearts(count) { + let heartsContainer = document.querySelector('.hearts-container'); // get the hearts container + if (!heartsContainer) { + heartsContainer = document.createElement("div"); + heartsContainer.className = "hearts-container"; + heartsContainer.setAttribute("aria-hidden", "true"); + document.body.appendChild(heartsContainer); + } - console.log('Adding random heart symbols'); + console.log('Adding heart symbols'); for (let i = 0; i < count; i++) { // create a new hearts elements @@ -63,8 +67,8 @@ function addRandomSymbols(count) { // set random horizontal position, animation delay and size(uncomment lines to enable) const randomLeft = Math.random() * 100; // position (0% to 100%) - const randomAnimationDelay = Math.random() * 14; // delay (0s to 14s) - const randomAnimationDelay2 = Math.random() * 5; // delay (0s to 5s) + const randomAnimationDelay = -(Math.random() * 16); // delay (-16s to 0s) + const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s) // apply styles heartsDiv.style.left = `${randomLeft}%`; @@ -80,46 +84,18 @@ function addRandomSymbols(count) { // add the hearts to the container heartsContainer.appendChild(heartsDiv); } - console.log('Random hearts symbols added'); + console.log('Heart symbols added'); } -// create hearts objects -function createHearts() { - const container = document.querySelector('.hearts-container') || document.createElement("div"); - - if (!document.querySelector('.hearts-container')) { - container.className = "hearts-container"; - container.setAttribute("aria-hidden", "true"); - document.body.appendChild(container); - } - - for (let i = 0; i < 12; i++) { - const heartsDiv = document.createElement("div"); - heartsDiv.className = "heart"; - heartsDiv.textContent = heartSymbols[i % heartSymbols.length]; - - // set random animation duration - if (enableDiffrentDuration) { - const randomAnimationDuration = Math.random() * 16 + 12; // delay (12s to 16s) - const randomAnimationDuration2 = Math.random() * 7 + 3; // delay (3s to 7s) - heartsDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; - } - - container.appendChild(heartsDiv); - } -} - - // initialize hearts function initializeHearts() { if (!hearts) return; // exit if hearts is disabled - createHearts(); - toggleHearts(); - const screenWidth = window.innerWidth; // get the screen width to detect mobile devices - if (randomSymbols && (screenWidth > 768 || randomSymbolsMobile)) { // add random heartss only on larger screens, unless enabled for mobile devices - addRandomSymbols(heartsCount); - } + const isMobile = window.matchMedia("only screen and (max-width: 768px)").matches; + const count = !isMobile ? heartsCount : heartsCountMobile; + + initHearts(count); + toggleHearts(); } initializeHearts(); \ No newline at end of file diff --git a/Jellyfin.Plugin.Seasonals/Web/resurrection.js b/Jellyfin.Plugin.Seasonals/Web/resurrection.js index bff4c61..5c8cfde 100644 --- a/Jellyfin.Plugin.Seasonals/Web/resurrection.js +++ b/Jellyfin.Plugin.Seasonals/Web/resurrection.js @@ -82,7 +82,7 @@ function addSymbols(count) { 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; + const delay = -(Math.random() * 12); container.appendChild(createSymbol(imageSrc, left, delay)); } } @@ -99,7 +99,7 @@ function initResurrection(count) { // 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; + const delay = -(Math.random() * 8); container.appendChild(createSymbol(imageSrc, left, delay)); }); diff --git a/Jellyfin.Plugin.Seasonals/Web/snowflakes.js b/Jellyfin.Plugin.Seasonals/Web/snowflakes.js index baba9a7..e123dea 100644 --- a/Jellyfin.Plugin.Seasonals/Web/snowflakes.js +++ b/Jellyfin.Plugin.Seasonals/Web/snowflakes.js @@ -70,7 +70,7 @@ function initSnowflakes(count) { // set random horizontal position, animation delay and size(uncomment lines to enable) const randomLeft = Math.random() * 100; // position (0% to 100%) - const randomAnimationDelay = Math.random() * 8; // delay (0s to 8s) + const randomAnimationDelay = -(Math.random() * 14); // delay (-14s to 0s) const randomAnimationDelay2 = -(Math.random() * 5); // delay (-5s to 0s) // apply styles