Add Halloween feature: implement fog layer, spiders, and mice animations with visibility control
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
z-index: 10000;
|
||||
contain: layout paint;
|
||||
}
|
||||
|
||||
@@ -82,71 +82,183 @@
|
||||
.halloween:nth-of-type(0) {
|
||||
left: 1%;
|
||||
-webkit-animation-delay: 0s, 0s;
|
||||
animation-delay: 0s, 0s
|
||||
animation-delay: 0s, 0s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(1) {
|
||||
left: 10%;
|
||||
-webkit-animation-delay: 1s, 1s;
|
||||
animation-delay: 1s, 1s
|
||||
-webkit-animation-delay: -1s, -1s;
|
||||
animation-delay: -1s, -1s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(2) {
|
||||
left: 20%;
|
||||
-webkit-animation-delay: 6s, .5s;
|
||||
animation-delay: 6s, .5s
|
||||
-webkit-animation-delay: -2s, -2s;
|
||||
animation-delay: -2s, -2s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(3) {
|
||||
left: 30%;
|
||||
-webkit-animation-delay: 4s, 2s;
|
||||
animation-delay: 4s, 2s
|
||||
-webkit-animation-delay: -3s, -3s;
|
||||
animation-delay: -3s, -3s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(4) {
|
||||
left: 40%;
|
||||
-webkit-animation-delay: 2s, 2s;
|
||||
animation-delay: 2s, 2s
|
||||
-webkit-animation-delay: -4s, -4s;
|
||||
animation-delay: -4s, -4s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(5) {
|
||||
left: 50%;
|
||||
-webkit-animation-delay: 8s, 3s;
|
||||
animation-delay: 8s, 3s
|
||||
-webkit-animation-delay: -5s, -5s;
|
||||
animation-delay: -5s, -5s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(6) {
|
||||
left: 60%;
|
||||
-webkit-animation-delay: 6s, 2s;
|
||||
animation-delay: 6s, 2s
|
||||
-webkit-animation-delay: -6s, -6s;
|
||||
animation-delay: -6s, -6s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(7) {
|
||||
left: 70%;
|
||||
-webkit-animation-delay: 2.5s, 1s;
|
||||
animation-delay: 2.5s, 1s
|
||||
-webkit-animation-delay: -7s, -7s;
|
||||
animation-delay: -7s, -7s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(8) {
|
||||
left: 80%;
|
||||
-webkit-animation-delay: 1s, 0s;
|
||||
animation-delay: 1s, 0s
|
||||
-webkit-animation-delay: -8s, -8s;
|
||||
animation-delay: -8s, -8s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(9) {
|
||||
left: 90%;
|
||||
-webkit-animation-delay: 3s, 1.5s;
|
||||
animation-delay: 3s, 1.5s
|
||||
-webkit-animation-delay: -9s, -9s;
|
||||
animation-delay: -9s, -9s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(10) {
|
||||
left: 25%;
|
||||
-webkit-animation-delay: 2s, 0s;
|
||||
animation-delay: 2s, 0s
|
||||
-webkit-animation-delay: -10s, -10s;
|
||||
animation-delay: -10s, -10s;
|
||||
}
|
||||
|
||||
.halloween:nth-of-type(11) {
|
||||
left: 65%;
|
||||
-webkit-animation-delay: 4s, 2.5s;
|
||||
animation-delay: 4s, 2.5s
|
||||
-webkit-animation-delay: -11s, -11s;
|
||||
animation-delay: -11s, -11s;
|
||||
}
|
||||
|
||||
/* --- Fog Layer --- */
|
||||
.halloween-fog-layer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(to top, black, transparent);
|
||||
-webkit-mask-image: linear-gradient(to top, black, transparent);
|
||||
}
|
||||
.halloween-fog-blob {
|
||||
position: absolute;
|
||||
bottom: -10vh;
|
||||
width: 150vw;
|
||||
height: 50vh;
|
||||
background: radial-gradient(ellipse at center, rgba(120, 130, 140, 0.4) 0%, transparent 60%);
|
||||
border-radius: 50%;
|
||||
filter: blur(15px);
|
||||
}
|
||||
.halloween-fog-blob:nth-child(1) {
|
||||
left: -20vw;
|
||||
animation: fog-float1 25s ease-in-out infinite alternate;
|
||||
}
|
||||
.halloween-fog-blob:nth-child(2) {
|
||||
left: -50vw;
|
||||
background: radial-gradient(ellipse at center, rgba(100, 110, 120, 0.3) 0%, transparent 65%);
|
||||
animation: fog-float2 35s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes fog-float1 {
|
||||
0% { transform: translateX(0) scale(1); opacity: 0.8; }
|
||||
50% { opacity: 1; }
|
||||
100% { transform: translateX(20vw) scale(1.1); opacity: 0.6; }
|
||||
}
|
||||
@keyframes fog-float2 {
|
||||
0% { transform: translateX(0) scale(1.1); opacity: 0.7; }
|
||||
50% { opacity: 1; }
|
||||
100% { transform: translateX(30vw) scale(1); opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* --- Spiders --- */
|
||||
.halloween-spider-wrapper {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1002;
|
||||
transform-origin: top;
|
||||
will-change: transform;
|
||||
pointer-events: auto;
|
||||
padding: 20px; /* Increase hit area safely */
|
||||
}
|
||||
.halloween-thread {
|
||||
width: 30px; /* Wider hit area for mouse interaction */
|
||||
height: 100vh;
|
||||
margin-top: -100vh;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.halloween-thread::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, rgba(200, 200, 200, 0.1), rgba(200, 200, 200, 0.6));
|
||||
}
|
||||
.halloween-spider {
|
||||
animation: spider-swing 3s ease-in-out infinite alternate;
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
/* MARK: SPIDER SWAY CONFIGURATION */
|
||||
/* Adjust degrees in 'rotate(...)' to change how far spider and thread swing in wind. */
|
||||
@keyframes wind-sway {
|
||||
0% { transform: rotate(0deg); }
|
||||
25% { transform: rotate(2deg); }
|
||||
75% { transform: rotate(-2deg); }
|
||||
100% { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
@keyframes spider-drop {
|
||||
0% { transform: translateY(-50px); }
|
||||
30% { transform: translateY(var(--drop-height, 50vh)); }
|
||||
60% { transform: translateY(var(--drop-height, 50vh)); }
|
||||
100% { transform: translateY(-50px); }
|
||||
}
|
||||
@keyframes spider-swing {
|
||||
0% { transform: rotate(-10deg); }
|
||||
100% { transform: rotate(10deg); }
|
||||
}
|
||||
|
||||
/* Mice */
|
||||
.halloween-mouse {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
will-change: left;
|
||||
}
|
||||
@keyframes mouse-run-right {
|
||||
0% { left: -10vw; }
|
||||
100% { left: 110vw; }
|
||||
}
|
||||
@keyframes mouse-run-left {
|
||||
0% { left: 110vw; }
|
||||
100% { left: -10vw; }
|
||||
}
|
||||
@@ -4,8 +4,16 @@ const halloween = config.EnableHalloween !== undefined ? config.EnableHalloween
|
||||
const randomSymbols = config.EnableRandomSymbols !== undefined ? config.EnableRandomSymbols : true; // enable more random symbols
|
||||
const randomSymbolsMobile = config.EnableRandomSymbolsMobile !== undefined ? config.EnableRandomSymbolsMobile : false; // enable random symbols on mobile devices (Warning: High values may affect performance)
|
||||
const enableDiffrentDuration = config.EnableDifferentDuration !== undefined ? config.EnableDifferentDuration : true; // enable different duration for the random halloween symbols
|
||||
const enableSpiders = config.EnableSpiders !== undefined ? config.EnableSpiders : true;
|
||||
const enableMice = config.EnableMice !== undefined ? config.EnableMice : true;
|
||||
const halloweenCount = config.SymbolCount || 25; // count of random extra symbols
|
||||
|
||||
const images = [
|
||||
"../Seasonals/Resources/halloween_images/ghost_20x20.png",
|
||||
"../Seasonals/Resources/halloween_images/bat_20x20.png",
|
||||
"../Seasonals/Resources/halloween_images/pumpkin_20x20.png",
|
||||
];
|
||||
|
||||
let msgPrinted = false; // flag to prevent multiple console messages
|
||||
|
||||
// function to check and control the halloween
|
||||
@@ -36,21 +44,13 @@ function toggleHalloween() {
|
||||
|
||||
// observe changes in the DOM
|
||||
const observer = new MutationObserver(toggleHalloween);
|
||||
|
||||
// start observation
|
||||
observer.observe(document.body, {
|
||||
childList: true, // observe adding/removing of child elements
|
||||
subtree: true, // observe all levels of the DOM tree
|
||||
attributes: true // observe changes to attributes (e.g. class changes)
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
|
||||
|
||||
const images = [
|
||||
"../Seasonals/Resources/halloween_images/ghost_20x20.png",
|
||||
"../Seasonals/Resources/halloween_images/bat_20x20.png",
|
||||
"../Seasonals/Resources/halloween_images/pumpkin_20x20.png",
|
||||
];
|
||||
|
||||
function addRandomSymbols(count) {
|
||||
const halloweenContainer = document.querySelector('.halloween-container'); // get the halloween container
|
||||
if (!halloweenContainer) return; // exit if halloween container is not found
|
||||
@@ -74,7 +74,7 @@ function addRandomSymbols(count) {
|
||||
// set random horizontal position, animation delay and size(uncomment lines to enable)
|
||||
const randomLeft = Math.random() * 100; // position (0% to 100%)
|
||||
const randomAnimationDelay = Math.random() * 10; // delay (0s to 10s)
|
||||
const randomAnimationDelay2 = Math.random() * 3; // delay (0s to 3s)
|
||||
const randomAnimationDelay2 = -(Math.random() * 3); // delay (-3s to 0s)
|
||||
|
||||
// apply styles
|
||||
halloweenDiv.style.left = `${randomLeft}%`;
|
||||
@@ -124,12 +124,137 @@ function createHalloween() {
|
||||
}
|
||||
}
|
||||
|
||||
// create fog layer
|
||||
function createFog(container) {
|
||||
const fogContainer = document.createElement('div');
|
||||
fogContainer.className = 'halloween-fog-layer';
|
||||
|
||||
const fog1 = document.createElement('div');
|
||||
fog1.className = 'halloween-fog-blob';
|
||||
|
||||
const fog2 = document.createElement('div');
|
||||
fog2.className = 'halloween-fog-blob';
|
||||
|
||||
fogContainer.appendChild(fog1);
|
||||
fogContainer.appendChild(fog2);
|
||||
container.appendChild(fogContainer);
|
||||
}
|
||||
|
||||
// create dropping spiders
|
||||
function createSpider(container) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'halloween-spider-wrapper';
|
||||
|
||||
wrapper.innerHTML = `
|
||||
<div class="halloween-sway" style="display:flex; flex-direction:column; align-items:center; transform-origin: 50% -100vh;">
|
||||
<div class="halloween-thread"></div>
|
||||
<svg class="halloween-spider" viewBox="0 0 24 24" width="30" height="30">
|
||||
<circle cx="12" cy="12" r="6" fill="#1a1a1a"/>
|
||||
<!-- left legs -->
|
||||
<path d="M12 12 l-8 -4 M12 12 l-9 0 M12 12 l-8 4 M12 12 l-6 8" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" fill="none"/>
|
||||
<!-- right legs -->
|
||||
<path d="M12 12 l8 -4 M12 12 l9 0 M12 12 l8 4 M12 12 l6 8" stroke="#1a1a1a" stroke-width="1.5" stroke-linecap="round" fill="none"/>
|
||||
<circle cx="10" cy="14" r="1.5" fill="#ff3333"/>
|
||||
<circle cx="14" cy="14" r="1.5" fill="#ff3333"/>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
wrapper.style.left = `${10 + Math.random() * 80}%`;
|
||||
const dropHeight = 30 + Math.random() * 50; // 30vh to 80vh
|
||||
wrapper.style.setProperty('--drop-height', `${dropHeight}vh`);
|
||||
|
||||
const duration = Math.random() * 6 + 6; // 6-12s drop
|
||||
wrapper.style.animation = `spider-drop ${duration}s ease-in-out forwards`;
|
||||
|
||||
// Start the sway animation only after the drop completes (30% of total duration)
|
||||
const sway = wrapper.querySelector('.halloween-sway');
|
||||
sway.style.animation = `wind-sway 8s ease-in-out ${duration * 0.3}s infinite`;
|
||||
|
||||
// Spider retreat logic
|
||||
let isRetreating = false;
|
||||
wrapper.addEventListener('mouseenter', () => {
|
||||
if (isRetreating) return;
|
||||
isRetreating = true;
|
||||
// Retreat smoothly by pushing margin up
|
||||
wrapper.style.transition = 'margin-top 0.4s ease-in';
|
||||
wrapper.style.marginTop = '-100vh';
|
||||
|
||||
setTimeout(() => {
|
||||
wrapper.remove();
|
||||
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
wrapper.addEventListener('animationend', () => {
|
||||
if (isRetreating) return;
|
||||
wrapper.remove();
|
||||
setTimeout(() => createSpider(container), Math.random() * 5000 + 1000);
|
||||
});
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
// create scurrying mice
|
||||
function createMouse(container) {
|
||||
const mouse = document.createElement('div');
|
||||
mouse.className = 'halloween-mouse';
|
||||
mouse.innerHTML = `
|
||||
<svg viewBox="0 0 30 15" width="40" height="20">
|
||||
<ellipse cx="15" cy="10" rx="10" ry="5" fill="#111"/>
|
||||
<circle cx="24" cy="10" r="4" fill="#111"/>
|
||||
<circle cx="24" cy="6" r="3" fill="#333"/>
|
||||
<path d="M 5 10 Q 0 10 0 2" stroke="#111" stroke-width="1.5" fill="none"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const direction = Math.random() > 0.5 ? 'right' : 'left';
|
||||
const duration = Math.random() * 3 + 2; // 2-5s run (fast)
|
||||
|
||||
if (direction === 'right') {
|
||||
mouse.style.animation = `mouse-run-right ${duration}s linear forwards`;
|
||||
mouse.style.transform = 'scaleX(1)';
|
||||
} else {
|
||||
mouse.style.animation = `mouse-run-left ${duration}s linear forwards`;
|
||||
mouse.style.transform = 'scaleX(-1)';
|
||||
}
|
||||
|
||||
mouse.style.bottom = `5px`; // Fixated bottom edge
|
||||
|
||||
mouse.addEventListener('animationend', () => {
|
||||
mouse.remove();
|
||||
setTimeout(() => createMouse(container), Math.random() * 4000 + 2000);
|
||||
});
|
||||
|
||||
container.appendChild(mouse);
|
||||
}
|
||||
|
||||
// initialize halloween
|
||||
function initializeHalloween() {
|
||||
if (!halloween) return; // exit if halloween is disabled
|
||||
createHalloween();
|
||||
toggleHalloween();
|
||||
|
||||
const container = document.querySelector('.halloween-container');
|
||||
|
||||
if (container) {
|
||||
createFog(container);
|
||||
|
||||
// Add a few spiders
|
||||
if (enableSpiders) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
setTimeout(() => createSpider(container), Math.random() * 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a few mice
|
||||
if (enableMice) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => createMouse(container), Math.random() * 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const screenWidth = window.innerWidth; // get the screen width to detect mobile devices
|
||||
if (randomSymbols && (screenWidth > 768 || randomSymbolsMobile)) { // add random halloweens only on larger screens, unless enabled for mobile devices
|
||||
addRandomSymbols(halloweenCount);
|
||||
|
||||
Reference in New Issue
Block a user