Enhance Olympic animation: add new ring types, implement 3D effects for rings and medals, and improve confetti generation with random shapes and animations
This commit is contained in:
@@ -13,10 +13,50 @@
|
|||||||
.olympia-symbol {
|
.olympia-symbol {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10vh;
|
top: -10vh;
|
||||||
animation: olympia-fall linear infinite;
|
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-flame {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0vh;
|
||||||
|
z-index: 50;
|
||||||
|
pointer-events: none;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-ring-css {
|
||||||
|
position: relative;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.olympia-ring-css::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 5px solid #0081C8; /* Default blue ring */
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.olympia-ring-css[style*="--ring-color"]::before {
|
||||||
|
border-color: var(--ring-color);
|
||||||
|
}
|
||||||
|
.olympia-symbol {
|
||||||
|
position: absolute;
|
||||||
|
top: -10vh;
|
||||||
|
font-size: 4rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
text-shadow: 0 0 10px rgba(255,255,255,0.2);
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-inner {
|
||||||
|
display: inline-block;
|
||||||
|
animation: olympia-sway linear infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
.olympia-symbol img {
|
.olympia-symbol img {
|
||||||
@@ -26,46 +66,76 @@
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.olympia-confetti-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 15;
|
||||||
|
top: 0;
|
||||||
|
will-change: transform;
|
||||||
|
animation-name: olympia-fall;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-confetti-sway {
|
||||||
|
will-change: transform;
|
||||||
|
animation-name: olympia-confetti-sway;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-direction: alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes olympia-confetti-sway {
|
||||||
|
0% { transform: translateX(calc(var(--sway-amount, 50px) * -1)); }
|
||||||
|
100% { transform: translateX(var(--sway-amount, 50px)); }
|
||||||
|
}
|
||||||
|
|
||||||
.olympia-confetti {
|
.olympia-confetti {
|
||||||
position: absolute;
|
|
||||||
top: -5vh;
|
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
opacity: 0.85;
|
background-color: rgb(0, 0, 0);
|
||||||
animation: olympia-confetti-fall linear infinite;
|
will-change: transform;
|
||||||
border-radius: 4px; /* slightly rounder confetti */
|
animation-name: olympia-confetti-flutter;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-confetti.circle {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-confetti.square {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.olympia-confetti.triangle {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes olympia-fall {
|
@keyframes olympia-fall {
|
||||||
0% {
|
0% { transform: translateY(-10vh); }
|
||||||
transform: translateY(-10vh) rotate(var(--start-rot, 0deg));
|
100% { transform: translateY(110vh); }
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
10% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
85% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(110vh) rotate(var(--end-rot, 360deg));
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes olympia-confetti-fall {
|
@keyframes olympia-sway {
|
||||||
0% {
|
0% { transform: rotate(-25deg) translateX(-20px); }
|
||||||
transform: translateY(-5vh) rotateX(0deg) rotateY(0deg);
|
100% { transform: rotate(25deg) translateX(20px); }
|
||||||
opacity: 0;
|
}
|
||||||
}
|
|
||||||
5% {
|
@keyframes olympia-tumble-3d {
|
||||||
opacity: 1;
|
0% { transform: rotate3d(calc(var(--rot-x) + 0.001), calc(var(--rot-y) + 0.001), calc(var(--rot-z) + 0.001), 0deg); }
|
||||||
}
|
100% { transform: rotate3d(calc(var(--rot-x) + 0.001), calc(var(--rot-y) + 0.001), calc(var(--rot-z) + 0.001), 360deg); }
|
||||||
90% {
|
}
|
||||||
opacity: 1;
|
|
||||||
}
|
@keyframes olympia-confetti-flutter {
|
||||||
100% {
|
0% {
|
||||||
transform: translateY(105vh) rotateX(720deg) rotateY(360deg);
|
transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), 0deg);
|
||||||
opacity: 0;
|
}
|
||||||
}
|
100% {
|
||||||
|
transform: rotate3d(var(--rx, 1), var(--ry, 1), var(--rz, 0), var(--rot-dir, 360deg));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,61 +60,187 @@ function createOlympia() {
|
|||||||
|
|
||||||
const useRandomDuration = enableDifferentDuration !== false;
|
const useRandomDuration = enableDifferentDuration !== false;
|
||||||
|
|
||||||
const activeItems = ['gold', 'silver', 'bronze', 'torch'];
|
const activeItems = ['gold', 'silver', 'bronze', 'torch', 'rings_blue', 'rings_yellow', 'rings_black', 'rings_green', 'rings_red'];
|
||||||
|
|
||||||
for (let i = 0; i < finalCount; i++) {
|
for (let i = 0; i < finalCount; i++) {
|
||||||
let symbol = document.createElement('div');
|
let symbol = document.createElement('div');
|
||||||
|
|
||||||
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
const randomItem = activeItems[Math.floor(Math.random() * activeItems.length)];
|
||||||
|
const isRing = randomItem.startsWith('rings');
|
||||||
|
const isMedal = ['gold', 'silver', 'bronze'].includes(randomItem);
|
||||||
|
|
||||||
symbol.className = `olympia-symbol olympia-${randomItem}`;
|
symbol.className = `olympia-symbol olympia-${randomItem}`;
|
||||||
|
|
||||||
let img = document.createElement('img');
|
// Create inner div for sway/rotation
|
||||||
img.src = `../Seasonals/Resources/olympia_images/${randomItem}.png`;
|
let innerDiv = document.createElement('div');
|
||||||
img.onerror = function() {
|
innerDiv.className = 'olympia-inner';
|
||||||
this.style.display = 'none';
|
let img = null;
|
||||||
this.parentElement.innerHTML = getOlympiaEmojiFallback(randomItem);
|
|
||||||
};
|
|
||||||
symbol.appendChild(img);
|
|
||||||
|
|
||||||
const leftPos = Math.random() * 100;
|
if (isRing) {
|
||||||
const delaySeconds = Math.random() * 10;
|
const ringColorMap = {
|
||||||
|
'rings_blue': '#0081C8',
|
||||||
let durationSeconds = 8;
|
'rings_yellow': '#FCB131',
|
||||||
if (useRandomDuration) {
|
'rings_black': '#000000',
|
||||||
durationSeconds = Math.random() * 5 + 6; // 6 to 11 seconds
|
'rings_green': '#00A651',
|
||||||
|
'rings_red': '#EE334E'
|
||||||
|
};
|
||||||
|
let ringDiv = document.createElement('div');
|
||||||
|
ringDiv.className = 'olympia-ring-css';
|
||||||
|
ringDiv.style.setProperty('--ring-color', ringColorMap[randomItem]);
|
||||||
|
innerDiv.appendChild(ringDiv);
|
||||||
|
|
||||||
|
// Add a 3D flip animation for rings and medals
|
||||||
|
const spinReverse = Math.random() > 0.5 ? 'reverse' : 'normal';
|
||||||
|
innerDiv.style.animation = `olympia-tumble-3d ${Math.random() * 4 + 4}s linear infinite ${spinReverse}`;
|
||||||
|
|
||||||
|
// Random 3D Rotation Axis for Tumbling
|
||||||
|
innerDiv.style.setProperty('--rot-x', (Math.random() * 2 - 1).toFixed(2));
|
||||||
|
innerDiv.style.setProperty('--rot-y', (Math.random() * 2 - 1).toFixed(2));
|
||||||
|
innerDiv.style.setProperty('--rot-z', (Math.random() * 2 - 1).toFixed(2));
|
||||||
|
} else {
|
||||||
|
img = document.createElement('img');
|
||||||
|
let imgName = randomItem;
|
||||||
|
if (isMedal) {
|
||||||
|
imgName = `${randomItem}_coin.gif`;
|
||||||
|
} else {
|
||||||
|
imgName = `${randomItem}.png`;
|
||||||
|
}
|
||||||
|
img.src = `../Seasonals/Resources/olympic_assets/${imgName}`;
|
||||||
|
img.onerror = function() {
|
||||||
|
this.style.display = 'none';
|
||||||
|
this.parentElement.innerHTML = getOlympiaEmojiFallback(randomItem);
|
||||||
|
};
|
||||||
|
innerDiv.appendChild(img);
|
||||||
|
|
||||||
|
if (isMedal) {
|
||||||
|
innerDiv.style.animation = `olympia-flip-3d ${Math.random() * 4 + 3}s linear infinite`;
|
||||||
|
} else {
|
||||||
|
// Torch sways, medals flip
|
||||||
|
const swayDur = Math.random() * 2 + 2; // 2 to 4s
|
||||||
|
const swayDir = Math.random() > 0.5 ? 'normal' : 'reverse';
|
||||||
|
innerDiv.style.animation = `olympia-sway ${swayDur}s ease-in-out infinite alternate ${swayDir}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startRot = Math.random() * 360;
|
symbol.appendChild(innerDiv);
|
||||||
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`;
|
const leftPos = Math.random() * 95;
|
||||||
symbol.style.animationDuration = `${durationSeconds}s`;
|
const delaySeconds = Math.random() * 10;
|
||||||
|
|
||||||
|
// Depth logic for medals and rings
|
||||||
|
const depth = Math.random();
|
||||||
|
const scale = 0.8 + depth * 0.4; // 0.8 to 1.2
|
||||||
|
const zIndex = Math.floor(depth * 30) + 10;
|
||||||
|
|
||||||
|
if (img) {
|
||||||
|
img.style.transform = `scale(${scale})`;
|
||||||
|
} else {
|
||||||
|
innerDiv.firstChild.style.transform = `scale(${scale})`;
|
||||||
|
}
|
||||||
|
symbol.style.zIndex = zIndex;
|
||||||
|
|
||||||
|
let durationSeconds = 8;
|
||||||
|
if (useRandomDuration) {
|
||||||
|
durationSeconds = (1 - depth) * 5 + 6 + Math.random() * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol.style.animation = `olympia-fall ${durationSeconds}s linear infinite`;
|
||||||
symbol.style.animationDelay = `${delaySeconds}s`;
|
symbol.style.animationDelay = `${delaySeconds}s`;
|
||||||
|
symbol.style.left = `${leftPos}vw`;
|
||||||
|
|
||||||
container.appendChild(symbol);
|
container.appendChild(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Olympic Ring Colors
|
// Olympic Torches (Fixed at bottom corners, symmetrically rotated inward)
|
||||||
|
// Generate one random inward rotation (10 to 25 deg) for both to share
|
||||||
|
const sharedTilt = Math.random() * 15 + 10;
|
||||||
|
|
||||||
|
const createTorch = (isLeft) => {
|
||||||
|
const torch = document.createElement('div');
|
||||||
|
torch.className = 'olympia-flame';
|
||||||
|
|
||||||
|
if (isLeft) {
|
||||||
|
torch.style.left = '5vw';
|
||||||
|
// Lean right, face normal
|
||||||
|
torch.style.transform = `rotate(${sharedTilt}deg) scaleX(1)`;
|
||||||
|
} else {
|
||||||
|
torch.style.right = '5vw';
|
||||||
|
// Lean left, mirror image
|
||||||
|
torch.style.transform = `rotate(-${sharedTilt}deg) scaleX(-1)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let torchImg = document.createElement('img');
|
||||||
|
torchImg.src = `../Seasonals/Resources/olympic_assets/torch.gif`;
|
||||||
|
torchImg.style.height = '25vh';
|
||||||
|
torchImg.style.objectFit = 'contain';
|
||||||
|
torchImg.onerror = function() {
|
||||||
|
this.style.display = 'none';
|
||||||
|
};
|
||||||
|
torch.appendChild(torchImg);
|
||||||
|
container.appendChild(torch);
|
||||||
|
};
|
||||||
|
|
||||||
|
createTorch(true);
|
||||||
|
createTorch(false);
|
||||||
|
|
||||||
|
// Olympic Ring Colors (Carnival Config)
|
||||||
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
|
const confettiColors = ['#0081C8', '#FCB131', '#000000', '#00A651', '#EE334E'];
|
||||||
const confettiCount = isMobile ? 30 : 60;
|
const confettiCount = isMobile ? 30 : 60;
|
||||||
|
|
||||||
for (let i = 0; i < confettiCount; i++) {
|
for (let i = 0; i < confettiCount; i++) {
|
||||||
|
let wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'olympia-confetti-wrapper';
|
||||||
|
|
||||||
|
let leftPos = Math.random() * 100;
|
||||||
|
wrapper.style.left = `${leftPos}vw`;
|
||||||
|
|
||||||
|
let fallDuration = Math.random() * 3 + 4; // 4 to 7 seconds to fall
|
||||||
|
wrapper.style.animationDuration = `${fallDuration}s`;
|
||||||
|
wrapper.style.animationDelay = `-${Math.random() * fallDuration}s`; // Negative delay so it distributes perfectly immediately
|
||||||
|
|
||||||
|
let swayWrapper = document.createElement('div');
|
||||||
|
swayWrapper.className = 'olympia-confetti-sway';
|
||||||
|
let swayDuration = Math.random() * 2 + 1.5; // 1.5s to 3.5s
|
||||||
|
swayWrapper.style.animationDuration = `${swayDuration}s`;
|
||||||
|
let swayAmount = Math.random() * 30 + 30; // 30px to 60px
|
||||||
|
swayWrapper.style.setProperty('--sway-amount', `${swayAmount}px`);
|
||||||
|
let initSwayDelay = Math.random() * swayDuration;
|
||||||
|
swayWrapper.style.animationDelay = `-${initSwayDelay}s`;
|
||||||
|
|
||||||
let confetti = document.createElement('div');
|
let confetti = document.createElement('div');
|
||||||
confetti.className = 'olympia-confetti';
|
confetti.className = 'olympia-confetti';
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
const leftPos = Math.random() * 100;
|
// Random shape
|
||||||
const delaySeconds = Math.random() * 8;
|
const shape = Math.random();
|
||||||
const duration = Math.random() * 3 + 5;
|
if (shape > 0.66) {
|
||||||
|
confetti.classList.add('circle');
|
||||||
|
const size = Math.random() * 5 + 5;
|
||||||
|
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;
|
||||||
|
const height = Math.random() * 5 + 8;
|
||||||
|
confetti.style.width = `${width}px`;
|
||||||
|
confetti.style.height = `${height}px`;
|
||||||
|
} else {
|
||||||
|
confetti.classList.add('triangle');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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`);
|
||||||
|
let rotateDuration = Math.random() * 0.8 + 0.4;
|
||||||
|
confetti.style.animationDuration = `${rotateDuration}s`;
|
||||||
|
|
||||||
confetti.style.left = `${leftPos}vw`;
|
swayWrapper.appendChild(confetti);
|
||||||
confetti.style.animationDuration = `${duration}s`;
|
wrapper.appendChild(swayWrapper);
|
||||||
confetti.style.animationDelay = `${delaySeconds}s`;
|
container.appendChild(wrapper);
|
||||||
|
|
||||||
container.appendChild(confetti);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +248,7 @@ function getOlympiaEmojiFallback(type) {
|
|||||||
if (type === 'gold') return '🥇';
|
if (type === 'gold') return '🥇';
|
||||||
if (type === 'silver') return '🥈';
|
if (type === 'silver') return '🥈';
|
||||||
if (type === 'bronze') return '🥉';
|
if (type === 'bronze') return '🥉';
|
||||||
if (type === 'torch') return '🔥';
|
return ''; // Rings will be handled by CSS or actual image
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeOlympia() {
|
function initializeOlympia() {
|
||||||
|
|||||||
Reference in New Issue
Block a user