Enhance sports feature: add asset management for various sports, improve animation effects, and refine confetti generation logic
This commit is contained in:
@@ -13,9 +13,14 @@
|
|||||||
.sports-symbol {
|
.sports-symbol {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10vh;
|
top: -10vh;
|
||||||
animation: sports-fall linear infinite;
|
/* Default is empty, assigned in JS */
|
||||||
font-size: 3rem; /* Fallback emoji size */
|
font-size: 3rem; /* Fallback emoji size */
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sports-inner {
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sports-symbol img {
|
.sports-symbol img {
|
||||||
@@ -35,6 +40,19 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sports-confetti.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sports-confetti.triangle {
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-bottom: 10px solid var(--shape-color, #FFCC00);
|
||||||
|
}
|
||||||
|
|
||||||
.sports-turf {
|
.sports-turf {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -46,26 +64,60 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes sports-fall {
|
@keyframes sports-bounce {
|
||||||
0% {
|
0% {
|
||||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
transform: translateY(-10vh);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
animation-timing-function: ease-in;
|
||||||
}
|
}
|
||||||
10% {
|
5% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
30% {
|
||||||
|
transform: translateY(85vh);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
} /* hit ground, start bouncing up */
|
||||||
|
50% {
|
||||||
|
transform: translateY(40vh);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
} /* peak of bounce, start falling */
|
||||||
|
70% {
|
||||||
|
transform: translateY(85vh);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
} /* hit ground, bounce up */
|
||||||
85% {
|
85% {
|
||||||
|
transform: translateY(70vh);
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
} /* smaller peak */
|
||||||
|
95% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
transform: translateY(110vh);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes sports-fall {
|
||||||
|
0% { transform: translateY(-10vh); opacity: 0; }
|
||||||
|
5% { opacity: 1; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { transform: translateY(110vh); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sports-sway {
|
||||||
|
0% { transform: rotate(-15deg) translateX(-10px); }
|
||||||
|
100% { transform: rotate(15deg) translateX(10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sports-spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(var(--spin-rot, 360deg)); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes sports-confetti-fall {
|
@keyframes sports-confetti-fall {
|
||||||
0% {
|
0% {
|
||||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
transform: translateY(-5vh) rotate3d(var(--rx), var(--ry), var(--rz), 0deg);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
5% {
|
5% {
|
||||||
@@ -75,7 +127,25 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
transform: translateY(105vh) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir));
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes sports-arc-x-right {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(130vw); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sports-arc-x-left {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(-130vw); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sports-arc-y {
|
||||||
|
0% { transform: translateY(110vh) scale(0.5) rotate(-30deg); opacity: 0; animation-timing-function: ease-out; }
|
||||||
|
5% { opacity: 1; }
|
||||||
|
50% { transform: translateY(10vh) scale(1.5) rotate(0deg); animation-timing-function: ease-in; }
|
||||||
|
95% { opacity: 1; }
|
||||||
|
100% { transform: translateY(110vh) scale(0.5) rotate(30deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
const config = window.SeasonalsPluginConfig?.Sports || {};
|
const config = window.SeasonalsPluginConfig?.Sports || {};
|
||||||
|
|
||||||
const sports = config.EnableSports !== undefined ? config.EnableSports : true;
|
const sports = config.EnableSports !== undefined ? config.EnableSports : true;
|
||||||
const symbolCount = config.SymbolCount || 25;
|
const symbolCount = config.SymbolCount || 5;
|
||||||
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
|
const useRandomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true;
|
||||||
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
const enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||||
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
|
const enableDifferentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true;
|
||||||
|
|
||||||
|
// Pre-declare and manage image assets
|
||||||
|
const SPORTS_ASSETS = {
|
||||||
|
badminton: ['badminton_1', 'badminton_2'],
|
||||||
|
baseball: ['baseball_1', 'baseball_2'],
|
||||||
|
basketball: ['basketball_1', 'basketball_2'],
|
||||||
|
billiard: Array.from({length: 14}, (_, i) => `billiard_ball_${i + 1}`),
|
||||||
|
bowling: ['bowling_1', 'bowling_2'],
|
||||||
|
football: Array.from({length: 5}, (_, i) => `football_${i + 1}`),
|
||||||
|
golf: ['golf_ball_1', 'golf_ball_2'],
|
||||||
|
rugby: ['rugby_ball_1', 'rugby_ball_2'],
|
||||||
|
table_tennis: ['table_tennis_ball_1', 'table_tennis_ball_2'],
|
||||||
|
tennis: ['tennis_ball_1', 'tennis_ball_2'],
|
||||||
|
volleyball: ['volleyball_1', 'volleyball_2'],
|
||||||
|
waterball: ['waterball_1', 'waterball_2']
|
||||||
|
};
|
||||||
|
|
||||||
|
const turfColorHex = config.TurfColor || '#228b22';
|
||||||
|
|
||||||
let msgPrinted = false;
|
let msgPrinted = false;
|
||||||
|
|
||||||
function toggleSports() {
|
function toggleSports() {
|
||||||
@@ -48,9 +66,12 @@ function createSports() {
|
|||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a turf/grass overlay at the bottom
|
// Parse turf color config
|
||||||
|
// Create a turf/grass overlay at the bottom using the provided hex
|
||||||
const turf = document.createElement('div');
|
const turf = document.createElement('div');
|
||||||
turf.className = 'sports-turf';
|
turf.className = 'sports-turf';
|
||||||
|
// Using hex with transparency (e.g., 4D = 30%, CC = 80%)
|
||||||
|
turf.style.background = `linear-gradient(180deg, transparent 0%, ${turfColorHex}4D 30%, ${turfColorHex}CC 100%)`;
|
||||||
container.appendChild(turf);
|
container.appendChild(turf);
|
||||||
|
|
||||||
const standardCount = 15;
|
const standardCount = 15;
|
||||||
@@ -65,27 +86,64 @@ function createSports() {
|
|||||||
|
|
||||||
const useRandomDuration = enableDifferentDuration !== false;
|
const useRandomDuration = enableDifferentDuration !== false;
|
||||||
|
|
||||||
// Standard sports items to spawn
|
// Map standard sports balls to spawn based on category configuration
|
||||||
const activeItems = ['soccer', 'football', 'yellow_card', 'red_card', 'trophy'];
|
const rawSportsBalls = config.SportsBalls || 'football,basketball,tennis,volleyball';
|
||||||
|
const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== '');
|
||||||
|
|
||||||
// Create falling sports items
|
// Assemble activeItems from categories
|
||||||
|
let activeItems = [];
|
||||||
|
chosenCategories.forEach(category => {
|
||||||
|
if (SPORTS_ASSETS[category]) {
|
||||||
|
activeItems.push(...SPORTS_ASSETS[category]);
|
||||||
|
} else {
|
||||||
|
// Legacy fallback (in case older explicit filenames remain in config string)
|
||||||
|
activeItems.push(category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activeItems.length === 0) activeItems.push(...SPORTS_ASSETS['football']); // fallback
|
||||||
|
|
||||||
|
// Track items we still need to show at least once
|
||||||
|
let guaranteedItems = [...activeItems];
|
||||||
|
|
||||||
|
// Create falling sports balls
|
||||||
for (let i = 0; i < finalCount; i++) {
|
for (let i = 0; i < finalCount; i++) {
|
||||||
let symbol = document.createElement('div');
|
let symbol = document.createElement('div');
|
||||||
|
|
||||||
// Randomly pick an item
|
// Pick a guaranteed ball first, otherwise pick completely randomly
|
||||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
let randomItem;
|
||||||
|
if (guaranteedItems.length > 0) {
|
||||||
|
const index = Math.floor(Math.random() * guaranteedItems.length);
|
||||||
|
randomItem = guaranteedItems[index];
|
||||||
|
guaranteedItems.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||||
|
}
|
||||||
|
|
||||||
symbol.className = `sports-symbol sports-${randomItem}`;
|
symbol.className = `sports-symbol sports-${randomItem}`;
|
||||||
|
|
||||||
|
// Create inner div for spinning rotation
|
||||||
|
let innerDiv = document.createElement('div');
|
||||||
|
innerDiv.className = 'sports-inner';
|
||||||
|
|
||||||
// Try load image
|
// Try load image
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = `../Seasonals/Resources/sports_images/${randomItem}.png`;
|
img.src = `../Seasonals/Resources/sport_assets/${randomItem}.png`;
|
||||||
img.onerror = function() {
|
img.onerror = function() {
|
||||||
this.style.display = 'none'; // hide broken image icon
|
this.style.display = 'none'; // hide broken image icon
|
||||||
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
|
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
|
||||||
};
|
};
|
||||||
symbol.appendChild(img);
|
innerDiv.appendChild(img);
|
||||||
|
|
||||||
const leftPos = Math.random() * 100;
|
// Balls should bounce infinitely
|
||||||
|
symbol.style.animationName = 'sports-bounce';
|
||||||
|
symbol.style.animationIterationCount = 'infinite';
|
||||||
|
innerDiv.style.animationName = 'sports-spin';
|
||||||
|
innerDiv.style.animationIterationCount = 'infinite';
|
||||||
|
|
||||||
|
symbol.appendChild(innerDiv);
|
||||||
|
|
||||||
|
const leftPos = Math.random() * 95;
|
||||||
const delaySeconds = Math.random() * 10;
|
const delaySeconds = Math.random() * 10;
|
||||||
|
|
||||||
let durationSeconds = 8;
|
let durationSeconds = 8;
|
||||||
@@ -93,10 +151,13 @@ function createSports() {
|
|||||||
durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds
|
durationSeconds = Math.random() * 4 + 6; // 6 to 10 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a random slight rotation difference
|
// Add a random spin
|
||||||
const startRot = Math.random() * 360;
|
const spinRot = (Math.random() > 0.5 ? 360 : -360) + "deg";
|
||||||
symbol.style.setProperty('--start-rot', `${startRot}deg`);
|
innerDiv.style.setProperty('--spin-rot', spinRot);
|
||||||
symbol.style.setProperty('--end-rot', `${startRot + (Math.random() > 0.5 ? 360 : -360)}deg`);
|
|
||||||
|
// Duration for the spin should be different from fall to look natural
|
||||||
|
const spinDuration = Math.random() * 2 + 2;
|
||||||
|
innerDiv.style.animationDuration = `${spinDuration}s`;
|
||||||
|
|
||||||
symbol.style.left = `${leftPos}vw`;
|
symbol.style.left = `${leftPos}vw`;
|
||||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
symbol.style.animationDuration = `${durationSeconds}s`;
|
||||||
@@ -105,6 +166,66 @@ function createSports() {
|
|||||||
container.appendChild(symbol);
|
container.appendChild(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the periodic flying trophy arc
|
||||||
|
function launchTrophy() {
|
||||||
|
if (!document.querySelector('.sports-container')) return;
|
||||||
|
|
||||||
|
const flyFromLeft = Math.random() > 0.5;
|
||||||
|
let trophySymbol = document.createElement('div');
|
||||||
|
trophySymbol.className = "sports-symbol sports-trophy-wrapper";
|
||||||
|
|
||||||
|
let trophyInner = document.createElement('div');
|
||||||
|
trophyInner.className = "sports-inner sports-trophy-inner";
|
||||||
|
|
||||||
|
let trophyImg = document.createElement('img');
|
||||||
|
trophyImg.src = `../Seasonals/Resources/sport_assets/trophy.gif`;
|
||||||
|
// Randomly scale trophy slightly larger
|
||||||
|
trophyImg.style.transform = `scale(${Math.random() * 0.5 + 0.8})`;
|
||||||
|
trophyImg.onerror = function() {
|
||||||
|
this.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
trophyInner.appendChild(trophyImg);
|
||||||
|
trophySymbol.appendChild(trophyInner);
|
||||||
|
|
||||||
|
if (flyFromLeft) {
|
||||||
|
trophySymbol.style.animationName = "sports-arc-x-right";
|
||||||
|
trophySymbol.style.left = "-15vw";
|
||||||
|
} else {
|
||||||
|
trophySymbol.style.animationName = "sports-arc-x-left";
|
||||||
|
trophySymbol.style.left = "115vw";
|
||||||
|
}
|
||||||
|
|
||||||
|
trophyInner.style.animationName = "sports-arc-y";
|
||||||
|
|
||||||
|
// Appearance timing
|
||||||
|
const arcDuration = 6 + Math.random() * 2;
|
||||||
|
|
||||||
|
trophySymbol.style.animationDuration = `${arcDuration}s`;
|
||||||
|
trophyInner.style.animationDuration = `${arcDuration}s`;
|
||||||
|
|
||||||
|
// Prevent looping for the trophy
|
||||||
|
trophySymbol.style.animationIterationCount = "1";
|
||||||
|
trophyInner.style.animationIterationCount = "1";
|
||||||
|
trophySymbol.style.animationFillMode = "forwards";
|
||||||
|
trophyInner.style.animationFillMode = "forwards";
|
||||||
|
|
||||||
|
container.appendChild(trophySymbol);
|
||||||
|
|
||||||
|
// Remove node after animation completes
|
||||||
|
setTimeout(() => {
|
||||||
|
if (trophySymbol && trophySymbol.parentNode) {
|
||||||
|
trophySymbol.parentNode.removeChild(trophySymbol);
|
||||||
|
}
|
||||||
|
}, arcDuration * 1000 + 500);
|
||||||
|
|
||||||
|
// Schedule the next trophy
|
||||||
|
setTimeout(launchTrophy, Math.random() * 20000 + 10000); // Wait 10-30s until next trophy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch initial trophy after a short delay
|
||||||
|
setTimeout(launchTrophy, Math.random() * 5000 + 2000);
|
||||||
|
|
||||||
// Add Germany Colored confetti (Black, Red, Gold)
|
// Add Germany Colored confetti (Black, Red, Gold)
|
||||||
const confettiColors = ['#000000', '#FF0000', '#FFCC00'];
|
const confettiColors = ['#000000', '#FF0000', '#FFCC00'];
|
||||||
const confettiCount = isMobile ? 30 : 60;
|
const confettiCount = isMobile ? 30 : 60;
|
||||||
@@ -116,6 +237,23 @@ function createSports() {
|
|||||||
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||||
confetti.style.backgroundColor = color;
|
confetti.style.backgroundColor = color;
|
||||||
|
|
||||||
|
// Random shape generator for varied confetti
|
||||||
|
const shape = Math.random();
|
||||||
|
if (shape > 0.66) {
|
||||||
|
confetti.classList.add('circle');
|
||||||
|
const size = Math.random() * 5 + 5; // 5-10px
|
||||||
|
confetti.style.width = `${size}px`;
|
||||||
|
confetti.style.height = `${size}px`;
|
||||||
|
} else if (shape > 0.33) {
|
||||||
|
confetti.classList.add('rect');
|
||||||
|
const width = Math.random() * 4 + 4; // 4-8px
|
||||||
|
const height = Math.random() * 5 + 8; // 8-13px
|
||||||
|
confetti.style.width = `${width}px`;
|
||||||
|
confetti.style.height = `${height}px`;
|
||||||
|
} else {
|
||||||
|
confetti.classList.add('triangle');
|
||||||
|
}
|
||||||
|
|
||||||
const leftPos = Math.random() * 100;
|
const leftPos = Math.random() * 100;
|
||||||
const delaySeconds = Math.random() * 8;
|
const delaySeconds = Math.random() * 8;
|
||||||
const duration = Math.random() * 3 + 4; // 4 to 7 seconds
|
const duration = Math.random() * 3 + 4; // 4 to 7 seconds
|
||||||
@@ -124,16 +262,28 @@ function createSports() {
|
|||||||
confetti.style.animationDuration = `${duration}s`;
|
confetti.style.animationDuration = `${duration}s`;
|
||||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
confetti.style.animationDelay = `${delaySeconds}s`;
|
||||||
|
|
||||||
|
// Random 3D Rotation for flutter
|
||||||
|
confetti.style.setProperty('--rx', Math.random().toFixed(2));
|
||||||
|
confetti.style.setProperty('--ry', Math.random().toFixed(2));
|
||||||
|
confetti.style.setProperty('--rz', (Math.random() * 0.5).toFixed(2));
|
||||||
|
confetti.style.setProperty('--rot-dir', `${(Math.random() > 0.5 ? 1 : -1) * 360}deg`);
|
||||||
|
|
||||||
container.appendChild(confetti);
|
container.appendChild(confetti);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmojiFallback(type) {
|
function getEmojiFallback(type) {
|
||||||
if (type === 'soccer') return '⚽';
|
if (type.includes('soccer') || type.includes('football')) return '⚽';
|
||||||
if (type === 'football') return '🏈';
|
if (type.includes('baseball')) return '⚾';
|
||||||
if (type === 'yellow_card') return '🟨';
|
if (type.includes('basketball')) return '🏀';
|
||||||
if (type === 'red_card') return '🟥';
|
if (type.includes('billiard')) return '🎱';
|
||||||
if (type === 'trophy') return '🏆';
|
if (type.includes('bowling')) return '🎳';
|
||||||
|
if (type.includes('golf')) return '⛳';
|
||||||
|
if (type.includes('rugby')) return '🏈';
|
||||||
|
if (type.includes('tennis')) return '🎾';
|
||||||
|
if (type.includes('volleyball')) return '🏐';
|
||||||
|
if (type.includes('badminton')) return '🏸';
|
||||||
|
if (type.includes('waterball')) return '🤽';
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user