diff --git a/client/src/images/tutorial/player-view.gif b/client/src/images/tutorial/player-view.gif
new file mode 100644
index 0000000..fafa5ee
Binary files /dev/null and b/client/src/images/tutorial/player-view.gif differ
diff --git a/client/src/modules/front_end_components/Confirmation.js b/client/src/modules/front_end_components/Confirmation.js
new file mode 100644
index 0000000..17cb9da
--- /dev/null
+++ b/client/src/modules/front_end_components/Confirmation.js
@@ -0,0 +1,63 @@
+import { toast } from './Toast.js';
+
+export const Confirmation = (message, onYes) => {
+ document.querySelector('#confirmation')?.remove();
+ document.querySelector('#confirmation-background')?.remove();
+
+ let confirmation = document.createElement('div');
+ confirmation.setAttribute('id', 'confirmation');
+ confirmation.innerHTML =
+ `
diff --git a/client/src/modules/front_end_components/Navbar.js b/client/src/modules/front_end_components/Navbar.js
index 731cc18..cd14850 100644
--- a/client/src/modules/front_end_components/Navbar.js
+++ b/client/src/modules/front_end_components/Navbar.js
@@ -43,7 +43,7 @@ function getNavbarLinks (page = null, device) {
'
Home ' +
'
Create ' +
'
How to Use ' +
- '
Contact ' +
+ '
Feedback ' +
'
Github ' +
'
Support the App ';
}
diff --git a/client/src/modules/game_creation/DeckStateManager.js b/client/src/modules/game_creation/DeckStateManager.js
index da2a9d9..c48df14 100644
--- a/client/src/modules/game_creation/DeckStateManager.js
+++ b/client/src/modules/game_creation/DeckStateManager.js
@@ -134,6 +134,7 @@ export class DeckStateManager {
document.getElementById('deck-list').appendChild(placeholder);
};
+ // TODO: refactor
updateDeckStatus = () => {
document.getElementById('deck-count').innerText = this.getDeckSize() + ' Players';
if (this.deck.length > 0) {
@@ -186,10 +187,14 @@ export class DeckStateManager {
const infoHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
+ const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
+ nameEl.classList.remove(globals.ALIGNMENT.GOOD);
+ nameEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
- document.getElementById('custom-role-info-modal-name').innerText = sortedDeck[i].role;
+ nameEl.innerText = sortedDeck[i].role;
+ nameEl.classList.add(sortedDeck[i].team);
alignmentEl.classList.add(sortedDeck[i].team);
document.getElementById('custom-role-info-modal-description').innerText = sortedDeck[i].description;
alignmentEl.innerText = sortedDeck[i].team;
diff --git a/client/src/modules/game_creation/RoleBox.js b/client/src/modules/game_creation/RoleBox.js
index b88e8af..019e129 100644
--- a/client/src/modules/game_creation/RoleBox.js
+++ b/client/src/modules/game_creation/RoleBox.js
@@ -3,6 +3,7 @@ import { globals } from '../../config/globals.js';
import { defaultRoles } from '../../config/defaultRoles.js';
import { toast } from '../front_end_components/Toast.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
+import { Confirmation } from '../front_end_components/Confirmation.js';
export class RoleBox {
constructor (container, deckManager) {
@@ -218,13 +219,13 @@ export class RoleBox {
if (remove) {
const removeHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
- if (confirm("Delete the role '" + name + "'?")) {
+ Confirmation("Delete the role '" + name + "'?", () => {
e.preventDefault();
this.removeFromCustomRoles(name);
if (this.category === 'custom') {
this.displayCustomRoles(document.getElementById('role-select'));
}
- }
+ });
}
};
role.querySelector('.role-remove').addEventListener('click', removeHandler);
@@ -234,8 +235,11 @@ export class RoleBox {
const infoHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
+ const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
+ nameEl.classList.remove(globals.ALIGNMENT.GOOD);
+ nameEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
let role;
if (isCustom) {
@@ -243,7 +247,8 @@ export class RoleBox {
} else {
role = this.getDefaultRole(name);
}
- document.getElementById('custom-role-info-modal-name').innerText = name;
+ nameEl.innerText = name;
+ nameEl.classList.add(role.team);
alignmentEl.classList.add(role.team);
document.getElementById('custom-role-info-modal-description').innerText = role.description;
alignmentEl.innerText = role.team;
diff --git a/client/src/modules/game_state/GameStateRenderer.js b/client/src/modules/game_state/GameStateRenderer.js
index 49d28d6..97935e0 100644
--- a/client/src/modules/game_state/GameStateRenderer.js
+++ b/client/src/modules/game_state/GameStateRenderer.js
@@ -6,6 +6,7 @@ import { XHRUtility } from '../utility/XHRUtility.js';
import { UserUtility } from '../utility/UserUtility.js';
// QRCode module via: https://github.com/soldair/node-qrcode
import { QRCode } from '../third_party/qrcode.js';
+import { Confirmation } from '../front_end_components/Confirmation.js';
export class GameStateRenderer {
constructor (stateBucket, socket) {
@@ -16,9 +17,9 @@ export class GameStateRenderer {
this.transferModHandlers = {};
this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
e.preventDefault();
- if (confirm('Start the game and deal roles?')) {
+ Confirmation('Start game and deal roles?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.START_GAME, stateBucket.currentGameState.accessCode);
- }
+ });
};
this.restartGameHandler = (e) => {
e.preventDefault();
@@ -356,9 +357,9 @@ export class GameStateRenderer {
}
} else if (!player.out && moderatorType) {
killPlayerHandlers[player.id] = () => {
- if (confirm('KILL ' + player.name + '?')) {
+ Confirmation('Kill \'' + player.name + '\'?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.KILL_PLAYER, accessCode, { personId: player.id });
- }
+ });
};
playerEl.querySelector('.kill-player-button').addEventListener('click', killPlayerHandlers[player.id]);
}
@@ -371,9 +372,9 @@ export class GameStateRenderer {
}
} else if (!player.revealed && moderatorType) {
revealRoleHandlers[player.id] = () => {
- if (confirm('REVEAL ' + player.name + '?')) {
+ Confirmation('Reveal \'' + player.name + '\'?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.REVEAL_PLAYER, accessCode, { personId: player.id });
- }
+ });
};
playerEl.querySelector('.reveal-role-button').addEventListener('click', revealRoleHandlers[player.id]);
}
@@ -398,13 +399,19 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
container.innerText = member.name;
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
- if (confirm('Transfer moderator powers to ' + member.name + '?')) {
+ ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
+ Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
const transferPrompt = document.getElementById('transfer-mod-prompt');
if (transferPrompt !== null) {
transferPrompt.innerHTML = '';
}
- socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.TRANSFER_MODERATOR, gameState.accessCode, { personId: member.id });
- }
+ socket.emit(
+ globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
+ globals.EVENT_IDS.TRANSFER_MODERATOR,
+ gameState.accessCode,
+ { personId: member.id }
+ );
+ });
}
};
@@ -532,13 +539,13 @@ function createEndGamePromptComponent (socket, stateBucket) {
div.innerHTML = HTMLFragments.END_GAME_PROMPT;
div.querySelector('#end-game-button').addEventListener('click', (e) => {
e.preventDefault();
- if (confirm('End the game?')) {
+ Confirmation('End the game?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.END_GAME,
stateBucket.currentGameState.accessCode
);
- }
+ });
});
document.getElementById('game-content').appendChild(div);
}
diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css
index 2eb8a8f..3284e8f 100644
--- a/client/src/styles/GLOBAL.css
+++ b/client/src/styles/GLOBAL.css
@@ -307,7 +307,7 @@ button {
}
#how-to-use-container h1 {
- color: #4b6bfa;
+ color: #d7d7d7;
font-family: signika-negative, sans-serif;
background-color: #1e1b26;
width: fit-content;
@@ -359,7 +359,7 @@ input {
color: #d7d7d7;
display: flex;
flex-direction: column;
- margin: 1em auto 0 auto;
+ margin: 1em auto 2em auto;
width: 90%;
max-width: 64em;
line-height: 1.5;
@@ -370,10 +370,39 @@ input {
font-weight: bold;
}
+#tutorial-links {text-align: left;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+}
+
+#tutorial-links li {
+ font-size: 25px;
+ color: #768df0;
+ text-decoration: underline;
+}
+
+#tutorial-links li a {
+ color: #768df0;
+ text-decoration: underline;
+ cursor: pointer;
+ font-family: 'signika-negative', sans-serif;
+ font-size: 18px;
+ width: fit-content;
+}
+
+#tutorial-links li a:hover {
+ color: gray;
+}
+
.tutorial-image-small {
width: 30em !important;
}
+.tutorial-image-small-portrait {
+ width: 20em !important;
+}
+
#desktop-links > a:nth-child(1), #mobile-links a:nth-child(1) {
margin: 0 0.5em;
width: 50px;
diff --git a/client/src/styles/confirmation.css b/client/src/styles/confirmation.css
new file mode 100644
index 0000000..512d035
--- /dev/null
+++ b/client/src/styles/confirmation.css
@@ -0,0 +1,48 @@
+#confirmation {
+ border-radius: 2px;
+ text-align: center;
+ position: fixed;
+ border: 2px solid #333243;
+ width: 85%;
+ z-index: 100001;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #191920;
+ align-items: center;
+ justify-content: center;
+ max-width: 25em;
+ font-family: 'signika-negative', sans-serif;
+ flex-direction: column;
+ padding: 1em;
+}
+
+#confirmation-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: calc(100% + 100px);
+ background-color: rgba(0, 0, 0, 0.80);
+ z-index: 100000;
+ cursor: pointer;
+}
+
+#confirmation-message {
+ font-size: 20px;
+ color: #e7e7e7;
+ margin: 1em 0 2em 0;
+}
+
+.confirmation-buttons button {
+ min-width: 5em;
+}
+
+.confirmation-buttons {
+ display: flex;
+ justify-content: space-between;
+}
+
+#confirmation-cancel-button {
+ background-color: #762323 !important;
+}
diff --git a/client/src/styles/game.css b/client/src/styles/game.css
index 4bcdd20..5d61825 100644
--- a/client/src/styles/game.css
+++ b/client/src/styles/game.css
@@ -843,7 +843,7 @@ canvas {
}
#game-role-back h4 {
- font-size: 24px;
+ font-size: 22px;
}
h2 {
diff --git a/client/src/styles/modal.css b/client/src/styles/modal.css
index 0ed0ed7..1223df0 100644
--- a/client/src/styles/modal.css
+++ b/client/src/styles/modal.css
@@ -15,6 +15,7 @@
flex-direction: column;
padding: 1em;
display: none;
+ border: 2px solid #333243;
}
.modal-background {
@@ -55,14 +56,20 @@
color: #d7d7d7;
text-align: left;
font-family: signika-negative, sans-serif;
+ align-items: flex-start;
+}
+
+#custom-role-info-modal h3 {
+ margin: 0 0 0.5em 0;
}
#custom-role-info-modal-description {
- margin: 2em 0;
+ border-radius: 3px;
+ background-color: black;
max-height: 10em;
overflow: auto;
- padding-top: 10px;
- border-top: 1px solid whitesmoke
+ padding: 5px;
+ margin-bottom: 2em;
}
#custom-role-info-modal-name {
@@ -70,9 +77,16 @@
font-size: 23px;
}
+#custom-role-info-modal label {
+ margin: 5px 0;
+}
+
#custom-role-info-modal-alignment {
- font-size: 20px;
+ border-radius: 3px;
+ background-color: black;
+ font-size: 18px;
font-weight: bold;
+ padding: 5px;
}
#change-name-modal, #transfer-mod-modal, #role-info-modal {
diff --git a/client/src/view_templates/CreateTemplate.js b/client/src/view_templates/CreateTemplate.js
index 87a6bc2..9950510 100644
--- a/client/src/view_templates/CreateTemplate.js
+++ b/client/src/view_templates/CreateTemplate.js
@@ -35,7 +35,9 @@ const template =
+
alignment:
+
description:
Close
diff --git a/client/src/views/create.html b/client/src/views/create.html
index 986c9bc..c5d701a 100644
--- a/client/src/views/create.html
+++ b/client/src/views/create.html
@@ -16,6 +16,7 @@
+
diff --git a/client/src/views/game.html b/client/src/views/game.html
index 935d264..6f48ab3 100644
--- a/client/src/views/game.html
+++ b/client/src/views/game.html
@@ -15,6 +15,7 @@
+
diff --git a/client/src/views/how-to-use.html b/client/src/views/how-to-use.html
index 086f432..beb9faa 100644
--- a/client/src/views/how-to-use.html
+++ b/client/src/views/how-to-use.html
@@ -22,7 +22,23 @@
-
+
+
This app serves as a means of running games in a social setting where a traditional
running of the game is hindered. This might be when people are meeting virtually, and thus roles can't be handed
out in-person, or when people are in-person but don't have Werewolf cards with them. You can use a deck of regular
@@ -32,7 +48,7 @@
players. This app attempts to provide the utilities necessary to run Werewolf with all the different roles you want,
wherever you can access the internet.
-
+
Creating a game through the app has 3 main components:
@@ -80,7 +96,7 @@
end. Whether or not the game ends immediately after that or continues longer is up to the moderator.
-
+
This is an example of what a
dedicated moderator sees during the game:
@@ -105,8 +121,23 @@
become a spectator:
+
+
+
+ This is an example of what a
player is seeing. The timer is running, and they view their
+ role by double-clicking it:
+
+
+
+ There are three main things - the
timer , your
role card
+ and the
player list . Players can view the timer, but only the current moderator can play and pause it.
+
Your role card starts flipped over - this is useful if you are in-person and don't want someone else accidentally seeing your role as
+ it is dealt.
You can view your role at any time by double-clicking/double-tapping it . Requiring a double-click guards against the possibility
+ of accidentally flipping your role when tapping other things. Within the
player list , you can see
+
who is alive or dead and
who has had their role revealed . There is
+ also a
role info button that, when pressed, displays all the different roles in the current game,
+ including their descriptions and alignment (good/evil).
-
More content coming soon.
diff --git a/karma.conf.js b/karma.conf.js
index 5c29d9e..1859ce3 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -8,7 +8,9 @@ module.exports = function(config) {
{ pattern: 'client/src/modules/*/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/config/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/model/*.js', type: 'module', included: true, served: true },
- { pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true }
+ { pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true },
+ { pattern: 'core-js/stable', type: 'module', included: true, served: true },
+ { pattern: 'regenerator-runtime/runtime', type: 'module', included: true, served: true }
],
reporters: ['progress'],
port: 9876,