Add carnival animation effects with CSS and JavaScript implementation
This commit is contained in:
101
Jellyfin.Plugin.Seasonals/Web/carnival.css
Normal file
101
Jellyfin.Plugin.Seasonals/Web/carnival.css
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
.carnival-container {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
perspective: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 15;
|
||||||
|
top: -20px;
|
||||||
|
will-change: top;
|
||||||
|
animation-name: carnival-fall;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-sway-wrapper {
|
||||||
|
will-change: transform;
|
||||||
|
animation-name: carnival-sway;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-direction: alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-confetti {
|
||||||
|
width: 8px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #f0f;
|
||||||
|
will-change: transform;
|
||||||
|
animation-name: carnival-flutter;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-duration: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-confetti.circle {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-confetti.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carnival-confetti.triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-bottom: 10px solid;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: inherit;
|
||||||
|
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes carnival-fall {
|
||||||
|
0% {
|
||||||
|
top: -10%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 110%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes carnival-sway {
|
||||||
|
0% {
|
||||||
|
transform: translateX(calc(var(--sway-amount, 50px) * -1));
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(var(--sway-amount, 50px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes carnival-flutter {
|
||||||
|
0% {
|
||||||
|
transform: rotate3d(1, 1, 1, 0deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: rotate3d(1, 0.5, 0, 90deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate3d(0.5, 1, 0.5, 180deg);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: rotate3d(0, 0.5, 1, 270deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate3d(1, 1, 1, 360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
174
Jellyfin.Plugin.Seasonals/Web/carnival.js
Normal file
174
Jellyfin.Plugin.Seasonals/Web/carnival.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
const config = window.SeasonalsPluginConfig?.Carnival || {};
|
||||||
|
|
||||||
|
const carnival = config.EnableCarnival !== undefined ? config.EnableCarnival : true;
|
||||||
|
const randomCarnival = config.EnableRandomCarnival !== undefined ? config.EnableRandomCarnival : true;
|
||||||
|
const randomCarnivalMobile = config.EnableRandomCarnivalMobile !== undefined ? config.EnableRandomCarnivalMobile : false;
|
||||||
|
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
|
||||||
|
const enableSway = config.EnableCarnivalSway !== undefined ? config.EnableCarnivalSway : true;
|
||||||
|
const carnivalCount = config.ObjectCount || 80;
|
||||||
|
|
||||||
|
let msgPrinted = false;
|
||||||
|
|
||||||
|
// function to check and control the carnival animation
|
||||||
|
function toggleCarnival() {
|
||||||
|
const carnivalContainer = document.querySelector('.carnival-container');
|
||||||
|
if (!carnivalContainer) 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');
|
||||||
|
|
||||||
|
// hide carnival if video/trailer player is active or dashboard is visible
|
||||||
|
if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) {
|
||||||
|
carnivalContainer.style.display = 'none'; // hide carnival
|
||||||
|
if (!msgPrinted) {
|
||||||
|
console.log('Carnival hidden');
|
||||||
|
msgPrinted = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
carnivalContainer.style.display = 'block'; // show carnival
|
||||||
|
if (msgPrinted) {
|
||||||
|
console.log('Carnival visible');
|
||||||
|
msgPrinted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// observe changes in the DOM
|
||||||
|
const observer = new MutationObserver(toggleCarnival);
|
||||||
|
|
||||||
|
// start observation
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true, // observe adding/removing of child elements
|
||||||
|
subtree: true, // observe all levels of the DOM tree
|
||||||
|
attributes: true // observe changes to attributes (e.g. class changes)
|
||||||
|
});
|
||||||
|
|
||||||
|
const confettiColors = [
|
||||||
|
'#fce18a', '#ff726d', '#b48def', '#f4306d',
|
||||||
|
'#36c5f0', '#2ccc5d', '#e9b31d', '#9b59b6',
|
||||||
|
'#3498db', '#e74c3c', '#1abc9c', '#f1c40f'
|
||||||
|
];
|
||||||
|
|
||||||
|
function createConfettiPiece(container, isInitial = false) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.classList.add('carnival-wrapper');
|
||||||
|
|
||||||
|
let swayWrapper = wrapper;
|
||||||
|
|
||||||
|
if (enableSway) {
|
||||||
|
swayWrapper = document.createElement('div');
|
||||||
|
swayWrapper.classList.add('carnival-sway-wrapper');
|
||||||
|
wrapper.appendChild(swayWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
const confetti = document.createElement('div');
|
||||||
|
confetti.classList.add('carnival-confetti');
|
||||||
|
|
||||||
|
// Random color
|
||||||
|
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||||
|
confetti.style.backgroundColor = color;
|
||||||
|
|
||||||
|
// Random shape
|
||||||
|
const shape = Math.random();
|
||||||
|
if (shape > 0.8) {
|
||||||
|
confetti.classList.add('circle');
|
||||||
|
} else if (shape > 0.6) {
|
||||||
|
confetti.classList.add('square');
|
||||||
|
} else if (shape > 0.4) {
|
||||||
|
confetti.classList.add('triangle');
|
||||||
|
} else {
|
||||||
|
confetti.classList.add('rect');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random position
|
||||||
|
wrapper.style.left = `${Math.random() * 100}%`;
|
||||||
|
|
||||||
|
// Random dimensions
|
||||||
|
if (!confetti.classList.contains('circle') && !confetti.classList.contains('square') && !confetti.classList.contains('triangle')) {
|
||||||
|
const width = Math.random() * 5 + 4;
|
||||||
|
const height = Math.random() * 6 + 8;
|
||||||
|
confetti.style.width = `${width}px`;
|
||||||
|
confetti.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation settings
|
||||||
|
const duration = Math.random() * 5 + 5;
|
||||||
|
|
||||||
|
let delay = 0;
|
||||||
|
if (isInitial) {
|
||||||
|
delay = -Math.random() * duration;
|
||||||
|
} else {
|
||||||
|
delay = Math.random() * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.style.animationDelay = `${delay}s`;
|
||||||
|
wrapper.style.animationDuration = `${duration}s`;
|
||||||
|
|
||||||
|
if (enableSway) {
|
||||||
|
// Random sway duration
|
||||||
|
const swayDuration = Math.random() * 2 + 3; // 3-5s per cycle
|
||||||
|
swayWrapper.style.animationDuration = `${swayDuration}s`;
|
||||||
|
swayWrapper.style.animationDelay = `-${Math.random() * 5}s`;
|
||||||
|
|
||||||
|
// Random sway amplitude (using CSS variable for dynamic keyframe)
|
||||||
|
// Sway between 30px and 100px
|
||||||
|
const swayAmount = Math.random() * 70 + 30;
|
||||||
|
const direction = Math.random() > 0.5 ? 1 : -1;
|
||||||
|
swayWrapper.style.setProperty('--sway-amount', `${swayAmount * direction}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flutter speed variation
|
||||||
|
confetti.style.animationDuration = `${Math.random() * 2 + 1}s`;
|
||||||
|
|
||||||
|
if (enableSway) {
|
||||||
|
swayWrapper.appendChild(confetti);
|
||||||
|
wrapper.appendChild(swayWrapper);
|
||||||
|
} else {
|
||||||
|
wrapper.appendChild(confetti);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRandomCarnivalObjects(count) {
|
||||||
|
const carnivalContainer = document.querySelector('.carnival-container');
|
||||||
|
if (!carnivalContainer) return;
|
||||||
|
|
||||||
|
console.log('Adding random carnival confetti');
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
createConfettiPiece(carnivalContainer, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize standard carnival objects
|
||||||
|
function initCarnivalObjects() {
|
||||||
|
let container = document.querySelector('.carnival-container');
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement("div");
|
||||||
|
container.className = "carnival-container";
|
||||||
|
container.setAttribute("aria-hidden", "true");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial confetti
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
createConfettiPiece(container, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize carnival
|
||||||
|
function initializeCarnival() {
|
||||||
|
if (!carnival) return; // exit if carnival is disabled
|
||||||
|
initCarnivalObjects();
|
||||||
|
toggleCarnival();
|
||||||
|
|
||||||
|
const screenWidth = window.innerWidth;
|
||||||
|
if (randomCarnival && (screenWidth > 768 || randomCarnivalMobile)) {
|
||||||
|
addRandomCarnivalObjects(carnivalCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeCarnival();
|
||||||
Reference in New Issue
Block a user