diff --git a/seasonals/autumn.css b/seasonals/autumn.css new file mode 100644 index 0000000..880df62 --- /dev/null +++ b/seasonals/autumn.css @@ -0,0 +1,133 @@ +.autumn-container { + display: block; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.leaf { + position: fixed; + top: -10%; + font-size: 1em; + color: #fff; + font-family: Arial, sans-serif; + text-shadow: 0 0 5px #000; + user-select: none; + -webkit-user-select: none; + cursor: default; + -webkit-animation-name: leaf-fall, leaf-shake; + -webkit-animation-duration: 7s, 3s; + -webkit-animation-timing-function: linear, ease-in-out; + -webkit-animation-iteration-count: infinite, infinite; + -webkit-user-select: none; + animation-name: leaf-fall, leaf-shake; + animation-duration: 7s, 3s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; +} + +@-webkit-keyframes leaf-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@-webkit-keyframes leaf-shake { + + 0%, + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 50% { + -webkit-transform: translateX(80px); + transform: translateX(80px); + } +} + +@keyframes leaf-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@keyframes leaf-shake { + + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(80px); + } +} + +.leaf:nth-of-type(0) { + left: 0%; + animation-delay: 0s, 0s; +} + +.leaf:nth-of-type(1) { + left: 10%; + animation-delay: 1s, 1s; +} + +.leaf:nth-of-type(2) { + left: 20%; + animation-delay: 6s, 0.5s; +} + +.leaf:nth-of-type(3) { + left: 30%; + animation-delay: 4s, 2s; +} + +.leaf:nth-of-type(4) { + left: 40%; + animation-delay: 2s, 2s; +} + +.leaf:nth-of-type(5) { + left: 50%; + animation-delay: 8s, 3s; +} + +.leaf:nth-of-type(6) { + left: 60%; + animation-delay: 6s, 2s; +} + +.leaf:nth-of-type(7) { + left: 70%; + animation-delay: 2.5s, 1s; +} + +.leaf:nth-of-type(8) { + left: 80%; + animation-delay: 1s, 0s; +} + +.leaf:nth-of-type(9) { + left: 90%; + animation-delay: 3s, 1.5s; +} + +.leaf:nth-of-type(10) { + left: 25%; + animation-delay: 2s, 0s; +} + +.leaf:nth-of-type(11) { + left: 65%; + animation-delay: 4s, 2.5s; +} \ No newline at end of file diff --git a/seasonals/autumn.js b/seasonals/autumn.js new file mode 100644 index 0000000..5dfdecb --- /dev/null +++ b/seasonals/autumn.js @@ -0,0 +1,148 @@ +const leaves = true; // enable/disable leaves +const randomLeaves = true; // enable random leaves +const randomLeavesMobile = false; // enable random leaves on mobile devices +const enableDiffrentDuration = true; // enable different duration for the random leaves +const leafCount = 25; // count of random extra leaves + + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the leaves +function toggleAutumn() { + const autumnContainer = document.querySelector('.autumn-container'); + if (!autumnContainer) 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'); + + // hide leaves if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + autumnContainer.style.display = 'none'; // hide leaves + if (!msgPrinted) { + console.log('Autumn hidden'); + msgPrinted = true; + } + } else { + autumnContainer.style.display = 'block'; // show leaves + if (msgPrinted) { + console.log('Autumn visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleAutumn); + +// 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) +}); + + +const images = [ + "./seasonals/autumn_images/acorn1.png", + "./seasonals/autumn_images/acorn2.png", + "./seasonals/autumn_images/leaf1.png", + "./seasonals/autumn_images/leaf2.png", + "./seasonals/autumn_images/leaf3.png", + "./seasonals/autumn_images/leaf4.png", + "./seasonals/autumn_images/leaf5.png", + "./seasonals/autumn_images/leaf6.png", + "./seasonals/autumn_images/leaf7.png", + "./seasonals/autumn_images/leaf8.png", + "./seasonals/autumn_images/leaf9.png", + "./seasonals/autumn_images/leaf10.png", + "./seasonals/autumn_images/leaf11.png", + "./seasonals/autumn_images/leaf12.png", + "./seasonals/autumn_images/leaf13.png", + "./seasonals/autumn_images/leaf14.png", + "./seasonals/autumn_images/leaf15.png", +]; + +function addRandomLeaves(count) { + const autumnContainer = document.querySelector('.autumn-container'); // get the leave container + if (!autumnContainer) return; // exit if leave container is not found + + console.log('Adding random leaves'); + + // Array of leave characters + for (let i = 0; i < count; i++) { + // create a new leave element + const leaveDiv = document.createElement('div'); + leaveDiv.className = "leaf"; + + // pick a random leaf symbol + const imageSrc = images[Math.floor(Math.random() * images.length)]; + const img = document.createElement("img"); + img.src = imageSrc; + + leaveDiv.appendChild(img); + + + // 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() * 12; // delay (0s to 12s) + const randomAnimationDelay2 = Math.random() * 5; // delay (0s to 5s) + + // apply styles + leaveDiv.style.left = `${randomLeft}%`; + leaveDiv.style.animationDelay = `${randomAnimationDelay}s, ${randomAnimationDelay2}s`; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + leafDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + // add the leave to the container + autumnContainer.appendChild(leaveDiv); + } + console.log('Random leaves added'); +} + +// initialize standard leaves +function initLeaves() { + const container = document.querySelector('.autumn-container') || document.createElement("div"); + + if (!document.querySelector('.autumn-container')) { + container.className = "autumn-container"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + for (let i = 0; i < 12; i++) { + const leafDiv = document.createElement("div"); + leafDiv.className = "leaf"; + + const img = document.createElement("img"); + img.src = images[Math.floor(Math.random() * images.length)]; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + leafDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + leafDiv.appendChild(img); + container.appendChild(leafDiv); + } +} + +// initialize leaves and add random leaves after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!leaves) return; // exit if leaves are disabled + initLeaves(); + toggleAutumn(); + + const screenWidth = window.innerWidth; // get the screen width to detect mobile devices + if (randomLeaves && (screenWidth > 768 || randomLeavesMobile)) { // add random leaves only on larger screens, unless enabled for mobile devices + addRandomLeaves(leafCount); + } +}); \ No newline at end of file diff --git a/seasonals/autumn_images/acorn1.png b/seasonals/autumn_images/acorn1.png new file mode 100644 index 0000000..c07e2cf Binary files /dev/null and b/seasonals/autumn_images/acorn1.png differ diff --git a/seasonals/autumn_images/acorn2.png b/seasonals/autumn_images/acorn2.png new file mode 100644 index 0000000..226e779 Binary files /dev/null and b/seasonals/autumn_images/acorn2.png differ diff --git a/seasonals/autumn_images/leaf1.png b/seasonals/autumn_images/leaf1.png new file mode 100644 index 0000000..49c1562 Binary files /dev/null and b/seasonals/autumn_images/leaf1.png differ diff --git a/seasonals/autumn_images/leaf10.png b/seasonals/autumn_images/leaf10.png new file mode 100644 index 0000000..d6b35e8 Binary files /dev/null and b/seasonals/autumn_images/leaf10.png differ diff --git a/seasonals/autumn_images/leaf11.png b/seasonals/autumn_images/leaf11.png new file mode 100644 index 0000000..a737474 Binary files /dev/null and b/seasonals/autumn_images/leaf11.png differ diff --git a/seasonals/autumn_images/leaf12.png b/seasonals/autumn_images/leaf12.png new file mode 100644 index 0000000..6d826f5 Binary files /dev/null and b/seasonals/autumn_images/leaf12.png differ diff --git a/seasonals/autumn_images/leaf13.png b/seasonals/autumn_images/leaf13.png new file mode 100644 index 0000000..606eca0 Binary files /dev/null and b/seasonals/autumn_images/leaf13.png differ diff --git a/seasonals/autumn_images/leaf14.png b/seasonals/autumn_images/leaf14.png new file mode 100644 index 0000000..efcb182 Binary files /dev/null and b/seasonals/autumn_images/leaf14.png differ diff --git a/seasonals/autumn_images/leaf15.png b/seasonals/autumn_images/leaf15.png new file mode 100644 index 0000000..85c5b81 Binary files /dev/null and b/seasonals/autumn_images/leaf15.png differ diff --git a/seasonals/autumn_images/leaf2.png b/seasonals/autumn_images/leaf2.png new file mode 100644 index 0000000..5803e96 Binary files /dev/null and b/seasonals/autumn_images/leaf2.png differ diff --git a/seasonals/autumn_images/leaf3.png b/seasonals/autumn_images/leaf3.png new file mode 100644 index 0000000..8562965 Binary files /dev/null and b/seasonals/autumn_images/leaf3.png differ diff --git a/seasonals/autumn_images/leaf4.png b/seasonals/autumn_images/leaf4.png new file mode 100644 index 0000000..54b9aa7 Binary files /dev/null and b/seasonals/autumn_images/leaf4.png differ diff --git a/seasonals/autumn_images/leaf5.png b/seasonals/autumn_images/leaf5.png new file mode 100644 index 0000000..2cd8e22 Binary files /dev/null and b/seasonals/autumn_images/leaf5.png differ diff --git a/seasonals/autumn_images/leaf6.png b/seasonals/autumn_images/leaf6.png new file mode 100644 index 0000000..154843f Binary files /dev/null and b/seasonals/autumn_images/leaf6.png differ diff --git a/seasonals/autumn_images/leaf7.png b/seasonals/autumn_images/leaf7.png new file mode 100644 index 0000000..7b89b00 Binary files /dev/null and b/seasonals/autumn_images/leaf7.png differ diff --git a/seasonals/autumn_images/leaf8.png b/seasonals/autumn_images/leaf8.png new file mode 100644 index 0000000..60ac740 Binary files /dev/null and b/seasonals/autumn_images/leaf8.png differ diff --git a/seasonals/autumn_images/leaf9.png b/seasonals/autumn_images/leaf9.png new file mode 100644 index 0000000..f59a39e Binary files /dev/null and b/seasonals/autumn_images/leaf9.png differ diff --git a/seasonals/christmas.css b/seasonals/christmas.css new file mode 100644 index 0000000..0b4a1cc --- /dev/null +++ b/seasonals/christmas.css @@ -0,0 +1,132 @@ +.christmas-container { + display: block; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.christmas { + position: fixed; + top: -10%; + font-size: 1em; + color: #fff; + font-family: Arial, sans-serif; + text-shadow: 0 0 5px #000; + user-select: none; + cursor: default; + -webkit-user-select: none; + -webkit-animation-name: christmas-fall, christmas-shake; + -webkit-animation-duration: 10s, 3s; + -webkit-animation-timing-function: linear, ease-in-out; + -webkit-animation-iteration-count: infinite, infinite; + animation-name: christmas-fall, christmas-shake; + animation-duration: 10s, 3s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; +} + +@-webkit-keyframes christmas-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@-webkit-keyframes christmas-shake { + + 0%, + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 50% { + -webkit-transform: translateX(80px); + transform: translateX(80px); + } +} + +@keyframes christmas-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@keyframes christmas-shake { + + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(80px); + } +} + +.christmas:nth-of-type(0) { + left: 0%; + animation-delay: 0s, 0s; +} + +.christmas:nth-of-type(1) { + left: 10%; + animation-delay: 1s, 1s; +} + +.christmas:nth-of-type(2) { + left: 20%; + animation-delay: 6s, 0.5s; +} + +.christmas:nth-of-type(3) { + left: 30%; + animation-delay: 4s, 2s; +} + +.christmas:nth-of-type(4) { + left: 40%; + animation-delay: 2s, 2s; +} + +.christmas:nth-of-type(5) { + left: 50%; + animation-delay: 8s, 3s; +} + +.christmas:nth-of-type(6) { + left: 60%; + animation-delay: 6s, 2s; +} + +.christmas:nth-of-type(7) { + left: 70%; + animation-delay: 2.5s, 1s; +} + +.christmas:nth-of-type(8) { + left: 80%; + animation-delay: 1s, 0s; +} + +.christmas:nth-of-type(9) { + left: 90%; + animation-delay: 3s, 1.5s; +} + +.christmas:nth-of-type(10) { + left: 25%; + animation-delay: 2s, 0s; +} + +.christmas:nth-of-type(11) { + left: 65%; + animation-delay: 4s, 2.5s; +} \ No newline at end of file diff --git a/seasonals/christmas.js b/seasonals/christmas.js new file mode 100644 index 0000000..d3e467e --- /dev/null +++ b/seasonals/christmas.js @@ -0,0 +1,122 @@ +const christmas = true; // enable/disable christmas +const randomChristmas = true; // enable random Christmas +const randomChristmasMobile = false; // enable random Christmas on mobile devices +const enableDiffrentDuration = true; // enable different duration for the random Christmas symbols +const christmasCount = 25; // count of random extra christmas + + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the christmas +function toggleChristmas() { + const christmasContainer = document.querySelector('.christmas-container'); + if (!christmasContainer) 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'); + + // hide christmas if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + christmasContainer.style.display = 'none'; // hide christmas + if (!msgPrinted) { + console.log('Christmas hidden'); + msgPrinted = true; + } + } else { + christmasContainer.style.display = 'block'; // show christmas + if (msgPrinted) { + console.log('Christmas visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleChristmas); + +// 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) +}); + +// Array of christmas characters +const christmasSymbols = ['❆', '🎁', '❄️', '🎁', '🎅', '🎊', '🎁', '🎉']; + +function addRandomChristmas(count) { + const christmasContainer = document.querySelector('.christmas-container'); // get the christmas container + if (!christmasContainer) return; // exit if christmas container is not found + + console.log('Adding random christmas'); + + for (let i = 0; i < count; i++) { + // create a new christmas element + const christmasDiv = document.createElement('div'); + christmasDiv.classList.add('christmas'); + + // pick a random christmas symbol + christmasDiv.textContent = christmasSymbols[Math.floor(Math.random() * christmasSymbols.length)]; + + // 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() * 12 + 8; // delay (8s to 12s) + const randomAnimationDelay2 = Math.random() * 5 + 3; // delay (0s to 5s) + + // apply styles + christmasDiv.style.left = `${randomLeft}%`; + christmasDiv.style.animationDelay = `${randomAnimationDelay}s, ${randomAnimationDelay2}s`; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + christmasDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + // add the christmas to the container + christmasContainer.appendChild(christmasDiv); + } + console.log('Random christmas added'); +} + +// initialize standard christmas +function initChristmas() { + const christmasContainer = document.querySelector('.christmas-container') || document.createElement("div"); + + if (!document.querySelector('.christmas-container')) { + christmasContainer.className = "christmas-container"; + christmasContainer.setAttribute("aria-hidden", "true"); + document.body.appendChild(christmasContainer); + } + + // create the 12 standard christmas + for (let i = 0; i < 12; i++) { + const christmasDiv = document.createElement('div'); + christmasDiv.className = 'christmas'; + christmasDiv.textContent = christmasSymbols[Math.floor(Math.random() * christmasSymbols.length)]; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + christmasDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + christmasContainer.appendChild(christmasDiv); + } +} + +// initialize christmas and add random christmas symbols after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!christmas) return; // exit if christmas is disabled + initChristmas(); + toggleChristmas(); + + const screenWidth = window.innerWidth; // get the screen width to detect mobile devices + if (randomChristmas && (screenWidth > 768 || randomChristmasMobile)) { // add random christmas only on larger screens, unless enabled for mobile devices + addRandomChristmas(christmasCount); + } +}); \ No newline at end of file diff --git a/seasonals/fireworks.css b/seasonals/fireworks.css new file mode 100644 index 0000000..5314a2b --- /dev/null +++ b/seasonals/fireworks.css @@ -0,0 +1,66 @@ +.fireworks { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; + + /* activate the following for fixed positioning */ + /*position: fixed;*/ + /*overflow: hidden;*/ +} + +.rocket-trail { + position: absolute; + left: var(--trailX); + top: var(--trailStartY); + width: 4px; + + /* activate the following for rocket trail */ + height: 60px; + background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); + filter: blur(2px); + + /* activate the following for rocket trail as a point */ + /*height: 4px;*/ + /*background: white;*/ + /*border-radius: 50%; + box-shadow: 0 0 8px 2px white;*/ + + animation: rocket-trail-animation 1s linear forwards; +} + +@keyframes rocket-trail-animation { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(calc(var(--trailEndY) - var(--trailStartY))); + opacity: 0; + } +} + +/* Animation for the particles */ +@keyframes fireworkParticle { + 0% { + opacity: 1; + transform: translate(0, 0); + } + 100% { + opacity: 0; + transform: translate(var(--x), var(--y)); + } +} + +.firework { + position: absolute; + width: 5px; + height: 5px; + background: white; + border-radius: 50%; + animation: fireworkParticle 1.5s ease-out forwards; + filter: blur(1px); +} \ No newline at end of file diff --git a/seasonals/fireworks.js b/seasonals/fireworks.js new file mode 100644 index 0000000..8fdba14 --- /dev/null +++ b/seasonals/fireworks.js @@ -0,0 +1,159 @@ +const fireworks = true; // enable/disable fireworks +const scrollFireworks = true; // enable fireworks to scroll with page content +const particlesPerFirework = 50; // count of particles per firework +const minFireworks = 3; // minimum number of simultaneous fireworks +const maxFireworks = 6; // maximum number of simultaneous fireworks +const intervalOfFireworks = 3200; // interval for the fireworks in milliseconds + +// array of color palettes for the fireworks +const colorPalettes = [ + ['#ff0000', '#ff7300', '#ff4500'], // red's + ['#0040ff', '#5a9bff', '#b0d9ff'], // blue's + ['#47ff00', '#8eff47', '#00ff7f'], // green's + ['#ffd700', '#c0c0c0', '#ff6347'], // gold, silver, red + ['#ff00ff', '#ff99ff', '#800080'], // magenta's + ['#ffef00', '#ffff99', '#ffd700'], // yellow's + ['#ff4500', '#ff6347', '#ff7f50'], // orange's + ['#e3e3e3', '#c0c0c0', '#7d7c7c'], // silver's +]; + +let msgPrinted = false; // flag to prevent multiple console messages +let spacing = 0; // spacing between fireworks + +// function to check and control fireworks +function toggleFirework() { + const fireworksContainer = document.querySelector('.fireworks'); + if (!fireworksContainer) 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'); + + // hide fireworks if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + fireworksContainer.style.display = 'none'; // hide fireworks + if (!msgPrinted) { + console.log('Fireworks hidden'); + clearInterval(fireworksInterval); + msgPrinted = true; + } + } else { + fireworksContainer.style.display = 'block'; // show fireworks + if (msgPrinted) { + console.log('Fireworks visible'); + startFireworks(); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleFirework); + +// 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) +}); + + +// Function to create a rocket trail +function createRocketTrail(x, startY, endY) { + const fireworkContainer = document.querySelector('.fireworks'); + const rocketTrail = document.createElement('div'); + rocketTrail.classList.add('rocket-trail'); + fireworkContainer.appendChild(rocketTrail); + + // Set position and animation + rocketTrail.style.setProperty('--trailX', `${x}px`); + rocketTrail.style.setProperty('--trailStartY', `${startY}px`); + rocketTrail.style.setProperty('--trailEndY', `${endY}px`); + + // Remove the element after the animation + setTimeout(() => { + fireworkContainer.removeChild(rocketTrail); + }, 2000); // Duration of the animation +} + +// Function for particle explosion +function createExplosion(x, y) { + const fireworkContainer = document.querySelector('.fireworks'); + + // Choose a random color palette + const chosenPalette = colorPalettes[Math.floor(Math.random() * colorPalettes.length)]; + + for (let i = 0; i < particlesPerFirework; i++) { + const particle = document.createElement('div'); + particle.classList.add('firework'); + + const angle = Math.random() * 2 * Math.PI; // Random direction + const distance = Math.random() * 180 + 100; // Random distance + const xOffset = Math.cos(angle) * distance; + const yOffset = Math.sin(angle) * distance; + + particle.style.left = `${x}px`; + particle.style.top = `${y}px`; + particle.style.setProperty('--x', `${xOffset}px`); + particle.style.setProperty('--y', `${yOffset}px`); + particle.style.background = chosenPalette[Math.floor(Math.random() * chosenPalette.length)]; + + fireworkContainer.appendChild(particle); + + // Remove particle after the animation + setTimeout(() => particle.remove(), 3000); + } +} + +// Function for the firework with trail +function launchFirework() { + const fireworkContainer = document.querySelector('.fireworks') || document.createElement("div"); + + if (!document.querySelector('.fireworks')) { + fireworkContainer.className = "fireworks"; + fireworkContainer.setAttribute("aria-hidden", "true"); + document.body.appendChild(fireworkContainer); + } + + // Random horizontal position + const x = Math.random() * window.innerWidth; // Any value across the entire width + + // Trail starts at the bottom and ends at a random height around the middle + let startY, endY; + if (scrollFireworks) { + // Y-position considers scrolling + startY = window.scrollY + window.innerHeight; // Bottom edge of the window plus the scroll offset + endY = Math.random() * window.innerHeight * 0.5 + window.innerHeight * 0.2 + window.scrollY; // Area around the middle, but also with scrolling + } else { + startY = window.innerHeight; // Bottom edge of the window + endY = Math.random() * window.innerHeight * 0.5 + window.innerHeight * 0.2; // Area around the middle + } + + // Create trail + createRocketTrail(x, startY, endY); + + // Create explosion + setTimeout(() => { + createExplosion(x, endY); // Explosion at the end height + }, 1000); // or 1200 +} + +// Start the firework routine +function startFireworks() { + fireworksInterval = setInterval(() => { + const randomCount = Math.floor(Math.random() * maxFireworks) + minFireworks; + for (let i = 0; i < randomCount; i++) { + setTimeout(() => { + launchFirework(); + }, i * 200); // 200ms delay between fireworks + } + }, intervalOfFireworks); // Interval between fireworks +} + +// Initialize fireworks and add random fireworks after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!fireworks) return; // exit if fireworks are disabled + startFireworks(); + toggleFirework(); +}); diff --git a/seasonals/halloween.css b/seasonals/halloween.css new file mode 100644 index 0000000..3239b38 --- /dev/null +++ b/seasonals/halloween.css @@ -0,0 +1,146 @@ +.halloween-container { + display: block; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.halloween { + position: fixed; + bottom: -10%; + z-index: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; + cursor: default; + -webkit-animation-name: halloween-fall, halloween-shake; + -webkit-animation-duration: 10s, 3s; + -webkit-animation-timing-function: linear, ease-in-out; + -webkit-animation-iteration-count: infinite, infinite; + -webkit-animation-play-state: running, running; + animation-name: halloween-fall, halloween-shake; + animation-duration: 10s, 3s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; + animation-play-state: running, running +} + +@-webkit-keyframes halloween-fall { + 0% { + bottom: -10% + } + + 100% { + bottom: 100% + } +} + +@-webkit-keyframes halloween-shake { + + 0%, + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } + + 50% { + -webkit-transform: translateX(80px); + transform: translateX(80px) + } +} + +@keyframes halloween-fall { + 0% { + bottom: -10% + } + + 100% { + bottom: 100% + } +} + +@keyframes halloween-shake { + + 0%, + 100% { + transform: translateX(0) + } + + 50% { + transform: translateX(80px) + } +} + +.halloween:nth-of-type(0) { + left: 1%; + -webkit-animation-delay: 0s, 0s; + animation-delay: 0s, 0s +} + +.halloween:nth-of-type(1) { + left: 10%; + -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 +} + +.halloween:nth-of-type(3) { + left: 30%; + -webkit-animation-delay: 4s, 2s; + animation-delay: 4s, 2s +} + +.halloween:nth-of-type(4) { + left: 40%; + -webkit-animation-delay: 2s, 2s; + animation-delay: 2s, 2s +} + +.halloween:nth-of-type(5) { + left: 50%; + -webkit-animation-delay: 8s, 3s; + animation-delay: 8s, 3s +} + +.halloween:nth-of-type(6) { + left: 60%; + -webkit-animation-delay: 6s, 2s; + animation-delay: 6s, 2s +} + +.halloween:nth-of-type(7) { + left: 70%; + -webkit-animation-delay: 2.5s, 1s; + animation-delay: 2.5s, 1s +} + +.halloween:nth-of-type(8) { + left: 80%; + -webkit-animation-delay: 1s, 0s; + animation-delay: 1s, 0s +} + +.halloween:nth-of-type(9) { + left: 90%; + -webkit-animation-delay: 3s, 1.5s; + animation-delay: 3s, 1.5s +} + +.halloween:nth-of-type(10) { + left: 25%; + -webkit-animation-delay: 2s, 0s; + animation-delay: 2s, 0s +} + +.halloween:nth-of-type(11) { + left: 65%; + -webkit-animation-delay: 4s, 2.5s; + animation-delay: 4s, 2.5s +} \ No newline at end of file diff --git a/seasonals/halloween.js b/seasonals/halloween.js new file mode 100644 index 0000000..2be049a --- /dev/null +++ b/seasonals/halloween.js @@ -0,0 +1,135 @@ +const halloween = true; // enable/disable halloween +const randomSymbols = true; // enable more random symbols +const randomSymbolsMobile = false; // enable random symbols on mobile devices +const enableDiffrentDuration = true; // enable different duration for the random halloween symbols +const halloweenCount = 25; // count of random extra symbols + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the halloween +function toggleHalloween() { + const halloweenContainer = document.querySelector('.snow-container'); + if (!halloweenContainer) 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'); + + // hide halloween if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + halloweenContainer.style.display = 'none'; // hide halloween + if (!msgPrinted) { + console.log('Halloween hidden'); + msgPrinted = true; + } + } else { + halloweenContainer.style.display = 'block'; // show halloween + if (msgPrinted) { + console.log('Halloween visible'); + msgPrinted = false; + } + } +} + +// 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) +}); + + +const images = [ + "./seasonals/halloween_images/ghost_20x20.png", + "./seasonals/halloween_images/bat_20x20.png", + "./seasonals/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 + + console.log('Adding random halloween symbols'); + + + for (let i = 0; i < count; i++) { + // create a new halloween elements + const halloweenDiv = document.createElement("div"); + halloweenDiv.className = "halloween"; + + // pick a random halloween symbol + const imageSrc = images[Math.floor(Math.random() * images.length)]; + const img = document.createElement("img"); + img.src = imageSrc; + + halloweenDiv.appendChild(img); + + + // 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) + + // apply styles + halloweenDiv.style.left = `${randomLeft}%`; + halloweenDiv.style.animationDelay = `${randomAnimationDelay}s, ${randomAnimationDelay2}s`; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + halloweenDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + // add the halloween to the container + halloweenContainer.appendChild(halloweenDiv); + } + console.log('Random halloween symbols added'); +} + +// create halloween objects +function createHalloween() { + const container = document.querySelector('.halloween-container') || document.createElement("div"); + + if (!document.querySelector('.halloween-container')) { + container.className = "halloween-container"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + for (let i = 0; i < 4; i++) { + images.forEach(imageSrc => { + const halloweenDiv = document.createElement("div"); + halloweenDiv.className = "halloween"; + + const img = document.createElement("img"); + img.src = imageSrc; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 10 + 6; // delay (6s to 10s) + const randomAnimationDuration2 = Math.random() * 5 + 2; // delay (2s to 5s) + halloweenDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + halloweenDiv.appendChild(img); + container.appendChild(halloweenDiv); + }); + } +} + +// initialize halloween after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!halloween) return; // exit if halloween is disabled + createHalloween(); + toggleHalloween(); + + 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); + } +}); diff --git a/seasonals/halloween_images/bat_20x20.png b/seasonals/halloween_images/bat_20x20.png new file mode 100644 index 0000000..4ed37d5 Binary files /dev/null and b/seasonals/halloween_images/bat_20x20.png differ diff --git a/seasonals/halloween_images/ghost_20x20.png b/seasonals/halloween_images/ghost_20x20.png new file mode 100644 index 0000000..18c7dc4 Binary files /dev/null and b/seasonals/halloween_images/ghost_20x20.png differ diff --git a/seasonals/halloween_images/lep_30x30.png b/seasonals/halloween_images/lep_30x30.png new file mode 100644 index 0000000..26e95b7 Binary files /dev/null and b/seasonals/halloween_images/lep_30x30.png differ diff --git a/seasonals/halloween_images/pumpkin_20x20.png b/seasonals/halloween_images/pumpkin_20x20.png new file mode 100644 index 0000000..46a77e7 Binary files /dev/null and b/seasonals/halloween_images/pumpkin_20x20.png differ diff --git a/seasonals/hearts.css b/seasonals/hearts.css new file mode 100644 index 0000000..82b669d --- /dev/null +++ b/seasonals/hearts.css @@ -0,0 +1,144 @@ +.hearts-container { + display: block; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.heart { + position: fixed; + bottom: -10%; + z-index: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; + cursor: default; + -webkit-animation-name: heart-fall, heart-shake; + -webkit-animation-duration: 14s, 5s; + -webkit-animation-timing-function: linear, ease-in-out; + -webkit-animation-iteration-count: infinite, infinite; + animation-name: heart-fall, heart-shake; + animation-duration: 14s, 5s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; +} + +@-webkit-keyframes heart-fall { + 0% { + bottom: -10% + } + + 100% { + bottom: 100% + } +} + +@-webkit-keyframes heart-shake { + + 0%, + 100% { + -webkit-transform: translateX(0); + transform: translateX(0) + } + + 50% { + -webkit-transform: translateX(80px); + transform: translateX(80px) + } +} + +@keyframes heart-fall { + 0% { + bottom: -10% + } + + 100% { + bottom: 100% + } +} + +@keyframes heart-shake { + + 0%, + 100% { + transform: translateX(0) + } + + 50% { + transform: translateX(80px) + } +} + +.heart:nth-of-type(0) { + left: 1%; + -webkit-animation-delay: 0s, 0s; + animation-delay: 0s, 0s +} + +.heart:nth-of-type(1) { + left: 10%; + -webkit-animation-delay: 1s, 1s; + animation-delay: 1s, 1s +} + +.heart:nth-of-type(2) { + left: 20%; + -webkit-animation-delay: 6s, .5s; + animation-delay: 6s, .5s +} + +.heart:nth-of-type(3) { + left: 30%; + -webkit-animation-delay: 4s, 2s; + animation-delay: 4s, 2s +} + +.heart:nth-of-type(4) { + left: 40%; + -webkit-animation-delay: 2s, 2s; + animation-delay: 2s, 2s +} + +.heart:nth-of-type(5) { + left: 50%; + -webkit-animation-delay: 8s, 3s; + animation-delay: 8s, 3s +} + +.heart:nth-of-type(6) { + left: 60%; + -webkit-animation-delay: 6s, 2s; + animation-delay: 6s, 2s +} + +.heart:nth-of-type(7) { + left: 70%; + -webkit-animation-delay: 2.5s, 1s; + animation-delay: 2.5s, 1s +} + +.heart:nth-of-type(8) { + left: 80%; + -webkit-animation-delay: 1s, 0s; + animation-delay: 1s, 0s +} + +.heart:nth-of-type(9) { + left: 90%; + -webkit-animation-delay: 3s, 1.5s; + animation-delay: 3s, 1.5s +} + +.heart:nth-of-type(10) { + left: 25%; + -webkit-animation-delay: 2s, 0s; + animation-delay: 2s, 0s +} + +.heart:nth-of-type(11) { + left: 65%; + -webkit-animation-delay: 4s, 2.5s; + animation-delay: 4s, 2.5s +} \ No newline at end of file diff --git a/seasonals/hearts.js b/seasonals/hearts.js new file mode 100644 index 0000000..fd59d3f --- /dev/null +++ b/seasonals/hearts.js @@ -0,0 +1,124 @@ +const hearts = true; // enable/disable hearts +const randomSymbols = true; // enable more random symbols +const randomSymbolsMobile = false; // enable random symbols on mobile devices +const enableDiffrentDuration = true; // enable different animation duration for random symbols +const heartsCount = 25; // count of random extra symbols + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the hearts +function toggleHearts() { + const heartsContainer = document.querySelector('.snow-container'); + if (!heartsContainer) 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'); + + // hide hearts if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + heartsContainer.style.display = 'none'; // hide hearts + if (!msgPrinted) { + console.log('Hearts hidden'); + msgPrinted = true; + } + } else { + heartsContainer.style.display = 'block'; // show hearts + if (msgPrinted) { + console.log('Hearts visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleHearts); + +// 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) +}); + + +// Array of hearts characters +const heartSymbols = ['❤️', '💕', '💞', '💓', '💗', '💖']; + + +function addRandomSymbols(count) { + const heartsContainer = document.querySelector('.hearts-container'); // get the hearts container + if (!heartsContainer) return; // exit if hearts container is not found + + console.log('Adding random heart symbols'); + + for (let i = 0; i < count; i++) { + // create a new hearts elements + const heartsDiv = document.createElement("div"); + heartsDiv.className = "heart"; + + // pick a random hearts symbol + heartsDiv.textContent = heartSymbols[Math.floor(Math.random() * heartSymbols.length)]; + + + // 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() * 14; // delay (0s to 14s) + const randomAnimationDelay2 = Math.random() * 5; // delay (0s to 5s) + + // apply styles + heartsDiv.style.left = `${randomLeft}%`; + heartsDiv.style.animationDelay = `${randomAnimationDelay}s, ${randomAnimationDelay2}s`; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 16 + 12; // delay (12s to 16s) + const randomAnimationDuration2 = Math.random() * 7 + 3; // delay (3s to 7s) + heartsDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + // add the hearts to the container + heartsContainer.appendChild(heartsDiv); + } + console.log('Random hearts symbols added'); +} + +// create hearts objects +function createHearts() { + const container = document.querySelector('.hearts-container') || document.createElement("div"); + + if (!document.querySelector('.hearts-container')) { + container.className = "hearts-container"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + for (let i = 0; i < 12; i++) { + const heartsDiv = document.createElement("div"); + heartsDiv.className = "heart"; + heartsDiv.textContent = heartSymbols[i % heartSymbols.length]; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 16 + 12; // delay (12s to 16s) + const randomAnimationDuration2 = Math.random() * 7 + 3; // delay (3s to 7s) + heartsDiv.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + container.appendChild(heartsDiv); + } +} + + +// initialize hearts after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!hearts) return; // exit if hearts is disabled + createHearts(); + toggleHearts(); + + const screenWidth = window.innerWidth; // get the screen width to detect mobile devices + if (randomSymbols && (screenWidth > 768 || randomSymbolsMobile)) { // add random heartss only on larger screens, unless enabled for mobile devices + addRandomSymbols(heartsCount); + } +}); diff --git a/seasonals.js b/seasonals/seasonals.js similarity index 100% rename from seasonals.js rename to seasonals/seasonals.js diff --git a/seasonals/snowfall.css b/seasonals/snowfall.css new file mode 100644 index 0000000..8e8f25c --- /dev/null +++ b/seasonals/snowfall.css @@ -0,0 +1,8 @@ +.snowflake { + position: absolute; + background-color: white; + /* background-color: rgba(255, 255, 255, 0.8); */ + border-radius: 50%; + pointer-events: none; + /* opacity: 0.7; */ +} \ No newline at end of file diff --git a/seasonals/snowfall.js b/seasonals/snowfall.js new file mode 100644 index 0000000..c9c55b2 --- /dev/null +++ b/seasonals/snowfall.js @@ -0,0 +1,106 @@ +const snowfall = true; // enable/disable snowfall +const snowflakesCount = 500; // count of snowflakes (recommended values: 300-600) +const snowFallSpeed = 3; // speed of snowfall (recommended values: 0-5) + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the snowfall +function toggleSnowfall() { + const snowfallContainer = document.querySelector('.snowfall'); + if (!snowfallContainer) 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'); + + // hide snowfall if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + snowfallContainer.style.display = 'none'; // hide snowfall + if (!msgPrinted) { + console.log('Snowfall hidden'); + msgPrinted = true; + } + } else { + snowfallContainer.style.display = 'block'; // show snowfall + if (msgPrinted) { + console.log('Snowfall visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleSnowfall); + +// 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) +}); + + +function createSnowflakes() { + const container = document.querySelector('.snowfall') || document.createElement("div"); + + if (!document.querySelector('.snowfall')) { + container.className = "snowfall"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + for (let i = 0; i < snowflakesCount; i++) { + const snowflake = document.createElement('div'); + snowflake.classList.add('snowflake'); + + // random size between 1 and 3 pixels + const size = Math.random() * 3 + 1; + snowflake.style.width = `${size}px`; + snowflake.style.height = `${size}px`; + + // random starting position + snowflake.style.left = `${Math.random() * windowWidth}px`; + snowflake.style.top = `${Math.random() * windowHeight}px`; + + container.appendChild(snowflake); + + animateSnowflake(snowflake); + } +} + +function animateSnowflake(snowflake) { + // Animation Parameter + const speed = Math.random() * snowFallSpeed + 1; + // const speed = Math.random() * 3 + 1; + const sidewaysMovement = Math.random() * 2 - 1; + + function fall() { + const currentTop = parseFloat(snowflake.style.top || 0); + const currentLeft = parseFloat(snowflake.style.left || 0); + + // fall and sideways movement + snowflake.style.top = `${currentTop + speed}px`; + snowflake.style.left = `${currentLeft + sidewaysMovement}px`; + + // if snowflake is out of the window, reset its position + if (currentTop > window.innerHeight) { + snowflake.style.top = '0px'; + snowflake.style.left = `${Math.random() * window.innerWidth}px`; + } + + requestAnimationFrame(fall); + } + + fall(); +} + +// initialize snowfall after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!snowfall) return; // exit if snowfall is disabled + createSnowflakes(); +}); diff --git a/seasonals/snowflakes.css b/seasonals/snowflakes.css new file mode 100644 index 0000000..e898e7d --- /dev/null +++ b/seasonals/snowflakes.css @@ -0,0 +1,132 @@ +.snowflakes { + display: block; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.snowflake { + position: fixed; + top: -10%; + font-size: 1em; + color: #fff; + font-family: Arial, sans-serif; + text-shadow: 0 0 5px #000; + user-select: none; + -webkit-user-select: none; + cursor: default; + -webkit-animation-name: heart-fall, heart-shake; + -webkit-animation-duration: 12s, 3s; + -webkit-animation-timing-function: linear, ease-in-out; + -webkit-animation-iteration-count: infinite, infinite; + animation-name: snowflakes-fall, snowflakes-shake; + animation-duration: 12s, 3s; + animation-timing-function: linear, ease-in-out; + animation-iteration-count: infinite, infinite; +} + +@-webkit-keyframes snowflakes-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@-webkit-keyframes snowflakes-shake { + + 0%, + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 50% { + -webkit-transform: translateX(80px); + transform: translateX(80px); + } +} + +@keyframes snowflakes-fall { + 0% { + top: -10%; + } + + 100% { + top: 100%; + } +} + +@keyframes snowflakes-shake { + + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(80px); + } +} + +.snowflake:nth-of-type(0) { + left: 0%; + animation-delay: 0s, 0s; +} + +.snowflake:nth-of-type(1) { + left: 10%; + animation-delay: 1s, 1s; +} + +.snowflake:nth-of-type(2) { + left: 20%; + animation-delay: 6s, 0.5s; +} + +.snowflake:nth-of-type(3) { + left: 30%; + animation-delay: 4s, 2s; +} + +.snowflake:nth-of-type(4) { + left: 40%; + animation-delay: 2s, 2s; +} + +.snowflake:nth-of-type(5) { + left: 50%; + animation-delay: 8s, 3s; +} + +.snowflake:nth-of-type(6) { + left: 60%; + animation-delay: 6s, 2s; +} + +.snowflake:nth-of-type(7) { + left: 70%; + animation-delay: 2.5s, 1s; +} + +.snowflake:nth-of-type(8) { + left: 80%; + animation-delay: 1s, 0s; +} + +.snowflake:nth-of-type(9) { + left: 90%; + animation-delay: 3s, 1.5s; +} + +.snowflake:nth-of-type(10) { + left: 25%; + animation-delay: 2s, 0s; +} + +.snowflake:nth-of-type(11) { + left: 65%; + animation-delay: 4s, 2.5s; +} \ No newline at end of file diff --git a/seasonals/snowflakes.js b/seasonals/snowflakes.js new file mode 100644 index 0000000..a3d3dc9 --- /dev/null +++ b/seasonals/snowflakes.js @@ -0,0 +1,130 @@ +const snowflakes = true; // enable/disable snowflakes +const randomSnowflakes = true; // enable random Snowflakes +const randomSnowflakesMobile = false; // enable random Snowflakes on mobile devices +const enableColoredSnowflakes = true; // enable colored snowflakes on mobile devices +const enableDiffrentDuration = true; // enable different animation duration for random symbols +const snowflakeCount = 25; // count of random extra snowflakes + + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the snowflakes +function toggleSnowflakes() { + const snowflakeContainer = document.querySelector('.snowflakes'); + if (!snowflakeContainer) 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'); + + // hide snowflakes if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + snowflakeContainer.style.display = 'none'; // hide snowflakes + if (!msgPrinted) { + console.log('Snowflakes hidden'); + msgPrinted = true; + } + } else { + snowflakeContainer.style.display = 'block'; // show snowflakes + if (msgPrinted) { + console.log('Snowflakes visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleSnowflakes); + +// 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) +}); + +function addRandomSnowflakes(count) { + const snowflakeContainer = document.querySelector('.snowflakes'); // get the snowflake container + if (!snowflakeContainer) return; // exit if snowflake container is not found + + console.log('Adding random snowflakes'); + + const snowflakeSymbols = ['❅', '❆']; // some snowflake symbols + const snowflakeSymbolsMobile = ['❅', '❆', '❄']; // some snowflake symbols mobile version + + for (let i = 0; i < count; i++) { + // create a new snowflake element + const snowflake = document.createElement('div'); + snowflake.classList.add('snowflake'); + + // pick a random snowflake symbol + if (enableColoredSnowflakes) { + snowflake.textContent = snowflakeSymbolsMobile[Math.floor(Math.random() * snowflakeSymbolsMobile.length)]; + } else { + snowflake.textContent = snowflakeSymbols[Math.floor(Math.random() * snowflakeSymbols.length)]; + } + + // 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() * 8; // delay (0s to 8s) + const randomAnimationDelay2 = Math.random() * 5; // delay (0s to 5s) + + // apply styles + snowflake.style.left = `${randomLeft}%`; + snowflake.style.animationDelay = `${randomAnimationDelay}s, ${randomAnimationDelay2}s`; + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 14 + 10; // delay (10s to 14s) + const randomAnimationDuration2 = Math.random() * 5 + 3; // delay (3s to 5s) + snowflake.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + // add the snowflake to the container + snowflakeContainer.appendChild(snowflake); + } + console.log('Random snowflakes added'); +} + +// initialize standard snowflakes +function initSnowflakes() { + const snowflakesContainer = document.querySelector('.snowflakes') || document.createElement("div"); + + if (!document.querySelector('.snowflakes')) { + snowflakesContainer.className = "snowflakes"; + snowflakesContainer.setAttribute("aria-hidden", "true"); + document.body.appendChild(snowflakesContainer); + } + + // Array of snowflake characters + const snowflakeSymbols = ['❅', '❆']; + + // create the 12 standard snowflakes + for (let i = 0; i < 12; i++) { + const snowflake = document.createElement('div'); + snowflake.className = 'snowflake'; + snowflake.textContent = snowflakeSymbols[i % 2]; // change between ❅ and ❆ + + // set random animation duration + if (enableDiffrentDuration) { + const randomAnimationDuration = Math.random() * 14 + 10; // delay (10s to 14s) + const randomAnimationDuration2 = Math.random() * 5 + 3; // delay (3s to 5s) + snowflake.style.animationDuration = `${randomAnimationDuration}s, ${randomAnimationDuration2}s`; + } + + snowflakesContainer.appendChild(snowflake); + } +} + +// initialize snowflakes and add random snowflakes after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!snowflakes) return; // exit if snowflakes are disabled + initSnowflakes(); + toggleSnowflakes(); + + const screenWidth = window.innerWidth; // get the screen width to detect mobile devices + if (randomSnowflakes && (screenWidth > 768 || randomSnowflakesMobile)) { // add random snowflakes only on larger screens, unless enabled for mobile devices + addRandomSnowflakes(snowflakeCount); + } +}); \ No newline at end of file diff --git a/seasonals/snowstorm.css b/seasonals/snowstorm.css new file mode 100644 index 0000000..a2057cb --- /dev/null +++ b/seasonals/snowstorm.css @@ -0,0 +1,7 @@ +.snowflake { + position: absolute; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 50%; + pointer-events: none; + opacity: 0.7; +} \ No newline at end of file diff --git a/seasonals/snowstorm.js b/seasonals/snowstorm.js new file mode 100644 index 0000000..2b0925e --- /dev/null +++ b/seasonals/snowstorm.js @@ -0,0 +1,104 @@ +const snowstorm = true; // enable/disable snowstorm +const snowflakesCount = 500; // count of snowflakes (recommended values: 300-600) +const snowFallSpeed = 4; // speed of snowfall (recommended values: 0-8) + +let msgPrinted = false; // flag to prevent multiple console messages + +// function to check and control the snowstorm +function toggleSnowstorm() { + const snowstormContainer = document.querySelector('.snowstorm'); + if (!snowstormContainer) 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'); + + // hide snowstorm if video/trailer player is active or dashboard is visible + if (videoPlayer || trailerPlayer || isDashboard || hasUserMenu) { + snowstormContainer.style.display = 'none'; // hide snowstorm + if (!msgPrinted) { + console.log('Snowstorm hidden'); + msgPrinted = true; + } + } else { + snowstormContainer.style.display = 'block'; // show snowstorm + if (msgPrinted) { + console.log('Snowstorm visible'); + msgPrinted = false; + } + } +} + +// observe changes in the DOM +const observer = new MutationObserver(toggleSnowstorm); + +// 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) +}); + + +function createSnowstorm() { + const container = document.querySelector('.snowstorm') || document.createElement("div"); + + if (!document.querySelector('.snowstorm')) { + container.className = "snowstorm"; + container.setAttribute("aria-hidden", "true"); + document.body.appendChild(container); + } + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + for (let i = 0; i < snowflakesCount; i++) { + const snowflake = document.createElement('div'); + snowflake.classList.add('snowflake'); + + // random size + const size = Math.random() * 4 + 1; + snowflake.style.width = `${size}px`; + snowflake.style.height = `${size}px`; + + // random starting position + snowflake.style.left = `${Math.random() * windowWidth}px`; + snowflake.style.top = `${Math.random() * windowHeight}px`; + + container.appendChild(snowflake); + + animateSnowflake(snowflake); + } +} + +function animateSnowflake(snowflake) { + // animation parameters + const fallSpeed = Math.random() * snowFallSpeed + 2; + const horizontalWind = Math.random() * 4 - 2; + const verticalVariation = Math.random() * 4 - 1; + + function fall() { + const currentTop = parseFloat(snowflake.style.top || 0); + const currentLeft = parseFloat(snowflake.style.left || 0); + + snowflake.style.top = `${currentTop + fallSpeed + verticalVariation}px`; + snowflake.style.left = `${currentLeft + horizontalWind}px`; + + // if snowflake is out of the window, reset its position + if (currentTop > window.innerHeight) { + snowflake.style.top = '0px'; + snowflake.style.left = `${Math.random() * window.innerWidth}px`; + } + + requestAnimationFrame(fall); + } + + fall(); +} + +// initialize snowstorm after the DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + if (!snowstorm) return; // exit if snowstorm is disabled + createSnowstorm(); +}); diff --git a/seasonalsWithClass.js b/seasonalsWithClass.js deleted file mode 100644 index 1d73f13..0000000 --- a/seasonalsWithClass.js +++ /dev/null @@ -1,92 +0,0 @@ -// seasonals.js -class SeasonalThemes { - constructor() { - this.currentTheme = this.determineCurrentTheme(); - this.loadTheme(); - } - - determineCurrentTheme() { - const date = new Date(); - const month = date.getMonth(); - - // theme logic - if (month === 11 || month === 0 || month === 1) return 'winter'; // december, january, february - if (month >= 2 && month <= 4) return 'spring'; - if (month >= 5 && month <= 7) return 'summer'; - if (month >= 8 && month <= 10) return 'autumn'; - - return ''; // Fallback (nothing) - } - - loadTheme() { - const themeConfigs = { - snowflakes: { - css: 'snowflakes.css', - js: 'snowflakes.js', - containerClass: 'snowflakes' - }, - snowfall: { - js: 'snowfall.js', - containerClass: 'snowfall' - }, - snowstorm: { - css: 'snowstorm.css', - js: 'snowstorm.js', - containerClass: 'snowstorm' - }, - fireworks: { - css: 'fireworks.css', - js: 'fireworks.js', - containerClass: 'fireworks' - }, - haloween: { - css: 'haloween.css', - js: 'haloween.js', - containerClass: 'haloween' - }, - summer: { - css: 'summer.css', - js: 'summer.js', - containerClass: 'summer' - }, - autumn: { - css: 'autumn.css', - js: 'autumn.js', - containerClass: 'autumn' - }, - spring: { - css: 'spring.css', - js: 'spring.js', - containerClass: 'spring' - }, - }; - - const theme = themeConfigs[this.currentTheme]; - - // load css dynamically - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = theme.css; - document.head.appendChild(link); - - // load js dynamically - const script = document.createElement('script'); - script.src = theme.js; - document.body.appendChild(script); - - // add container class with dynamic theme class - // const container = document.createElement('div'); - // container.className = `seasonals ${theme.containerClass}`; - // document.body.appendChild(container); - - // reclassify existing container - const container = document.querySelector('.seasonals-container'); - container.className = `${theme.containerClass}`; - // container.className = theme.containerClass; - } -} - -// Initialisierung beim Laden der Seite -document.addEventListener('DOMContentLoaded', () => { - new SeasonalThemes(); -}); \ No newline at end of file