fix(game-ui): 공통 Lumen 버튼 모양을 통일한다

상하단 이동·갱신 제어와 장수 목록, 토너먼트, 명령 선택을 공통 semantic button family로 연결한다.

고정 행은 공통 fixed-height 변형이 hover와 active 높이를 보정하게 하고 실제 Chromium geometry 회귀를 추가한다.
This commit is contained in:
2026-08-21 16:31:42 +00:00
parent 31b8b01055
commit aa912a2eed
25 changed files with 628 additions and 377 deletions
+37 -1
View File
@@ -2186,7 +2186,43 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn
await selectedMenu.evaluate((element) => ((element as HTMLDetailsElement).open = false)); await selectedMenu.evaluate((element) => ((element as HTMLDetailsElement).open = false));
await expect(selectedMenu).not.toHaveAttribute('open', ''); await expect(selectedMenu).not.toHaveAttribute('open', '');
await page.locator('[data-main-target="commands"] .select-command').click(); const selectCommand = page.locator('[data-main-target="commands"] .select-command');
await expect(selectCommand).toHaveClass(/legacy-button--info/u);
await page.mouse.move(1, 1);
const measureSelectCommand = () =>
selectCommand.evaluate((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
top: rect.top,
bottom: rect.bottom,
height: rect.height,
marginTop: style.marginTop,
borderBottomWidth: style.borderBottomWidth,
borderRadius: style.borderRadius,
backgroundColor: style.backgroundColor,
};
});
const selectDefault = await measureSelectCommand();
expect(selectDefault).toMatchObject({
height: 34,
marginTop: '0px',
borderBottomWidth: '4px',
borderRadius: '5.25px',
backgroundColor: 'rgb(52, 152, 219)',
});
await selectCommand.hover();
const selectHover = await measureSelectCommand();
expect(selectHover).toMatchObject({ height: 33, marginTop: '1px', borderBottomWidth: '3px' });
expect(selectHover.bottom).toBeCloseTo(selectDefault.bottom, 2);
const selectBox = await selectCommand.boundingBox();
if (!selectBox) throw new Error('select command control is not measurable');
await page.mouse.move(selectBox.x + selectBox.width / 2, selectBox.y + selectBox.height / 2);
await page.mouse.down();
const selectActive = await measureSelectCommand();
expect(selectActive).toMatchObject({ height: 32, marginTop: '2px', borderBottomWidth: '2px' });
expect(selectActive.bottom).toBeCloseTo(selectDefault.bottom, 2);
await page.mouse.up();
const picker = page.getByTestId('command-picker'); const picker = page.getByTestId('command-picker');
await expect(picker).toBeVisible(); await expect(picker).toBeVisible();
// The trigger can end up directly above a newly opened category button. // The trigger can end up directly above a newly opened category button.
@@ -205,6 +205,86 @@ test('nation generals keeps the 1000px legacy grid and redacted member columns',
await expect(page.locator('#nation-general-list')).toContainText('?'); await expect(page.locator('#nation-general-list')).toContainText('?');
}); });
test('nation generals top controls share fixed Lumen state geometry on desktop and mobile', async ({
page,
}, testInfo) => {
await install(page);
const evidence: Record<string, unknown> = {};
for (const viewport of [
{ width: 1200, height: 900 },
{ width: 500, height: 900 },
]) {
await page.setViewportSize(viewport);
await page.goto('nation/generals');
await expect(page.locator('#nation-general-list')).toBeVisible();
const controls = [
page.getByRole('button', { name: '돌아가기' }),
page.getByRole('button', { name: '갱신' }),
page.getByRole('button', { name: '보기 모드⌄' }),
page.getByRole('button', { name: '열 선택⌄' }),
];
const viewportEvidence: Record<string, unknown> = {};
for (const control of controls) {
const label = (await control.textContent())?.trim() ?? 'unknown';
await expect(control).toHaveClass(/legacy-button--fixed-height/u);
const measure = () =>
control.evaluate((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
top: rect.top,
bottom: rect.bottom,
height: rect.height,
marginTop: style.marginTop,
borderBottomWidth: style.borderBottomWidth,
borderRadius: style.borderRadius,
backgroundColor: style.backgroundColor,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
};
});
await page.mouse.move(viewport.width - 1, viewport.height - 1);
const base = await measure();
expect(base).toMatchObject({
height: 32,
marginTop: '0px',
borderBottomWidth: '4px',
borderRadius: '5.25px',
fontSize: '14px',
});
expect(base.fontFamily).toContain('Pretendard');
await control.hover();
const hover = await measure();
expect(hover).toMatchObject({ height: 31, marginTop: '1px', borderBottomWidth: '3px' });
expect(hover.bottom).toBeCloseTo(base.bottom, 2);
const box = await control.boundingBox();
if (!box) throw new Error(`${label} control is not measurable`);
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
const active = await measure();
expect(active).toMatchObject({ height: 30, marginTop: '2px', borderBottomWidth: '2px' });
expect(active.bottom).toBeCloseTo(base.bottom, 2);
await page.mouse.move(viewport.width - 1, viewport.height - 1);
await page.mouse.up();
viewportEvidence[label] = { default: base, hover, active };
}
evidence[`${viewport.width}x${viewport.height}`] = viewportEvidence;
await page.screenshot({
path: testInfo.outputPath(`nation-general-buttons-${viewport.width}.png`),
fullPage: true,
});
}
await testInfo.attach('nation-general-button-geometry', {
body: JSON.stringify(evidence, null, 2),
contentType: 'application/json',
});
});
test('nation generals restores Ref group, saved view, sort, and Korean search behavior', async ({ page }, testInfo) => { test('nation generals restores Ref group, saved view, sort, and Korean search behavior', async ({ page }, testInfo) => {
await install(page); await install(page);
await page.setViewportSize({ width: 1200, height: 900 }); await page.setViewportSize({ width: 1200, height: 900 });
@@ -405,7 +485,10 @@ test('secret office renders five Ref-style command briefs and the forbidden erro
'5 : 휴식', '5 : 휴식',
]); ]);
await expect(commandRows.nth(2)).toHaveAttribute('title', '【다른장수】에게 쌀 200을 증여'); await expect(commandRows.nth(2)).toHaveAttribute('title', '【다른장수】에게 쌀 200을 증여');
const geometry = await page.locator('#secret-general-list .turns').first().evaluate((element) => { const geometry = await page
.locator('#secret-general-list .turns')
.first()
.evaluate((element) => {
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
const style = getComputedStyle(element); const style = getComputedStyle(element);
return { return {
@@ -395,7 +395,10 @@ test('join refresh shows the assigned preliminary group immediately with accessi
await refresh.focus(); await refresh.focus();
await expect(refresh).toBeFocused(); await expect(refresh).toBeFocused();
await refresh.hover(); await refresh.hover();
await expect(refresh).toHaveCSS('filter', 'brightness(1.25)'); await expect(refresh).toHaveCSS('filter', 'none');
await expect(refresh).toHaveCSS('height', '43px');
await expect(refresh).toHaveCSS('margin-top', '1px');
await expect(refresh).toHaveCSS('border-bottom-width', '3px');
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
}); });
@@ -602,7 +605,11 @@ test('mobile betting rankings use tabs and keep dedicated icons beside general n
await expect(dialog.getByText('예상 환수금 280')).toBeVisible(); await expect(dialog.getByText('예상 환수금 280')).toBeVisible();
await dialog.getByLabel('베팅 금액').selectOption('50'); await dialog.getByLabel('베팅 금액').selectOption('50');
await expect(dialog.getByText('예상 환수금 1,400')).toBeVisible(); await expect(dialog.getByText('예상 환수금 1,400')).toBeVisible();
await persistScreenshot(page, 'tournament-betting-dialog-mobile', testInfo.outputPath('betting-dialog-mobile.webp')); await persistScreenshot(
page,
'tournament-betting-dialog-mobile',
testInfo.outputPath('betting-dialog-mobile.webp')
);
await dialog.getByRole('button', { name: '베팅 등록' }).click(); await dialog.getByRole('button', { name: '베팅 등록' }).click();
await expect(dialog).not.toBeVisible(); await expect(dialog).not.toBeVisible();
await expect(page.getByRole('status')).toHaveText('베팅이 등록되었습니다.'); await expect(page.getByRole('status')).toHaveText('베팅이 등록되었습니다.');
@@ -144,7 +144,7 @@
/* /*
* Ref Bootstrap 5.2 + Lumen button family. This class owns the common raised * Ref Bootstrap 5.2 + Lumen button family. This class owns the common raised
* edge and pressed movement. Semantic modifiers below only select face, edge, * edge and pressed movement. Semantic modifiers below only select face, edge,
* and text colors; width and fixed-height compensation stay with the owner. * and text colors; width and the optional fixed-height value stay with the owner.
* Existing semantic modifiers also opt in for backward compatibility. * Existing semantic modifiers also opt in for backward compatibility.
*/ */
.legacy-button:is( .legacy-button:is(
@@ -173,6 +173,15 @@
vertical-align: middle; vertical-align: middle;
} }
/*
* Top bars and other fixed rows keep their owner-provided height while using
* the same Lumen edge movement. Shrinking the box with the edge keeps its
* bottom coordinate fixed instead of moving the whole control down.
*/
.legacy-button.legacy-button--fixed-height {
height: var(--legacy-button-height);
}
.legacy-button.legacy-button--secondary { .legacy-button.legacy-button--secondary {
--legacy-button-bg: var(--sammo-button-secondary-bg); --legacy-button-bg: var(--sammo-button-secondary-bg);
--legacy-button-border: var(--sammo-button-secondary-border); --legacy-button-border: var(--sammo-button-secondary-border);
@@ -213,6 +222,10 @@
background: var(--legacy-button-bg); background: var(--legacy-button-bg);
} }
.legacy-button.legacy-button--fixed-height:not(:disabled, [aria-disabled='true']):is(:hover, [aria-expanded='true']) {
height: calc(var(--legacy-button-height) - 1px);
}
.legacy-button:is( .legacy-button:is(
.legacy-button--lumen, .legacy-button--lumen,
.legacy-button--primary, .legacy-button--primary,
@@ -244,6 +257,10 @@
box-shadow: none; box-shadow: none;
} }
.legacy-button.legacy-button--fixed-height:not(:disabled, [aria-disabled='true']):active {
height: calc(var(--legacy-button-height) - 2px);
}
.legacy-button:is( .legacy-button:is(
.legacy-button--lumen, .legacy-button--lumen,
.legacy-button--primary, .legacy-button--primary,
@@ -588,7 +588,13 @@ const clickOutsideMenu = (event: Event) => {
</button> </button>
</div> </div>
</details> </details>
<button type="button" class="select-command" @click="togglePicker()">명령 선택 </button> <button
type="button"
class="legacy-button legacy-button--info legacy-button--fixed-height select-command"
@click="togglePicker()"
>
명령 선택
</button>
</div> </div>
<div class="queue-area"> <div class="queue-area">
@@ -806,8 +812,7 @@ const clickOutsideMenu = (event: Event) => {
} }
.control-pad > button, .control-pad > button,
.clock, .clock,
.legacy-menu > summary, .legacy-menu > summary {
.select-command {
box-sizing: border-box; box-sizing: border-box;
min-height: 34px; min-height: 34px;
border: 0; border: 0;
@@ -822,6 +827,12 @@ const clickOutsideMenu = (event: Event) => {
cursor: pointer; cursor: pointer;
list-style: none; list-style: none;
} }
.select-command {
--legacy-button-height: 34px;
display: grid;
place-items: center;
padding: 4px;
}
.clock { .clock {
background: #345c85; background: #345c85;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
@@ -1024,9 +1035,6 @@ const clickOutsideMenu = (event: Event) => {
grid-template-columns: 5fr 7fr; grid-template-columns: 5fr 7fr;
order: 1; order: 1;
} }
.advanced-actions > * {
border-radius: 0 !important;
}
.bottom-actions { .bottom-actions {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -61,7 +61,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
> >
<div class="bottom-item"> <div class="bottom-item">
<button <button
class="bottom-trigger legacy-button legacy-button--navigation" class="bottom-trigger legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button" type="button"
data-bottom-menu="global" data-bottom-menu="global"
:aria-expanded="openId === 'global'" :aria-expanded="openId === 'global'"
@@ -132,7 +132,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
<div class="bottom-item nation-bottom-item"> <div class="bottom-item nation-bottom-item">
<button <button
class="bottom-trigger nation-trigger legacy-button legacy-button--lumen" class="bottom-trigger nation-trigger legacy-button legacy-button--lumen legacy-button--fixed-height"
:style="{ :style="{
'--legacy-button-bg': nationMenuColor, '--legacy-button-bg': nationMenuColor,
'--legacy-button-border': 'color-mix(in srgb, var(--nation-menu-color) 90%, #000)', '--legacy-button-border': 'color-mix(in srgb, var(--nation-menu-color) 90%, #000)',
@@ -178,7 +178,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
<div class="bottom-item"> <div class="bottom-item">
<button <button
class="bottom-trigger quick-trigger legacy-button legacy-button--dark" class="bottom-trigger quick-trigger legacy-button legacy-button--dark legacy-button--fixed-height"
type="button" type="button"
data-bottom-menu="quick" data-bottom-menu="quick"
:aria-expanded="openId === 'quick'" :aria-expanded="openId === 'quick'"
@@ -221,7 +221,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
<div class="bottom-refresh-controls"> <div class="bottom-refresh-controls">
<button <button
class="bottom-trigger auto-refresh-trigger legacy-button legacy-button--navigation" class="bottom-trigger auto-refresh-trigger legacy-button legacy-button--navigation legacy-button--fixed-height"
:class="{ active: realtimeEnabled }" :class="{ active: realtimeEnabled }"
type="button" type="button"
data-bottom-menu="auto-refresh" data-bottom-menu="auto-refresh"
@@ -232,7 +232,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
<strong>{{ realtimeEnabled ? 'ON' : 'OFF' }}</strong> <strong>{{ realtimeEnabled ? 'ON' : 'OFF' }}</strong>
</button> </button>
<button <button
class="bottom-trigger manual-refresh-trigger legacy-button legacy-button--dark" class="bottom-trigger manual-refresh-trigger legacy-button legacy-button--dark legacy-button--fixed-height"
type="button" type="button"
data-bottom-menu="manual-refresh" data-bottom-menu="manual-refresh"
aria-label="직접 갱신" aria-label="직접 갱신"
@@ -279,9 +279,9 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
} }
.bottom-trigger { .bottom-trigger {
--legacy-button-height: 45px;
box-sizing: border-box; box-sizing: border-box;
width: 125px; width: 125px;
height: 45px;
padding: 6px 4px; padding: 6px 4px;
font-family: inherit; font-family: inherit;
font-size: 16px; font-size: 16px;
@@ -316,15 +316,6 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
line-height: 1; line-height: 1;
} }
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true']):hover,
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true'])[aria-expanded='true'] {
height: 44px;
}
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true']):active {
height: 43px;
}
.dropup-caret { .dropup-caret {
display: inline-block; display: inline-block;
margin-left: 4px; margin-left: 4px;
@@ -14,6 +14,7 @@ defineProps<{
<button <button
type="button" type="button"
role="tab" role="tab"
class="legacy-button legacy-button--dark legacy-button--fixed-height"
:aria-selected="activePage === 'tournament'" :aria-selected="activePage === 'tournament'"
:class="{ active: activePage === 'tournament' }" :class="{ active: activePage === 'tournament' }"
@click="navigate" @click="navigate"
@@ -25,6 +26,7 @@ defineProps<{
<button <button
type="button" type="button"
role="tab" role="tab"
class="legacy-button legacy-button--dark legacy-button--fixed-height"
:aria-selected="activePage === 'betting'" :aria-selected="activePage === 'betting'"
:class="{ active: activePage === 'betting' }" :class="{ active: activePage === 'betting' }"
@click="navigate" @click="navigate"
@@ -34,7 +36,13 @@ defineProps<{
</RouterLink> </RouterLink>
</nav> </nav>
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="close-button" type="button" @click="navigate"> 닫기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
type="button"
@click="navigate"
>
닫기
</button>
</RouterLink> </RouterLink>
</div> </div>
</header> </header>
@@ -64,33 +72,21 @@ defineProps<{
gap: 4px; gap: 4px;
} }
button { button {
height: 44px; --legacy-button-height: 44px;
margin: 0; margin: 0;
border: 1px solid #666;
border-radius: 5.25px;
background: #444;
color: #fff;
font-size: 14px; font-size: 14px;
line-height: 18px;
cursor: pointer;
} }
.tournament-page-tabs button { .tournament-page-tabs button {
min-width: 72px; min-width: 72px;
padding: 10px 12px; padding: 10px 12px;
} }
.tournament-page-tabs button.active { .tournament-page-tabs button.active {
border-color: #f39c12; --legacy-button-bg: #8a5b13;
background: #8a5b13; --legacy-button-border: #704a0f;
} }
.close-button { .close-button {
width: 88px; width: 88px;
padding: 10px 16px; padding: 10px 16px;
border-color: #375a7f;
background: #375a7f;
}
button:hover,
button:focus {
filter: brightness(1.25);
} }
button:focus-visible { button:focus-visible {
outline: 2px solid #f39c12; outline: 2px solid #f39c12;
+31 -62
View File
@@ -197,20 +197,31 @@ onMounted(() => {
:class="activeTab === 'resource' ? 'resource-page' : 'unique-page'" :class="activeTab === 'resource' ? 'resource-page' : 'unique-page'"
> >
<header class="top-back-bar bg0"> <header class="top-back-bar bg0">
<button class="legacy-button close-button" type="button" @click="closeWindow"> 닫기</button> <button
<button class="legacy-button reload-button" type="button" :disabled="loading" @click="loadOverview"> class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
type="button"
@click="closeWindow"
>
닫기
</button>
<button
class="legacy-button legacy-button--navigation legacy-button--fixed-height reload-button"
type="button"
:disabled="loading"
@click="loadOverview"
>
갱신 갱신
</button> </button>
<h1>{{ activeTab === 'resource' ? '경매장' : '유니크 경매장' }}</h1> <h1>{{ activeTab === 'resource' ? '경매장' : '유니크 경매장' }}</h1>
<button <button
class="legacy-button tab-button" class="legacy-button legacy-button--dark tab-button"
:aria-pressed="activeTab === 'resource'" :aria-pressed="activeTab === 'resource'"
@click="activeTab = 'resource'" @click="activeTab = 'resource'"
> >
/ /
</button> </button>
<button <button
class="legacy-button tab-button" class="legacy-button legacy-button--dark tab-button"
:aria-pressed="activeTab === 'unique'" :aria-pressed="activeTab === 'unique'"
@click="activeTab = 'unique'" @click="activeTab = 'unique'"
> >
@@ -318,7 +329,12 @@ onMounted(() => {
step="10" step="10"
required required
/> />
<button class="legacy-button" :disabled="actionBusy || selectedResource.isCallerHost">입찰</button> <button
class="legacy-button legacy-button--dark"
:disabled="actionBusy || selectedResource.isCallerHost"
>
입찰
</button>
</form> </form>
<h3 class="subsection-title">경매 등록</h3> <h3 class="subsection-title">경매 등록</h3>
@@ -331,7 +347,7 @@ onMounted(() => {
<legend>매물</legend> <legend>매물</legend>
<div class="item-toggle"> <div class="item-toggle">
<button <button
class="legacy-button" class="legacy-button legacy-button--dark"
type="button" type="button"
:aria-pressed="openForm.type === 'BUY_RICE'" :aria-pressed="openForm.type === 'BUY_RICE'"
@click="openForm.type = 'BUY_RICE'" @click="openForm.type = 'BUY_RICE'"
@@ -339,7 +355,7 @@ onMounted(() => {
</button> </button>
<button <button
class="legacy-button" class="legacy-button legacy-button--dark"
type="button" type="button"
:aria-pressed="openForm.type === 'SELL_RICE'" :aria-pressed="openForm.type === 'SELL_RICE'"
@click="openForm.type = 'SELL_RICE'" @click="openForm.type = 'SELL_RICE'"
@@ -363,7 +379,7 @@ onMounted(() => {
<span>마감가 ({{ openForm.type === 'BUY_RICE' ? '금' : '쌀' }})</span> <span>마감가 ({{ openForm.type === 'BUY_RICE' ? '금' : '쌀' }})</span>
<input v-model.number="openForm.finishBidAmount" type="number" min="100" max="10000" step="10" /> <input v-model.number="openForm.finishBidAmount" type="number" min="100" max="10000" step="10" />
</label> </label>
<button class="legacy-button register-button" :disabled="actionBusy">등록</button> <button class="legacy-button legacy-button--dark register-button" :disabled="actionBusy">등록</button>
</form> </form>
<h3 class="subsection-title">이전 경매(최근 20)</h3> <h3 class="subsection-title">이전 경매(최근 20)</h3>
@@ -408,7 +424,7 @@ onMounted(() => {
유산포인트 (잔여: {{ formatNumber(uniqueDetail.remainPoint) }}포인트) 유산포인트 (잔여: {{ formatNumber(uniqueDetail.remainPoint) }}포인트)
</label> </label>
<input id="unique-bid" v-model.number="bidAmount" type="number" min="1" required /> <input id="unique-bid" v-model.number="bidAmount" type="number" min="1" required />
<button class="legacy-button" :disabled="actionBusy">입찰</button> <button class="legacy-button legacy-button--dark" :disabled="actionBusy">입찰</button>
</form> </form>
</template> </template>
</section> </section>
@@ -480,7 +496,9 @@ onMounted(() => {
</section> </section>
<footer class="bottom-bar bg0"> <footer class="bottom-bar bg0">
<button class="legacy-button close-button" type="button" @click="closeWindow"> 닫기</button> <button class="legacy-button legacy-button--navigation close-button" type="button" @click="closeWindow">
닫기
</button>
</footer> </footer>
</main> </main>
</template> </template>
@@ -530,66 +548,17 @@ onMounted(() => {
line-height: 32px; line-height: 32px;
text-align: center; text-align: center;
} }
.legacy-button {
box-sizing: border-box;
border: solid #3d3d3d;
border-width: 0 1px 4px;
border-radius: 5.25px;
padding: 5.25px 10.5px;
color: #fff;
background: #444;
font: inherit;
font-weight: 700;
line-height: 21px;
cursor: pointer;
}
.legacy-button:hover,
.legacy-button:focus {
border-color: #353535;
background: #393939;
}
.legacy-button:focus-visible {
outline: 2px solid #8ab4f8;
outline-offset: -2px;
}
.legacy-button:active,
.legacy-button[aria-pressed='true'] {
border-color: #303030;
background: #333;
}
.legacy-button:disabled {
cursor: default;
opacity: 0.65;
}
.close-button, .close-button,
.reload-button { .reload-button {
margin-right: 2px; margin-right: 2px;
border-color: #004f28;
background: #00582c;
}
.close-button:hover,
.close-button:focus,
.reload-button:hover,
.reload-button:focus {
border-color: #004523;
background: #004a25;
} }
.top-back-bar .close-button, .top-back-bar .close-button,
.top-back-bar .reload-button { .top-back-bar .reload-button {
height: 32px; --legacy-button-height: 32px;
}
.tab-button {
border-color: #3d3d3d;
background: #444;
} }
.tab-button[aria-pressed='true'] { .tab-button[aria-pressed='true'] {
border-color: #3d3d3d; --legacy-button-bg: #333;
background: #444; --legacy-button-border: #303030;
}
.tab-button:hover,
.tab-button:focus {
border-color: #3d3d3d;
background: #444;
} }
.section-title, .section-title,
.subsection-title, .subsection-title,
@@ -238,9 +238,20 @@ onMounted(() => {
<main class="ref-shell battle-page"> <main class="ref-shell battle-page">
<header class="battle-top legacy-bg0"> <header class="battle-top legacy-bg0">
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="battle-nav" type="button" @click="navigate"> 닫기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height battle-nav"
type="button"
@click="navigate"
>
닫기
</button>
</RouterLink> </RouterLink>
<button class="battle-nav" @click="loadBattleCenter">갱신</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height battle-nav"
@click="loadBattleCenter"
>
갱신
</button>
<h1>감찰부</h1> <h1>감찰부</h1>
<div></div> <div></div>
<div></div> <div></div>
@@ -308,7 +319,9 @@ onMounted(() => {
</section> </section>
<footer class="battle-footer legacy-bg0"> <footer class="battle-footer legacy-bg0">
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="battle-nav" type="button" @click="navigate"> 닫기</button> <button class="legacy-button legacy-button--navigation battle-nav" type="button" @click="navigate">
닫기
</button>
</RouterLink> </RouterLink>
</footer> </footer>
</main> </main>
@@ -421,17 +434,11 @@ onMounted(() => {
text-align: center; text-align: center;
} }
.battle-nav { .battle-nav {
--legacy-button-height: 32px;
box-sizing: border-box; box-sizing: border-box;
height: 32px;
margin-right: 2px; margin-right: 2px;
border: 0;
border-radius: 3px;
display: grid; display: grid;
place-items: center; place-items: center;
background: #00582c;
color: #fff;
font: inherit;
font-weight: 700;
text-decoration: none; text-decoration: none;
} }
.battle-footer { .battle-footer {
+23 -16
View File
@@ -130,7 +130,13 @@ const placeBet = async () => {
<main id="tournament-betting-container" class="betting-page"> <main id="tournament-betting-container" class="betting-page">
<TournamentPageHeader class="bg0" active-page="betting" title="베 팅 장" /> <TournamentPageHeader class="bg0" active-page="betting" title="베 팅 장" />
<section class="toolbar bg0"> <section class="toolbar bg0">
<button type="button" @click="load">갱신</button> <button
class="legacy-button legacy-button--secondary legacy-button--fixed-height"
type="button"
@click="load"
>
갱신
</button>
<span v-if="loading">불러오는 중...</span> <span v-if="loading">불러오는 중...</span>
<span v-if="message" role="status">{{ message }}</span> <span v-if="message" role="status">{{ message }}</span>
</section> </section>
@@ -159,12 +165,7 @@ const placeBet = async () => {
@request-bet="openBetDialog" @request-bet="openBetDialog"
/> />
<dialog <dialog ref="betDialog" class="bet-dialog" aria-labelledby="bet-dialog-title" @close="selectedTarget = null">
ref="betDialog"
class="bet-dialog"
aria-labelledby="bet-dialog-title"
@close="selectedTarget = null"
>
<form v-if="selectedTarget" class="bet-dialog-content" @submit.prevent="placeBet"> <form v-if="selectedTarget" class="bet-dialog-content" @submit.prevent="placeBet">
<header> <header>
<h2 id="bet-dialog-title">베팅하기</h2> <h2 id="bet-dialog-title">베팅하기</h2>
@@ -194,9 +195,13 @@ const placeBet = async () => {
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
<span class="gold-color">{{ selectedAmount }}</span> <span class="gold-color">{{ selectedAmount }}</span>
<span aria-hidden="true">=</span> <span aria-hidden="true">=</span>
<strong class="return-color">예상 환수금 {{ selectedExpectedReturn.toLocaleString('ko-KR') }}</strong> <strong class="return-color"
>예상 환수금 {{ selectedExpectedReturn.toLocaleString('ko-KR') }}</strong
>
</output> </output>
<p class="bet-preview-note">현재 배당 기준 예상값이며, 베팅 상황에 따라 최종 배당은 달라질 있습니다.</p> <p class="bet-preview-note">
현재 배당 기준 예상값이며, 베팅 상황에 따라 최종 배당은 달라질 있습니다.
</p>
<p v-if="betError" class="bet-dialog-error" role="alert">{{ betError }}</p> <p v-if="betError" class="bet-dialog-error" role="alert">{{ betError }}</p>
<footer> <footer>
<button type="button" :disabled="placingBet" @click="closeBetDialog">취소</button> <button type="button" :disabled="placingBet" @click="closeBetDialog">취소</button>
@@ -289,7 +294,9 @@ const placeBet = async () => {
</section> </section>
<footer class="betting-footer bg0"> <footer class="betting-footer bg0">
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="close-button" type="button" @click="navigate"> 닫기</button> <button class="legacy-button legacy-button--navigation close-button" type="button" @click="navigate">
닫기
</button>
</RouterLink> </RouterLink>
<small> <small>
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit 삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit
@@ -334,8 +341,8 @@ const placeBet = async () => {
text-align: left; text-align: left;
} }
.toolbar button { .toolbar button {
--legacy-button-height: 44px;
min-width: 72px; min-width: 72px;
height: 44px;
padding: 10px 16px; padding: 10px 16px;
font-size: 14px; font-size: 14px;
} }
@@ -380,7 +387,7 @@ select {
background: #000; background: #000;
color: #fff; color: #fff;
} }
button { button:not(.legacy-button) {
height: 35.5px; height: 35.5px;
color: #fff; color: #fff;
background: #444; background: #444;
@@ -388,16 +395,16 @@ button {
border-radius: 5.25px; border-radius: 5.25px;
cursor: pointer; cursor: pointer;
} }
button:hover, button:not(.legacy-button):hover,
button:focus { button:not(.legacy-button):focus {
filter: brightness(1.25); filter: brightness(1.25);
} }
button:focus-visible, button:not(.legacy-button):focus-visible,
select:focus-visible { select:focus-visible {
outline: 2px solid #f39c12; outline: 2px solid #f39c12;
outline-offset: 1px; outline-offset: 1px;
} }
button:disabled, button:not(.legacy-button):disabled,
select:disabled { select:disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5; opacity: 0.5;
+20 -53
View File
@@ -126,7 +126,13 @@ onMounted(() => {
<div v-if="accessChecked && !canAccess" class="legacy-raw-access-error" role="alert">{{ errorMessage }}</div> <div v-if="accessChecked && !canAccess" class="legacy-raw-access-error" role="alert">{{ errorMessage }}</div>
<main v-else id="container" class="legacy-board-page"> <main v-else id="container" class="legacy-board-page">
<header class="top-back-bar bg0"> <header class="top-back-bar bg0">
<button class="legacy-button back-button" type="button" @click="closeBoard">돌아가기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button"
type="button"
@click="closeBoard"
>
돌아가기
</button>
<div></div> <div></div>
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
<div></div> <div></div>
@@ -163,7 +169,7 @@ onMounted(() => {
<div></div> <div></div>
<button <button
id="submitArticle" id="submitArticle"
class="legacy-button legacy-button--secondary" class="legacy-button legacy-button--secondary legacy-button--fixed-height"
type="button" type="button"
@click="submitArticle" @click="submitArticle"
> >
@@ -222,7 +228,7 @@ onMounted(() => {
@keyup.enter="submitComment(article.id)" @keyup.enter="submitComment(article.id)"
/> />
<button <button
class="legacy-button submit-comment" class="legacy-button legacy-button--dark legacy-button--fixed-height submit-comment"
type="button" type="button"
@click="submitComment(article.id)" @click="submitComment(article.id)"
> >
@@ -235,7 +241,13 @@ onMounted(() => {
</section> </section>
<footer class="bottom-bar bg0"> <footer class="bottom-bar bg0">
<button class="legacy-button back-button" type="button" @click="closeBoard">돌아가기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button"
type="button"
@click="closeBoard"
>
돌아가기
</button>
</footer> </footer>
</template> </template>
</main> </main>
@@ -296,47 +308,9 @@ onMounted(() => {
text-align: center; text-align: center;
} }
.legacy-button {
min-height: 31px;
box-sizing: border-box;
border: 1px solid #3d3d3d;
border-radius: 4px;
padding: 4px 12px;
color: #fff;
background: #444;
font: inherit;
font-weight: 600;
line-height: 1.5;
text-align: center;
text-decoration: none;
cursor: pointer;
}
.legacy-button:hover {
border-color: #3d3d3d;
background: #444;
}
.legacy-button:focus-visible {
outline: none;
}
.legacy-button:active {
border-color: #3d3d3d;
background: #444;
}
.back-button { .back-button {
height: 32px; --legacy-button-height: 32px;
margin-right: 2px; margin-right: 2px;
border-color: #004f28;
background: #00582c;
}
.back-button:hover,
.back-button:focus {
border-color: #004523;
background: #004a25;
} }
.board-state { .board-state {
@@ -414,17 +388,10 @@ onMounted(() => {
} }
.article-submit-row .legacy-button { .article-submit-row .legacy-button {
--legacy-button-height: 35.5px;
width: auto; width: auto;
min-height: 35.5px;
margin-right: 10.5px; margin-right: 10.5px;
margin-left: 10.5px; margin-left: 10.5px;
transition: none;
}
.article-submit-row .legacy-button:hover,
.article-submit-row .legacy-button:focus,
.article-submit-row .legacy-button:active {
border-color: transparent;
} }
.article-frame { .article-frame {
@@ -513,8 +480,8 @@ onMounted(() => {
} }
.submit-comment { .submit-comment {
--legacy-button-height: 29.375px;
width: 83.333px; width: 83.333px;
min-height: 29.375px;
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
flex: 0 0 auto; flex: 0 0 auto;
@@ -529,9 +496,9 @@ onMounted(() => {
} }
.bottom-bar .back-button { .bottom-bar .back-button {
--legacy-button-height: 35.5px;
display: inline-block; display: inline-block;
width: 71px; width: 71px;
height: 35.5px;
margin: 0; margin: 0;
padding-right: 6px; padding-right: 6px;
padding-left: 6px; padding-left: 6px;
+17 -11
View File
@@ -318,8 +318,19 @@ const repeatTurns = async (amount: number) => {
<template> <template>
<main class="chief-page"> <main class="chief-page">
<header class="chief-top legacy-bg0"> <header class="chief-top legacy-bg0">
<button class="chief-nav" type="button" @click="router.push('/')">돌아가기</button> <button
<button class="chief-nav" @click="loadChiefCenter">갱신</button> class="legacy-button legacy-button--navigation legacy-button--fixed-height chief-nav"
type="button"
@click="router.push('/')"
>
돌아가기
</button>
<button
class="legacy-button legacy-button--navigation legacy-button--fixed-height chief-nav"
@click="loadChiefCenter"
>
갱신
</button>
<h1>사령부</h1> <h1>사령부</h1>
<div></div> <div></div>
<div></div> <div></div>
@@ -442,7 +453,9 @@ const repeatTurns = async (amount: number) => {
</template> </template>
</div> </div>
<footer class="chief-footer legacy-bg0"> <footer class="chief-footer legacy-bg0">
<button class="chief-nav" type="button" @click="router.push('/')">돌아가기</button> <button class="legacy-button legacy-button--navigation chief-nav" type="button" @click="router.push('/')">
돌아가기
</button>
</footer> </footer>
</main> </main>
</template> </template>
@@ -643,19 +656,12 @@ const repeatTurns = async (amount: number) => {
text-align: center; text-align: center;
} }
.chief-nav { .chief-nav {
--legacy-button-height: 32px;
box-sizing: border-box; box-sizing: border-box;
height: 32px;
margin-right: 2px; margin-right: 2px;
border: 0;
border-radius: 3px;
display: grid; display: grid;
place-items: center; place-items: center;
background: #00582c;
color: #fff;
font: inherit;
font-weight: 700;
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
.layout-desktop { .layout-desktop {
display: block; display: block;
+17 -8
View File
@@ -53,7 +53,13 @@ onMounted(async () => {
<template> <template>
<main class="global-page legacy-bg0"> <main class="global-page legacy-bg0">
<header class="legacy-title"> <header class="legacy-title">
<button type="button" @click="goBack">돌아가기</button><strong>중원 정보</strong> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button"
@click="goBack"
>
돌아가기</button
><strong>중원 정보</strong>
</header> </header>
<p v-if="error" class="error">{{ error }}</p> <p v-if="error" class="error">{{ error }}</p>
<section v-if="data" class="section"> <section v-if="data" class="section">
@@ -147,7 +153,15 @@ onMounted(async () => {
</div> </div>
</div> </div>
</section> </section>
<footer class="legacy-title footer"><button type="button" @click="goBack">돌아가기</button></footer> <footer class="legacy-title footer">
<button
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button"
@click="goBack"
>
돌아가기
</button>
</footer>
<button class="legacy-compat-button" type="button" tabindex="-1" aria-hidden="true" /> <button class="legacy-compat-button" type="button" tabindex="-1" aria-hidden="true" />
</main> </main>
</template> </template>
@@ -178,15 +192,10 @@ onMounted(async () => {
line-height: 32px; line-height: 32px;
} }
.legacy-title button { .legacy-title button {
--legacy-button-height: 32px;
position: absolute; position: absolute;
inset: 0 auto 0 0; inset: 0 auto 0 0;
width: 88px; width: 88px;
border: 1px solid #0a9960;
border-radius: 0 0 4px;
background: #087f45;
color: #fff;
font-weight: 700;
cursor: pointer;
} }
.legacy-compat-button { .legacy-compat-button {
display: none; display: none;
+5 -4
View File
@@ -449,10 +449,12 @@ onMounted(() => {
<template> <template>
<header class="top-back-bar legacy-bg0"> <header class="top-back-bar legacy-bg0">
<RouterLink class="top-button legacy-button legacy-button--navigation" to="/">돌아가기</RouterLink> <RouterLink class="top-button legacy-button legacy-button--navigation legacy-button--fixed-height" to="/"
>돌아가기</RouterLink
>
<strong>유산 관리</strong> <strong>유산 관리</strong>
<button <button
class="top-button legacy-button legacy-button--navigation" class="top-button legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button" type="button"
:disabled="loading" :disabled="loading"
@click="loadStatus" @click="loadStatus"
@@ -786,8 +788,7 @@ onMounted(() => {
} }
.top-button { .top-button {
height: 32px; --legacy-button-height: 32px;
min-height: 32px;
padding: 2px 8px; padding: 2px 8px;
text-decoration: none; text-decoration: none;
} }
@@ -338,7 +338,13 @@ onMounted(() => {
<main id="nation-betting-container" class="nation-betting-page legacy-bg0"> <main id="nation-betting-container" class="nation-betting-page legacy-bg0">
<header class="legacy-top-bar"> <header class="legacy-top-bar">
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="legacy-button legacy-button--navigation" type="button" @click="navigate">돌아가기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button"
@click="navigate"
>
돌아가기
</button>
</RouterLink> </RouterLink>
<div></div> <div></div>
<h1>국가 베팅장</h1> <h1>국가 베팅장</h1>
@@ -395,7 +401,7 @@ onMounted(() => {
<div>사용 포인트: {{ usedAmount.toLocaleString('ko-KR') }}</div> <div>사용 포인트: {{ usedAmount.toLocaleString('ko-KR') }}</div>
<div>대상: {{ selectionLabel(selectedKey) }}</div> <div>대상: {{ selectionLabel(selectedKey) }}</div>
<input v-model.number="amount" aria-label="베팅 금액" type="number" min="10" max="1000" step="10" /> <input v-model.number="amount" aria-label="베팅 금액" type="number" min="10" max="1000" step="10" />
<button type="submit" :disabled="submitting">베팅</button> <button class="legacy-button legacy-button--primary" type="submit" :disabled="submitting">베팅</button>
</form> </form>
<div class="payout-table"> <div class="payout-table">
@@ -475,33 +481,10 @@ onMounted(() => {
text-align: center; text-align: center;
} }
.legacy-nav-button, .legacy-top-bar .legacy-button {
.betting-form button { --legacy-button-height: 32px;
height: 32px;
border: 0;
background: #00582c;
color: #fff;
font-weight: 600;
text-align: center;
text-decoration: none;
cursor: pointer;
} }
.legacy-nav-button {
display: grid;
place-items: center;
margin-right: 2px;
}
.legacy-nav-button:hover,
.legacy-nav-button:focus,
.betting-form button:hover,
.betting-form button:focus {
filter: brightness(1.18);
}
.legacy-nav-button:focus-visible,
.betting-form button:focus-visible,
.betting-candidate:focus-visible, .betting-candidate:focus-visible,
.betting-item:focus-visible { .betting-item:focus-visible {
outline: 2px solid #f39c12; outline: 2px solid #f39c12;
@@ -662,9 +645,8 @@ onMounted(() => {
padding-top: 20px; padding-top: 20px;
} }
.betting-footer .legacy-nav-button { .betting-footer .legacy-button {
width: 90px; width: 90px;
height: 35.5px;
} }
.betting-notice, .betting-notice,
@@ -536,14 +536,25 @@ onBeforeUnmount(() => {
<main class="general-page legacy-bg0"> <main class="general-page legacy-bg0">
<header class="top-bar"> <header class="top-bar">
<span class="left-actions"> <span class="left-actions">
<button class="top-button nation-button" @click="router.push('/')">돌아가기</button> <button
<button class="top-button nation-button" :disabled="loading" @click="load">갱신</button> class="legacy-button legacy-button--navigation legacy-button--fixed-height top-button nation-button"
@click="router.push('/')"
>
돌아가기
</button>
<button
class="legacy-button legacy-button--navigation legacy-button--fixed-height top-button nation-button"
:disabled="loading"
@click="load"
>
갱신
</button>
</span> </span>
<strong>세력 장수</strong> <strong>세력 장수</strong>
<span class="right-actions"> <span class="right-actions">
<span class="dropdown"> <span class="dropdown">
<button <button
class="top-button mode-button" class="legacy-button legacy-button--primary legacy-button--fixed-height top-button mode-button"
:aria-expanded="viewMenuOpen" :aria-expanded="viewMenuOpen"
@click=" @click="
viewMenuOpen = !viewMenuOpen; viewMenuOpen = !viewMenuOpen;
@@ -576,7 +587,7 @@ onBeforeUnmount(() => {
</span> </span>
<span class="dropdown"> <span class="dropdown">
<button <button
class="top-button columns-button" class="legacy-button legacy-button--info legacy-button--fixed-height top-button columns-button"
:aria-expanded="columnMenuOpen" :aria-expanded="columnMenuOpen"
@click=" @click="
columnMenuOpen = !columnMenuOpen; columnMenuOpen = !columnMenuOpen;
@@ -850,45 +861,30 @@ onBeforeUnmount(() => {
right: 0; right: 0;
} }
.top-button { .top-button {
--legacy-button-height: 32px;
display: inline-flex; display: inline-flex;
width: 89px; width: 89px;
height: 32px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
border: 0;
border-right: 1px solid #151515;
border-radius: 3px;
color: #fff;
font-size: 14px; font-size: 14px;
font-weight: 700;
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
.nation-button { .nation-button {
background: #006c48; --legacy-button-bg: #006c48;
} --legacy-button-border: #005d3e;
.nation-button:hover {
background: #00855a;
} }
.mode-button { .mode-button {
background: #375a7f; --legacy-button-bg: #375a7f;
border-bottom: 0 solid #325172; --legacy-button-border: #325172;
}
.mode-button:hover,
.mode-button[aria-expanded='true'],
.mode-button:active {
border-bottom-width: 3px;
} }
.mode-button, .mode-button,
.columns-button { .columns-button {
width: 90px; width: 90px;
} }
.columns-button { .columns-button {
background: #3297cf; --legacy-button-bg: #3297cf;
} --legacy-button-border: #2b80b0;
.columns-button:hover {
filter: brightness(1.12);
} }
.dropdown { .dropdown {
position: relative; position: relative;
@@ -173,7 +173,9 @@ onMounted(() => void loadStratFinan());
<template> <template>
<main id="finance-container" class="page-finance"> <main id="finance-container" class="page-finance">
<nav class="top-back-bar"> <nav class="top-back-bar">
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink> <RouterLink class="legacy-button legacy-button--navigation legacy-button--fixed-height" to="/"
>돌아가기</RouterLink
>
<span /> <span />
<strong>내무부</strong> <strong>내무부</strong>
<span /> <span />
@@ -224,7 +226,7 @@ onMounted(() => void loadStratFinan());
<span> <span>
<button <button
v-if="canEdit && !editingNationMsg" v-if="canEdit && !editingNationMsg"
class="message-button" class="legacy-button legacy-button--secondary message-button"
type="button" type="button"
@click="enableEditNationMsg" @click="enableEditNationMsg"
> >
@@ -232,7 +234,7 @@ onMounted(() => void loadStratFinan());
</button> </button>
<button <button
v-if="canEdit && editingNationMsg" v-if="canEdit && editingNationMsg"
class="policy-submit" class="legacy-button legacy-button--primary policy-submit"
type="button" type="button"
@click="saveNationMsg" @click="saveNationMsg"
> >
@@ -240,7 +242,7 @@ onMounted(() => void loadStratFinan());
</button> </button>
<button <button
v-if="canEdit && editingNationMsg" v-if="canEdit && editingNationMsg"
class="policy-cancel" class="legacy-button legacy-button--secondary policy-cancel"
type="button" type="button"
@click="rollbackNationMsg" @click="rollbackNationMsg"
> >
@@ -249,24 +251,15 @@ onMounted(() => void loadStratFinan());
</span> </span>
</header> </header>
<div v-if="!editingNationMsg" class="message-preview" v-html="nationMsg || '내용 없음'" /> <div v-if="!editingNationMsg" class="message-preview" v-html="nationMsg || '내용 없음'" />
<LegacyHtmlEditor <LegacyHtmlEditor v-else v-model="nationMsgDraft" :max-length="16384" aria-label="국가 방침" />
v-else
v-model="nationMsgDraft"
:max-length="16384"
aria-label="국가 방침"
/>
</section> </section>
<section <section id="scout-message-form" class="message-form" :class="{ 'message-form--editing': editingScoutMsg }">
id="scout-message-form"
class="message-form"
:class="{ 'message-form--editing': editingScoutMsg }"
>
<header class="green-header"> <header class="green-header">
<span>임관 권유</span> <span>임관 권유</span>
<span> <span>
<button <button
v-if="canEdit && !editingScoutMsg" v-if="canEdit && !editingScoutMsg"
class="message-button" class="legacy-button legacy-button--secondary message-button"
type="button" type="button"
@click="enableEditScoutMsg" @click="enableEditScoutMsg"
> >
@@ -274,7 +267,7 @@ onMounted(() => void loadStratFinan());
</button> </button>
<button <button
v-if="canEdit && editingScoutMsg" v-if="canEdit && editingScoutMsg"
class="policy-submit" class="legacy-button legacy-button--primary policy-submit"
type="button" type="button"
@click="saveScoutMsg" @click="saveScoutMsg"
> >
@@ -282,7 +275,7 @@ onMounted(() => void loadStratFinan());
</button> </button>
<button <button
v-if="canEdit && editingScoutMsg" v-if="canEdit && editingScoutMsg"
class="policy-cancel" class="legacy-button legacy-button--secondary policy-cancel"
type="button" type="button"
@click="rollbackScoutMsg" @click="rollbackScoutMsg"
> >
@@ -292,12 +285,7 @@ onMounted(() => void loadStratFinan());
</header> </header>
<div class="scout-limit">870px x 200px를 넘어서는 내용은 표시되지 않습니다.</div> <div class="scout-limit">870px x 200px를 넘어서는 내용은 표시되지 않습니다.</div>
<div v-if="!editingScoutMsg" class="message-preview scout-preview" v-html="scoutMsg || '내용 없음'" /> <div v-if="!editingScoutMsg" class="message-preview scout-preview" v-html="scoutMsg || '내용 없음'" />
<LegacyHtmlEditor <LegacyHtmlEditor v-else v-model="scoutMsgDraft" :max-length="1000" aria-label="임관 권유" />
v-else
v-model="scoutMsgDraft"
:max-length="1000"
aria-label="임관 권유"
/>
</section> </section>
<div class="finance-title">예산&amp;정책</div> <div class="finance-title">예산&amp;정책</div>
@@ -362,10 +350,17 @@ onMounted(() => void loadStratFinan());
<input v-model.number="policy.rate" aria-label="세율" type="number" min="5" max="30" /><span <input v-model.number="policy.rate" aria-label="세율" type="number" min="5" max="30" /><span
>%</span >%</span
> >
<button v-if="canEdit" class="policy-submit" type="button" @click="setRate">변경</button>
<button <button
v-if="canEdit" v-if="canEdit"
class="policy-cancel" class="legacy-button legacy-button--primary policy-submit"
type="button"
@click="setRate"
>
변경
</button>
<button
v-if="canEdit"
class="legacy-button legacy-button--secondary policy-cancel"
type="button" type="button"
@click="policy.rate = oldPolicy.rate" @click="policy.rate = oldPolicy.rate"
> >
@@ -379,10 +374,17 @@ onMounted(() => void loadStratFinan());
<input v-model.number="policy.bill" aria-label="지급률" type="number" min="20" max="200" /><span <input v-model.number="policy.bill" aria-label="지급률" type="number" min="20" max="200" /><span
>%</span >%</span
> >
<button v-if="canEdit" class="policy-submit" type="button" @click="setBill">변경</button>
<button <button
v-if="canEdit" v-if="canEdit"
class="policy-cancel" class="legacy-button legacy-button--primary policy-submit"
type="button"
@click="setBill"
>
변경
</button>
<button
v-if="canEdit"
class="legacy-button legacy-button--secondary policy-cancel"
type="button" type="button"
@click="policy.bill = oldPolicy.bill" @click="policy.bill = oldPolicy.bill"
> >
@@ -400,10 +402,17 @@ onMounted(() => void loadStratFinan());
min="1" min="1"
max="99" max="99"
/><span></span> /><span></span>
<button v-if="canEdit" class="policy-submit" type="button" @click="setSecretLimit">변경</button>
<button <button
v-if="canEdit" v-if="canEdit"
class="policy-cancel" class="legacy-button legacy-button--primary policy-submit"
type="button"
@click="setSecretLimit"
>
변경
</button>
<button
v-if="canEdit"
class="legacy-button legacy-button--secondary policy-cancel"
type="button" type="button"
@click="policy.secretLimit = oldPolicy.secretLimit" @click="policy.secretLimit = oldPolicy.secretLimit"
> >
@@ -438,7 +447,7 @@ onMounted(() => void loadStratFinan());
<input v-for="index in 4" :key="`compat-input-${index}`" type="hidden" /> <input v-for="index in 4" :key="`compat-input-${index}`" type="hidden" />
</div> </div>
<footer class="bottom-bar"> <footer class="bottom-bar">
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink> <RouterLink class="legacy-button legacy-button--navigation" to="/">돌아가기</RouterLink>
</footer> </footer>
</template> </template>
</main> </main>
@@ -472,32 +481,8 @@ onMounted(() => void loadStratFinan());
.top-back-bar button { .top-back-bar button {
grid-column: 5; grid-column: 5;
} }
.legacy-button, .top-back-bar .legacy-button {
button { --legacy-button-height: 32px;
box-sizing: border-box;
border: 1px solid #00502a;
border-radius: 4px;
padding: 5.25px 10.5px;
color: #fff;
background: #00582c;
font: inherit;
line-height: 21px;
text-align: center;
text-decoration: none;
cursor: pointer;
}
.message-button,
.policy-cancel {
border-color: #6c757d;
background: #6c757d;
}
.policy-submit {
border-color: #325172;
background: #375a7f;
}
button:hover,
.legacy-button:hover {
filter: brightness(1.2);
} }
button:focus-visible, button:focus-visible,
.legacy-button:focus-visible, .legacy-button:focus-visible,
+4 -10
View File
@@ -355,13 +355,14 @@ const submitPriority = async (section: PrioritySectionKey) => {
error.value = `설정하지 못했습니다: ${resolveErrorMessage(caught)}`; error.value = `설정하지 못했습니다: ${resolveErrorMessage(caught)}`;
} }
}; };
</script> </script>
<template> <template>
<main id="npc-policy-page" class="npc-page"> <main id="npc-policy-page" class="npc-page">
<nav class="top-back-bar legacy-bg0"> <nav class="top-back-bar legacy-bg0">
<RouterLink class="back-button" to="/">돌아가기</RouterLink> <RouterLink class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button" to="/"
>돌아가기</RouterLink
>
<strong>NPC 정책</strong> <strong>NPC 정책</strong>
</nav> </nav>
@@ -579,21 +580,14 @@ const submitPriority = async (section: PrioritySectionKey) => {
} }
.back-button { .back-button {
--legacy-button-height: 32px;
position: absolute; position: absolute;
inset: 0 auto 0 0; inset: 0 auto 0 0;
width: 88px; width: 88px;
color: #fff;
background: #087f45;
border: 1px solid #0a9960;
border-radius: 0 0 4px;
font-weight: 700;
line-height: 30px;
text-decoration: none; text-decoration: none;
} }
.back-button:hover,
.back-button:focus-visible { .back-button:focus-visible {
background: #0a9960;
outline: 2px solid #fff; outline: 2px solid #fff;
outline-offset: -2px; outline-offset: -2px;
} }
+7 -4
View File
@@ -209,11 +209,15 @@ onMounted(() => {
<template> <template>
<main id="container" class="pageVote bg0"> <main id="container" class="pageVote bg0">
<header class="back_bar bg0"> <header class="back_bar bg0">
<button class="legacy-button legacy-button--navigation back_btn" type="button" @click="router.push('/')"> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height back_btn"
type="button"
@click="router.push('/')"
>
닫기 닫기
</button> </button>
<button <button
class="legacy-button legacy-button--navigation reload_btn" class="legacy-button legacy-button--navigation legacy-button--fixed-height reload_btn"
type="button" type="button"
:disabled="loading" :disabled="loading"
@click="reloadVote" @click="reloadVote"
@@ -453,8 +457,7 @@ onMounted(() => {
.back_btn, .back_btn,
.reload_btn { .reload_btn {
height: 32px; --legacy-button-height: 32px;
min-height: 32px;
margin-right: 2px; margin-right: 2px;
font-weight: 600; font-weight: 600;
} }
+18 -9
View File
@@ -190,10 +190,16 @@ const start = async () => {
<TournamentPageHeader class="bg0" active-page="tournament" title="삼모전 토너먼트" /> <TournamentPageHeader class="bg0" active-page="tournament" title="삼모전 토너먼트" />
<section class="toolbar bg0"> <section class="toolbar bg0">
<button type="button" @click="load">갱신</button> <button
class="legacy-button legacy-button--secondary legacy-button--fixed-height"
type="button"
@click="load"
>
갱신
</button>
<button <button
type="button" type="button"
class="join-button" class="legacy-button legacy-button--navigation legacy-button--fixed-height join-button"
:disabled="snapshot?.state?.stage !== 1 || isParticipant" :disabled="snapshot?.state?.stage !== 1 || isParticipant"
@click="join" @click="join"
> >
@@ -382,7 +388,9 @@ const start = async () => {
<input type="hidden" name="tournamentAction" value="join" /> <input type="hidden" name="tournamentAction" value="join" />
<footer class="tournament-footer bg0"> <footer class="tournament-footer bg0">
<RouterLink v-slot="{ navigate }" custom to="/"> <RouterLink v-slot="{ navigate }" custom to="/">
<button class="close-button" type="button" @click="navigate"> 닫기</button> <button class="legacy-button legacy-button--navigation close-button" type="button" @click="navigate">
닫기
</button>
</RouterLink> </RouterLink>
<small> <small>
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit 삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit
@@ -439,8 +447,8 @@ const start = async () => {
padding: 1px; padding: 1px;
} }
.toolbar button { .toolbar button {
--legacy-button-height: 44px;
min-width: 72px; min-width: 72px;
height: 44px;
padding: 10px 16px; padding: 10px 16px;
font-size: 14px; font-size: 14px;
} }
@@ -451,7 +459,7 @@ const start = async () => {
min-height: 32px; min-height: 32px;
padding: 5px; padding: 5px;
} }
button { button:not(.legacy-button) {
height: 35.5px; height: 35.5px;
margin: 0 2px; margin: 0 2px;
border: 1px solid #666; border: 1px solid #666;
@@ -460,16 +468,17 @@ button {
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
} }
button:hover, button:not(.legacy-button):hover,
button:focus { button:not(.legacy-button):focus {
filter: brightness(1.25); filter: brightness(1.25);
} }
button:focus-visible { button:not(.legacy-button):focus-visible {
outline: 2px solid #f39c12; outline: 2px solid #f39c12;
outline-offset: 1px; outline-offset: 1px;
} }
.join-button { .join-button {
background: #8a5b13; --legacy-button-bg: #8a5b13;
--legacy-button-border: #704a0f;
} }
.operator-row span { .operator-row span {
color: orange; color: orange;
+3 -4
View File
@@ -179,14 +179,14 @@ onMounted(() => {
<main id="container" class="legacy-troop-page"> <main id="container" class="legacy-troop-page">
<header class="topBackBar bg0"> <header class="topBackBar bg0">
<button <button
class="legacy-button legacy-button--navigation legacyNavButton backLink" class="legacy-button legacy-button--navigation legacy-button--fixed-height legacyNavButton backLink"
type="button" type="button"
@click="router.push('/')" @click="router.push('/')"
> >
돌아가기 돌아가기
</button> </button>
<button <button
class="legacy-button legacy-button--navigation legacyNavButton reloadButton" class="legacy-button legacy-button--navigation legacy-button--fixed-height legacyNavButton reloadButton"
type="button" type="button"
:disabled="loading" :disabled="loading"
@click="refresh" @click="refresh"
@@ -395,8 +395,7 @@ onMounted(() => {
} }
.legacyNavButton { .legacyNavButton {
height: 32px; --legacy-button-height: 32px;
min-height: 32px;
margin-right: 2px; margin-right: 2px;
font-weight: 600; font-weight: 600;
} }
+12 -15
View File
@@ -137,11 +137,18 @@ onMounted(async () => {
<main id="yearbook-container" class="yearbook-page legacy-bg0"> <main id="yearbook-container" class="yearbook-page legacy-bg0">
<header class="yearbook-title legacy-bg0"> <header class="yearbook-title legacy-bg0">
<strong> </strong> <strong> </strong>
<button class="legacy-button close-button" type="button" @click="closePage"> 닫기</button> <button
class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
type="button"
@click="closePage"
>
닫기
</button>
<span class="settings-menu"> <span class="settings-menu">
<button <button
class="legacy-button legacy-button--navigation" class="legacy-button legacy-button--navigation legacy-button--fixed-height"
type="button" type="button"
:aria-expanded="settingsOpen"
@click="settingsOpen = !settingsOpen" @click="settingsOpen = !settingsOpen"
> >
설정 설정
@@ -235,7 +242,7 @@ onMounted(async () => {
</section> </section>
<footer class="yearbook-footer"> <footer class="yearbook-footer">
<button class="legacy-button" type="button" @click="closePage">창 닫기</button> <button class="legacy-button legacy-button--navigation" type="button" @click="closePage">창 닫기</button>
</footer> </footer>
<div class="dropdown-compat-buttons" aria-hidden="true"> <div class="dropdown-compat-buttons" aria-hidden="true">
<button type="button" tabindex="-1" /><button type="button" tabindex="-1" /> <button type="button" tabindex="-1" /><button type="button" tabindex="-1" />
@@ -286,14 +293,14 @@ onMounted(async () => {
} }
.yearbook-title .close-button { .yearbook-title .close-button {
left: 0; left: 0;
height: 32px; --legacy-button-height: 32px;
} }
.settings-menu { .settings-menu {
right: 0; right: 0;
height: 32px; height: 32px;
} }
.settings-menu > .legacy-button { .settings-menu > .legacy-button {
height: 32px; --legacy-button-height: 32px;
} }
.settings-item { .settings-item {
position: absolute; position: absolute;
@@ -393,16 +400,6 @@ onMounted(async () => {
.dropdown-compat-buttons { .dropdown-compat-buttons {
display: none; display: none;
} }
.year-selector .legacy-button {
border: 0;
background: #444;
}
.settings-menu > .legacy-button,
.yearbook-title .close-button {
border: 0;
background: #00582c;
}
.log-content { .log-content {
min-height: 72px; min-height: 72px;
padding: 1px 0; padding: 1px 0;
@@ -0,0 +1,71 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { describe, it } from 'node:test';
const source = (relativePath: string) => readFile(path.resolve(import.meta.dirname, '../src', relativePath), 'utf8');
void describe('shared Lumen button family', () => {
void it('keeps fixed-height state compensation in the shared control layer', async () => {
const css = await source('assets/styles/legacy-controls.css');
assert.match(css, /\.legacy-button\.legacy-button--fixed-height\s*\{/u);
assert.match(css, /height:\s*calc\(var\(--legacy-button-height\) - 1px\)/u);
assert.match(css, /height:\s*calc\(var\(--legacy-button-height\) - 2px\)/u);
});
void it('connects every reported control to a semantic Lumen variant', async () => {
const [nationGenerals, tournamentHeader, reservedEditor] = await Promise.all([
source('views/NationGeneralsView.vue'),
source('components/tournament/TournamentPageHeader.vue'),
source('components/command/ReservedCommandEditor.vue'),
]);
assert.match(
nationGenerals,
/legacy-button legacy-button--navigation legacy-button--fixed-height top-button nation-button/u
);
assert.match(
nationGenerals,
/legacy-button legacy-button--primary legacy-button--fixed-height top-button mode-button/u
);
assert.match(
nationGenerals,
/legacy-button legacy-button--info legacy-button--fixed-height top-button columns-button/u
);
for (const label of ['돌아가기', '갱신', '보기 모드⌄', '열 선택⌄']) {
assert.match(nationGenerals, new RegExp(label, 'u'));
}
for (const label of ['토너먼트', '베팅장', '창 닫기']) {
assert.match(tournamentHeader, new RegExp(`legacy-button[\\s\\S]{0,260}${label}`, 'u'));
}
assert.match(reservedEditor, /legacy-button legacy-button--info legacy-button--fixed-height select-command/u);
assert.match(reservedEditor, / /u);
});
void it('uses the same family for audited Ref TopBackBar navigation controls', async () => {
const files = [
'views/AuctionView.vue',
'views/BattleCenterView.vue',
'views/BoardView.vue',
'views/ChiefCenterView.vue',
'views/GlobalInfoView.vue',
'views/InheritView.vue',
'views/NationBettingView.vue',
'views/NationStratFinanView.vue',
'views/NpcControlView.vue',
'views/SurveyView.vue',
'views/TroopView.vue',
'views/YearbookView.vue',
];
const contents = await Promise.all(files.map(source));
for (const [index, content] of contents.entries()) {
assert.match(
content,
/legacy-button legacy-button--navigation/u,
`${files[index]} must opt its raised navigation control into the shared family`
);
}
});
});
+4 -3
View File
@@ -77,10 +77,11 @@ validating one representative button.
The primitive does not set a fixed `min-height`: Ref's 35.5px default height is The primitive does not set a fixed `min-height`: Ref's 35.5px default height is
the result of line-height, padding, and the 4px edge, so it naturally becomes the result of line-height, padding, and the 4px edge, so it naturally becomes
34.5px/33.5px while the 1px/2px top margin keeps the bottom coordinate fixed. 34.5px/33.5px while the 1px/2px top margin keeps the bottom coordinate fixed.
Only a fixed-height owner such as the mobile bottom bar supplies explicit A fixed row opts into `.legacy-button--fixed-height` and supplies only
45px/44px/43px state compensation. `--legacy-button-height`; the shared layer derives the hover/active heights so
every owner keeps the same bottom-coordinate contract.
Only layout belongs in the SFC: width, grid column, fixed-height compensation, Only layout belongs in the SFC: width, grid column, the fixed height variable,
margins required by the page, and breakpoint-specific placement. Color base margins required by the page, and breakpoint-specific placement. Color base
variables may be supplied by the owner for dynamic nation/scenario colors, but variables may be supplied by the owner for dynamic nation/scenario colors, but
border construction, font weight, hover/focus/active, and disabled presentation border construction, font weight, hover/focus/active, and disabled presentation
@@ -190,6 +190,84 @@ test.beforeEach(async ({ page }) => {
await installFixture(page); await installFixture(page);
}); });
test('tournament controls share fixed Lumen state geometry on desktop and mobile', async ({ page }, testInfo) => {
const evidence: Record<string, unknown> = {};
for (const viewport of [
{ width: 1024, height: 768 },
{ width: 500, height: 900 },
]) {
await page.setViewportSize(viewport);
await page.goto(`${gameUrl}/che/tournament`);
await expect(page.getByText('삼모전 토너먼트')).toBeVisible();
const controls = [
page.getByRole('tab', { name: '토너먼트' }),
page.getByRole('tab', { name: '베팅장' }),
page.getByRole('button', { name: '창 닫기' }).first(),
page.getByRole('button', { name: '갱신' }),
];
const viewportEvidence: Record<string, unknown> = {};
for (const control of controls) {
const label = (await control.textContent())?.trim() ?? 'unknown';
await control.scrollIntoViewIfNeeded();
await expect(control).toHaveClass(/legacy-button--fixed-height/u);
const measure = () =>
control.evaluate((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
top: rect.top,
bottom: rect.bottom,
height: rect.height,
marginTop: style.marginTop,
borderBottomWidth: style.borderBottomWidth,
borderRadius: style.borderRadius,
backgroundColor: style.backgroundColor,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
};
});
await page.mouse.move(viewport.width - 1, viewport.height - 1);
const base = await measure();
expect(base).toMatchObject({
height: 44,
marginTop: '0px',
borderBottomWidth: '4px',
borderRadius: '5.25px',
fontSize: '14px',
});
expect(base.fontFamily).toContain('Pretendard');
await control.hover();
const hover = await measure();
expect(hover).toMatchObject({ height: 43, marginTop: '1px', borderBottomWidth: '3px' });
expect(hover.bottom).toBeCloseTo(base.bottom, 2);
const box = await control.boundingBox();
if (!box) throw new Error(`${label} tournament control is not measurable`);
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
const active = await measure();
expect(active).toMatchObject({ height: 42, marginTop: '2px', borderBottomWidth: '2px' });
expect(active.bottom).toBeCloseTo(base.bottom, 2);
await page.mouse.move(viewport.width - 1, viewport.height - 1);
await page.mouse.up();
viewportEvidence[label] = { default: base, hover, active };
}
evidence[`${viewport.width}x${viewport.height}`] = viewportEvidence;
if (artifactDir) {
await page.screenshot({
path: `${artifactDir}/core-tournament-buttons-${viewport.width}.png`,
fullPage: true,
});
}
}
await testInfo.attach('tournament-button-geometry', {
body: JSON.stringify(evidence, null, 2),
contentType: 'application/json',
});
});
test('tournament keeps the legacy 2000px bracket and 250px group geometry', async ({ page }) => { test('tournament keeps the legacy 2000px bracket and 250px group geometry', async ({ page }) => {
await page.setViewportSize({ width: 2200, height: 1000 }); await page.setViewportSize({ width: 2200, height: 1000 });
await page.goto(`${gameUrl}/che/tournament`); await page.goto(`${gameUrl}/che/tournament`);
@@ -210,6 +288,9 @@ test('tournament keeps the legacy 2000px bracket and 250px group geometry', asyn
groupWidth: groupTable.getBoundingClientRect().width, groupWidth: groupTable.getBoundingClientRect().width,
titleHeight: title.getBoundingClientRect().height, titleHeight: title.getBoundingClientRect().height,
refreshHeight: refresh.getBoundingClientRect().height, refreshHeight: refresh.getBoundingClientRect().height,
refreshBottom: refresh.getBoundingClientRect().bottom,
refreshMarginTop: getComputedStyle(refresh).marginTop,
refreshEdge: getComputedStyle(refresh).borderBottomWidth,
refreshRadius: getComputedStyle(refresh).borderRadius, refreshRadius: getComputedStyle(refresh).borderRadius,
fontFamily: style.fontFamily, fontFamily: style.fontFamily,
fontSize: style.fontSize, fontSize: style.fontSize,
@@ -222,7 +303,9 @@ test('tournament keeps the legacy 2000px bracket and 250px group geometry', asyn
candidateWidth: 125, candidateWidth: 125,
groupWidth: 250, groupWidth: 250,
titleHeight: 55.6875, titleHeight: 55.6875,
refreshHeight: 35.5, refreshHeight: 44,
refreshMarginTop: '0px',
refreshEdge: '4px',
refreshRadius: '5.25px', refreshRadius: '5.25px',
fontSize: '14px', fontSize: '14px',
}); });
@@ -231,12 +314,39 @@ test('tournament keeps the legacy 2000px bracket and 250px group geometry', asyn
if (artifactDir) await page.screenshot({ path: `${artifactDir}/core-tournament.png`, fullPage: true }); if (artifactDir) await page.screenshot({ path: `${artifactDir}/core-tournament.png`, fullPage: true });
const refresh = page.getByRole('button', { name: '갱신' }); const refresh = page.getByRole('button', { name: '갱신' });
const before = await refresh.evaluate((element) => getComputedStyle(element).filter);
await refresh.hover(); await refresh.hover();
const hover = await refresh.evaluate((element) => getComputedStyle(element).filter); const hover = await refresh.evaluate((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
bottom: rect.bottom,
height: rect.height,
marginTop: style.marginTop,
borderBottomWidth: style.borderBottomWidth,
filter: style.filter,
};
});
expect(hover).toMatchObject({ height: 43, marginTop: '1px', borderBottomWidth: '3px', filter: 'none' });
expect(hover.bottom).toBeCloseTo(geometry.refreshBottom, 2);
const box = await refresh.boundingBox();
if (!box) throw new Error('tournament refresh is not measurable');
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
const active = await refresh.evaluate((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
bottom: rect.bottom,
height: rect.height,
marginTop: style.marginTop,
borderBottomWidth: style.borderBottomWidth,
};
});
expect(active).toMatchObject({ height: 42, marginTop: '2px', borderBottomWidth: '2px' });
expect(active.bottom).toBeCloseTo(geometry.refreshBottom, 2);
await page.mouse.up();
await refresh.focus(); await refresh.focus();
await expect(refresh).toBeFocused(); await expect(refresh).toBeFocused();
expect(hover).not.toBe(before);
}); });
test('tournament keeps the fixed legacy canvas at a 1024px viewport', async ({ page }) => { test('tournament keeps the fixed legacy canvas at a 1024px viewport', async ({ page }) => {