fix(game-ui): match mobile bottom button styles to Ref
This commit is contained in:
@@ -35,6 +35,7 @@ type NavigationFixture = {
|
|||||||
currentYear?: number;
|
currentYear?: number;
|
||||||
currentMonth?: number;
|
currentMonth?: number;
|
||||||
scenarioTitle?: string;
|
scenarioTitle?: string;
|
||||||
|
nationColor?: string;
|
||||||
lastExecuted?: string | null;
|
lastExecuted?: string | null;
|
||||||
latestVote?: { id: number; title: string; hasVoted: boolean } | null;
|
latestVote?: { id: number; title: string; hasVoted: boolean } | null;
|
||||||
globalRecords?: Array<{ id: number; text: string }>;
|
globalRecords?: Array<{ id: number; text: string }>;
|
||||||
@@ -267,7 +268,7 @@ const generalContext = (state: NavigationFixture) => ({
|
|||||||
nation: {
|
nation: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: '위',
|
name: '위',
|
||||||
color: '#008000',
|
color: state.nationColor ?? '#008000',
|
||||||
level: state.nationLevel,
|
level: state.nationLevel,
|
||||||
gold: 10_000,
|
gold: 10_000,
|
||||||
rice: 20_000,
|
rice: 20_000,
|
||||||
@@ -425,7 +426,7 @@ const installFixture = async (page: Page, state: NavigationFixture) => {
|
|||||||
year: state.currentYear ?? 185,
|
year: state.currentYear ?? 185,
|
||||||
month: state.currentMonth ?? 1,
|
month: state.currentMonth ?? 1,
|
||||||
cityList: [[1, 8, state.cityState ?? 0, 1, 1, 1]],
|
cityList: [[1, 8, state.cityState ?? 0, 1, 1, 1]],
|
||||||
nationList: [[1, '위', '#008000', 1]],
|
nationList: [[1, '위', state.nationColor ?? '#008000', 1]],
|
||||||
spyList: {},
|
spyList: {},
|
||||||
shownByGeneralList: [],
|
shownByGeneralList: [],
|
||||||
myCity: 1,
|
myCity: 1,
|
||||||
@@ -1409,6 +1410,111 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy
|
|||||||
await persistArtifact(page, `${basePath.slice(1)}-mobile-500`);
|
await persistArtifact(page, `${basePath.slice(1)}-mobile-500`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mobile bottom controls preserve Ref navigation color, contrast, and pressed depth', async ({ page }) => {
|
||||||
|
const state: NavigationFixture = {
|
||||||
|
officerLevel: 5,
|
||||||
|
permission: 2,
|
||||||
|
nationLevel: 3,
|
||||||
|
stage: 0,
|
||||||
|
npcMode: 1,
|
||||||
|
nationColor: '#FFFF00',
|
||||||
|
generalMeCalls: 0,
|
||||||
|
operations: [],
|
||||||
|
};
|
||||||
|
await installFixture(page, state);
|
||||||
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
|
await waitForMain(page);
|
||||||
|
|
||||||
|
const buttonStyle = (selector: string) =>
|
||||||
|
page.locator(selector).evaluate((element) => {
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
backgroundColor: style.backgroundColor,
|
||||||
|
color: style.color,
|
||||||
|
borderBottomWidth: style.borderBottomWidth,
|
||||||
|
marginTop: style.marginTop,
|
||||||
|
fontWeight: style.fontWeight,
|
||||||
|
height: rect.height,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const pointerDownStyle = async (selector: string) => {
|
||||||
|
const target = page.locator(selector);
|
||||||
|
const box = await target.boundingBox();
|
||||||
|
if (!box) throw new Error(`mobile bottom control is not measurable: ${selector}`);
|
||||||
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||||
|
await page.mouse.down();
|
||||||
|
const activeStyle = await buttonStyle(selector);
|
||||||
|
await page.mouse.move(1, 1);
|
||||||
|
await page.mouse.up();
|
||||||
|
return activeStyle;
|
||||||
|
};
|
||||||
|
|
||||||
|
const globalSelector = '[data-bottom-menu="global"]';
|
||||||
|
await expect(page.locator(globalSelector)).toBeVisible();
|
||||||
|
expect(await buttonStyle(globalSelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
color: 'rgb(255, 255, 255)',
|
||||||
|
borderBottomWidth: '4px',
|
||||||
|
marginTop: '0px',
|
||||||
|
fontWeight: '700',
|
||||||
|
height: 45,
|
||||||
|
});
|
||||||
|
await page.locator(globalSelector).hover();
|
||||||
|
expect(await buttonStyle(globalSelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
borderBottomWidth: '3px',
|
||||||
|
marginTop: '1px',
|
||||||
|
height: 44,
|
||||||
|
});
|
||||||
|
expect(await pointerDownStyle(globalSelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
borderBottomWidth: '2px',
|
||||||
|
marginTop: '2px',
|
||||||
|
height: 43,
|
||||||
|
});
|
||||||
|
|
||||||
|
const nationTrigger = page.locator('[data-bottom-menu="nation"]');
|
||||||
|
await expect(nationTrigger).toHaveCSS('background-color', 'rgb(255, 255, 0)');
|
||||||
|
await expect(nationTrigger).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||||
|
|
||||||
|
await page.locator('[data-bottom-menu="quick"]').click();
|
||||||
|
const lobbySelector = '#mobile-quick-menu .lobby-link';
|
||||||
|
await expect(page.locator(lobbySelector)).toBeVisible();
|
||||||
|
expect(await buttonStyle(lobbySelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
color: 'rgb(255, 255, 255)',
|
||||||
|
borderBottomWidth: '4px',
|
||||||
|
marginTop: '0px',
|
||||||
|
fontWeight: '700',
|
||||||
|
height: 40,
|
||||||
|
});
|
||||||
|
expect(await pointerDownStyle(lobbySelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
borderBottomWidth: '2px',
|
||||||
|
marginTop: '2px',
|
||||||
|
height: 38,
|
||||||
|
});
|
||||||
|
|
||||||
|
const autoRefreshSelector = '[data-bottom-menu="auto-refresh"]';
|
||||||
|
expect(await buttonStyle(autoRefreshSelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
color: 'rgb(255, 255, 255)',
|
||||||
|
borderBottomWidth: '4px',
|
||||||
|
marginTop: '0px',
|
||||||
|
fontWeight: '700',
|
||||||
|
height: 45,
|
||||||
|
});
|
||||||
|
expect(await pointerDownStyle(autoRefreshSelector)).toMatchObject({
|
||||||
|
backgroundColor: 'rgb(0, 88, 44)',
|
||||||
|
borderBottomWidth: '2px',
|
||||||
|
marginTop: '2px',
|
||||||
|
height: 43,
|
||||||
|
});
|
||||||
|
|
||||||
|
await persistArtifact(page, `${basePath.slice(1)}-mobile-bottom-ref-buttons`);
|
||||||
|
});
|
||||||
|
|
||||||
test('real mobile devices initially fit the complete 500px game canvas', async ({ browser }, testInfo) => {
|
test('real mobile devices initially fit the complete 500px game canvas', async ({ browser }, testInfo) => {
|
||||||
test.setTimeout(60_000);
|
test.setTimeout(60_000);
|
||||||
const configuredBaseUrl = testInfo.project.use.baseURL;
|
const configuredBaseUrl = testInfo.project.use.baseURL;
|
||||||
@@ -1629,7 +1735,10 @@ test('mobile single document refreshes once and preserves tokens on lobby return
|
|||||||
await autoRefresh.focus();
|
await autoRefresh.focus();
|
||||||
await expect(autoRefresh).toBeFocused();
|
await expect(autoRefresh).toBeFocused();
|
||||||
await autoRefresh.hover();
|
await autoRefresh.hover();
|
||||||
await expect(autoRefresh).toHaveCSS('filter', 'brightness(1.14)');
|
await expect(autoRefresh).toHaveCSS('filter', 'none');
|
||||||
|
await expect(autoRefresh).toHaveCSS('background-color', 'rgb(0, 88, 44)');
|
||||||
|
await expect(autoRefresh).toHaveCSS('border-bottom-width', '3px');
|
||||||
|
await expect(autoRefresh).toHaveCSS('margin-top', '1px');
|
||||||
await autoRefresh.click();
|
await autoRefresh.click();
|
||||||
const disabledAutoRefresh = page.getByRole('button', { name: '자동 갱신 OFF' });
|
const disabledAutoRefresh = page.getByRole('button', { name: '자동 갱신 OFF' });
|
||||||
await expect(disabledAutoRefresh).toHaveAttribute('aria-pressed', 'false');
|
await expect(disabledAutoRefresh).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { legacyNationTextColor } from '../../utils/legacyNationColor';
|
||||||
import MainNavigationLink from './MainNavigationLink.vue';
|
import MainNavigationLink from './MainNavigationLink.vue';
|
||||||
import {
|
import {
|
||||||
buildGlobalNavigation,
|
buildGlobalNavigation,
|
||||||
@@ -31,6 +32,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const { setRoot, openId, close, toggle } = useMenuPopup();
|
const { setRoot, openId, close, toggle } = useMenuPopup();
|
||||||
const globalEntries = computed(() => buildGlobalNavigation(props.npcMode));
|
const globalEntries = computed(() => buildGlobalNavigation(props.npcMode));
|
||||||
|
const nationMenuColor = computed(() => props.nationColor || '#000000');
|
||||||
|
const nationMenuTextColor = computed(() => legacyNationTextColor(nationMenuColor.value));
|
||||||
const isActive = (link: MainNavigationLinkItem) => link.highlightStage === props.tournamentStage;
|
const isActive = (link: MainNavigationLinkItem) => link.highlightStage === props.tournamentStage;
|
||||||
|
|
||||||
const onQuick = (item: QuickNavigationItem) => {
|
const onQuick = (item: QuickNavigationItem) => {
|
||||||
@@ -43,12 +46,15 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
<nav
|
<nav
|
||||||
:ref="setRoot"
|
:ref="setRoot"
|
||||||
class="main-mobile-bottom"
|
class="main-mobile-bottom"
|
||||||
:style="{ '--nation-menu-color': nationColor || '#000000' }"
|
:style="{
|
||||||
|
'--nation-menu-color': nationMenuColor,
|
||||||
|
'--nation-menu-text-color': nationMenuTextColor,
|
||||||
|
}"
|
||||||
aria-label="모바일 빠른 메뉴"
|
aria-label="모바일 빠른 메뉴"
|
||||||
>
|
>
|
||||||
<div class="bottom-item">
|
<div class="bottom-item">
|
||||||
<button
|
<button
|
||||||
class="bottom-trigger"
|
class="bottom-trigger ref-navigation-button"
|
||||||
type="button"
|
type="button"
|
||||||
data-bottom-menu="global"
|
data-bottom-menu="global"
|
||||||
:aria-expanded="openId === 'global'"
|
:aria-expanded="openId === 'global'"
|
||||||
@@ -185,7 +191,12 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li class="bottom-lobby" role="none">
|
<li class="bottom-lobby" role="none">
|
||||||
<button class="quick-link lobby-link" type="button" role="menuitem" @click="emit('lobby')">
|
<button
|
||||||
|
class="quick-link lobby-link ref-navigation-button"
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
@click="emit('lobby')"
|
||||||
|
>
|
||||||
로비로
|
로비로
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -194,7 +205,7 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
|
|
||||||
<div class="bottom-refresh-controls">
|
<div class="bottom-refresh-controls">
|
||||||
<button
|
<button
|
||||||
class="bottom-trigger auto-refresh-trigger"
|
class="bottom-trigger auto-refresh-trigger ref-navigation-button"
|
||||||
:class="{ active: realtimeEnabled }"
|
:class="{ active: realtimeEnabled }"
|
||||||
type="button"
|
type="button"
|
||||||
data-bottom-menu="auto-refresh"
|
data-bottom-menu="auto-refresh"
|
||||||
@@ -270,6 +281,7 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
.nation-trigger {
|
.nation-trigger {
|
||||||
border-color: color-mix(in srgb, var(--nation-menu-color) 85%, #000);
|
border-color: color-mix(in srgb, var(--nation-menu-color) 85%, #000);
|
||||||
background: var(--nation-menu-color);
|
background: var(--nation-menu-color);
|
||||||
|
color: var(--nation-menu-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-trigger {
|
.quick-trigger {
|
||||||
@@ -292,10 +304,6 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-refresh-trigger.active {
|
|
||||||
background-color: #164f2c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-refresh-trigger.active strong {
|
.auto-refresh-trigger.active strong {
|
||||||
color: #9ef0b8;
|
color: #9ef0b8;
|
||||||
}
|
}
|
||||||
@@ -323,6 +331,43 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
filter: brightness(0.82);
|
filter: brightness(0.82);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ref's btn-sammo-base2 is a Lumen-style navigation button. Its lower edge
|
||||||
|
* shortens while the control moves down, so hover and pointer-down visibly
|
||||||
|
* feel pressed instead of only changing brightness.
|
||||||
|
*/
|
||||||
|
.ref-navigation-button {
|
||||||
|
border-color: var(--sammo-button-navigation-border);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 1px 4px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
transition:
|
||||||
|
color 0.15s,
|
||||||
|
background-color 0.15s,
|
||||||
|
border-color 0.15s,
|
||||||
|
box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-trigger.ref-navigation-button:hover,
|
||||||
|
.bottom-trigger.ref-navigation-button:focus-visible,
|
||||||
|
.bottom-trigger.ref-navigation-button[aria-expanded='true'] {
|
||||||
|
height: 44px;
|
||||||
|
margin-top: 1px;
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-trigger.ref-navigation-button:active {
|
||||||
|
height: 43px;
|
||||||
|
margin-top: 2px;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dropup-caret {
|
.dropup-caret {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
@@ -413,8 +458,29 @@ const onQuick = (item: QuickNavigationItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lobby-link {
|
.lobby-link {
|
||||||
|
width: auto;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #302016 var(--sammo-texture-walnut);
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
border-color: var(--sammo-button-navigation-border);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 1px 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-link:hover,
|
||||||
|
.lobby-link:focus-visible {
|
||||||
|
margin-top: 1px;
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-link:active {
|
||||||
|
margin-top: 2px;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
background: var(--sammo-button-navigation-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 939.98px) {
|
@media (max-width: 939.98px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user