const MSG_DEFAULT_X = 100; const LOGO_DEFAULT_X = 24; const LOGO_DEFAULT_Y = 24; const TIMER_DELAY_MS = 50; const STATE_IDLE = 'idle'; const STATE_AWAY_IN = 'away_in'; const STATE_AWAY_OUT = 'away_out'; const STATE_AWAY = 'away'; const STATE_THRESHOLD_MAP = { idle: 14, away_in: 12, away_out: 8, away: Infinity, } const MESSAGE_MAX_LEVEL = 50; const BLACKOUT_TIMEOUT_SEC = 6; const BLACKOUT_TIMEOUT = Math.round(BLACKOUT_TIMEOUT_SEC * 1000 / TIMER_DELAY_MS); document.addEventListener("DOMContentLoaded", () => { Msg.elementRef = document.getElementById('msg'); Logo.elementRef = document.getElementById('logo'); Controller.bodyEl = document.getElementById('body'); Controller.mainEl = document.getElementById('main'); Controller.footerEl = document.getElementById('footer'); Logo.elementRef.addEventListener('click', Controller.handleMouseEvent); Logo.elementRef.addEventListener('mouseover', Controller.handleMouseEvent); Controller.timerId = setInterval(Controller.handleTimerEvent, 50); }); Controller = {}; Controller.state = STATE_IDLE; Controller.blackout_timeout = BLACKOUT_TIMEOUT Controller.tickRegress = function() { if (Controller.blackout_timeout > 0){ Controller.blackout_timeout--; return; } Controller.mainEl.remove(); Controller.footerEl.remove(); clearInterval(Controller.timerId); Msg.next(); } Controller.handleMouseEvent = function() { if (Controller.state === STATE_IDLE) { Logo.tickProgress(); Msg.next(); } } Controller.handleTimerEvent = function() { if (Controller.state === STATE_IDLE) { Logo.tickRegress(); Msg.tickRegress(); } else if (Controller.state === STATE_AWAY_IN || Controller.state === STATE_AWAY_OUT) { Logo.tickProgress(); Msg.updateModel(); } else if (Controller.state === STATE_AWAY) { Controller.tickRegress(); Msg.updateModel(); } } Controller.getNextState = function () { switch (Controller.state) { case STATE_IDLE: return STATE_AWAY_IN; case STATE_AWAY_IN: return STATE_AWAY_OUT; case STATE_AWAY_OUT: return STATE_AWAY; } throw Error('Not implemented'); } Msg = {}; Msg.locked = false; Msg.sequence = Msg.texts = [ '', '', '', 'Stop', '', '', '', 'I\'m serious', '', '', '', 'Last chance', '', 'Oh NO', 'What have you done?', '> Reload the page or persist in the doomed world you have created.\nConnection closed.', ]; Msg.tickRegress = function() { if (Msg.level > 0) { Msg.level --; Msg.updateModel(); } } Msg.next = function() { if (Msg.texts.length > 0) { let text = Msg.texts.shift(); if (!text) return; Msg.elementRef.innerText = text; Msg.level = MESSAGE_MAX_LEVEL; Msg.updateModel(); if (Msg.texts.length === 0) { Msg.elementRef.setAttribute('style', `margin-left: 0; opacity: 100%;`); Msg.locked = true; Msg.sequence = text.split(' ') Msg.elementRef.innerText = ''; Msg.elementRef.classList.toggle('float') Msg.timerId = setInterval(Msg.next_word, 120); } } } Msg.next_word = function() { if (Msg.sequence.length > 0) { Msg.elementRef.innerText += ' '+Msg.sequence.shift(); } } Msg.updateModel = function() { if (Msg.locked) return; let animationProg = Msg.level / MESSAGE_MAX_LEVEL; // click 100% -> idle 0% let x = MSG_DEFAULT_X + 30 * (1-animationProg); let op = animationProg*100; if (Controller.state === STATE_AWAY_OUT || Controller.state === STATE_AWAY_IN) { x = MSG_DEFAULT_X; op = 100; } if (Controller.state === STATE_AWAY) { animationProg = (Controller.blackout_timeout/BLACKOUT_TIMEOUT) x = MSG_DEFAULT_X * animationProg; op = 100 * animationProg; } Msg.elementRef.setAttribute('style', `margin-left: ${x}px; opacity: ${op}%;`); } Logo = {}; Logo.level = 0; Logo.max_level = 0; Logo.x = LOGO_DEFAULT_X; Logo.y = LOGO_DEFAULT_Y; Logo.lightness = 0; Logo.hue = 120; Logo.scale = 100; Logo.angle = 0; Logo.hide = false; Logo.tickProgress = function() { Logo.max_level += 1; Logo.level = Logo.max_level; Logo.updateModel(); } Logo.tickRegress = function() { if (Logo.level > 0) { Logo.level = Math.max(0, Logo.level * .9); Logo.updateModel(); } } Logo.updateModel = function() { let threshold = STATE_THRESHOLD_MAP[Controller.state]; let animationProg = Logo.level/Logo.max_level; // click 100% -> idle 0% let stateSwitchProg = Logo.max_level/threshold; // start 0% -> next state 100% if (stateSwitchProg >= 1) { Controller.state = Controller.getNextState(); Logo.level = 0; Logo.max_level = 0; if (Controller.state === STATE_AWAY) { Logo.elementRef.remove(); Controller.bodyEl.setAttribute('style', 'background-color: #000000;'); Controller.mainEl.setAttribute('style', 'top: 200%'); Controller.footerEl.setAttribute('style', 'margin-bottom: -50%'); Msg.next(); } Logo.updateView(); return; } if (Controller.state === STATE_IDLE) { Logo.lightness = 80*Math.pow(animationProg, 4); Logo.hue = 120 - 120*stateSwitchProg; Logo.scale = 100 + 50*Math.pow(animationProg, 2) } else if (Controller.state === STATE_AWAY_IN) { Logo.hue = 360*stateSwitchProg; Logo.lightness = 25 + (Math.random()*20); Logo.x = Math.max(-1000, LOGO_DEFAULT_X - Math.pow(stateSwitchProg, 4)) + (Math.random()*20); Logo.y = LOGO_DEFAULT_Y + (Math.random()*20); Logo.scale = 200 + 30*stateSwitchProg; Logo.angle = 20 + 20*stateSwitchProg; } else if (Controller.state === STATE_AWAY_OUT) { Logo.hue = 0; Logo.lightness = 50; Logo.x = Math.min(3000, -1000 + 4000*stateSwitchProg); Logo.scale = 400; Logo.angle = 360*stateSwitchProg; } Logo.updateView(); } Logo.getAttrs = function() { let x = (Logo.x || LOGO_DEFAULT_X); let y = (Logo.y || LOGO_DEFAULT_Y); let angle = (Logo.angle || 0) return { 'background-color': `hsl(${Logo.hue}, 100%, ${Logo.lightness}%)`, 'transform': `scale(${Logo.scale}%)`, 'left': `${x}px`, 'top': `${y}px`, 'rotate': `${angle}deg`, } } Logo.updateView = function() { let logoStyleAttrs = []; for (const [key, value] of Object.entries(Logo.getAttrs())) { logoStyleAttrs.push(`${key}: ${value}`); } Logo.elementRef.setAttribute('style', logoStyleAttrs.join('; ')); }