Files
Jellyfin-Seasonals-Plugin/Jellyfin.Plugin.Seasonals/Web/space.js
CodeDevMLH 8f322fd6cf Refactor birthday, olympia, space, and sports animations; enhance symbol management and visual effects
- Updated birthday.js to improve balloon visuals and confetti effects, including new color schemes and enhanced pop animations.
- Removed legacy fallback logic in olympia.js and improved image error handling.
- Enhanced space.js with configurable counts for planets, astronauts, satellites, ISS, and rockets; improved shooting star animations and added random image swapping.
- Simplified sports.js by removing legacy emoji fallback logic and optimizing ball creation based on selected categories.
- Adjusted CSS styles across birthday, olympia, space, and sports for better visual consistency and performance.
[skip ci]
2026-02-27 01:18:08 +01:00

282 lines
11 KiB
JavaScript

const config = window.SeasonalsPluginConfig?.Space || {};
const space = config.EnableSpace !== undefined ? config.EnableSpace : true;
const planetCountConf = config.PlanetCount !== undefined ? config.PlanetCount : 6;
const astronautCountConf = config.AstronautCount !== undefined ? config.AstronautCount : 1;
const satelliteCountConf = config.SatelliteCount !== undefined ? config.SatelliteCount : 4;
const issCountConf = config.IssCount !== undefined ? config.IssCount : 1;
const rocketCountConf = config.RocketCount !== undefined ? config.RocketCount : 1;
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
const astronautImages = [
"../Seasonals/Resources/space_assets/astronaut_1.gif"
];
const planetImages = [
"../Seasonals/Resources/space_assets/planet_1.png",
"../Seasonals/Resources/space_assets/planet_2.png",
"../Seasonals/Resources/space_assets/planet_3.png",
"../Seasonals/Resources/space_assets/planet_4.png",
"../Seasonals/Resources/space_assets/planet_5.png",
"../Seasonals/Resources/space_assets/planet_6.png",
"../Seasonals/Resources/space_assets/planet_7.png",
"../Seasonals/Resources/space_assets/planet_8.png",
"../Seasonals/Resources/space_assets/planet_9.png"
];
const satelliteImages = [
"../Seasonals/Resources/space_assets/Satellite_1.gif",
"../Seasonals/Resources/space_assets/Satellite_2.gif"
];
const issImage = "../Seasonals/Resources/space_assets/iss.png";
const rocketImages = [
"../Seasonals/Resources/space_assets/rocket.gif",
"../Seasonals/Resources/space_assets/space-shuttle.png"
]
let msgPrinted = false;
function toggleSpace() {
const container = document.querySelector('.space-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('Space hidden');
msgPrinted = true;
}
} else {
container.style.display = 'block';
if (msgPrinted) {
console.log('Space visible');
msgPrinted = false;
}
}
}
const observer = new MutationObserver(toggleSpace);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
function createSpace() {
const container = document.querySelector('.space-container') || document.createElement('div');
if (!document.querySelector('.space-container')) {
container.className = 'space-container';
container.setAttribute("aria-hidden", "true");
document.body.appendChild(container);
}
const standardPlanetCount = 4;
const standardAstronautCount = 1;
const standardSatelliteCount = 2;
const standardIssCount = 1;
const standardRocketCount = 1;
let isMobile = window.matchMedia("only screen and (max-width: 768px)").matches;
let pCount = planetCountConf;
let aCount = astronautCountConf;
let sCount = satelliteCountConf;
let iCount = issCountConf;
let rCount = rocketCountConf;
if (isMobile && !enableRandomMobile) {
pCount = standardPlanetCount;
aCount = standardAstronautCount;
sCount = standardSatelliteCount;
iCount = standardIssCount;
rCount = standardRocketCount;
}
// Add Nebula Glow
const bgGlow = document.createElement('div');
bgGlow.className = 'space-bg-glow';
container.appendChild(bgGlow);
// Add CSS Starfield
const starfield = document.createElement('div');
starfield.className = 'space-starfield';
let boxShadows1 = [];
let boxShadows2 = [];
let boxShadows3 = [];
// Generate random stars for parallax starfield using CSS % / vw sizes for responsiveness
for (let i = 0; i < 150; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows1.push(`${x}vw ${y}vh #FFF`);
boxShadows1.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
for (let i = 0; i < 50; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows2.push(`${x}vw ${y}vh #FFF`);
boxShadows2.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
for (let i = 0; i < 20; i++) {
let x = (Math.random() * 100).toFixed(2);
let y = (Math.random() * 100).toFixed(2);
boxShadows3.push(`${x}vw ${y}vh #FFF`);
boxShadows3.push(`${x}vw ${(parseFloat(y) + 100).toFixed(2)}vh #FFF`);
}
const starLayer1 = document.createElement('div');
starLayer1.style.width = '1px'; starLayer1.style.height = '1px';
starLayer1.style.background = 'transparent';
starLayer1.style.boxShadow = boxShadows1.join(", ");
starLayer1.style.animation = 'space-star-drift 200s linear infinite';
starfield.appendChild(starLayer1);
const starLayer2 = document.createElement('div');
starLayer2.style.width = '2px'; starLayer2.style.height = '2px';
starLayer2.style.background = 'transparent';
starLayer2.style.boxShadow = boxShadows2.join(", ");
starLayer2.style.animation = 'space-star-drift 150s linear infinite';
starfield.appendChild(starLayer2);
const starLayer3 = document.createElement('div');
starLayer3.style.width = '3px'; starLayer3.style.height = '3px';
starLayer3.style.background = 'transparent';
starLayer3.style.boxShadow = boxShadows3.join(", ");
starLayer3.style.animation = 'space-star-drift 100s linear infinite';
starfield.appendChild(starLayer3);
container.appendChild(starfield);
// Shooting stars
const shootingStarCount = isMobile ? 1 : 2; // Less frequent
for (let i = 0; i < shootingStarCount; i++) {
const streak = document.createElement('div');
streak.className = 'space-shooting-star';
// Pick a random tail direction and fall direction to match
const isFromLeft = Math.random() > 0.5;
// Direction angle: random between 15deg-75deg (left) or 105deg-165deg (right)
// so they don't always fall in the exact same quadrant trajectory
let angle = isFromLeft
? Math.random() * 60 + 15
: Math.random() * 60 + 105;
streak.style.setProperty('--shoot-angle', `${angle}deg`);
const topStart = Math.random() * 50;
streak.style.left = isFromLeft ? '-20vw' : '120vw';
streak.style.top = `${topStart}vh`;
// Travel 200 viewport widths exactly along the rotated angle
streak.style.setProperty('--shoot-distance', '200vw');
streak.style.animationDelay = `${Math.random() * 20}s`;
// MARK: Shooting Star Speed
const flightCycleDuration = Math.random() * 10 + 15; // 15-25s
streak.style.animationDuration = `${flightCycleDuration}s`;
container.appendChild(streak);
}
const useRandomDuration = enableDifferentDuration !== false;
function createSpaceItem(imageArr, cCount, addedClass) {
for (let i = 0; i < cCount; i++) {
let symbol = document.createElement('div');
const randomImage = imageArr[Math.floor(Math.random() * imageArr.length)];
symbol.className = `space-symbol ${addedClass}`;
let img = document.createElement('img');
img.src = randomImage;
img.onerror = function() {
this.style.display = 'none';
};
symbol.appendChild(img);
const topPos = Math.random() * 90; // 0 to 90vh
// Zero gravity sizes / speeds
const depth = Math.random();
// Make background elements (depth close to 0) much smaller than foreground
const distanceScale = 0.15 + (depth * 0.85); // 0.15 to 1.0
symbol.style.zIndex = Math.floor(depth * 30) + 20;
let durationSeconds = 30; // Very slow
if (useRandomDuration) {
durationSeconds = (1 - depth) * 40 + 30 + Math.random() * 10 - 5;
}
// Randomly pick direction: left-to-right OR right-to-left
const goRight = Math.random() > 0.5;
const baseTransformScale = goRight ? 'scaleX(-1)' : 'scaleX(1)';
if (goRight) {
symbol.style.animationName = 'space-drift-right';
symbol.style.left = '-20vw';
symbol.style.right = 'auto';
} else {
symbol.style.animationName = 'space-drift-left';
symbol.style.right = '-20vw';
symbol.style.left = 'auto';
}
symbol.style.top = `${topPos}vh`;
symbol.style.animationDuration = `${durationSeconds}s`;
// Negative delay correctly scatters them initially across the screen
// so they don't all appear to spawn from the edge at the start
const delaySeconds = -(Math.random() * durationSeconds);
symbol.style.animationDelay = `${delaySeconds}s`;
// Slow rotation inside inner div
const rotationDiv = document.createElement('div');
const rotDur = Math.random() * 20 + 20; // 20-40s spin
const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal';
rotationDiv.style.animation = `space-slow-spin ${rotDur}s linear infinite ${spinReverse}`;
// Apply final static scaling and facing to inner image directly
img.style.transform = `scale(${distanceScale}) ${baseTransformScale}`;
rotationDiv.appendChild(img);
symbol.appendChild(rotationDiv);
// Swap to a random image from the pool every time it completes an orbit (disappears)
if (imageArr.length > 1) {
// The animation delay pushes the initial cycle, so we use setInterval matched to duration
setInterval(() => {
// Update only if currently out of bounds to avoid popping
const rect = symbol.getBoundingClientRect();
if (rect.right < 0 || rect.left > window.innerWidth) {
img.src = imageArr[Math.floor(Math.random() * imageArr.length)];
}
}, 2000); // Check occasionally if it's off screen
}
container.appendChild(symbol);
}
}
createSpaceItem(planetImages, pCount, 'space-planet');
createSpaceItem(astronautImages, aCount, 'space-astronaut');
createSpaceItem(satelliteImages, sCount, 'space-satellite');
createSpaceItem([issImage], iCount, 'space-iss');
createSpaceItem(rocketImages, rCount, 'space-rocket');
}
function initializeSpace() {
if (!space) return;
createSpace();
toggleSpace();
}
initializeSpace();