diff --git a/client/config/defaultCards.js b/client/config/defaultCards.js index b24fd81..9e25dce 100644 --- a/client/config/defaultCards.js +++ b/client/config/defaultCards.js @@ -33,5 +33,10 @@ export const defaultCards = [ role: "Hunter", team: "good", description: "If you are alive with a wolf at the end of the game, you best the wolf, and the village wins.", + }, + { + role: "Mason", + team: "good", + description: "Masons know who the other masons are.", } ]; diff --git a/client/config/globals.js b/client/config/globals.js index 23feb1e..f4293f5 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -15,11 +15,13 @@ export const globals = { KILL_PLAYER: 'killPlayer', REVEAL_PLAYER: 'revealPlayer', TRANSFER_MODERATOR: 'transferModerator', - CHANGE_NAME: 'changeName' + CHANGE_NAME: 'changeName', + END_GAME: 'endGame' }, STATUS: { LOBBY: "lobby", - IN_PROGRESS: "in progress" + IN_PROGRESS: "in progress", + ENDED: "ended" }, ALIGNMENT: { GOOD: "good", @@ -51,6 +53,7 @@ export const globals = { player: ' \uD83C\uDFAE', moderator: ' \uD83D\uDC51', 'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51', - spectator: ' \uD83D\uDC7B' + spectator: ' \uD83D\uDC7B', + killed: '\uD83D\uDC80' } }; diff --git a/client/images/GitHub-Mark-32px.png b/client/images/GitHub-Mark-32px.png new file mode 100644 index 0000000..8b25551 Binary files /dev/null and b/client/images/GitHub-Mark-32px.png differ diff --git a/client/images/GitHub-Mark-Light-32px.png b/client/images/GitHub-Mark-Light-32px.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/client/images/GitHub-Mark-Light-32px.png differ diff --git a/client/images/clock.svg b/client/images/clock.svg index 2aa2e4f..0aa8181 100644 --- a/client/images/clock.svg +++ b/client/images/clock.svg @@ -9,6 +9,6 @@ Layer 1 - + diff --git a/client/images/email.svg b/client/images/email.svg new file mode 100644 index 0000000..b510f10 --- /dev/null +++ b/client/images/email.svg @@ -0,0 +1 @@ + diff --git a/client/images/info.svg b/client/images/info.svg new file mode 100644 index 0000000..c997041 --- /dev/null +++ b/client/images/info.svg @@ -0,0 +1,14 @@ + + + + background + + + + + + + Layer 1 + + + diff --git a/client/images/person.svg b/client/images/person.svg new file mode 100644 index 0000000..17e7fea --- /dev/null +++ b/client/images/person.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/client/images/roles/Villager-2.png b/client/images/roles/Villager-2.png new file mode 100644 index 0000000..c2283a6 Binary files /dev/null and b/client/images/roles/Villager-2.png differ diff --git a/client/images/vanilla_js.png b/client/images/vanilla_js.png new file mode 100644 index 0000000..0b030a3 Binary files /dev/null and b/client/images/vanilla_js.png differ diff --git a/client/images/x.svg b/client/images/x.svg new file mode 100644 index 0000000..5f6dd02 --- /dev/null +++ b/client/images/x.svg @@ -0,0 +1,14 @@ + + + + background + + + + + + + Layer 1 + + + diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index ed237ff..5b59379 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -48,6 +48,29 @@ export class GameStateRenderer { let copyImg = document.createElement("img"); copyImg.setAttribute("src", "../images/copy.svg"); gameLinkContainer.appendChild(copyImg); + + let time = document.getElementById("game-time"); + let playerCount = document.getElementById("game-player-count"); + playerCount.innerText = getGameSize(this.stateBucket.currentGameState.deck) + ' Players' + + if (this.stateBucket.currentGameState.timerParams) { + let timeString = ""; + let hours = this.stateBucket.currentGameState.timerParams.hours; + let minutes = this.stateBucket.currentGameState.timerParams.minutes + if (hours) { + timeString += hours > 1 + ? hours + ' hours ' + : hours + ' hour ' + } + if (minutes) { + timeString += minutes > 1 + ? minutes + ' minutes ' + : minutes + ' minute ' + } + time.innerText = timeString; + } else { + time.innerText = 'untimed'; + } } renderLobbyFooter() { @@ -69,7 +92,16 @@ export class GameStateRenderer { renderModeratorView() { let div = document.createElement("div"); div.innerHTML = templates.END_GAME_PROMPT; - document.body.appendChild(div); + document.getElementById("game-content").appendChild(div); + document.getElementById("end-game-button").addEventListener('click', (e) => { + e.preventDefault(); + if (confirm("End the game?")) { + this.socket.emit( + globals.COMMANDS.END_GAME, + this.stateBucket.currentGameState.accessCode + ); + } + }); let modTransferButton = document.getElementById("mod-transfer-button"); modTransferButton.addEventListener( @@ -78,7 +110,7 @@ export class GameStateRenderer { ModalManager.displayModal( "transfer-mod-modal", "transfer-mod-modal-background", - "close-modal-button" + "close-mod-transfer-modal-button" ) } ) @@ -118,18 +150,7 @@ export class GameStateRenderer { } renderPlayersWithRoleAndAlignmentInfo() { - document.querySelectorAll('.game-player').forEach((el) => { - let pointer = el.dataset.pointer; - if (pointer && this.killPlayerHandlers[pointer]) { - el.removeEventListener('click', this.killPlayerHandlers[pointer]); - delete this.killPlayerHandlers[pointer]; - } - if (pointer && this.revealRoleHandlers[pointer]) { - el.removeEventListener('click', this.revealRoleHandlers[pointer]); - delete this.revealRoleHandlers[pointer]; - } - el.remove(); - }); + removeExistingPlayerElements(this.killPlayerHandlers, this.revealRoleHandlers); this.stateBucket.currentGameState.people.sort((a, b) => { return a.name >= b.name ? 1 : -1; }); @@ -151,7 +172,8 @@ export class GameStateRenderer { this.stateBucket.currentGameState.accessCode, globals.ALIGNMENT.GOOD, this.stateBucket.currentGameState.moderator.userType, - this.socket); + this.socket + ); document.getElementById("players-alive-label").innerText = 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' + this.stateBucket.currentGameState.people.length + ' Alive'; @@ -208,28 +230,27 @@ export class GameStateRenderer { } el.remove(); }); - let modalContent = document.getElementById("transfer-mod-form-content"); - if (modalContent) { - renderPotentialMods( - this.stateBucket.currentGameState, - this.stateBucket.currentGameState.people, - this.transferModHandlers, - modalContent, - this.socket - ); - renderPotentialMods( // spectators can also be made mods. - this.stateBucket.currentGameState, - this.stateBucket.currentGameState.spectators, - this.transferModHandlers, - modalContent, - this.socket - ); - } + renderPotentialMods( + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.people, + this.transferModHandlers, + this.socket + ); + renderPotentialMods( // spectators can also be made mods. + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.spectators, + this.transferModHandlers, + this.socket + ); } + renderEndOfGame() { + this.renderPlayersWithNoRoleInformationUnlessRevealed(); + } } -function renderPotentialMods(gameState, group, transferModHandlers, modalContent, socket) { +function renderPotentialMods(gameState, group, transferModHandlers, socket) { + let modalContent = document.getElementById("transfer-mod-modal-content"); for (let member of group) { if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) { let container = document.createElement("div"); @@ -244,6 +265,7 @@ function renderPotentialMods(gameState, group, transferModHandlers, modalContent container.addEventListener('click', transferModHandlers[member.id]); modalContent.appendChild(container); + console.log('test'); } } } @@ -420,3 +442,18 @@ function insertPlaceholderButton(container, append, type) { container.querySelector('.player-action-buttons').prepend(button); } } + +function removeExistingPlayerElements(killPlayerHandlers, revealRoleHandlers) { + document.querySelectorAll('.game-player').forEach((el) => { + let pointer = el.dataset.pointer; + if (pointer && killPlayerHandlers[pointer]) { + el.removeEventListener('click', killPlayerHandlers[pointer]); + delete killPlayerHandlers[pointer]; + } + if (pointer && revealRoleHandlers[pointer]) { + el.removeEventListener('click', revealRoleHandlers[pointer]); + delete revealRoleHandlers[pointer]; + } + el.remove(); + }); +} diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index cbd73e0..ad34eba 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -22,6 +22,11 @@ export class GameTimerManager { let instance = this; let timer = document.getElementById('game-timer'); timer.classList.remove('paused'); + timer.classList.remove('paused-low'); + timer.classList.remove('low'); + if (totalTime < 60000) { + timer.classList.add('low'); + } timer.innerText = totalTime < 60000 ? returnHumanReadableTime(totalTime, true) : returnHumanReadableTime(totalTime); @@ -29,6 +34,9 @@ export class GameTimerManager { if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds >= 0) { if (e.data.timeRemainingInMilliseconds === 0) { instance.displayExpiredTime(); + } else if (e.data.timeRemainingInMilliseconds < 60000) { + timer.classList.add('low'); + timer.innerText = e.data.displayTime; } else { timer.innerText = e.data.displayTime; } @@ -49,10 +57,14 @@ export class GameTimerManager { timerWorker.postMessage('stop'); let timer = document.getElementById('game-timer'); - timer.innerText = timeRemaining < 60000 - ? returnHumanReadableTime(timeRemaining, true) - : returnHumanReadableTime(timeRemaining); - timer.classList.add('paused'); + if (timeRemaining < 60000) { + timer.innerText = returnHumanReadableTime(timeRemaining, true); + timer.classList.add('paused-low'); + timer.classList.add('low'); + } else { + timer.innerText = returnHumanReadableTime(timeRemaining); + timer.classList.add('paused'); + } } } @@ -65,10 +77,14 @@ export class GameTimerManager { } let timer = document.getElementById('game-timer'); - timer.innerText = time < 60000 - ? returnHumanReadableTime(time, true) - : returnHumanReadableTime(time); - timer.classList.add('paused'); + if (time < 60000) { + timer.innerText = returnHumanReadableTime(time, true); + timer.classList.add('paused-low'); + timer.classList.add('low'); + } else { + timer.innerText = returnHumanReadableTime(time); + timer.classList.add('paused'); + } } displayExpiredTime() { diff --git a/client/modules/ModalManager.js b/client/modules/ModalManager.js index ba95163..e5e4d19 100644 --- a/client/modules/ModalManager.js +++ b/client/modules/ModalManager.js @@ -18,6 +18,8 @@ function displayModal(modalId, backgroundId, closeButtonId) { }); closeBtn.removeEventListener("click", closeModalHandler); closeBtn.addEventListener("click", closeModalHandler); + } else { + throw new Error("One or more of the ids supplied to ModalManager.displayModal is invalid."); } } diff --git a/client/modules/Templates.js b/client/modules/Templates.js index 3699a98..ae6a5b3 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -5,8 +5,19 @@ export const templates = { "" + "" + "" + - "
" + - "
" + + "
" + + "
" + + "clock" + + "
" + + "
" + + "
" + + "person" + + "
" + + "
" + + "
" + + "
" + + "
" + "" + "
" + "
" + @@ -31,6 +42,9 @@ export const templates = { "" + "
" + "
" + + "
" + + "
" + "
" + "" + + "
" + + "
" + "" + - "
" + + "
" + "" + "
" + "
", MODERATOR_GAME_VIEW: "" + "" + "
" + "
" + @@ -77,6 +91,9 @@ export const templates = { "
" + "
" + "
" + "" + + "
" + + "
" + "
" + "
" + "" + @@ -178,5 +195,31 @@ export const templates = { "" + "
" + "" + + "
", + ROLE_INFO_MODAL: + "" + + "", + END_OF_GAME_VIEW: + "

The moderator has ended the game. Roles are revealed.

" + + "
" + + "
" + + "
" + + "
" + + "" + + "" + + "" + + "
" + + "
" + + "
" + + "" + + "
" + "
" + } diff --git a/client/modules/Toast.js b/client/modules/Toast.js index f6366dd..8c83b17 100644 --- a/client/modules/Toast.js +++ b/client/modules/Toast.js @@ -9,7 +9,7 @@ export const toast = (message, type, positionAtTop = true, dispelAutomatically=t function buildAndInsertMessageElement (message, type, positionAtTop, dispelAutomatically, duration) { cancelCurrentToast(); let backgroundColor, border; - const position = positionAtTop ? 'top:2rem;' : 'bottom: 35px;'; + const position = positionAtTop ? 'top:2rem;' : 'bottom: 90px;'; switch (type) { case 'warning': backgroundColor = '#fff5b1'; diff --git a/client/scripts/game.js b/client/scripts/game.js index ddc253a..832598d 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -69,7 +69,7 @@ function prepareGamePage(environment, socket, timerWorker) { } }) } else { - toast("Name must be fewer than 30 characters.", 'error', true, true, 8); + toast("Name must be between 1 and 30 characters.", 'error', true, true, 8); } } } @@ -131,12 +131,19 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer) default: break; } - socket.emit(globals.COMMANDS.GET_TIME_REMAINING, currentGameState.accessCode); break; + case globals.STATUS.ENDED: + let container = document.getElementById("game-state-container") + container.innerHTML = templates.END_OF_GAME_VIEW; + container.classList.add('vertical-flex'); + gameStateRenderer.renderEndOfGame(); + break; default: break; } + + activateRoleInfoButton(stateBucket.currentGameState.deck); } function displayClientInfo(name, userType) { @@ -148,7 +155,7 @@ function displayClientInfo(name, userType) { function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) { if (!socket.hasListeners(globals.EVENTS.PLAYER_JOINED)) { socket.on(globals.EVENTS.PLAYER_JOINED, (player, gameIsFull) => { - toast(player.name + " joined!", "success", false); + toast(player.name + " joined!", "success", false, true, 3); stateBucket.currentGameState.people.push(player); gameStateRenderer.renderLobbyPlayers(); if ( @@ -187,7 +194,7 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo killedPerson.out = true; if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(killedPerson.name + ' killed.', 'success', true, true, 6); - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED) } else { if (killedPerson.id === stateBucket.currentGameState.client.id) { let clientUserType = document.getElementById("client-user-type"); @@ -195,9 +202,9 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80' } gameStateRenderer.updatePlayerCardToKilledState(); - toast('You have been killed!', 'warning', false, true, 6); + toast('You have been killed!', 'warning', true, true, 6); } else { - toast(killedPerson.name + ' was killed!', 'warning', false, true, 6); + toast(killedPerson.name + ' was killed!', 'warning', true, true, 6); } if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -218,12 +225,12 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo revealedPerson.alignment = revealData.alignment; if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(revealedPerson.name + ' revealed.', 'success', true, true, 6); - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED) } else { if (revealedPerson.id === stateBucket.currentGameState.client.id) { - toast('Your role has been revealed!', 'warning', false, true, 6); + toast('Your role has been revealed!', 'warning', true, true, 6); } else { - toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', false, true, 6); + toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 6); } if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -242,19 +249,27 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer); }); } + + if (!socket.hasListeners(globals.COMMANDS.END_GAME)) { + socket.on(globals.COMMANDS.END_GAME, (people) => { + stateBucket.currentGameState.people = people; + stateBucket.currentGameState.status = globals.STATUS.ENDED; + processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer); + }); + } } function displayStartGamePromptForModerators(gameState, socket) { let div = document.createElement("div"); div.innerHTML = templates.START_GAME_PROMPT; - document.body.appendChild(div); - document.getElementById("start-game-button").addEventListener('click', (e) => { + div.querySelector('#start-game-button').addEventListener('click', (e) => { e.preventDefault(); if (confirm("Start the game and deal roles?")) { - socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode, gameState.client.cookie); + socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode); } }); + document.body.appendChild(div); } function runGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { @@ -269,7 +284,7 @@ function runGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { } function validateName(name) { - return typeof name === 'string' && name.length <= 30; + return typeof name === 'string' && name.length > 0 && name.length <= 30; } function propagateNameChange(gameState, name, personId) { @@ -297,7 +312,7 @@ function updateDOMWithNameChange(gameState, gameStateRenderer) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false); break; case globals.USER_TYPES.MODERATOR: - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(); + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(gameState.status === globals.STATUS.ENDED); break; case globals.USER_TYPES.TEMPORARY_MODERATOR: gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -306,3 +321,43 @@ function updateDOMWithNameChange(gameState, gameStateRenderer) { break; } } + +function activateRoleInfoButton(deck) { + deck.sort((a, b) => { + return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + }) + document.getElementById("role-info-button").addEventListener("click", (e) => { + e.preventDefault(); + document.getElementById("prompt").innerHTML = templates.ROLE_INFO_MODAL; + let modalContent = document.getElementById('game-role-info-container'); + for (let card of deck) { + let roleDiv = document.createElement("div"); + let roleNameDiv = document.createElement("div"); + + roleNameDiv.classList.add('role-info-name'); + + let roleName = document.createElement("h5"); + let roleQuantity = document.createElement("h5"); + let roleDescription = document.createElement("p"); + + roleDescription.innerText = card.description; + roleName.innerText = card.role; + roleQuantity.innerText = card.quantity + 'x'; + + if (card.team === globals.ALIGNMENT.GOOD) { + roleName.classList.add(globals.ALIGNMENT.GOOD); + } else { + roleName.classList.add(globals.ALIGNMENT.EVIL); + } + + roleNameDiv .appendChild(roleQuantity); + roleNameDiv .appendChild(roleName); + + roleDiv.appendChild(roleNameDiv); + roleDiv.appendChild(roleDescription); + + modalContent.appendChild(roleDiv); + } + ModalManager.displayModal('role-info-modal', 'role-info-modal-background', 'close-role-info-modal-button'); + }); +} diff --git a/client/scripts/home.js b/client/scripts/home.js index 87e4872..f30de66 100644 --- a/client/scripts/home.js +++ b/client/scripts/home.js @@ -7,17 +7,19 @@ export const home = () => { let userCode = document.getElementById("room-code").value; if (roomCodeIsValid(userCode)) { attemptToJoinGame(userCode); + } else { + toast('Invalid code. Codes are 6 numbers or letters.', 'error', true, true); } } }; function roomCodeIsValid(code) { - return typeof code === "string" && /^[a-z0-9]{6}$/.test(code); + return typeof code === "string" && /^[a-z0-9]{6}$/.test(code.toLowerCase()); } function attemptToJoinGame(code) { XHRUtility.xhr( - '/api/games/availability/' + code, + '/api/games/availability/' + code.toLowerCase().trim(), 'GET', null, null diff --git a/client/styles/GLOBAL.css b/client/styles/GLOBAL.css index b782f18..ebecbfc 100644 --- a/client/styles/GLOBAL.css +++ b/client/styles/GLOBAL.css @@ -54,6 +54,45 @@ h3 { margin: 0.5em 0; } +#footer { + bottom: 0; + width: 100%; + text-align: center; + align-items: center; + display: flex; + justify-content: center; + flex-wrap: wrap; + color: #d7d7d7; + font-size: 14px; + margin-top: 3em; +} +#footer a img { + width: 32px; +} + +#footer a { + color: #f7f7f7; + text-decoration: none; + cursor: pointer; + font-family: 'diavlo', sans-serif; +} + +#footer a:hover { + color: gray; +} + +#footer div { + display: flex; +} + +#footer > div, #footer > a { + margin: 0.5em; +} + +#footer div:nth-child(2) > a, #footer div:nth-child(2) > p { + margin: 0 5px; +} + label { color: #d7d7d7; font-family: 'signika-negative', sans-serif; @@ -68,11 +107,21 @@ input, textarea { color: #f7f7f7; } +a { + text-decoration: none; +} + textarea, input { font-family: 'signika-negative', sans-serif; font-size: 16px; } +button { + display: flex; + align-items: center; + justify-content: center; +} + button, input[type="submit"] { font-family: 'signika-negative', sans-serif !important; padding: 10px; @@ -119,7 +168,12 @@ input { border-radius: 3px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 2px 4px 0 rgb(0 0 0 / 25%); + box-shadow: 0 1px 1px rgba(0,0,0,0.11), + 0 2px 2px rgba(0,0,0,0.11), + 0 4px 4px rgba(0,0,0,0.11), + 0 8px 8px rgba(0,0,0,0.11), + 0 16px 16px rgba(0,0,0,0.11), + 0 32px 32px rgba(0,0,0,0.11); left: 0; right: 0; width: fit-content; @@ -196,7 +250,7 @@ input { width: 100%; max-width: 30em; height: 8em; - margin: 0 auto 0.5em auto; + margin: 0 auto 1em auto; } .animated-placeholder-invisible { @@ -208,10 +262,11 @@ input { display: flex; margin: 0; justify-content: center; + width: 100%; } .placeholder-row .animated-placeholder-short { - margin: 0 0 0.5em 0; + margin: 0 0 1em 0; } .good, .compact-card.good .card-role { @@ -280,12 +335,17 @@ input { @media(max-width: 550px) { h1 { - font-size: 35px; + font-size: 30px; } #step-1 div { font-size: 20px; } + + .info-message { + padding: 5px; + font-size: 16px; + } } @media(min-width: 551px) { @@ -404,6 +464,37 @@ input { transform: rotate(330deg); animation-delay: 0s; } + +.bmc-button span:nth-child(1) { + font-size: 20px; +} + +.bmc-button { + line-height: 35px !important; + height:40px !important; + text-decoration: none !important; + display:inline-flex !important; + align-items: center !important; + color:#ffffff !important; + background-color:#333243 !important; + border-radius: 5px !important; + border: 1px solid transparent !important; + padding: 7px 15px 7px 10px !important; + font-size: 15px !important; + box-shadow: 0px 1px 1px rgba(190, 190, 190, 0.5) !important; + -webkit-box-shadow: 0px 1px 2px 1px rgba(190, 190, 190, 0.5) !important; + font-family: sitewide-sans-serif, sans-serif !important; + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; +} + +.bmc-button:hover, .bmc-button:active, .bmc-button:focus { + -webkit-box-shadow: 0px 1px 2px 2px rgba(190, 190, 190, 0.5) !important; + text-decoration: none !important;box-shadow: 0px 1px 2px 2px rgba(190, 190, 190, 0.5) !important; + opacity: 0.85 !important;color:#ffffff !important; +} + + @keyframes lds-spinner { 0% { opacity: 1; diff --git a/client/styles/create.css b/client/styles/create.css index 5224e5e..74b7fd5 100644 --- a/client/styles/create.css +++ b/client/styles/create.css @@ -10,7 +10,7 @@ border-radius: 3px; user-select: none; max-width: 15em; - min-width: 12em; + min-width: 9em; display: flex; height: max-content; } @@ -42,6 +42,7 @@ .card-role { font-weight: bold; + pointer-events: none; } .compact-card-right p { @@ -106,7 +107,7 @@ #step-4 > div { display: flex; flex-direction: column; - align-items: center; + align-items: flex-start; text-align: left; justify-content: center; margin: 0 auto; @@ -244,6 +245,7 @@ input[type="number"] { #creation-step-tracker { display: flex; justify-content: center; + margin: 0 20px; } #step-forward-button, #step-back-button, #create-game { diff --git a/client/styles/game.css b/client/styles/game.css index 5ac025a..3a0e601 100644 --- a/client/styles/game.css +++ b/client/styles/game.css @@ -14,6 +14,10 @@ margin: 0.5em 0; } +body { + margin-bottom: 100px; +} + #lobby-players { overflow-y: auto; max-height: 30em; @@ -45,7 +49,11 @@ flex-wrap: wrap; display: flex; width: 95%; - margin: 0 auto 115px auto; + margin: 1em auto 0 auto; +} + +#game-state-container h2 { + margin: 0.5em 0; } #lobby-header { @@ -61,6 +69,28 @@ h1 { margin: 1em; } +#game-content .placeholder-row:nth-child(1) { + margin-top: 2em; +} + +#footer.game-page-footer { + margin-top: 1em; + margin-bottom: 1em; +} + +#footer.game-page-footer a { + margin: 0 5px; +} + +#end-of-game-header { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +#end-of-game-header button { + margin: 0.5em; +} .potential-moderator { display: flex; color: #d7d7d7; @@ -96,17 +126,83 @@ h1 { margin-top: 10px; padding: 7px; border-radius: 3px; - background-color: #722c2c; + background-color: #121314; + border: 2px solid #333243; color: whitesmoke; align-items: center; display: flex; transition: background-color 0.2s; } -#game-player-count { - color: whitesmoke; +.role-info-name { + display: flex; +} + +.role-info-name h5:nth-child(1) { + margin-right: 10px; + color: #21ba45; +} + +#role-info-button { + margin-top: 1em; +} + +#role-info-button img { + height: 25px; + margin-left: 10px; +} + +#game-role-info-container { + display: flex; + flex-direction: column; + align-items: flex-start; + margin: 1em 0; + overflow-y: auto; + max-height: 35em; +} + +#game-role-info-container > div { + width: 95%; +} + +#transfer-mod-modal-content { + overflow-y: auto; + max-height: 35em; + width: 100%; +} + +.potential-moderator { + width: 90%; +} + +#role-info-modal #modal-button-container { + margin-top: 1em; +} + +#game-role-info-container .role-info-name { + padding: 5px; + border-radius: 3px; + font-size: 20px; + font-family: signika-negative, sans-serif; margin: 0.5em 0; - font-size: 25px; + background-color: #15191c; +} + +#role-info-modal h2 { + color: #d7d7d7; + font-family: diavlo, sans-serif; + font-weight: normal; +} + +#game-role-info-container p, #game-role-info-container h5 { + text-align: left; + font-weight: normal; +} + +#game-role-info-container p { + color: #d7d7d7; + font-size: 14px; + font-family: signika-negative, sans-serif; } #game-role { @@ -183,6 +279,12 @@ h1 { border: 1px solid #747474; } +#game-timer.low { + color: #e71c0d; + border: 1px solid #ca1b17; + background-color: #361a1a; +} + #role-name { position: absolute; top: 6%; @@ -215,7 +317,7 @@ h1 { bottom: 8%; left: 50%; transform: translate(-50%, 0); - font-size: 16px; + font-size: 15px; width: 78%; max-height: 6em; } @@ -279,7 +381,7 @@ label[for='moderator'] { border-radius: 3px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 2px 4px 0 rgb(0 0 0 / 25%); + box-shadow: 0 -2px 6px 0 rgb(0 0 0 / 45%); left: 0; right: 0; bottom: 0; @@ -355,11 +457,15 @@ label[for='moderator'] { animation: pulse 0.75s linear infinite alternate; } +.paused-low { + animation: pulse-low 0.75s linear infinite alternate; +} + #game-header { display: flex; flex-wrap: wrap; - align-items: center; flex-direction: column; + align-items: flex-start; } .timer-container-moderator { @@ -380,12 +486,7 @@ label[for='moderator'] { justify-content: space-between; margin: 0.5em 0; position: relative; - box-shadow: 0 1px 1px rgba(0,0,0,0.11), - 0 2px 2px rgba(0,0,0,0.11), - 0 4px 4px rgba(0,0,0,0.11), - 0 8px 8px rgba(0,0,0,0.11), - 0 16px 16px rgba(0,0,0,0.11), - 0 32px 32px rgba(0,0,0,0.11); + box-shadow: 2px 3px 6px rgb(0 0 0 / 50%); } .game-player-name { @@ -488,14 +589,30 @@ label[for='moderator'] { #game-player-list { overflow-y: auto; overflow-x: hidden; - padding: 0 10px; + padding: 0; max-height: 37em; } #game-player-list > div { padding: 2px 10px; border-radius: 3px; - margin-bottom: 1em; + margin-bottom: 0.5em; +} + +#game-parameters { + color: #d7d7d7; + font-size: 25px; + margin: 0.5em; +} + +#game-parameters > div { + display: flex; + align-items: center; +} + +#game-parameters img { + height: 20px; + margin-right: 10px; } #players-alive-label { @@ -512,6 +629,10 @@ label[for='moderator'] { justify-content: center; } +#transfer-mod-modal #modal-button-container { + justify-content: center; +} + #change-name-modal-background { cursor: default; } @@ -520,6 +641,88 @@ label[for='moderator'] { background-color: #333243; padding: 10px 10px 0 10px; border-radius: 3px; + min-height: 25em; + min-width: 15em; + max-width: 30em; +} + +#transfer-mod-modal-content { + margin-bottom: 2em; +} + +#game-state-container.vertical-flex { + flex-direction: column; + align-items: center; +} + +@media(max-width: 500px) { + #client-name { + font-size: 25px; + } + + #client-user-type, #game-parameters { + font-size: 20px; + } + + #game-state-container { + margin: 0 auto 0 auto; + } + + button { + font-size: 16px; + padding: 5px; + } + + #play-pause img { + width: 45px; + } + + .make-mod-button { + font-size: 16px; + padding: 5px; + } + + .game-player-name { + font-size: 16px; + } + + #game-timer { + font-size: 30px; + } + + #players-alive-label { + font-size: 20px; + } + + #start-game-prompt, #end-game-prompt { + height: 65px; + } + + #start-game-button, #end-game-button { + font-size: 20px; + padding: 5px; + } + + #game-role, #game-role-back { + height: 20em; + max-width: 15em; + } + + #client-container { + margin: 0; + } + + #game-role-back p { + font-size: 16px; + } + + #game-role-back h4 { + font-size: 20px; + } + + h2 { + font-size: 18px; + } } @keyframes pulse { @@ -530,6 +733,14 @@ label[for='moderator'] { } } +@keyframes pulse-low { + from { + color: rgba(231, 28, 13 , 0.1); + } to { + color: rgba(231, 28, 13, 1); + } +} + @keyframes fade-in-slide-up { 0% { opacity: 0; diff --git a/client/styles/home.css b/client/styles/home.css index f84aee8..e8092c0 100644 --- a/client/styles/home.css +++ b/client/styles/home.css @@ -5,7 +5,6 @@ html { body { align-items: center; justify-content: center; - height: 100%; } button { @@ -31,7 +30,18 @@ a button { #join-button { min-width: 6em; max-height: 3em; - color: #21ba45; + background-color: #1c8a36; + color: whitesmoke; + font-size: 16px; +} + +#join-button:hover { + background-color: #326243; + border: 2px solid #1c8a36; +} + +#join-form div:nth-child(1) { + margin-right: 1em; } h3 { @@ -42,11 +52,11 @@ h3 { font-family: 'diavlo', sans-serif; } -img { +img[src='../images/logo_cropped.gif'] { max-width: 400px; width: 63vw; min-width: 250px; - margin: 1em 0; + margin: 3em 0 1em 0; } form > div { diff --git a/client/styles/modal.css b/client/styles/modal.css index aa600dd..a2bfa24 100644 --- a/client/styles/modal.css +++ b/client/styles/modal.css @@ -2,7 +2,7 @@ border-radius: 2px; text-align: center; position: fixed; - width: 100%; + width: 85%; z-index: 100; top: 50%; left: 50%; @@ -10,7 +10,7 @@ background-color: #23282b; align-items: center; justify-content: center; - max-width: 19em; + max-width: 25em; max-height: 80%; height: fit-content; font-family: sans-serif; diff --git a/client/views/game.html b/client/views/game.html index f4cea66..150cee5 100644 --- a/client/views/game.html +++ b/client/views/game.html @@ -43,7 +43,32 @@ Home -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+