diff --git a/app/game-frontend/e2e/commandArguments.spec.ts b/app/game-frontend/e2e/commandArguments.spec.ts index e8d3265c..2991c6ac 100644 --- a/app/game-frontend/e2e/commandArguments.spec.ts +++ b/app/game-frontend/e2e/commandArguments.spec.ts @@ -465,6 +465,7 @@ test('enters general and nation command arguments and sends exact values', async const requests = await install(page); await page.setViewportSize({ width: 1200, height: 900 }); await page.goto('/'); + await expect(page.getByTestId('current-city-marker')).toHaveCount(0); await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click(); await page.getByTestId('command-picker').getByRole('button', { name: /화계/ }).click(); @@ -474,6 +475,12 @@ test('enters general and nation command arguments and sends exact values', async await expect(form.getByTestId('command-argument-guidance')).toContainText('선택한 도시에 화계를 실행합니다.'); await expect(form.getByTestId('command-map-target-summary')).toContainText('현재 도시에서 0칸'); const commandMap = form.getByTestId('command-argument-map'); + const currentCityMarker = commandMap.getByTestId('current-city-marker'); + const selectionStatus = form.getByTestId('command-map-selection-status'); + await expect(currentCityMarker).toHaveText('현재'); + await expect(currentCityMarker).toHaveAttribute('aria-label', '현재 도시 업'); + await expect(selectionStatus).toContainText('현재 도시업'); + await expect(selectionStatus).toContainText('선택 도시업'); const mapCities = commandMap.locator('.city-base'); await expect(mapCities).toHaveCount(2); await expect(commandMap.locator('.city-bg')).toHaveCount(2); @@ -515,11 +522,17 @@ test('enters general and nation command arguments and sends exact values', async await mapCities.nth(1).click(); await expect(form.locator('select')).toHaveValue('2'); await expect(form.getByTestId('command-map-target-summary')).toContainText('현재 도시에서 1칸'); + await expect(selectionStatus).toContainText('현재 도시업'); + await expect(selectionStatus).toContainText('선택 도시허창'); + await expect(currentCityMarker).toHaveAttribute('aria-label', '현재 도시 업'); + await expect(mapCities.nth(0)).toHaveClass(/mine/); + await expect(mapCities.nth(1)).toHaveClass(/selected/); await mapCities.nth(1).hover(); expect(await mapCities.nth(1).evaluate((element) => getComputedStyle(element).cursor)).toBe('pointer'); await mapCities.nth(1).focus(); await expect(mapCities.nth(1)).toBeFocused(); await expect(page).toHaveURL(/\/$/); + await commandMap.screenshot({ path: test.info().outputPath('main-city-current-marker-desktop.png') }); const mapGeometry = await form.getByTestId('command-argument-map').evaluate((element) => { const area = element.querySelector('.map-area')!; const rect = area.getBoundingClientRect(); @@ -565,7 +578,7 @@ test('enters general and nation command arguments and sends exact values', async expect(Number.parseFloat(geometry.fontSize)).toBeGreaterThanOrEqual(10); }); -test('shows Ref recruitment details and preserves the 1000px desktop and 500px mobile information layouts', async ({ +test('uses a Ref-style full recruitment page without horizontal overflow on desktop or mobile', async ({ page, }, testInfo) => { const requests = await install(page); @@ -576,7 +589,10 @@ test('shows Ref recruitment details and preserves the 1000px desktop and 500px m let picker = page.getByTestId('command-picker'); await picker.getByRole('button', { name: '내정', exact: true }).click(); await picker.getByRole('button', { name: '징병', exact: true }).click(); - let form = picker.getByTestId('recruitment-command-form'); + await expect(picker).toHaveAttribute('role', 'dialog'); + await expect(picker).toHaveAttribute('aria-modal', 'true'); + await expect(picker.getByRole('button', { name: '명령 입력 닫기', exact: true })).toBeFocused(); + const form = picker.getByTestId('recruitment-command-form'); await expect(form).toContainText('현재 기술력 : 1등급'); await expect(form).toContainText('공격'); await expect(form).toContainText('방어'); @@ -606,19 +622,23 @@ test('shows Ref recruitment details and preserves the 1000px desktop and 500px m }) : null; return { + overlay: element.closest('[data-testid="command-picker"]')?.getBoundingClientRect().toJSON(), formWidth: element.getBoundingClientRect().width, rowHeight: row?.getBoundingClientRect().height ?? 0, infoWidth: info?.getBoundingClientRect().width ?? 0, imageNaturalSize: naturalSize, scrollWidth: element.scrollWidth, clientWidth: element.clientWidth, + bodyOverflow: getComputedStyle(document.body).overflow, }; }); - expect(desktopGeometry.formWidth).toBeCloseTo(986, 0); + expect(desktopGeometry.overlay).toMatchObject({ x: 0, y: 0, width: 1200, height: 900 }); + expect(desktopGeometry.formWidth).toBe(1000); expect(desktopGeometry.rowHeight).toBeGreaterThanOrEqual(64); expect(desktopGeometry.infoWidth).toBeCloseTo(250, 0); expect(desktopGeometry.imageNaturalSize).toEqual({ width: 128, height: 128 }); expect(desktopGeometry.scrollWidth).toBe(desktopGeometry.clientWidth); + expect(desktopGeometry.bodyOverflow).toBe('hidden'); const infantry = form.getByRole('button', { name: '보병 선택 가능', exact: true }); await infantry.getByRole('button', { name: '절반', exact: true }).click(); @@ -628,49 +648,116 @@ test('shows Ref recruitment details and preserves the 1000px desktop and 500px m expect(JSON.stringify(requests)).toContain('"crewType":1100'); expect(JSON.stringify(requests)).toContain('"amount":3500'); - await page.setViewportSize({ width: 390, height: 844 }); + await page.setViewportSize({ width: 500, height: 844 }); await page.getByRole('button', { name: '2턴 명령 입력', exact: true }).click(); picker = page.getByTestId('command-picker'); await picker.getByRole('button', { name: '내정', exact: true }).click(); await picker.getByRole('button', { name: '징병', exact: true }).click(); - form = picker.getByTestId('recruitment-command-form'); - await page.evaluate(() => window.scrollTo(0, 0)); - const mobileGeometry = await form.evaluate((element) => { - const row = element.querySelector('.crew-row'); - const image = row?.querySelector('.crew-image'); - const info = row?.querySelector('.crew-info'); - const selectedPanel = element.querySelector('.mobile-selected-panel'); + await expect(picker.getByRole('button', { name: '명령 입력 닫기', exact: true })).toBeFocused(); + const referenceWidthGeometry = await picker.evaluate((element) => { + const formElement = element.querySelector('[data-testid="recruitment-command-form"]')!; + const row = formElement.querySelector('.crew-row')!; + const selectedPanel = formElement.querySelector('.mobile-selected-panel')!; return { - formLeft: element.getBoundingClientRect().left, - formWidth: element.getBoundingClientRect().width, - rowWidth: row?.getBoundingClientRect().width ?? 0, - rowHeight: row?.getBoundingClientRect().height ?? 0, - imageWidth: image?.getBoundingClientRect().width ?? 0, - infoWidth: info?.getBoundingClientRect().width ?? 0, - selectedDisplay: selectedPanel ? getComputedStyle(selectedPanel).display : '', + overlayWidth: element.getBoundingClientRect().width, + rowWidth: row.getBoundingClientRect().width, + rowGridColumns: getComputedStyle(row).gridTemplateColumns, + selectedWidth: selectedPanel.getBoundingClientRect().width, + selectedGridColumns: getComputedStyle(selectedPanel).gridTemplateColumns, scrollWidth: element.scrollWidth, clientWidth: element.clientWidth, - documentScrollWidth: document.documentElement.scrollWidth, }; }); - expect(mobileGeometry.formLeft).toBe(0); - expect(mobileGeometry.formWidth).toBe(500); - expect(mobileGeometry.rowWidth).toBe(500); + expect(referenceWidthGeometry).toMatchObject({ + overlayWidth: 500, + rowWidth: 500, + rowGridColumns: '64px 76px 30px 30px 30px 270px', + selectedWidth: 500, + selectedGridColumns: '64px 76px 270px 90px', + scrollWidth: 500, + clientWidth: 500, + }); + + await page.setViewportSize({ width: 390, height: 844 }); + const mobileGeometry = await picker.evaluate((element) => { + const formElement = element.querySelector('[data-testid="recruitment-command-form"]')!; + const row = formElement.querySelector('.crew-row'); + const image = row?.querySelector('.crew-image'); + const info = row?.querySelector('.crew-info'); + const selectedPanel = formElement.querySelector('.mobile-selected-panel'); + const rect = element.getBoundingClientRect(); + const bottomElement = document.elementFromPoint(window.innerWidth / 2, window.innerHeight - 1); + return { + overlay: rect.toJSON(), + overlayCoversBottom: bottomElement instanceof Element && element.contains(bottomElement), + formLeft: formElement.getBoundingClientRect().left, + formWidth: formElement.getBoundingClientRect().width, + rowWidth: row?.getBoundingClientRect().width ?? 0, + rowHeight: row?.getBoundingClientRect().height ?? 0, + rowGridColumns: row ? getComputedStyle(row).gridTemplateColumns : '', + rowGridRows: row ? getComputedStyle(row).gridTemplateRows : '', + imageWidth: image?.getBoundingClientRect().width ?? 0, + infoWidth: info?.getBoundingClientRect().width ?? 0, + selectedWidth: selectedPanel?.getBoundingClientRect().width ?? 0, + selectedDisplay: selectedPanel ? getComputedStyle(selectedPanel).display : '', + actionsBottom: element.querySelector('.picker-actions')?.getBoundingClientRect().bottom ?? 0, + viewportHeight: window.innerHeight, + scrollWidth: element.scrollWidth, + clientWidth: element.clientWidth, + bodyOverflow: getComputedStyle(document.body).overflow, + }; + }); + expect(mobileGeometry.overlay).toMatchObject({ x: 0, y: 0, width: 390, height: 844 }); + expect(mobileGeometry.overlayCoversBottom).toBe(true); + expect(mobileGeometry.rowGridColumns).toBe('64px 76px 30px 30px 30px 160px'); + expect(mobileGeometry).toMatchObject({ + formLeft: 0, + formWidth: 390, + rowWidth: 390, + imageWidth: 64, + selectedWidth: 390, + selectedDisplay: 'grid', + actionsBottom: 844, + viewportHeight: 844, + bodyOverflow: 'hidden', + }); expect(mobileGeometry.rowHeight).toBeGreaterThanOrEqual(64); - expect(mobileGeometry.rowHeight).toBeLessThanOrEqual(66); - expect(mobileGeometry.imageWidth).toBe(64); - expect(mobileGeometry.infoWidth).toBe(270); - expect(mobileGeometry.selectedDisplay).toBe('grid'); + expect(mobileGeometry.infoWidth).toBeGreaterThanOrEqual(150); expect(mobileGeometry.scrollWidth).toBe(mobileGeometry.clientWidth); - expect(mobileGeometry.documentScrollWidth).toBeGreaterThanOrEqual(500); + + await page.setViewportSize({ width: 390, height: 360 }); + await picker.evaluate((element) => (element.scrollTop = 160)); + const stickyGeometry = await picker.evaluate((element) => { + const header = element.querySelector(':scope > header')!; + const listFront = element.querySelector('.recruitment-list-front')!; + const actions = element.querySelector('.picker-actions')!; + return { + scrollTop: element.scrollTop, + headerTop: header.getBoundingClientRect().top, + listFrontTop: listFront.getBoundingClientRect().top, + actionsBottom: actions.getBoundingClientRect().bottom, + viewportHeight: window.innerHeight, + }; + }); + expect(stickyGeometry.scrollTop).toBeGreaterThan(0); + expect(stickyGeometry.headerTop).toBe(0); + expect(stickyGeometry.listFrontTop).toBeGreaterThanOrEqual(44); + expect(stickyGeometry.actionsBottom).toBe(stickyGeometry.viewportHeight); + + await page.setViewportSize({ width: 390, height: 844 }); + await picker.evaluate((element) => (element.scrollTop = 0)); await page.screenshot({ path: testInfo.outputPath('recruitment-mobile.png') }); await picker.getByRole('button', { name: '명령 다시 선택', exact: true }).click(); + await expect.poll(() => page.evaluate(() => getComputedStyle(document.body).overflow)).not.toBe('hidden'); await picker.getByRole('button', { name: '내정', exact: true }).click(); await picker.getByRole('button', { name: '모병', exact: true }).click(); const mercenaryForm = picker.getByTestId('recruitment-command-form'); await expect(mercenaryForm).toContainText('모병은 가격 2배의 자금이 소요됩니다.'); await expect(mercenaryForm.locator('.mobile-selected-panel output')).toHaveText('1,346금'); + await page.keyboard.press('Escape'); + await expect(picker).toHaveCount(0); + await expect.poll(() => page.evaluate(() => getComputedStyle(document.body).overflow)).not.toBe('hidden'); }); test('uses the map to choose a nation target in the chief command window', async ({ page }) => { @@ -685,6 +772,9 @@ test('uses the map to choose a nation target in the chief command window', async await expect(form.getByTestId('command-argument-guidance')).toContainText('초반 제한'); await form.getByTestId('command-argument-map').locator('.city-base').nth(1).click(); await expect(form.locator('select')).toHaveValue('2'); + await expect(form.getByTestId('command-map-selection-status')).toContainText('현재 도시업'); + await expect(form.getByTestId('command-map-selection-status')).toContainText('선택 국가적국'); + await expect(form.getByTestId('current-city-marker')).toHaveAttribute('aria-label', '현재 도시 업'); await expect(form.getByTestId('command-map-target-summary')).toContainText('수도 허창 · 도시 1개'); await expect(page).toHaveURL(/\/che\/chief-center$/); await page.screenshot({ path: test.info().outputPath('chief-nation-map-option.png'), fullPage: true }); @@ -697,6 +787,16 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click(); const picker = page.getByTestId('command-picker'); await picker.getByRole('button', { name: /화계/ }).click(); + const mobileForm = picker.getByTestId('command-argument-form'); + const mobileMap = mobileForm.getByTestId('command-argument-map'); + const mobileCities = mobileMap.locator('.city-base'); + await mobileCities.nth(1).click(); + await expect(mobileForm.locator('select')).toHaveValue('2'); + await expect(mobileForm.getByTestId('command-map-selection-status')).toContainText('현재 도시업'); + await expect(mobileForm.getByTestId('command-map-selection-status')).toContainText('선택 도시허창'); + await expect(mobileMap.getByTestId('current-city-marker')).toHaveAttribute('aria-label', '현재 도시 업'); + await expect(mobileCities.nth(0)).toHaveClass(/mine/); + await expect(mobileCities.nth(1)).toHaveClass(/selected/); const geometry = await picker.evaluate((element) => { const map = element.querySelector('[data-testid="command-argument-map"] .map-area')!; const pickerRect = element.getBoundingClientRect(); @@ -708,6 +808,19 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag pickerScrollWidth: element.scrollWidth, mapWidth: mapRect.width, mapHeight: mapRect.height, + marker: (() => { + const marker = element.querySelector('[data-testid="current-city-marker"]')!; + const rect = marker.getBoundingClientRect(); + const style = getComputedStyle(marker); + return { + left: rect.left, + right: rect.right, + top: rect.top, + bottom: rect.bottom, + pointerEvents: style.pointerEvents, + borderColor: style.borderColor, + }; + })(), }; }); expect(geometry.pickerX).toBeGreaterThanOrEqual(0); @@ -716,6 +829,12 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag expect(geometry.pickerScrollWidth).toBeLessThanOrEqual(geometry.pickerWidth); expect(geometry.mapWidth).toBeGreaterThan(470); expect(geometry.mapHeight / geometry.mapWidth).toBeCloseTo(5 / 7, 2); + expect(geometry.marker.left).toBeGreaterThanOrEqual(0); + expect(geometry.marker.right).toBeLessThanOrEqual(500); + expect(geometry.marker.top).toBeGreaterThanOrEqual(0); + expect(geometry.marker.bottom).toBeLessThanOrEqual(900); + expect(geometry.marker.pointerEvents).toBe('none'); + expect(geometry.marker.borderColor).toBe('rgb(130, 207, 255)'); await page.screenshot({ path: test.info().outputPath('main-city-map-option-mobile.png'), fullPage: true }); }); diff --git a/app/game-frontend/e2e/mainNavigation.spec.ts b/app/game-frontend/e2e/mainNavigation.spec.ts index 47b1c56b..c93bf20d 100644 --- a/app/game-frontend/e2e/mainNavigation.spec.ts +++ b/app/game-frontend/e2e/mainNavigation.spec.ts @@ -35,6 +35,7 @@ type NavigationFixture = { currentYear?: number; currentMonth?: number; scenarioTitle?: string; + lastExecuted?: string | null; latestVote?: { id: number; title: string; hasVoted: boolean } | null; globalRecords?: Array<{ id: number; text: string }>; generalRecords?: Array<{ id: number; text: string }>; @@ -441,7 +442,7 @@ const installFixture = async (page: Page, state: NavigationFixture) => { onlineNations: '위(1)', onlineGenerals: '메뉴검증장수', nationNotice: '

국가 방침

', - lastExecuted: null, + lastExecuted: state.lastExecuted === undefined ? '2026-08-13T00:05:06.000Z' : state.lastExecuted, latestVote: state.latestVote === undefined ? { id: 9, title: '메뉴 설문', hasVoted: false } @@ -585,6 +586,10 @@ const persistArtifact = async (page: Page, name: string) => { globalPopup: describe('#mobile-global-menu'), nationPopup: describe('#mobile-nation-menu'), quickPopup: describe('#mobile-quick-menu'), + activityStatus: describe('.activity-status'), + executionStatus: describe('.execution-status'), + tournamentStatus: describe('.tournament-status'), + voteStatus: describe('.vote-status'), commandMenu: describe('.reserved-command-editor details[open] .menu-items'), commandDividers: [ ...document.querySelectorAll('.reserved-command-editor details[open] .menu-divider'), @@ -617,6 +622,7 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro stage: 1, npcMode: 1, scenarioTitle: '메인 화면 검증 시나리오', + lastExecuted: '2026-08-13T00:05:06.000Z', generalMeCalls: 0, operations: [], }; @@ -633,31 +639,35 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro await expect(page.getByRole('heading', { name: '메인 화면 검증 시나리오', exact: true })).toHaveCount(1); await expect(page.locator('.game-shell__subtitle')).toHaveText('185년 1월 · 턴 10분'); await expect(page.locator('.game-shell__subtitle')).not.toContainText('메인 화면 검증 시나리오'); + await expect(page.locator('.execution-status')).toHaveText('동작 시각: 08-13 09:05'); await expect(page.locator('.tournament-status')).toHaveText('토너먼트: 참가 모집중'); await expect(page.locator('.vote-status')).toHaveText('설문: 메뉴 설문'); const headerStatusGeometry = await page.locator('.main-page').evaluate((element) => { const title = element.querySelector('.game-shell__title'); const subtitle = element.querySelector('.game-shell__subtitle'); const activity = element.querySelector('.activity-status'); + const execution = element.querySelector('.execution-status'); const tournament = element.querySelector('.tournament-status'); const survey = element.querySelector('.vote-status'); - if (!title || !subtitle || !activity || !tournament || !survey) { + if (!title || !subtitle || !activity || !execution || !tournament || !survey) { throw new Error('main header status geometry is incomplete'); } return { title: title.getBoundingClientRect().toJSON(), subtitle: subtitle.getBoundingClientRect().toJSON(), activity: activity.getBoundingClientRect().toJSON(), + execution: execution.getBoundingClientRect().toJSON(), tournament: tournament.getBoundingClientRect().toJSON(), survey: survey.getBoundingClientRect().toJSON(), activityColumns: getComputedStyle(activity).gridTemplateColumns, }; }); expect(headerStatusGeometry.subtitle.y).toBeGreaterThanOrEqual(headerStatusGeometry.title.bottom); - expect(headerStatusGeometry.activity.width).toBeCloseTo(666.67, 0); + expect(headerStatusGeometry.activity.width).toBeCloseTo(1000, 0); + expect(headerStatusGeometry.execution.width).toBeCloseTo(333.33, 0); expect(headerStatusGeometry.tournament.width).toBeCloseTo(333.33, 0); expect(headerStatusGeometry.survey.width).toBeCloseTo(333.33, 0); - expect(headerStatusGeometry.activityColumns.split(' ')).toHaveLength(2); + expect(headerStatusGeometry.activityColumns.split(' ')).toHaveLength(3); const tournamentStatusLink = page.locator('.tournament-status a'); const surveyStatusLink = page.locator('.vote-status a'); await tournamentStatusLink.hover(); @@ -1265,6 +1275,7 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy stage: 6, npcMode: 1, scenarioTitle: '모바일 검증 시나리오', + lastExecuted: null, latestVote: null, generalMeCalls: 0, operations: [], @@ -1286,25 +1297,27 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy await page.setViewportSize({ width: 500, height: 900 }); await expect(page.getByRole('heading', { name: '모바일 검증 시나리오', exact: true })).toHaveCount(1); await expect(page.locator('.game-shell__subtitle')).toHaveText('185년 1월 · 턴 10분'); + await expect(page.locator('.execution-status')).toHaveText('동작 시각: 기록 없음'); await expect(page.locator('.tournament-status')).toHaveText('토너먼트: 베팅 진행중'); await expect(page.locator('.vote-status')).toHaveText('설문: 진행 중인 설문 없음'); const activityGeometry = await page.locator('.activity-status').evaluate((element) => { + const execution = element.querySelector('.execution-status'); const tournament = element.querySelector('.tournament-status'); const survey = element.querySelector('.vote-status'); - if (!tournament || !survey) throw new Error('activity status is incomplete'); + if (!execution || !tournament || !survey) throw new Error('activity status is incomplete'); return { width: element.getBoundingClientRect().width, + executionWidth: execution.getBoundingClientRect().width, tournamentWidth: tournament.getBoundingClientRect().width, surveyWidth: survey.getBoundingClientRect().width, columns: getComputedStyle(element).gridTemplateColumns, }; }); - expect(activityGeometry).toMatchObject({ - width: 500, - tournamentWidth: 250, - surveyWidth: 250, - columns: '250px 250px', - }); + expect(activityGeometry.width).toBe(500); + expect(activityGeometry.executionWidth).toBeCloseTo(166.67, 0); + expect(activityGeometry.tournamentWidth).toBeCloseTo(166.67, 0); + expect(activityGeometry.surveyWidth).toBeCloseTo(166.67, 0); + expect(activityGeometry.columns.split(' ')).toHaveLength(3); await expect .poll(() => page diff --git a/app/game-frontend/src/components/command/RecruitmentCommandForm.vue b/app/game-frontend/src/components/command/RecruitmentCommandForm.vue index e271bee5..fb5b3cc1 100644 --- a/app/game-frontend/src/components/command/RecruitmentCommandForm.vue +++ b/app/game-frontend/src/components/command/RecruitmentCommandForm.vue @@ -423,14 +423,16 @@ watch( display: none; } -@media (max-width: 600px) { +@media (max-width: 939px) { .recruitment-command-form { - width: 500px; + width: 100%; + max-width: 100%; + overflow-x: clip; } .recruitment-list-front { position: sticky; z-index: 5; - top: 0; + top: 44px; background: #1d1d1d; } .recruitment-status { @@ -438,7 +440,7 @@ watch( } .mobile-selected-panel { display: grid; - grid-template-columns: 64px 76px 270px 90px; + grid-template-columns: 64px minmax(58px, 76px) minmax(0, 1fr) minmax(64px, 90px); min-height: 64px; align-items: stretch; } @@ -458,10 +460,11 @@ watch( text-align: right; } .crew-grid { - grid-template: - 'image name attack defence speed info' 32px - 'image name avoid cost rice info' 32px / - 64px 76px 30px 30px 30px 270px; + grid-template-areas: + 'image name attack defence speed info' + 'image name avoid cost rice info'; + grid-template-columns: 64px minmax(58px, 76px) repeat(3, minmax(26px, 30px)) minmax(0, 1fr); + grid-template-rows: 32px 32px; } .crew-grid .crew-image { grid-area: image; @@ -518,7 +521,27 @@ watch( } .crew-info { overflow: hidden; + overflow-wrap: anywhere; font-size: 0.9em; } } + +@media (max-width: 440px) { + .mobile-selected-panel { + grid-template-columns: 64px minmax(58px, 76px) minmax(0, 1fr) 72px; + min-height: 86px; + } + .mobile-selected-panel .amount-panel label { + grid-template-columns: auto minmax(34px, 1fr) auto; + } + .mobile-selected-panel output { + grid-column: 1 / -1; + min-height: 22px; + padding: 2px 4px; + } + .mobile-selected-panel .quick-buttons button, + .mobile-selected-panel .submit-recruit { + min-height: 32px; + } +} diff --git a/app/game-frontend/src/components/command/ReservedCommandEditor.vue b/app/game-frontend/src/components/command/ReservedCommandEditor.vue index 70eb56aa..4415981c 100644 --- a/app/game-frontend/src/components/command/ReservedCommandEditor.vue +++ b/app/game-frontend/src/components/command/ReservedCommandEditor.vue @@ -1,5 +1,5 @@