From f193477ce369fa8ce98ed0168f3877e2e61d8cfa Mon Sep 17 00:00:00 2001 From: Andrio Celos Date: Sun, 4 Feb 2024 22:32:09 +1100 Subject: [PATCH] Add WIP gallery page and custom card editor --- TableturfBattleClient/index.html | 30 +++ TableturfBattleClient/src/CardButton.ts | 4 +- TableturfBattleClient/src/CardDisplay.ts | 66 +++-- TableturfBattleClient/src/CardList.ts | 18 +- TableturfBattleClient/src/Config.ts | 4 + TableturfBattleClient/src/ICardElement.ts | 4 + .../src/Pages/DeckEditPage.ts | 2 +- .../src/Pages/GalleryPage.ts | 245 ++++++++++++++++++ TableturfBattleClient/src/Pages/GamePage.ts | 2 +- .../src/Pages/PreGamePage.ts | 11 +- TableturfBattleClient/src/app.ts | 6 +- TableturfBattleClient/tableturf.css | 145 ++++++++++- TableturfBattleServer/Program.cs | 2 +- 13 files changed, 498 insertions(+), 41 deletions(-) create mode 100644 TableturfBattleClient/src/ICardElement.ts create mode 100644 TableturfBattleClient/src/Pages/GalleryPage.ts diff --git a/TableturfBattleClient/index.html b/TableturfBattleClient/index.html index 3ce6103..31c6148 100644 --- a/TableturfBattleClient/index.html +++ b/TableturfBattleClient/index.html @@ -57,6 +57,7 @@

Edit decks | + Card list | Replay | Settings | Help @@ -584,6 +585,35 @@

+

Select a stage.

diff --git a/TableturfBattleClient/src/CardButton.ts b/TableturfBattleClient/src/CardButton.ts index 95f52c3..f3c556c 100644 --- a/TableturfBattleClient/src/CardButton.ts +++ b/TableturfBattleClient/src/CardButton.ts @@ -1,6 +1,7 @@ /// -class CardButton extends CheckButton { +class CardButton extends CheckButton implements ICardElement { + readonly element: HTMLButtonElement; private static idNumber = 0; readonly card: Card; @@ -13,6 +14,7 @@ class CardButton extends CheckButton { if (card.number < 0) button.classList.add('upcoming'); button.dataset.cardNumber = card.number.toString(); super(button); + this.element = button; this.card = card; diff --git a/TableturfBattleClient/src/CardDisplay.ts b/TableturfBattleClient/src/CardDisplay.ts index 1b828e1..5cf54ac 100644 --- a/TableturfBattleClient/src/CardDisplay.ts +++ b/TableturfBattleClient/src/CardDisplay.ts @@ -1,15 +1,26 @@ -class CardDisplay { +class CardDisplay implements ICardElement { readonly card: Card; - readonly element: SVGSVGElement; + readonly element: HTMLElement; + readonly svg: SVGSVGElement; + private readonly sizeElement: SVGTextElement; + private readonly specialCostGroup: SVGGElement; + private level: number; - constructor(card: Card, level: number) { - let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + constructor(card: Card, level: number, elementType: string = 'div') { + this.card = card; + this.level = level; + + const element = document.createElement(elementType); + element.classList.add('card'); + element.classList.add([ 'common', 'rare', 'fresh' ][card.rarity]); + this.element = element; + + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('viewBox', '0 0 635 885'); svg.setAttribute('alt', card.name); - this.element = svg; + this.svg = svg; + element.appendChild(svg); - svg.classList.add('card'); - svg.classList.add([ 'common', 'rare', 'fresh' ][card.rarity]); if (card.number < 0) svg.classList.add('upcoming'); svg.dataset.cardNumber = card.number.toString(); svg.style.setProperty("--number", card.number.toString()); @@ -44,6 +55,7 @@ class CardDisplay { // Grid const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + g.setAttribute('class', 'cardGrid'); g.setAttribute('transform', 'translate(380 604) rotate(6.5) scale(0.283)'); svg.appendChild(g); @@ -54,6 +66,7 @@ class CardDisplay { text1.setAttribute('class', 'cardDisplayName'); text1.setAttribute('x', '50%'); text1.setAttribute('y', '168'); + text1.setAttribute('text-anchor', 'middle'); text1.setAttribute('font-size', '76'); text1.setAttribute('font-weight', 'bold'); text1.setAttribute('stroke', 'black'); @@ -108,28 +121,17 @@ class CardDisplay { // Size svg.insertAdjacentHTML('beforeend', ``); - svg.insertAdjacentHTML('beforeend', `${card.size}`); + svg.insertAdjacentHTML('beforeend', `${card.size}`); + this.sizeElement = svg.lastElementChild as SVGTextElement; // Special cost const g2 = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + this.specialCostGroup = g2; g2.setAttribute('class', 'specialCost'); g2.setAttribute('transform', 'translate(170 806) scale(0.32)'); svg.appendChild(g2); - for (let i = 0; i < card.specialCost; i++) { - let rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - const image = document.createElementNS('http://www.w3.org/2000/svg', 'image'); - image.setAttribute('href', 'assets/SpecialOverlay.png'); - for (const el of [ rect, image ]) { - el.setAttribute('x', (110 * (i % 5)).toString()); - el.setAttribute('y', (-125 * Math.floor(i / 5)).toString()); - el.setAttribute('width', '95'); - el.setAttribute('height', '95'); - g2.appendChild(el); - } - } - - this.card = card; + this.setSpecialCost(card.specialCost); } static CreateSvgCardGrid(card: Card, parent: SVGElement) { @@ -162,4 +164,24 @@ class CardDisplay { } } } + + setSpecialCost(value: number) { + clearChildren(this.specialCostGroup); + for (let i = 0; i < value; i++) { + let rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + const image = document.createElementNS('http://www.w3.org/2000/svg', 'image'); + image.setAttribute('href', 'assets/SpecialOverlay.png'); + for (const el of [ rect, image ]) { + el.setAttribute('x', (110 * (i % 5)).toString()); + el.setAttribute('y', (-125 * Math.floor(i / 5)).toString()); + el.setAttribute('width', '95'); + el.setAttribute('height', '95'); + this.specialCostGroup.appendChild(el); + } + } + } + + setSize(value: number) { + this.sizeElement.innerHTML = value.toString(); + } } diff --git a/TableturfBattleClient/src/CardList.ts b/TableturfBattleClient/src/CardList.ts index a3887a3..5852a06 100644 --- a/TableturfBattleClient/src/CardList.ts +++ b/TableturfBattleClient/src/CardList.ts @@ -1,8 +1,8 @@ -class CardList { +class CardList { readonly listElement: HTMLElement; readonly sortBox: HTMLSelectElement; readonly filterBox: HTMLInputElement; - readonly cardButtons: CardButton[] = [ ]; + readonly cardButtons: T[] = [ ]; static readonly cardSortOrders: { [key: string]: (a: Card, b: Card) => number } = { 'number': (a, b) => CardList.compareByNumber(a, b), @@ -42,7 +42,7 @@ class CardList { filterBox.addEventListener('input', () => { const s = filterBox.value.toLowerCase(); for (const button of this.cardButtons) - button.buttonElement.hidden = s != '' && !button.card.name.toLowerCase().includes(s); + button.element.hidden = s != '' && !button.card.name.toLowerCase().includes(s); }); for (const label in CardList.cardSortOrders) { @@ -59,17 +59,17 @@ class CardList { clearChildren(this.listElement); this.cardButtons.sort((a, b) => sortOrder(a.card, b.card)); for (const button of this.cardButtons) - this.listElement.appendChild(button.buttonElement); + this.listElement.appendChild(button.element); } } - static fromId(id: string, sortBoxId: string, filterBoxId: string) { - return new CardList(document.getElementById(id)!, document.getElementById(sortBoxId) as HTMLSelectElement, document.getElementById(filterBoxId) as HTMLInputElement); + static fromId(id: string, sortBoxId: string, filterBoxId: string) { + return new CardList(document.getElementById(id)!, document.getElementById(sortBoxId) as HTMLSelectElement, document.getElementById(filterBoxId) as HTMLInputElement); } - add(button: CardButton) { + add(button: T) { this.cardButtons.push(button); - this.listElement.appendChild(button.buttonElement); + this.listElement.appendChild(button.element); } setSortOrder(sortOrder: string) { @@ -80,6 +80,6 @@ class CardList { clearFilter() { this.filterBox.value = ''; for (const button of this.cardButtons) - button.buttonElement.hidden = false; + button.element.hidden = false; } } diff --git a/TableturfBattleClient/src/Config.ts b/TableturfBattleClient/src/Config.ts index 8985b12..105b457 100644 --- a/TableturfBattleClient/src/Config.ts +++ b/TableturfBattleClient/src/Config.ts @@ -52,3 +52,7 @@ let userConfig = new Config(); function saveSettings() { localStorage.setItem('settings', JSON.stringify(userConfig)); } + +function saveChecklist() { + localStorage.setItem('checklist', JSON.stringify(ownedCards)); +} diff --git a/TableturfBattleClient/src/ICardElement.ts b/TableturfBattleClient/src/ICardElement.ts new file mode 100644 index 0000000..f690bca --- /dev/null +++ b/TableturfBattleClient/src/ICardElement.ts @@ -0,0 +1,4 @@ +interface ICardElement { + card: Card; + element: HTMLElement; +} diff --git a/TableturfBattleClient/src/Pages/DeckEditPage.ts b/TableturfBattleClient/src/Pages/DeckEditPage.ts index 12fb508..1bef532 100644 --- a/TableturfBattleClient/src/Pages/DeckEditPage.ts +++ b/TableturfBattleClient/src/Pages/DeckEditPage.ts @@ -3,7 +3,7 @@ const deckNameLabel2 = document.getElementById('deckName2')!; const deckEditSize = document.getElementById('deckEditSize')!; const deckCardListEdit = document.getElementById('deckCardListEdit')!; -const cardList = CardList.fromId('cardList', 'cardListSortBox', 'cardListFilterBox'); +const cardList = CardList.fromId('cardList', 'cardListSortBox', 'cardListFilterBox'); const cardListButtonGroup = new CheckButtonGroup(); const deckEditMenu = document.getElementById('deckEditMenu')!; diff --git a/TableturfBattleClient/src/Pages/GalleryPage.ts b/TableturfBattleClient/src/Pages/GalleryPage.ts new file mode 100644 index 0000000..29f1423 --- /dev/null +++ b/TableturfBattleClient/src/Pages/GalleryPage.ts @@ -0,0 +1,245 @@ +const galleryCardList = CardList.fromId('galleryCardList', 'gallerySortBox', 'galleryFilterBox'); +const galleryBackButton = document.getElementById('galleryBackButton') as HTMLLinkElement; +const galleryCardDialog = document.getElementById('galleryCardDialog') as HTMLDialogElement; + +const galleryNewCustomCardButton = document.getElementById('galleryNewCustomCardButton') as HTMLButtonElement; +const galleryChecklistBox = document.getElementById('galleryChecklistBox') as HTMLInputElement; +const bitsToCompleteField = document.getElementById('bitsToCompleteField') as HTMLElement; + +let galleryCardDisplay: CardDisplay | null = null; +const galleryCardEditor = document.getElementById('galleryCardEditor') as HTMLButtonElement; +const galleryCardEditorGridButtons: HTMLButtonElement[][] = [ ]; +const galleryCardEditorSpecialCostButtons: HTMLButtonElement[] = [ ]; +const galleryCardEditorSpecialCost = document.getElementById('galleryCardEditorSpecialCost') as HTMLElement; +const galleryCardEditorSpecialCostDefaultBox = document.getElementById('galleryCardEditorSpecialCostDefaultBox') as HTMLInputElement; +const galleryCardEditorEditButton = document.getElementById('galleryCardEditorEditButton') as HTMLButtonElement; +const galleryCardEditorSubmitButton = document.getElementById('galleryCardEditorSubmitButton') as HTMLButtonElement; +const galleryCardEditorCancelButton = document.getElementById('galleryCardEditorCancelButton') as HTMLButtonElement; + +const ownedCards: {[key: number]: number} = { 6: 0, 34: 0, 159: 0, 13: 0, 45: 0, 137: 0, 22: 0, 52: 0, 141: 0, 28: 0, 55: 0, 103: 0, 40: 0, 56: 0, 92: 0 }; +let lastGridButton: HTMLButtonElement | null = null; +let customCardSpecialCost = 0; + +function showCardList() { + showPage('gallery'); +} + +function galleryInitCardDatabase(cards: Card[]) { + for (const card of cards.concat(customCards)) { + const display = new CardDisplay(card, 1, 'button'); + + const cardNumber = document.createElement('div'); + cardNumber.className = 'cardNumber'; + cardNumber.innerText = card.number >= 0 ? `No. ${card.number}` : 'Upcoming'; + display.element.insertBefore(cardNumber, display.element.firstChild); + + galleryCardList.add(display); + + display.element.addEventListener('click', () => { + if (galleryChecklistBox.checked) { + if (card.number in ownedCards) { + delete ownedCards[card.number]; + display.element.classList.add('unowned'); + } else { + ownedCards[card.number] = 0; + display.element.classList.remove('unowned'); + } + updateBitsToComplete(); + saveChecklist(); + } else { + const existingEl = galleryCardDialog.firstElementChild; + if (existingEl && existingEl.tagName != 'FORM') + galleryCardDialog.removeChild(existingEl); + const display = new CardDisplay(card, 1); + galleryCardDisplay = display; + galleryCardDialog.insertBefore(display.element, galleryCardDialog.firstChild); + + galleryCardEditor.parentElement?.removeChild(galleryCardEditor); + display.element.appendChild(galleryCardEditor); + galleryCardEditor.hidden = true; + display.element.classList.remove('editing'); + galleryCardEditorEditButton.hidden = false; + galleryCardEditorSubmitButton.hidden = true; + galleryCardEditorCancelButton.innerText = 'Close'; + + galleryCardDialog.showModal(); + } + }); + } + updateBitsToComplete(); +} + +galleryBackButton.addEventListener('click', e => { + e.preventDefault(); + showPage('preGame'); + + if (canPushState) { + try { + history.pushState(null, '', '.'); + } catch { + canPushState = false; + } + } + if (location.hash) + location.hash = ''; +}); + +galleryChecklistBox.addEventListener('change', () => { + if (galleryChecklistBox.checked) { + for (const cardDisplay of galleryCardList.cardButtons) { + if (cardDisplay.card.number in ownedCards) + cardDisplay.element.classList.remove('unowned'); + else + cardDisplay.element.classList.add('unowned'); + } + } else { + for (const cardDisplay of galleryCardList.cardButtons) + cardDisplay.element.classList.remove('unowned'); + } +}); + +function updateBitsToComplete() { + if (!cardDatabase.cards) throw new Error('Card database not loaded'); + let bitsRequired = 0; + for (const card of cardDatabase.cards) { + if (card.number in ownedCards) continue; + switch (card.rarity) { + case Rarity.Fresh: bitsRequired += 40; break; + case Rarity.Rare: bitsRequired += 15; break; + default: bitsRequired += 5; break; + } + } + bitsToCompleteField.innerText = bitsRequired.toString(); +} + +{ + const customCardsString = localStorage.getItem('customCards'); + if (customCardsString) { + for (const card of JSON.parse(customCardsString)) { + customCards.push(Card.fromJson(card)); + } + } + + for (let x = 0; x < 8; x++) { + const row = [ ]; + for (let y = 0; y < 8; y++) { + const button = document.createElement('button'); + button.type = 'button'; + button.dataset.state = Space.Empty.toString(); + button.dataset.x = x.toString(); + button.dataset.y = y.toString(); + button.addEventListener('click', () => { + const state = parseInt(button.dataset.state ?? '0'); + switch (state) { + case Space.Empty: + button.dataset.state = Space.Ink1.toString(); + break; + case Space.Ink1: + if (lastGridButton == button) { + // When a space is pressed twice, move the special space there. + for (const row of galleryCardEditorGridButtons) { + for (const button2 of row) { + if (button2 == button) + button2.dataset.state = Space.SpecialInactive1.toString(); + else if (button2.dataset.state == Space.SpecialInactive1.toString()) + button2.dataset.state = Space.Ink1.toString(); + } + } + } else + button.dataset.state = Space.Empty.toString(); + break; + default: + button.dataset.state = Space.Empty.toString(); + break; + } + lastGridButton = button; + + updateCustomCardSize(); + }); + row.push(button); + } + galleryCardEditorGridButtons.push(row); + } + + const galleryCardEditorGrid = document.getElementById('galleryCardEditorGrid')!; + for (let y = 0; y < 8; y++) { + for (let x = 0; x < 8; x++) { + galleryCardEditorGrid.appendChild(galleryCardEditorGridButtons[x][y]); + } + } + + // Load the saved checklist. + const checklistString = localStorage.getItem('checklist'); + if (checklistString) { + const cards = JSON.parse(checklistString); + Object.assign(ownedCards, cards); + } +} + +for (let i = 0; i < 10; i++) { + const button = document.createElement('button'); + const n = i < 5 ? i + 6 : i - 4; + button.dataset.value = n.toString(); + galleryCardEditorSpecialCost.appendChild(button); + galleryCardEditorSpecialCostButtons.push(button); + button.addEventListener('click', () => { + customCardSpecialCost = n; + galleryCardEditorSpecialCostDefaultBox.checked = false; + updateCustomCardSpecialCost(); + }); +} + +function updateCustomCardSize() { + let size = 0, hasSpecialSpace = false; + for (const row of galleryCardEditorGridButtons) { + for (const button2 of row) { + switch (parseInt(button2.dataset.state!)) { + case Space.Ink1: + size++; + break; + case Space.SpecialInactive1: + size++; + hasSpecialSpace = true; + break; + } + } + } + + galleryCardDisplay!.setSize(size); + if (galleryCardEditorSpecialCostDefaultBox.checked) { + customCardSpecialCost = + size <= 3 ? 1 + : size <= 5 ? 2 + : size <= 8 ? 3 + : size <= 11 ? 4 + : size <= 15 ? 5 + : 6; + if (!hasSpecialSpace && customCardSpecialCost > 3) + customCardSpecialCost = 3; + updateCustomCardSpecialCost(); + } +} + +function updateCustomCardSpecialCost() { + galleryCardDisplay?.setSpecialCost(customCardSpecialCost); + for (let i = 0; i < galleryCardEditorSpecialCostButtons.length; i++) { + const button = galleryCardEditorSpecialCostButtons[i]; + if (parseInt(button.dataset.value!) <= customCardSpecialCost) + button.classList.add('active'); + else + button.classList.remove('active'); + } +} + +galleryCardEditorSpecialCostDefaultBox.addEventListener('change', () => { + if (galleryCardEditorSpecialCostDefaultBox.checked) + updateCustomCardSize(); +}); + +galleryCardEditorEditButton.addEventListener('click', () => { + galleryCardEditor.hidden = false; + galleryCardDisplay?.element.classList.add('editing'); + galleryCardEditorEditButton.hidden = true; + galleryCardEditorSubmitButton.hidden = false; + galleryCardEditorCancelButton.innerText = 'Cancel'; +}); diff --git a/TableturfBattleClient/src/Pages/GamePage.ts b/TableturfBattleClient/src/Pages/GamePage.ts index 4b01bef..5e176c0 100644 --- a/TableturfBattleClient/src/Pages/GamePage.ts +++ b/TableturfBattleClient/src/Pages/GamePage.ts @@ -47,7 +47,7 @@ playContainers.sort((a, b) => parseInt(a.dataset.index || '0') - parseInt(b.data const testControls = document.getElementById('testControls')!; const testDeckList = document.getElementById('testDeckList')!; -const testAllCardsList = CardList.fromId('testAllCardsList', 'testAllCardsListSortBox', 'testAllCardsListFilterBox'); +const testAllCardsList = CardList.fromId('testAllCardsList', 'testAllCardsListSortBox', 'testAllCardsListFilterBox'); const testPlacementList = document.getElementById('testPlacementList')!; const testDeckButton = CheckButton.fromId('testDeckButton'); const testDeckContainer = document.getElementById('testDeckContainer')!; diff --git a/TableturfBattleClient/src/Pages/PreGamePage.ts b/TableturfBattleClient/src/Pages/PreGamePage.ts index 470563d..85bd178 100644 --- a/TableturfBattleClient/src/Pages/PreGamePage.ts +++ b/TableturfBattleClient/src/Pages/PreGamePage.ts @@ -5,6 +5,7 @@ const joinGameButton = document.getElementById('joinGameButton')!; const nameBox = document.getElementById('nameBox') as HTMLInputElement; const gameIDBox = document.getElementById('gameIDBox') as HTMLInputElement; const preGameDeckEditorButton = document.getElementById('preGameDeckEditorButton') as HTMLLinkElement; +const preGameGalleryButton = document.getElementById('preGameGalleryButton') as HTMLLinkElement; const preGameLoadingSection = document.getElementById('preGameLoadingSection')!; const preGameLoadingLabel = document.getElementById('preGameLoadingLabel')!; const preGameReplayButton = document.getElementById('preGameReplayButton') as HTMLLinkElement; @@ -301,6 +302,12 @@ preGameDeckEditorButton.addEventListener('click', e => { setUrl('deckeditor'); }); +preGameGalleryButton.addEventListener('click', e => { + e.preventDefault(); + showCardList(); + setUrl('cardlist'); +}); + preGameSettingsButton.addEventListener('click', e => { e.preventDefault(); optionsColourGoodBox.value = userConfig.goodColour ?? 'yellow'; @@ -419,6 +426,8 @@ window.addEventListener('popstate', () => { } } -if (!canPushState) +if (!canPushState) { preGameDeckEditorButton.href = '#deckeditor'; + preGameGalleryButton.href = '#cardlist'; +} setLoadingMessage('Loading game data...'); diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts index 615c6ec..a27385b 100644 --- a/TableturfBattleClient/src/app.ts +++ b/TableturfBattleClient/src/app.ts @@ -18,6 +18,7 @@ let initialiseCallback: (() => void) | null = null; let canPushState = isSecureContext && location.protocol != 'file:'; const decks = [ new SavedDeck('Starter Deck', 0, [ 6, 34, 159, 13, 45, 137, 22, 52, 141, 28, 55, 103, 40, 56, 92 ], new Array(15).fill(1), true) ]; +const customCards: Card[] = [ ]; let selectedDeck: SavedDeck | null = null; let editingDeck = false; let deckModified = false; @@ -50,6 +51,7 @@ function onInitialise(callback: () => void) { function initCardDatabase(cards: Card[]) { deckEditInitCardDatabase(cards); + galleryInitCardDatabase(cards); if (!cards.find(c => c.number < 0)) { gameSetupAllowUpcomingCardsBox.parentElement!.hidden = true; lobbyAllowUpcomingCardsBox.parentElement!.hidden = true; @@ -63,7 +65,7 @@ function initStageDatabase(stages: Stage[]) { // Pages const pages = new Map(); -for (var id of [ 'noJS', 'preGame', 'lobby', 'game', 'deckList', 'deckEdit' ]) { +for (var id of [ 'noJS', 'preGame', 'lobby', 'game', 'deckList', 'deckEdit', 'gallery' ]) { let el = document.getElementById(`${id}Page`) as HTMLDivElement; if (!el) throw new EvalError(`Element not found: ${id}Page`); pages.set(id, el); @@ -484,6 +486,8 @@ function processUrl() { clearGame(); if (location.pathname.endsWith('/deckeditor') || location.hash == '#deckeditor') onInitialise(showDeckList); + else if (location.pathname.endsWith('/cardlist') || location.hash == '#cardlist') + onInitialise(showCardList); else { showPage('preGame'); if (location.pathname.endsWith('/help') || location.hash == '#help') diff --git a/TableturfBattleClient/tableturf.css b/TableturfBattleClient/tableturf.css index 294ae27..ba4aad6 100644 --- a/TableturfBattleClient/tableturf.css +++ b/TableturfBattleClient/tableturf.css @@ -501,10 +501,6 @@ dialog::backdrop { .cardNumber { display: none; -} - -.cardListGrid .cardButton:hover .cardNumber { - display: block; position: absolute; background: grey; border: 1px solid black; @@ -515,6 +511,10 @@ dialog::backdrop { z-index: 1; } +.cardListGrid .cardButton:hover .cardNumber { + display: block; +} + .cardName { text-align: center; line-height: 1.25em; @@ -605,6 +605,7 @@ dialog::backdrop { } .playContainer .card { animation: 0.1s ease-out forwards flipCardIn; + height: 100%; } .playContainer .card.preview { animation: none; @@ -1810,6 +1811,142 @@ button.dragging { #deckSleevesList label:nth-of-type(24) { background-position: -700% -200%; } #deckSleevesList label:nth-of-type(25) { background-position: 0% -300%; } +/* Card list */ + +#galleryPage:not([hidden]) { + height: 100vh; + display: flex; + flex-flow: column; +} + +#galleryCardList { + display: grid; + grid-template-columns: repeat(auto-fill, 10em); + grid-auto-rows: auto; + gap: 0.5em; + justify-content: space-evenly; +} + +#galleryCardList .card { + height: 14rem; + border: none; + background: none; + padding: 0; + transition: transform 0.25s ease; +} + +#galleryCardList .card.unowned svg { + opacity: 0.333; +} + +#galleryCardList .card:hover { + transform: scale(1.1); +} + +#galleryCardList .card:hover .cardNumber { + display: block; + font-size: 1rem; + top: -0.5em; +} + +#galleryCardDialog > .card { + height: min(75vh, 100vw); +} + +#galleryCardEditor { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + font-size: calc(min(75vh, 100vw) * 0.08); +} + +#galleryCardEditorName { + position: absolute; + left: 5%; + top: 8%; + width: 90%; + height: 20%; + color: black; + background: none; + border: none; + font: inherit; + line-height: 1.25em; + text-align: center; +} + +.card.editing :is(.cardDisplayName, .cardGrid) { + display: none; +} + +#galleryCardEditorGrid { + position: absolute; + left: 20%; + right: 20%; + top: 30%; + bottom: 30%; + display: grid; + grid-template-columns: repeat(8, 1fr); + grid-template-rows: repeat(8, 1fr); +} + +#galleryCardEditorGrid button { + position: relative; + border: none; +} +:is(#galleryCardEditorGrid, #galleryCardEditorSpecialCost) button:is(:hover, :focus)::before { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: #ffffff80; + content: ''; +} + +#galleryCardEditorGrid button[data-state="0"] { + border: 1px solid grey; + background: #00000080; +} +#galleryCardEditorGrid button[data-state="4"] { + border: 1px solid grey; + background: url('assets/InkOverlay.png') center/cover, var(--primary-colour-1); +} +#galleryCardEditorGrid button[data-state="8"] { + border: 1px solid grey; + background: url('assets/SpecialOverlay.png') center/cover, var(--special-colour-1); +} + +#galleryCardEditorSpecialCost { + display: none; + position: absolute; + grid-template-columns: repeat(5, 1fr); + left: 26.8%; + top: 86.8%; + gap: 0.072em; +} + +#galleryCardEditorSpecialCost button { + width: 1.7em; + height: 1.7em; + border: none; + background: #00000080; + position: relative; +} +#galleryCardEditorSpecialCost button.active { + background: url('assets/SpecialOverlay.png') center/cover, var(--special-colour-1); +} + +#galleryCardEditorSpecialCost label { + position: absolute; + left: 0; + top: 105%; + font: 33% 'Splatoon 2', sans-serif; + background: grey; + padding: 0 1em; +} + /* Help */ #helpControls { diff --git a/TableturfBattleServer/Program.cs b/TableturfBattleServer/Program.cs index 0cf3a39..99f214c 100644 --- a/TableturfBattleServer/Program.cs +++ b/TableturfBattleServer/Program.cs @@ -93,7 +93,7 @@ internal partial class Program { private static void HttpServer_OnRequest(object? sender, HttpRequestEventArgs e) { e.Response.AppendHeader("Access-Control-Allow-Origin", "*"); if (!e.Request.RawUrl.StartsWith("/api/")) { - var path = e.Request.RawUrl == "/" || e.Request.RawUrl.StartsWith("/deckeditor") || e.Request.RawUrl.StartsWith("/game/") || e.Request.RawUrl.StartsWith("/replay/") + var path = e.Request.RawUrl == "/" || e.Request.RawUrl.StartsWith("/deckeditor") || e.Request.RawUrl.StartsWith("/cardlist") || e.Request.RawUrl.StartsWith("/game/") || e.Request.RawUrl.StartsWith("/replay/") ? "index.html" : HttpUtility.UrlDecode(e.Request.RawUrl[1..]); if (e.TryReadFile(path, out var bytes))