#1 Implement deck editor test mode. Resolves #1.

This commit is contained in:
Andrio Celos
2022-12-29 19:20:24 +11:00
parent 0236d75e6d
commit 708ad27356
11 changed files with 517 additions and 95 deletions

View File

@@ -202,6 +202,33 @@
</div>
</div>
<div id="testControls">
<div id="testControlsHeader" class="testControls">
<label for="testDeckButton">
<input type="radio" id="testDeckButton" name="testPage"/>Deck
</label>
<label for="testAllCardsButton">
<input type="radio" id="testAllCardsButton" name="testPage"/>All cards
</label>
</div>
<div id="testDeckContainer" class="testControls">
<button id="testAllCardsMobileButton">All cards</button>
<div id="testDeckList"></div>
</div>
<div id="testAllCardsContainer" class="testControls" hidden>
<label for="testAllCardsListSortBox">
Sort by
<select id="testAllCardsListSortBox" autocomplete="off"></select>
</label>
<div id="testAllCardsList" class="cardListGrid"></div>
</div>
<div id="testControlsFooter" class="testControls">
<button type="button" id="testUndoButton" disabled>Undo</button>
<button type="button" id="testBackButton">End test</button>
</div>
</div>
<div id="testCardListBackdrop" hidden></div>
<div id="turnNumberContainer" hidden>
<p>Turns left</p>
<div id="turnNumberLabel">12</div>
@@ -233,6 +260,8 @@
<div class="playContainer" data-index="3"></div>
<div class="playContainer" data-index="2"></div>
<div class="playContainer" data-index="0"></div>
<div id="testPlacementList"></div>
</div>
<div id="deckListPage" hidden>
<section id="deckEditordeckListPage">
@@ -295,16 +324,18 @@
<a id="deckCardListBackButton" href="#">Back</a>
<label for="cardListSortBox">
Sort by
<select id="cardListSortBox" autocomplete="off">
<option value="number">number</option>
<option value="name">name</option>
<option value="size">size</option>
<option value="rarity">rarity</option>
</select>
<select id="cardListSortBox" autocomplete="off"></select>
</label>
<div id="cardList"></div>
<div id="cardList" class="cardListGrid"></div>
</section>
<div id="deckEditorCardListBackdrop"></div>
<dialog id="testStageSelectionDialog">
<h3>Select a stage.</h3>
<form id="testStageSelectionForm" method="dialog">
<div id="testStageSelectionList"></div>
<button type="submit">Cancel</button>
</form>
</dialog>
</div>
<dialog id="errorDialog">
<div id="errorMessage"></div>

View File

@@ -3,7 +3,7 @@ class Card {
name: string;
rarity: Rarity;
specialCost: number;
grid: Space[][];
grid: readonly (readonly Space[])[];
size: number;
private minX: number;

View File

@@ -0,0 +1,41 @@
class CardList {
readonly listElement: HTMLElement;
readonly sortBox: HTMLSelectElement;
readonly cardButtons: CardButton[] = [ ];
static readonly cardSortOrders: { [key: string]: ((a: Card, b: Card) => number) | undefined } = {
'number': (a, b) => a.number - b.number,
'name': (a, b) => a.name.localeCompare(b.name),
'size': (a, b) => a.size != b.size ? a.size - b.size : a.number - b.number,
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : a.number - b.number,
}
constructor(listElement: HTMLElement, sortBox: HTMLSelectElement) {
this.listElement = listElement;
this.sortBox = sortBox;
sortBox.addEventListener('change', () => {
const sortOrder = CardList.cardSortOrders[sortBox.value];
if (sortOrder) {
clearChildren(listElement);
this.cardButtons.sort((a, b) => sortOrder(a.card, b.card));
for (const button of this.cardButtons)
listElement.appendChild(button.element);
}
});
for (const label in CardList.cardSortOrders) {
const option = document.createElement('option');
option.value = label;
option.innerText = label;
sortBox.appendChild(option);
}
}
static fromId(id: string, sortBoxId: string) { return new CardList(document.getElementById(id)!, document.getElementById(sortBoxId) as HTMLSelectElement); }
add(button: CardButton) {
this.cardButtons.push(button);
this.listElement.appendChild(button.element);
}
}

View File

@@ -17,7 +17,7 @@ let currentGame: {
let enterGameTimeout: number | null = null;
let currentReplay: {
turns: Move[][],
placements: { placements: Placement[], specialSpacesActivated: Point[] }[],
placements: PlacementResults[],
initialDrawOrder: number[][],
drawOrder: number[][]
} | null = null;

View File

@@ -1,14 +1,18 @@
/// <reference path="../CardList.ts"/>
const deckNameLabel2 = document.getElementById('deckName2')!;
const deckEditSize = document.getElementById('deckEditSize')!;
const deckCardListEdit = document.getElementById('deckCardListEdit')!;
const cardList = document.getElementById('cardList')!;
const cardList = CardList.fromId('cardList', 'cardListSortBox');
const deckTestButton = document.getElementById('deckTestButton') as HTMLButtonElement;
const deckSaveButton = document.getElementById('deckSaveButton') as HTMLButtonElement;
const deckCancelButton = document.getElementById('deckCancelButton') as HTMLButtonElement;
const deckCardListBackButton = document.getElementById('deckCardListBackButton') as HTMLLinkElement;
const cardListSortBox = document.getElementById('cardListSortBox') as HTMLSelectElement;
const testStageSelectionList = document.getElementById('testStageSelectionList')!;
const testStageButtons: StageButton[] = [ ];
const testStageSelectionDialog = document.getElementById('testStageSelectionDialog') as HTMLDialogElement;
const cardButtons: CardButton[] = [ ];
const deckEditCardButtons: (CardButton | HTMLLabelElement)[] = [ ];
let selectedDeckCardIndex: number | null = null;
@@ -54,10 +58,10 @@ function createDeckEditCardButton(index: number, card: number) {
}
selectedDeckCardIndex = index;
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
button2.checked = button2.card.number == card;
}
cardList.parentElement!.classList.add('selecting');
cardList.listElement.parentElement!.classList.add('selecting');
}
});
return button;
@@ -82,10 +86,10 @@ function createDeckEditEmptySlotButton(index: number) {
}
selectedDeckCardIndex = index;
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
button2.checked = false;
}
cardList.parentElement!.classList.add('selecting');
cardList.listElement.parentElement!.classList.add('selecting');
}
});
element.appendChild(input);
@@ -122,10 +126,10 @@ function initCardDatabase(cards: Card[]) {
for (const card of cards) {
const button = new CardButton('radio', card);
button.inputElement.name = 'deckEditorCardList';
cardButtons.push(button);
cardList.add(button);
button.inputElement.addEventListener('input', () => {
if (button.inputElement.checked) {
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
if (button2 != button)
button2.checked = false;
}
@@ -143,14 +147,14 @@ function initCardDatabase(cards: Card[]) {
deckEditCardButtons[selectedDeckCardIndex] = button3;
deckEditUpdateSize();
cardList.parentElement!.classList.remove('selecting');
cardList.listElement.parentElement!.classList.remove('selecting');
if (!deckModified) {
deckModified = true;
window.addEventListener('beforeunload', onBeforeUnload_deckEditor);
}
}
});
cardList.appendChild(button.element);
addTestCard(card);
}
}
@@ -164,24 +168,7 @@ deckCardListBackButton.addEventListener('click', e => {
(o as HTMLElement).classList.remove('checked');
}
}
cardList.parentElement!.classList.remove('selecting');
});
const cardSortOrders: { [key: string]: ((a: Card, b: Card) => number) | undefined } = {
'number': (a, b) => a.number - b.number,
'name': (a, b) => a.name.localeCompare(b.name),
'size': (a, b) => a.size != b.size ? a.size - b.size : a.number - b.number,
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : a.number - b.number,
}
cardListSortBox.addEventListener('change', () => {
const sortOrder = cardSortOrders[cardListSortBox.value];
if (sortOrder) {
clearChildren(cardList);
cardButtons.sort((a, b) => sortOrder(a.card, b.card));
for (const button of cardButtons)
cardList.appendChild(button.element);
}
cardList.listElement.parentElement!.classList.remove('selecting');
});
function stopEditingDeck() {
@@ -195,3 +182,39 @@ function onBeforeUnload_deckEditor(e: BeforeUnloadEvent) {
e.preventDefault();
return 'You have unsaved changes to your deck that will be lost. Are you sure you want to continue?';
}
function addTestStage(stage: Stage) {
const button = new StageButton(stage);
testStageButtons.push(button);
button.inputElement.name = 'stage';
button.inputElement.addEventListener('input', () => {
if (button.inputElement.checked) {
stageRandomLabel.classList.remove('checked');
for (const button2 of testStageButtons) {
if (button2 != button)
button2.element.classList.remove('checked');
}
clearChildren(testDeckList);
testDeckCardButtons.splice(0);
for (const el of deckEditCardButtons) {
const card = (el as CardButton).card;
if (card) {
addTestDeckCard(card);
}
}
testStageSelectionDialog.close();
initTest(stage);
}
});
button.setStartSpaces(2);
testStageSelectionList.appendChild(button.element);
}
deckTestButton.addEventListener('click', _ => {
for (const button of testStageButtons)
button.checked = false;
testStageSelectionDialog.showModal();
});

View File

@@ -38,6 +38,20 @@ const showDeckCloseButton = document.getElementById('showDeckCloseButton') as HT
const playContainers = Array.from(document.getElementsByClassName('playContainer')) as HTMLDivElement[];
playContainers.sort((a, b) => parseInt(a.dataset.index || '0') - parseInt(b.dataset.index || '0'));
const testControls = document.getElementById('testControls')!;
const testDeckList = document.getElementById('testDeckList')!;
const testAllCardsList = CardList.fromId('testAllCardsList', 'testAllCardsListSortBox');
const testPlacementList = document.getElementById('testPlacementList')!;
const testDeckButton = CheckButton.fromId('testDeckButton');
const testDeckContainer = document.getElementById('testDeckContainer')!;
const testAllCardsButton = CheckButton.fromId('testAllCardsButton');
const testAllCardsContainer = document.getElementById('testAllCardsContainer')!;
const testUndoButton = document.getElementById('testUndoButton') as HTMLButtonElement;
const testBackButton = document.getElementById('testBackButton') as HTMLButtonElement;
const testDeckCardButtons: CardButton[] = [ ];
const testPlacements: { card: Card, placementResults: PlacementResults }[] = [ ];
const testCardListBackdrop = document.getElementById('testCardListBackdrop')!;
let testMode = false;
let canPlay = false;
@@ -55,23 +69,60 @@ cols[4][4] = Space.SpecialInactive2;
board.resize(cols);
function initGame() {
testMode = false;
gamePage.classList.remove('deckTest');
playControls.hidden = false;
resultContainer.hidden = true;
replayControls.hidden = true;
gameButtonsContainer.hidden = false;
testControls.hidden = true;
showPage('game');
}
function initReplay() {
testMode = false;
gamePage.classList.remove('deckTest');
playControls.hidden = true;
resultContainer.hidden = true;
replayControls.hidden = false;
gameButtonsContainer.hidden = true;
testControls.hidden = true;
canPlay = false;
showPage('game');
clearPlayContainers();
turnNumberLabel.setTurnNumber(1);
}
function initTest(stage: Stage) {
currentGame = { id: 'test', maxPlayers: 2, players: [ ], webSocket: null, turnNumber: 1, me: { playerIndex: 0, move: null, deck: null, hand: null, cardsUsed: [ ] } };
board.resize(stage.copyGrid());
const startSpaces = stage.getStartSpaces(2);
for (let i = 0; i < 2; i++)
board.grid[startSpaces[i].x][startSpaces[i].y] = Space.SpecialInactive1 | i;
board.refresh();
for (var o of playerBars)
o.element.hidden = true;
testMode = true;
testPlacements.splice(0);
clearChildren(testPlacementList);
gamePage.classList.add('deckTest');
gamePage.dataset.myPlayerIndex = '0';
gamePage.dataset.uiBaseColourIsSpecialColour = 'true';
playControls.hidden = true;
resultContainer.hidden = true;
replayControls.hidden = true;
gameButtonsContainer.hidden = false;
testControls.hidden = false;
clearPlayContainers();
turnNumberLabel.setTurnNumber(null);
showPage('game');
testDeckButton.checked = true;
testAllCardsButton.checked = false;
setTestListPage(false);
}
replayNextButton.addEventListener('click', _ => {
if (currentGame == null || currentReplay == null || currentGame.turnNumber > 12) return;
@@ -152,27 +203,10 @@ replayPreviousButton.addEventListener('click', _ => {
el.innerText = '';
}
// Unwind the turn.
for (const p of result.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame.players[space & 3];
player2.specialPoints--;
player2.totalSpecialPoints--;
board.grid[p.x][p.y] &= ~4;
}
currentGame.turnNumber--;
for (let i = result.placements.length - 1; i >= 0; i--) {
const placement = result.placements[i];
for (const p of placement.spacesAffected) {
if (p.oldState == undefined) throw new TypeError('oldState missing');
board.grid[p.space.x][p.space.y] = p.oldState;
}
}
undoTurn(result);
currentGame!.turnNumber--;
gamePage.classList.remove('gameEnded');
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
board.refresh();
turnNumberLabel.setTurnNumber(currentGame!.turnNumber);
for (let i = 0; i < currentGame.players.length; i++) {
const move = currentReplay.turns[currentGame.turnNumber - 1][i];
@@ -186,6 +220,28 @@ replayPreviousButton.addEventListener('click', _ => {
}
});
function undoTurn(turn: PlacementResults) {
for (const p of turn.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame!.players[space & 3];
if (player2) {
player2.specialPoints--;
player2.totalSpecialPoints--;
}
board.grid[p.x][p.y] &= ~4;
}
for (let i = turn.placements.length - 1; i >= 0; i--) {
const placement = turn.placements[i];
for (const p of placement.spacesAffected) {
if (p.oldState == undefined) throw new TypeError('oldState missing');
board.grid[p.space.x][p.space.y] = p.oldState;
}
}
board.refresh();
}
replayFlipBox.addEventListener('input', _ => {
if (currentGame == null || currentReplay == null) return;
if (replayAnimationAbortController) {
@@ -203,6 +259,96 @@ replayFlipBox.addEventListener('input', _ => {
board.resize();
});
function addTestDeckCard(card: Card) {
const button = new CardButton('radio', cardDatabase.get(card.number));
button.inputElement.name = 'deckEditorTestSelectedCard'
testDeckList.appendChild(button.element);
testDeckCardButtons.push(button);
button.inputElement.addEventListener('input', () => {
if (button.checked) {
for (const button2 of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button2 != button)
button2.element.classList.remove('checked');
}
board.cardPlaying = card;
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3);
}
board.cardRotation = board.flip ? 2 : 0;
board.refreshHighlight();
board.table.focus();
}
});
}
function addTestCard(card: Card) {
const button = new CardButton('radio', cardDatabase.get(card.number));
button.inputElement.name = 'deckEditorTestSelectedCard'
testAllCardsList.add(button);
button.inputElement.addEventListener('input', () => {
if (button.checked) {
for (const button2 of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button2 != button)
button2.element.classList.remove('checked');
}
board.cardPlaying = card;
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3);
}
board.cardRotation = board.flip ? 2 : 0;
board.refreshHighlight();
board.table.focus();
if (!testCardListBackdrop.hidden) {
testCardListBackdrop.hidden = true;
gamePage.removeChild(testAllCardsContainer);
testControls.appendChild(testAllCardsContainer);
setTestListPage(false);
}
}
});
}
testUndoButton.addEventListener('click', _ => {
const turn = testPlacements.pop();
if (turn) {
undoTurn(turn.placementResults);
testPlacementList.removeChild(testPlacementList.firstChild!);
for (const button of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button.card.number == turn.card.number)
button.enabled = true;
}
testUndoButton.disabled = testPlacements.length == 0;
}
});
testBackButton.addEventListener('click', _ => {
showPage('deckEdit');
});
testDeckButton.input.addEventListener('input', _ => {
if (testDeckButton.checked) setTestListPage(false);
});
testAllCardsButton.input.addEventListener('input', _ => {
if (testAllCardsButton.checked) setTestListPage(true);
});
function setTestListPage(showAllCards: boolean) {
testDeckButton.checked = !showAllCards;
testAllCardsButton.checked = showAllCards;
testDeckContainer.hidden = showAllCards
testAllCardsContainer.hidden = !showAllCards;
}
document.getElementById('testAllCardsMobileButton')!.addEventListener('click', _ => {
setTestListPage(true);
testControls.removeChild(testAllCardsContainer);
gamePage.appendChild(testAllCardsContainer);
testCardListBackdrop.hidden = false;
});
function loadPlayers(players: Player[]) {
gamePage.dataset.players = players.length.toString();
for (let i = 0; i < players.length; i++) {
@@ -359,6 +505,7 @@ function showResult() {
function clearShowDeck() {
showDeckContainer.hidden = true;
testCardListBackdrop.hidden = true;
clearChildren(showDeckListElement);
showDeckButtons.splice(0);
}
@@ -522,7 +669,26 @@ board.onclick = (x, y) => {
if (testMode) {
const move: PlayMove = { card: board.cardPlaying, isPass: false, x, y, rotation: board.cardRotation, isSpecialAttack: false };
const r = board.makePlacements([ move ]);
testPlacements.push({ card: board.cardPlaying, placementResults: r });
board.refresh();
var li = document.createElement('div');
li.innerText = board.cardPlaying.name;
if (testDeckCardButtons.find(b => b.card.number == board.cardPlaying!.number))
li.classList.add('deckCard');
else
li.classList.add('externalCard');
testPlacementList.insertBefore(li, testPlacementList.firstChild);
for (const button of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button.card.number == board.cardPlaying.number) {
button.checked = false;
button.enabled = false;
}
}
board.cardPlaying = null;
testUndoButton.disabled = false;
} else if (canPlay) {
canPlay = false;
// Send the play to the server.
@@ -580,9 +746,11 @@ function focusFirstEnabledHandCard() {
function toggleShowDeck() {
if (showDeckContainer.hidden) {
showDeckContainer.hidden = false;
testCardListBackdrop.hidden = false;
showDeckCloseButton.focus();
} else {
showDeckContainer.hidden = true;
testCardListBackdrop.hidden = true;
focusFirstEnabledHandCard();
}
}
@@ -596,7 +764,7 @@ rotateRightButton.addEventListener('click', () => {
board.refreshHighlight();
});
gameDeckButton.addEventListener('click', toggleShowDeck);
showDeckCloseButton.addEventListener('click', () => showDeckContainer.hidden = true);
showDeckCloseButton.addEventListener('click', toggleShowDeck);
document.addEventListener('keydown', e => {
if (!pages.get('game')!.hidden) {

View File

@@ -213,6 +213,7 @@ function initStageDatabase(stages: Stage[]) {
});
button.setStartSpaces(2);
stageList.appendChild(button.element);
addTestStage(stage);
}
document.getElementById('stageListLoadingSection')!.hidden = true;
}

View File

@@ -255,7 +255,7 @@ function loadReplay(base64: string) {
webSocket: null
};
board.resize(stage.grid);
board.resize(stage.copyGrid());
const startSpaces = stage.getStartSpaces(numPlayers);
for (let i = 0; i < numPlayers; i++)
board.grid[startSpaces[i].x][startSpaces[i].y] = Space.SpecialInactive1 | i;

View File

@@ -2,3 +2,8 @@ interface Placement {
players: number[],
spacesAffected: { space: Point, newState: Space, oldState?: Space }[]
}
interface PlacementResults {
placements: Placement[],
specialSpacesActivated: Point[]
}

View File

@@ -1,6 +1,6 @@
class Stage {
name: string;
grid: Space[][];
grid: readonly (readonly Space[])[];
startSpaces: Point[][];
constructor(name: string, grid: Space[][], startSpaces: Point[][]) {
@@ -21,4 +21,8 @@ class Stage {
}
return list!;
}
copyGrid() {
return Array.from(this.grid, a => a.slice(0));
}
}

View File

@@ -28,6 +28,9 @@ body {
--primary-colour-4 : hsl(155, 95%, 50%);
--special-colour-4 : hsl(120, 95%, 50%);
--special-accent-colour-4: hsl(135, 95%, 85%);
--player-primary-colour: var(--primary-colour-1);
--player-special-colour: var(--special-colour-1);
--player-special-accent-colour: var(--special-accent-colour-1);
}
#gamePage {
@@ -410,23 +413,34 @@ dialog::backdrop {
/* Card list */
#cardList {
display: flex;
flex-wrap: wrap;
overflow-y: scroll;
grid-row: 2;
grid-column: 1 / -1;
}
.cardListControl {
display: grid;
grid-template-rows: auto 1fr;
}
.cardListGrid:not([hidden]) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11em, 1fr));
overflow-y: auto;
justify-items: center;
}
/* Game page */
#gamePage:not([hidden]) {
--controls-width: 11em;
display: grid;
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1] 10em [play-column-2] 10em;
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1] 10em [play-column-2] 10em;
grid-template-rows: [player-row-1] auto [player-row-3] auto [hand-row] 1fr [player-row-2] auto [player-row-0] auto;
height: 100vh;
}
#gamePage.deckTest {
--controls-width: 14em;
}
#gamePage:where([data-players="2"]):not([hidden]) {
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 20em;
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 20em;
}
.playerBar[data-index="0"] {
@@ -469,7 +483,8 @@ dialog::backdrop {
padding-right: 10px;
}
#scoreSection {
#gamePage.deckTest #scoreSection { display: none; }
#scoreSection:not([hidden]) {
grid-column: score-column;
grid-row: 2 / -2;
display: flex;
@@ -501,7 +516,7 @@ dialog::backdrop {
text-align: center;
}
#playControls > label {
:is(#playControls, #testControlsHeader) > label {
grid-row: button-row;
margin: 0.5em;
padding: 0 0.5em;
@@ -510,14 +525,14 @@ dialog::backdrop {
text-shadow: 0 0 4px black;
border-radius: 0.5em;
}
#playControls > label input {
:is(#playControls, #testControlsHeader) > label input {
position: absolute;
left: -333px;
}
#playControls > label:hover { background: var(--player-ui-highlight-colour); }
#playControls > label:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
#playControls > label:is(:active, .checked) { background: var(--player-ui-highlight2-colour); }
#playControls > label.disabled { background: grey; }
:is(#playControls, #testControlsHeader) > label:hover { background: var(--player-ui-highlight-colour); }
:is(#playControls, #testControlsHeader) > label:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
:is(#playControls, #testControlsHeader) > label:is(:active, .checked) { background: var(--player-ui-highlight2-colour); }
:is(#playControls, #testControlsHeader) > label.disabled { background: grey; }
#showDeckContainer {
grid-column: 1 / span 3;
@@ -531,7 +546,7 @@ dialog::backdrop {
height: 100%;
text-align: center;
}
#showDeckList {
#showDeckList, #testDeckList {
display: grid;
grid-template-columns: auto auto auto;
list-style-type: none;
@@ -540,6 +555,7 @@ dialog::backdrop {
padding: 0;
margin: 0;
justify-items: center;
align-items: center;
}
#showDeckList > li {
margin: 0.25em;
@@ -552,22 +568,76 @@ dialog::backdrop {
#showDeckList > li.used {
opacity: 0.35;
}
#showDeckCloseButton {
#showDeckCloseButton, #testControlsFooter button {
border: none;
font-family: inherit;
color: inherit;
width: 40%;
grid-row: button-row;
margin: 1em;
margin: 0.5em;
padding: 0 0.5em;
text-align: center;
background: var(--player-ui-base-colour);
text-shadow: 0 0 4px black;
border-radius: 0.5em;
font-size: inherit;
}
#showDeckCloseButton {
width: 40%;
grid-row: button-row;
}
:is(#showDeckCloseButton, #testControlsFooter button):hover { background: var(--player-ui-highlight-colour); }
:is(#showDeckCloseButton, #testControlsFooter button):focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
:is(#showDeckCloseButton, #testControlsFooter button):active { background: var(--player-ui-highlight2-colour); }
#testControls:not([hidden]) {
grid-column: 1 / span 2;
grid-row: 1 / -1;
display: grid;
grid-template-rows: [header] auto [list] 1fr [buttons] auto;
}
#testControlsHeader, #testControlsFooter {
display: grid;
grid-template-columns: 1fr 1fr;
}
#testDeckContainer {
grid-row: list;
overflow: hidden;
}
#testDeckList {
height: 100%;
}
#testAllCardsContainer:not([hidden]) {
overflow: hidden;
display: grid;
grid-template-rows: auto 1fr;
grid-row: 2;
}
#testAllCardsMobileButton {
display: none;
font: inherit;
color: inherit;
background: transparent;
border: 0.25em solid var(--player-ui-base-colour);
}
#testAllCardsMobileButton:hover { border-color: var(--player-ui-highlight-colour); }
#testAllCardsMobileButton:focus-within { outline: 2px solid var(--player-special-accent-colour); }
#testAllCardsMobileButton:active { border-color: var(--player-special-accent-colour); }
#testDeckList, #testAllCardsList {
flex-grow: 1;
}
#testAllCardsList:not([hidden]) {
font-size: 70%;
}
#testCardListBackdrop {
background: #00000080;
z-index: 1;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
#showDeckCloseButton:hover { background: var(--player-ui-highlight-colour); }
#showDeckCloseButton:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
#showDeckCloseButton:active { background: var(--player-ui-highlight2-colour); }
.playContainer { text-align: center; }
@@ -585,6 +655,25 @@ dialog::backdrop {
margin: 0;
}
#testPlacementList {
display: none;
}
#gamePage.deckTest #testPlacementList {
grid-column: -3 / -1;
grid-row: 1 / -1;
display: flex;
flex-flow: column;
margin: 1em;
gap: 1em;
}
#testPlacementList div {
background: #222;
padding: 0.5em;
}
#testPlacementList div.deckCard {
background: #246;
}
#gameButtonsContainer {
grid-column: score-column;
grid-row: 1 / -1;
@@ -613,6 +702,9 @@ dialog::backdrop {
opacity: 1;
color: var(--player-ui-highlight-colour);
}
#gamePage.deckTest #gameDeckButton {
display: none;
}
#boardSection {
grid-column: board-column;
@@ -866,7 +958,7 @@ dialog::backdrop {
:is(#deckListPage, #deckEditPage):not([hidden]) {
height: 100vh;
display: grid;
grid-template-columns: auto auto;
grid-template-columns: auto 1fr;
}
.deckList > label, #addDeckControls > button {
@@ -920,7 +1012,7 @@ dialog::backdrop {
#deckCardListView, #deckCardListEdit {
display: grid;
grid-template-columns: auto auto auto;
justify-content: center;
justify-items: center;
grid-column: 2;
grid-row: 1 / 5;
overflow-y: scroll;
@@ -976,6 +1068,13 @@ dialog::backdrop {
opacity: 0.5;
}
#testStageSelectionList {
max-width: 52em;
display: flex;
flex-flow: row wrap;
justify-content: center;
}
/* Small display layout */
@media (max-width: 89rem) or (max-height: 42rem) {
#gamePage, #deckCardListView, #deckCardListEdit {
@@ -988,7 +1087,7 @@ dialog::backdrop {
}
#gamePage:not([hidden]) {
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 10em;
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 10em;
}
.playContainer { position: relative; }
@@ -1019,9 +1118,15 @@ dialog::backdrop {
@media (max-width: 40rem) {
.card {
margin: 0;
width: auto;
}
.cardListGrid:not([hidden]) {
justify-items: stretch;
}
.cardListGrid .card {
width: auto;
justify-self: stretch;
}
/* Lobby - Mobile layout */
#lobbyPage:not([hidden]) {
@@ -1034,14 +1139,13 @@ dialog::backdrop {
height: initial;
}
#stageList {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
font-size: x-small;
#stageList, #testStageSelectionList {
font-size: 75%;
}
.stage, .stageRandom {
width: auto;
flex-basis: 10em;
flex-grow: 1;
}
/* Game - Mobile layout */
@@ -1085,7 +1189,7 @@ dialog::backdrop {
width: initial;
}
#scoreSection {
#scoreSection:not([hidden]) {
grid-column: 1;
grid-row: 2;
grid-template-rows: 1fr;
@@ -1108,7 +1212,7 @@ dialog::backdrop {
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: auto;
width: 100vw;
overflow: scroll visible;
overflow: scroll hidden;
}
#showDeckContainer {
@@ -1149,6 +1253,14 @@ dialog::backdrop {
#gameButtonsContainer button {
font-size: 250%;
}
#gamePage > #testAllCardsContainer:not([hidden]) {
grid-row: 1 / -1;
grid-column: 1 / -1;
background: black;
z-index: 2;
margin-top: 5em;
}
/* Deck editor - Mobile layout */
@@ -1220,14 +1332,51 @@ dialog::backdrop {
}
#cardList {
font-size: 50%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
font-size: 55%;
}
#cardList .card {
height: 13em;
}
#testControls:not([hidden]) {
grid-column: 1 / -1;
grid-row: -2;
}
#testControlsHeader {
display: none;
}
#testAllCardsMobileButton {
display: inline-block;
width: 7em;
}
#testDeckContainer:not([hidden]) {
display: grid;
grid-template-columns: auto auto;
overflow-x: scroll;
}
#testDeckList {
width: max-content;
display: inline-block;
padding: 0;
margin: 0;
justify-items: center;
align-items: center;
overflow: initial;
}
#gamePage.deckTest #testPlacementList {
grid-column: 1 / -1;
grid-row: 1;
height: 7em;
flex-flow: row wrap;
align-content: flex-start;
margin: 0.5em;
gap: 0.5em;
}
#testPlacementList div {
padding: 0 0.5em;
}
}
/* Loading spinner - based on loading.io */