diff --git a/server.js b/server.js index 2b9187d..bd725d2 100644 --- a/server.js +++ b/server.js @@ -169,7 +169,10 @@ io.on('connection', function(socket) { if (game) { let player = game.players.find((player) => player.id === id); game.players.find((player) => player.id === id).dead = true; - game.message = player.name + ", a " + player.card.role + ", has been killed!"; + game.killedPlayer = player.name; + game.lastKilled = player.id; + game.killedRole = player.card.role; + game.message = player.name + ", a " + player.card.role + ", was killed!" const winCheck = teamWon(game); if (winCheck === "wolf") { game.winningTeam = "wolf"; diff --git a/static/game.js b/static/game.js index ed87fbb..fb50f57 100644 --- a/static/game.js +++ b/static/game.js @@ -7,16 +7,26 @@ let clock; let currentGame = null; let cardFlippedOver = false; let cardRendered = false; +let lastKilled = null; // respond to the game state received from the server socket.on('state', function(game) { currentGame = game; - if (game.message) { - document.getElementById("message-box").innerText = game.message; - } buildGameBasedOnState(); }); +window.onblur = function() { // pause animations if the window is not in focus + this.document.querySelector("#overlay").style.animationPlayState = 'paused'; + this.document.querySelector("#killed-role").style.animationPlayState = 'paused'; + this.document.querySelector("#killed-name").style.animationPlayState = 'paused'; +} + +window.onfocus = function() { // play animations when window is focused + this.document.querySelector("#overlay").style.animationPlayState = 'running'; + this.document.querySelector("#killed-role").style.animationPlayState = 'running'; + this.document.querySelector("#killed-name").style.animationPlayState = 'running'; +} + function buildGameBasedOnState() { switch(currentGame.state) { case "lobby": @@ -33,6 +43,41 @@ function buildGameBasedOnState() { } } +function hideAfterExit(e) { + e.target.style.display = 'none' + e.target.classList.remove(e.target.exitClass); +} + +function triggerExitAnimation(e) { + e.target.classList.remove(e.target.entranceClass); + e.target.classList.remove(e.target.exitClass); + e.target.offsetWidth; + e.target.classList.add(e.target.exitClass); + window.setTimeout(()=>{ + e.target.addEventListener('animationend', hideAfterExit, {"capture": true, "once": true}); + },0); +} + +function triggerEntranceAnimation(selector, entranceClass, exitClass, image) { + let transitionEl = document.querySelector(selector); + transitionEl.style.display = 'flex'; + transitionEl.addEventListener('animationend', triggerExitAnimation, {"capture": true, "once": true}); + transitionEl.classList.remove(entranceClass); + transitionEl.entranceClass = entranceClass; + transitionEl.exitClass = exitClass; + transitionEl.offsetWidth; + if (image) { + transitionEl.setAttribute("src", "../assets/images/roles/" + currentGame.killedRole + ".png"); + } + transitionEl.classList.add(entranceClass); +} + +function playKilledAnimation() { + triggerEntranceAnimation('#overlay', 'animate-overlay-in', 'animate-overlay-out', false); + triggerEntranceAnimation('#killed-role', 'animate-role-in', 'animate-role-out', true); + triggerEntranceAnimation('#killed-name', 'animate-name-in', 'animate-name-out', false); +} + function launchGame() { randomlyDealCardsToPlayers(); socket.emit('startGame', { players: currentGame.players , code: currentGame.accessCode}); @@ -79,6 +124,12 @@ function renderEndSplash() { } function renderGame() { + if (currentGame.killedRole && currentGame.lastKilled !== lastKilled) { // a new player has been killed + lastKilled = currentGame.lastKilled; + document.getElementById("killed-name").innerText = currentGame.killedPlayer + " was a " + currentGame.killedRole + "!"; + playKilledAnimation(); + document.getElementById("message-box").innerText = currentGame.message; + } const player = currentGame.players.find((player) => player.id === sessionStorage.getItem("id")); // render the header diff --git a/static/styles.css b/static/styles.css index c347409..daae2c8 100644 --- a/static/styles.css +++ b/static/styles.css @@ -90,6 +90,19 @@ #join-game-container a button { width: 100%; } + + #overlay { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + #killed-name { + padding-top: 0; + margin: 0 0.5em; + font-size: 2em; + } } @media(min-width: 750.01px) { @@ -175,6 +188,17 @@ margin-right: 1em; } + #killed-name { + padding-top: 2em; + font-size: 2.5em; + margin: 0; + } + + #overlay { + justify-content: flex-start; + align-items: center; + } + } @font-face { @@ -699,6 +723,17 @@ label { } } +@keyframes flip-slide { + 0% { + transform: rotateY(0deg); + transform: translateX(-100px); + } + 100% { + transform: rotateY(-90deg); + transform: translateX(0px); + } +} + @keyframes flip-down { 0% { transform: rotateY(0deg); @@ -756,6 +791,12 @@ label { animation-fill-mode: forwards; } +.flip-slide{ + animation: flip-slide 1s; + animation-fill-mode: forwards; +} + + #game-card h2 { font-size: 1.7em; font-family: 'diavlo', sans-serif; @@ -1015,6 +1056,151 @@ label { color: #7d0b0b; } +#overlay { + position: fixed; + user-select: none; + display: none; + flex-direction: column; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0,0,0,0.9); + z-index: 3; +} + +#killed-name { + display: none; + color: white; +} + +#killed-role { + display: none; + margin: 0 auto; +} + +@keyframes slide-fade-in-top { + 0% { + opacity: 0; + transform: translateY(-150px) + + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-fade-out-top { + 0% { + opacity: 1; + transform: translateY(0px) + + } + 100% { + opacity: 0; + transform: translateY(-150px); + } +} + +@keyframes slide-fade-in-bottom { + 0% { + opacity: 0; + transform: translateY(150px) + + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-fade-out-bottom { + 0% { + opacity: 1; + transform: translateY(0); + } + 100% { + opacity: 0; + transform: translateY(150px) + + } +} + +@keyframes fade-overlay-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes fade-overlay-out { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +.animate-overlay-in { + animation: fade-overlay-in 5s; + animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + animation-fill-mode: forwards; + -webkit-animation: fade-overlay-in 5s; + -webkit-animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + -webkit-animation-fill-mode: forwards; +} + +.animate-overlay-out { + animation: fade-overlay-out 1s; + animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + animation-fill-mode: forwards; + -webkit-animation: fade-overlay-out 1s; + -webkit-animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + -webkit-animation-fill-mode: forwards; +} + +.animate-name-in { + animation: slide-fade-in-top 5s; + animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + animation-fill-mode: forwards; + -webkit-animation: slide-fade-in-top 5s; + -webkit-animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + -webkit-animation-fill-mode: forwards; +} + +.animate-name-out { + animation: slide-fade-out-top 1s; + animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + animation-fill-mode: forwards; + -webkit-animation: slide-fade-out-top 1s; + -webkit-animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + -webkit-animation-fill-mode: forwards; +} + +.animate-role-in { + animation: slide-fade-in-bottom 5s; + animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + animation-fill-mode: forwards; + -webkit-animation: slide-fade-in-bottom 5s; + -webkit-animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1); + -webkit-animation-fill-mode: forwards; +} + +.animate-role-out { + animation: slide-fade-out-bottom 1s; + animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + animation-fill-mode: forwards; + -webkit-animation: slide-fade-out-bottom 1s; + -webkit-animation-timing-function: cubic-bezier(0.95, 0.05, 0.795, 0.035); + -webkit-animation-fill-mode: forwards; +} + @keyframes animatetop { from {top: -300px; opacity: 0} to {top: 0; opacity: 1} diff --git a/views/game.html b/views/game.html index 0fe352f..09ac599 100644 --- a/views/game.html +++ b/views/game.html @@ -9,6 +9,10 @@