├── .gitignore ├── README.md ├── app.js ├── assets ├── CarnivaleeFreakshow.woff ├── CarnivaleeFreakshow.woff2 ├── Feral-Regular.ttf ├── Feral-Regular.woff ├── Feral-Regular.woff2 ├── KingthingsTrypewriter2.woff ├── KingthingsTrypewriter2.woff2 ├── aero_arrow.cur ├── aero_link.cur ├── beam_i.cur ├── demonic-woman-scream-6333.mp3 ├── face-no-eyes.png ├── face-outline-transparent.png ├── face-outline.png ├── face-stare.png ├── glass-crack.png ├── grab.cur ├── grabbing.cur ├── ice-cracking.mp3 ├── jack_in_the_box.mp3 ├── lnodrop.cur ├── mixkit-hard-horror-hit-drum-565.mp3 ├── mouse_recordings.js ├── no_il.cur ├── ouija_bg.jpg ├── ouija_bg.png ├── ouija_bg_face_no_eyes.jpg ├── ouija_bg_face_outline.jpg ├── ouija_bg_face_stare.jpg ├── planchette.png ├── planchette2.png ├── screenshot.jpg ├── screenshot2-transparent.png ├── screenshot2.jpg ├── screenshot3.jpg ├── screenshot4.jpg └── screenshot5.jpg ├── chatbot.js ├── cloudflare-worker-geo.js ├── cloudflare-worker-lokittaja.js ├── favicon.ico ├── index.html ├── robots.txt └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | wip/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## O̅͒͊̎̌ͯ͋̀̉uͭ̒͂̉ͪ͐̌ͦ̀i͒͋ͭ͋́͑͗͒̔̅̓̓̎̒̾͂̈̔̍j̇͗ͮ̉͑̃ͩͩ̀̒ͥͧ̉ǎ͂ͣͩͥ̿̓ͤͬ ͛ͦ͌̔ͣ͑̄ͭ̎̑̅̽͑ͯoͦ̈͋̀͗ͧ͒̈̊̃̆̐̓͑͋̄̀nͧ͆ͬ̅̾l̈͊ͪͮ̄̽ͧi̇ͨ̿̂ͤͣ͛̒̐̐̈̾͋ͥ̌ͩ͆̀n̋́̃̍̑̓ͪeͨ̇ͥ̍ͭ͗̓ͣͮͤͪͩ̓ͣͥͫͮ͋̚ 2 | 3 | A web ho̲̘̙̜̮ͦrro̦̅̑̆̀r experience where you communicate with sp̙̝̜͐̇̀ir̛͡͏̀͝its͍̙̅ͣ using a Ouija board. 4 | 5 | Try it here: :scream: :scream: :scream: [https://ouija.attejuvonen.fi](https://ouija.attejuvonen.fi) :scream: :scream: :scream: 6 | 7 | ![Screenshot of Ouija Online](assets/screenshot2-transparent.png) 8 | 9 | #### Notable features: 10 | 11 | - Despite existing only as a web page, the spirits have the ability to m̜͙̼͉̊͐ͬo̖̗͆̽v̫̺̘̇͗e̮͎̦̞̼ ̩̥͉̆͋̆̎ͅt̞̬͍͓̅̾̎͆́h̫ễ̱̦̤͖̊̅ͭͥ̚ ̳̗͐ͥͭp̺̜̤̅̏̌ͮl̤͒̌a̙̺̣̗̘͆ͣ̒͗͊ͅy̝̻̭̖͑ͭ͑̉e̱͔̯̬̘̅̀͋̍̃ͥr͔͇͉̯̣ͤ̍'͚͎̖̲̞̬͈̈̅̇̑̇š́ͮͦ ̠͈͚̣̩̝ͅm̹̌͊o̗͐ͧ̇̊̏̉̔uͯͧ̓̎̋ͣ̈́s̺̩̗͓͉͈͒̾ͬͯ̒ͧ̆e͉͔̘̒̇̂ͤ towards letters on the board 12 | - Two different chatbots available: a __scripted experience__ is available for all, and for players who have OpenAI API keys, there is a more versatile __GPT-3 mode__ 13 | - The scripted experience has 3 achievements to unlock and various special effects including a few jumpscares :scream: 14 | 15 | #### How does it work 16 | 17 | - Ouija Online is built as a static website with vanilla JS, HTML, and CSS. No frameworks, no libraries, no generators, no bundlers, no servers. You can fork this repo and simply open `index.html` in your browser. (There are a couple of Cloudflare Workers to provide geolocation and logging, but these are supplementary functions and the app works fine without the workers.) 18 | - Control of the user's mouse is an illusion (settings have a toggle to reveal the trick visually). 19 | - Chatbot in GPT-3 mode constructs a prompt with verbal instructions, question-answer examples, previous question and answer, and current question. We request 5 completions for the prompt, and then heuristically choose one of them (considering length, repetition, dullness, etc.). 20 | - Chatbot in scripted mode utilizes state-of-the-art if-else technology. 21 | 22 | #### Attribution 23 | 24 | Design and implementation: [Baobab Koodaa](https://github.com/baobabKoodaa) 25 | 26 | Assets and effects: 27 | 28 | - The Ouija board image is a photograph of the original Ouija board from 1889, created by Kennard Novelty Company. According to [Wikipedia](https://en.wikipedia.org/wiki/Ouija#/media/File:Ouija_board_-_Kennard_Novelty_Company.png), this image is in the public domain. Photographer is unknown. 29 | - Planchette PNG image is from [KindPNG](https://www.kindpng.com/imgv/hToiomo_transparent-planchette-png-ouija-board-planchette-png-png/), which provide permission for use in "non-commercial or personal projects". Author is unknown. 30 | - Smoke effect used in tooltip is adapted from work by [chokcoco](https://segmentfault.com/a/1190000041189786/en). I modified the effect heavily in order to get smoother transitions for dissipation and hover. Those transitions now animate the Perlin turbulence filter by using SVG animate (not CSS!) in addition to some CSS transitions. 31 | - Spirit message text glitch Tiktok effect was popularized by Tiktok, implementation adapted from [AmazingCSS](https://amazingcss.com/glitch-text-effect-like-tiktok/). 32 | - Spirit message text glitch Zalgotext was inspired by [the legendary Stackoverflow answer](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags), implementation adapted from [tchouky](https://eeemo.net/) and [The Great Rambler](https://github.com/TheGreatRambler/another-zalgo.js/) 33 | - Magnifying glass effect adapted from [W3Schools](https://www.w3schools.com/howto/howto_js_image_magnifier_glass.asp) example code. 34 | - Font 'Feral' was created by Marcus Lien Gundersen and was downloaded from [1001fonts](https://www.1001fonts.com/feral-font.html), which provide permission for both personal and commercial use. 35 | - Font 'Carnevalee Freakshow' was created by Chris Hansen and was downloaded from [1001fonts](https://www.1001fonts.com/carnivalee-freakshow-font.html), which provide permission for both personal and commercial use. 36 | - Font 'Kingthings Trypewriter 2' was created by Kevin King and was downloaded from [1001fonts](https://www.1001fonts.com/kingthings-trypewriter-2-font.html), which provide permission for both personal and commercial use. 37 | - Background pattern used in first-visit popup is from [Hero Patterns](https://heropatterns.com/), which provide permission for use under CC BY 4.0 license. 38 | - Icons for settings and external links are from [FontAwesome](https://fontawesome.com/icons/gear?s=solid) with permission for both personal and commercial use. 39 | - Jack-in-the-box audio is from [SoundBible](https://soundbible.com/1872-Jack-In-The-Box.html), uploaded by Mike Koenig with attribution license. 40 | - Creepy old photo used in easter egg is from [Vintage Everyday](https://www.vintag.es/2016/11/these-50-creepy-photographs-early-20th.html), copyright expired. 41 | - Easter egg jumpscare audio is from [Mixkit](https://mixkit.co/free-sound-effects/horror/), which provide permission for both personal and commercial use. 42 | - Banshee scream jumpscare audio is from [Pixabay](https://pixabay.com), which provide permission for both personal and commercial use. 43 | - Glass crack PNG image is from [SeekPNG](https://www.seekpng.com/ipng/u2q8i1y3r5i1t4a9_the-gallery-for-broken-glass-transparent-png-broken/), which provide permission for personal use. 44 | - Glass crack audio was recorded in-house. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // This web app is designed for Chrome, some animations are disabled in Firefox due to performance issues in Firefox. 2 | const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 3 | const reduceAnimations = isFirefox 4 | 5 | // String literals 6 | const ON_PLANCHETTE = 'onPlanchette' 7 | const ON_USER_MESSAGE = 'onUserMessage' 8 | const ON_BUTTON = 'onButton' 9 | const ON_TEXT_INPUT = 'onTextInput' 10 | const SHUT_UP = 'shutUp' 11 | const RECORDING = 'recording' 12 | const TURN_SPIRIT = 'turnSpirit' 13 | const TURN_USER = 'turnUser' 14 | const OUIJA_USER_ID = 'ouija-user-id' 15 | const OUIJA_PLAYER_NAME = 'ouija-player-name' 16 | const OUIJA_SUICIDE_POPUP_SHOWN_ONCE = 'ouija-suicide-popup-shown-once' 17 | const OUIJA_SUICIDE_PREVENTION_ACTIVATED = 'ouija-suicide-prevention-activated' 18 | 19 | // Achievements! 20 | const FALSE_PROPHETS_ACHIEVEMENT = 'achievementFalseProphets' 21 | const EASTER_EGG_ACHIEVEMENT = 'achievementEasterEgg' 22 | const POSSESSED_ACHIEVEMENT = 'achievementPossessed' 23 | const allAchievements = [EASTER_EGG_ACHIEVEMENT, POSSESSED_ACHIEVEMENT, FALSE_PROPHETS_ACHIEVEMENT] 24 | 25 | // Global state 26 | let loadingOverlayDisabled = false 27 | let gameOver = false 28 | let popupIsOpen = false 29 | let showTips = true 30 | let easyMode = false 31 | let easterEggVisible = false 32 | let draggingPlanchette = false 33 | let planchetteTransformX = 0 // In % relative to planchette's own size (to support browser resizing) 34 | let planchetteTransformY = 0 // In % relative to planchette's own size (to support browser resizing) 35 | let prevX = undefined 36 | let prevY = undefined 37 | let offsetX = 0 // In % relative to planchette's size (to support browser resizing consistently with planchetteHelper) 38 | let offsetY = 0 // In % relative to planchette's size (to support browser resizing consistently with planchetteHelper) 39 | let userMoveCount = 0 40 | let remainingGoals = '' 41 | let revealedSpiritLetters = '' 42 | let userMessage = '' 43 | let currentExchangeNumber = 0 44 | let turn = TURN_USER 45 | let hoveringOverTooltip = false 46 | let speedMode = false 47 | let revealMouse = false 48 | let debug = {} 49 | 50 | // Magnifier constants 51 | const MAG_ZOOM = 1.2 52 | const MAG_LEFT = 0.465 53 | const MAG_TOP = 0.58 54 | 55 | // Offset constants 56 | const SPIRIT_MAX_DIST = 43.0 // Values above 43 will cause confusing UX when target is on different "row" of letters than the player is! 57 | const SPIRIT_STOCHASTIC_STR = 0.6 58 | const SPIRIT_ACCEL_STR = 1.4 59 | const OFFSET_CANCELLATION_STR = 0.3 60 | const OFFSET_CANCELLATION_LOW_URGENCY = 0.3 61 | 62 | // Planchette drag limitations (board area) 63 | const HARD_Y_MAX = 80 64 | const HARD_Y_MIN = -160 65 | const HARD_X_MAX = 270 66 | const HARD_X_MIN = -255 67 | const SOFT_Y_MAX = HARD_Y_MAX - 10 68 | const SOFT_Y_MIN = HARD_Y_MIN - 10 69 | const SOFT_X_MAX = HARD_X_MAX - 10 70 | const SOFT_X_MIN = HARD_X_MIN - 10 71 | 72 | // User message constants 73 | USER_MESSAGE_MAX_LENGTH = 40 74 | 75 | // Goal constants. Coordinates are in % relative to planchette transform. 76 | const ALLOWED_CHARS = 'abcdefghijklmnopqrstuvwxyz1234567890' 77 | const CHAR_SELECT_MAX_DIST = 10 78 | const goalCoords = { 79 | "0": { "x": 96.57, "y": -0.67 }, 80 | "1": { "x": -110.95, "y": -1.07 }, 81 | "2": { "x": -92.67, "y": -0.27 }, 82 | "3": { "x": -69.56, "y": -0.27 }, 83 | "4": { "x": -45.9, "y": -0.27 }, 84 | "5": { "x": -23.86, "y": -0.27 }, 85 | "6": { "x": -1.28, "y": -0.67 }, 86 | "7": { "x": 20.23, "y": -0.67 }, 87 | "8": { "x": 42.27, "y": -0.27 }, 88 | "9": { "x": 67.54, "y": -0.67 }, 89 | "a": { "x": -159.6, "y": -58.17 }, 90 | "b": { "x": -139.2, "y": -77.69 }, 91 | "c": { "x": -115.5, "y": -91.63 }, 92 | "d": { "x": -91.94, "y": -101.99 }, 93 | "e": { "x": -64.52, "y": -110.36 }, 94 | "f": { "x": -35.48, "y": -115.5 }, 95 | "g": { "x": -6.57, "y": -116.62 }, 96 | "h": { "x": 24.94, "y": -115.17 }, 97 | "i": { "x": 52.9, "y": -111.67 }, 98 | "j": { "x": 74.94, "y": -105.69 }, 99 | "k": { "x": 105.05, "y": -94.14 }, 100 | "l": { "x": 130.32, "y": -79.4 }, 101 | "m": { "x": 152.9, "y": -63.06 }, 102 | "n": { "x": -152.8, "y": -7.68 }, 103 | "o": { "x": -136.2, "y": -22.82 }, 104 | "p": { "x": -117.9, "y": -36.37 }, 105 | "q": { "x": -94.81, "y": -47.92 }, 106 | "r": { "x": -65.24, "y": -57.48 }, 107 | "s": { "x": -36.75, "y": -63.46 }, 108 | "t": { "x": -6.75, "y": -67.17 }, 109 | "u": { "x": 24.53, "y": -65.21 }, 110 | "v": { "x": 55.17, "y": -60.83 }, 111 | "w": { "x": 86.36, "y": -51.67 }, 112 | "x": { "x": 111.63, "y": -37.72 }, 113 | "y": { "x": 134.21, "y": -24.18 }, 114 | "z": { "x": 147.65, "y": -9.44 } 115 | } 116 | 117 | // Easter egg coordinates (relative to planchette's size). 118 | const easterEggLocation = { 119 | x: -200.78, 120 | y: 41.95 121 | } 122 | 123 | // Tooltip hovering area. Coordinates are relative to planchette's size. 124 | const tooltipHoverArea = { 125 | top: 0.57, 126 | left: 0.83, 127 | bottom: 0.87, 128 | right: 0.96 129 | } 130 | 131 | const loadAnalyticsScript = function() { 132 | // We need DOMContentLoaded to execute fast, that's why we insert analytics scripts only AFTER the first 'load' event has fired. 133 | const script = document.createElement("script") 134 | script.setAttribute('data-domain', 'ouija.attejuvonen.fi') 135 | script.src = "https://plausible.io/js/plausible.js" 136 | document.getElementsByTagName("head")[0].appendChild(script) 137 | } 138 | 139 | const preloadImage = function(url) { 140 | const img = document.createElement("img") 141 | img.src = url 142 | img.style = "display: none;" 143 | img.alt = "" 144 | document.body.appendChild(img) 145 | } 146 | 147 | const preloadSomeImages = function() { 148 | // Preloading these images prevents a flicker issue with magnifying glass. This preload is delayed on purpose to improve initial load time. 149 | preloadImage("assets/ouija_bg_face_stare.jpg") 150 | preloadImage("assets/ouija_bg_face_no_eyes.jpg") 151 | preloadImage("assets/ouija_bg_face_outline.jpg") 152 | } 153 | 154 | const disableLoadingOverlay = function() { 155 | if (loadingOverlayDisabled) return 156 | loadingOverlayDisabled = true 157 | document.getElementById('loadingOverlay').style.opacity = 0 158 | setTimeout(() => { 159 | document.getElementById('loadingOverlay').style.display = 'none' 160 | }, 1000) 161 | } 162 | 163 | // Memoize some values to reduce reflows (improve performance). 164 | let boardWidth = 0 165 | let boardHeight = 0 166 | let boardTop = 0 167 | let boardLeft = 0 168 | let planchetteWidth = 0 169 | let planchetteHeight = 0 170 | let magSize = 0 171 | let windowWidth = 0 172 | let windowHeight = 0 173 | const resizeUpdates = function () { 174 | // Update memoized values 175 | const boardBounds = document.getElementById('boardContainer').getBoundingClientRect() 176 | boardTop = boardBounds.top 177 | boardLeft = boardBounds.left 178 | boardWidth = boardBounds.width 179 | boardHeight = boardBounds.height 180 | const planchette = document.getElementById('planchette') 181 | planchetteWidth = planchette.clientWidth 182 | planchetteHeight = planchette.clientHeight 183 | windowWidth = window.innerWidth 184 | windowHeight = window.innerHeight 185 | 186 | // Update magnifying glass 187 | const mag = document.getElementById('magnifying-glass') 188 | magSize = planchetteWidth * 0.40 189 | Object.assign(mag.style, { 190 | left: (100 * MAG_LEFT) + "%", 191 | top: (100 * MAG_TOP) + "%", 192 | width: magSize + 'px', 193 | height: magSize + 'px', 194 | backgroundSize: (MAG_ZOOM * 100 * boardWidth / magSize) + '% ' + (MAG_ZOOM * 100 * boardHeight / magSize) + '%', 195 | visibility: 'visible' 196 | }) 197 | updateMagnifyingGlassPosition() 198 | 199 | // Disable loading overlay 200 | disableLoadingOverlay() 201 | 202 | // Fix edge case where user sees 2 cursors when using keyboard shortcut to resize browser 203 | const cursor = document.getElementById("cursor") 204 | if (cursor) { 205 | cursor.style.visibility = "hidden"; 206 | } 207 | } 208 | // Call resizeUpdates after the first complete render and after each resize 209 | window.addEventListener('load', function (event) { 210 | resizeUpdates() 211 | setTimeout(() => { 212 | loadAnalyticsScript() 213 | preloadSomeImages() 214 | }, 500) 215 | }); 216 | window.addEventListener('resize', function (event) { 217 | resizeUpdates() 218 | }); 219 | 220 | const LOG_ENDPOINT = 'https://endpoint1.collection.eu.sumologic.com/receiver/v1/http/ZaVnC4dhaV0vBIvS0oahg-8LYhDkyFCtWZ2zZ_7NbP0x0PYd0DmEk2cLgA4DJlePqnHWB5KjcxPFudwdOmtop0b6isr9VgLeKHYmzJ6eSqkD0cyZ6FNQiQ==' 221 | const LOG_PROXY_ENDPOINT = 'https://lokittaja.atte-cloudflare.workers.dev/' 222 | const logToSumoLogic = function (message) { 223 | if (window.localStorage.getItem('ouija-dont-log-myself') || window.location.href.startsWith("file")) { 224 | // Exclude debug testing messages from logs (use incognito if you need to test logging) 225 | return 226 | } 227 | const augmentedMessage = `${window.localStorage.getItem(OUIJA_USER_ID)}:${Date.now()}:${using_GPT3 ? "GPT-3" : "Simple"}:${message}` 228 | fetch(`${LOG_ENDPOINT}?${augmentedMessage}`) 229 | .catch((error => { 230 | // Adblocker blocks direct requests to SumoLogic? Fallback to using log proxy 231 | fetch(`${LOG_PROXY_ENDPOINT}?${augmentedMessage}`, { mode: 'cors' }) 232 | .catch((error2) => { 233 | console.log('Logging failed', error, error2) 234 | }) 235 | })) 236 | } 237 | 238 | const paintCursorWithOffset = function (cursor, realX, realY) { 239 | let x = realX + offsetX * planchetteWidth / 100 240 | let y = realY + offsetY * planchetteHeight / 100 241 | cursor.style.top = (y - boardTop) + "px"; 242 | cursor.style.left = (x - boardLeft) + "px"; 243 | } 244 | 245 | const updateMagnifyingGlassPosition = function () { 246 | const mag = document.getElementById('magnifying-glass') 247 | const xMoveWithOffset = planchetteTransformX + offsetX 248 | const yMoveWithOffset = planchetteTransformY + offsetY 249 | 250 | // Move location of the magnifying glass 251 | const xMag = xMoveWithOffset * planchetteWidth / magSize 252 | const yMag = yMoveWithOffset * planchetteHeight / magSize 253 | mag.style.transform = `translateX(${xMag}%) translateY(${yMag}%)`; 254 | 255 | // Move background image inside magnifying glass (using pixels because I couldn't get % to work, this is why we have to call this method inside resizeUpdates) 256 | const xBgPosPx = -(xMoveWithOffset * planchetteWidth + MAG_LEFT * 100 * boardWidth) / 100 * MAG_ZOOM - magSize * MAG_ZOOM * (1 - 1 / MAG_ZOOM) / 2 257 | const yBgPosPx = -(yMoveWithOffset * planchetteHeight + MAG_TOP * 100 * boardHeight) / 100 * MAG_ZOOM - magSize * MAG_ZOOM * (1 - 1 / MAG_ZOOM) / 2 258 | mag.style.backgroundPosition = `${xBgPosPx}px ${yBgPosPx}px` 259 | } 260 | 261 | const updatePlanchettePosition = function () { 262 | const planchette = document.getElementById('planchette') 263 | const planchetteHelper = document.getElementById('planchetteHelper') 264 | 265 | planchette.style.transform = `translateX(${planchetteTransformX + offsetX}%) translateY(${planchetteTransformY + offsetY}%) rotate(130deg)`; 266 | planchetteHelper.style.transform = `translateX(${planchetteTransformX}%) translateY(${planchetteTransformY}%) rotate(130deg)`; 267 | updateMagnifyingGlassPosition() 268 | } 269 | 270 | const spiritGuidanceToOffset = function (x, y, diffX, diffY, goalX, goalY, dist) { 271 | const accelDistanceMultiplier = (SPIRIT_MAX_DIST - dist) / SPIRIT_MAX_DIST 272 | if (Math.random() > SPIRIT_STOCHASTIC_STR) { 273 | return 274 | } 275 | if (diffX != 0 && Math.sign(goalX - x) == Math.sign(diffX)) { // X axis move is in preferable direction 276 | if (dist > 5) { // reduce jittery accels on top of goal 277 | offsetX += diffX * SPIRIT_ACCEL_STR * 1.3 * 100.0 / planchetteWidth // accelerate 278 | } 279 | } else if (diffX != 0) { // X axis move is in undesired direction 280 | if (dist < 10) { // sharp cutoff for deceleration to reinforce feeling of "magnetic force" and to reduce unnecessary offsetting 281 | offsetX -= diffX * SPIRIT_ACCEL_STR * accelDistanceMultiplier * 100.0 / planchetteWidth // decelerate 282 | } 283 | } 284 | if (diffY != 0 && Math.sign(goalY - y) == Math.sign(diffY)) { // Y axis move is in preferable direction 285 | if (dist > 5) { 286 | offsetY += diffY * SPIRIT_ACCEL_STR * 1.3 * 100.0 / planchetteHeight // accelerate 287 | } 288 | } else if (diffY != 0) { 289 | if (dist < 10) { 290 | offsetY -= diffY * SPIRIT_ACCEL_STR * accelDistanceMultiplier * 100.0 / planchetteHeight // decelerate 291 | } 292 | } 293 | } 294 | 295 | const clearOffsets = function () { 296 | if (offsetX != 0) { 297 | planchetteTransformX += offsetX // This negates any effect on planchette positioning 298 | offsetX = 0 299 | } 300 | if (offsetY != 0) { 301 | planchetteTransformY += offsetY 302 | offsetY = 0 303 | } 304 | } 305 | 306 | const cancelSomeOffset = function (diffX, diffY, urgency) { 307 | // Will accelerate or decelerate latest mouse move in an effort to reduce offset 308 | const e = OFFSET_CANCELLATION_STR * urgency 309 | if (Math.abs(offsetX) > 1) { 310 | const sign = (Math.sign(offsetX) == Math.sign(diffX) ? -1 : 1) // Decelerate or accelerate depending on sign 311 | offsetX += sign * diffX * e 312 | if (!draggingPlanchette) { 313 | // When planchette is not being dragged, we need to do this to "negate" the effect on planchette position 314 | planchetteTransformX -= sign * diffX * e 315 | } 316 | } 317 | if (Math.abs(offsetY) > 1) { 318 | const sign = (Math.sign(offsetY) == Math.sign(diffY) ? -1 : 1) 319 | offsetY += sign * diffY * e 320 | if (!draggingPlanchette) { 321 | planchetteTransformY -= sign * diffY * e 322 | } 323 | } 324 | } 325 | 326 | const startedHoverOnTooltip = function () { 327 | document.getElementById('tooltipSymbol').style.filter = 'blur(2px)' 328 | // Set 'from' attribute to make animation smooth even when the user abruptly moves in and out with the mouse 329 | document.getElementById('animateToFocus').setAttributeNS(null, 'from', document.getElementById('smokeFilterMap').scale.animVal) 330 | document.getElementById('animateToFocus').beginElement() 331 | } 332 | 333 | const stoppedHoverOnTooltip = function () { 334 | if (!isBlackSmokeClickable) return 335 | document.getElementById('tooltipSymbol').style.filter = 'blur(11px)' 336 | document.getElementById('animateRemoveFocus').setAttributeNS(null, 'from', document.getElementById('smokeFilterMap').scale.animVal) 337 | document.getElementById('animateRemoveFocus').beginElement() 338 | } 339 | 340 | const displayEasterEgg = function() { 341 | if (!easterEggVisible) { 342 | easterEggVisible = true 343 | document.getElementById('boardEasterEggHelper').style.display = 'block' 344 | document.getElementById('boardEasterEggHelper').offsetHeight // Trigger reflow. 345 | document.getElementById('boardEasterEggHelper').style.opacity = 1 346 | setTimeout(() => { 347 | document.getElementById('board').src = 'assets/ouija_bg_face_outline.jpg' 348 | document.getElementById('magnifying-glass').style.backgroundImage = 'assets/ouija_bg_face_stare.jpg' 349 | document.getElementById('boardEasterEggHelper').style.display = 'none' 350 | }, 4000) 351 | } 352 | } 353 | 354 | const flyBanshee = function() { 355 | easterEggVisible = false 356 | gameOver = true 357 | document.getElementById('audio-scream1').play() 358 | // Stop blinking caret 359 | document.getElementById('userMessagePre').classList = ['orangey-text'] 360 | // Clear easter egg from board (because it flies into the screen) 361 | document.getElementById('board').src = 'assets/ouija_bg.jpg' 362 | document.getElementById('magnifying-glass').style.backgroundImage = 'assets/ouija_bg.jpg' 363 | // Fly, banshee, fly 364 | document.getElementById('banshee').style.opacity = 0.7 365 | document.getElementById('banshee').style.animationName = 'banshee-flying' 366 | setTimeout(() => { 367 | document.getElementById('glassCrack').style.display = 'block' 368 | document.getElementById('audio-crack1').play() 369 | turn = 'no-one' 370 | document.getElementById('planchette').classList = ['planchette-no-glow'] 371 | }, 300) 372 | setTimeout(() => { 373 | if (!pauseSmokeAnimation) { 374 | document.getElementById('tooltipSymbol').innerText = '' 375 | document.getElementById('tooltipSymbol').innerHTML = '' // shape smoke into smiley emoticon 376 | startedHoverOnTooltip() // make smoke focus so smiley emoticon is easier to notice 377 | isBlackSmokeClickable = false 378 | } 379 | }, 600) 380 | setTimeout(() => { 381 | if (!pauseSmokeAnimation) { 382 | stopSmokeAnimation() 383 | } 384 | }, 2500) 385 | setTimeout(() => { 386 | unlockAchievement(FALSE_PROPHETS_ACHIEVEMENT) 387 | }, 3000) 388 | } 389 | 390 | const jackSound = function() { 391 | setTimeout(() => { 392 | document.getElementById('audio-jack').play() 393 | }, 1000) 394 | } 395 | 396 | const lightFlash = function() { 397 | document.getElementById('lightFlash').style.display = 'block'; 398 | setTimeout(() => { 399 | document.getElementById('lightFlash').style.display = 'none'; 400 | }, 50) 401 | } 402 | 403 | const preloadAudio = function(id) { 404 | const a = document.getElementById(id) 405 | a.volume = 0.0 406 | a.play() 407 | setTimeout(() => { 408 | a.pause() 409 | a.currentTime = 0 410 | a.volume = 1.0 411 | }, 100) 412 | } 413 | 414 | const questLineTick = function() { 415 | if (!showTips) { 416 | return 417 | } 418 | if (currentTooltip === 0) delayedCreateTooltip(1) 419 | else if (currentTooltip === 1) { 420 | logToSumoLogic('!REVEALED_FIRST_SPIRIT_MESSAGE') 421 | delayedCreateTooltip(2) 422 | } 423 | else if (currentTooltip === 2 && questGoals.who <= 0) { 424 | logToSumoLogic('!SOLVED_QUEST_1') 425 | delayedCreateTooltip(3) 426 | } 427 | else if (currentTooltip === 3 && questGoals.where <= 0) { 428 | logToSumoLogic('!SOLVED_QUEST_2') 429 | delayedCreateTooltip(4) 430 | preloadAudio('audio-scream1') 431 | preloadAudio('audio-crack1') 432 | } 433 | // Easter egg face on board 434 | if (currentTooltip >= 3 && turn === TURN_SPIRIT) { 435 | displayEasterEgg() 436 | preloadAudio('audio-drum1') 437 | } 438 | } 439 | 440 | // When the player picks up the planchette, the spirit tugs slightly in the direction of the goal. 441 | // This tug is different from spiritGuidance, because guidance only accelerates or decelerates movements 442 | // that the player makes. This tug is independent of the player's movements. As such, it provides 443 | // a different sensation and variation to the "feel" of the game. However, the main purpose of this tug 444 | // is to allow the player to discover goals faster. Without the tug players have to slowly and painfully 445 | // hover over all the letters. With the tug players can immediately go in the right direction (once they 446 | // learn to flow along with the mechanic). 447 | // 448 | // Problem: many players ONLY noticed spirit tug, never noticed guidance effects, so they played 449 | // the game by just doing click-click-click. Boring! In order to encourage players to discover all 450 | // mouse effects, a 15-second timer was added, so this tug can not be initiated consecutively. 451 | const TUG_STRENGTH = 1/15 452 | let lastTugTime = 0 453 | const beginSpiritTug = function() { 454 | if (easyMode) { 455 | easyModeRecursiveTimerSpiritTug() 456 | return 457 | } 458 | const currTugTime = Date.now() 459 | if (lastTugTime + 15000 > currTugTime) return 460 | lastTugTime = currTugTime 461 | let maxMovesLeft = 20 462 | let recursiveTimerSpiritTug = function () { 463 | if (remainingGoals.length === 0) return 464 | if (maxMovesLeft <= 0) return 465 | const goalX = goalCoords[remainingGoals[0]].x 466 | const goalY = goalCoords[remainingGoals[0]].y 467 | const diffX = goalX - (planchetteTransformX + offsetX) 468 | const diffY = goalY - (planchetteTransformY + offsetY) 469 | const dist = Math.sqrt(diffX * diffX + diffY * diffY) 470 | offsetX += diffX * maxMovesLeft * TUG_STRENGTH / dist 471 | offsetY += diffY * maxMovesLeft * TUG_STRENGTH / dist 472 | maxMovesLeft -= 1 473 | updatePlanchettePosition() 474 | paintCursorWithOffset(document.getElementById("cursor"), prevX, prevY) 475 | setTimeout(() => { recursiveTimerSpiritTug() }, 10) 476 | } 477 | recursiveTimerSpiritTug() 478 | } 479 | 480 | const EASY_MODE_TUG_STRENGTH = 1/25 481 | let easyModeRecursiveTimerSpiritTug = function () { 482 | if (remainingGoals.length === 0) return 483 | const goalX = goalCoords[remainingGoals[0]].x 484 | const goalY = goalCoords[remainingGoals[0]].y 485 | const diffX = goalX - (planchetteTransformX + offsetX) 486 | const diffY = goalY - (planchetteTransformY + offsetY) 487 | offsetX += diffX * EASY_MODE_TUG_STRENGTH 488 | offsetY += diffY * EASY_MODE_TUG_STRENGTH 489 | updatePlanchettePosition() 490 | paintCursorWithOffset(document.getElementById("cursor"), prevX, prevY) 491 | setTimeout(() => { 492 | if (draggingPlanchette) { 493 | easyModeRecursiveTimerSpiritTug() 494 | } 495 | }, 10) 496 | } 497 | 498 | const mouseMoved = function (event, onObject) { 499 | const currX = event.clientX 500 | const currY = event.clientY 501 | const diffX = (userMoveCount > 0 ? currX - prevX : 0) 502 | const diffY = (userMoveCount > 0 ? currY - prevY : 0) 503 | prevX = currX 504 | prevY = currY 505 | userMoveCount += 1 506 | 507 | if (draggingPlanchette) { 508 | const x = planchetteTransformX + offsetX + diffX * 100.0 / planchetteWidth 509 | const y = planchetteTransformY + offsetY + diffY * 100.0 / planchetteHeight 510 | if (y > HARD_Y_MAX || y < HARD_Y_MIN || x > HARD_X_MAX || x < HARD_X_MIN) { 511 | // Prevent planchette from being moved outside board 512 | stopDraggingPlanchette(event) 513 | return 514 | } 515 | planchetteTransformX += diffX * 100.0 / planchetteWidth 516 | planchetteTransformY += diffY * 100.0 / planchetteHeight 517 | 518 | const goalX = remainingGoals.length > 0 ? goalCoords[remainingGoals[0]].x : -10000 519 | const goalY = remainingGoals.length > 0 ? goalCoords[remainingGoals[0]].y : -10000 520 | 521 | // Possibly accelerate/decelerate move by modifying cursor offset 522 | const dist = Math.sqrt((x - goalX) * (x - goalX) + (y - goalY) * (y - goalY)) 523 | if (dist < SPIRIT_MAX_DIST) { 524 | // Modify cursor offset to guide the user towards goal 525 | spiritGuidanceToOffset(x, y, diffX, diffY, goalX, goalY, dist) 526 | } else { 527 | // User dragging planchette but is not near goal, use this opportunity to 528 | // reduce cursor offset to prevent the real cursor from escaping window. 529 | if (y < SOFT_Y_MAX && y > SOFT_Y_MIN && x < SOFT_X_MAX && x > SOFT_X_MIN) { 530 | // Soft limits are used to prevent us from accelerating the user beyond hard limits where planchette would be trapped 531 | cancelSomeOffset(diffX, diffY, OFFSET_CANCELLATION_LOW_URGENCY) 532 | } 533 | } 534 | } else { 535 | // User is not dragging planchette. 536 | 537 | // Take the opportunity to reduce cursor offset to prevent the real cursor from escaping window. 538 | // We need coordinates relative to viewport. 539 | let x = (currX + offsetX * planchetteWidth / 100) / windowWidth 540 | let y = (currY + offsetY * planchetteHeight / 100) / windowHeight 541 | const minimumEscapeDistance = Math.min(Math.abs(x - 0), Math.abs(x - 1), Math.abs(y - 1), Math.abs(y - 0)) 542 | if (minimumEscapeDistance > 0.04) { 543 | // TODO instead of linear we need to have exponential urgency towards the edges of window 544 | const urgency = Math.max(1 - minimumEscapeDistance / 0.30, OFFSET_CANCELLATION_LOW_URGENCY) 545 | cancelSomeOffset(diffX, diffY, urgency) 546 | } else { 547 | // We are too close to the edge, immediately cancel all offset 548 | clearOffsets() 549 | } 550 | 551 | // Determine if user's fake cursor is hovering over tooltip. 552 | // We need coordinates relative to board (to support browser resizing). 553 | x = (x * windowWidth - boardLeft) / boardWidth 554 | y = (y * windowHeight - boardTop) / boardHeight 555 | if (showTips && !popupIsOpen && !pauseSmokeAnimation && x > tooltipHoverArea.left && x < tooltipHoverArea.right && y > tooltipHoverArea.top && y < tooltipHoverArea.bottom) { 556 | // We are hovering over tooltip. 557 | if (!hoveringOverTooltip) { 558 | // We just now entered the hover area. 559 | hoveringOverTooltip = true 560 | startedHoverOnTooltip() 561 | } 562 | } else { 563 | // We are not hovering over tooltip (or popup is open so we dont do the hover effect anyway). 564 | if (hoveringOverTooltip) { 565 | // We just now left the hover area. 566 | hoveringOverTooltip = false 567 | stoppedHoverOnTooltip() 568 | } 569 | } 570 | 571 | } 572 | updatePlanchettePosition() 573 | 574 | const chooseCursorIcon = function () { 575 | if (draggingPlanchette) return 'assets/grabbing.cur' 576 | if (onObject === ON_PLANCHETTE && turn === TURN_SPIRIT) return 'assets/grab.cur' 577 | if (onObject === ON_BUTTON) return 'assets/aero_link.cur' 578 | if (hoveringOverTooltip) return 'assets/aero_link.cur' 579 | return 'assets/aero_arrow.cur' 580 | } 581 | 582 | const cursor = document.getElementById("cursor") 583 | const newCursorSrc = chooseCursorIcon() 584 | if (cursor.src !== newCursorSrc) cursor.src = newCursorSrc 585 | cursor.style.visibility = "visible"; 586 | paintCursorWithOffset(cursor, prevX, prevY) 587 | 588 | event.preventDefault() 589 | } 590 | 591 | const startDraggingPlanchette = function (event) { 592 | if (turn === TURN_SPIRIT) { 593 | draggingPlanchette = true 594 | cursor.src = "assets/grabbing.cur" 595 | if (easterEggVisible) document.getElementById('magnifying-glass').style.backgroundImage = "url('assets/ouija_bg_face_stare.jpg')" 596 | beginSpiritTug() 597 | event.preventDefault() // Fixes issue in Firefox that made dragging immediately stop. 598 | } 599 | } 600 | const stopDraggingPlanchette = function (event, source) { 601 | draggingPlanchette = false 602 | const newCursor = (source === ON_PLANCHETTE ? 'assets/grab.cur' : 'assets/aero_arrow.cur') 603 | cursor.src = newCursor 604 | const x = planchetteTransformX + offsetX 605 | const y = planchetteTransformY + offsetY 606 | if (easterEggVisible) { 607 | document.getElementById('magnifying-glass').style.backgroundImage = "url('assets/ouija_bg_face_no_eyes.jpg')" 608 | const c = easterEggLocation 609 | const dist = Math.sqrt((x - c.x) * (x - c.x) + (y - c.y) * (y - c.y)) 610 | if (dist < CHAR_SELECT_MAX_DIST) { 611 | unlockAchievement(EASTER_EGG_ACHIEVEMENT) 612 | } 613 | } 614 | if (source === ON_PLANCHETTE) { 615 | if (debug[RECORDING]) { 616 | const chars = ALLOWED_CHARS 617 | let i = 0 618 | while (debug[RECORDING][chars[i]]) i++ 619 | debug[RECORDING][chars[i]] = { 620 | x: Math.round(100 * x) / 100, 621 | y: Math.round(100 * y) / 100, 622 | } 623 | console.log(debug[RECORDING]) 624 | } else { 625 | // What is the closest character to planchette 626 | let closestDist = 99999999 627 | let closestChar = 'a' 628 | for (let i = 0; i < ALLOWED_CHARS.length; i++) { 629 | const char = ALLOWED_CHARS[i] 630 | const c = goalCoords[char] 631 | const dist = Math.sqrt((x - c.x) * (x - c.x) + (y - c.y) * (y - c.y)) 632 | if (dist < closestDist) { 633 | closestDist = dist 634 | closestChar = char 635 | } 636 | } 637 | if (closestDist < CHAR_SELECT_MAX_DIST) { 638 | // Close enough, interpret that user intends to select this character 639 | if (remainingGoals.length > 0 && closestChar === remainingGoals[0]) { 640 | // Selected character was goal, move onto the next goal. 641 | addCharToRevealedMessage(closestChar) 642 | remainingGoals = remainingGoals.substring(1) 643 | if (remainingGoals.length === 0) { 644 | thingsToDoAfterSpiritMessageHasBeenRevealed() 645 | } 646 | // Reset tug timer (if player is not on easy mode, when they pick up the planchette, there will be a small tug available again) 647 | lastTugTime = 0 648 | } 649 | } 650 | } 651 | } 652 | event.stopPropagation() 653 | } 654 | 655 | const zalgoChars = { 656 | up: [ 657 | '\u030d', '\u030e', '\u0311', 658 | '\u0306', '\u0310', '\u0352', '\u0357', '\u0351', '\u0307', 659 | '\u0308', '\u030a', '\u0342', '\u0343', '\u0344', '\u034a', 660 | '\u034b', '\u034c', '\u0303', '\u0302', '\u030c', '\u0350', 661 | '\u0300', '\u0301', '\u030b', '\u030f', '\u0312', '\u0313', 662 | '\u0314', '\u033d', '\u0309', '\u033e', '\u035b', 663 | ], 664 | upLetters: { 665 | a: '\u0363', 666 | e: '\u0364', 667 | i: '\u0365', 668 | o: '\u0366', 669 | u: '\u0367', 670 | c: '\u0368', 671 | d: '\u0369', 672 | h: '\u036a', 673 | m: '\u036b', 674 | r: '\u036c', 675 | t: '\u036d', 676 | v: '\u036e', 677 | x: '\u036f', 678 | s: '\u033e' 679 | }, 680 | middle: [ 681 | '\u0315', '\u031b', '\u0340', '\u0341', '\u0358', '\u0327', '\u0328' 682 | ], 683 | down: [ 684 | '\u0316', '\u0317', '\u0318', '\u0319', '\u031c', '\u031d', 685 | '\u031e', '\u031f', '\u0320', '\u0324', '\u0325', '\u0326', 686 | '\u0329', '\u032a', '\u032b', '\u032c', '\u032d', '\u032e', 687 | '\u032f', '\u0330', '\u0331', '\u0332', '\u0333', '\u0339', 688 | '\u033a', '\u033b', '\u033c', '\u0347', '\u0348', 689 | '\u0349', '\u034d', '\u034e', '\u0353', '\u0354', '\u0355', 690 | '\u0356', '\u0359', '\u035a', '\u0323' 691 | ] 692 | } 693 | 694 | function zalgoRandInt(min, max) { 695 | return Math.floor(Math.random() * (max - min + 1)) + min 696 | } 697 | 698 | function zalgoAddRandomChars(charIn, conf) { 699 | let charOut = charIn 700 | for (const direction in conf) { 701 | const arr = zalgoChars[direction] 702 | for (let count=0; count=0; i--) { 712 | charOut += zalgoChars.upLetters[letters[i]] || "" 713 | } 714 | return charOut 715 | } 716 | 717 | const zalgoMessages = [ 718 | 'hecomes', 719 | 'ithurts', 720 | 'cthuihu', 721 | 'xerxes', 722 | 'doom', 723 | // TODO: more 724 | // no diatrics characters available to represent: tainted, unholy, toolate, scourge, pestilence, zalgo, mortal, breach, corrupt, pestilence, extinguish 725 | ] 726 | 727 | const zalgoize = function(char) { 728 | if (currentExchangeNumber === 0) { 729 | // Special case here. The interaction begins with no zalgoization and gradually increases in intensity. 730 | return char 731 | } 732 | const MAX_INTENSITY_REACHED_AFTER_EXCHANGE_NUMBER = 20.0 733 | const intensity = Math.min(currentExchangeNumber / MAX_INTENSITY_REACHED_AFTER_EXCHANGE_NUMBER, 1.0) 734 | if (intensity === 1.0 && Math.random() > 0.9) { 735 | // Special case here. Once max intensity is reached we occasionally drop in a hidden message in zalgotext 736 | char = zalgoAddRandomChars(char, { up: 2, down: 2 } ) 737 | char = zalgoAddSpecificLetters(char, zalgoMessages[zalgoRandInt(0, zalgoMessages.length-1)]) 738 | char = zalgoAddRandomChars(char, { up: 2 } ) 739 | return char 740 | } 741 | // Typical case below. 742 | if (intensity < 1.0 && Math.random()*2 > intensity+0.3) { 743 | // Before we reach max intensity, we want only occasional characters to be zalgoized. 744 | // After we reach max intensity, all characters will be zalgoized. 745 | return char 746 | } 747 | conf = { 748 | up: zalgoRandInt(2, Math.max(2, Math.round(14 * intensity))), 749 | middle: zalgoRandInt(0, 5), 750 | down: zalgoRandInt(1, Math.max(1, Math.round(6 * intensity))) 751 | } 752 | char = zalgoAddRandomChars(char, conf) 753 | return char 754 | } 755 | 756 | const addCharToRevealedMessage = function (char) { 757 | revealedSpiritLetters += zalgoize(char) 758 | const container = document.getElementById('spiritMessageContainer') 759 | container.innerText = revealedSpiritLetters 760 | container.setAttribute('data-text', revealedSpiritLetters) 761 | } 762 | 763 | const switchTurnToSpirit = function () { 764 | turn = TURN_SPIRIT 765 | 766 | // Clear previously revealed spirit message 767 | revealedSpiritLetters = '' 768 | 769 | // Indicate to user that planchette can be dragged 770 | document.getElementById('planchette').classList = [' planchette-active-glow'] 771 | 772 | // Clear previously revealed answer from visuals 773 | const container = document.getElementById('spiritMessageContainer') 774 | container.innerText = '' 775 | container.setAttribute('data-text', '') 776 | 777 | // Stop blinking caret 778 | document.getElementById('userMessagePre').classList = ['orangey-text'] 779 | 780 | // Maybe update tooltip 781 | questLineTick() 782 | } 783 | 784 | const getInitialDelay = function() { 785 | // Intent is to emulate a human who takes a little bit longer before starting to write 786 | return 700 + Math.round(Math.random() * 1000) 787 | } 788 | 789 | const thingsToDoAfterSpiritMessageHasBeenRevealed = function() { 790 | turn = TURN_USER 791 | console.log('Spirit: ' + revealedSpiritLetters.toUpperCase()) 792 | document.getElementById('planchette').classList = ['planchette-no-glow'] 793 | document.getElementById('userMessagePre').innerText = '' 794 | document.getElementById('userMessagePre').classList = ['blinking-caret'] 795 | currentExchangeNumber++ 796 | questLineTick() 797 | if (completionEffect) { 798 | setTimeout(() => { 799 | completionEffect() 800 | completionEffect = null 801 | }, 1000 + Math.round(Math.random() * 3000)) 802 | } 803 | } 804 | 805 | const getConsecutiveDelay = function(nextChar) { 806 | // Intent is to emulate a human with varying delays between key presses 807 | const prev1 = revealedSpiritLetters.charAt(revealedSpiritLetters.length-1) 808 | const prev2 = revealedSpiritLetters.charAt(revealedSpiritLetters.length-2) 809 | if (nextChar == prev1 || nextChar == prev2) { 810 | return 60 + Math.round(Math.random() * 120) 811 | } 812 | return 80 + Math.round(Math.random() * 400) 813 | } 814 | 815 | const recursivelyRevealSpiritMessage = function (delayParam) { 816 | turn = 'no-one' 817 | document.getElementById('planchette').classList = ['planchette-no-glow'] 818 | if (remainingGoals.length === 0) { 819 | thingsToDoAfterSpiritMessageHasBeenRevealed() 820 | } else { 821 | const delay = delayParam ?? getInitialDelay() 822 | setTimeout(() => { 823 | const c = remainingGoals[0] 824 | const nextDelay = getConsecutiveDelay(c) 825 | remainingGoals = remainingGoals.substring(1) 826 | addCharToRevealedMessage(c) 827 | recursivelyRevealSpiritMessage(nextDelay) 828 | }, delay) 829 | } 830 | } 831 | 832 | const spiritIsReadyToCommunicate = function (rawMessage) { 833 | const message = rawMessage.toLocaleLowerCase().replace(/[^0-9a-z]/gi, '') 834 | const container = document.getElementById('spiritMessageContainer') 835 | 836 | logToSumoLogic(previousInput + " -> " + message) 837 | 838 | remainingGoals = message 839 | if (speedMode) { 840 | recursivelyRevealSpiritMessage() 841 | } 842 | } 843 | 844 | let currentTooltip = 0 845 | 846 | const delayedCreateTooltip = function(i) { 847 | currentTooltip = i 848 | if (!using_GPT3) stopSmokeAnimation() 849 | setTimeout(() => createTooltip(i), 2500) 850 | } 851 | 852 | const createTooltip = function (i) { 853 | if (!SCRIPTED_TOOLTIPS) { 854 | // Possibly slow connection and chatbot.js hasn't loaded yet 855 | setTimeout(() => createTooltip(i), 500) 856 | return 857 | } 858 | const t = SCRIPTED_TOOLTIPS[i] 859 | document.getElementById('tooltipSymbol').innerText = t.tooltip 860 | document.getElementById('tooltipContent').innerHTML = `

${t.headline}

\n${t.paragraphs.map((str) => '

' + str + '

\n').join("")}` 861 | if (showTips && !using_GPT3) { 862 | startSmokeAnimation() 863 | } 864 | } 865 | 866 | const openTooltipPopup = function (e) { 867 | clearOffsets() 868 | mouseMoved(e) // To update cursor location after clearing offsets. 869 | document.getElementById('board').style.filter = `blur(10px) brightness(0.6)` 870 | document.getElementById('planchette').style.filter = `blur(10px) brightness(0.6)` 871 | document.getElementById('planchetteHelper').style.filter = `blur(10px) brightness(0.6)` 872 | document.getElementById('spiritMessageContainer').style.visibility = `hidden` 873 | document.getElementById('userMessageContainer').style.visibility = `hidden` 874 | document.getElementById('tooltipPopup').style.opacity = '1' 875 | document.getElementById('tooltipPopup').style.display = `block` 876 | } 877 | 878 | const closeTooltipPopup = function (shutUp) { 879 | if (shutUp) toggleShowTips() 880 | document.getElementById('settingsGear').classList.remove(['one-time-gear-roll']) 881 | document.getElementById('planchette').style.removeProperty('filter') 882 | document.getElementById('tooltipPopup').style.display = 'none' 883 | document.getElementById('board').style.filter = `none`; 884 | document.getElementById('planchetteHelper').style.filter = `none`; 885 | document.getElementById('spiritMessageContainer').style.visibility = `visible` 886 | document.getElementById('userMessageContainer').style.visibility = `visible`; 887 | } 888 | 889 | let isBlackSmokeClickable = true 890 | const toggleTooltipPopup = function (e, shutUp) { 891 | if (!isBlackSmokeClickable) return 892 | popupIsOpen = !popupIsOpen 893 | popupIsOpen ? openTooltipPopup(e) : closeTooltipPopup(shutUp) 894 | } 895 | 896 | const openSettingsPopup = function () { 897 | document.getElementById('settingsGear').classList.add(['one-time-gear-roll']) 898 | document.getElementById('board').style.filter = `blur(10px) brightness(0.6)` 899 | document.getElementById('planchette').style.filter = `blur(10px) brightness(0.6)` 900 | document.getElementById('planchetteHelper').style.filter = `blur(10px) brightness(0.6)` 901 | document.getElementById('spiritMessageContainer').style.visibility = `hidden` 902 | document.getElementById('userMessageContainer').style.visibility = `hidden` 903 | document.getElementById('settingsPopup').style.opacity = '1' 904 | document.getElementById('settingsPopup').style.display = `block` 905 | } 906 | 907 | const closeSettingsPopup = function () { 908 | document.getElementById('settingsGear').classList.remove(['one-time-gear-roll']) 909 | document.getElementById('planchette').style.removeProperty('filter') 910 | document.getElementById('settingsPopup').style.display = 'none' 911 | document.getElementById('board').style.filter = `none`; 912 | document.getElementById('planchetteHelper').style.filter = `none`; 913 | document.getElementById('spiritMessageContainer').style.visibility = `visible` 914 | document.getElementById('userMessageContainer').style.visibility = `visible`; 915 | } 916 | 917 | const toggleSettingsPopup = function () { 918 | popupIsOpen = !popupIsOpen 919 | popupIsOpen ? openSettingsPopup() : closeSettingsPopup() 920 | } 921 | 922 | const toggleEasyMode = function() { 923 | easyMode = !easyMode 924 | const slider = document.getElementById('easyModeSliderSlider') 925 | if (easyMode) { 926 | slider.classList.add(['checked']) 927 | } else { 928 | slider.classList.remove(['checked']) 929 | } 930 | } 931 | 932 | const toggleShowTips = function () { 933 | showTips = !showTips 934 | const slider = document.getElementById('showTipsSliderSlider') 935 | if (showTips) { 936 | slider.classList.add(['checked']) 937 | if (!using_GPT3) startSmokeAnimation() 938 | } else { 939 | slider.classList.remove(['checked']) 940 | if (!using_GPT3) stopSmokeAnimation() 941 | } 942 | } 943 | 944 | const toggleRevealMouse = function () { 945 | revealMouse = !revealMouse 946 | const slider = document.getElementById('revealMouseSliderSlider') 947 | if (!revealMouse) { 948 | slider.classList.remove(['checked']) 949 | document.getElementById('planchetteHelper').style.opacity = 0 950 | document.getElementById('bg').style.cursor = 'none' 951 | document.getElementById('hoverBoard').style.cursor = 'none' 952 | document.getElementById('planchette').style.cursor = 'none' 953 | document.getElementById('planchetteHelper').style.cursor = 'none' 954 | } else { 955 | slider.classList.add(['checked']) 956 | document.getElementById('planchetteHelper').style.opacity = 0.5 957 | document.getElementById('bg').style.cursor = 'auto' 958 | document.getElementById('hoverBoard').style.cursor = 'auto' 959 | document.getElementById('planchette').style.cursor = 'auto' 960 | document.getElementById('planchetteHelper').style.cursor = 'auto' 961 | } 962 | } 963 | 964 | const toggleSpeedMode = function () { 965 | speedMode = !speedMode 966 | const slider = document.getElementById('speedModeSliderSlider') 967 | if (speedMode) { 968 | slider.classList.add(['checked']) 969 | localStorage.setItem('ouija-speedmode', true) 970 | } else { 971 | slider.classList.remove(['checked']) 972 | localStorage.removeItem('ouija-speedmode') 973 | } 974 | if (speedMode && remainingGoals.length > 0) { 975 | recursivelyRevealSpiritMessage() 976 | } 977 | } 978 | 979 | const toggleOpenAI = function () { 980 | using_GPT3 = !using_GPT3 981 | const slider = document.getElementById('openAISliderSlider') 982 | if (!using_GPT3) { 983 | slider.classList.remove(['checked']) 984 | document.getElementById('settingsPopupButtonsContainer').style.marginTop = '1.5vw' 985 | document.getElementById('textInputAPIKey').style.marginTop = '1.7vw' 986 | if (showTips) startSmokeAnimation() 987 | } else { 988 | slider.classList.add(['checked']) 989 | document.getElementById('settingsPopupButtonsContainer').style.marginTop = '4.1vw' 990 | document.getElementById('textInputAPIKey').value = window.localStorage.getItem('ouija-openai-api-key') 991 | document.getElementById('textInputAPIKey').style.marginTop = '1vw' 992 | if (showTips) stopSmokeAnimation() 993 | } 994 | 995 | } 996 | 997 | const userPastedAPIKey = function () { 998 | window.localStorage.setItem('ouija-openai-api-key', document.getElementById('textInputAPIKey').value) 999 | } 1000 | 1001 | const mouseCheck = function () { 1002 | if (userMoveCount < 20) { 1003 | document.getElementById('mouseRequirement').style.color = 'red' 1004 | return false 1005 | } 1006 | return true 1007 | } 1008 | 1009 | const openConsentPopup = function () { 1010 | popupIsOpen = true 1011 | document.getElementById('board').style.filter = `blur(10px) brightness(0.6)` 1012 | document.getElementById('planchette').style.filter = `blur(10px) brightness(0.6)` 1013 | document.getElementById('planchetteHelper').style.filter = `blur(10px) brightness(0.6)` 1014 | document.getElementById('userMessageContainer').style.visibility = `hidden` 1015 | document.getElementById('consentPopup').style.display = `block` 1016 | document.getElementById('settingsGear').style.opacity = `0` 1017 | document.getElementById('settingsGear').style.transition = `opacity 3s` 1018 | } 1019 | 1020 | const closeConsentPopup = function () { 1021 | popupIsOpen = false 1022 | window.localStorage.setItem(OUIJA_USER_ID, 'user' + Math.round(1000000000 * Math.random())) 1023 | document.getElementById('consentPopup').style.opacity = '0' 1024 | document.getElementById('settingsGear').style.opacity = `0.7` 1025 | setTimeout(() => { 1026 | document.getElementById('consentPopup').style.display = 'none' 1027 | document.getElementById('settingsGear').style.removeProperty('opacity') 1028 | document.getElementById('settingsGear').style.removeProperty('transition') 1029 | createTooltip(0) 1030 | }, 3000) 1031 | document.getElementById('board').style.filter = `none`; 1032 | document.getElementById('planchette').style.removeProperty('filter') 1033 | document.getElementById('planchetteHelper').style.filter = `none`; 1034 | document.getElementById('userMessageContainer').style.visibility = `visible`; 1035 | document.getElementById('consentYes').classList.add(['one-time-bump']) 1036 | const elementsToFadeOut = document.getElementsByClassName('consentText') 1037 | for (let i = 0; i < elementsToFadeOut.length; i++) { 1038 | elementsToFadeOut[i].style.opacity = '0.4'; 1039 | } 1040 | document.getElementById('changingConsentText').innerText = 'Thank you for complying. We shall come for your soul after death.' 1041 | document.getElementById('consentYes').style.pointerEvents = 'none' 1042 | document.getElementById('consentNo').style.pointerEvents = 'none' 1043 | } 1044 | 1045 | const consentYes = function () { 1046 | if (!mouseCheck()) return 1047 | logToSumoLogic('!CONSENT_YES') 1048 | closeConsentPopup() 1049 | } 1050 | 1051 | const consentNo = function () { 1052 | if (!mouseCheck()) return 1053 | logToSumoLogic('!CONSENT_NO') 1054 | // Make it appear as if the user clicked 'yes', even though they actually clicked 'no' 1055 | const shiftXbuttonWidth = document.getElementById('consentNo').getBoundingClientRect().width * 100.0 / planchetteWidth 1056 | const shiftXmarginWidth = windowWidth * 1.5 / planchetteWidth 1057 | const shiftX = shiftXbuttonWidth + shiftXmarginWidth 1058 | offsetX -= shiftX // Shift mouse to consentYes button 1059 | planchetteTransformX += shiftX // Negate any effect on planchette's position 1060 | const cursor = document.getElementById("cursor") 1061 | paintCursorWithOffset(cursor, prevX, prevY) 1062 | closeConsentPopup() 1063 | } 1064 | 1065 | let pauseSmokeAnimation = true 1066 | let timerToPauseSmokeAnimation = null 1067 | const startSmokeAnimation = function () { 1068 | // We have a scale transition to start/stop smoke animation, 1069 | // and after that transition is done we want to kill our animation tick for performance reasons. 1070 | // The user might toggle on/off before the transition is completed. 1071 | if (timerToPauseSmokeAnimation) { 1072 | // This might happen if the user quickly toggles tips off and on. 1073 | clearTimeout(timerToPauseSmokeAnimation) 1074 | document.getElementById('animateRemoveFocus').beginElement() 1075 | } 1076 | document.getElementById('tooltipSymbol').style.transform = 'scale(1.0)' 1077 | document.getElementById('tooltipSymbol').style.filter = 'blur(11px)' 1078 | document.getElementById('tooltipSymbol').style.opacity = '1.0' 1079 | if (!pauseSmokeAnimation) { 1080 | return 1081 | } 1082 | pauseSmokeAnimation = false 1083 | const filter = document.getElementById("smokeFilterTurbulence"); 1084 | let frames = 1; 1085 | let rad = Math.PI / 180; 1086 | let bfx, bfy; 1087 | let prevTime = Date.now() 1088 | const tickSmokeAnimation = function () { 1089 | if (pauseSmokeAnimation) { 1090 | // This pause only exists for performance reasons, smoke would be hidden anyway. 1091 | return 1092 | } 1093 | // Use elapsedTime to stabilize animation speed regardless of device 1094 | const currTime = Date.now() 1095 | const timeElapsed = currTime - prevTime 1096 | prevTime = currTime 1097 | frames += .02 * timeElapsed 1098 | 1099 | bfx = 0.03; 1100 | bfy = 0.03; 1101 | 1102 | bfx += 0.005 * Math.cos(frames * rad); 1103 | bfy += 0.005 * Math.sin(frames * rad); 1104 | 1105 | bf = bfx.toString() + " " + bfy.toString(); 1106 | filter.setAttributeNS(null, "baseFrequency", bf); 1107 | 1108 | // Use requestIdleBallback instead of requestAnimationFrame, because 1109 | // we want our mouse move event listener callbacks to be prioritized 1110 | // higher than this low-prio animation in the queue for event loop. 1111 | 1112 | // Due to performance reasons we disable this animation on Firefox. 1113 | if (!reduceAnimations) window.requestIdleCallback(tickSmokeAnimation); 1114 | } 1115 | window.requestIdleCallback(tickSmokeAnimation); 1116 | } 1117 | 1118 | const stopSmokeAnimation = function () { 1119 | if (reduceAnimations) { 1120 | document.getElementById('tooltipSymbol').style.transform = 'scale(0.0)' 1121 | document.getElementById('tooltipSymbol').style.filter = 'blur(11px)' 1122 | document.getElementById('tooltipSymbol').style.opacity = '0.0' 1123 | return 1124 | } 1125 | document.getElementById('tooltipSymbol').style.transform = 'scale(3.0)' 1126 | document.getElementById('tooltipSymbol').style.filter = 'blur(13px)' 1127 | document.getElementById('tooltipSymbol').style.opacity = '0.0' 1128 | document.getElementById('animateDissipate').setAttributeNS(null, 'from', document.getElementById('smokeFilterMap').scale.animVal) 1129 | document.getElementById('animateDissipate').beginElement() 1130 | timerToPauseSmokeAnimation = setTimeout(() => { 1131 | pauseSmokeAnimation = true 1132 | document.getElementById('tooltipSymbol').classList.add('disable-transitions') 1133 | document.getElementById('tooltipSymbol').style.transform = 'scale(0.0)' 1134 | document.getElementById('tooltipSymbol').style.filter = 'blur(11px)' 1135 | document.getElementById('tooltipSymbol').style.opacity = '1.0' 1136 | document.getElementById('tooltipSymbol').offsetHeight // Trigger a reflow while transitions are disabled. 1137 | document.getElementById('tooltipSymbol').classList.remove('disable-transitions') 1138 | document.getElementById('animateRemoveFocus').beginElement() 1139 | }, 2000) 1140 | } 1141 | 1142 | const displaySuicidePreventionPopup = function() { 1143 | document.getElementById(`suicide-prevention-popup`).style.bottom = '20px' 1144 | document.getElementById(`suicide-prevention-popup`).style.transition = 'bottom 2s cubic-bezier(.12, 1, .96, .97)' 1145 | } 1146 | const hideSuicidePreventionPopup = function() { 1147 | document.getElementById(`suicide-prevention-popup`).style.bottom = '-17vw' 1148 | document.getElementById(`suicide-prevention-popup`).style.transition = 'bottom 2s ease-out' 1149 | } 1150 | const showSuicidePreventionOverlay = function() { 1151 | gameOver = true 1152 | document.getElementById('suicidePreventionOverlay').style.display = 'block' 1153 | // Hide blinking cursor 1154 | document.getElementById('userMessagePre').classList = ['orangey-text'] 1155 | } 1156 | document.getElementById('suicide-prevention-button-fine').addEventListener('click', hideSuicidePreventionPopup) 1157 | document.getElementById('suicide-prevention-button-help').addEventListener('click', e => { 1158 | hideSuicidePreventionPopup() 1159 | showSuicidePreventionOverlay() 1160 | window.localStorage.setItem(OUIJA_SUICIDE_PREVENTION_ACTIVATED, true) 1161 | }) 1162 | if (window.localStorage.getItem(OUIJA_SUICIDE_PREVENTION_ACTIVATED)) { 1163 | showSuicidePreventionOverlay() 1164 | } 1165 | 1166 | const displayNotification = function(achievement) { 1167 | document.getElementById(`${achievement}-notification`).style.bottom = '-3px' 1168 | document.getElementById(`${achievement}-notification`).style.transition = 'bottom 2s cubic-bezier(.12, 1, .96, .97)' 1169 | setTimeout(() => { 1170 | document.getElementById(`${achievement}-notification`).style.bottom = '-12vw' 1171 | document.getElementById(`${achievement}-notification`).style.transition = 'bottom 2s ease-in' 1172 | }, 4000) 1173 | } 1174 | 1175 | const activateAchievementIcon = function(achievement) { 1176 | // Remove not-achieved hint icon 1177 | document.getElementsByClassName('not-achieved')[0].remove() 1178 | // Add new element in its place 1179 | document.getElementById('achievement-row').innerHTML += ` 1180 | 1181 | 1182 | 1183 | ` 1184 | document.getElementById('achievement-row').offsetHeight // Trigger reflow. 1185 | setTimeout(() => { 1186 | // I don't understand why this doesn't work without timer. 1187 | document.getElementById(achievement).addEventListener('click', e => { trophyClicked(achievement) }) 1188 | }, 1000) 1189 | 1190 | } 1191 | 1192 | const unlockAchievement = function(achievement) { 1193 | if (window.localStorage.getItem(`ouija-${achievement}`)) { 1194 | // Only unlock once per achievement 1195 | return 1196 | } 1197 | if (achievement !== FALSE_PROPHETS_ACHIEVEMENT) { 1198 | document.getElementById('audio-drum1').play() 1199 | } 1200 | window.localStorage.setItem(`ouija-${achievement}`, 'true') 1201 | logToSumoLogic(`!ACHIEVEMENT_${achievement}`) 1202 | activateAchievementIcon(achievement) 1203 | displayNotification(achievement) 1204 | } 1205 | 1206 | const rageEffect = function() { 1207 | const element = document.getElementById('boardContainer') 1208 | element.classList.remove("shake-board") 1209 | element.offsetWidth // Trigger reflow. 1210 | element.classList.add("shake-board") 1211 | } 1212 | 1213 | const shakeMouse = function() { 1214 | let step = 0 1215 | let recursiveTimerReplay = function () { 1216 | const rec = mouse_recordings 1217 | if (step >= rec.length) return 1218 | const diffX = rec[step][1] / planchetteWidth * 100 1219 | const diffY = rec[step][2] / planchetteHeight * 100 1220 | offsetX += diffX 1221 | planchetteTransformX -= diffX 1222 | offsetY += diffY 1223 | planchetteTransformY -= diffY 1224 | step = step + 1 1225 | paintCursorWithOffset(document.getElementById("cursor"), prevX, prevY) 1226 | setTimeout(() => { recursiveTimerReplay() }, 0) 1227 | } 1228 | recursiveTimerReplay() 1229 | } 1230 | 1231 | const switchToNormalCursor = function (e) { 1232 | stopDraggingPlanchette(e) 1233 | document.getElementById("cursor").style.visibility = "hidden" 1234 | clearOffsets() 1235 | updatePlanchettePosition() 1236 | } 1237 | 1238 | const trophyClicked = function(achievement) { 1239 | if (window.localStorage.getItem(`ouija-${achievement}`)) { 1240 | displayNotification(achievement) 1241 | } 1242 | } 1243 | 1244 | // Mouse move events 1245 | document.getElementById('loadingOverlay').addEventListener('mousemove', e => { userMoveCount += 1 }) 1246 | document.getElementById('bg').addEventListener('mousemove', e => { mouseMoved(e) }) 1247 | document.getElementById('hoverBoard').addEventListener('mousemove', e => { mouseMoved(e) }) 1248 | document.getElementById('planchette').addEventListener('mousemove', e => { mouseMoved(e) }) 1249 | document.getElementById('planchetteHelper').addEventListener('mousemove', e => { mouseMoved(e, ON_PLANCHETTE) }) 1250 | document.querySelectorAll('.popupHoverboard').forEach((element) => element.addEventListener('mousemove', e => { mouseMoved(e) })) 1251 | document.querySelectorAll('.popupButton').forEach((element) => element.addEventListener('mousemove', e => { mouseMoved(e, ON_BUTTON) })) 1252 | document.querySelectorAll('.slider').forEach((element) => element.addEventListener('mousemove', e => { mouseMoved(e, ON_BUTTON) })) 1253 | document.getElementById('extRowGithub').addEventListener('mousemove', e => { mouseMoved(e, ON_BUTTON) }) 1254 | document.getElementById('extRowFeedback').addEventListener('mousemove', e => { mouseMoved(e, ON_BUTTON) }) 1255 | 1256 | // Button click events 1257 | document.getElementById('consentYes').addEventListener('click', e => { consentYes() }) 1258 | document.getElementById('consentNo').addEventListener('click', e => { consentNo() }) 1259 | document.getElementById('bg').addEventListener('click', e => { if (hoveringOverTooltip && showTips && !pauseSmokeAnimation) toggleTooltipPopup(e) }) 1260 | document.getElementById('hoverBoard').addEventListener('click', e => { if (hoveringOverTooltip && showTips && !pauseSmokeAnimation) toggleTooltipPopup(e) }) 1261 | document.getElementById('tooltipContinueButton').addEventListener('click', e => { toggleTooltipPopup(e) }) 1262 | document.getElementById('tooltipShutUpButton').addEventListener('click', e => { toggleTooltipPopup(e, SHUT_UP) }) 1263 | document.getElementById('saveSettings').addEventListener('click', e => { toggleSettingsPopup() }) 1264 | document.getElementById('easyModeSlider').addEventListener('click', e => { toggleEasyMode() }) 1265 | document.getElementById('showTipsSlider').addEventListener('click', e => { toggleShowTips() }) 1266 | document.getElementById('revealMouseSlider').addEventListener('click', e => { toggleRevealMouse() }) 1267 | document.getElementById('speedModeSlider').addEventListener('click', e => { toggleSpeedMode() }) 1268 | document.getElementById('openAISlider').addEventListener('click', e => { toggleOpenAI() }) 1269 | document.getElementById('settingsGear').addEventListener('click', e => { toggleSettingsPopup() }) 1270 | 1271 | // Activating planchetteDragging 1272 | document.getElementById('planchetteHelper').addEventListener('mousedown', e => { startDraggingPlanchette(e) }) 1273 | 1274 | // Deactivating planchetteDragging 1275 | document.getElementById('planchetteHelper').addEventListener('mouseup', e => { stopDraggingPlanchette(e, ON_PLANCHETTE) }) 1276 | document.body.addEventListener('mouseup', e => { stopDraggingPlanchette(e) }) 1277 | 1278 | // Switches from fake cursor to normal cursor 1279 | document.body.addEventListener('mouseleave', e => { switchToNormalCursor(e) }) 1280 | document.getElementById('suicide-prevention-popup').addEventListener('mousemove', e => { switchToNormalCursor(e) }) 1281 | document.getElementById('settingsGearHoverboard').addEventListener('mousemove', e => { switchToNormalCursor(e) }) 1282 | document.getElementById('achievement-hoverboard').addEventListener('mousemove', e => { switchToNormalCursor(e) }) 1283 | document.getElementById('textInputAPIKey').addEventListener('mousemove', e => { switchToNormalCursor(e) }) 1284 | 1285 | // Keydown events 1286 | document.body.addEventListener('keydown', e => { 1287 | const lowerCasedChar = e.key.toLocaleLowerCase() 1288 | if (turn === TURN_USER && !popupIsOpen && !gameOver) { 1289 | const m = document.getElementById('userMessagePre') 1290 | if (e.key == 'Enter') { 1291 | if (m.innerText.length >= 2) { 1292 | possessedMessage = '' // this may be needed in case user sends half-consumed possessedMessage 1293 | const userQuestion = m.innerText 1294 | console.log('Player: ' + userQuestion) 1295 | switchTurnToSpirit() 1296 | dispatchToSpirit(userQuestion, spiritIsReadyToCommunicate) 1297 | } 1298 | } else if (possessedMessage?.length > 0) { 1299 | const c = possessedMessage.charAt(0) 1300 | possessedMessage = possessedMessage.substring(1) 1301 | m.innerText += c.toUpperCase() 1302 | } else if (ALLOWED_CHARS.includes(lowerCasedChar) || lowerCasedChar == ' ') { 1303 | if (m.innerText.length < USER_MESSAGE_MAX_LENGTH) { 1304 | m.innerText += lowerCasedChar.toUpperCase() 1305 | } 1306 | } else if (e.key == 'Backspace' && m.innerText.length > 0) { 1307 | m.innerText = m.innerText.substring(0, m.innerText.length - 1) 1308 | } else if (e.key == "'") { 1309 | // Fix Firefox issue. 1310 | e.preventDefault() 1311 | } 1312 | } 1313 | 1314 | // Helpers for debugging 1315 | if (!window.location.href.endsWith("#debug")) { 1316 | return 1317 | } 1318 | if (e.key == "=") { 1319 | debug[RECORDING] = {} 1320 | console.log('Recording') 1321 | } 1322 | }) 1323 | 1324 | const cleanText = function (rawText) { 1325 | const lowerCased = rawText.toLocaleLowerCase() 1326 | let filtered = '' 1327 | for (let i = 0; i < lowerCased.length; i++) { 1328 | if (ALLOWED_CHARS.includes(lowerCased[i])) { 1329 | filtered += lowerCased[i] 1330 | } 1331 | } 1332 | return filtered 1333 | } 1334 | 1335 | allAchievements 1336 | .filter(achievement => window.localStorage.getItem(`ouija-${achievement}`)) 1337 | .map(achievement => activateAchievementIcon(achievement)) 1338 | 1339 | if (window.localStorage.getItem(`ouija-speedmode`)) { 1340 | toggleSpeedMode() 1341 | } 1342 | 1343 | if (reduceAnimations) { 1344 | document.getElementById('board').style.animationName = 'no-animation' 1345 | document.getElementById('board').style.boxShadow = '0 0 50px 20px #d35400' 1346 | } -------------------------------------------------------------------------------- /assets/CarnivaleeFreakshow.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/CarnivaleeFreakshow.woff -------------------------------------------------------------------------------- /assets/CarnivaleeFreakshow.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/CarnivaleeFreakshow.woff2 -------------------------------------------------------------------------------- /assets/Feral-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/Feral-Regular.ttf -------------------------------------------------------------------------------- /assets/Feral-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/Feral-Regular.woff -------------------------------------------------------------------------------- /assets/Feral-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/Feral-Regular.woff2 -------------------------------------------------------------------------------- /assets/KingthingsTrypewriter2.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/KingthingsTrypewriter2.woff -------------------------------------------------------------------------------- /assets/KingthingsTrypewriter2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/KingthingsTrypewriter2.woff2 -------------------------------------------------------------------------------- /assets/aero_arrow.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/aero_arrow.cur -------------------------------------------------------------------------------- /assets/aero_link.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/aero_link.cur -------------------------------------------------------------------------------- /assets/beam_i.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/beam_i.cur -------------------------------------------------------------------------------- /assets/demonic-woman-scream-6333.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/demonic-woman-scream-6333.mp3 -------------------------------------------------------------------------------- /assets/face-no-eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/face-no-eyes.png -------------------------------------------------------------------------------- /assets/face-outline-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/face-outline-transparent.png -------------------------------------------------------------------------------- /assets/face-outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/face-outline.png -------------------------------------------------------------------------------- /assets/face-stare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/face-stare.png -------------------------------------------------------------------------------- /assets/glass-crack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/glass-crack.png -------------------------------------------------------------------------------- /assets/grab.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/grab.cur -------------------------------------------------------------------------------- /assets/grabbing.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/grabbing.cur -------------------------------------------------------------------------------- /assets/ice-cracking.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ice-cracking.mp3 -------------------------------------------------------------------------------- /assets/jack_in_the_box.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/jack_in_the_box.mp3 -------------------------------------------------------------------------------- /assets/lnodrop.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/lnodrop.cur -------------------------------------------------------------------------------- /assets/mixkit-hard-horror-hit-drum-565.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/mixkit-hard-horror-hit-drum-565.mp3 -------------------------------------------------------------------------------- /assets/mouse_recordings.js: -------------------------------------------------------------------------------- 1 | mouse_recordings = [ 2 | [ 3 | 3, 4 | -16, 5 | 10 6 | ], 7 | [ 8 | 3, 9 | -1, 10 | 0 11 | ], 12 | [ 13 | 12, 14 | -1, 15 | 0 16 | ], 17 | [ 18 | 7, 19 | 0, 20 | -1 21 | ], 22 | [ 23 | 5, 24 | -1, 25 | 0 26 | ], 27 | [ 28 | 6, 29 | 0, 30 | -1 31 | ], 32 | [ 33 | 4, 34 | -1, 35 | 0 36 | ], 37 | [ 38 | 5, 39 | 0, 40 | -1 41 | ], 42 | [ 43 | 8, 44 | 0, 45 | -1 46 | ], 47 | [ 48 | 2, 49 | 0, 50 | -1 51 | ], 52 | [ 53 | 1, 54 | -1, 55 | 0 56 | ], 57 | [ 58 | 9, 59 | 0, 60 | -1 61 | ], 62 | [ 63 | 6, 64 | 0, 65 | -1 66 | ], 67 | [ 68 | 8, 69 | 0, 70 | -1 71 | ], 72 | [ 73 | 8, 74 | 0, 75 | -1 76 | ], 77 | [ 78 | 4, 79 | 0, 80 | -1 81 | ], 82 | [ 83 | 6, 84 | 1, 85 | 0 86 | ], 87 | [ 88 | 0, 89 | 0, 90 | -1 91 | ], 92 | [ 93 | 2, 94 | 1, 95 | 0 96 | ], 97 | [ 98 | 7, 99 | 0, 100 | -1 101 | ], 102 | [ 103 | 1, 104 | 1, 105 | 0 106 | ], 107 | [ 108 | 7, 109 | 0, 110 | -1 111 | ], 112 | [ 113 | 1, 114 | 1, 115 | 0 116 | ], 117 | [ 118 | 5, 119 | 0, 120 | -1 121 | ], 122 | [ 123 | 7, 124 | 1, 125 | 0 126 | ], 127 | [ 128 | 5, 129 | 1, 130 | 0 131 | ], 132 | [ 133 | 3, 134 | 0, 135 | -1 136 | ], 137 | [ 138 | 4, 139 | 1, 140 | 0 141 | ], 142 | [ 143 | 5, 144 | 1, 145 | 0 146 | ], 147 | [ 148 | 3, 149 | 0, 150 | -1 151 | ], 152 | [ 153 | 2, 154 | 1, 155 | 0 156 | ], 157 | [ 158 | 2, 159 | 1, 160 | 0 161 | ], 162 | [ 163 | 3, 164 | 1, 165 | 0 166 | ], 167 | [ 168 | 5, 169 | 1, 170 | 0 171 | ], 172 | [ 173 | 4, 174 | 1, 175 | 0 176 | ], 177 | [ 178 | 2, 179 | 1, 180 | 0 181 | ], 182 | [ 183 | 2, 184 | 0, 185 | -1 186 | ], 187 | [ 188 | 2, 189 | 1, 190 | 0 191 | ], 192 | [ 193 | 4, 194 | 1, 195 | 0 196 | ], 197 | [ 198 | 6, 199 | 1, 200 | 0 201 | ], 202 | [ 203 | 1, 204 | 1, 205 | 0 206 | ], 207 | [ 208 | 6, 209 | 1, 210 | 0 211 | ], 212 | [ 213 | 6, 214 | 1, 215 | 0 216 | ], 217 | [ 218 | 6, 219 | 1, 220 | 0 221 | ], 222 | [ 223 | 4, 224 | 1, 225 | 0 226 | ], 227 | [ 228 | 3, 229 | 0, 230 | 1 231 | ], 232 | [ 233 | 2, 234 | 1, 235 | 0 236 | ], 237 | [ 238 | 3, 239 | 0, 240 | 1 241 | ], 242 | [ 243 | 4, 244 | 1, 245 | 1 246 | ], 247 | [ 248 | 2, 249 | 0, 250 | 1 251 | ], 252 | [ 253 | 2, 254 | 1, 255 | 0 256 | ], 257 | [ 258 | 3, 259 | 0, 260 | 1 261 | ], 262 | [ 263 | 2, 264 | 1, 265 | 0 266 | ], 267 | [ 268 | 1, 269 | 0, 270 | 1 271 | ], 272 | [ 273 | 1, 274 | 1, 275 | 0 276 | ], 277 | [ 278 | 3, 279 | 0, 280 | 1 281 | ], 282 | [ 283 | 3, 284 | 1, 285 | 1 286 | ], 287 | [ 288 | 2, 289 | 0, 290 | 1 291 | ], 292 | [ 293 | 4, 294 | 1, 295 | 0 296 | ], 297 | [ 298 | 1, 299 | 0, 300 | 1 301 | ], 302 | [ 303 | 2, 304 | 0, 305 | 1 306 | ], 307 | [ 308 | 4, 309 | 0, 310 | 1 311 | ], 312 | [ 313 | 1, 314 | 0, 315 | 1 316 | ], 317 | [ 318 | 3, 319 | 0, 320 | 1 321 | ], 322 | [ 323 | 4, 324 | 0, 325 | 1 326 | ], 327 | [ 328 | 4, 329 | 0, 330 | 1 331 | ], 332 | [ 333 | 1, 334 | 0, 335 | 1 336 | ], 337 | [ 338 | 3, 339 | 0, 340 | 1 341 | ], 342 | [ 343 | 4, 344 | 0, 345 | 1 346 | ], 347 | [ 348 | 5, 349 | 0, 350 | 1 351 | ], 352 | [ 353 | 0, 354 | 0, 355 | 1 356 | ], 357 | [ 358 | 3, 359 | -1, 360 | 1 361 | ], 362 | [ 363 | 4, 364 | -1, 365 | 0 366 | ], 367 | [ 368 | 1, 369 | 0, 370 | 1 371 | ], 372 | [ 373 | 2, 374 | -1, 375 | 0 376 | ], 377 | [ 378 | 2, 379 | 0, 380 | 1 381 | ], 382 | [ 383 | 1, 384 | 0, 385 | 1 386 | ], 387 | [ 388 | 1, 389 | -1, 390 | 0 391 | ], 392 | [ 393 | 5, 394 | -1, 395 | 0 396 | ], 397 | [ 398 | 1, 399 | 0, 400 | 1 401 | ], 402 | [ 403 | 3, 404 | -1, 405 | 0 406 | ], 407 | [ 408 | 4, 409 | 0, 410 | 1 411 | ], 412 | [ 413 | 5, 414 | -1, 415 | 0 416 | ], 417 | [ 418 | 1, 419 | -1, 420 | 0 421 | ], 422 | [ 423 | 4, 424 | 0, 425 | 1 426 | ], 427 | [ 428 | 6, 429 | -1, 430 | 0 431 | ], 432 | [ 433 | 26, 434 | -1, 435 | 0 436 | ], 437 | [ 438 | 12, 439 | -1, 440 | 0 441 | ], 442 | [ 443 | 4, 444 | -1, 445 | 0 446 | ], 447 | [ 448 | 2, 449 | 0, 450 | -1 451 | ], 452 | [ 453 | 6, 454 | -1, 455 | 0 456 | ], 457 | [ 458 | 2, 459 | 0, 460 | -1 461 | ], 462 | [ 463 | 6, 464 | -1, 465 | 0 466 | ], 467 | [ 468 | 1, 469 | 0, 470 | -1 471 | ], 472 | [ 473 | 3, 474 | 0, 475 | -1 476 | ], 477 | [ 478 | 0, 479 | -1, 480 | 0 481 | ], 482 | [ 483 | 3, 484 | -1, 485 | 0 486 | ], 487 | [ 488 | 1, 489 | 0, 490 | -1 491 | ], 492 | [ 493 | 4, 494 | -1, 495 | 0 496 | ], 497 | [ 498 | 1, 499 | 0, 500 | -1 501 | ], 502 | [ 503 | 5, 504 | 0, 505 | -1 506 | ], 507 | [ 508 | 0, 509 | -1, 510 | -1 511 | ], 512 | [ 513 | 5, 514 | 0, 515 | -1 516 | ], 517 | [ 518 | 3, 519 | 0, 520 | -1 521 | ], 522 | [ 523 | 0, 524 | -1, 525 | 0 526 | ], 527 | [ 528 | 2, 529 | 0, 530 | -1 531 | ], 532 | [ 533 | 1, 534 | 0, 535 | -1 536 | ], 537 | [ 538 | 1, 539 | -1, 540 | 0 541 | ], 542 | [ 543 | 2, 544 | 0, 545 | -1 546 | ], 547 | [ 548 | 3, 549 | 0, 550 | -1 551 | ], 552 | [ 553 | 3, 554 | 0, 555 | -1 556 | ], 557 | [ 558 | 1, 559 | 0, 560 | -1 561 | ], 562 | [ 563 | 6, 564 | 0, 565 | -1 566 | ], 567 | [ 568 | 3, 569 | 1, 570 | 0 571 | ], 572 | [ 573 | 1, 574 | 0, 575 | -1 576 | ], 577 | [ 578 | 5, 579 | 0, 580 | -1 581 | ], 582 | [ 583 | 2, 584 | 1, 585 | 0 586 | ], 587 | [ 588 | 3, 589 | 0, 590 | -1 591 | ], 592 | [ 593 | 5, 594 | 0, 595 | -1 596 | ], 597 | [ 598 | 2, 599 | 1, 600 | 0 601 | ], 602 | [ 603 | 2, 604 | 0, 605 | -1 606 | ], 607 | [ 608 | 6, 609 | 1, 610 | 0 611 | ], 612 | [ 613 | 2, 614 | 0, 615 | -1 616 | ], 617 | [ 618 | 4, 619 | 1, 620 | 0 621 | ], 622 | [ 623 | 4, 624 | 0, 625 | -1 626 | ], 627 | [ 628 | 3, 629 | 0, 630 | -1 631 | ], 632 | [ 633 | 1, 634 | 1, 635 | 0 636 | ], 637 | [ 638 | 6, 639 | 1, 640 | 0 641 | ], 642 | [ 643 | 4, 644 | 0, 645 | -1 646 | ], 647 | [ 648 | 4, 649 | 1, 650 | 0 651 | ], 652 | [ 653 | 4, 654 | 1, 655 | 0 656 | ], 657 | [ 658 | 5, 659 | 1, 660 | 0 661 | ], 662 | [ 663 | 7, 664 | 1, 665 | 0 666 | ], 667 | [ 668 | 5, 669 | 1, 670 | 0 671 | ], 672 | [ 673 | 2, 674 | 1, 675 | 0 676 | ], 677 | [ 678 | 7, 679 | 1, 680 | 0 681 | ], 682 | [ 683 | 5, 684 | 1, 685 | 0 686 | ], 687 | [ 688 | 7, 689 | 1, 690 | 0 691 | ], 692 | [ 693 | 5, 694 | 0, 695 | 1 696 | ], 697 | [ 698 | 1, 699 | 1, 700 | 0 701 | ], 702 | [ 703 | 1, 704 | 1, 705 | 0 706 | ], 707 | [ 708 | 1, 709 | 0, 710 | 1 711 | ], 712 | [ 713 | 5, 714 | 1, 715 | 0 716 | ], 717 | [ 718 | 1, 719 | 0, 720 | 1 721 | ], 722 | [ 723 | 4, 724 | 1, 725 | 0 726 | ], 727 | [ 728 | 3, 729 | 0, 730 | 1 731 | ], 732 | [ 733 | 2, 734 | 1, 735 | 0 736 | ], 737 | [ 738 | 3, 739 | 1, 740 | 0 741 | ], 742 | [ 743 | 1, 744 | 0, 745 | 1 746 | ], 747 | [ 748 | 3, 749 | 0, 750 | 1 751 | ], 752 | [ 753 | 2, 754 | 1, 755 | 0 756 | ], 757 | [ 758 | 5, 759 | 0, 760 | 1 761 | ], 762 | [ 763 | 3, 764 | 1, 765 | 0 766 | ], 767 | [ 768 | 6, 769 | 0, 770 | 1 771 | ], 772 | [ 773 | 8, 774 | 1, 775 | 0 776 | ], 777 | [ 778 | 4, 779 | 0, 780 | 1 781 | ], 782 | [ 783 | 13, 784 | 0, 785 | 1 786 | ], 787 | [ 788 | 4, 789 | 0, 790 | 1 791 | ], 792 | [ 793 | 12, 794 | 0, 795 | 1 796 | ], 797 | [ 798 | 6, 799 | -1, 800 | 0 801 | ], 802 | [ 803 | 5, 804 | 0, 805 | 1 806 | ], 807 | [ 808 | 5, 809 | -1, 810 | 0 811 | ], 812 | [ 813 | 9, 814 | -1, 815 | 0 816 | ], 817 | [ 818 | 4, 819 | 0, 820 | 1 821 | ], 822 | [ 823 | 1, 824 | -1, 825 | 0 826 | ], 827 | [ 828 | 9, 829 | -1, 830 | 0 831 | ], 832 | [ 833 | 12, 834 | -1, 835 | 0 836 | ], 837 | [ 838 | 12, 839 | -1, 840 | 0 841 | ], 842 | [ 843 | 7, 844 | -1, 845 | 0 846 | ], 847 | [ 848 | 10, 849 | -1, 850 | 0 851 | ], 852 | [ 853 | 8, 854 | 0, 855 | -1 856 | ], 857 | [ 858 | 1, 859 | -1, 860 | 0 861 | ], 862 | [ 863 | 4, 864 | 0, 865 | -1 866 | ], 867 | [ 868 | 4, 869 | 0, 870 | -1 871 | ], 872 | [ 873 | 3, 874 | 0, 875 | -1 876 | ], 877 | [ 878 | 0, 879 | -1, 880 | 0 881 | ], 882 | [ 883 | 3, 884 | 0, 885 | -1 886 | ], 887 | [ 888 | 3, 889 | 0, 890 | -1 891 | ], 892 | [ 893 | 4, 894 | 0, 895 | -1 896 | ], 897 | [ 898 | 3, 899 | 0, 900 | -1 901 | ], 902 | [ 903 | 2, 904 | 0, 905 | -1 906 | ], 907 | [ 908 | 3, 909 | 0, 910 | -1 911 | ], 912 | [ 913 | 5, 914 | 0, 915 | -1 916 | ], 917 | [ 918 | 4, 919 | 0, 920 | -1 921 | ], 922 | [ 923 | 2, 924 | 0, 925 | -1 926 | ], 927 | [ 928 | 4, 929 | 0, 930 | -1 931 | ], 932 | [ 933 | 5, 934 | 0, 935 | -1 936 | ], 937 | [ 938 | 5, 939 | 0, 940 | -1 941 | ], 942 | [ 943 | 2, 944 | 0, 945 | -1 946 | ], 947 | [ 948 | 7, 949 | 1, 950 | 0 951 | ], 952 | [ 953 | 0, 954 | 0, 955 | -1 956 | ], 957 | [ 958 | 9, 959 | 0, 960 | -1 961 | ], 962 | [ 963 | 1, 964 | 1, 965 | 0 966 | ], 967 | [ 968 | 8, 969 | 0, 970 | -1 971 | ], 972 | [ 973 | 1, 974 | 1, 975 | 0 976 | ], 977 | [ 978 | 5, 979 | 1, 980 | 0 981 | ], 982 | [ 983 | 2, 984 | 0, 985 | -1 986 | ], 987 | [ 988 | 7, 989 | 1, 990 | 0 991 | ], 992 | [ 993 | 9, 994 | 1, 995 | 0 996 | ], 997 | [ 998 | 4, 999 | 0, 1000 | -1 1001 | ], 1002 | [ 1003 | 6, 1004 | 1, 1005 | 0 1006 | ], 1007 | [ 1008 | 4, 1009 | 1, 1010 | 0 1011 | ], 1012 | [ 1013 | 13, 1014 | 1, 1015 | 0 1016 | ], 1017 | [ 1018 | 12, 1019 | 1, 1020 | 0 1021 | ], 1022 | [ 1023 | 9, 1024 | 1, 1025 | 0 1026 | ], 1027 | [ 1028 | 3, 1029 | 1, 1030 | 1 1031 | ], 1032 | [ 1033 | 4, 1034 | 0, 1035 | 1 1036 | ], 1037 | [ 1038 | 3, 1039 | 1, 1040 | 0 1041 | ], 1042 | [ 1043 | 4, 1044 | 0, 1045 | 1 1046 | ], 1047 | [ 1048 | 3, 1049 | 1, 1050 | 0 1051 | ], 1052 | [ 1053 | 10, 1054 | 0, 1055 | 1 1056 | ], 1057 | [ 1058 | 3, 1059 | 1, 1060 | 1 1061 | ], 1062 | [ 1063 | 3, 1064 | 0, 1065 | 1 1066 | ], 1067 | [ 1068 | 3, 1069 | 1, 1070 | 0 1071 | ], 1072 | [ 1073 | 1, 1074 | 0, 1075 | 1 1076 | ], 1077 | [ 1078 | 6, 1079 | 0, 1080 | 1 1081 | ], 1082 | [ 1083 | 4, 1084 | 0, 1085 | 1 1086 | ], 1087 | [ 1088 | 3, 1089 | 0, 1090 | 1 1091 | ], 1092 | [ 1093 | 5, 1094 | 0, 1095 | 1 1096 | ], 1097 | [ 1098 | 5, 1099 | 0, 1100 | 1 1101 | ], 1102 | [ 1103 | 5, 1104 | 0, 1105 | 1 1106 | ], 1107 | [ 1108 | 3, 1109 | 0, 1110 | 1 1111 | ], 1112 | [ 1113 | 2, 1114 | -1, 1115 | 0 1116 | ], 1117 | [ 1118 | 4, 1119 | 0, 1120 | 1 1121 | ], 1122 | [ 1123 | 6, 1124 | -1, 1125 | 0 1126 | ], 1127 | [ 1128 | 3, 1129 | 0, 1130 | 1 1131 | ], 1132 | [ 1133 | 5, 1134 | -1, 1135 | 0 1136 | ], 1137 | [ 1138 | 5, 1139 | 0, 1140 | 1 1141 | ], 1142 | [ 1143 | 2, 1144 | -1, 1145 | 0 1146 | ], 1147 | [ 1148 | 4, 1149 | -1, 1150 | 0 1151 | ], 1152 | [ 1153 | 8, 1154 | -1, 1155 | 0 1156 | ], 1157 | [ 1158 | 11, 1159 | -1, 1160 | 0 1161 | ], 1162 | [ 1163 | 14, 1164 | -1, 1165 | 0 1166 | ], 1167 | [ 1168 | 5, 1169 | -1, 1170 | 0 1171 | ], 1172 | [ 1173 | 9, 1174 | -1, 1175 | 0 1176 | ], 1177 | [ 1178 | 5, 1179 | 0, 1180 | -1 1181 | ], 1182 | [ 1183 | 2, 1184 | -1, 1185 | 0 1186 | ], 1187 | [ 1188 | 3, 1189 | 0, 1190 | -1 1191 | ], 1192 | [ 1193 | 5, 1194 | -1, 1195 | 0 1196 | ], 1197 | [ 1198 | 1, 1199 | 0, 1200 | -1 1201 | ], 1202 | [ 1203 | 0, 1204 | -1, 1205 | 0 1206 | ], 1207 | [ 1208 | 1, 1209 | 0, 1210 | -1 1211 | ], 1212 | [ 1213 | 4, 1214 | 0, 1215 | -1 1216 | ], 1217 | [ 1218 | 1, 1219 | -1, 1220 | 0 1221 | ], 1222 | [ 1223 | 5, 1224 | 0, 1225 | -1 1226 | ], 1227 | [ 1228 | 1, 1229 | -1, 1230 | 0 1231 | ], 1232 | [ 1233 | 1, 1234 | 0, 1235 | -1 1236 | ], 1237 | [ 1238 | 2, 1239 | 0, 1240 | -1 1241 | ], 1242 | [ 1243 | 3, 1244 | -1, 1245 | 0 1246 | ], 1247 | [ 1248 | 1, 1249 | 0, 1250 | -1 1251 | ], 1252 | [ 1253 | 2, 1254 | 0, 1255 | -1 1256 | ], 1257 | [ 1258 | 3, 1259 | 0, 1260 | -1 1261 | ], 1262 | [ 1263 | 1, 1264 | -1, 1265 | 0 1266 | ], 1267 | [ 1268 | 1, 1269 | 0, 1270 | -1 1271 | ], 1272 | [ 1273 | 5, 1274 | 0, 1275 | -1 1276 | ], 1277 | [ 1278 | 1, 1279 | -1, 1280 | 0 1281 | ], 1282 | [ 1283 | 3, 1284 | 0, 1285 | -1 1286 | ], 1287 | [ 1288 | 8, 1289 | 0, 1290 | -1 1291 | ], 1292 | [ 1293 | 6, 1294 | 0, 1295 | -1 1296 | ], 1297 | [ 1298 | 10, 1299 | 1, 1300 | 0 1301 | ], 1302 | [ 1303 | 3, 1304 | 0, 1305 | -1 1306 | ], 1307 | [ 1308 | 11, 1309 | 1, 1310 | 0 1311 | ], 1312 | [ 1313 | 8, 1314 | 1, 1315 | 0 1316 | ], 1317 | [ 1318 | 1, 1319 | 0, 1320 | -1 1321 | ], 1322 | [ 1323 | 6, 1324 | 1, 1325 | 0 1326 | ], 1327 | [ 1328 | 6, 1329 | 1, 1330 | 0 1331 | ], 1332 | [ 1333 | 3, 1334 | 1, 1335 | 0 1336 | ], 1337 | [ 1338 | 6, 1339 | 1, 1340 | 0 1341 | ], 1342 | [ 1343 | 4, 1344 | 0, 1345 | 1 1346 | ], 1347 | [ 1348 | 2, 1349 | 1, 1350 | 0 1351 | ], 1352 | [ 1353 | 3, 1354 | 0, 1355 | 1 1356 | ], 1357 | [ 1358 | 2, 1359 | 1, 1360 | 0 1361 | ], 1362 | [ 1363 | 1, 1364 | 0, 1365 | 1 1366 | ], 1367 | [ 1368 | 1, 1369 | 1, 1370 | 0 1371 | ], 1372 | [ 1373 | 3, 1374 | 0, 1375 | 1 1376 | ], 1377 | [ 1378 | 1, 1379 | 1, 1380 | 0 1381 | ], 1382 | [ 1383 | 3, 1384 | 0, 1385 | 1 1386 | ], 1387 | [ 1388 | 3, 1389 | 1, 1390 | 0 1391 | ], 1392 | [ 1393 | 1, 1394 | 0, 1395 | 1 1396 | ], 1397 | [ 1398 | 2, 1399 | 0, 1400 | 1 1401 | ], 1402 | [ 1403 | 3, 1404 | 1, 1405 | 0 1406 | ], 1407 | [ 1408 | 0, 1409 | 1, 1410 | 0 1411 | ], 1412 | [ 1413 | 1, 1414 | 0, 1415 | 1 1416 | ], 1417 | [ 1418 | 5, 1419 | 0, 1420 | 1 1421 | ], 1422 | [ 1423 | 1, 1424 | 1, 1425 | 0 1426 | ], 1427 | [ 1428 | 3, 1429 | 0, 1430 | 1 1431 | ], 1432 | [ 1433 | 2, 1434 | 0, 1435 | 1 1436 | ], 1437 | [ 1438 | 4, 1439 | 1, 1440 | 0 1441 | ], 1442 | [ 1443 | 1, 1444 | 0, 1445 | 1 1446 | ], 1447 | [ 1448 | 7, 1449 | 0, 1450 | 1 1451 | ], 1452 | [ 1453 | 7, 1454 | 0, 1455 | 1 1456 | ], 1457 | [ 1458 | 3, 1459 | 0, 1460 | 1 1461 | ], 1462 | [ 1463 | 10, 1464 | 0, 1465 | 1 1466 | ], 1467 | [ 1468 | 6, 1469 | -1, 1470 | 0 1471 | ], 1472 | [ 1473 | 6, 1474 | -1, 1475 | 1 1476 | ], 1477 | [ 1478 | 3, 1479 | -1, 1480 | 0 1481 | ], 1482 | [ 1483 | 5, 1484 | -1, 1485 | 0 1486 | ], 1487 | [ 1488 | 5, 1489 | -1, 1490 | 0 1491 | ], 1492 | [ 1493 | 4, 1494 | -1, 1495 | 0 1496 | ], 1497 | [ 1498 | 1, 1499 | 0, 1500 | 1 1501 | ], 1502 | [ 1503 | 2, 1504 | -1, 1505 | 0 1506 | ], 1507 | [ 1508 | 3, 1509 | -1, 1510 | 0 1511 | ], 1512 | [ 1513 | 5, 1514 | -1, 1515 | 0 1516 | ], 1517 | [ 1518 | 3, 1519 | -1, 1520 | 0 1521 | ], 1522 | [ 1523 | 1, 1524 | -1, 1525 | 0 1526 | ], 1527 | [ 1528 | 5, 1529 | -1, 1530 | 0 1531 | ], 1532 | [ 1533 | 4, 1534 | -1, 1535 | 0 1536 | ], 1537 | [ 1538 | 4, 1539 | -1, 1540 | 0 1541 | ], 1542 | [ 1543 | 5, 1544 | -1, 1545 | 0 1546 | ], 1547 | [ 1548 | 1, 1549 | -1, 1550 | 0 1551 | ], 1552 | [ 1553 | 2, 1554 | 0, 1555 | -1 1556 | ], 1557 | [ 1558 | 2, 1559 | -1, 1560 | 0 1561 | ], 1562 | [ 1563 | 5, 1564 | -1, 1565 | 0 1566 | ], 1567 | [ 1568 | 1, 1569 | 0, 1570 | -1 1571 | ], 1572 | [ 1573 | 5, 1574 | -1, 1575 | 0 1576 | ], 1577 | [ 1578 | 0, 1579 | 0, 1580 | -1 1581 | ], 1582 | [ 1583 | 2, 1584 | -1, 1585 | 0 1586 | ], 1587 | [ 1588 | 1, 1589 | 0, 1590 | -1 1591 | ], 1592 | [ 1593 | 3, 1594 | 0, 1595 | -1 1596 | ], 1597 | [ 1598 | 1, 1599 | -1, 1600 | 0 1601 | ], 1602 | [ 1603 | 4, 1604 | 0, 1605 | -1 1606 | ], 1607 | [ 1608 | 0, 1609 | -1, 1610 | 0 1611 | ], 1612 | [ 1613 | 3, 1614 | 0, 1615 | -1 1616 | ], 1617 | [ 1618 | 2, 1619 | 0, 1620 | -1 1621 | ], 1622 | [ 1623 | 3, 1624 | -1, 1625 | 0 1626 | ], 1627 | [ 1628 | 2, 1629 | 0, 1630 | -1 1631 | ], 1632 | [ 1633 | 2, 1634 | -1, 1635 | 0 1636 | ], 1637 | [ 1638 | 1, 1639 | 0, 1640 | -1 1641 | ], 1642 | [ 1643 | 5, 1644 | 0, 1645 | -1 1646 | ], 1647 | [ 1648 | 2, 1649 | 0, 1650 | -1 1651 | ], 1652 | [ 1653 | 5, 1654 | 0, 1655 | -1 1656 | ], 1657 | [ 1658 | 6, 1659 | 0, 1660 | -1 1661 | ], 1662 | [ 1663 | 3, 1664 | 1, 1665 | 0 1666 | ], 1667 | [ 1668 | 1, 1669 | 0, 1670 | -1 1671 | ], 1672 | [ 1673 | 3, 1674 | 0, 1675 | -1 1676 | ], 1677 | [ 1678 | 1, 1679 | 1, 1680 | 0 1681 | ], 1682 | [ 1683 | 5, 1684 | 0, 1685 | -1 1686 | ], 1687 | [ 1688 | 2, 1689 | 1, 1690 | 0 1691 | ], 1692 | [ 1693 | 2, 1694 | 0, 1695 | -1 1696 | ], 1697 | [ 1698 | 2, 1699 | 1, 1700 | 0 1701 | ], 1702 | [ 1703 | 2, 1704 | 1, 1705 | 0 1706 | ], 1707 | [ 1708 | 1, 1709 | 0, 1710 | -1 1711 | ], 1712 | [ 1713 | 4, 1714 | 0, 1715 | -1 1716 | ], 1717 | [ 1718 | 1, 1719 | 1, 1720 | 0 1721 | ], 1722 | [ 1723 | 5, 1724 | 1, 1725 | 0 1726 | ], 1727 | [ 1728 | 4, 1729 | 0, 1730 | -1 1731 | ], 1732 | [ 1733 | 3, 1734 | 1, 1735 | 0 1736 | ], 1737 | [ 1738 | 3, 1739 | 1, 1740 | 0 1741 | ], 1742 | [ 1743 | 6, 1744 | 0, 1745 | -1 1746 | ], 1747 | [ 1748 | 1, 1749 | 1, 1750 | 0 1751 | ], 1752 | [ 1753 | 9, 1754 | 1, 1755 | 0 1756 | ], 1757 | [ 1758 | 8, 1759 | 1, 1760 | 0 1761 | ], 1762 | [ 1763 | 6, 1764 | 1, 1765 | 0 1766 | ], 1767 | [ 1768 | 2, 1769 | 1, 1770 | 0 1771 | ], 1772 | [ 1773 | 1, 1774 | 0, 1775 | 1 1776 | ], 1777 | [ 1778 | 3, 1779 | 1, 1780 | 0 1781 | ], 1782 | [ 1783 | 2, 1784 | 0, 1785 | 1 1786 | ], 1787 | [ 1788 | 2, 1789 | 0, 1790 | 1 1791 | ], 1792 | [ 1793 | 2, 1794 | 1, 1795 | 0 1796 | ], 1797 | [ 1798 | 2, 1799 | 0, 1800 | 1 1801 | ], 1802 | [ 1803 | 3, 1804 | 1, 1805 | 0 1806 | ], 1807 | [ 1808 | 1, 1809 | 0, 1810 | 1 1811 | ], 1812 | [ 1813 | 3, 1814 | 1, 1815 | 0 1816 | ], 1817 | [ 1818 | 1, 1819 | 0, 1820 | 1 1821 | ], 1822 | [ 1823 | 2, 1824 | 0, 1825 | 1 1826 | ], 1827 | [ 1828 | 4, 1829 | 0, 1830 | 1 1831 | ], 1832 | [ 1833 | 3, 1834 | 1, 1835 | 0 1836 | ], 1837 | [ 1838 | 1, 1839 | 0, 1840 | 1 1841 | ], 1842 | [ 1843 | 4, 1844 | 0, 1845 | 1 1846 | ], 1847 | [ 1848 | 4, 1849 | 0, 1850 | 1 1851 | ], 1852 | [ 1853 | 4, 1854 | 0, 1855 | 1 1856 | ], 1857 | [ 1858 | 4, 1859 | 0, 1860 | 1 1861 | ], 1862 | [ 1863 | 5, 1864 | 0, 1865 | 1 1866 | ], 1867 | [ 1868 | 2, 1869 | 0, 1870 | 1 1871 | ], 1872 | [ 1873 | 6, 1874 | 0, 1875 | 1 1876 | ], 1877 | [ 1878 | 6, 1879 | 0, 1880 | 1 1881 | ], 1882 | [ 1883 | 0, 1884 | -1, 1885 | 0 1886 | ], 1887 | [ 1888 | 3, 1889 | -1, 1890 | 0 1891 | ], 1892 | [ 1893 | 3, 1894 | 0, 1895 | 1 1896 | ], 1897 | [ 1898 | 4, 1899 | -1, 1900 | 0 1901 | ], 1902 | [ 1903 | 1, 1904 | 0, 1905 | 1 1906 | ], 1907 | [ 1908 | 7, 1909 | -1, 1910 | 0 1911 | ], 1912 | [ 1913 | 3, 1914 | 0, 1915 | 1 1916 | ], 1917 | [ 1918 | 4, 1919 | -1, 1920 | 0 1921 | ], 1922 | [ 1923 | 5, 1924 | -1, 1925 | 0 1926 | ], 1927 | [ 1928 | 8, 1929 | -1, 1930 | 0 1931 | ], 1932 | [ 1933 | 6, 1934 | -1, 1935 | 0 1936 | ], 1937 | [ 1938 | 8, 1939 | -1, 1940 | 0 1941 | ], 1942 | [ 1943 | 9, 1944 | -1, 1945 | 0 1946 | ], 1947 | [ 1948 | 2, 1949 | -1, 1950 | 0 1951 | ], 1952 | [ 1953 | 6, 1954 | -1, 1955 | 0 1956 | ], 1957 | [ 1958 | 5, 1959 | -1, 1960 | 0 1961 | ], 1962 | [ 1963 | 1, 1964 | 0, 1965 | -1 1966 | ], 1967 | [ 1968 | 4, 1969 | 0, 1970 | -1 1971 | ], 1972 | [ 1973 | 0, 1974 | -1, 1975 | 0 1976 | ], 1977 | [ 1978 | 2, 1979 | -1, 1980 | 0 1981 | ], 1982 | [ 1983 | 1, 1984 | 0, 1985 | -1 1986 | ], 1987 | [ 1988 | 4, 1989 | -1, 1990 | -1 1991 | ], 1992 | [ 1993 | 4, 1994 | 0, 1995 | -1 1996 | ], 1997 | [ 1998 | 1, 1999 | -1, 2000 | 0 2001 | ], 2002 | [ 2003 | 1, 2004 | 0, 2005 | -1 2006 | ], 2007 | [ 2008 | 3, 2009 | 0, 2010 | -1 2011 | ], 2012 | [ 2013 | 1, 2014 | -1, 2015 | 0 2016 | ], 2017 | [ 2018 | 2, 2019 | 0, 2020 | -1 2021 | ], 2022 | [ 2023 | 1, 2024 | -1, 2025 | 0 2026 | ], 2027 | [ 2028 | 1, 2029 | 0, 2030 | -1 2031 | ], 2032 | [ 2033 | 2, 2034 | 0, 2035 | -1 2036 | ], 2037 | [ 2038 | 1, 2039 | -1, 2040 | 0 2041 | ], 2042 | [ 2043 | 2, 2044 | 0, 2045 | -1 2046 | ], 2047 | [ 2048 | 3, 2049 | 0, 2050 | -1 2051 | ], 2052 | [ 2053 | 2, 2054 | 0, 2055 | -1 2056 | ], 2057 | [ 2058 | 1, 2059 | -1, 2060 | 0 2061 | ], 2062 | [ 2063 | 1, 2064 | 0, 2065 | -1 2066 | ], 2067 | [ 2068 | 3, 2069 | 0, 2070 | -1 2071 | ], 2072 | [ 2073 | 5, 2074 | 0, 2075 | -1 2076 | ], 2077 | [ 2078 | 2, 2079 | 0, 2080 | -1 2081 | ], 2082 | [ 2083 | 3, 2084 | 0, 2085 | -1 2086 | ], 2087 | [ 2088 | 4, 2089 | 0, 2090 | -1 2091 | ], 2092 | [ 2093 | 5, 2094 | 0, 2095 | -1 2096 | ], 2097 | [ 2098 | 3, 2099 | 1, 2100 | 0 2101 | ], 2102 | [ 2103 | 2, 2104 | 0, 2105 | -1 2106 | ], 2107 | [ 2108 | 2, 2109 | 0, 2110 | -1 2111 | ], 2112 | [ 2113 | 1, 2114 | 1, 2115 | 0 2116 | ], 2117 | [ 2118 | 2, 2119 | 1, 2120 | 0 2121 | ], 2122 | [ 2123 | 2, 2124 | 0, 2125 | -1 2126 | ], 2127 | [ 2128 | 3, 2129 | 1, 2130 | 0 2131 | ], 2132 | [ 2133 | 3, 2134 | 0, 2135 | -1 2136 | ], 2137 | [ 2138 | 1, 2139 | 1, 2140 | 0 2141 | ], 2142 | [ 2143 | 4, 2144 | 1, 2145 | 0 2146 | ], 2147 | [ 2148 | 3, 2149 | 1, 2150 | 0 2151 | ], 2152 | [ 2153 | 3, 2154 | 0, 2155 | -1 2156 | ], 2157 | [ 2158 | 2, 2159 | 1, 2160 | 0 2161 | ], 2162 | [ 2163 | 3, 2164 | 1, 2165 | 0 2166 | ], 2167 | [ 2168 | 6, 2169 | 1, 2170 | 0 2171 | ], 2172 | [ 2173 | 4, 2174 | 1, 2175 | 0 2176 | ], 2177 | [ 2178 | 9, 2179 | 1, 2180 | 0 2181 | ], 2182 | [ 2183 | 8, 2184 | 1, 2185 | 0 2186 | ], 2187 | [ 2188 | 7, 2189 | 0, 2190 | 1 2191 | ], 2192 | [ 2193 | 1, 2194 | 1, 2195 | 0 2196 | ], 2197 | [ 2198 | 3, 2199 | 0, 2200 | 1 2201 | ], 2202 | [ 2203 | 2, 2204 | 1, 2205 | 0 2206 | ], 2207 | [ 2208 | 3, 2209 | 1, 2210 | 1 2211 | ], 2212 | [ 2213 | 2, 2214 | 0, 2215 | 1 2216 | ], 2217 | [ 2218 | 3, 2219 | 1, 2220 | 0 2221 | ], 2222 | [ 2223 | 1, 2224 | 0, 2225 | 1 2226 | ], 2227 | [ 2228 | 4, 2229 | 0, 2230 | 1 2231 | ], 2232 | [ 2233 | 1, 2234 | 1, 2235 | 0 2236 | ], 2237 | [ 2238 | 3, 2239 | 0, 2240 | 1 2241 | ], 2242 | [ 2243 | 2, 2244 | 0, 2245 | 1 2246 | ], 2247 | [ 2248 | 2, 2249 | 1, 2250 | 0 2251 | ], 2252 | [ 2253 | 1, 2254 | 0, 2255 | 1 2256 | ], 2257 | [ 2258 | 5, 2259 | 1, 2260 | 0 2261 | ], 2262 | [ 2263 | 0, 2264 | 0, 2265 | 1 2266 | ], 2267 | [ 2268 | 2, 2269 | 0, 2270 | 1 2271 | ], 2272 | [ 2273 | 3, 2274 | 0, 2275 | 1 2276 | ], 2277 | [ 2278 | 4, 2279 | 1, 2280 | 0 2281 | ], 2282 | [ 2283 | 1, 2284 | 0, 2285 | 1 2286 | ], 2287 | [ 2288 | 3, 2289 | 0, 2290 | 1 2291 | ], 2292 | [ 2293 | 4, 2294 | 0, 2295 | 1 2296 | ], 2297 | [ 2298 | 3, 2299 | 0, 2300 | 1 2301 | ], 2302 | [ 2303 | 4, 2304 | 0, 2305 | 1 2306 | ], 2307 | [ 2308 | 5, 2309 | 0, 2310 | 1 2311 | ], 2312 | [ 2313 | 6, 2314 | 0, 2315 | 1 2316 | ], 2317 | [ 2318 | 4, 2319 | 0, 2320 | 1 2321 | ], 2322 | [ 2323 | 4, 2324 | -1, 2325 | 0 2326 | ], 2327 | [ 2328 | 2, 2329 | -1, 2330 | 1 2331 | ], 2332 | [ 2333 | 6, 2334 | -1, 2335 | 0 2336 | ], 2337 | [ 2338 | 6, 2339 | 0, 2340 | 1 2341 | ], 2342 | [ 2343 | 1, 2344 | -1, 2345 | 0 2346 | ], 2347 | [ 2348 | 6, 2349 | -1, 2350 | 0 2351 | ], 2352 | [ 2353 | 3, 2354 | -1, 2355 | 0 2356 | ], 2357 | [ 2358 | 2, 2359 | 0, 2360 | 1 2361 | ], 2362 | [ 2363 | 4, 2364 | -1, 2365 | 0 2366 | ], 2367 | [ 2368 | 7, 2369 | -1, 2370 | 0 2371 | ], 2372 | [ 2373 | 12, 2374 | -1, 2375 | 0 2376 | ], 2377 | [ 2378 | 10, 2379 | -1, 2380 | 0 2381 | ], 2382 | [ 2383 | 3, 2384 | -1, 2385 | 0 2386 | ], 2387 | [ 2388 | 8, 2389 | -1, 2390 | -1 2391 | ], 2392 | [ 2393 | 5, 2394 | 0, 2395 | -1 2396 | ], 2397 | [ 2398 | 5, 2399 | -1, 2400 | -1 2401 | ], 2402 | [ 2403 | 2, 2404 | 0, 2405 | -1 2406 | ], 2407 | [ 2408 | 3, 2409 | 0, 2410 | -1 2411 | ], 2412 | [ 2413 | 1, 2414 | -1, 2415 | 0 2416 | ], 2417 | [ 2418 | 2, 2419 | 0, 2420 | -1 2421 | ], 2422 | [ 2423 | 4, 2424 | 0, 2425 | -1 2426 | ], 2427 | [ 2428 | 1, 2429 | -1, 2430 | 0 2431 | ], 2432 | [ 2433 | 1, 2434 | 0, 2435 | -1 2436 | ], 2437 | [ 2438 | 2, 2439 | 0, 2440 | -1 2441 | ], 2442 | [ 2443 | 3, 2444 | 0, 2445 | -1 2446 | ], 2447 | [ 2448 | 2, 2449 | 0, 2450 | -1 2451 | ], 2452 | [ 2453 | 1, 2454 | 0, 2455 | -1 2456 | ], 2457 | [ 2458 | 3, 2459 | 0, 2460 | -1 2461 | ], 2462 | [ 2463 | 3, 2464 | 0, 2465 | -1 2466 | ], 2467 | [ 2468 | 3, 2469 | 0, 2470 | -1 2471 | ], 2472 | [ 2473 | 1, 2474 | 0, 2475 | -1 2476 | ], 2477 | [ 2478 | 4, 2479 | 0, 2480 | -1 2481 | ], 2482 | [ 2483 | 3, 2484 | 0, 2485 | -1 2486 | ], 2487 | [ 2488 | 4, 2489 | 0, 2490 | -1 2491 | ], 2492 | [ 2493 | 1, 2494 | 1, 2495 | 0 2496 | ], 2497 | [ 2498 | 1, 2499 | 0, 2500 | -1 2501 | ], 2502 | [ 2503 | 4, 2504 | 1, 2505 | -1 2506 | ], 2507 | [ 2508 | 5, 2509 | 0, 2510 | -1 2511 | ], 2512 | [ 2513 | 1, 2514 | 1, 2515 | 0 2516 | ], 2517 | [ 2518 | 4, 2519 | 0, 2520 | -1 2521 | ], 2522 | [ 2523 | 1, 2524 | 1, 2525 | 0 2526 | ], 2527 | [ 2528 | 1, 2529 | 1, 2530 | -1 2531 | ], 2532 | [ 2533 | 4, 2534 | 1, 2535 | 0 2536 | ], 2537 | [ 2538 | 2, 2539 | 0, 2540 | -1 2541 | ], 2542 | [ 2543 | 2, 2544 | 1, 2545 | 0 2546 | ], 2547 | [ 2548 | 4, 2549 | 1, 2550 | 0 2551 | ], 2552 | [ 2553 | 1, 2554 | 0, 2555 | -1 2556 | ], 2557 | [ 2558 | 2, 2559 | 1, 2560 | 0 2561 | ], 2562 | [ 2563 | 2, 2564 | 1, 2565 | 0 2566 | ], 2567 | [ 2568 | 3, 2569 | 1, 2570 | 0 2571 | ], 2572 | [ 2573 | 4, 2574 | 1, 2575 | 0 2576 | ], 2577 | [ 2578 | 1, 2579 | 0, 2580 | -1 2581 | ], 2582 | [ 2583 | 2, 2584 | 1, 2585 | 0 2586 | ], 2587 | [ 2588 | 3, 2589 | 1, 2590 | 0 2591 | ], 2592 | [ 2593 | 4, 2594 | 1, 2595 | 0 2596 | ], 2597 | [ 2598 | 5, 2599 | 1, 2600 | 0 2601 | ], 2602 | [ 2603 | 5, 2604 | 1, 2605 | 0 2606 | ], 2607 | [ 2608 | 4, 2609 | 1, 2610 | 0 2611 | ], 2612 | [ 2613 | 5, 2614 | 1, 2615 | 0 2616 | ], 2617 | [ 2618 | 1, 2619 | 0, 2620 | 1 2621 | ], 2622 | [ 2623 | 2, 2624 | 1, 2625 | 0 2626 | ], 2627 | [ 2628 | 4, 2629 | 0, 2630 | 1 2631 | ], 2632 | [ 2633 | 1, 2634 | 1, 2635 | 0 2636 | ], 2637 | [ 2638 | 3, 2639 | 1, 2640 | 0 2641 | ], 2642 | [ 2643 | 1, 2644 | 0, 2645 | 1 2646 | ], 2647 | [ 2648 | 2, 2649 | 1, 2650 | 0 2651 | ], 2652 | [ 2653 | 1, 2654 | 0, 2655 | 1 2656 | ], 2657 | [ 2658 | 4, 2659 | 1, 2660 | 0 2661 | ], 2662 | [ 2663 | 1, 2664 | 0, 2665 | 1 2666 | ], 2667 | [ 2668 | 3, 2669 | 0, 2670 | 1 2671 | ], 2672 | [ 2673 | 1, 2674 | 1, 2675 | 0 2676 | ], 2677 | [ 2678 | 2, 2679 | 1, 2680 | 0 2681 | ], 2682 | [ 2683 | 1, 2684 | 0, 2685 | 1 2686 | ], 2687 | [ 2688 | 2, 2689 | 0, 2690 | 1 2691 | ], 2692 | [ 2693 | 4, 2694 | 0, 2695 | 1 2696 | ], 2697 | [ 2698 | 1, 2699 | 1, 2700 | 0 2701 | ], 2702 | [ 2703 | 5, 2704 | 0, 2705 | 1 2706 | ], 2707 | [ 2708 | 3, 2709 | 0, 2710 | 1 2711 | ], 2712 | [ 2713 | 2, 2714 | 0, 2715 | 1 2716 | ], 2717 | [ 2718 | 5, 2719 | 0, 2720 | 1 2721 | ], 2722 | [ 2723 | 4, 2724 | 0, 2725 | 1 2726 | ], 2727 | [ 2728 | 4, 2729 | 0, 2730 | 1 2731 | ], 2732 | [ 2733 | 3, 2734 | 0, 2735 | 1 2736 | ], 2737 | [ 2738 | 4, 2739 | 0, 2740 | 1 2741 | ], 2742 | [ 2743 | 4, 2744 | 0, 2745 | 1 2746 | ], 2747 | [ 2748 | 1, 2749 | -1, 2750 | 0 2751 | ], 2752 | [ 2753 | 3, 2754 | -1, 2755 | 0 2756 | ], 2757 | [ 2758 | 1, 2759 | 0, 2760 | 1 2761 | ], 2762 | [ 2763 | 3, 2764 | -1, 2765 | 1 2766 | ], 2767 | [ 2768 | 4, 2769 | -1, 2770 | 0 2771 | ], 2772 | [ 2773 | 1, 2774 | 0, 2775 | 1 2776 | ], 2777 | [ 2778 | 5, 2779 | -1, 2780 | 0 2781 | ], 2782 | [ 2783 | 2, 2784 | -1, 2785 | 0 2786 | ], 2787 | [ 2788 | 0, 2789 | 0, 2790 | 1 2791 | ] 2792 | ] -------------------------------------------------------------------------------- /assets/no_il.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/no_il.cur -------------------------------------------------------------------------------- /assets/ouija_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ouija_bg.jpg -------------------------------------------------------------------------------- /assets/ouija_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ouija_bg.png -------------------------------------------------------------------------------- /assets/ouija_bg_face_no_eyes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ouija_bg_face_no_eyes.jpg -------------------------------------------------------------------------------- /assets/ouija_bg_face_outline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ouija_bg_face_outline.jpg -------------------------------------------------------------------------------- /assets/ouija_bg_face_stare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/ouija_bg_face_stare.jpg -------------------------------------------------------------------------------- /assets/planchette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/planchette.png -------------------------------------------------------------------------------- /assets/planchette2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/planchette2.png -------------------------------------------------------------------------------- /assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot.jpg -------------------------------------------------------------------------------- /assets/screenshot2-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot2-transparent.png -------------------------------------------------------------------------------- /assets/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot2.jpg -------------------------------------------------------------------------------- /assets/screenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot3.jpg -------------------------------------------------------------------------------- /assets/screenshot4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot4.jpg -------------------------------------------------------------------------------- /assets/screenshot5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/assets/screenshot5.jpg -------------------------------------------------------------------------------- /cloudflare-worker-geo.js: -------------------------------------------------------------------------------- 1 | addEventListener('fetch', event => { 2 | const data = { 3 | city: event.request.cf.city, 4 | colo: event.request.cf.colo, 5 | country : event.request.cf.country, 6 | region : event.request.cf.region, 7 | }; 8 | const json = JSON.stringify(data, null, 2); 9 | 10 | return event.respondWith( 11 | new Response(json, { 12 | headers: { 13 | 'content-type': 'application/json;charset=UTF-8', 14 | 'Access-Control-Allow-Origin': '*', 15 | }, 16 | }) 17 | ); 18 | }); -------------------------------------------------------------------------------- /cloudflare-worker-lokittaja.js: -------------------------------------------------------------------------------- 1 | addEventListener("fetch", event => { 2 | event.respondWith(handleRequest(event.request)) 3 | }) 4 | 5 | async function handleRequest(request) { 6 | const proxy_url = "https://endpoint1.collection.eu.sumologic.com/receiver/v1/http/ZaVnC4dhaV0vBIvS0oahg-8LYhDkyFCtWZ2zZ_7NbP0x0PYd0DmEk2cLgA4DJlePqnHWB5KjcxPFudwdOmtop0b6isr9VgLeKHYmzJ6eSqkD0cyZ6FNQiQ==" 7 | const params = "" + request.url.split('?')[1] 8 | if (params === "" || params === "undefined") { 9 | return new Response("Invalid request") 10 | } 11 | const response = await fetch(proxy_url + '?' + params); 12 | return new Response("Logged", { 13 | headers: { 14 | 'content-type': 'application/json;charset=UTF-8', 15 | 'Access-Control-Allow-Origin': '*', 16 | }, 17 | }) 18 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baobabKoodaa/ouija/9fa490d1ebfe94c91acea78abf865bf13d784a6d/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ouija Online - A horror experience 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 37 |
38 | 39 | 40 |
41 |
42 |

Your life matters

43 |

We all go through rough patches. It's okay to ask for help. Sometimes just talking about it can help.

44 |

For text-based support in the U.S., U.K., Canada, and Ireland, text HOME to 741741.

45 |

Phone support is available around the world.

46 |

The Ouija board is disabled because it can make you feel worse when you're already in a bad state of mind. Take care of yourself first. You are important.

47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 |
?
79 | 80 | 81 | 82 | 83 | 85 | 87 | 89 | 90 | 91 | 92 |
93 | 94 | 95 |
96 |
97 |
98 | 99 | 100 |
101 | 102 | 103 |
104 |

105 |         
106 | 107 | 108 |
109 |
110 |

Welcome, friend

111 |
    112 |
  • This website provides a horror experience. It is akin to a magic show; no actual spirits are 113 | involved. 114 |
  • 115 |
  • Your conversations with spirits are recorded for development purposes, so do not divulge any 116 | sensitive 117 | information.
  • 118 |
  • This experience requires a mouse. Please use your mouse to proceed.
  • 119 |
120 |

Would you like to offer your soul to support this website?

121 |
122 |
Consent to selling
your soul
123 |
Enter without selling
your soul
124 |
125 |
126 | 127 | 128 |
129 |
130 |
131 |
132 |
133 |
Continue
134 |
No more tips
135 |
136 |
137 | 138 | 139 |
140 |
141 |
142 |

Switchboard

143 | 144 |
145 | 146 | 148 | 149 | View source code 150 |
151 |
152 | 153 |
154 | 155 | 157 | 158 | Contact 159 |
160 |
161 | 165 | 169 | 173 | 177 | 181 |
182 | 184 |
185 |
Continue
186 |
187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
199 | 200 | 201 |
202 |
203 | 204 | 206 | 207 |
208 | 209 | 210 |
211 |
212 | 213 | 214 | Locked achievement 215 | 217 | 218 | 219 | Locked achievement 220 | 222 | 223 | 224 | Locked achievement 225 | 227 | 228 |
229 | 230 | 231 |
232 | 233 | 235 | 236 |
237 |

238 | Ghost writer 239 |

240 |

241 | Evil spirit possessed you to write and you let it happen. 242 |

243 |
244 |
245 |
246 | 247 | 249 | 250 |
251 |

252 | That looks like nothing to me 253 |

254 |

255 | You used the planchette to inspect the board closer. 256 |

257 |
258 |
259 |
260 | 261 | 263 | 264 |
265 |

266 | Beware of false prophets 267 |

268 |

269 | You thought angering the spirit would be a good idea. 270 |

271 |
272 |
273 | 274 | 275 |
276 |
277 |

278 | Sorry to interrupt the game... 279 |

280 |

281 | How are you doing? 282 |

283 |

284 | 😊 I'm fine, just playing 285 |

286 |

287 | 😔 It's a rough time for me 288 |

289 |
290 |
291 | 292 | 293 |
294 | 295 | 296 | 297 |
298 | 299 | 300 | 301 | 302 | 303 | 305 | 306 | 308 | 309 | 311 | 312 | 314 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | 4 | /* Nothing should be selectable. */ 5 | user-select: none; 6 | -moz-user-select: none; 7 | -khtml-user-select: none; 8 | -webkit-user-select: none; 9 | -o-user-select: none; 10 | -ms-user-select: none; 11 | -webkit-touch-callout: none; 12 | 13 | /* Nothing should be draggable. */ 14 | user-drag: none; 15 | -webkit-user-drag: none; 16 | } 17 | 18 | noscript { 19 | color: white; 20 | } 21 | 22 | p, 23 | h1, 24 | pre { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | a { 30 | color: black; 31 | cursor: none; 32 | } 33 | 34 | html { 35 | height: 100%; 36 | } 37 | 38 | body { 39 | /* Using 32px margin around body to avoid funny business with cursor near the edges of window */ 40 | margin: 32px; 41 | height: calc(100% - 64px); 42 | overflow-y: hidden; 43 | overflow-x: hidden; 44 | padding: 0; 45 | background: black; 46 | } 47 | 48 | @font-face { 49 | font-family: 'Feral'; 50 | src: url('assets/Feral-Regular.woff2') format('woff2'), 51 | url('assets/Feral-Regular.woff') format('woff'); 52 | font-weight: normal; 53 | font-style: normal; 54 | font-display: swap; 55 | } 56 | 57 | @font-face { 58 | font-family: 'Carnivalee Freakshow'; 59 | src: url('assets/CarnivaleeFreakshow.woff2') format('woff2'), 60 | url('assets/CarnivaleeFreakshow.woff') format('woff'); 61 | font-weight: normal; 62 | font-style: normal; 63 | font-display: swap; 64 | } 65 | 66 | @font-face { 67 | font-family: 'Kingthings Trypewriter 2'; 68 | src: url('assets/KingthingsTrypewriter2.woff2') format('woff2'), 69 | url('assets/KingthingsTrypewriter2.woff') format('woff'); 70 | font-weight: normal; 71 | font-style: normal; 72 | font-display: swap; 73 | } 74 | 75 | h1 { 76 | font-size: 4vw; 77 | } 78 | 79 | #loadingOverlay { 80 | background-color: black; 81 | position: absolute; 82 | top: 0; 83 | left: 0; 84 | width: 100%; 85 | height: 100%; 86 | z-index: 100000; 87 | opacity: 1.0; 88 | transition: opacity 1.0s ease-in; 89 | } 90 | 91 | #suicidePreventionOverlay { 92 | display: none; 93 | background-color: black; 94 | position: absolute; 95 | top: 0; 96 | left: 0; 97 | width: 100%; 98 | height: 100%; 99 | z-index: 99999; 100 | opacity: 0.8; 101 | color: white; 102 | font-family: 'Kingthings Trypewriter 2'; 103 | } 104 | 105 | #suicideTextContainer { 106 | position: absolute; 107 | width: 48%; 108 | height: 35vw; 109 | left: 46%; 110 | top: 53%; 111 | -webkit-transform: translate(-50%, -50%); 112 | transform: translate(-50%, -50%); 113 | } 114 | 115 | #suicideTextContainer h1 { 116 | font-size: 3vw; 117 | } 118 | 119 | #suicideTextContainer p { 120 | margin-top: 1vw; 121 | font-size: 1.5vw; 122 | } 123 | 124 | #suicideTextContainer p a { 125 | color: #fbedc8; 126 | cursor: pointer; 127 | } 128 | 129 | #lightFlash { 130 | background-color: white; 131 | position: absolute; 132 | top: 0; 133 | left: 0; 134 | width: 100%; 135 | height: 100%; 136 | z-index: 100001; 137 | /* background-image: url('assets/face-no-eyes.png'); 138 | background-repeat: no-repeat; 139 | background-size: auto 100%; 140 | background-position-x: center; */ 141 | display: none; 142 | } 143 | 144 | #settingsGearHoverboard { 145 | position: absolute; 146 | top: 0vw; 147 | right: 0vw; 148 | width: 6vw; 149 | height: 6vw; 150 | z-index: 998; 151 | } 152 | 153 | #settingsGear { 154 | position: absolute; 155 | top: 2vw; 156 | right: 2vw; 157 | width: 2vw; 158 | height: 2vw; 159 | fill: #d35400; 160 | opacity: 0.7; 161 | cursor: pointer; 162 | z-index: 999; 163 | } 164 | 165 | #settingsGear:hover { 166 | opacity: 1.0; 167 | } 168 | 169 | .popupContainer { 170 | display: none; 171 | z-index: 994; 172 | border: 1px solid black; 173 | border-radius: 10px; 174 | background-color: #fbedc8; 175 | color: black; 176 | font-size: 2vw; 177 | position: absolute; 178 | left: 50%; 179 | top: 50%; 180 | width: 80%; 181 | -webkit-transform: translate(-50%, -50%); 182 | transform: translate(-50%, -50%); 183 | padding: 1.5vw; 184 | cursor: none; 185 | transition: opacity 3s ease-in; 186 | opacity: 1.0; 187 | font-family: 'Carnivalee Freakshow'; 188 | background-color: #fbedc8; 189 | background-image: url("data:image/svg+xml,%3Csvg width='180' height='180' viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M82.42 180h-1.415L0 98.995v-2.827L6.167 90 0 83.833V81.004L81.005 0h2.827L90 6.167 96.167 0H98.996L180 81.005v2.827L173.833 90 180 96.167V98.996L98.995 180h-2.827L90 173.833 83.833 180H82.42zm0-1.414L1.413 97.58 8.994 90l-7.58-7.58L82.42 1.413 90 8.994l7.58-7.58 81.006 81.005-7.58 7.58 7.58 7.58-81.005 81.006-7.58-7.58-7.58 7.58zM175.196 0h-25.832c1.033 2.924 2.616 5.59 4.625 7.868C152.145 9.682 151 12.208 151 15c0 5.523 4.477 10 10 10 1.657 0 3 1.343 3 3v4h16V0h-4.803c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6s-6-2.686-6-6c0-1.093.292-2.117.803-3h10.394-13.685C161.18.938 161 1.948 161 3v4c-4.418 0-8 3.582-8 8s3.582 8 8 8c2.76 0 5 2.24 5 5v2h4v-4h2v4h4v-4h2v4h2V0h-4.803zm-15.783 0c-.27.954-.414 1.96-.414 3v2.2c-1.25.254-2.414.74-3.447 1.412-1.716-1.93-3.098-4.164-4.054-6.612h7.914zM180 17h-3l2.143-10H180v10zm-30.635 163c-.884-2.502-1.365-5.195-1.365-8 0-13.255 10.748-24 23.99-24H180v32h-30.635zm12.147 0c.5-1.416 1.345-2.67 2.434-3.66l-1.345-1.48c-1.498 1.364-2.62 3.136-3.186 5.14H151.5c-.97-2.48-1.5-5.177-1.5-8 0-12.15 9.84-22 22-22h8v30h-18.488zm13.685 0c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 148h8.01C21.26 148 32 158.742 32 172c0 2.805-.48 5.498-1.366 8H0v-32zm0 2h8c12.15 0 22 9.847 22 22 0 2.822-.53 5.52-1.5 8h-7.914c-.567-2.004-1.688-3.776-3.187-5.14l-1.346 1.48c1.09.99 1.933 2.244 2.434 3.66H0v-30zm15.197 30c-1.037-1.793-2.976-3-5.197-3-2.22 0-4.16 1.207-5.197 3h10.394zM0 32h16v-4c0-1.657 1.343-3 3-3 5.523 0 10-4.477 10-10 0-2.794-1.145-5.32-2.992-7.134C28.018 5.586 29.6 2.924 30.634 0H0v32zm0-2h2v-4h2v4h4v-4h2v4h4v-2c0-2.76 2.24-5 5-5 4.418 0 8-3.582 8-8s-3.582-8-8-8V3c0-1.052-.18-2.062-.512-3H0v30zM28.5 0c-.954 2.448-2.335 4.683-4.05 6.613-1.035-.672-2.2-1.16-3.45-1.413V3c0-1.04-.144-2.046-.414-3H28.5zM0 17h3L.857 7H0v10zM15.197 0c.51.883.803 1.907.803 3 0 3.314-2.686 6-6 6S4 6.314 4 3c0-1.093.292-2.117.803-3h10.394zM109 115c-1.657 0-3 1.343-3 3v4H74v-4c0-1.657-1.343-3-3-3-5.523 0-10-4.477-10-10 0-2.793 1.145-5.318 2.99-7.132C60.262 93.638 58 88.084 58 82c0-13.255 10.748-24 23.99-24h16.02C111.26 58 122 68.742 122 82c0 6.082-2.263 11.636-5.992 15.866C117.855 99.68 119 102.206 119 105c0 5.523-4.477 10-10 10zm0-2c-2.76 0-5 2.24-5 5v2h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-4h-2v4h-4v-2c0-2.76-2.24-5-5-5-4.418 0-8-3.582-8-8s3.582-8 8-8v-4c0-2.64 1.136-5.013 2.946-6.66L72.6 84.86C70.39 86.874 69 89.775 69 93v2.2c-1.25.254-2.414.74-3.447 1.412C62.098 92.727 60 87.61 60 82c0-12.15 9.84-22 22-22h16c12.15 0 22 9.847 22 22 0 5.61-2.097 10.728-5.55 14.613-1.035-.672-2.2-1.16-3.45-1.413V93c0-3.226-1.39-6.127-3.6-8.14l-1.346 1.48C107.864 87.987 109 90.36 109 93v4c4.418 0 8 3.582 8 8s-3.582 8-8 8zM90.857 97L93 107h-6l2.143-10h1.714zM80 99c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm20 0c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6z' fill='%23d9c99f' fill-opacity='0.24' fill-rule='evenodd'/%3E%3C/svg%3E"); 190 | } 191 | 192 | .popupContainer>ul { 193 | margin-top: 1.5vw; 194 | margin-bottom: 0; 195 | } 196 | 197 | .popupContainer>p, 198 | #tooltipContent>p { 199 | margin-top: 1.5vw; 200 | } 201 | 202 | .popupHoverboard { 203 | position: absolute; 204 | top: 0; 205 | left: 0; 206 | width: 100%; 207 | height: 100%; 208 | z-index: 995; 209 | } 210 | 211 | .popupButtonsContainer { 212 | display: flex; 213 | flex-direction: row; 214 | width: 100%; 215 | text-align: center; 216 | margin-top: 1.5vw; 217 | z-index: 997; 218 | transition: margin-top 0.4s ease-in-out; 219 | } 220 | 221 | .popupButton { 222 | border: 1px solid black; 223 | border-radius: 10px; 224 | padding: 0.5vw; 225 | width: 100%; 226 | flex-grow: 1; 227 | z-index: 996; 228 | } 229 | 230 | .primaryButton { 231 | background-color: #958219; 232 | color: white; 233 | transition: 0.3s linear; 234 | } 235 | 236 | .primaryButton:hover { 237 | background-color: #968a47; 238 | box-shadow: 0 0 15px #5c533b; 239 | text-shadow: 0 0 5px #5c533b; 240 | } 241 | 242 | #consentNo { 243 | background-color: #ebebeb; 244 | margin-left: 1.5vw; 245 | transition: 0.3s linear; 246 | } 247 | 248 | #consentNo:hover { 249 | color: gray; 250 | } 251 | 252 | #tooltipContinueButton { 253 | height: 4vw; 254 | line-height: 2.6vw; 255 | } 256 | 257 | #tooltipShutUpButton { 258 | height: 4vw; 259 | line-height: 2.6vw; 260 | background-color: #ebebeb; 261 | margin-left: 1.5vw; 262 | transition: 0.3s linear; 263 | } 264 | 265 | #tooltipShutUpButton:hover { 266 | color: gray; 267 | } 268 | 269 | #saveSettings { 270 | height: 4vw; 271 | line-height: 2.6vw; 272 | } 273 | 274 | #textInputAPIKey { 275 | background-color: white; 276 | position: absolute; 277 | border-radius: 5px; 278 | margin-top: 1.7vw; 279 | width: 31.3vw; 280 | left: 7vw; 281 | height: 2vw; 282 | line-height: 2vw; 283 | transition: margin-top 0.4s ease-in-out; 284 | z-index: 996; 285 | } 286 | 287 | .externalLinkRow { 288 | position: relative; 289 | display: inline-block; 290 | width: 100%; 291 | text-align: center; 292 | margin-top: 1.5vw; 293 | z-index: 997; 294 | text-align: left; 295 | } 296 | 297 | .externalLinkRow svg { 298 | height: 2.6vw; 299 | vertical-align: middle; 300 | fill: #958219; 301 | } 302 | 303 | .externalLinkRow span { 304 | vertical-align: middle; 305 | } 306 | 307 | .externalLinkRow:hover { 308 | opacity: 0.5; 309 | } 310 | 311 | #banshee { 312 | position: absolute; 313 | width: 3vw; 314 | top: 69%; 315 | left: 10%; 316 | opacity: 0; 317 | z-index: 10000; 318 | pointer-events: none; 319 | filter: blur(0.3px); 320 | animation-duration: 0.7s; 321 | animation-timing-function: ease-in; 322 | animation-fill-mode: forwards; 323 | } 324 | 325 | #glassCrack { 326 | display: none; 327 | pointer-events: none; 328 | position: absolute; 329 | width: 80%; 330 | left: -64px; 331 | top: 50%; 332 | transform: translateY(-50%); 333 | z-index: 10000; 334 | } 335 | 336 | @keyframes banshee-flying { 337 | 0% { 338 | transform: scale(1.0); 339 | } 340 | 50% { 341 | transform: scale(15.0); 342 | } 343 | 100% { 344 | transform: scale(300) translateX(0.4vw) translateY(-0.1vw) 345 | } 346 | } 347 | 348 | #achievement-hoverboard { 349 | position: absolute; 350 | bottom: 0vw; 351 | right: 0vw; 352 | width: 13vw; 353 | height: 6vw; 354 | z-index: 7; 355 | } 356 | 357 | #achievement-row { 358 | position: absolute; 359 | bottom: 2vw; 360 | right: 2vw; 361 | fill: #d35400; 362 | z-index: 8; 363 | } 364 | 365 | #achievement-row svg { 366 | margin-left: 1vw; 367 | vertical-align: middle; 368 | width: 2vw; 369 | opacity: 0; 370 | fill: #d35400; 371 | } 372 | 373 | #achievement-row svg.not-achieved { 374 | opacity: 0.4; 375 | fill: grey; 376 | } 377 | 378 | #achievement-row svg.achieved { 379 | opacity: 0.7; 380 | cursor: pointer; 381 | } 382 | 383 | #achievement-row svg.achieved:hover { 384 | opacity: 1; 385 | } 386 | 387 | #suicide-prevention-popup { 388 | position: absolute; 389 | bottom: -17vw; 390 | left: 20px; 391 | width: 40vw; 392 | height: 15vw; 393 | border-radius: 5px; 394 | background-color: #2c2722; 395 | border: 2px solid #d354009d; 396 | z-index: 998; 397 | } 398 | 399 | #suicide-prevention-popup .button { 400 | padding: 5px; 401 | width: fit-content; 402 | min-width: 18vw; 403 | color: #fbedc8; 404 | border: 2px solid #fbedc8; 405 | border-radius: 5px; 406 | cursor: pointer; 407 | } 408 | 409 | .achievement-notification { 410 | position: absolute; 411 | bottom: -12vw; 412 | right: -3px; 413 | width: 25vw; 414 | height: 10vw; 415 | border-radius: 5px; 416 | background-color: #2c2722; 417 | border: 2px solid #d354009d; 418 | pointer-events: none; 419 | z-index: 9; 420 | } 421 | 422 | .achievement-notification svg { 423 | position: absolute; 424 | top: -1.2vw; 425 | left: -1.2vw; 426 | width: 4vw; 427 | fill: #d35400; 428 | } 429 | 430 | .achievementText { 431 | font-family: 'Kingthings Trypewriter 2'; 432 | margin-left: 4vw; 433 | margin-right: 1vw; 434 | top: 50%; 435 | position: absolute; 436 | transform: translateY(-50%); 437 | } 438 | 439 | .achievementText h1 { 440 | color: white; 441 | font-size: 1.8vw; 442 | font-style: italic; 443 | } 444 | 445 | .achievementText p { 446 | color: white; 447 | margin-top: 1vw; 448 | font-size: 1vw; 449 | } 450 | 451 | #popupSettings { 452 | z-index: 996; 453 | } 454 | 455 | .switch { 456 | position: relative; 457 | display: block; 458 | width: 4.6vw; 459 | height: 2.6vw; 460 | z-index: 996; 461 | margin-top: 1.5vw; 462 | } 463 | 464 | .switch input { 465 | opacity: 0; 466 | width: 0; 467 | height: 0; 468 | } 469 | 470 | .slider { 471 | position: absolute; 472 | cursor: none; 473 | top: 0; 474 | left: 0; 475 | right: 0; 476 | bottom: 0; 477 | background-color: #ccc; 478 | -webkit-transition: .4s; 479 | transition: .4s; 480 | border-radius: 34px; 481 | } 482 | 483 | .slider:not(.checked):hover { 484 | background-color: #bbb; 485 | } 486 | 487 | .slider.checked:hover { 488 | background-color: #968a47; 489 | } 490 | 491 | .slider-text { 492 | position: absolute; 493 | display: inline-block; 494 | left: 5.6vw; 495 | height: 2.4vw; 496 | width: 30vw; 497 | line-height: 2.4vw; 498 | pointer-events: none; 499 | } 500 | 501 | .slider:before { 502 | position: absolute; 503 | content: ""; 504 | height: 2.0vw; 505 | width: 2.0vw; 506 | left: 0.3vw; 507 | bottom: 0.3vw; 508 | background-color: white; 509 | -webkit-transition: .4s; 510 | transition: .4s; 511 | border-radius: 50%; 512 | } 513 | 514 | .checked { 515 | background-color: #958219; 516 | } 517 | 518 | .checked:before { 519 | -webkit-transform: translateX(2.0vw); 520 | -ms-transform: translateX(2.0vw); 521 | transform: translateX(2.0vw); 522 | } 523 | 524 | #bg { 525 | position: relative; 526 | width: 100%; 527 | height: 100%; 528 | cursor: none; 529 | z-index: 1; 530 | } 531 | 532 | @keyframes board-glow { 533 | 0% { 534 | box-shadow: 0 0 50px #d35400; 535 | } 536 | 537 | 100% { 538 | box-shadow: 0 0 50px 20px #d35400; 539 | } 540 | } 541 | 542 | @keyframes scale-up-down { 543 | 0% { 544 | transform: scale(1.0) 545 | } 546 | 547 | 50% { 548 | transform: scale(1.1) 549 | } 550 | 551 | 100% { 552 | transform: scale(1.0) 553 | } 554 | } 555 | 556 | @keyframes rotate-circle { 557 | 0% { 558 | transform: rotateZ(0deg) 559 | } 560 | 561 | 100% { 562 | transform: rotateZ(180deg) 563 | } 564 | } 565 | 566 | .one-time-bump { 567 | animation-name: scale-up-down; 568 | animation-iteration-count: 1; 569 | animation-duration: 0.5s; 570 | } 571 | 572 | .one-time-gear-roll { 573 | animation-name: rotate-circle; 574 | animation-timing-function: cubic-bezier(.12, 1, .96, .97); 575 | animation-iteration-count: 1; 576 | animation-fill-mode: forwards; 577 | animation-duration: 1s; 578 | } 579 | 580 | #boardContainer { 581 | position: absolute; 582 | width: 50%; 583 | left: 50%; 584 | top: 50%; 585 | -webkit-transform: translate(-50%, -50%); 586 | transform: translate(-50%, -50%); 587 | z-index: 10; 588 | } 589 | 590 | .shake-board { 591 | animation-name: board-shake; 592 | animation-duration: 0.2s; 593 | animation-timing-function: ease-out; 594 | animation-iteration-count: 1; 595 | } 596 | 597 | @keyframes board-shake { 598 | 0% { 599 | transform: translate(-50%, -50%); 600 | } 601 | 20% { 602 | transform: translate(-45%, -45%) rotate3d(1, 1, 1, 10deg) scale(1.03); 603 | } 604 | 40% { 605 | transform: translate(-55%, -45%) rotate3d(1, 1, 1, -10deg) scale(1.1); 606 | } 607 | 60% { 608 | transform: translate(-45%, -45%) rotate3d(1, 1, 1, 10deg) scale(1.07); 609 | } 610 | 80% { 611 | transform: translate(-55%, -45%) rotate3d(1, 1, 1, -10deg) scale(1.03); 612 | } 613 | 100% { 614 | transform: translate(-50%, -50%); 615 | } 616 | } 617 | 618 | #board { 619 | position: relative; 620 | width: 100%; 621 | border-radius: 10%; 622 | z-index: 11; 623 | animation-name: board-glow; 624 | animation-iteration-count: infinite; 625 | animation-duration: 2s; 626 | animation-timing-function: ease-in-out; 627 | animation-direction: alternate; 628 | } 629 | 630 | #boardEasterEggHelper { 631 | display: none; 632 | position: absolute; 633 | top: 0; 634 | left: 0; 635 | width: 100%; 636 | border-radius: 10%; 637 | z-index: 12; 638 | opacity: 0; 639 | transition: 4s ease-in; 640 | } 641 | 642 | #hoverBoard { 643 | position: absolute; 644 | top: 0%; 645 | left: 0%; 646 | width: 100%; 647 | height: 100%; 648 | z-index: 13; 649 | cursor: none; 650 | } 651 | 652 | #planchette, 653 | #planchetteHelper { 654 | position: absolute; 655 | top: 44%; 656 | left: 40.5%; 657 | width: 18%; 658 | transform: rotate(130deg); 659 | cursor: none; 660 | } 661 | 662 | #planchette { 663 | z-index: 15; 664 | transition: filter 1.5s ease-in; 665 | /* TODO ease-in-out? */ 666 | } 667 | 668 | .planchette-no-glow { 669 | filter: drop-shadow(5px 5px 5px #222); 670 | } 671 | 672 | .planchette-active-glow { 673 | filter: drop-shadow(5px 5px 5px rgb(255, 198, 198)); 674 | /** filter: drop-shadow(5px 5px 5px '#fce'); */ 675 | } 676 | 677 | #planchetteHelper { 678 | z-index: 16; 679 | opacity: 0; 680 | /* Can't use visibility:hidden because need mouse movements to trigger events */ 681 | } 682 | 683 | #magnifying-glass { 684 | background-image: url('assets/ouija_bg.jpg'); 685 | background-repeat: no-repeat; 686 | position: absolute; 687 | border-radius: 50%; 688 | cursor: none; 689 | visibility: hidden; 690 | z-index: 14; 691 | } 692 | 693 | #magnifying-glass-surface { 694 | background-color: #563e23; 695 | opacity: 0.4; 696 | width: 100%; 697 | height: 100%; 698 | } 699 | 700 | #tooltipContainer { 701 | position: absolute; 702 | right: 0; 703 | bottom: 2vw; 704 | width: 10vw; 705 | height: 15vw; 706 | z-index: 12; 707 | cursor: none; 708 | pointer-events: none; 709 | filter: url(#smokeFilter); 710 | } 711 | 712 | #tooltipSymbol { 713 | line-height: 15vw; 714 | font-family: 'Kingthings Trypewriter 2', 'Courier New', 'Courier', monospace; 715 | font-size: 14vw; 716 | color: black; 717 | text-align: center; 718 | vertical-align: middle; 719 | filter: blur(11px); 720 | transform: scale(0.0); 721 | transition: filter 2s ease-out, transform 2s ease-in-out, opacity 2s ease-in-out; 722 | /* color: transparent; 723 | background: linear-gradient(#fff, #999, #ddd, #888); 724 | background-clip: text; 725 | -webkit-background-clip: text; */ 726 | } 727 | 728 | #spiritMessageContainer { 729 | position: absolute; 730 | top: -13%; 731 | pointer-events: none; 732 | 733 | /* Centering */ 734 | left: -50%; 735 | width: 100vw; 736 | text-align: center; 737 | letter-spacing: 3vw; 738 | text-indent: 3vw; 739 | 740 | /* Styling */ 741 | font-size: 2vw; 742 | color: #fff; 743 | text-shadow: 0 0 5px #d35400; 744 | text-transform: uppercase; 745 | font-family: 'Feral'; 746 | 747 | } 748 | 749 | #spiritMessageContainer::before, 750 | #spiritMessageContainer::after { 751 | content: attr(data-text); 752 | position: absolute; 753 | top: 0; 754 | left: 0; 755 | width: 100%; 756 | height: 100%; 757 | z-index: -1; 758 | } 759 | 760 | #spiritMessageContainer::before { 761 | color: #f3ba39; 762 | animation: glitch-effect 6s infinite; 763 | } 764 | 765 | #spiritMessageContainer::after { 766 | color: #f3ba39; 767 | animation: glitch-effect 4s infinite; 768 | } 769 | 770 | @keyframes glitch-effect { 771 | 0% { 772 | left: -4px; 773 | top: -4px; 774 | } 775 | 776 | 25% { 777 | left: 4px; 778 | top: 0px; 779 | } 780 | 781 | 50% { 782 | left: -2px; 783 | top: 4px; 784 | } 785 | 786 | 75% { 787 | left: 2px; 788 | top: -2px; 789 | } 790 | 791 | 100% { 792 | left: 0px; 793 | top: -4px; 794 | } 795 | } 796 | 797 | #userMessageContainer { 798 | position: absolute; 799 | left: -50%; 800 | bottom: -13%; 801 | display: flex; 802 | justify-content: center; 803 | width: 100vw; 804 | height: 3vh; 805 | pointer-events: none; 806 | } 807 | 808 | #userMessagePre { 809 | position: absolute; 810 | height: 3vh; 811 | line-height: 3vh; 812 | font-size: 2vw; 813 | letter-spacing: 0.8vw; 814 | text-transform: uppercase; 815 | font-family: 'Kingthings Trypewriter 2', 'Courier New', 'Courier', monospace; 816 | color: #fff; 817 | transition: color 1.0s ease-out; 818 | } 819 | 820 | .blinking-caret { 821 | border-right: .15em solid #ff9900; 822 | animation: blink-caret .75s step-end infinite; 823 | } 824 | 825 | @keyframes blink-caret { 826 | 827 | from, 828 | to { 829 | border-color: transparent 830 | } 831 | 832 | 50% { 833 | border-color: #ff9900; 834 | } 835 | } 836 | 837 | .orangey-text { 838 | color: #563e23 !important; 839 | } 840 | 841 | #cursor { 842 | position: absolute; 843 | top: 30%; 844 | left: 30%; 845 | height: 33px; 846 | /* height must be evenly divisible from 128px */ 847 | visibility: hidden; 848 | pointer-events: none; 849 | z-index: 1000; 850 | } 851 | 852 | .disable-transitions { 853 | transition: none !important; 854 | } --------------------------------------------------------------------------------