Add seasonal themes for Birthday, Olympia, Space, Sports, and Underwater: implement CSS and JavaScript for visual effects and configurations
This commit is contained in:
145
Jellyfin.Plugin.Seasonals/Web/birthday.js
Normal file
145
Jellyfin.Plugin.Seasonals/Web/birthday.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const config = window.SeasonalsPluginConfig?.Birthday || {};
|
||||
|
||||
const birthday = config.EnableBirthday !== undefined ? config.EnableBirthday : true;
|
||||
const symbolCount = config.SymbolCount || 25;
|
||||
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
|
||||
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
|
||||
|
||||
let msgPrinted = false;
|
||||
|
||||
function toggleBirthday() {
|
||||
const container = document.querySelector('.birthday-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('Birthday hidden');
|
||||
msgPrinted = true;
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'block';
|
||||
if (msgPrinted) {
|
||||
console.log('Birthday visible');
|
||||
msgPrinted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(toggleBirthday);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
|
||||
function createBirthday() {
|
||||
const container = document.querySelector('.birthday-container') || document.createElement('div');
|
||||
|
||||
if (!document.querySelector('.birthday-container')) {
|
||||
container.className = 'birthday-container';
|
||||
container.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Spawn Birthday Cake at the bottom
|
||||
const cake = document.createElement('div');
|
||||
cake.className = 'birthday-cake';
|
||||
let cakeImg = document.createElement('img');
|
||||
cakeImg.src = `../Seasonals/Resources/birthday_images/cake.png`;
|
||||
cakeImg.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = '🎂';
|
||||
};
|
||||
cake.appendChild(cakeImg);
|
||||
container.appendChild(cake);
|
||||
|
||||
const standardCount = 15;
|
||||
const totalSymbols = symbolCount + standardCount;
|
||||
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
|
||||
let finalCount = totalSymbols;
|
||||
|
||||
if (isMobile) {
|
||||
finalCount = enableRandomMobile ? totalSymbols : standardCount;
|
||||
}
|
||||
|
||||
const useRandomDuration = enableDifferentDuration !== false;
|
||||
|
||||
// We'll treat balloons and gifts as rising symbols
|
||||
const activeItems = ['balloon_red', 'balloon_blue', 'balloon_yellow', 'gift'];
|
||||
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `birthday-symbol birthday-${randomItem}`;
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/birthday_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getBirthdayEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const leftPos = Math.random() * 95;
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 9;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 5 + 7; // 7 to 12 seconds
|
||||
}
|
||||
|
||||
const startRot = (Math.random() * 20) - 10; // -10 to +10 spread
|
||||
symbol.style.setProperty('--start-rot', `${startRot}deg`);
|
||||
|
||||
symbol.style.left = `${leftPos}vw`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
|
||||
// Party Confetti
|
||||
const confettiColors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000'];
|
||||
const confettiCount = isMobile ? 40 : 80;
|
||||
|
||||
for (let i = 0; i < confettiCount; i++) {
|
||||
let confetti = document.createElement('div');
|
||||
confetti.className = 'birthday-confetti';
|
||||
|
||||
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||
confetti.style.backgroundColor = color;
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 3 + 4;
|
||||
|
||||
confetti.style.left = `${leftPos}vw`;
|
||||
confetti.style.animationDuration = `${duration}s`;
|
||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(confetti);
|
||||
}
|
||||
}
|
||||
|
||||
function getBirthdayEmojiFallback(type) {
|
||||
if (type.startsWith('balloon')) return '🎈';
|
||||
if (type === 'gift') return '🎁';
|
||||
return '';
|
||||
}
|
||||
|
||||
function initializeBirthday() {
|
||||
if (!birthday) return;
|
||||
createBirthday();
|
||||
toggleBirthday();
|
||||
}
|
||||
|
||||
initializeBirthday();
|
||||
Reference in New Issue
Block a user