diff --git a/Jellyfin.Plugin.Seasonals/Web/autumn.css b/Jellyfin.Plugin.Seasonals/Web/autumn.css
index ae67665..9fc46a7 100644
--- a/Jellyfin.Plugin.Seasonals/Web/autumn.css
+++ b/Jellyfin.Plugin.Seasonals/Web/autumn.css
@@ -1,164 +1,139 @@
-.autumn-container {
- display: block;
- position: fixed;
- overflow: hidden;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 10;
- contain: layout paint;
-}
-
-.leaf {
- 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;
- -webkit-user-select: none;
- cursor: default;
- -webkit-animation-name: leaf-fall, leaf-shake;
- -webkit-animation-duration: 7s, 4s;
- -webkit-animation-timing-function: linear, ease-in-out;
- -webkit-animation-iteration-count: infinite, infinite;
- -webkit-user-select: none;
- animation-name: leaf-fall, leaf-shake;
- animation-duration: 7s, 4s;
- animation-timing-function: linear, ease-in-out;
- animation-iteration-count: infinite, infinite;
-}
-
-/* Class to disable rotation */
-.no-rotation {
- --rotate-start: 0deg !important;
- --rotate-end: 0deg !important;
-}
-
-@-webkit-keyframes leaf-fall {
- 0% {
- translate: 0 -10vh;
- }
-
- 100% {
- translate: 0 100vh;
- }
-}
-
-@keyframes leaf-fall {
- 0% {
- translate: 0 -10vh;
- }
-
- 100% {
- translate: 0 100vh;
- }
-}
-
-@-webkit-keyframes leaf-shake {
- 0%, 100% {
- transform: translateX(0) rotate(var(--rotate-start, -20deg));
- }
- 50% {
- transform: translateX(80px) rotate(var(--rotate-end, 20deg));
- }
-}
-
-@keyframes leaf-shake {
- 0%, 100% {
- transform: translateX(0) rotate(var(--rotate-start, -20deg));
- }
- 50% {
- transform: translateX(80px) rotate(var(--rotate-end, 20deg));
- }
-}
-
-.leaf:nth-of-type(0) {
- left: 0%;
- animation-delay: 0s, 0s;
- --rotate-start: -25deg;
- --rotate-end: 22deg;
-}
-
-.leaf:nth-of-type(1) {
- left: 10%;
- animation-delay: 1s, 0.5s;
- --rotate-start: -32deg;
- --rotate-end: 35deg;
-}
-
-.leaf:nth-of-type(2) {
- left: 20%;
- animation-delay: 6s, 1s;
- --rotate-start: -28deg;
- --rotate-end: 28deg;
-}
-
-.leaf:nth-of-type(3) {
- left: 30%;
- animation-delay: 4s, 1.5s;
- --rotate-start: -38deg;
- --rotate-end: 32deg;
-}
-
-.leaf:nth-of-type(4) {
- left: 40%;
- animation-delay: 2s, 0.8s;
- --rotate-start: -22deg;
- --rotate-end: 38deg;
-}
-
-.leaf:nth-of-type(5) {
- left: 50%;
- animation-delay: 8s, 2s;
- --rotate-start: -35deg;
- --rotate-end: 25deg;
-}
-
-.leaf:nth-of-type(6) {
- left: 60%;
- animation-delay: 6s, 1.2s;
- --rotate-start: -40deg;
- --rotate-end: 40deg;
-}
-
-.leaf:nth-of-type(7) {
- left: 70%;
- animation-delay: 2.5s, 0.3s;
- --rotate-start: -30deg;
- --rotate-end: 30deg;
-}
-
-.leaf:nth-of-type(8) {
- left: 80%;
- animation-delay: 1s, 1.8s;
- --rotate-start: -26deg;
- --rotate-end: 36deg;
-}
-
-.leaf:nth-of-type(9) {
- left: 90%;
- animation-delay: 3s, 0.7s;
- --rotate-start: -34deg;
- --rotate-end: 24deg;
-}
-
-.leaf:nth-of-type(10) {
- left: 25%;
- animation-delay: 2s, 2.3s;
- --rotate-start: -29deg;
- --rotate-end: 33deg;
-}
-
-.leaf:nth-of-type(11) {
- left: 65%;
- animation-delay: 4s, 1.4s;
- --rotate-start: -37deg;
- --rotate-end: 27deg;
+.autumn-container {
+ display: block;
+ position: fixed;
+ overflow: hidden;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 10;
+ contain: layout paint;
+}
+
+.leaf {
+ 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: leaf-fall, leaf-shake;
+ animation-duration: 7s, 4s;
+ animation-timing-function: linear, ease-in-out;
+ animation-iteration-count: infinite, infinite;
+}
+
+/* Class to disable rotation */
+.no-rotation {
+ --rotate-start: 0deg !important;
+ --rotate-end: 0deg !important;
+}
+
+
+@keyframes leaf-fall {
+ 0% {
+ translate: 0 -10vh;
+ }
+
+ 100% {
+ translate: 0 100vh;
+ }
+}
+
+
+@keyframes leaf-shake {
+ 0%, 100% {
+ transform: translateX(0) rotate(var(--rotate-start, -20deg));
+ }
+ 50% {
+ transform: translateX(80px) rotate(var(--rotate-end, 20deg));
+ }
+}
+
+.leaf:nth-of-type(0) {
+ left: 0%;
+ animation-delay: 0s, 0s;
+ --rotate-start: -25deg;
+ --rotate-end: 22deg;
+}
+
+.leaf:nth-of-type(1) {
+ left: 10%;
+ animation-delay: 1s, 0.5s;
+ --rotate-start: -32deg;
+ --rotate-end: 35deg;
+}
+
+.leaf:nth-of-type(2) {
+ left: 20%;
+ animation-delay: 6s, 1s;
+ --rotate-start: -28deg;
+ --rotate-end: 28deg;
+}
+
+.leaf:nth-of-type(3) {
+ left: 30%;
+ animation-delay: 4s, 1.5s;
+ --rotate-start: -38deg;
+ --rotate-end: 32deg;
+}
+
+.leaf:nth-of-type(4) {
+ left: 40%;
+ animation-delay: 2s, 0.8s;
+ --rotate-start: -22deg;
+ --rotate-end: 38deg;
+}
+
+.leaf:nth-of-type(5) {
+ left: 50%;
+ animation-delay: 8s, 2s;
+ --rotate-start: -35deg;
+ --rotate-end: 25deg;
+}
+
+.leaf:nth-of-type(6) {
+ left: 60%;
+ animation-delay: 6s, 1.2s;
+ --rotate-start: -40deg;
+ --rotate-end: 40deg;
+}
+
+.leaf:nth-of-type(7) {
+ left: 70%;
+ animation-delay: 2.5s, 0.3s;
+ --rotate-start: -30deg;
+ --rotate-end: 30deg;
+}
+
+.leaf:nth-of-type(8) {
+ left: 80%;
+ animation-delay: 1s, 1.8s;
+ --rotate-start: -26deg;
+ --rotate-end: 36deg;
+}
+
+.leaf:nth-of-type(9) {
+ left: 90%;
+ animation-delay: 3s, 0.7s;
+ --rotate-start: -34deg;
+ --rotate-end: 24deg;
+}
+
+.leaf:nth-of-type(10) {
+ left: 25%;
+ animation-delay: 2s, 2.3s;
+ --rotate-start: -29deg;
+ --rotate-end: 33deg;
+}
+
+.leaf:nth-of-type(11) {
+ left: 65%;
+ animation-delay: 4s, 1.4s;
+ --rotate-start: -37deg;
+ --rotate-end: 27deg;
}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Seasonals/Web/birthday.css b/Jellyfin.Plugin.Seasonals/Web/birthday.css
index 564a05c..3c2a9e7 100644
--- a/Jellyfin.Plugin.Seasonals/Web/birthday.css
+++ b/Jellyfin.Plugin.Seasonals/Web/birthday.css
@@ -8,9 +8,11 @@
z-index: 9999;
overflow: hidden;
contain: strict;
+ contain: layout paint;
}
.birthday-symbol {
+ will-change: opacity;
position: fixed;
top: 0;
left: 0;
@@ -29,7 +31,7 @@
}
.birthday-inner {
- pointer-events: auto; /* Allow hover over the actual item */
+ pointer-events: auto;
cursor: crosshair;
display: inline-block;
}
@@ -138,7 +140,7 @@
opacity: 1;
}
100% {
- transform: translateY(calc(var(--burst-y) + 150px)); /* Gravity pull downwards */
+ transform: translateY(calc(var(--burst-y) + 150px));
opacity: 0;
}
}
diff --git a/Jellyfin.Plugin.Seasonals/Web/birthday.js b/Jellyfin.Plugin.Seasonals/Web/birthday.js
index 1713740..e8af398 100644
--- a/Jellyfin.Plugin.Seasonals/Web/birthday.js
+++ b/Jellyfin.Plugin.Seasonals/Web/birthday.js
@@ -228,7 +228,19 @@ function createBirthday() {
// Ensure the burst container is appended to the main document body or the birthday container
createBalloonPopConfetti(document.body, cx, cy, balloonColors[randomItem]);
}
- }, { once: true });
+ });
+
+ // Reset the balloon when it reappears at the bottom of the screen
+ symbol.addEventListener('animationiteration', function(e) {
+ // Ignore bubbling events from the inner sway animation
+ if (e.animationName === 'birthday-rise' || e.target === symbol) {
+ if (innerDiv.classList.contains('popped')) {
+ innerDiv.classList.remove('popped');
+ innerDiv.style.animation = '';
+ innerDiv.style.pointerEvents = 'auto';
+ }
+ }
+ });
}
const startRot = (Math.random() * 20) - 10; // -10 to +10 spread
diff --git a/Jellyfin.Plugin.Seasonals/Web/christmas.css b/Jellyfin.Plugin.Seasonals/Web/christmas.css
index 33f283c..81ef3bf 100644
--- a/Jellyfin.Plugin.Seasonals/Web/christmas.css
+++ b/Jellyfin.Plugin.Seasonals/Web/christmas.css
@@ -1,138 +1,112 @@
-.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;
- -webkit-user-select: none;
- -webkit-animation-name: christmas-fall, christmas-shake;
- -webkit-animation-duration: 10s, 3s;
- -webkit-animation-timing-function: linear, ease-in-out;
- -webkit-animation-iteration-count: infinite, infinite;
- animation-name: christmas-fall, christmas-shake;
- animation-duration: 10s, 3s;
- animation-timing-function: linear, ease-in-out;
- animation-iteration-count: infinite, infinite;
-}
-
-@-webkit-keyframes christmas-fall {
- 0% {
- translate: 0 -10vh;
- }
-
- 100% {
- translate: 0 110vh;
- }
-}
-
-@-webkit-keyframes christmas-shake {
-
- 0%,
- 100% {
- transform: translateX(0);
- }
-
- 50% {
- transform: translateX(80px);
- }
-}
-
-@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);
+ }
+}
+
+.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;
}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Seasonals/Web/earthday.css b/Jellyfin.Plugin.Seasonals/Web/earthday.css
index 4a7d917..1453b56 100644
--- a/Jellyfin.Plugin.Seasonals/Web/earthday.css
+++ b/Jellyfin.Plugin.Seasonals/Web/earthday.css
@@ -7,9 +7,11 @@
pointer-events: none;
z-index: 1000;
overflow: hidden;
+ contain: layout paint;
}
.earthday-meadow {
+ will-change: transform;
position: absolute;
bottom: 0;
left: 0;
@@ -25,6 +27,7 @@
}
.earthday-sway {
+ will-change: transform;
transform-origin: bottom center;
animation: sway-grass 4s ease-in-out infinite alternate;
}
diff --git a/Jellyfin.Plugin.Seasonals/Web/easter.css b/Jellyfin.Plugin.Seasonals/Web/easter.css
index a0a606f..3a87ac1 100644
--- a/Jellyfin.Plugin.Seasonals/Web/easter.css
+++ b/Jellyfin.Plugin.Seasonals/Web/easter.css
@@ -9,6 +9,7 @@
z-index: 10000;
contain: strict;
overflow: hidden;
+ contain: layout paint;
}
.easter-grass-container {
@@ -38,6 +39,7 @@
/* sway */
.easter-sway {
+ will-change: transform;
transform-origin: bottom center;
animation: easter-wind-sway 6s ease-in-out infinite alternate;
}
diff --git a/Jellyfin.Plugin.Seasonals/Web/eid.css b/Jellyfin.Plugin.Seasonals/Web/eid.css
index 43cc54d..ca7fa4f 100644
--- a/Jellyfin.Plugin.Seasonals/Web/eid.css
+++ b/Jellyfin.Plugin.Seasonals/Web/eid.css
@@ -9,6 +9,7 @@
z-index: 10;
contain: strict;
overflow: hidden;
+ contain: layout paint;
}
.eid-symbol {
@@ -18,12 +19,14 @@
}
.eid-symbol.floating-star {
+ will-change: opacity;
opacity: 0;
animation: eid-twinkle 4s ease-in-out infinite;
mix-blend-mode: screen;
}
.lantern-rope {
+ will-change: transform;
position: absolute;
top: 0;
width: 2px;
diff --git a/Jellyfin.Plugin.Seasonals/Web/filmnoir.css b/Jellyfin.Plugin.Seasonals/Web/filmnoir.css
index 28d9dc1..fbd9f69 100644
--- a/Jellyfin.Plugin.Seasonals/Web/filmnoir.css
+++ b/Jellyfin.Plugin.Seasonals/Web/filmnoir.css
@@ -22,8 +22,9 @@
/* Film grain */
.filmnoir-grain {
+ will-change: transform, opacity;
position: absolute;
- top: -50%;
+ top: 0;
left: -50%;
width: 200%;
height: 200%;
@@ -32,6 +33,7 @@
pointer-events: none;
mix-blend-mode: overlay;
opacity: 0.3;
+ translate: 0 -50vh;
}
/* Vignette */
@@ -48,6 +50,7 @@
/* Occasional flicker and scratch */
.filmnoir-scratches {
+ will-change: opacity;
position: absolute;
top: 0;
left: 0;
diff --git a/Jellyfin.Plugin.Seasonals/Web/fireworks.css b/Jellyfin.Plugin.Seasonals/Web/fireworks.css
index 1dc299b..af6d1da 100644
--- a/Jellyfin.Plugin.Seasonals/Web/fireworks.css
+++ b/Jellyfin.Plugin.Seasonals/Web/fireworks.css
@@ -11,9 +11,10 @@
}
.rocket-trail {
+ will-change: transform;
position: absolute;
left: var(--trailX);
- top: var(--trailStartY);
+ top: 0;
width: 4px;
/* activate the following for rocket trail */
@@ -28,6 +29,7 @@
box-shadow: 0 0 8px 2px white;*/
animation: rocket-trail-animation 1s linear forwards;
+ translate: 0 var(--trailStartY);
}
@keyframes rocket-trail-animation {
@@ -56,6 +58,7 @@
}
.firework {
+ will-change: transform;
position: absolute;
width: 5px;
height: 5px;
diff --git a/Jellyfin.Plugin.Seasonals/Web/frost.css b/Jellyfin.Plugin.Seasonals/Web/frost.css
index eeb846c..e3cd503 100644
--- a/Jellyfin.Plugin.Seasonals/Web/frost.css
+++ b/Jellyfin.Plugin.Seasonals/Web/frost.css
@@ -9,16 +9,17 @@
z-index: 10;
overflow: hidden;
contain: strict;
+ contain: layout paint;
}
.frost-layer {
+ will-change: transform;
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);
@@ -27,14 +28,13 @@
animation: frost-creep 4s ease-out forwards;
}
-/* Subtle repeating star/crystal pattern */
.frost-crystals {
+ will-change: transform;
position: absolute;
- top: -5%;
+ top: 0;
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,'),
@@ -43,12 +43,9 @@
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;
+ translate: 0 -5vh;
}
@keyframes frost-creep {
diff --git a/Jellyfin.Plugin.Seasonals/Web/halloween.css b/Jellyfin.Plugin.Seasonals/Web/halloween.css
index 2cbce01..c88ed9e 100644
--- a/Jellyfin.Plugin.Seasonals/Web/halloween.css
+++ b/Jellyfin.Plugin.Seasonals/Web/halloween.css
@@ -12,20 +12,12 @@
}
.halloween {
+ will-change: transform;
position: fixed;
bottom: -10%;
z-index: 15;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
user-select: none;
- -webkit-user-select: none;
cursor: default;
- -webkit-animation-name: halloween-fall, halloween-shake;
- -webkit-animation-duration: 10s, 3s;
- -webkit-animation-timing-function: linear, ease-in-out;
- -webkit-animation-iteration-count: infinite, infinite;
- -webkit-animation-play-state: running, running;
animation-name: halloween-fall, halloween-shake;
animation-duration: 10s, 3s;
animation-timing-function: linear, ease-in-out;
@@ -33,29 +25,7 @@
animation-play-state: running, running
}
-@-webkit-keyframes halloween-fall {
- 0% {
- bottom: -10%;
- }
- 100% {
- bottom: 110%;
- }
-}
-
-@-webkit-keyframes halloween-shake {
-
- 0%,
- 100% {
- -webkit-transform: translateX(0);
- transform: translateX(0)
- }
-
- 50% {
- -webkit-transform: translateX(80px);
- transform: translateX(80px)
- }
-}
@keyframes halloween-fall {
0% {
@@ -81,73 +51,61 @@
.halloween:nth-of-type(0) {
left: 1%;
- -webkit-animation-delay: 0s, 0s;
animation-delay: 0s, 0s;
}
.halloween:nth-of-type(1) {
left: 10%;
- -webkit-animation-delay: -1s, -1s;
animation-delay: -1s, -1s;
}
.halloween:nth-of-type(2) {
left: 20%;
- -webkit-animation-delay: -2s, -2s;
animation-delay: -2s, -2s;
}
.halloween:nth-of-type(3) {
left: 30%;
- -webkit-animation-delay: -3s, -3s;
animation-delay: -3s, -3s;
}
.halloween:nth-of-type(4) {
left: 40%;
- -webkit-animation-delay: -4s, -4s;
animation-delay: -4s, -4s;
}
.halloween:nth-of-type(5) {
left: 50%;
- -webkit-animation-delay: -5s, -5s;
animation-delay: -5s, -5s;
}
.halloween:nth-of-type(6) {
left: 60%;
- -webkit-animation-delay: -6s, -6s;
animation-delay: -6s, -6s;
}
.halloween:nth-of-type(7) {
left: 70%;
- -webkit-animation-delay: -7s, -7s;
animation-delay: -7s, -7s;
}
.halloween:nth-of-type(8) {
left: 80%;
- -webkit-animation-delay: -8s, -8s;
animation-delay: -8s, -8s;
}
.halloween:nth-of-type(9) {
left: 90%;
- -webkit-animation-delay: -9s, -9s;
animation-delay: -9s, -9s;
}
.halloween:nth-of-type(10) {
left: 25%;
- -webkit-animation-delay: -10s, -10s;
animation-delay: -10s, -10s;
}
.halloween:nth-of-type(11) {
left: 65%;
- -webkit-animation-delay: -11s, -11s;
animation-delay: -11s, -11s;
}
@@ -162,7 +120,6 @@
z-index: 1000;
overflow: hidden;
mask-image: linear-gradient(to top, black, transparent);
- -webkit-mask-image: linear-gradient(to top, black, transparent);
}
.halloween-fog-blob {
position: absolute;
@@ -174,10 +131,12 @@
filter: blur(15px);
}
.halloween-fog-blob:nth-child(1) {
+ will-change: transform;
left: -20vw;
animation: fog-float1 25s ease-in-out infinite alternate;
}
.halloween-fog-blob:nth-child(2) {
+ will-change: transform;
left: -50vw;
background: radial-gradient(ellipse at center, rgba(100, 110, 120, 0.3) 0%, transparent 65%);
animation: fog-float2 35s ease-in-out infinite alternate;
@@ -196,7 +155,7 @@
/* --- Spiders --- */
.halloween-spider-wrapper {
position: absolute;
- top: -50px;
+ top: 0;
display: flex;
flex-direction: column;
align-items: center;
@@ -204,8 +163,10 @@
transform-origin: top;
will-change: transform;
pointer-events: auto;
- padding: 20px; /* Increase hit area safely */
+ padding: 20px; /* Increase hit area */
+ translate: 0 -50px;
}
+
.halloween-thread {
width: 30px; /* Wider hit area for mouse interaction */
height: 100vh;
@@ -223,12 +184,12 @@
background: linear-gradient(to bottom, rgba(200, 200, 200, 0.1), rgba(200, 200, 200, 0.6));
}
.halloween-spider {
+ will-change: transform;
animation: spider-swing 3s ease-in-out infinite alternate;
transform-origin: top center;
}
/* MARK: SPIDER SWAY CONFIGURATION */
-/* Adjust degrees in 'rotate(...)' to change how far spider and thread swing in wind. */
@keyframes wind-sway {
0% { transform: rotate(0deg); }
25% { transform: rotate(2deg); }
diff --git a/Jellyfin.Plugin.Seasonals/Web/hearts.css b/Jellyfin.Plugin.Seasonals/Web/hearts.css
index 5f61fda..e4fce52 100644
--- a/Jellyfin.Plugin.Seasonals/Web/hearts.css
+++ b/Jellyfin.Plugin.Seasonals/Web/hearts.css
@@ -12,48 +12,21 @@
}
.heart {
+ will-change: transform;
position: fixed;
bottom: -10%;
z-index: 15;
- -webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- -webkit-user-select: none;
cursor: default;
- -webkit-animation-name: heart-fall, heart-shake;
- -webkit-animation-duration: 14s, 5s;
- -webkit-animation-timing-function: linear, ease-in-out;
- -webkit-animation-iteration-count: infinite, infinite;
animation-name: heart-fall, heart-shake;
animation-duration: 14s, 5s;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite;
}
-@-webkit-keyframes heart-fall {
- 0% {
- bottom: -10%;
- }
- 100% {
- bottom: 110%;
- }
-}
-
-@-webkit-keyframes heart-shake {
-
- 0%,
- 100% {
- -webkit-transform: translateX(0);
- transform: translateX(0)
- }
-
- 50% {
- -webkit-transform: translateX(80px);
- transform: translateX(80px)
- }
-}
@keyframes heart-fall {
0% {
@@ -79,72 +52,60 @@
.heart:nth-of-type(0) {
left: 1%;
- -webkit-animation-delay: 0s, 0s;
animation-delay: 0s, 0s
}
.heart:nth-of-type(1) {
left: 10%;
- -webkit-animation-delay: 1s, 1s;
animation-delay: 1s, 1s
}
.heart:nth-of-type(2) {
left: 20%;
- -webkit-animation-delay: 6s, .5s;
animation-delay: 6s, .5s
}
.heart:nth-of-type(3) {
left: 30%;
- -webkit-animation-delay: 4s, 2s;
animation-delay: 4s, 2s
}
.heart:nth-of-type(4) {
left: 40%;
- -webkit-animation-delay: 2s, 2s;
animation-delay: 2s, 2s
}
.heart:nth-of-type(5) {
left: 50%;
- -webkit-animation-delay: 8s, 3s;
animation-delay: 8s, 3s
}
.heart:nth-of-type(6) {
left: 60%;
- -webkit-animation-delay: 6s, 2s;
animation-delay: 6s, 2s
}
.heart:nth-of-type(7) {
left: 70%;
- -webkit-animation-delay: 2.5s, 1s;
animation-delay: 2.5s, 1s
}
.heart:nth-of-type(8) {
left: 80%;
- -webkit-animation-delay: 1s, 0s;
animation-delay: 1s, 0s
}
.heart:nth-of-type(9) {
left: 90%;
- -webkit-animation-delay: 3s, 1.5s;
animation-delay: 3s, 1.5s
}
.heart:nth-of-type(10) {
left: 25%;
- -webkit-animation-delay: 2s, 0s;
animation-delay: 2s, 0s
}
.heart:nth-of-type(11) {
left: 65%;
- -webkit-animation-delay: 4s, 2.5s;
animation-delay: 4s, 2.5s
}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Seasonals/Web/marioday.css b/Jellyfin.Plugin.Seasonals/Web/marioday.css
index 4254a12..3af7de2 100644
--- a/Jellyfin.Plugin.Seasonals/Web/marioday.css
+++ b/Jellyfin.Plugin.Seasonals/Web/marioday.css
@@ -29,11 +29,13 @@
}
.mario-jump {
+ will-change: transform;
animation: jump-arc 0.8s ease-in-out;
}
/* 8-bit coin styling */
.mario-coin {
+ will-change: transform;
position: absolute;
width: 32px;
height: 32px;
@@ -47,11 +49,12 @@
.mario-coin::after {
content: '';
position: absolute;
- top: 6px;
+ top: 0;
left: 10px;
width: 4px;
height: 12px;
background: #daa520;
+ translate: 0 6px;
}
@keyframes mario-run {
diff --git a/Jellyfin.Plugin.Seasonals/Web/oktoberfest.css b/Jellyfin.Plugin.Seasonals/Web/oktoberfest.css
index 3e7da8a..58a1ed1 100644
--- a/Jellyfin.Plugin.Seasonals/Web/oktoberfest.css
+++ b/Jellyfin.Plugin.Seasonals/Web/oktoberfest.css
@@ -12,13 +12,15 @@
}
.oktoberfest-symbol {
+ will-change: transform;
position: absolute;
- top: -10%;
+ top: 0;
font-size: 2.2em;
user-select: none;
animation-name: oktoberfest-fall, oktoberfest-sway;
animation-timing-function: linear, ease-in-out;
animation-iteration-count: infinite, infinite;
+ translate: 0 -10vh;
}
@keyframes oktoberfest-fall {