Compare commits
3 Commits
9b8a563e43
...
c66ccf970e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c66ccf970e | ||
|
|
861f431e50 | ||
|
|
be4313d776 |
90
Jellyfin.Plugin.Seasonals/Web/birthday.css
Normal file
90
Jellyfin.Plugin.Seasonals/Web/birthday.css
Normal file
@@ -0,0 +1,90 @@
|
||||
.birthday-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.birthday-cake {
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
font-size: 8rem;
|
||||
z-index: 50;
|
||||
filter: drop-shadow(0 0 10px rgba(255,255,255,0.4));
|
||||
}
|
||||
|
||||
.birthday-cake img {
|
||||
height: 15vh;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.birthday-symbol {
|
||||
position: absolute;
|
||||
bottom: -10vh; /* balloons rise from bottom */
|
||||
animation: birthday-rise linear infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.95;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.birthday-symbol img {
|
||||
width: 6vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.birthday-confetti {
|
||||
position: absolute;
|
||||
top: -5vh;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
opacity: 0.9;
|
||||
animation: birthday-confetti-fall linear infinite;
|
||||
z-index: 30;
|
||||
/* Mix of circles and squares by using CSS variables or random in JS. For simplicity, we make all slightly rounded rectangles */
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@keyframes birthday-rise {
|
||||
0% {
|
||||
transform: translateY(10vh) rotate(var(--start-rot, 0deg));
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-110vh) rotate(calc(var(--start-rot, 0deg) * -1));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes birthday-confetti-fall {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg) rotateZ(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg) rotateZ(180deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
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();
|
||||
71
Jellyfin.Plugin.Seasonals/Web/olympia.css
Normal file
71
Jellyfin.Plugin.Seasonals/Web/olympia.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.olympia-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.olympia-symbol {
|
||||
position: absolute;
|
||||
top: -10vh;
|
||||
animation: olympia-fall linear infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.95;
|
||||
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.olympia-symbol img {
|
||||
width: 6vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.olympia-confetti {
|
||||
position: absolute;
|
||||
top: -5vh;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
opacity: 0.85;
|
||||
animation: olympia-confetti-fall linear infinite;
|
||||
border-radius: 4px; /* slightly rounder confetti */
|
||||
}
|
||||
|
||||
@keyframes olympia-fall {
|
||||
0% {
|
||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
85% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes olympia-confetti-fall {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
135
Jellyfin.Plugin.Seasonals/Web/olympia.js
Normal file
135
Jellyfin.Plugin.Seasonals/Web/olympia.js
Normal file
@@ -0,0 +1,135 @@
|
||||
const config = window.SeasonalsPluginConfig?.Olympia || {};
|
||||
|
||||
const olympia = config.EnableOlympia !== undefined ? config.EnableOlympia : 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 toggleOlympia() {
|
||||
const container = document.querySelector('.olympia-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('Olympia hidden');
|
||||
msgPrinted = true;
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'block';
|
||||
if (msgPrinted) {
|
||||
console.log('Olympia visible');
|
||||
msgPrinted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(toggleOlympia);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
|
||||
function createOlympia() {
|
||||
const container = document.querySelector('.olympia-container') || document.createElement('div');
|
||||
|
||||
if (!document.querySelector('.olympia-container')) {
|
||||
container.className = 'olympia-container';
|
||||
container.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const activeItems = ['gold', 'silver', 'bronze', 'torch'];
|
||||
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `olympia-symbol olympia-${randomItem}`;
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/olympia_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getOlympiaEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 8;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 5 + 6; // 6 to 11 seconds
|
||||
}
|
||||
|
||||
const startRot = Math.random() * 360;
|
||||
symbol.style.setProperty('--start-rot', `${startRot}deg`);
|
||||
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
|
||||
|
||||
symbol.style.left = `${leftPos}vw`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
|
||||
// Olympic Ring Colors
|
||||
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
|
||||
const confettiCount = isMobile ? 30 : 60;
|
||||
|
||||
for (let i = 0; i < confettiCount; i++) {
|
||||
let confetti = document.createElement('div');
|
||||
confetti.className = 'olympia-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 + 5;
|
||||
|
||||
confetti.style.left = `${leftPos}vw`;
|
||||
confetti.style.animationDuration = `${duration}s`;
|
||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(confetti);
|
||||
}
|
||||
}
|
||||
|
||||
function getOlympiaEmojiFallback(type) {
|
||||
if (type === 'gold') return '🥇';
|
||||
if (type === 'silver') return '🥈';
|
||||
if (type === 'bronze') return '🥉';
|
||||
if (type === 'torch') return '🔥';
|
||||
return '';
|
||||
}
|
||||
|
||||
function initializeOlympia() {
|
||||
if (!olympia) return;
|
||||
createOlympia();
|
||||
toggleOlympia();
|
||||
}
|
||||
|
||||
initializeOlympia();
|
||||
@@ -108,6 +108,76 @@ const ThemeConfigs = {
|
||||
js: '../Seasonals/Resources/earthday.js',
|
||||
containerClass: 'earthday-container'
|
||||
},
|
||||
frost: {
|
||||
css: '../Seasonals/Resources/frost.css',
|
||||
js: '../Seasonals/Resources/frost.js',
|
||||
containerClass: 'frost-container'
|
||||
},
|
||||
filmnoir: {
|
||||
css: '../Seasonals/Resources/filmnoir.css',
|
||||
js: '../Seasonals/Resources/filmnoir.js',
|
||||
containerClass: 'filmnoir-container'
|
||||
},
|
||||
oscar: {
|
||||
css: '../Seasonals/Resources/oscar.css',
|
||||
js: '../Seasonals/Resources/oscar.js',
|
||||
containerClass: 'oscar-container'
|
||||
},
|
||||
marioday: {
|
||||
css: '../Seasonals/Resources/marioday.css',
|
||||
js: '../Seasonals/Resources/marioday.js',
|
||||
containerClass: 'marioday-container'
|
||||
},
|
||||
starwars: {
|
||||
css: '../Seasonals/Resources/starwars.css',
|
||||
js: '../Seasonals/Resources/starwars.js',
|
||||
containerClass: 'starwars-container'
|
||||
},
|
||||
oktoberfest: {
|
||||
css: '../Seasonals/Resources/oktoberfest.css',
|
||||
js: '../Seasonals/Resources/oktoberfest.js',
|
||||
containerClass: 'oktoberfest-container'
|
||||
},
|
||||
friday13: {
|
||||
css: '../Seasonals/Resources/friday13.css',
|
||||
js: '../Seasonals/Resources/friday13.js',
|
||||
containerClass: 'friday13-container'
|
||||
},
|
||||
eid: {
|
||||
css: '../Seasonals/Resources/eid.css',
|
||||
js: '../Seasonals/Resources/eid.js',
|
||||
containerClass: 'eid-container'
|
||||
},
|
||||
legacyhalloween: {
|
||||
css: '../Seasonals/Resources/legacyhalloween.css',
|
||||
js: '../Seasonals/Resources/legacyhalloween.js',
|
||||
containerClass: 'legacyhalloween-container'
|
||||
},
|
||||
sports: {
|
||||
css: '../Seasonals/Resources/sports.css',
|
||||
js: '../Seasonals/Resources/sports.js',
|
||||
containerClass: 'sports-container'
|
||||
},
|
||||
olympia: {
|
||||
css: '../Seasonals/Resources/olympia.css',
|
||||
js: '../Seasonals/Resources/olympia.js',
|
||||
containerClass: 'olympia-container'
|
||||
},
|
||||
space: {
|
||||
css: '../Seasonals/Resources/space.css',
|
||||
js: '../Seasonals/Resources/space.js',
|
||||
containerClass: 'space-container'
|
||||
},
|
||||
underwater: {
|
||||
css: '../Seasonals/Resources/underwater.css',
|
||||
js: '../Seasonals/Resources/underwater.js',
|
||||
containerClass: 'underwater-container'
|
||||
},
|
||||
birthday: {
|
||||
css: '../Seasonals/Resources/birthday.css',
|
||||
js: '../Seasonals/Resources/birthday.js',
|
||||
containerClass: 'birthday-container'
|
||||
},
|
||||
none: {
|
||||
containerClass: 'none'
|
||||
},
|
||||
|
||||
58
Jellyfin.Plugin.Seasonals/Web/space.css
Normal file
58
Jellyfin.Plugin.Seasonals/Web/space.css
Normal file
@@ -0,0 +1,58 @@
|
||||
.space-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.space-symbol {
|
||||
position: absolute;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.85;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.space-symbol img {
|
||||
width: 6vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
/* Add a slow spin to images */
|
||||
animation: space-slow-spin var(--rot-dur, 20s) linear infinite;
|
||||
}
|
||||
|
||||
/* Specific elements scaling */
|
||||
.space-planet1, .space-planet2 { font-size: 4rem; }
|
||||
.space-planet1 img, .space-planet2 img { width: 8vh; max-width: 80px; }
|
||||
.space-star { font-size: 2rem; opacity: 0.6; }
|
||||
.space-star img { width: 3vh; max-width: 30px; }
|
||||
|
||||
@keyframes space-drift-right {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(-1);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(120vw) scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes space-drift-left {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-120vw);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes space-slow-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
124
Jellyfin.Plugin.Seasonals/Web/space.js
Normal file
124
Jellyfin.Plugin.Seasonals/Web/space.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const config = window.SeasonalsPluginConfig?.Space || {};
|
||||
|
||||
const space = config.EnableSpace !== undefined ? config.EnableSpace : 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 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 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;
|
||||
|
||||
const activeItems = ['planet1', 'planet2', 'star', 'astronaut', 'rocket'];
|
||||
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `space-symbol space-${randomItem}`;
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/space_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getSpaceEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const topPos = Math.random() * 90; // 0 to 90vh
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 15;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 15 + 15; // 15 to 30 seconds for slow drift
|
||||
}
|
||||
|
||||
// Randomly pick direction: left-to-right OR right-to-left
|
||||
const goRight = Math.random() > 0.5;
|
||||
if (goRight) {
|
||||
symbol.style.animationName = 'space-drift-right';
|
||||
symbol.style.left = '-10vw';
|
||||
symbol.style.transform = 'scaleX(-1)'; // flip some items horizontally if moving right
|
||||
} else {
|
||||
symbol.style.animationName = 'space-drift-left';
|
||||
symbol.style.right = '-10vw';
|
||||
}
|
||||
|
||||
symbol.style.top = `${topPos}vh`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
// Add a slow rotation
|
||||
symbol.style.setProperty('--rot-dur', `${durationSeconds}s`);
|
||||
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
function getSpaceEmojiFallback(type) {
|
||||
if (type === 'planet1') return '🪐';
|
||||
if (type === 'planet2') return '🌍';
|
||||
if (type === 'star') return '⭐';
|
||||
if (type === 'astronaut') return '👨🚀';
|
||||
if (type === 'rocket') return '🚀';
|
||||
return '✨';
|
||||
}
|
||||
|
||||
function initializeSpace() {
|
||||
if (!space) return;
|
||||
createSpace();
|
||||
toggleSpace();
|
||||
}
|
||||
|
||||
initializeSpace();
|
||||
81
Jellyfin.Plugin.Seasonals/Web/sports.css
Normal file
81
Jellyfin.Plugin.Seasonals/Web/sports.css
Normal file
@@ -0,0 +1,81 @@
|
||||
.sports-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.sports-symbol {
|
||||
position: absolute;
|
||||
top: -10vh;
|
||||
animation: sports-fall linear infinite;
|
||||
font-size: 3rem; /* Fallback emoji size */
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.sports-symbol img {
|
||||
width: 6vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.sports-confetti {
|
||||
position: absolute;
|
||||
top: -5vh;
|
||||
width: 10px;
|
||||
height: 15px;
|
||||
opacity: 0.8;
|
||||
animation: sports-confetti-fall linear infinite;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sports-turf {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 12vh;
|
||||
background: linear-gradient(180deg, transparent 0%, rgba(34, 139, 34, 0.4) 30%, rgba(0, 100, 0, 0.8) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes sports-fall {
|
||||
0% {
|
||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
85% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sports-confetti-fall {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
146
Jellyfin.Plugin.Seasonals/Web/sports.js
Normal file
146
Jellyfin.Plugin.Seasonals/Web/sports.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const config = window.SeasonalsPluginConfig?.Sports || {};
|
||||
|
||||
const sports = config.EnableSports !== undefined ? config.EnableSports : 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 toggleSports() {
|
||||
const container = document.querySelector('.sports-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('Sports hidden');
|
||||
msgPrinted = true;
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'block';
|
||||
if (msgPrinted) {
|
||||
console.log('Sports visible');
|
||||
msgPrinted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(toggleSports);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
|
||||
function createSports() {
|
||||
const container = document.querySelector('.sports-container') || document.createElement('div');
|
||||
|
||||
if (!document.querySelector('.sports-container')) {
|
||||
container.className = 'sports-container';
|
||||
container.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Create a turf/grass overlay at the bottom
|
||||
const turf = document.createElement('div');
|
||||
turf.className = 'sports-turf';
|
||||
container.appendChild(turf);
|
||||
|
||||
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;
|
||||
|
||||
// Standard sports items to spawn
|
||||
const activeItems = ['soccer', 'football', 'yellow_card', 'red_card', 'trophy'];
|
||||
|
||||
// Create falling sports items
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
// Randomly pick an item
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `sports-symbol sports-${randomItem}`;
|
||||
|
||||
// Try load image
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/sports_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none'; // hide broken image icon
|
||||
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 8;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds
|
||||
}
|
||||
|
||||
// Add a random slight rotation difference
|
||||
const startRot = Math.random() * 360;
|
||||
symbol.style.setProperty('--start-rot', `${startRot}deg`);
|
||||
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
|
||||
|
||||
symbol.style.left = `${leftPos}vw`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
|
||||
// Add Germany Colored confetti (Black, Red, Gold)
|
||||
const confettiColors = ['#000000', '#FF0000', '#FFCC00'];
|
||||
const confettiCount = isMobile ? 30 : 60;
|
||||
|
||||
for (let i = 0; i < confettiCount; i++) {
|
||||
let confetti = document.createElement('div');
|
||||
confetti.className = 'sports-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; // 4 to 7 seconds
|
||||
|
||||
confetti.style.left = `${leftPos}vw`;
|
||||
confetti.style.animationDuration = `${duration}s`;
|
||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
container.appendChild(confetti);
|
||||
}
|
||||
}
|
||||
|
||||
function getEmojiFallback(type) {
|
||||
if (type === 'soccer') return '⚽';
|
||||
if (type === 'football') return '🏈';
|
||||
if (type === 'yellow_card') return '🟨';
|
||||
if (type === 'red_card') return '🟥';
|
||||
if (type === 'trophy') return '🏆';
|
||||
return '';
|
||||
}
|
||||
|
||||
function initializeSports() {
|
||||
if (!sports) return;
|
||||
createSports();
|
||||
toggleSports();
|
||||
}
|
||||
|
||||
initializeSports();
|
||||
@@ -154,6 +154,7 @@
|
||||
border-radius: 8px;
|
||||
aspect-ratio: 2/3;
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -175,7 +176,7 @@
|
||||
background: rgba(0, 164, 220, 0.15);
|
||||
border-top: 1px solid rgba(0, 164, 220, 0.3);
|
||||
padding: 0.5em 1.5em;
|
||||
z-index: 199;
|
||||
z-index: -1;
|
||||
font-size: 0.8em;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
@@ -190,7 +191,7 @@
|
||||
|
||||
<body>
|
||||
<!-- Mock Header -->
|
||||
<div class="skinHeader">
|
||||
<div class="skinHeader skinHeader-withBackground">
|
||||
<div class="skinHeader-content">
|
||||
<span>Jellyfin</span>
|
||||
</div>
|
||||
@@ -239,6 +240,7 @@
|
||||
<option value="snowstorm">Snowstorm</option>
|
||||
<option value="fireworks">Fireworks</option>
|
||||
<option value="halloween">Halloween</option>
|
||||
<option value="legacyhalloween">Legacy Halloween</option>
|
||||
<option value="hearts">Hearts</option>
|
||||
<option value="christmas">Christmas</option>
|
||||
<option value="santa">Santa</option>
|
||||
@@ -255,6 +257,19 @@
|
||||
<option value="pride">Pride</option>
|
||||
<option value="rain">Rain</option>
|
||||
<option value="storm">Storm (Epilepsy Warning!)</option>
|
||||
<option value="frost">Frost / Ice</option>
|
||||
<option value="filmnoir">Film-Noir</option>
|
||||
<option value="oscar">Oscar Awards</option>
|
||||
<option value="marioday">Mario Day</option>
|
||||
<option value="starwars">Star Wars Day</option>
|
||||
<option value="oktoberfest">Oktoberfest</option>
|
||||
<option value="friday13">Friday the 13th</option>
|
||||
<option value="eid">Eid al-Fitr</option>
|
||||
<option value="sports">Sports / Football</option>
|
||||
<option value="olympia">Olympia / Games</option>
|
||||
<option value="space">Space / Sci-Fi</option>
|
||||
<option value="underwater">Underwater</option>
|
||||
<option value="birthday">Birthday</option>
|
||||
<option value="custom">⚙ Custom (Local Files)</option>
|
||||
</select>
|
||||
|
||||
@@ -331,6 +346,7 @@
|
||||
snowstorm: { css: 'snowstorm.css', js: 'snowstorm.js', container: 'snowstorm-container' },
|
||||
fireworks: { css: 'fireworks.css', js: 'fireworks.js', container: 'fireworks' },
|
||||
halloween: { css: 'halloween.css', js: 'halloween.js', container: 'halloween-container' },
|
||||
legacyhalloween: { css: 'legacyhalloween.css', js: 'legacyhalloween.js', container: 'legacyhalloween-container' },
|
||||
hearts: { css: 'hearts.css', js: 'hearts.js', container: 'hearts-container' },
|
||||
christmas: { css: 'christmas.css', js: 'christmas.js', container: 'christmas-container' },
|
||||
santa: { css: 'santa.css', js: 'santa.js', container: 'santa-container' },
|
||||
@@ -347,6 +363,19 @@
|
||||
pride: { css: 'pride.css', js: 'pride.js', container: 'pride-container' },
|
||||
rain: { css: 'rain.css', js: 'rain.js', container: 'rain-container' },
|
||||
storm: { css: 'storm.css', js: 'storm.js', container: 'storm-container' },
|
||||
frost: { css: 'frost.css', js: 'frost.js', container: 'frost-container' },
|
||||
filmnoir: { css: 'filmnoir.css', js: 'filmnoir.js', container: 'filmnoir-container' },
|
||||
oscar: { css: 'oscar.css', js: 'oscar.js', container: 'oscar-container' },
|
||||
marioday: { css: 'marioday.css', js: 'marioday.js', container: 'marioday-container' },
|
||||
starwars: { css: 'starwars.css', js: 'starwars.js', container: 'starwars-container' },
|
||||
oktoberfest: { css: 'oktoberfest.css', js: 'oktoberfest.js', container: 'oktoberfest-container' },
|
||||
friday13: { css: 'friday13.css', js: 'friday13.js', container: 'friday13-container' },
|
||||
eid: { css: 'eid.css', js: 'eid.js', container: 'eid-container' },
|
||||
sports: { css: 'sports.css', js: 'sports.js', container: 'sports-container' },
|
||||
olympia: { css: 'olympia.css', js: 'olympia.js', container: 'olympia-container' },
|
||||
space: { css: 'space.css', js: 'space.js', container: 'space-container' },
|
||||
underwater: { css: 'underwater.css', js: 'underwater.js', container: 'underwater-container' },
|
||||
birthday: { css: 'birthday.css', js: 'birthday.js', container: 'birthday-container' }
|
||||
};
|
||||
|
||||
const select = document.getElementById('theme-select');
|
||||
@@ -370,13 +399,16 @@
|
||||
// Remove any theme-created containers on body
|
||||
const knownContainers = [
|
||||
'.snowfall-container', '.snowflakes', '.snowstorm-container',
|
||||
'.fireworks', '.halloween-container', '.hearts-container',
|
||||
'.christmas-container', '.santa-container', '.autumn-container',
|
||||
'.fireworks', '.halloween-container', '.legacyhalloween-container', '.hearts-container',
|
||||
'.christmas-container', '.santa-container', '.autumn-container',
|
||||
'.easter-container', '.resurrection-container', '.spring-container',
|
||||
'.summer-container', '.carnival-container', '.cherryblossom-container',
|
||||
'.earthday-container', '.eurovision-container', '.piday-container',
|
||||
'.pride-container', '.rain-container', '.storm-container'
|
||||
'.pride-container', '.rain-container', '.storm-container',
|
||||
'.frost-container', '.filmnoir-container', '.oscar-container',
|
||||
'.marioday-container', '.starwars-container', '.oktoberfest-container',
|
||||
'.friday13-container', '.eid-container', '.sports-container',
|
||||
'.olympia-container', '.space-container', '.underwater-container', '.birthday-container'
|
||||
];
|
||||
knownContainers.forEach(sel => {
|
||||
document.querySelectorAll(sel).forEach(el => {
|
||||
|
||||
88
Jellyfin.Plugin.Seasonals/Web/underwater.css
Normal file
88
Jellyfin.Plugin.Seasonals/Web/underwater.css
Normal file
@@ -0,0 +1,88 @@
|
||||
.underwater-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.underwater-bg {
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
background: linear-gradient(180deg, rgba(0, 105, 148, 0.1) 0%, rgba(0, 48, 143, 0.4) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.underwater-symbol {
|
||||
position: absolute;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
font-size: 3rem;
|
||||
opacity: 0.9;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.underwater-symbol img {
|
||||
width: 6vh;
|
||||
height: auto;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.underwater-seaweed {
|
||||
position: absolute;
|
||||
bottom: -1vh;
|
||||
font-size: 4rem;
|
||||
transform-origin: bottom center;
|
||||
animation: underwater-sway 4s ease-in-out infinite alternate;
|
||||
z-index: 30;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.underwater-seaweed img {
|
||||
height: 12vh;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
.underwater-bubble {
|
||||
position: absolute;
|
||||
bottom: -5vh;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1));
|
||||
box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.5);
|
||||
animation: underwater-bubble-rise linear infinite;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
@keyframes underwater-swim-right {
|
||||
0% { transform: translateX(0) translateY(0) scaleX(-1); }
|
||||
50% { transform: translateX(65vw) translateY(-5vh) scaleX(-1); }
|
||||
100% { transform: translateX(130vw) translateY(0) scaleX(-1); }
|
||||
}
|
||||
|
||||
@keyframes underwater-swim-left {
|
||||
0% { transform: translateX(0) translateY(0); }
|
||||
50% { transform: translateX(-65vw) translateY(5vh); }
|
||||
100% { transform: translateX(-130vw) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes underwater-sway {
|
||||
0% { transform: rotate(-10deg); }
|
||||
100% { transform: rotate(10deg); }
|
||||
}
|
||||
|
||||
@keyframes underwater-bubble-rise {
|
||||
0% { transform: translateY(0) translateX(0); opacity: 0; }
|
||||
10% { opacity: 0.7; }
|
||||
50% { transform: translateY(-55vh) translateX(-20px); }
|
||||
90% { opacity: 0; }
|
||||
100% { transform: translateY(-110vh) translateX(10px); opacity: 0; }
|
||||
}
|
||||
180
Jellyfin.Plugin.Seasonals/Web/underwater.js
Normal file
180
Jellyfin.Plugin.Seasonals/Web/underwater.js
Normal file
@@ -0,0 +1,180 @@
|
||||
const config = window.SeasonalsPluginConfig?.Underwater || {};
|
||||
|
||||
const underwater = config.EnableUnderwater !== undefined ? config.EnableUnderwater : true;
|
||||
const symbolCount = config.SymbolCount || 15;
|
||||
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 toggleUnderwater() {
|
||||
const container = document.querySelector('.underwater-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('Underwater hidden');
|
||||
msgPrinted = true;
|
||||
}
|
||||
} else {
|
||||
container.style.display = 'block';
|
||||
if (msgPrinted) {
|
||||
console.log('Underwater visible');
|
||||
msgPrinted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(toggleUnderwater);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
|
||||
function createUnderwater() {
|
||||
const container = document.querySelector('.underwater-container') || document.createElement('div');
|
||||
|
||||
if (!document.querySelector('.underwater-container')) {
|
||||
container.className = 'underwater-container';
|
||||
container.setAttribute("aria-hidden", "true");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Deep blue overlay
|
||||
const bg = document.createElement('div');
|
||||
bg.className = 'underwater-bg';
|
||||
container.appendChild(bg);
|
||||
|
||||
const standardCount = 8;
|
||||
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;
|
||||
|
||||
// Seaweed swaying at the bottom
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let seaweed = document.createElement('div');
|
||||
seaweed.className = 'underwater-seaweed';
|
||||
seaweed.style.left = `${10 + (i * 25)}vw`;
|
||||
seaweed.style.animationDelay = `-${Math.random() * 5}s`;
|
||||
|
||||
// Randomly flip
|
||||
if (Math.random() > 0.5) {
|
||||
seaweed.style.transform = 'scaleX(-1)';
|
||||
}
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = '../Seasonals/Resources/underwater_images/seaweed.png';
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = '🌿';
|
||||
this.parentElement.style.fontSize = '3rem';
|
||||
this.parentElement.style.bottom = '0';
|
||||
this.parentElement.style.transformOrigin = 'bottom center';
|
||||
};
|
||||
seaweed.appendChild(img);
|
||||
container.appendChild(seaweed);
|
||||
}
|
||||
|
||||
const activeItems = ['fish_orange', 'fish_blue', 'jellyfish', 'turtle'];
|
||||
|
||||
for (let i = 0; i < finalCount; i++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
symbol.className = `underwater-symbol underwater-${randomItem}`;
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/underwater_images/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none';
|
||||
this.parentElement.innerHTML = getUnderwaterEmojiFallback(randomItem);
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
|
||||
const topPos = 10 + Math.random() * 80; // 10 to 90vh
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 15;
|
||||
if (useRandomDuration) {
|
||||
durationSeconds = Math.random() * 10 + 15; // 15 to 25 seconds slow swimming
|
||||
}
|
||||
|
||||
// Randomly pick direction: left-to-right OR right-to-left
|
||||
const goRight = Math.random() > 0.5;
|
||||
if (goRight) {
|
||||
symbol.style.animationName = 'underwater-swim-right';
|
||||
symbol.style.left = '-10vw';
|
||||
} else {
|
||||
symbol.style.animationName = 'underwater-swim-left';
|
||||
symbol.style.right = '-10vw';
|
||||
symbol.style.transform = 'scaleX(-1)'; // flip fish to face left
|
||||
}
|
||||
|
||||
symbol.style.top = `${topPos}vh`;
|
||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
// Small vertical sway
|
||||
const swimSway = document.createElement('div');
|
||||
swimSway.style.animation = `underwater-sway ${Math.random() * 2 + 3}s ease-in-out infinite`;
|
||||
swimSway.appendChild(symbol.cloneNode(true));
|
||||
symbol.innerHTML = '';
|
||||
symbol.appendChild(swimSway);
|
||||
|
||||
container.appendChild(symbol);
|
||||
}
|
||||
|
||||
// Bubbles
|
||||
const bubbleCount = isMobile ? 15 : 30;
|
||||
|
||||
for (let i = 0; i < bubbleCount; i++) {
|
||||
let bubble = document.createElement('div');
|
||||
bubble.className = 'underwater-bubble';
|
||||
|
||||
const leftPos = Math.random() * 100;
|
||||
const delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 4 + 4; // 4 to 8s rising
|
||||
|
||||
bubble.style.left = `${leftPos}vw`;
|
||||
bubble.style.animationDuration = `${duration}s`;
|
||||
bubble.style.animationDelay = `${delaySeconds}s`;
|
||||
|
||||
// randomize bubble size
|
||||
const size = Math.random() * 15 + 5;
|
||||
bubble.style.width = `${size}px`;
|
||||
bubble.style.height = `${size}px`;
|
||||
|
||||
container.appendChild(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
function getUnderwaterEmojiFallback(type) {
|
||||
if (type === 'fish_orange') return '🐠';
|
||||
if (type === 'fish_blue') return '🐟';
|
||||
if (type === 'jellyfish') return '🪼';
|
||||
if (type === 'turtle') return '🐢';
|
||||
return '🫧';
|
||||
}
|
||||
|
||||
function initializeUnderwater() {
|
||||
if (!underwater) return;
|
||||
createUnderwater();
|
||||
toggleUnderwater();
|
||||
}
|
||||
|
||||
initializeUnderwater();
|
||||
Reference in New Issue
Block a user