# ![Alt Tag](https://files.catbox.moe/ph1rgd.gif){50px:50px} Seia DVD ![Alt Tag](https://files.catbox.moe/ph1rgd.gif){50px:50px} ### What is this? A user script that adds a few funny widgets to the 4chan thread page. Such as: - Some buttons for spawning seias all over the thread. - Two clocks that displays the time in the time zones both global and JP servers use. - Lists upcoming character birthdays. *** ### How do I use it? ###### One time use (w/ Browser Console) 1) Copy the script code below (there should be a copy button on top right of the script section). 2) Go to the 4chan thread. 3) Open your browser dev tools. 4) Go to the "Console" tab. 5) Paste it. (If the browser doesn't allow you to paste anything then just type "allow pasting" first) 6) Press "Enter". ###### Always Available (w/ Userscript Manager) 0) Make sure you have a Tampermonkey/Violentmonkey/Greasemonkey extension installed. (howto assumes tamper but it should be the same) 1) Copy the script code below (there should be a copy button on top right of the script section). 2) Open your dashboard. 3) [+] (Add a new script) 4) Paste it. 5) Save. 6. %#808080% You can turn on updates in the settings to make sure the script is updated whenever something new is added. (Optional) %% *** ### Script !!! info Moving the curson over the code below will display a copy icon on the top right of the code, click it to copy the entire script code. ``` // -------------- START OF CODE -------------- // ==UserScript== // @name DVDoom // @namespace http://tampermonkey.net/ // @version 3.3.0 // @description Changes in 3.3.0: Testing new banners for quick links. // @author Seianon and Mimorianon // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @namespace rccom // @match *://boards.4chan.org/*/thread/* // @grant none // @connect 4chan.org // @connect 4channel.org // @updateURL https://rentry.org/DVDoomSCRIPT/raw // @downloadURL https://rentry.org/DVDoomSCRIPT/raw // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // ==/UserScript== (async function () { // Inject styles const styleElement = document.createElement('style'); styleElement.textContent = ` .image-container { display: flex; align-items: center; justify-content: center; height: 60px; margin: auto; } .image-container img { max-width: 100%; max-height: 100%; display: block; } .centered-text { text-align: center; margin: 0; font-size: 14px; line-height: 20px; height: 20px; overflow: hidden; } .student-name { display: block; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .seia-heart { display: inline-block; width: 200px; aspect-ratio: 1; border-image: radial-gradient(red 69%, #0000 70%) 84.5%/50%; clip-path: polygon(-41% 0, 50% 91%, 141% 0); } @keyframes seia-heart-animation { from { transform: translate3d(0, 0, 0); opacity: 0.65; } } .seia-text { font: 800 20px Arial; -webkit-text-fill-color: black; -webkit-text-stroke: 1px; -webkit-text-stroke-color: white} .quantity { display: flex; align-items: center; justify-content: center; padding: 0; } .quantity__minus, .quantity__plus { display: block; flex-grow: 1; width: 0; height: 23px; margin: 0; background: #dee0ee; text-decoration: none; text-align: center; line-height: 23px; } .seia-button { display: block; margin: 0; background: #dee0ee; text-decoration: none; text-align: center; line-height: 23px; } .seia-button:hover { cursor: pointer; background: #575b71; color: currentColor !important; } .quantity__minus:hover, .quantity__plus:hover { cursor: pointer; background: #575b71; color: #fff !important; } .quantity__minus { border-radius: 3px 0 0 3px; -webkit-user-select: none; -moz-user-select: none; -khtml-user-select: none; -ms-user-select: none; } .quantity__plus { border-radius: 0 3px 3px 0; -webkit-user-select: none; -moz-user-select: none; -khtml-user-select: none; -ms-user-select: none; } .quantity__input { width: 40px; height: 19px; margin: 0; padding: 0; text-align: center; border-top: 2px solid #dee0ee; border-bottom: 2px solid #dee0ee; border-left: 1px solid #dee0ee; border-right: 2px solid #dee0ee; background: #fff; color: #8184a1; } input.quantity__input[type=number] { -moz-appearance: textfield; } input.quantity__input::-webkit-outer-spin-button, input.quantity__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .quantity__minus:link, .quantity__plus:link { color: #8184a1; } .quantity__minus:visited, .quantity__plus:visited { color: #fff; } `; document.head.appendChild(styleElement); // Set up DOM elements const fragment = document.createDocumentFragment(); const seiaEnclosure = document.createElement('div'); seiaEnclosure.id = 'seiaEnclosure'; const dvDoomParentDiv = document.createElement('div'); dvDoomParentDiv.id = 'DVDoomParent'; dvDoomParentDiv.style.display = 'flex'; dvDoomParentDiv.style.justifyContent = 'space-between'; // Append these divs to the fragment fragment.appendChild(dvDoomParentDiv); fragment.appendChild(seiaEnclosure); // Prepend the fragment to the .thread element const threadElement = document.getElementsByClassName("navLinks desktop")[0]; threadElement.parentNode.insertBefore(fragment, threadElement); // Adding a horizontal rule below the parent div if needed dvDoomParentDiv.insertAdjacentHTML('afterend', '
'); // Constants const DEFAULT_SIZE = 75; const DEFAULT_SPEED = 2; const MAX_TRAIL_LENGTH = 25; // Dynamic window size handling let screenWidth = window.innerWidth; let screenHeight = window.innerHeight; window.addEventListener('resize', () => { screenWidth = window.innerWidth; screenHeight = window.innerHeight; }); // End of Service flag let EOS = false; // Seia management const SEIA_TYPE_MAP = new Map(); const SEIA_STRING_ENUM = {}; // Function used for integer randomization. function randomInt(min, max) { return Math.floor(Math.random() * max) + min; } // Used for image caching. const imageURLMap = { default: 'https://files.catbox.moe/ph1rgd.gif', defaultHearts: 'https://files.catbox.moe/0nujy7.gif', blush: 'https://files.catbox.moe/oz1irf.gif', blushHearts: 'https://files.catbox.moe/h6qcnz.gif', shiny: 'https://files.catbox.moe/p23xa2.gif', hole: 'https://files.catbox.moe/ri3pe7.png' }; // Preloaded images will be stored here // Maybe we don't even need to use base 64? const imageCache = {}; ////////////// /// Seia collision logic const COORDINATE_LENGTH = 500; const SEIA_COORDINATE_MAP = []; const CoordinatesListItem = (superclass) => class CoordinatesListItem extends superclass { constructor(...args) { super(...args); this.currentCoordinate = Math.floor(this.positionY / COORDINATE_LENGTH); const currentHead = SEIA_COORDINATE_MAP[this.currentCoordinate]; if(currentHead) { currentHead.prev = this; this.next = currentHead; } SEIA_COORDINATE_MAP[this.currentCoordinate] = this; this.prev = null; } get mass() { var density = 1; return density * this.elementSize * this.elementSize; } get v() { return [this.directionX, this.directionY]; } dettach() { const currentHead = SEIA_COORDINATE_MAP[this.currentCoordinate]; const nextNode = this.next; const prevNode = this.prev; this.next = null; this.prev = null; if (currentHead === this) SEIA_COORDINATE_MAP[this.currentCoordinate] = nextNode; if (nextNode) nextNode.prev = prevNode; if (prevNode) prevNode.next = nextNode; } attach() { const currentHead = SEIA_COORDINATE_MAP[this.currentCoordinate]; if(currentHead) { currentHead.prev = this; this.next = currentHead; } SEIA_COORDINATE_MAP[this.currentCoordinate] = this; this.prev = null; } updateCoordinatePosition() { // Obtain the next coordinate. const nextCoordinate = Math.floor(this.positionY / COORDINATE_LENGTH); // Check if it has changed. if ((nextCoordinate != this.currentCoordinate) && (nextCoordinate >= 0)) { // console.log("Changing " + this.id); this.dettach(); this.currentCoordinate = nextCoordinate; this.attach(); } } collision(seiaStart) { let other = seiaStart; while(other) { var dx = this.positionX - other.positionX; var dy = this.positionY - other.positionY; if (Math.sqrt(dx * dx + dy * dy) < ((this.elementSize / 2) + (other.elementSize / 2))) { var res = [this.directionX - other.directionX, this.directionY - other.directionY]; if (res[0] *(other.positionX - this.positionX) + res[1] * (other.positionY - this.positionY) >= 0 ) { var m1 = this.mass var m2 = other.mass var theta = -Math.atan2(other.positionY - this.positionY, other.positionX - this.positionX); var v1 = rotate(this.v, theta); var v2 = rotate(other.v, theta); var u1 = rotate([v1[0] * (m1 - m2)/(m1 + m2) + v2[0] * 2 * m2/(m1 + m2), v1[1]], -theta); var u2 = rotate([v2[0] * (m2 - m1)/(m1 + m2) + v1[0] * 2 * m1/(m1 + m2), v2[1]], -theta); this.directionX = u1[0]; this.directionY = u1[1]; this.facing = (this.directionX > 0 ? 1 : -1); other.directionX = u2[0]; other.directionY = u2[1]; other.facing = (other.directionX > 0 ? 1 : -1); if (this.hue !== null) { if (other.hue === null) { other.hue = this.hue; } else if (this.hue !== other.hue) { other.hue = this.hue = null; } } else { if (other.hue !== null) { this.hue = other.hue; } } this.syncUI(); other.syncUI(); } } other = other.next; } } // Method to destroy a DVDoom instance. destroy() { // Remove the element. this.dettach(); // Continue the removal. super.destroy(); } } async function preloadImages() { const loadImageAsBase64 = async (url) => { const response = await fetch(url); const blob = await response.blob(); return new Promise((resolve) => { resolve(window.URL.createObjectURL(blob)); }); }; for (const [key, url] of Object.entries(imageURLMap)) { imageCache[key] = await loadImageAsBase64(url); } } // Ensure images are preloaded before continuing await preloadImages(); // Mouse position tracking function debounce(func, wait) { let timeout; return function () { const context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } const mousePos = { x: 0, y: 0 }; document.addEventListener('mousemove', (event) => { mousePos.x = event.clientX; mousePos.y = event.clientY; }); function checkCollisions(seiaArray) { for (let i = 0; i < seiaArray.length; i++) { for (let j = i + 1; j < seiaArray.length; j++) { const seia1 = seiaArray[i]; const seia2 = seiaArray[j]; if (seia1.isHeld || seia2.isHeld || seia1.type === 'game' || seia2.type === 'game') { continue; } const dx = seia2.positionX - seia1.positionX; const dy = seia2.positionY - seia1.positionY; const distanceSquared = dx * dx + dy * dy; const minDist = seia1.elementSize / 2 + seia2.elementSize / 2; const minDistSquared = minDist * minDist; if (distanceSquared < minDistSquared) { const distance = Math.sqrt(distanceSquared); // Calculate sqrt only if there's a collision const overlap = minDist - distance; const nx = dx / distance; const ny = dy / distance; const separation = overlap / 10; const sx = nx * separation; const sy = ny * separation; seia1.positionX -= sx / 2; seia1.positionY -= sy / 2; seia2.positionX += sx / 2; seia2.positionY += sy / 2; const v1 = { x: seia1.directionX, y: seia1.directionY }; const v2 = { x: seia2.directionX, y: seia2.directionY }; seia1.directionX = v2.x; seia1.directionY = v2.y; seia2.directionX = v1.x; seia2.directionY = v1.y; seia1.syncUI(); seia2.syncUI(); } } } } /** * Rotates a point or velocity vector around the origin. * @param {number} x The x-coordinate of the point/vector to rotate. * @param {number} y The y-coordinate of the point/vector to rotate. * @param {number} sin The precomputed sine of the angle to rotate. * @param {number} cos The precomputed cosine of the angle to rotate. * @param {boolean} reverse If true, rotates counterclockwise; otherwise, clockwise. * @returns {{x: number, y: number}} The rotated point/vector. */ // So, the funny thing is I declared this but never read it anywhere. // I'll leave it here in case you have another Seia idea that involes rotation! function rotation(x, y, sin, cos, reverse) { return { x: (reverse) ? (x * cos + y * sin) : (x * cos - y * sin), y: (reverse) ? (y * cos - x * sin) : (y * cos + x * sin) }; } function rotate(v, theta) { return [v[0] * Math.cos(theta) - v[1] * Math.sin(theta), v[0] * Math.sin(theta) + v[1] * Math.cos(theta)]; } // Use a function to set multiple styles at once to reduce layout thrashing const setStyles = (elem, styles) => { Object.assign(elem.style, styles); }; //////////////////////////////////////////////////// /////////////////// SEIA CLASSES /////////////////// //////////////////////////////////////////////////// //////////////////// Base // Base class to be extended by every type of Seia. class DVDoom { constructor(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing, type) { // Base UI fields. this.elementSize = elementSize; this.positionX = positionX; this.positionY = positionY; this.hue = hue; this.background = background; this.facing = facing; // Movement fields. this.directionX = directionX; this.directionY = directionY; this.maxSpeed = speed; this.speed = speed; // Other fields. this.eos = false; this.type = type; this.collisionCooldown = 0; // Setup of the DVDoom's UI view. this.htmlElement = document.createElement('div'); this.htmlElement.className = 'doomvdoom'; let filterType = (hue === null) ? `grayscale(1)` : `hue-rotate(${hue}deg)`; setStyles(this.htmlElement, { transform: `scaleX(${facing})`, position: 'absolute', left: `${positionX}px`, top: `${positionY}px`, height: `${elementSize}px`, width: `${elementSize}px`, filter: filterType, background: `url("${background}")`, pointerEvents: 'none', backgroundSize: 'cover', maskMode: 'luminance' }); } triggerCollisionCooldown(frames) { this.collisionCooldown = frames; } // Reflect changes in the DVDoom instance to the UI. syncUI() { if (this.collisionCooldown > 0) { this.collisionCooldown--; // Decrement cooldown timer return; // Skip movement if cooling down } this.htmlElement.style.left = `${this.positionX}px`; this.htmlElement.style.top = `${this.positionY}px`; } // Method to adjust a DVDoom instance per tick. adjust() { // Return true. return true; } // Method to destroy a DVDoom instance. destroy() { // Remove the element. this.htmlElement.remove(); // Ensure the element was marked for deletion. this.eos = true; } // Factory method to create a DVDoom instance, implementation should be provided based on specific use case. static create() { return null; } // Method to handle same class collisions. static handleCollisions() {} } //////////////////// Mixins // Class containing all cursor reaction logic, by extending this class a Seia is able to interact with the cursor. class DVDoomCursorMixin extends DVDoom { constructor(...args){ // Ensure the upper class is properly set up. super(...args); // Mouse related variables. this.launchCooldown = 0; this.mouseCollisionCooldown = 0; this.isDragging = false; this.velocityX = 0; this.velocityY = 0; this.isHeld = false; // Create the listeners functions. this.boundMouseDown = this.handleMouseDown.bind(this); this.boundMouseMove = this.handleMouseMove.bind(this); this.boundMouseUp = this.handleMouseUp.bind(this); // Add all necessary listeners. this.htmlElement.addEventListener('mousedown', this.boundMouseDown); // Set the element style. setStyles(this.htmlElement, { pointerEvents: 'auto', }); } handleMouseDown(event) { if (event.target === this.htmlElement) { this.isDragging = true; this.isHeld = true; this.directionX = 0; this.directionY = 0; this.dragStartX = event.clientX; this.dragStartY = event.clientY; document.addEventListener('mousemove', this.boundMouseMove); document.addEventListener('mouseup', this.boundMouseUp); } } handleMouseMove(event) { if (this.isDragging) { let deltaX = event.clientX - this.dragStartX; let deltaY = event.clientY - this.dragStartY; // Update position and velocities this.positionX += deltaX; this.positionY += deltaY; this.velocityX = deltaX; this.velocityY = deltaY; this.syncUI(); // Reset drag start positions for next calculation this.dragStartX = event.clientX; this.dragStartY = event.clientY; } } handleMouseUp() { this.isDragging = false; this.isHeld = false; this.launchSeia(this.velocityX, this.velocityY); // Remove mouse event listeners when not dragging document.removeEventListener('mousemove', this.boundMouseMove); document.removeEventListener('mouseup', this.boundMouseUp); } // Might way to have it gradually lose speed at some point // But for now let's have fun with it launchSeia(deltaX, deltaY) { // Use a constant threshold for 'significant' drag const significantDragThreshold = 10; const dragNumber = Math.hypot(deltaX, deltaY); if (dragNumber < significantDragThreshold) { return; // Ignore insignificant drags } // Launch the seia by updating its direction this.directionX = deltaX / dragNumber; this.directionY = deltaY / dragNumber; this.speed = dragNumber / significantDragThreshold; // Set cooldowns to avoid immediate re-collision this.launchCooldown = 30; this.mouseCollisionCooldown = 60; } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Skip adjustments if the seia is held or cooldowns are active if (this.isHeld) return false; if (this.launchCooldown > 0 || this.mouseCollisionCooldown > 0) { this.launchCooldown--; this.mouseCollisionCooldown--; } if (this.mouseCollisionCooldown == 0 && this.type !== 'game') { const dx = mousePos.x - (this.positionX + this.elementSize / 2); const dy = (mousePos.y + window.scrollY) - (this.positionY + this.elementSize / 2); const distanceSquared = dx * dx + dy * dy; const collisionRadiusSquared = Math.max(100, (this.elementSize / 2) * (this.elementSize / 2)); // Compare with the square of the collision radius if (distanceSquared < collisionRadiusSquared) { const collisionRadius = Math.sqrt(collisionRadiusSquared); // Calculate sqrt only if needed this.directionX = -dx / collisionRadius; this.directionY = -dy / collisionRadius; this.mouseCollisionCooldown = 60; // Reset cooldown to prevent immediate re-collision return false; } } if (this.speed > this.maxSpeed) { this.speed *= 0.99; } return true; } destroy() { // Remove all listeners. document.removeEventListener('mousedown', this.boundMouseDown); // Continue its deconstruction. super.destroy() } } // Class containing all cursor reaction logic, by extending this class a Seia is able to interact with the cursor. class DVDoomCursorDragMixin extends DVDoom { constructor(...args){ // Ensure the upper class is properly set up. super(...args); // Mouse related variables. this.isDragging = false; this.velocityX = 0; this.velocityY = 0; this.isHeld = false; // Create the listeners functions. this.boundMouseDown = this.handleMouseDown.bind(this); this.boundMouseMove = this.handleMouseMove.bind(this); this.boundMouseUp = this.handleMouseUp.bind(this); // Add all necessary listeners. this.htmlElement.addEventListener('mousedown', this.boundMouseDown); // Set the element style. setStyles(this.htmlElement, { pointerEvents: 'auto', }); } handleMouseDown(event) { if (event.target === this.htmlElement) { this.isDragging = true; this.isHeld = true; this.directionX = 0; this.directionY = 0; this.dragStartX = event.clientX; this.dragStartY = event.clientY; event.preventDefault(); document.addEventListener('mousemove', this.boundMouseMove); document.addEventListener('mouseup', this.boundMouseUp); } } handleMouseMove(event) { if (this.isDragging) { let deltaX = ( Math.min(Math.max(this.elementSize, event.clientX), (screenWidth - (this.elementSize))) - Math.min(Math.max(this.elementSize, this.dragStartX), (screenWidth - (this.elementSize))) ); let deltaY = ( Math.min(Math.max(this.elementSize, event.clientY + scrollY), (screenHeight - this.elementSize)) - Math.min(Math.max(this.elementSize, this.dragStartY + scrollY), (screenHeight - this.elementSize)) ); // Update position and velocities this.positionX += deltaX; this.positionY += deltaY; this.velocityX = deltaX; this.velocityY = deltaY; this.speed += Math.hypot(deltaX, deltaY); this.speed *= 0.9; this.syncUI(); // Reset drag start positions for next calculation this.dragStartX = event.clientX; this.dragStartY = event.clientY; } } handleMouseUp() { // Remove mouse event listeners when not dragging document.removeEventListener('mousemove', this.boundMouseMove); document.removeEventListener('mouseup', this.boundMouseUp); this.isDragging = false; this.isHeld = false; this.launchSeia(this.velocityX, this.velocityY); this.velocityX = 0; this.velocityY = 0; } // Might way to have it gradually lose speed at some point // But for now let's have fun with it launchSeia(deltaX, deltaY) { // Use a constant threshold for 'significant' drag const significantDragThreshold = 10; const dragNumber = Math.hypot(deltaX, deltaY); if (dragNumber < significantDragThreshold) { this.speed = 0; return; // Ignore insignificant drags } // Launch the seia by updating its direction this.directionX = deltaX / dragNumber; this.directionY = deltaY / dragNumber; this.facing = this.directionX > 0 ? 1 : -1; this.speed /= significantDragThreshold; } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Skip adjustments if the seia is held or cooldowns are active if (this.isHeld) return false; if (this.speed > this.maxSpeed) { this.speed *= 0.99; } return true; } destroy() { // Remove all listeners. document.removeEventListener('mousedown', this.boundMouseDown); // Continue its deconstruction. super.destroy() } } //////////////////// Seias // Seia => a more erratic version of the classic Seia that can bounce unpredictably with various sizes and speeds. class DVDoomRE extends DVDoom { adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Use a helper function to handle the bounce logic this.handleBounce(); // Directly update styles using the setStyles function setStyles(this.htmlElement, { left: `${this.positionX}px`, top: `${this.positionY}px`, filter: `hue-rotate(${this.hue}deg)`, transform: `scaleX(${this.facing})` }); return true; } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Update the direction. this.htmlElement.style["transform"] = `scaleX(${this.facing})`; } handleBounce() { if (((this.positionY + (this.elementSize * 1.3)) >= screenHeight) && this.directionY > 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); this.hue = Math.floor(Math.random() * 360); // Randomly adjust the hue } else if ((this.positionY < 0) && this.directionY < 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); this.hue = Math.floor(Math.random() * 360); // Randomly adjust the hue } if (((this.positionX + (this.elementSize * 1.3)) >= screenWidth) && this.directionX > 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); this.hue = Math.floor(Math.random() * 360); // Randomly adjust the hue this.facing = -1; // Flip the scaleX value } else if ((this.positionX < 0)&& this.directionX < 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); this.hue = Math.floor(Math.random() * 360); // Randomly adjust the hue this.facing = 1; // Flip the scaleX value } this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; } static create() { // Existing make method implementation // As in, I left it untouched because I was starting to spend a significant time trying to "make it better" let sizeMultiplier = ((Math.random() * 0.75) + 0.5); let elementSize = DEFAULT_SIZE * sizeMultiplier; let positionX = Math.floor(Math.random() * ((screenWidth * 0.99) - elementSize)); let positionY = Math.floor(Math.random() * ((screenHeight * 0.99) - elementSize)); let directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * ((Math.random() >= 0.5) ? 1 : -1); let directionY = (2 - Math.abs(directionX)) * ((Math.random() >= 0.5) ? 1 : -1); let speed = DEFAULT_SPEED * (Math.random() + 0.5); let hue = Math.floor(Math.random() * 360); let facing = (directionX > 0 ? 1 : -1); if (Math.random() < 0.001) { let background = imageCache.shiny; return new DVDoomREShiny(elementSize, positionX, positionY, directionX, directionY, speed, 0, background, facing, sizeMultiplier); } else { let background = imageCache.default; return new DVDoomRE(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); } } } // Shiny Seia => a very rare golden version of the normal Seia which emits a golden glow. class DVDoomREShiny extends DVDoom { constructor(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing, sizeMultiplier) { super(elementSize, positionX, positionY, directionX, directionY, speed, 60, background, facing); // Set styles using setStyles function for better performance (I hope) setStyles(this.htmlElement, { filter: `drop-shadow(0px 0px ${15 * sizeMultiplier}px #ffd000) contrast(130%) brightness(150%)`, backgroundImage: `url("${background}")`, }); } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Any specific behavior for DVDoomREShiny can go here, if needed // Since super.adjust() already handles position updates and bounce logic, // we may not need to repeat that logic here unless there's something different // for shiny ones. I've left it untouched for this reason. if (((this.positionY + (this.elementSize * 1.3)) >= screenHeight) && this.directionY > 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); } else if ((this.positionY < 0) && this.directionY < 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); } if (((this.positionX + (this.elementSize * 1.3)) >= screenWidth) && this.directionX > 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); } else if ((this.positionX < 0) && this.directionX < 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); } this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; return true; } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Update the direction. this.htmlElement.style["transform"] = `scaleX(${this.facing})`; } } // Classic Seia => dvd like behavior of the initial version of the script. class DVDoomClassic extends DVDoom { adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; if ( (this.positionY + this.elementSize * 1.3 >= screenHeight && this.directionY > 0) || (this.positionY <= 0 && this.directionY < 0) ) { this.directionY *= -1; this.hue = Math.floor(Math.random() * 360); } if ( (this.positionX + this.elementSize * 1.3 >= screenWidth && this.directionX > 0) || (this.positionX <= 0 && this.directionX < 0) ) { this.directionX *= -1; this.hue = Math.floor(Math.random() * 360); } // Move the element this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; return true; // Continue animation } static create() { // This method has a logic error; it should instantiate DVDoomClassic, not DVDoomRE // I think. I think? Change it back to RE if that's intentional let elementSize = DEFAULT_SIZE; let positionX = Math.floor(Math.random() * (screenWidth - elementSize)); let positionY = Math.floor(Math.random() * (screenHeight - elementSize)); let directionX = (Math.random() >= 0.5) ? 1 : -1; let directionY = (Math.random() >= 0.5) ? 1 : -1; let speed = DEFAULT_SPEED; let hue = Math.floor(Math.random() * 360); let facing = 1; let background = imageCache.default; // Create a new instance of DVDoomClassic instead of DVDoomRE return new DVDoomClassic(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); } } // Trailling Seia => a hue shifting seia that leaves behind a trail. class DVDoomTrailing extends DVDoomCursorDragMixin { constructor(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing) { super(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); this.trail = []; this.maxTrailLength = 10; this.lastSpawn = 0; } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Update the direction. this.htmlElement.style["transform"] = `scaleX(${this.facing})`; this.htmlElement.style["filter"] = `hue-rotate(${this.hue}deg)`; } adjust() { // Manage trail spawning if (this.lastSpawn > (10 / (this.speed / this.maxSpeed))) { this.spawnTrail(); this.lastSpawn = 0; } else { this.lastSpawn++; } // Fade out trail elements over time this.fadeTrailElements(); // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Bounce logic for edges this.bounceOnEdges(); // Update position and visual appearance this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; this.hue = ((this.hue + 1) % 360); return true; } spawnTrail() { let newTrailSeia = new DvDoomAuxStationary( this.elementSize, this.positionX, this.positionY, this.directionX, this.directionY, this.speed, this.hue, this.background, (this.directionX > 0 ? 1 : -1) ); this.trail.push(newTrailSeia); seiaEnclosure.insertBefore(newTrailSeia.htmlElement, this.htmlElement); } fadeTrailElements() { for (let index = 0; index < this.trail.length; index++) { let currentTrailElement = this.trail[index].htmlElement; currentTrailElement.style.opacity = (index) / MAX_TRAIL_LENGTH; } if (this.trail.length > MAX_TRAIL_LENGTH) { this.trail[0].destroy(); this.trail.splice(0, 1); } } bounceOnEdges() { if (((this.positionY + (this.elementSize * 1.3)) >= screenHeight) && this.directionY > 0) { this.bounce('y', -1); } else if ((this.positionY < 0) && this.directionY < 0) { this.bounce('y', 1); } if (((this.positionX + (this.elementSize * 1.3)) >= screenWidth) && this.directionX > 0) { this.bounce('x', -1); } else if ((this.positionX < 0) && this.directionX < 0) { this.bounce('x', 1); } } bounce(axis, direction) { if (axis === 'y') { this.directionY = ((Math.random() * 0.8) + 0.2) * direction; this.directionX = (1 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); } else { this.directionX = ((Math.random() * 0.8) + 0.2) * direction; this.directionY = (1 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); this.facing = (this.directionX > 0) ? 1 : -1; } } destroy() { // Empty out the trail. this.trail.forEach((seiaTrailElement) => seiaTrailElement.destroy()); this.trail = []; // Continue its deconstruction. super.destroy(); } static create() { let elementSize = DEFAULT_SIZE; let positionX = Math.floor(Math.random() * ((screenWidth * 0.99) - elementSize)); let positionY = Math.floor(Math.random() * ((screenHeight * 0.99) - elementSize)); let directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * ((Math.random() >= 0.5) ? 1 : -1); let directionY = (2 - Math.abs(directionX)) * ((Math.random() >= 0.5) ? 1 : -1); let speed = DEFAULT_SPEED; let hue = Math.floor(Math.random() * 360); let facing = (directionX > 0 ? 1 : -1); let background = imageCache.default; return new DVDoomTrailing(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); } } // Fractal Seia => a Seia that splits into two smaller Seias upon impact. class DVDoomFractal extends DVDoom { splitSeia(wallHit) { this.eos = true; let elementSize = Math.floor(this.elementSize * 0.75); if (elementSize < 12) { return; } const documentfragment = document.createDocumentFragment(); let directionY1; let directionX1; let directionY2; let directionX2; if (wallHit) { directionX1 = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * (this.directionX > 0 ? -1 : 1); directionY1 = (2 - Math.abs(directionX1)) * ((Math.random() >= 0.5) ? 1 : -1); directionX2 = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * (this.directionX > 0 ? -1 : 1); directionY2 = (2 - Math.abs(directionX2)) * ((Math.random() >= 0.5) ? 1 : -1); } else { directionY1 = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * (this.directionY > 0 ? -1 : 1); directionX1 = (2 - Math.abs(directionY1)) * ((Math.random() >= 0.5) ? 1 : -1); directionY2 = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * (this.directionY > 0 ? -1 : 1); directionX2 = (2 - Math.abs(directionY2)) * ((Math.random() >= 0.5) ? 1 : -1); } let positionX1 = this.positionX + (directionX1 * this.speed); let positionY1 = this.positionY + (directionY1 * this.speed); let positionX2 = this.positionX + (directionX2 * this.speed); let positionY2 = this.positionY + (directionY2 * this.speed); let childSeia1 = new DVDoomFractal( elementSize, positionX1, positionY1, directionX1, directionY1, this.speed, Math.floor(Math.random() * 360), this.background, (directionX1 > 0 ? 1 : -1) ); SEIA_TYPE_MAP.get(DVDoomFractal).push(childSeia1); documentfragment.appendChild(childSeia1.htmlElement); let childSeia2 = new DVDoomFractal( elementSize, positionX2, positionY2, directionX2, directionY2, this.speed, Math.floor(Math.random() * 360), this.background, (directionX2 > 0 ? 1 : -1) ); SEIA_TYPE_MAP.get(DVDoomFractal).push(childSeia2); documentfragment.appendChild(childSeia2.htmlElement); seiaEnclosure.appendChild(documentfragment); } adjust() { if (((this.positionY + (this.elementSize * 1.3)) >= screenHeight) && this.directionY > 0) { this.splitSeia(false); return false; } else if ((this.positionY < 0) && this.directionY < 0) { this.splitSeia(false); return false; } if (((this.positionX + (this.elementSize * 1.3)) >= screenWidth) && this.directionX > 0) { this.splitSeia(true); return false; } else if ((this.positionX < 0) && this.directionX < 0) { this.splitSeia(true); return false; } this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; return true; } static create() { let elementSize = DEFAULT_SIZE * 2.5; let positionX = Math.floor(Math.random() * ((screenWidth * 0.99) - elementSize)); let positionY = Math.floor(Math.random() * ((screenHeight * 0.99) - elementSize)); let directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * ((Math.random() >= 0.5) ? 1 : -1); let directionY = (2 - Math.abs(directionX)) * ((Math.random() >= 0.5) ? 1 : -1); let speed = DEFAULT_SPEED; let hue = Math.floor(Math.random() * 360); let facing = (directionX > 0 ? 1 : -1); let background = imageCache.default; return new DVDoomFractal(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing, background); } } // Snake (Player) Seia => a Seia that can be controlled through the WASD keys, eats food-type point Seias to grow its tail. Dies on impact with walls or death-type point Seias. class DVDoomPlayer extends DVDoom { constructor(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing) { super(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); this.trail = []; this.maxTrailSize = 0; this.lastSpawn = 0; this.type = 'game'; this.customEventListener = (event) => { switch (event.key) { case 'a': if (this.directionX == 0) { this.directionY = 0; this.directionX = -1; this.facing = -1; } break; case 'd': if (this.directionX == 0) { this.directionY = 0; this.directionX = 1; this.facing = 1; } break; case 'w': if (this.directionY == 0) { this.directionX = 0; this.directionY = -1; } break; case 's': if (this.directionY == 0) { this.directionX = 0; this.directionY = 1; } break; default: break; } } window.addEventListener( "keydown", this.customEventListener, true, ); } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Update the direction. this.htmlElement.style["transform"] = `scaleX(${this.facing})`; this.htmlElement.style["-webkit-filter"] = `hue-rotate(${this.hue}deg)`; } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; for (let index = this.trail.length - 4; index > 0; index--) { let currentSeiaHunter = this.trail[index]; if ( ((this.positionX + (this.elementSize * 0.15)) < (currentSeiaHunter.positionX + currentSeiaHunter.elementSize)) && ((this.positionX + (this.elementSize * 0.85)) > currentSeiaHunter.positionX) && ((this.positionY + (this.elementSize * 0.15)) < (currentSeiaHunter.positionY + currentSeiaHunter.elementSize)) && ((this.positionY + (this.elementSize * 0.85)) > (currentSeiaHunter.positionY)) ) { for (let index = this.trail.length - 1; index >= 0; index--) { this.trail[index].destroy(); this.trail.splice(index, 1); } this.eos = true; return false; } } if ( (this.positionY + (this.elementSize * 1.3) >= screenHeight) || (this.positionY < 0) || (this.positionX + (this.elementSize * 1.3) >= screenWidth) || (this.positionX < 0) ) { for (let index = this.trail.length - 1; index >= 0; index--) { this.trail[index].destroy(); this.trail.splice(index, 1); } this.eos = true; return false; } if (this.lastSpawn > 18 && this.maxTrailSize > 0) { if (this.trail.length === this.maxTrailSize) { this.trail[0].destroy(); this.trail.splice(0, 1); } let newTrailSeia = new DvDoomAuxStationary( this.elementSize, this.positionX, this.positionY, this.directionX, this.directionY, this.speed, this.hue, this.background, (this.directionX > 0 ? 1 : -1) ); this.trail.push(newTrailSeia); seiaEnclosure.insertBefore(newTrailSeia.htmlElement, this.htmlElement); this.lastSpawn = 0; } this.hue = ((this.hue + 1) % 360); this.lastSpawn += 1; this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; this.htmlElement.style.left = this.positionX + "px"; this.htmlElement.style.top = this.positionY + "px"; return true; } destroy() { for (let index = this.trail.length - 1; index >= 0; index--) { let currentTrailElement = this.trail[index]; currentTrailElement.destroy(); this.trail.splice(index, 1); } window.removeEventListener('keydown', this.customEventListener); super.destroy(); } static create() { let elementSize = DEFAULT_SIZE * 0.8; let positionX = Math.floor(((screenWidth * 0.5) - elementSize)); let positionY = Math.floor(elementSize * 2); let directionX = 0; let directionY = 1; let speed = DEFAULT_SPEED * 1.5; let hue = Math.floor(Math.random() * 360); let facing = 1; let background = imageCache.shiny; return new DVDoomPlayer(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing, background); } } // Snake (Point) Seia => a Seia that can be of three types (none: changes to another type on impact; food: can be eaten by the player; death: will kill the player). class DVDoomPlayerPoint extends DVDoom { constructor(elementSize, positionX, positionY, directionX, directionY, speed, background, facing) { super(elementSize, positionX, positionY, directionX, directionY, speed, null, background, facing); this.pointMode = null; this.type = 'game'; } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Update the direction. this.htmlElement.style["transform"] = `scaleX(${this.facing})`; this.htmlElement.style["-webkit-filter"] = `hue-rotate(${this.hue}deg)`; } changeMode() { switch ((Math.random() * 3) << 0) { case 2: case 1: this.hue = 60; this.pointMode = true; break; case 0: this.hue = 300; this.pointMode = false; break; default: this.hue = 0; this.pointMode = null; break; } } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; let seiaPlayers = SEIA_TYPE_MAP.get(DVDoomPlayer); if (this.pointMode != null) { for (let index = seiaPlayers.length - 1; index >= 0; index--) { let currentSeiaHunter = seiaPlayers[index]; if ( ((this.positionX) < (currentSeiaHunter.positionX + currentSeiaHunter.elementSize)) && ((this.positionX + (this.elementSize)) > currentSeiaHunter.positionX) && ((this.positionY) < (currentSeiaHunter.positionY + currentSeiaHunter.elementSize)) && ((this.positionY + (this.elementSize)) > (currentSeiaHunter.positionY)) ) { if (this.pointMode) { currentSeiaHunter.maxTrailSize += 1; } else { currentSeiaHunter.eos = true; } this.eos = true; return false; } } } if (((this.positionY + (this.elementSize * 1.3)) >= screenHeight) && this.directionY > 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); this.changeMode(); } else if ((this.positionY < 0) && this.directionY < 0) { this.directionY = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionX = (2 - Math.abs(this.directionY)) * (this.directionX > 0 ? 1 : -1); this.changeMode(); } if (((this.positionX + (this.elementSize * 1.3)) >= screenWidth) && this.directionX > 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * -1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); this.facing = -1; this.changeMode(); } else if ((this.positionX < 0) && this.directionX < 0) { this.directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * 1; this.directionY = (2 - Math.abs(this.directionX)) * (this.directionY > 0 ? 1 : -1); this.facing = 1; this.changeMode(); } this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; return true; } static create() { let sizeMultiplier = 0.5; let elementSize = DEFAULT_SIZE * sizeMultiplier; let positionX = Math.floor(Math.random() * ((screenWidth * 0.99) - elementSize)); let positionY = Math.floor(Math.random() * ((screenHeight * 0.99) - elementSize)); let directionX = ((Math.random() * 2 * 0.8) + (2 * 0.2)) * ((Math.random() >= 0.5) ? 1 : -1); let directionY = (2 - Math.abs(directionX)) * ((Math.random() >= 0.5) ? 1 : -1); let speed = DEFAULT_SPEED * (Math.random() * 0.5) + 0.5; let facing = (directionX > 0 ? 1 : -1); let background = imageCache.shiny; return new DVDoomPlayerPoint(elementSize, positionX, positionY, directionX, directionY, speed, background, facing, background); } } // Rain Seia => a Seia that falls from top to bottom, upon impact teleports back to the top. class DVDoomRain extends DVDoom { constructor(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing, opacity) { super(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); this.opacity = opacity; setStyles(this.htmlElement, { width: `${(elementSize * 0.35)}px`, backgroundSize: '100% 100%', backgroundRepeat: 'no-repeat', maskMode: 'luminance', opacity: this.opacity }); } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if (!super.adjust()) return false; // Check if it has reached the bottom. if (this.positionY + (this.elementSize * 1.3) >= screenHeight && this.directionY > 0) { this.directionY = Math.random() + 1; this.positionY = this.elementSize; this.htmlElement.style["-webkit-filter"] = 'hue-rotate(' + Math.floor(Math.random() * 360) + 'deg)'; this.htmlElement.style.transform = 'scaleX(' + (Math.random() > 0.5 ? 1 : -1) + ')'; this.speed = DEFAULT_SPEED * (Math.random() + 1); } this.positionY += this.directionY * this.speed; return true; } static create() { const sizeMultiplier = Math.random() * 0.5 + 0.5; const elementSize = DEFAULT_SIZE * sizeMultiplier; const positionX = Math.floor(Math.random() * (screenWidth - elementSize)); const positionY = Math.floor(Math.random() * (screenHeight - elementSize)); const directionY = Math.random() + 1; const speed = DEFAULT_SPEED * (Math.random() + 1); const hue = Math.floor(Math.random() * 360); const facing = Math.random() > 0.5 ? 1 : -1; const background = imageCache.shiny; const opacity = Math.random() * 0.4 + 0.3; return new DVDoomRain(elementSize, positionX, positionY, 0, directionY, speed, hue, background, facing, opacity); } } // Wife Seia => a Seia that moves towards the cursor. class DVDoomWife extends DVDoomCursorDragMixin { constructor(...args) { super(...args); this.chaseSpeed = this.speed; this.speed = 0; this.directionChaseX = this.directionX; this.directionChaseY = this.directionY; this.heightModifier = 100; this.currentCursorX = mousePos.x; this.currentCursorY = mousePos.y; this.currentClicks = 0; this.doomChildren = []; setStyles(this.htmlElement, { // filter: `drop-shadow(0px 0px ${5}px rgba(235, 52, 210, 0.4))`, backgroundSize: `100% ${this.heightModifier}%`, backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom', }); // Uselful for the "return" reaction. this.windowVisibilityChange = this.onVisibilityChanged.bind(this); this.leftPageDate = -1; var eventName; this.isVisible = true; if ((this.propName = "hidden") in document) eventName = "visibilitychange"; else if ((this.propName = "msHidden") in document) eventName = "msvisibilitychange"; else if ((this.propName = "mozHidden") in document) eventName = "mozvisibilitychange"; else if ((this.propName = "webkitHidden") in document) eventName = "webkitvisibilitychange"; if (eventName) document.addEventListener(eventName, this.windowVisibilityChange); if ("onfocusin" in document) document.onfocusin = document.onfocusout = this.windowVisibilityChange; //IE 9 window.onpageshow = window.onpagehide = window.onfocus = window.onblur = this.windowVisibilityChange; // Changing tab with alt+tab if (document[this.propName] !== undefined) this.windowVisibilityChange({ type: document[this.propName] ? "blur" : "focus" }); // Setup of the Seia's text box. this.seiaText = ''; this.textLifetime = 0; this.htmlElementText = document.createElement('a'); this.htmlElementText.className = 'seia-text'; this.htmlElementText.textContent = this.seiaText; setStyles(this.htmlElementText, { display: 'block', whiteSpace: 'pre-line', width: `${this.elementSize * 3}px`, height: `${this.elementSize * 0.5}px`, textAlign: "center", marginLeft: `${this.elementSize * -1}px`, marginTop: `${this.elementSize * -0.25}px`, pointerEvents: 'none' }); this.htmlElement.append(this.htmlElementText); } onVisibilityChanged(event) { event = event || window.event; if (this.isVisible && (["blur", "focusout", "pagehide"].includes(event.type) || (document && document[this.propName]))){ this.isVisible = false; this.leftPageDate = performance.now(); } else if (!this.isVisible && (["focus", "focusin", "pageshow"].includes(event.type) || (document && !document[this.propName]))){ this.isVisible = true; // Check if enough time has passed. if (this.leftPageDate >= 0 && (performance.now() > (this.leftPageDate + (5 * 60 * 1000)))) { this.seiaText = 'you came back'; this.textLifetime = 200; } this.leftPageDate = -1; } } syncUI() { super.syncUI(); setStyles(this.htmlElement, { transform: `scaleX(${this.facing})`, background: `url("${this.background}")`, backgroundSize: `100% ${this.heightModifier}%`, backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom', }); this.htmlElementText.textContent = this.seiaText; setStyles(this.htmlElementText, { transform: `scaleX(${this.facing})`, display: ((this.textLifetime > 0) ? 'block' : 'none') }); this.doomChildren.forEach((doomChild) => doomChild.syncUI()); } handleMouseDown(event) { super.handleMouseDown(event); this.heightModifier = 85; this.heartPat(); } adjust() { if (this.heightModifier < 100) { this.heightModifier += 1; } else { this.currentClicks = 0; this.background = imageCache.defaultHearts; } if (this.textLifetime === 0) { this.seiaText = ''; } else { this.textLifetime -= 1; } // Get the current date. const currentUTCDate = new Date(); if (currentUTCDate.getUTCHours() == 19 && 0 == currentUTCDate.getUTCMinutes() == currentUTCDate.getUTCSeconds()) { this.seiaText = 'reset seia' this.textLifetime = 200; } // Adjust the child seias. for (let i = 0; i < this.doomChildren.length; i++) { const seia1 = this.doomChildren[i]; for (let j = i + 1; j < this.doomChildren.length; j++) { const seia2 = this.doomChildren[j]; if (seia1.isHeld || seia2.isHeld) { continue; } const dx = (seia2.positionX + (seia2.elementSize / 2)) - (seia1.positionX + (seia1.elementSize / 2)); const dy = (seia2.positionY + (seia2.elementSize / 2)) - (seia1.positionY + (seia1.elementSize / 2)); const distanceSquared = dx * dx + dy * dy; const minDist = (seia1.elementSize / 2 + seia2.elementSize / 2) * 1.5; const minDistSquared = minDist * minDist; if (distanceSquared < minDistSquared) { const distance = Math.sqrt(distanceSquared); // Calculate sqrt only if there's a collision const overlap = minDist - distance; const nx = dx / distance; const ny = dy / distance; const separation = overlap / 10; const sx = nx * separation; const sy = ny * separation; seia1.positionX -= sx / 2; seia1.positionY -= sy / 2; seia2.positionX += sx / 2; seia2.positionY += sy / 2; const v1 = { x: seia1.directionX, y: seia1.directionY }; const v2 = { x: seia2.directionX, y: seia2.directionY }; seia1.directionX = v2.x; seia1.directionY = v2.y; seia2.directionX = v1.x; seia2.directionY = v1.y; } } seia1.adjust(); } // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if (!super.adjust()) return false; // Use a helper function to handle the bounce logic this.handleBounce(); const dx = mousePos.x - (this.positionX + this.elementSize / 2); const dy = (mousePos.y + window.scrollY) - (this.positionY + this.elementSize / 2); const dxy = Math.hypot(dx, dy); if (dxy > (this.elementSize * 1.5)) { this.directionChaseX = dx / dxy; this.directionChaseY = dy / dxy; this.chaseSpeed = (dxy / (this.elementSize * 1.5)); if (this.speed < (this.maxSpeed * 1.1)) { this.speed = 0; this.positionX += this.directionChaseX * this.chaseSpeed; this.positionY += this.directionChaseY * this.chaseSpeed; this.facing = (this.directionChaseX > 0 ? 1 : -1); return false; } } if (this.speed > 0) { this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; this.facing = (this.directionX > 0 ? 1 : -1); } return true; } heartPat() { this.currentClicks += 1; this.background = imageCache.blushHearts; if (this.currentClicks > 12) { // Reset the clicks. this.currentClicks = 0; this.createChild(); } var c = document.createDocumentFragment(); var cc = document.createElement("div"); for (var i=0; i<3; i++) { var e = document.createElement("i"); e.className = 'seia-heart'; setStyles(e, { width: '15px', left: `${(this.facing > 0 ? (mousePos.x - this.positionX - 7.5) : -(mousePos.x - this.positionX - this.elementSize + 7.5))}px`, top: `${(mousePos.y + window.scrollY) - this.positionY - 7.5}px`, transform: `translate3d(${ randomInt(-75, 125) }px, ${ randomInt(-80, 80) }px, 0) rotate(${ randomInt(-20, 20) }deg)`, animation: `seia-heart-animation 1000ms ease-out forwards`, opacity: 0, position: `absolute`, overflow: `visible`, pointerEvents: 'none' }) cc.appendChild(e); } // document.body.appendChild(c); c.append(cc); this.htmlElement.append(c); setTimeout(() => cc.remove(), 1100); } createChild() { let elementSize = DEFAULT_SIZE * 0.6; let directionX = (Math.random() >= 0.5) ? 1 : -1; let directionY = (Math.random() >= 0.5) ? 1 : -1; let speed = DEFAULT_SPEED * 0.75; let facing = 1; let background = imageCache.defaultHearts; const doomChild = new DVDoomWifeChild(this, elementSize, this.positionX, this.positionY, directionX, directionY, speed, 0, background, facing); // Add the doomchild. this.doomChildren.push(doomChild); seiaEnclosure.insertBefore(doomChild.htmlElement, this.htmlElement); // SEIA_TYPE_MAP.get(DVDoomWifeChild).push(doomChild); } handleBounce() { const nextPosY = this.positionY + (this.elementSize * 1.3); const nextPosX = this.positionX + (this.elementSize * 1.3); if ((nextPosY >= screenHeight && this.directionY > 0) || (this.positionY < 0 && this.directionY < 0)) { this.directionY *= -1; } if ((nextPosX >= screenWidth && this.directionX > 0) || (this.positionX < 0 && this.directionX < 0)) { this.directionX *= -1; } } destroy() { try { document.removeEventListener('visibilitychange', this.windowVisibilityChange); } catch { }; this.doomChildren.forEach((doomChild) => doomChild.destroy()); super.destroy(); } static create() { let elementSize = DEFAULT_SIZE; let directionX = (Math.random() >= 0.5) ? 1 : -1; let directionY = (Math.random() >= 0.5) ? 1 : -1; let speed = DEFAULT_SPEED; let facing = 1; let background = imageCache.defaultHearts; return new DVDoomWife(elementSize, mousePos.x, mousePos.y, directionX, directionY, speed, 0, background, facing); } } // Wife Seia's (Child) => a Seia that moves towards the Seia (Wife). class DVDoomWifeChild extends DVDoomCursorDragMixin { constructor(parentSeia, ...args) { super(...args); this.parentSeia = parentSeia; this.chaseSpeed = 0; this.speed = this.speed * 2; this.directionChaseX = this.directionX; this.directionChaseY = this.directionY; this.heightModifier = 100; this.currentCursorX = mousePos.x; this.currentCursorY = mousePos.y; this.currentClicks = 0; setStyles(this.htmlElement, { backgroundSize: `100% ${this.heightModifier}%`, backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom', }); } syncUI() { super.syncUI(); setStyles(this.htmlElement, { transform: `scaleX(${this.facing})`, background: `url("${this.background}")`, backgroundSize: `100% ${this.heightModifier}%`, backgroundRepeat: 'no-repeat', backgroundPosition: 'bottom', }); } handleMouseDown(event) { super.handleMouseDown(event); this.heightModifier = 85; this.heartPat(); } adjust() { if (this.heightModifier < 100) { this.heightModifier += 1; } else { this.currentClicks = 0; this.background = imageCache.defaultHearts; } // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if (!super.adjust()) return false; // Use a helper function to handle the bounce logic this.handleBounce(); const dx = this.parentSeia.positionX + (this.parentSeia.elementSize / 2) - (this.positionX + this.elementSize / 2); const dy = this.parentSeia.positionY + (this.parentSeia.elementSize / 2) - (this.positionY + this.elementSize / 2); const dxy = Math.hypot(dx, dy); if (dxy > (this.elementSize * 3)) { this.directionChaseX = dx / dxy; this.directionChaseY = dy / dxy; this.chaseSpeed = (dxy / (this.elementSize * 1.5)); if (this.speed < (this.maxSpeed * 1.1)) { this.speed = 0; this.positionX += this.directionChaseX * this.chaseSpeed; this.positionY += this.directionChaseY * this.chaseSpeed; this.facing = (this.directionChaseX > 0 ? 1 : -1); return false; } } if (this.speed > 0) { this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; this.facing = (this.directionX > 0 ? 1 : -1); } return true; } heartPat() { this.background = imageCache.blushHearts; } handleBounce() { const nextPosY = this.positionY + (this.elementSize * 1.3); const nextPosX = this.positionX + (this.elementSize * 1.3); if ((nextPosY >= screenHeight && this.directionY > 0) || (this.positionY < 0 && this.directionY < 0)) { this.directionY *= -1; } if ((nextPosX >= screenWidth && this.directionX > 0) || (this.positionX < 0 && this.directionX < 0)) { this.directionX *= -1; } } } // Seia (Canvas) => Colored or Uncolored Seias, when a seia with color clashes with a colorless, the colorless becomes colored, when opposite colors clash they both become colorless. class DVDoomCanvas extends CoordinatesListItem(DVDoom) { constructor(...args) { super(...args); } adjust() { // Call the superclass method, if it returns false, consider the adjustment concluded and return immediatly. if(!super.adjust()) return false; // Use a helper function to handle the bounce logic this.handleBounce(); // Update the position in the coordinate array. this.updateCoordinatePosition(); return true; } syncUI() { // Call the superclass method is called to update the UI. super.syncUI(); // Directly update styles using the setStyles function. setStyles(this.htmlElement, { left: `${this.positionX}px`, top: `${this.positionY}px`, filter: (this.hue === null) ? `grayscale(1)` : `hue-rotate(${this.hue}deg)`, transform: `scaleX(${this.facing})` }); } handleBounce() { if ( (this.positionY + this.elementSize * 1.3 >= screenHeight && this.directionY > 0) || (this.positionY <= 0 && this.directionY < 0) ) { this.directionY *= -1; } if ( (this.positionX + this.elementSize * 1.3 >= screenWidth && this.directionX > 0) || (this.positionX <= 0 && this.directionX < 0) ) { this.directionX *= -1; this.facing = (this.directionX > 0 ? 1 : -1); } this.positionX += this.directionX * this.speed; this.positionY += this.directionY * this.speed; } static handleCollisions() { for (let i = 0; i < SEIA_COORDINATE_MAP.length; i++) { // Obtain the current coordinate. let currentSeia = SEIA_COORDINATE_MAP[i]; // Loop until no more seias can be found. while (currentSeia) { currentSeia.collision(currentSeia.next); currentSeia.collision(SEIA_COORDINATE_MAP[i + 1]); currentSeia = currentSeia.next; } } } static create() { // Existing make method implementation // As in, I left it untouched because I was starting to spend a significant time trying to "make it better" let elementSize = DEFAULT_SIZE; let positionX = Math.floor(Math.random() * ((screenWidth * 0.99) - elementSize)); let positionY = Math.floor(Math.random() * ((screenHeight * 0.99) - elementSize)); let directionX = ((Math.random() * 4 * 0.8) + (2 * 0.2)) * ((Math.random() >= 0.5) ? 1 : -1); let directionY = (4 - Math.abs(directionX)) * ((Math.random() >= 0.5) ? 1 : -1); let speed = 1; let hue = Math.floor(Math.random() * 360); let facing = (directionX > 0 ? 1 : -1); let background = imageCache.shiny; if (Math.random() < 0.05) { return new DVDoomCanvas(elementSize, positionX, positionY, directionX, directionY, speed, hue, background, facing); } else { return new DVDoomCanvas(elementSize, positionX, positionY, directionX, directionY, speed, null, background, facing); } } } //////////////////// Other // Class used for static "ghost" Seia, mainly used for trails and the like. class DvDoomAuxStationary extends DVDoom { adjust() {} } //////////////////////////////////////////////////// ///////////////// SEIA MAIN LOGIC ////////////////// //////////////////////////////////////////////////// // Changed how it works, and batch updated so it's less resource heavy function addNewSeia(seiaCountToAdd, seiaTypeString, clearPrevious) { const seiaType = SEIA_STRING_ENUM[seiaTypeString]; const seiaTypeList = SEIA_TYPE_MAP.get(seiaType); // Clear existing Seias of the same type if specified if (clearPrevious) { seiaTypeList.forEach(seia => seia.destroy()); seiaTypeList.length = 0; // Clear the array efficiently } const documentFragment = new DocumentFragment(); for (let i = 0; i < seiaCountToAdd; i++) { let seia = seiaType.create(); // Create new Seia seiaTypeList.push(seia); // Add it to the type list documentFragment.appendChild(seia.htmlElement); // Append to the document fragment } seiaEnclosure.appendChild(documentFragment); // Batch DOM update } // Here we're going to call this at a lower frame rate so it reduces lag when anons want to have 1k+ Seias on screen // Is 60 low these days... I'm getting old let fpsInterval = 1000 / 60; // Adjust to 60 FPS let lastFrameTime = Date.now(); function animateSEIAS() { window.requestAnimationFrame(animateSEIAS); // Recursively call the animation frame screenHeight = document.body.clientHeight; screenWidth = document.body.clientWidth; let now = Date.now(); let elapsed = now - lastFrameTime; // Flatten the list of all seias just once per animation frame const allSeias = []; SEIA_TYPE_MAP.forEach(seias => allSeias.push(...seias)); if (elapsed > fpsInterval) { lastFrameTime = now - (elapsed % fpsInterval); // Iterate over all Seia types and their lists SEIA_TYPE_MAP.forEach((seiaTypeList, seiaType) => { for (let i = seiaTypeList.length - 1; i >= 0; i--) { const seia = seiaTypeList[i]; if (!seia.eos) { seia.adjust(); if (!seia.eos) { seia.syncUI(); } else { seia.destroy(); // Clean up resources seiaTypeList.splice(i, 1); // Remove from list } } else { seia.destroy(); // Clean up resources seiaTypeList.splice(i, 1); // Remove from list } } // Handle collisions. seiaType.handleCollisions(); }); // Collision checks if (allSeias.length > 1) { // Only perform if there are Seias to check // checkCollisions(allSeias); } // Check End of Service flag // Let's call it tha- Nyo! It's EoS "End of Service"! // I had a FASTER way to do this and a lot more optimized but it messed it up to where it wouldn't work // WILL come back to this later if (EOS) { for (const [seiaType, seiaTypeList] of SEIA_TYPE_MAP) { for (let index = seiaTypeList.length - 1; index >= 0; index--) { let currentSeia = seiaTypeList[index]; currentSeia.destroy(); seiaTypeList.splice(index, 1); } } EOS = false; } } } const SEIA_TYPES = [ DVDoomRE, DVDoomClassic, DVDoomFractal, DVDoomTrailing, DVDoomRain, DVDoomPlayer, DVDoomPlayerPoint, DVDoomCanvas, DVDoomWife, ]; SEIA_TYPES.forEach(seiaType => { SEIA_TYPE_MAP.set(seiaType, []); // Initialize the map with an empty array for each type SEIA_STRING_ENUM[seiaType.name] = seiaType; // Map the class name to the class constructor }); window.addNewSeia = addNewSeia; window.forceEoS = () => { EOS = true; }; ////////////////////////////////////////////////// // DVD Seia Menu UI // // Class containing the seia table menu UI logic. class SeiaMenuUI { static INPUTS = { "single": [1], "small": [1, 2, 3, 4, 5], "medium": [1, 2, 3, 4, 5, 10, 25, 50], "large": [1, 2, 3, 4, 5, 10, 25, 50, 75, 100, 150, 200, 300, 400, 500, 1000], } constructor(name, color, possibleValues, defaultValueIndex) { this.steps = possibleValues; this.startIndex = defaultValueIndex; this.value = this.steps[this.startIndex]; this.color = color; this.body = document.createElement("td"); setStyles(this.body, { color: color, padding: '8px', fontWeight: 'bold', textAlign: 'center', maxWidth: '150px' }) // Add the name of the section. this.name = name; const nameElement = document.createElement("span"); nameElement.textContent = this.name; setStyles(nameElement, { color: color, padding: '8px', fontWeight: 'bold' }) this.body.appendChild(nameElement); // Add a divider below the name. const dividerElement = document.createElement("hr"); this.body.appendChild(dividerElement); } createInput() { const parentDiv = document.createElement("div"); parentDiv.className = 'quantity'; const input = document.createElement('input'); input.className = 'quantity__input'; input.name = 'quantity'; input.type = 'number'; input.addEventListener('input', (function () { this.value = input.value; }).bind(this)); let currentIndex = 0; input.value = this.value; const orderedSteps = this.steps; const reversedSteps = [...this.steps].reverse(); const incrementButton = (function (e) { e.preventDefault(); var currentValue = input.value; if (currentValue < orderedSteps[0]) { currentIndex = 0; } else if (currentValue !== orderedSteps[currentIndex]) { var index = orderedSteps.findIndex(function(number) { return number > currentValue; }); currentIndex = (index > 0) ? index : orderedSteps.length - 1; } else if (currentIndex < orderedSteps.length) { currentIndex++; } else { currentIndex = orderedSteps.length - 1; } input.value = orderedSteps[currentIndex]; this.value = input.value; }).bind(this); const decrementButton = (function (e) { e.preventDefault(); var currentValue = parseInt(input.value, 10); if (currentValue > orderedSteps[orderedSteps.length - 1]) { currentIndex = orderedSteps.length - 1; } else if (currentValue !== orderedSteps[currentIndex]) { var index = reversedSteps.findIndex(function(number) { return number < currentValue; }); currentIndex = (index > 0) ? (orderedSteps.length - index - 1) : 0; } else if (currentIndex > 0) { currentIndex -= 1; } else { currentIndex = 0; } input.value = orderedSteps[currentIndex]; this.value = input.value; }).bind(this); const buttonMinus = document.createElement('a'); buttonMinus.className = 'quantity__minus'; buttonMinus.onclick = decrementButton; buttonMinus.innerHTML = '-'; const buttonPlus = document.createElement('a'); buttonPlus.className = 'quantity__plus'; buttonPlus.onclick = incrementButton; buttonPlus.innerHTML = '+'; parentDiv.appendChild(buttonMinus); parentDiv.appendChild(input); parentDiv.appendChild(buttonPlus); this.body.appendChild(parentDiv); return this; } createButton(buttonText, buttonTitle, buttonAction) { const buttonElement = document.createElement("a"); buttonElement.textContent = buttonText; buttonElement.onclick = () => buttonAction(this.value); buttonElement.className = 'seia-button'; buttonElement.title = buttonTitle; setStyles(buttonElement, { color: this.color, fontWeight: 'bold', cursor: 'pointer', marginTop: '5px' }) this.body.appendChild(buttonElement); return this; } make() { return this.body; } } // List of seia cells in the table. const seiaGridMap = [ [ new SeiaMenuUI('Seia', 'red', SeiaMenuUI.INPUTS.large, 9).createInput().createButton('Spawn', 'Add Seia', (n) => addNewSeia(n, 'DVDoomRE')).make(), new SeiaMenuUI('Seia (Classic)', 'limegreen', SeiaMenuUI.INPUTS.large, 9).createInput().createButton('Spawn', 'Add Classic Seia', (n) => addNewSeia(n, 'DVDoomClassic')).make(), new SeiaMenuUI('Seia (Fractal)', 'blue', SeiaMenuUI.INPUTS.small, 0).createInput().createButton('Spawn', 'Add Fractal Seia', (n) => addNewSeia(n, 'DVDoomFractal')).make(), new SeiaMenuUI('Seia (Trailing)', 'sandybrown', SeiaMenuUI.INPUTS.medium, 2).createInput().createButton('Spawn', 'Add Trailing Seia', (n) => addNewSeia(n, 'DVDoomTrailing')).make(), ], [ new SeiaMenuUI('Seia (Rain)', 'cornflowerblue', SeiaMenuUI.INPUTS.large, 9).createInput().createButton('Spawn', 'Add Rain Seia', (n) => addNewSeia(n, 'DVDoomRain')).make(), new SeiaMenuUI('Seia (Game)', 'indigo', SeiaMenuUI.INPUTS.large, 9).createInput().createButton('Spawn', 'Add game point Seia', (n) => addNewSeia(n, 'DVDoomPlayerPoint')).createButton('Start', 'Add game player Seia', (n) => addNewSeia(1, 'DVDoomPlayer', true)).make(), new SeiaMenuUI('Seia (Canvas)', 'darkturquoise', SeiaMenuUI.INPUTS.large, 9).createInput().createButton('Spawn', 'Add canvas Seia', (n) => addNewSeia(n, 'DVDoomCanvas')).make(), new SeiaMenuUI('Seia (Wife)', 'deeppink', SeiaMenuUI.INPUTS.single, 0).createButton('Spawn', 'Span a wife Seia', (n) => addNewSeia(n, 'DVDoomWife', true)).make(), ] ]; // I added tooltips because I thought it'd be nice document.getElementById("DVDoomParent").insertAdjacentHTML( 'afterbegin', `
\< Seia DVD Menu \>
`); const tableElement = document.getElementById("seia-table"); const tableDocumentFragment = document.createDocumentFragment(); seiaGridMap.forEach((rowUI) => { const rowElement = document.createElement('tr'); setStyles(rowElement, { tableLayout: "fixed", }); rowUI.forEach((cellUI) => { rowElement.appendChild(cellUI); }); tableDocumentFragment.append(rowElement); }); tableElement.appendChild(tableDocumentFragment); // Add the EoS button. tableElement.insertAdjacentHTML('beforeend', ` EoS ` ); animateSEIAS(); //////////////////////////////////////////////////////////////////////// //////////////////////////// BIRTHDAY LOGIC //////////////////////////// //////////////////////////////////////////////////////////////////////// const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; let fullStringTop = ""; let fullStringBottom = ""; // Function to add days to a date function addDays(date, days) { const copy = new Date(Number(date)); copy.setDate(date.getDate() + days); return copy; } // Fetch birthdays data from the API (Don't worry, I asked for permission to use this API for this purpose. It's fine.) async function fetchBirthdays() { const response = await fetch('https://schale.gg/data/en/students.json'); const data = await response.json(); // This is assuming that the fetched JSON data is an array directly. // If the actual data is an object with a property that holds the array, you'll need to adjust the code to match that structure. // But for now, it will work with the current structure of the fetched data. In case it changes, it probably won't, adjust this code accordingly. return data.reduce((acc, student) => { // Extract the month and day from the birthday, considering the suffixes const birthdayMatch = student.BirthDay.match(/(\d+)\/(\d+)/); if (!birthdayMatch) return acc; // Skip if the birthday doesn't match the expected format (This shouldn't be an issue, but let's keep it here just in case) const monthNumber = parseInt(birthdayMatch[1], 10); const day = parseInt(birthdayMatch[2], 10); if (!acc[monthNumber]) { acc[monthNumber] = {}; } if (!acc[monthNumber][day]) { acc[monthNumber][day] = []; } // Last name, first name const studentFullName = `${student.FamilyName} ${student.PersonalName}`; // Find an existing student entry for this day let studentEntry = acc[monthNumber][day].find(char => char.name === studentFullName); // If found, push the new image to this student's image array (In other words, if this student has an alt) if (studentEntry) { if (!studentEntry.images.includes(`https://schale.gg/images/student/collection/${student.Id}.webp`)) { studentEntry.images.push(`https://schale.gg/images/student/collection/${student.Id}.webp`); } } else { // If not found, create a new entry for this student acc[monthNumber][day].push({ name: studentFullName, images: [`https://schale.gg/images/student/collection/${student.Id}.webp`] }); } return acc; }, {}); } const birthdays = await fetchBirthdays(); const baseDate = new Date(); // This loop checks for birthdays today and up to the next 7 days for (let i = 0; i <= 6; i++) { const currentDate = addDays(baseDate, i); const month = currentDate.getMonth() + 1; const day = currentDate.getDate(); const studentsByBirthday = birthdays[month]?.[day]; if (studentsByBirthday) { fullStringTop += `${months[currentDate.getMonth()]} ${day}`; fullStringBottom += ``; for (const student of studentsByBirthday) { fullStringBottom += `
${student.name.replace(' ', '\n')}
${student.name}
`; } fullStringBottom += ``; } } if (fullStringTop === '') { fullStringBottom = `
No upcoming student birthdays in the next 7 days
`; } // To Seia: I sort of want to change this. Might do it at a later date. Might. // (It's hard, I tried. Might come back to it another time, but low priority) let finalString = `` + fullStringTop + `` + fullStringBottom + `` + `
\< Student Birthdays \>
JST TIME
UTC TIME
` document.getElementById("DVDoomParent").insertAdjacentHTML( 'afterbegin', `
${finalString}
` ); // To Seia: It's en-US but I'll leave it as such. I just wanted to change how the birhdays we're pulled because you hardcoding it... const clockCallbacks = {}; const clockJST = document.getElementById("clockJST"); const clockUTC = document.getElementById("clockUTC"); const clockStyle = { hour: "numeric", minute: "numeric", second: "numeric", weekday: "short", month: "long", day: "numeric", hourCycle: 'h23' } setInterval(() => { const dateToUpdate = new Date(); clockJST.textContent = dateToUpdate.toLocaleString('en-US', { ...clockStyle, ...{ timeZone: 'Japan' } }).replace(' at', ',') + ' JST'; clockUTC.textContent = dateToUpdate.toLocaleString('en-US', { ...clockStyle, ...{ timeZone: 'UTC' } }).replace(' at', ',') + ' UTC'; for (const [_, callbackFunction] of Object.entries(clockCallbacks)) { callbackFunction(dateToUpdate); } }, 1000); ////////////////////////////////////////////////// document.querySelectorAll('.image-container').forEach(container => { let images = container.dataset.images.split(','); let currentImageIndex = 0; // Remove duplicates from the images array; shouldn't be an issue but just in case images = [...new Set(images)]; // Set an interval only if there are multiple images to cycle through // I'll be honest, I REALLY hope this works with 2-alt students like Haruna if (images.length > 1) { setInterval(() => { currentImageIndex = (currentImageIndex + 1) % images.length; // Loop back to the first image container.querySelector('img').src = images[currentImageIndex]; // Update the image source }, 5000); // Change image every 5 seconds (5000 is 5000 milliseconds) // Inconsistent, is it miliseconds or frames? Ah, who cares. } }); //////////////////////////////////////////////////////////////////////// ///////////////////////////////// FEED ///////////////////////////////// //////////////////////////////////////////////////////////////////////// document.getElementById("DVDoomParent").insertAdjacentHTML('beforeend', `
`); async function fetchFeed() { const response = await fetch('https://rentry.org/DVDoomFEED/raw'); const data = await response.json(); let feedString = ""; data.forEach((feedItem) => { if(feedItem["approved"]) { // Obtain the ID. const feedElementId = feedItem["id"]; // Set the string. feedString += `
${feedItem['text']}

${feedItem['text']}
${feedItem['description']}
`; // Check if countdown is enabled. if (feedItem["countdown"]) { // Obtain the countdown date. const countDownDate = new Date(feedItem["countdown"]).getTime(); // Set the callback function. clockCallbacks[feedElementId] = function (currentDateNow) { // Find the distance between now and the count down date var distance = countDownDate - currentDateNow; // Time calculations for days, hours, minutes and seconds var days = Math.floor(distance / (1000 * 60 * 60 * 24)); var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((distance % (1000 * 60)) / 1000); // If the count down is over, write some text if (distance < 0) { document.getElementById(feedElementId).innerHTML = ""; delete clockCallbacks[feedElementId]; } else { // Correctly construct the time. let displayString = ""; if (days > 0) { displayString = days + "d " + hours + "h " + minutes + "m " + seconds + "s"; } else if (hours > 0) { displayString = hours + "h " + minutes + "m " + seconds + "s"; } else if (minutes > 0) { displayString = minutes + "m " + seconds + "s"; } else if (seconds > 0) { displayString = seconds + "s"; } else { displayString = ""; } // Output the result in an element with right id; document.getElementById(feedElementId).innerHTML = displayString; } }; } } }); const seiaFeed = document.getElementById("seia-feed"); if (feedString === "") { seiaFeed.hidden = true; } else { seiaFeed.insertAdjacentHTML('beforeend', feedString); seiaFeed.hidden = false; } }; fetchFeed(); //////////////////////////////////////////////////////////////////////// /////////////////////////////// GUIDES ///////////////////////////////// //////////////////////////////////////////////////////////////////////// document.getElementById("DVDoomParent").insertAdjacentHTML('afterend', `
`); async function fetchUsefulLinks() { const response = await fetch('https://rentry.org/DVDoomMISC/raw'); const data = await response.json(); let usefulStuffLeft = ""; let usefulStuffRight = ""; data.forEach((feedItem) => { if(feedItem["enabled"]) { // The string to add. const stringToAdd = `
`; if (feedItem["side"] === "left") { // Set the string. usefulStuffLeft += stringToAdd; } else if(feedItem["side"] === "right") { // Set the string. usefulStuffRight += stringToAdd; } } }); const seiaFeed = document.getElementById("seia-useful-links"); if (usefulStuffLeft === "" && usefulStuffRight === "") { seiaFeed.hidden = true; } else { seiaFeed.insertAdjacentHTML('beforeend', `
${usefulStuffLeft}
${usefulStuffRight}
`); seiaFeed.hidden = false; } }; fetchUsefulLinks(); //////////////////////////////////////////////////////////////////////// ///////////////////////////////// HOLE ///////////////////////////////// //////////////////////////////////////////////////////////////////////// let holeStyle = null; function alternateHole() { const holeButton = document.getElementById('holeButton'); if (holeStyle) { // Remove the element. holeStyle.remove(); holeStyle = null; holeButton.style.color = null; holeButton.style.fontWeight = null; } else { // Create the element. holeStyle = document.createElement('style'); holeStyle.textContent = ` a.fileThumb { overflow: hidden; } a.fileThumb img:not(.full-image):not(.expanded-thumb) { -webkit-mask: url("${imageCache.hole}"); -webkit-mask-repeat: no-repeat; -webkit-mask-size: cover; -webkit-mask-position: center; } `; document.head.appendChild(holeStyle); holeButton.style.color = 'red'; holeButton.style.fontWeight = 'bold'; } } window.alternateHole = alternateHole; document.getElementsByClassName("navLinks desktop")[0].insertAdjacentHTML( 'beforeend', ' [Hole] ' ); //////////////////////////////////////////////////////////////////////// /////////////////////////////// TWITTER //////////////////////////////// //////////////////////////////////////////////////////////////////////// function functionHideMenu(currentElement) { window.removeEventListener('click', clickToHideDisplayMenu); document.removeEventListener("keydown", keyToHideDisplayMenu); currentElement.previousSibling.className = 'menu-button'; currentElement.remove(); } function clickToHideDisplayMenu(e) { const currentElement = document.getElementById('twitter-menu'); if (!currentElement.contains(e.target)) { functionHideMenu(currentElement); } } function keyToHideDisplayMenu(e) { if (e.key === 'Escape') { const currentElement = document.getElementById('twitter-menu'); functionHideMenu(currentElement); } } function functionDisplayMenu(e, twitterArrowContainer, tweetUser, tweetId) { // Get the positions. const boundingBox = twitterArrowContainer.getBoundingClientRect(); const fragment = document.createDocumentFragment(); const twitterMenu = document.createElement('div'); twitterMenu.className = 'dialog'; twitterMenu.id = 'twitter-menu'; twitterMenu.tabindex = 0; twitterMenu.dataType = "get"; let nitterURL, sotweURL; if(tweetId === '') { nitterURL = `https://nitter.poast.org/${tweetUser}`; sotweURL = `https://sotwe.com/${tweetUser}`; } else { nitterURL = `https://nitter.poast.org/${tweetUser}/status/${tweetId}`; sotweURL = `https://sotwe.com/tweet/${tweetId}`; } twitterMenu.innerHTML = ` Nitter Sotwe `; window.addEventListener('click', clickToHideDisplayMenu); document.addEventListener("keydown", keyToHideDisplayMenu); setStyles(twitterMenu, { zIndex: 2, position: 'absolute', top: `${boundingBox.bottom + window.scrollY}px`, left: `${boundingBox.left + window.scrollX}px`, }); fragment.appendChild(twitterMenu); twitterArrowContainer.parentNode.insertBefore(fragment, twitterArrowContainer.nextElementSibling); } window.getAlternativeURLs = (twitterArrowContainer, e, tweetUser, tweetId) => { const currentActiveElement = document.getElementById('twitter-menu'); if (currentActiveElement) { if (twitterArrowContainer.className == 'menu-button') { functionHideMenu(currentActiveElement); twitterArrowContainer.className = 'menu-button active'; functionDisplayMenu(e, twitterArrowContainer, tweetUser, tweetId); } else { functionHideMenu(currentActiveElement); } } else { twitterArrowContainer.className = 'menu-button active'; functionDisplayMenu(e, twitterArrowContainer, tweetUser, tweetId); } e.stopPropagation(); }; function addTwitterOptions(linkifiedTwitter) { linkifiedTwitter.classList.add("seia-checked"); const fragment = document.createDocumentFragment(); const twitterArrowContainer = document.createElement('a'); twitterArrowContainer.className = 'menu-button'; setStyles(twitterArrowContainer, { 'width': '18px', 'textAlign': 'center' }) twitterArrowContainer.style.width = '18px'; const twitterArrowIcon = document.createElement('i'); twitterArrowIcon.className = 'fa fa-angle-down'; twitterArrowContainer.appendChild(twitterArrowIcon); // Obtain the host. const host = linkifiedTwitter.text.replace('https://', '').replace('www.', '').split('.com')[0].toLowerCase(); let prefixElement; if(host == 'x' || host == 'fixupx' || host == 'twitter') { prefixElement = linkifiedTwitter.nextElementSibling; if (prefixElement !== null && prefixElement.className === 'embedder') { prefixElement = prefixElement.nextElementSibling; } } else { return; } const tweetId = (linkifiedTwitter.text.includes('/status/')) ? linkifiedTwitter.text.split('/status/').pop() : ''; const tweetUser = linkifiedTwitter.text.split('.com/').pop().split('/')[0]; fragment.appendChild(twitterArrowContainer); twitterArrowContainer.setAttribute('onclick', `getAlternativeURLs(this, event, '${tweetUser}', '${tweetId}')`); linkifiedTwitter.parentNode.insertBefore(fragment, prefixElement); } // The live collection to listen. const linkifyListener = document.getElementsByClassName("linkify"); // Every 5 seconds add twitter options to the links. setInterval(() => { // Obtain the linkified twitter. [...linkifyListener].forEach((element) => { // Check if element does not have the right class. if (!element.classList.contains('seia-checked')) addTwitterOptions(element); }); }, 5000); })(); ```