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 {
|
||||
position: absolute;
|
||||
top: -10vh;
|
||||
animation: sports-fall linear infinite;
|
||||
/* Default is empty, assigned in JS */
|
||||
font-size: 3rem; /* Fallback emoji size */
|
||||
opacity: 0.9;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.sports-inner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sports-symbol img {
|
||||
@@ -35,6 +40,19 @@
|
||||
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 {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -46,26 +64,60 @@
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes sports-fall {
|
||||
@keyframes sports-bounce {
|
||||
0% {
|
||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
||||
transform: translateY(-10vh);
|
||||
opacity: 0;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
10% {
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
85% {
|
||||
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% {
|
||||
transform: translateY(70vh);
|
||||
animation-timing-function: ease-in;
|
||||
} /* smaller peak */
|
||||
95% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
||||
transform: translateY(110vh);
|
||||
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 {
|
||||
0% {
|
||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
||||
transform: translateY(-5vh) rotate3d(var(--rx), var(--ry), var(--rz), 0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
@@ -75,7 +127,25 @@
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
||||
transform: translateY(105vh) rotate3d(var(--rx), var(--ry), var(--rz), var(--rot-dir));
|
||||
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 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 enableRandomMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false;
|
||||
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;
|
||||
|
||||
function toggleSports() {
|
||||
@@ -48,9 +66,12 @@ function createSports() {
|
||||
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');
|
||||
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);
|
||||
|
||||
const standardCount = 15;
|
||||
@@ -65,27 +86,64 @@ function createSports() {
|
||||
|
||||
const useRandomDuration = enableDifferentDuration !== false;
|
||||
|
||||
// Standard sports items to spawn
|
||||
const activeItems = ['soccer', 'football', 'yellow_card', 'red_card', 'trophy'];
|
||||
// Map standard sports balls to spawn based on category configuration
|
||||
const rawSportsBalls = config.SportsBalls || 'football,basketball,tennis,volleyball';
|
||||
const chosenCategories = rawSportsBalls.split(',').map(s => s.trim()).filter(s => s !== '');
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
// Create falling sports items
|
||||
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++) {
|
||||
let symbol = document.createElement('div');
|
||||
|
||||
// Randomly pick an item
|
||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||
// Pick a guaranteed ball first, otherwise pick completely randomly
|
||||
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}`;
|
||||
|
||||
// Create inner div for spinning rotation
|
||||
let innerDiv = document.createElement('div');
|
||||
innerDiv.className = 'sports-inner';
|
||||
|
||||
// Try load image
|
||||
let img = document.createElement('img');
|
||||
img.src = `../Seasonals/Resources/sports_images/${randomItem}.png`;
|
||||
img.src = `../Seasonals/Resources/sport_assets/${randomItem}.png`;
|
||||
img.onerror = function() {
|
||||
this.style.display = 'none'; // hide broken image icon
|
||||
this.parentElement.innerHTML = getEmojiFallback(randomItem); // inject emoji fallback
|
||||
};
|
||||
symbol.appendChild(img);
|
||||
innerDiv.appendChild(img);
|
||||
|
||||
// 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() * 100;
|
||||
const leftPos = Math.random() * 95;
|
||||
const delaySeconds = Math.random() * 10;
|
||||
|
||||
let durationSeconds = 8;
|
||||
@@ -93,10 +151,13 @@ function createSports() {
|
||||
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`);
|
||||
// Add a random spin
|
||||
const spinRot = (Math.random() > 0.5 ? 360 : -360) + "deg";
|
||||
innerDiv.style.setProperty('--spin-rot', spinRot);
|
||||
|
||||
// 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.animationDuration = `${durationSeconds}s`;
|
||||
@@ -104,6 +165,66 @@ function createSports() {
|
||||
|
||||
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)
|
||||
const confettiColors = ['#000000', '#FF0000', '#FFCC00'];
|
||||
@@ -116,6 +237,23 @@ function createSports() {
|
||||
const color = confettiColors[Math.floor(Math.random() * confettiColors.length)];
|
||||
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 delaySeconds = Math.random() * 8;
|
||||
const duration = Math.random() * 3 + 4; // 4 to 7 seconds
|
||||
@@ -123,17 +261,29 @@ function createSports() {
|
||||
confetti.style.left = `${leftPos}vw`;
|
||||
confetti.style.animationDuration = `${duration}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);
|
||||
}
|
||||
}
|
||||
|
||||
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 '🏆';
|
||||
if (type.includes('soccer') || type.includes('football')) return '⚽';
|
||||
if (type.includes('baseball')) return '⚾';
|
||||
if (type.includes('basketball')) return '🏀';
|
||||
if (type.includes('billiard')) 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 '';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user