{"version":3,"sources":["gameEngine/engine.js","sprites/playerSprite.jsx","sprites/npcSprite.jsx","sprites/circleSprite.js","forms/startDialog.jsx","forms/endDialog.jsx","gameEngine/gameCanvas.jsx","coalesce.jsx","serviceWorker.js","index.jsx"],"names":["CircleObject","xPos","yPos","radius","thisCircle","collisionCheck","circle1","circle2","xDelta","Math","abs","yDelta","sqrt","SpawnRing","circle","getRandomSpawnLocation","randomSeed","random","PI","xSpawn","cos","ySpawn","sin","RectObject","width","height","thisRect","childRectBoundsCheck","childObject","childCircleBoundsCheck","PlayerSprite","ColourScheme","useContext","ColourSchemeContext","cx","cy","r","fill","White","className","NpcSprite","type","SPRITE_TYPES","SpriteTypesContext","foodStyle","enemyStyle","FOOD","require","ANIMATION_STATES","breathe","circleSprite","newSpriteState","bounds","animationState","status","CircleSprite","thisCircleSprite","StartDialog","toggleClockActive","displayStartDialog","setDisplayStartDialog","onClick","disabled","EndDialog","resetGame","displayEndDialog","setDisplayEndDialog","clock","ENEMY","createContext","GameCanvas","resetClock","useDimensions","canvasRef","canvasWidth","x","y","canvasHeight","useState","CANVAS_STARTING_SIZE","canvasBounds","setCanvasBounds","playerMovementSpeed","setPlayerMovementSpeed","playerSpriteData","setPlayerSpriteData","UP","DOWN","LEFT","RIGHT","playerMovementStatus","setPlayerMovementStatus","npcSprites","setNpcSprites","spawnRing","setSpawnRing","spriteDeleteBox","setSpriteDeleteBox","npcSpawnInterval","setNpcSpawnInterval","npcMovementSpeed","setNpcMovementSpeed","spriteTypeSkew","setSpriteTypeSkew","reset","keyboardRef","useRef","useEffect","MARGIN_SIZE","newPlayerData","newPlayerBounds","current","focus","npcCanvasWidth","undefined","npcCanvasHeight","coordinates","npcSizeFactor","npcVectorFactor","npcPathX","npcPathY","spriteType","round","push","spriteMovementVector","key","changed","i","length","currSprite","newPlayerSpriteData","splice","role","onKeyDown","event","newMovementStatus","onKeyUp","onKeyPress","tabIndex","ref","style","Provider","value","map","spriteParams","COLOUR_SCHEME","Coalesce","setClock","clockActive","setClockActive","useCallback","timer","setInterval","clearInterval","Boolean","window","location","hostname","match","ReactDOM","render","StrictMode","document","getElementById","navigator","serviceWorker","ready","then","registration","unregister","catch","error","console","message"],"mappings":"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","file":"static/js/main.03e82a89.chunk.js","sourcesContent":["/**\n * Creates a new box-shaped object. If height is omitted, a square is created.\n * @param {number} xPos - X position on screen (anchored to top-left corner)\n * @param {number} yPos - Y position on screen (anchored to top-left corner)\n * @param {number} width - Width of element\n * @param {number} height - Height of element. Create a square by omitting this.\n */\nfunction RectObject(xPos, yPos, width, height = width) {\n const thisRect = {\n xPos, yPos, width, height,\n };\n return {\n ...thisRect,\n /**\n * This function checks if the given coordinates are valid for a child\n * rectangle inside this one. Returns boolean.\n */\n childRectBoundsCheck: (childObject) => {\n if (childObject.xPos < thisRect.xPos) { return false; }\n if (childObject.yPos < thisRect.yPos) { return false; }\n if (childObject.xPos + childObject.width > thisRect.xPos + thisRect.width) { return false; }\n if (childObject.yPos + childObject.height > thisRect.yPos + thisRect.height) { return false; }\n return true;\n },\n /**\n * This function checks if the given coordinates are valid for a child\n * circle inside this rectangle. Returns boolean.\n */\n childCircleBoundsCheck: (childObject) => {\n if (childObject.xPos - childObject.radius < thisRect.xPos) {\n return false;\n }\n if (childObject.yPos - childObject.radius < thisRect.yPos) {\n return false;\n }\n if (childObject.xPos + childObject.radius > thisRect.xPos + thisRect.width) {\n return false;\n }\n if (childObject.yPos + childObject.radius > thisRect.yPos + thisRect.height) {\n return false;\n }\n return true;\n },\n };\n}\n\n/**\n * Creates a new circle-shaped object.\n * @param {number} xPos - X position on screen (anchored to center)\n * @param {number} yPos - Y position on screen (anchored to center)\n * @param {number} radius - Radius of element\n */\nexport function CircleObject(xPos, yPos, radius) {\n const thisCircle = {\n xPos, yPos, radius,\n };\n return {\n ...thisCircle,\n };\n}\n\n/**\n * Checks if the coordinates given for another circle will result\n * in a collision with this one. Returns boolean.\n * @param {*} circle1 The first CircleObject\n * @param {*} circle2 The second CircleObject\n * @returns {boolean} True if a collision has occurred, false if not.\n */\nexport function collisionCheck(circle1, circle2) {\n const xDelta = Math.abs(circle1.xPos - circle2.xPos);\n const yDelta = Math.abs(circle1.yPos - circle2.yPos);\n const centerDistance = Math.sqrt((xDelta ** 2) + (yDelta ** 2));\n if (centerDistance <= circle1.radius + circle2.radius) {\n return true;\n }\n return false;\n}\n\n/**\n * An invisible ring that returns random points around its curcumference\n * for sprites to spawn along.\n * @param {*} xPos - X position on screen (anchored to center)\n * @param {*} yPos - Y position on screen (anchored to center)\n * @param {*} radius - Radius of ring\n */\nexport function SpawnRing(xPos, yPos, radius) {\n // TODO make this an ellipsis to deal with rectangular windows\n const circle = CircleObject(xPos, yPos, radius);\n\n return {\n ...circle,\n /**\n * Returns a random coordinate along the circle's circumference\n */\n getRandomSpawnLocation: () => {\n const randomSeed = Math.random() * 2 * Math.PI;\n const xSpawn = xPos + radius * Math.cos(randomSeed);\n const ySpawn = yPos + radius * Math.sin(randomSeed);\n return { xSpawn, ySpawn };\n },\n };\n}\n\nexport default RectObject;\n","import React, { useContext } from 'react';\nimport PropTypes from 'prop-types';\nimport { ColourSchemeContext } from '../coalesce';\n\n/**\n * User-controllable sprite\n * @param {*} props - Inputs are starting x and y position, and a clock signal.\n */\nfunction PlayerSprite({\n xPos, yPos, radius,\n}) {\n const ColourScheme = useContext(ColourSchemeContext);\n\n return (\n // TODO make multiple circles over time\n \n );\n}\n\nPlayerSprite.propTypes = {\n xPos: PropTypes.number.isRequired,\n yPos: PropTypes.number.isRequired,\n radius: PropTypes.number.isRequired,\n};\n\nexport default PlayerSprite;\n","import React, { useContext } from 'react';\nimport PropTypes from 'prop-types';\nimport { ColourSchemeContext } from '../coalesce';\nimport { SpriteTypesContext } from '../gameEngine/gameCanvas';\n\nfunction NpcSprite({\n xPos, yPos, radius, type,\n}) {\n const ColourScheme = useContext(ColourSchemeContext);\n const SPRITE_TYPES = useContext(SpriteTypesContext);\n const foodStyle = ColourScheme['Bright Blue'];\n const enemyStyle = ColourScheme['Deep Red'];\n\n return (\n \n );\n}\n\nNpcSprite.propTypes = {\n xPos: PropTypes.number.isRequired,\n yPos: PropTypes.number.isRequired,\n radius: PropTypes.number.isRequired,\n type: PropTypes.number.isRequired,\n};\n\nexport default NpcSprite;\n","const { CircleObject } = require('../gameEngine/engine');\n\nconst ANIMATION_STATES = { SHRINKING: 1, GROWING: 2 };\nconst BREATHING_ANIMATION_MAGNITUDE = 0.15;\n\nfunction CircleSprite(xPos, yPos, radius) {\n const thisCircleSprite = {\n bounds: CircleObject(xPos, yPos, radius),\n animationState: {\n radius,\n status: ANIMATION_STATES.SHRINKING,\n },\n };\n\n return {\n ...thisCircleSprite,\n };\n}\n\nexport function breathe(circleSprite) {\n const newSpriteState = { ...circleSprite };\n const { bounds, animationState } = { ...circleSprite };\n if (animationState.status === ANIMATION_STATES.GROWING) {\n animationState.radius += 0.1;\n } else if (animationState.status === ANIMATION_STATES.SHRINKING) {\n animationState.radius -= 0.1;\n }\n if (animationState.radius / bounds.radius > 1 + BREATHING_ANIMATION_MAGNITUDE) {\n animationState.status = ANIMATION_STATES.SHRINKING;\n } else\n if (animationState.radius / bounds.radius\n < 1 - BREATHING_ANIMATION_MAGNITUDE) {\n animationState.status = ANIMATION_STATES.GROWING;\n }\n return newSpriteState;\n}\n\nexport default CircleSprite;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nfunction StartDialog({ toggleClockActive, displayStartDialog, setDisplayStartDialog }) {\n function start() {\n toggleClockActive();\n setDisplayStartDialog(false);\n }\n\n return (\n
\n

Welcome to Coalesce.

\n
    \n
  • Use the arrow keys to move.
  • \n
  • Eat the blue nodes - they are food.
  • \n
  • Avoid the red nodes - they are enemies.
  • \n
  • \n See how long you can last. It's survival of the fittest out here! \n
  • \n
  • Hit the enter key to begin.
  • \n
\n \n
\n );\n}\nStartDialog.propTypes = {\n toggleClockActive: PropTypes.func.isRequired,\n displayStartDialog: PropTypes.bool.isRequired,\n setDisplayStartDialog: PropTypes.func.isRequired,\n};\n\nexport default StartDialog;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nfunction EndDialog({\n resetGame, displayEndDialog, setDisplayEndDialog, clock,\n}) {\n function reset() {\n resetGame();\n setDisplayEndDialog(false);\n }\n\n return (\n
\n

Game Over

\n
\n

You died

\n

\n {`You scored ${clock} points.`}\n

\n
\n \n
\n );\n}\nEndDialog.propTypes = {\n resetGame: PropTypes.func.isRequired,\n displayEndDialog: PropTypes.bool.isRequired,\n setDisplayEndDialog: PropTypes.func.isRequired,\n clock: PropTypes.number.isRequired,\n};\n\nexport default EndDialog;\n","import React, {\n createContext, useEffect, useRef, useState,\n} from 'react';\nimport PropTypes from 'prop-types';\nimport useDimensions from 'react-use-dimensions';\nimport PlayerSprite from '../sprites/playerSprite';\nimport NpcSprite from '../sprites/npcSprite';\nimport RectObject, { SpawnRing, collisionCheck } from './engine';\nimport CircleSprite, { breathe } from '../sprites/circleSprite';\nimport StartDialog from '../forms/startDialog';\nimport EndDialog from '../forms/endDialog';\n\nconst SPRITE_TYPES = { FOOD: 1, ENEMY: 2 };\nexport const SpriteTypesContext = createContext(SPRITE_TYPES);\n\n// TODO pause button\n\nfunction GameCanvas({ clock, toggleClockActive, resetClock }) {\n // ----------------------------- GAME CONFIG -------------------------\n const DIFFICULTY_INCREASE_INTERVAL = 2000;\n const DIFFICULTY_INCREASE_FACTOR = 2; // must be a whole number\n\n // ----------------------------- CANVAS CONFIG -------------------------\n const [canvasRef, {\n // eslint-disable-next-line no-unused-vars\n x: _x, y: _y, width: canvasWidth, height: canvasHeight,\n }] = useDimensions();\n const CANVAS_STARTING_SIZE = 500;\n const MARGIN_SIZE = 5;\n const [canvasBounds, setCanvasBounds] = useState(RectObject(\n MARGIN_SIZE, MARGIN_SIZE, CANVAS_STARTING_SIZE - MARGIN_SIZE * 2,\n CANVAS_STARTING_SIZE - MARGIN_SIZE * 2,\n ));\n const [displayStartDialog, setDisplayStartDialog] = useState(true);\n const [displayEndDialog, setDisplayEndDialog] = useState(false);\n\n // ----------------------------- SPRITE CONFIG -------------------------\n const BREATHING_ANIMATION_SPEED = 4;\n\n // PLAYER -------------------------\n const PLAYER_SPRITE_STARTING_RADIUS = 12;\n const PLAYER_SPRITE_MAX_RADIUS = 90;\n const PLAYER_SPRITE_GROWTH_RATE = 3;\n const PLAYER_STARTING_MOVEMENT_SPEED = 1;\n const [playerMovementSpeed, setPlayerMovementSpeed] = useState(PLAYER_STARTING_MOVEMENT_SPEED);\n const [playerSpriteData, setPlayerSpriteData] = useState({\n ...CircleSprite(CANVAS_STARTING_SIZE / 2,\n CANVAS_STARTING_SIZE / 2, PLAYER_SPRITE_STARTING_RADIUS),\n });\n const [playerMovementStatus, setPlayerMovementStatus] = useState({\n UP: false,\n DOWN: false,\n LEFT: false,\n RIGHT: false,\n });\n\n // NPCS -------------------------\n const NPC_BASE_RADIUS = 6;\n const NPC_RADIUS_RANDOMNESS = 1.5;\n const NPC_VECTOR_RANDOMNESS = 0.5;\n const STARTING_NPC_MOVEMENT_SPEED = 0.001;\n const STARTING_SPRITE_TYPE_SKEW = 0.1;\n const STARTING_NPC_SPAWN_INTERVAL = 500;\n const [npcSprites, setNpcSprites] = useState([]);\n const [spawnRing, setSpawnRing] = useState(SpawnRing(CANVAS_STARTING_SIZE / 2,\n CANVAS_STARTING_SIZE / 2, CANVAS_STARTING_SIZE / 1.5));\n const [spriteDeleteBox, setSpriteDeleteBox] = useState(RectObject(-400, -400,\n CANVAS_STARTING_SIZE + 800, CANVAS_STARTING_SIZE + 800));\n const [npcSpawnInterval, setNpcSpawnInterval] = useState(STARTING_NPC_SPAWN_INTERVAL);\n const [npcMovementSpeed, setNpcMovementSpeed] = useState(STARTING_NPC_MOVEMENT_SPEED);\n const [spriteTypeSkew, setSpriteTypeSkew] = useState(STARTING_SPRITE_TYPE_SKEW);\n\n // ----------------------------- GAME LOGIC -------------------------\n function reset() {\n setNpcSprites([]);\n setPlayerSpriteData({\n ...CircleSprite(canvasWidth / 2,\n canvasHeight / 2, PLAYER_SPRITE_STARTING_RADIUS),\n });\n setNpcMovementSpeed(STARTING_NPC_MOVEMENT_SPEED);\n setSpriteTypeSkew(STARTING_SPRITE_TYPE_SKEW);\n setNpcSpawnInterval(STARTING_NPC_SPAWN_INTERVAL);\n setPlayerMovementSpeed(PLAYER_STARTING_MOVEMENT_SPEED);\n resetClock();\n }\n\n // --------------------------- KEYBOARD HANDLERS ----------------------\n const keyboardRef = useRef();\n function keyDown(event) {\n const newMovementStatus = { ...playerMovementStatus };\n switch (event.key) {\n case 'ArrowUp':\n newMovementStatus.UP = true;\n break;\n case 'ArrowDown':\n newMovementStatus.DOWN = true;\n break;\n case 'ArrowLeft':\n newMovementStatus.LEFT = true;\n break;\n case 'ArrowRight':\n newMovementStatus.RIGHT = true;\n break;\n default:\n break;\n }\n setPlayerMovementStatus(newMovementStatus);\n }\n\n function keyUp(event) {\n const newMovementStatus = { ...playerMovementStatus };\n switch (event.key) {\n case 'ArrowUp':\n newMovementStatus.UP = false;\n break;\n case 'ArrowDown':\n newMovementStatus.DOWN = false;\n break;\n case 'ArrowLeft':\n newMovementStatus.LEFT = false;\n break;\n case 'ArrowRight':\n newMovementStatus.RIGHT = false;\n break;\n default:\n break;\n }\n setPlayerMovementStatus(newMovementStatus);\n }\n\n function keyPress(event) {\n if (event.key === 'Enter') {\n if (displayStartDialog) {\n toggleClockActive();\n setDisplayStartDialog(false);\n }\n if (displayEndDialog) {\n reset();\n setDisplayEndDialog(false);\n }\n }\n }\n\n // ----------------------------- VISUAL LOGIC -------------------------\n // sync canvas dimensions - triggered by resizing window\n useEffect(() => {\n setCanvasBounds(RectObject(MARGIN_SIZE, MARGIN_SIZE, canvasWidth - MARGIN_SIZE * 2,\n canvasHeight - MARGIN_SIZE * 2));\n setSpawnRing(SpawnRing(canvasWidth / 2,\n canvasHeight / 2, canvasWidth / 1.5));\n setSpriteDeleteBox(RectObject(-400, -400,\n canvasWidth + 800, canvasHeight + 800));\n\n // reset player to middle when resizing window\n const newPlayerData = { ...playerSpriteData };\n const newPlayerBounds = { ...playerSpriteData.bounds };\n newPlayerBounds.xPos = canvasWidth / 2;\n newPlayerBounds.yPos = canvasHeight / 2;\n newPlayerData.bounds = newPlayerBounds;\n setPlayerSpriteData(newPlayerData);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [canvasWidth, canvasHeight]);\n\n // clock-triggered block\n useEffect(() => {\n // lock keyboard focus to player\n keyboardRef.current.focus();\n\n // spawn new sprites\n if (clock % npcSpawnInterval === 0) {\n // use default canvas width if we can't yet measure the DOM\n const npcCanvasWidth = (canvasWidth === undefined) ? CANVAS_STARTING_SIZE : canvasWidth;\n const npcCanvasHeight = (canvasHeight === undefined) ? CANVAS_STARTING_SIZE : canvasHeight;\n\n const coordinates = spawnRing.getRandomSpawnLocation();\n const npcSizeFactor = (Math.random() + 0.6) * NPC_RADIUS_RANDOMNESS;\n const npcVectorFactor = (Math.random() - 0.5) * (npcCanvasWidth * NPC_VECTOR_RANDOMNESS);\n const npcPathX = coordinates.xSpawn - npcCanvasWidth / 2 + npcVectorFactor;\n const npcPathY = coordinates.ySpawn - npcCanvasHeight / 2 + npcVectorFactor;\n const spriteType = Math.round(Math.random() + spriteTypeSkew);\n npcSprites.push(\n {\n ...CircleSprite(coordinates.xSpawn, coordinates.ySpawn,\n NPC_BASE_RADIUS * npcSizeFactor),\n spriteMovementVector: {\n x: -(npcPathX * npcMovementSpeed),\n y: -(npcPathY * npcMovementSpeed),\n },\n type: spriteType,\n key: clock,\n },\n );\n }\n\n // move player sprite\n const newPlayerBounds = { ...playerSpriteData.bounds };\n let changed = false;\n if (playerMovementStatus.UP) {\n newPlayerBounds.yPos -= playerMovementSpeed;\n changed = true;\n }\n if (playerMovementStatus.DOWN) {\n newPlayerBounds.yPos += playerMovementSpeed;\n changed = true;\n }\n if (playerMovementStatus.LEFT) {\n newPlayerBounds.xPos -= playerMovementSpeed;\n changed = true;\n }\n if (playerMovementStatus.RIGHT) {\n newPlayerBounds.xPos += playerMovementSpeed;\n changed = true;\n }\n if (changed && canvasBounds.childCircleBoundsCheck(newPlayerBounds)) {\n const newPlayerData = { ...playerSpriteData };\n newPlayerData.bounds = newPlayerBounds;\n setPlayerSpriteData(newPlayerData);\n }\n\n // breathing effect\n if (clock % BREATHING_ANIMATION_SPEED === 0) {\n setPlayerSpriteData(breathe(playerSpriteData));\n }\n\n // move other sprites\n for (let i = 0; i < npcSprites.length; i += 1) {\n let currSprite = npcSprites[i];\n // TODO make enemies home in on player (psuedo-AI)\n currSprite.bounds.xPos += currSprite.spriteMovementVector.x;\n currSprite.bounds.yPos += currSprite.spriteMovementVector.y;\n\n // breathing effect\n if (clock % BREATHING_ANIMATION_SPEED === 0) {\n currSprite = breathe(currSprite);\n }\n }\n\n for (let i = 0; i < npcSprites.length; i += 1) {\n const currSprite = npcSprites[i];\n\n // delete sprites that are outside deletion bounds\n if (!spriteDeleteBox.childCircleBoundsCheck(currSprite.bounds)) {\n npcSprites.splice(i, 1);\n i -= 1;\n\n // check for collisions\n // TODO - make this work based on animation size\n } else if (collisionCheck(playerSpriteData.bounds, currSprite.bounds)) {\n const newPlayerSpriteData = { ...playerSpriteData };\n if (currSprite.type === SPRITE_TYPES.FOOD\n && playerSpriteData.bounds.radius <= PLAYER_SPRITE_MAX_RADIUS) {\n newPlayerSpriteData.bounds.radius += PLAYER_SPRITE_GROWTH_RATE;\n } else {\n newPlayerSpriteData.bounds.radius -= PLAYER_SPRITE_GROWTH_RATE;\n }\n newPlayerSpriteData.animationState.radius = newPlayerSpriteData.bounds.radius - 1;\n setPlayerSpriteData(newPlayerSpriteData);\n npcSprites.splice(i, 1);\n i -= 1;\n // game over when sprite size is zero\n if (newPlayerSpriteData.bounds.radius <= 0) {\n toggleClockActive();\n setDisplayEndDialog(true);\n }\n }\n }\n\n // at set intervals, increase game difficulty.\n if (clock !== 0 && clock % DIFFICULTY_INCREASE_INTERVAL === 0) {\n const incrementFactor = 1 + DIFFICULTY_INCREASE_FACTOR / 10;\n const decrementFactor = 1 - DIFFICULTY_INCREASE_FACTOR / 6;\n\n setNpcSpawnInterval(Math.round(npcSpawnInterval * decrementFactor));\n setNpcMovementSpeed(npcMovementSpeed * incrementFactor);\n setPlayerMovementSpeed(playerMovementSpeed * incrementFactor);\n setSpriteTypeSkew(spriteTypeSkew * incrementFactor);\n }\n // TODO remove all react-hooks/exhaustive-deps errors hidden by these rules\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [clock]);\n\n // ----------------------------- RENDERING -------------------------\n return (\n
\n \n \n
\n Score - \n {clock}\n
\n \n \n \n {npcSprites.map((spriteParams) => (\n \n ))}\n \n \n
\n );\n}\n\nGameCanvas.propTypes = {\n clock: PropTypes.number.isRequired,\n toggleClockActive: PropTypes.func.isRequired,\n resetClock: PropTypes.func.isRequired,\n};\n\nexport default GameCanvas;\n","import React, {\n createContext, useCallback, useEffect, useState,\n} from 'react';\nimport GameCanvas from './gameEngine/gameCanvas';\nimport './styles/main.css';\n\nconst COLOUR_SCHEME = {\n 'Mid Gray': '#364545',\n 'Dark Gray': '#101919',\n 'Deep Red': '#68121e',\n White: '#f5f5f5',\n 'Bright Blue': '#44c5c5',\n};\n\nexport const ColourSchemeContext = createContext(COLOUR_SCHEME);\n\nfunction Coalesce() {\n const UPDATE_INTERVAL = 4.17; // this is 240Hz in ms.\n\n const [clock, setClock] = useState(0);\n const [clockActive, setClockActive] = useState(false);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const toggleClockActive = useCallback(() => {\n setClockActive(!clockActive);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const resetClock = useCallback(() => {\n setClock(0);\n setClockActive(true);\n });\n\n useEffect(() => {\n let timer;\n if (clockActive) {\n timer = setInterval(() => {\n setClock(clock + 1);\n }, UPDATE_INTERVAL);\n }\n return () => {\n clearInterval(timer);\n };\n });\n\n // TODO add about section\n\n return (\n \n
\n
\n

Coalesce

\n

Pure Reactive Play.

\n
\n \n
\n

Made in 2020 by Oliver Reynolds.

\n
\n
\n
\n );\n}\n\nexport default Coalesce;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' },\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready\n .then(registration => {\n registration.unregister();\n })\n .catch(error => {\n console.error(error.message);\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './styles/index.css';\nimport Coalesce from './coalesce';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root'),\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}