diff --git a/TableturfBattleClient/index.html b/TableturfBattleClient/index.html index aca6fdb..e03e144 100644 --- a/TableturfBattleClient/index.html +++ b/TableturfBattleClient/index.html @@ -202,6 +202,33 @@ +
+
+ + +
+
+ +
+
+ +
+ + +
+
+ +
diff --git a/TableturfBattleClient/src/Card.ts b/TableturfBattleClient/src/Card.ts index 56b1735..f33076d 100644 --- a/TableturfBattleClient/src/Card.ts +++ b/TableturfBattleClient/src/Card.ts @@ -3,7 +3,7 @@ class Card { name: string; rarity: Rarity; specialCost: number; - grid: Space[][]; + grid: readonly (readonly Space[])[]; size: number; private minX: number; diff --git a/TableturfBattleClient/src/CardList.ts b/TableturfBattleClient/src/CardList.ts new file mode 100644 index 0000000..44dbd34 --- /dev/null +++ b/TableturfBattleClient/src/CardList.ts @@ -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); + } +} diff --git a/TableturfBattleClient/src/GameVariables.ts b/TableturfBattleClient/src/GameVariables.ts index 9385106..ad12230 100644 --- a/TableturfBattleClient/src/GameVariables.ts +++ b/TableturfBattleClient/src/GameVariables.ts @@ -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; diff --git a/TableturfBattleClient/src/Pages/DeckEditPage.ts b/TableturfBattleClient/src/Pages/DeckEditPage.ts index daf1164..7862edc 100644 --- a/TableturfBattleClient/src/Pages/DeckEditPage.ts +++ b/TableturfBattleClient/src/Pages/DeckEditPage.ts @@ -1,14 +1,18 @@ +/// + 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(); +}); diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index 32d19a5..df28de4 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -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) { diff --git a/TableturfBattleClient/src/Pages/LobbyPage.ts b/TableturfBattleClient/src/Pages/LobbyPage.ts index 13cfeec..3380c1b 100644 --- a/TableturfBattleClient/src/Pages/LobbyPage.ts +++ b/TableturfBattleClient/src/Pages/LobbyPage.ts @@ -213,6 +213,7 @@ function initStageDatabase(stages: Stage[]) { }); button.setStartSpaces(2); stageList.appendChild(button.element); + addTestStage(stage); } document.getElementById('stageListLoadingSection')!.hidden = true; } diff --git a/TableturfBattleClient/src/Pages/PreGamePage.ts b/TableturfBattleClient/src/Pages/PreGamePage.ts index ec91bea..0a5c20d 100644 --- a/TableturfBattleClient/src/Pages/PreGamePage.ts +++ b/TableturfBattleClient/src/Pages/PreGamePage.ts @@ -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; diff --git a/TableturfBattleClient/src/Placement.ts b/TableturfBattleClient/src/Placement.ts index ff25f1d..5c9e702 100644 --- a/TableturfBattleClient/src/Placement.ts +++ b/TableturfBattleClient/src/Placement.ts @@ -2,3 +2,8 @@ interface Placement { players: number[], spacesAffected: { space: Point, newState: Space, oldState?: Space }[] } + +interface PlacementResults { + placements: Placement[], + specialSpacesActivated: Point[] +} diff --git a/TableturfBattleClient/src/Stage.ts b/TableturfBattleClient/src/Stage.ts index d29ca29..13aee3c 100644 --- a/TableturfBattleClient/src/Stage.ts +++ b/TableturfBattleClient/src/Stage.ts @@ -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)); + } } diff --git a/TableturfBattleClient/tableturf.css b/TableturfBattleClient/tableturf.css index ae8332a..96c8c77 100644 --- a/TableturfBattleClient/tableturf.css +++ b/TableturfBattleClient/tableturf.css @@ -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 */