fix(frontend): unify legacy button composition
This commit is contained in:
@@ -259,13 +259,14 @@ test('renders the legacy desktop grid with matching computed geometry and states
|
||||
});
|
||||
|
||||
const kickButton = page.getByRole('button', { name: '부대원 추방...' }).first();
|
||||
expect(await kickButton.evaluate((button) => getComputedStyle(button).backgroundColor)).toBe('rgb(68, 68, 68)');
|
||||
await kickButton.hover();
|
||||
const hoverStyle = await kickButton.evaluate((button) => ({
|
||||
cursor: getComputedStyle(button).cursor,
|
||||
filter: getComputedStyle(button).filter,
|
||||
borderBottomWidth: getComputedStyle(button).borderBottomWidth,
|
||||
}));
|
||||
expect(hoverStyle.cursor).toBe('pointer');
|
||||
expect(hoverStyle.filter).not.toBe('none');
|
||||
expect(hoverStyle.borderBottomWidth).toBe('3px');
|
||||
|
||||
await page.locator('.troopMember').nth(1).hover();
|
||||
await expect(page.getByRole('tooltip')).toContainText('조운');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import 'tailwindcss';
|
||||
@import './styles/tokens.css';
|
||||
@import './styles/legacy-controls.css';
|
||||
@import './styles/game-shell.css';
|
||||
@import './styles/ref-shell.css';
|
||||
|
||||
@@ -44,33 +45,3 @@ textarea {
|
||||
background-color: #172a52;
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.legacy-button {
|
||||
display: inline-block;
|
||||
border: 1px solid #12195b;
|
||||
border-radius: 3px;
|
||||
background: #141c65;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legacy-button:hover,
|
||||
.legacy-button:focus,
|
||||
.legacy-button:active {
|
||||
border-color: #0f154c;
|
||||
background: #101651;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.legacy-button:focus-visible {
|
||||
outline: 2px solid #f39c12;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.legacy-button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
.legacy-button {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--sammo-button-base1-border);
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
background: var(--sammo-button-base1-bg);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legacy-button:hover,
|
||||
.legacy-button:focus,
|
||||
.legacy-button:active {
|
||||
border-color: var(--sammo-button-base1-hover-border);
|
||||
background: var(--sammo-button-base1-hover-bg);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.legacy-button:focus-visible {
|
||||
outline: 2px solid var(--sammo-color-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.legacy-button:disabled,
|
||||
.legacy-button[aria-disabled='true'] {
|
||||
cursor: default;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ref Bootstrap 5.2 + Lumen button family. The modifier describes the legacy
|
||||
* semantic role; width and placement remain in the owning scoped component.
|
||||
*/
|
||||
.legacy-button:is(
|
||||
.legacy-button--primary,
|
||||
.legacy-button--secondary,
|
||||
.legacy-button--danger,
|
||||
.legacy-button--info,
|
||||
.legacy-button--navigation
|
||||
) {
|
||||
--legacy-button-bg: var(--sammo-button-primary-bg);
|
||||
--legacy-button-border: var(--sammo-button-primary-border);
|
||||
min-height: 35.5px;
|
||||
margin-top: 0;
|
||||
border-color: var(--legacy-button-border);
|
||||
border-style: solid;
|
||||
border-width: 0 1px 4px;
|
||||
border-radius: 5.25px;
|
||||
padding: 5.25px 10.5px;
|
||||
background: var(--legacy-button-bg);
|
||||
color: #fff;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--secondary {
|
||||
--legacy-button-bg: var(--sammo-button-secondary-bg);
|
||||
--legacy-button-border: var(--sammo-button-secondary-border);
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--danger {
|
||||
--legacy-button-bg: var(--sammo-button-danger-bg);
|
||||
--legacy-button-border: var(--sammo-button-danger-border);
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--info {
|
||||
--legacy-button-bg: var(--sammo-button-info-bg);
|
||||
--legacy-button-border: var(--sammo-button-info-border);
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--navigation {
|
||||
--legacy-button-bg: var(--sammo-button-navigation-bg);
|
||||
--legacy-button-border: var(--sammo-button-navigation-border);
|
||||
}
|
||||
|
||||
.legacy-button:is(
|
||||
.legacy-button--primary,
|
||||
.legacy-button--secondary,
|
||||
.legacy-button--danger,
|
||||
.legacy-button--info,
|
||||
.legacy-button--navigation
|
||||
):not(:disabled, [aria-disabled='true']):hover {
|
||||
margin-top: 1px;
|
||||
border-color: var(--legacy-button-border);
|
||||
border-bottom-width: 3px;
|
||||
background: var(--legacy-button-bg);
|
||||
}
|
||||
|
||||
.legacy-button:is(
|
||||
.legacy-button--primary,
|
||||
.legacy-button--secondary,
|
||||
.legacy-button--danger,
|
||||
.legacy-button--info,
|
||||
.legacy-button--navigation
|
||||
):not(:disabled, [aria-disabled='true']):active {
|
||||
margin-top: 2px;
|
||||
border-color: var(--legacy-button-border);
|
||||
border-bottom-width: 2px;
|
||||
background: var(--legacy-button-bg);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.legacy-button:is(
|
||||
.legacy-button--primary,
|
||||
.legacy-button--secondary,
|
||||
.legacy-button--danger,
|
||||
.legacy-button--info,
|
||||
.legacy-button--navigation
|
||||
):focus {
|
||||
border-color: var(--legacy-button-border);
|
||||
background: var(--legacy-button-bg);
|
||||
}
|
||||
@@ -6,6 +6,20 @@
|
||||
--sammo-color-border: rgba(201, 164, 90, 0.4);
|
||||
--sammo-color-action-bg: rgba(16, 16, 16, 0.6);
|
||||
--sammo-color-error: #f5b7b1;
|
||||
--sammo-button-base1-bg: #141c65;
|
||||
--sammo-button-base1-border: #12195b;
|
||||
--sammo-button-base1-hover-bg: #101651;
|
||||
--sammo-button-base1-hover-border: #0f154c;
|
||||
--sammo-button-primary-bg: #375a7f;
|
||||
--sammo-button-primary-border: #325172;
|
||||
--sammo-button-secondary-bg: #444;
|
||||
--sammo-button-secondary-border: #3d3d3d;
|
||||
--sammo-button-danger-bg: #e74c3c;
|
||||
--sammo-button-danger-border: #d04436;
|
||||
--sammo-button-info-bg: #3498db;
|
||||
--sammo-button-info-border: #2f89c5;
|
||||
--sammo-button-navigation-bg: #00582c;
|
||||
--sammo-button-navigation-border: #004f28;
|
||||
--sammo-texture-walnut: url('/image/game/back_walnut.jpg');
|
||||
--sammo-texture-green: url('/image/game/back_green.jpg');
|
||||
--sammo-texture-blue: url('/image/game/back_blue.jpg');
|
||||
|
||||
@@ -427,9 +427,16 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<header class="top-back-bar legacy-bg0">
|
||||
<RouterLink class="top-button legacy-button" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="top-button legacy-button legacy-button--navigation" to="/">돌아가기</RouterLink>
|
||||
<strong>유산 관리</strong>
|
||||
<button class="top-button legacy-button" type="button" :disabled="loading" @click="loadStatus">갱신</button>
|
||||
<button
|
||||
class="top-button legacy-button legacy-button--navigation"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="loadStatus"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main id="container" class="inherit-page legacy-bg0">
|
||||
@@ -490,7 +497,7 @@ onMounted(() => {
|
||||
></small
|
||||
>
|
||||
<button
|
||||
class="legacy-button buy-button"
|
||||
class="legacy-button legacy-button--primary buy-button"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="reserveSpecialWar"
|
||||
>
|
||||
@@ -524,7 +531,7 @@ onMounted(() => {
|
||||
}}</small
|
||||
>
|
||||
<button
|
||||
class="legacy-button buy-button"
|
||||
class="legacy-button legacy-button--primary buy-button"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="openUniqueAuction"
|
||||
>
|
||||
@@ -539,7 +546,11 @@ onMounted(() => {
|
||||
<article class="shop-item simple-item">
|
||||
<div class="control-row">
|
||||
<span>랜덤 턴 초기화</span
|
||||
><button class="legacy-button" :disabled="isUnited || actionBusy" @click="resetTurnTime">
|
||||
><button
|
||||
class="legacy-button legacy-button--primary"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="resetTurnTime"
|
||||
>
|
||||
구입
|
||||
</button>
|
||||
</div>
|
||||
@@ -552,7 +563,11 @@ onMounted(() => {
|
||||
<article class="shop-item simple-item">
|
||||
<div class="control-row">
|
||||
<span>랜덤 유니크 획득</span
|
||||
><button class="legacy-button" :disabled="isUnited || actionBusy" @click="buyRandomUnique">
|
||||
><button
|
||||
class="legacy-button legacy-button--primary"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="buyRandomUnique"
|
||||
>
|
||||
구입
|
||||
</button>
|
||||
</div>
|
||||
@@ -565,7 +580,11 @@ onMounted(() => {
|
||||
<article class="shop-item simple-item">
|
||||
<div class="control-row">
|
||||
<span>즉시 전투 특기 초기화</span
|
||||
><button class="legacy-button" :disabled="isUnited || actionBusy" @click="resetSpecialWar">
|
||||
><button
|
||||
class="legacy-button legacy-button--primary"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="resetSpecialWar"
|
||||
>
|
||||
구입
|
||||
</button>
|
||||
</div>
|
||||
@@ -596,14 +615,14 @@ onMounted(() => {
|
||||
>
|
||||
<div class="dual-buttons">
|
||||
<button
|
||||
class="legacy-button secondary"
|
||||
class="legacy-button legacy-button--secondary"
|
||||
:disabled="actionBusy"
|
||||
@click="buffTargets[key] = status.buffLevels[key] ?? 0"
|
||||
>
|
||||
리셋
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button"
|
||||
class="legacy-button legacy-button--primary"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="buyHiddenBuff(key)"
|
||||
>
|
||||
@@ -635,7 +654,11 @@ onMounted(() => {
|
||||
>필요 포인트: {{ status.inheritConst.inheritCheckOwnerPoint }}</b
|
||||
></small
|
||||
>
|
||||
<button class="legacy-button buy-button" :disabled="isUnited || actionBusy" @click="checkOwner">
|
||||
<button
|
||||
class="legacy-button legacy-button--primary buy-button"
|
||||
:disabled="isUnited || actionBusy"
|
||||
@click="checkOwner"
|
||||
>
|
||||
소유자 찾기
|
||||
</button>
|
||||
<p v-if="ownerResult" class="owner-result">
|
||||
@@ -689,7 +712,7 @@ onMounted(() => {
|
||||
><br /><span v-if="resetStatErrors.length">{{ resetStatErrors[0] }}</span></small
|
||||
>
|
||||
<button
|
||||
class="legacy-button buy-button"
|
||||
class="legacy-button legacy-button--primary buy-button"
|
||||
:disabled="isUnited || actionBusy || resetStatErrors.length > 0"
|
||||
@click="resetStats"
|
||||
>
|
||||
@@ -707,7 +730,11 @@ onMounted(() => {
|
||||
<small>[{{ new Date(entry.createdAt).toLocaleString('ko-KR') }}]</small>
|
||||
<span>{{ entry.text }}</span>
|
||||
</div>
|
||||
<button class="legacy-button more-button" :disabled="logLoading || logEnd" @click="loadLogs()">
|
||||
<button
|
||||
class="legacy-button legacy-button--secondary more-button"
|
||||
:disabled="logLoading || logEnd"
|
||||
@click="loadLogs()"
|
||||
>
|
||||
더 가져오기
|
||||
</button>
|
||||
</section>
|
||||
@@ -872,11 +899,6 @@ onMounted(() => {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.legacy-button.secondary {
|
||||
border-color: #51585e;
|
||||
background: #5c636a;
|
||||
}
|
||||
|
||||
.bottom-actions .shop-item:first-child {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
@@ -213,8 +213,13 @@ onMounted(() => {
|
||||
<template>
|
||||
<main id="container" class="pageVote bg0">
|
||||
<header class="back_bar bg0">
|
||||
<RouterLink class="btn btn-sammo-base2 back_btn" to="/">창 닫기</RouterLink>
|
||||
<button class="btn btn-sammo-base2 reload_btn" type="button" :disabled="loading" @click="reloadVote">
|
||||
<RouterLink class="legacy-button legacy-button--navigation back_btn" to="/">창 닫기</RouterLink>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation reload_btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="reloadVote"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<h2 class="title"></h2>
|
||||
@@ -305,7 +310,9 @@ onMounted(() => {
|
||||
<template v-if="canVote">
|
||||
<td class="text-center">투표</td>
|
||||
<td colspan="2">
|
||||
<button class="btn btn-primary vote-submit" @click="submitVote">투표</button>
|
||||
<button class="legacy-button legacy-button--secondary vote-submit" @click="submitVote">
|
||||
투표
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
<td v-else colspan="3" class="text-center">결산</td>
|
||||
@@ -348,7 +355,11 @@ onMounted(() => {
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button class="btn btn-primary comment-submit" type="submit">댓글 달기</button></td>
|
||||
<td>
|
||||
<button class="legacy-button legacy-button--secondary comment-submit" type="submit">
|
||||
댓글 달기
|
||||
</button>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<input v-model="myComment" class="form-control" maxlength="200" aria-label="댓글" />
|
||||
</td>
|
||||
@@ -395,13 +406,15 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-submit">
|
||||
<button class="btn btn-primary" type="button" @click="submitNewVote">제출</button>
|
||||
<button class="legacy-button legacy-button--secondary" type="button" @click="submitNewVote">
|
||||
제출
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<footer class="bottom_bar bg0">
|
||||
<RouterLink class="btn btn-sammo-base2 back_btn" to="/">창 닫기</RouterLink>
|
||||
<RouterLink class="legacy-button legacy-button--navigation back_btn" to="/">창 닫기</RouterLink>
|
||||
</footer>
|
||||
</main>
|
||||
</template>
|
||||
@@ -438,54 +451,19 @@ onMounted(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 35.5px;
|
||||
padding: 5.25px 10.5px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5.25px;
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid #8ab4f8;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn-sammo-base2 {
|
||||
.back_btn,
|
||||
.reload_btn {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
margin-right: 2px;
|
||||
border-color: #004f28;
|
||||
background: #00582c;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.back_bar .btn-sammo-base2 {
|
||||
.back_bar .back_btn,
|
||||
.back_bar .reload_btn {
|
||||
width: 88px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-color: #0d6efd;
|
||||
background: #0d6efd;
|
||||
}
|
||||
|
||||
#vote-title {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.5;
|
||||
|
||||
@@ -177,8 +177,15 @@ onMounted(() => {
|
||||
<template>
|
||||
<main id="container" class="legacy-troop-page">
|
||||
<header class="topBackBar bg0">
|
||||
<RouterLink class="btn legacyNavButton backLink" to="/">돌아가기</RouterLink>
|
||||
<button class="btn legacyNavButton reloadButton" type="button" :disabled="loading" @click="refresh">
|
||||
<RouterLink class="legacy-button legacy-button--navigation legacyNavButton backLink" to="/"
|
||||
>돌아가기</RouterLink
|
||||
>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacyNavButton reloadButton"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="refresh"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<h2>부대 편성</h2>
|
||||
@@ -242,25 +249,33 @@ onMounted(() => {
|
||||
|
||||
<div class="troopAction">
|
||||
<div v-if="dialogKind === null || dialogTroopId !== troop.id" class="actionButtons">
|
||||
<button v-if="data.me.troopId === 0" class="btn btn-primary" @click="joinTroop(troop)">
|
||||
<button
|
||||
v-if="data.me.troopId === 0"
|
||||
class="legacy-button legacy-button--primary"
|
||||
@click="joinTroop(troop)"
|
||||
>
|
||||
부대 탑승
|
||||
</button>
|
||||
<button
|
||||
v-if="data.me.troopId === troop.id"
|
||||
class="btn"
|
||||
:class="data.me.id === data.me.troopId ? 'btn-danger' : 'btn-primary'"
|
||||
class="legacy-button"
|
||||
:class="data.me.id === data.me.troopId ? 'legacy-button--danger' : 'legacy-button--primary'"
|
||||
@click="exitTroop(troop)"
|
||||
>
|
||||
{{ data.me.id === data.me.troopId ? '부대 해산' : '부대 탈퇴' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="data.me.troopId === troop.id && data.me.id === data.me.troopId"
|
||||
class="btn btn-secondary"
|
||||
class="legacy-button legacy-button--secondary"
|
||||
@click="openKick(troop)"
|
||||
>
|
||||
부대원 추방...
|
||||
</button>
|
||||
<button v-if="data.permission >= 4" class="btn btn-info" @click="openRename(troop)">
|
||||
<button
|
||||
v-if="data.permission >= 4"
|
||||
class="legacy-button legacy-button--info"
|
||||
@click="openRename(troop)"
|
||||
>
|
||||
부대명 변경...
|
||||
</button>
|
||||
</div>
|
||||
@@ -270,10 +285,12 @@ onMounted(() => {
|
||||
<input v-model.trim="editName" class="formControl" type="text" aria-label="새 부대명" />
|
||||
</div>
|
||||
<div class="subBtnCancel">
|
||||
<button class="btn btn-secondary" @click="closeDialog">취소</button>
|
||||
<button class="legacy-button legacy-button--secondary" @click="closeDialog">취소</button>
|
||||
</div>
|
||||
<div class="subBtnOK">
|
||||
<button class="btn btn-primary" @click="renameTroop(troop)">변경</button>
|
||||
<button class="legacy-button legacy-button--primary" @click="renameTroop(troop)">
|
||||
변경
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="subDialog kickDialog">
|
||||
@@ -290,10 +307,12 @@ onMounted(() => {
|
||||
</select>
|
||||
</div>
|
||||
<div class="subBtnCancel">
|
||||
<button class="btn btn-secondary" @click="closeDialog">취소</button>
|
||||
<button class="legacy-button legacy-button--secondary" @click="closeDialog">취소</button>
|
||||
</div>
|
||||
<div class="subBtnOK">
|
||||
<button class="btn btn-primary" @click="kickMember(troop)">추방</button>
|
||||
<button class="legacy-button legacy-button--primary" @click="kickMember(troop)">
|
||||
추방
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,12 +324,16 @@ onMounted(() => {
|
||||
<div v-if="data.me.troopId === 0" class="makeNewTroop">
|
||||
<div class="makeTitle bg1 center">부대 창설</div>
|
||||
<input v-model.trim="createName" class="formControl troopNameField" type="text" aria-label="부대명" />
|
||||
<button class="btn btn-secondary createButton" @click="makeTroop">부대 창설</button>
|
||||
<button class="legacy-button legacy-button--secondary createButton" @click="makeTroop">
|
||||
부대 창설
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="bottomBar bg0">
|
||||
<RouterLink class="btn legacyNavButton backLink" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="legacy-button legacy-button--navigation legacyNavButton backLink" to="/"
|
||||
>돌아가기</RouterLink
|
||||
>
|
||||
<div></div>
|
||||
</footer>
|
||||
<div v-if="popupMember" id="generalPopup" :style="{ top: `${popupTop}px` }" role="tooltip">
|
||||
@@ -359,14 +382,11 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn.legacyNavButton {
|
||||
.legacyNavButton {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
margin-right: 2px;
|
||||
border-color: #004f28;
|
||||
color: #fff;
|
||||
background: #00582c;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notice {
|
||||
@@ -490,51 +510,6 @@ onMounted(() => {
|
||||
grid-row: 1/3;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-height: 31px;
|
||||
padding: 0.2em 0.75em;
|
||||
border: 1px solid #777;
|
||||
border-radius: 4px;
|
||||
color: #eee;
|
||||
background: #555;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid #8ab4f8;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-color: #0d6efd;
|
||||
background: #0d6efd;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
border-color: #dc3545;
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
border-color: #0dcaf0;
|
||||
color: #111;
|
||||
background: #0dcaf0;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
border-color: #6c757d;
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.formControl {
|
||||
width: 100%;
|
||||
min-height: 31px;
|
||||
@@ -561,6 +536,9 @@ onMounted(() => {
|
||||
.bottomBar .legacyNavButton {
|
||||
width: 70px;
|
||||
margin: 0;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (min-width: 501px) {
|
||||
@@ -624,7 +602,7 @@ onMounted(() => {
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.legacy-troop-page {
|
||||
width: 511px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#generalPopup {
|
||||
|
||||
@@ -22,6 +22,39 @@ loads the following layers:
|
||||
4. Scoped SFC styles: page-specific grids, fixed table dimensions, selectors,
|
||||
and state styling. These remain closest to the DOM contract they implement.
|
||||
|
||||
`styles/legacy-controls.css` is the shared control layer between tokens and the
|
||||
two shell layers. It owns only control geometry and state rules that are proven
|
||||
identical in the Ref Bootstrap/Lumen family. A page still owns control width,
|
||||
grid placement, and any visual family that is not Bootstrap/Lumen.
|
||||
|
||||
## Button composition
|
||||
|
||||
Choose the Ref visual family before choosing a semantic color. Buttons from
|
||||
different historical families are not made identical merely because they have
|
||||
the same label.
|
||||
|
||||
| Ref family | Core composition | Use |
|
||||
| ---------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------- |
|
||||
| Bootstrap/Lumen primary | `.legacy-button.legacy-button--primary` | commit, purchase, submit, or another affirmative mutation |
|
||||
| Bootstrap/Lumen secondary | `.legacy-button.legacy-button--secondary` | reset, cancel, neutral toggle, or load-more |
|
||||
| Bootstrap/Lumen danger | `.legacy-button.legacy-button--danger` | destructive action only when Ref uses `variant="danger"` |
|
||||
| Bootstrap/Lumen info | `.legacy-button.legacy-button--info` | informational or edit action only when Ref uses `variant="info"` |
|
||||
| `btn-sammo-base2` navigation | `.legacy-button.legacy-button--navigation` | page back/close and paired reload controls |
|
||||
| page-specific/native control | feature-namespaced scoped class | only when Ref computed geometry or interaction differs from the Bootstrap/Lumen family |
|
||||
|
||||
The base class supplies accessible link/button normalization and the historical
|
||||
`base1` fallback used by already measured screens. New Bootstrap/Lumen controls
|
||||
must add an explicit semantic modifier; do not infer a mutation role from a
|
||||
label such as `구입` in page CSS. A disabled control keeps its semantic color
|
||||
and uses the shared opacity/cursor state. Hover and active use the Ref Lumen
|
||||
bottom-border movement rather than an unrelated brightness filter.
|
||||
|
||||
Only layout belongs in the SFC: width, grid column, margins required by the
|
||||
page, and breakpoint-specific placement. Color, border, font weight,
|
||||
hover/focus/active, and disabled presentation belong in
|
||||
`legacy-controls.css` when the Ref family is shared. Generic `.btn`, `button`,
|
||||
or `.primary` rules must not be promoted globally.
|
||||
|
||||
## Class naming
|
||||
|
||||
- `.game-shell`, `.game-shell__header`, `.game-shell__actions`: flexible
|
||||
|
||||
@@ -167,6 +167,8 @@ test.describe('inheritance management legacy parity', () => {
|
||||
const container = getComputedStyle(document.querySelector<HTMLElement>('#container')!);
|
||||
const title = getComputedStyle(document.querySelector<HTMLElement>('.section-title')!);
|
||||
const button = getComputedStyle(document.querySelector<HTMLElement>('.buy-button')!);
|
||||
const navigation = getComputedStyle(document.querySelector<HTMLElement>('.top-button')!);
|
||||
const secondary = getComputedStyle(document.querySelector<HTMLElement>('.dual-buttons button')!);
|
||||
return {
|
||||
container: rect('#container'),
|
||||
firstPoint: rect('#inherit_sum'),
|
||||
@@ -175,6 +177,9 @@ test.describe('inheritance management legacy parity', () => {
|
||||
backgroundImage: container.backgroundImage,
|
||||
titleBackgroundImage: title.backgroundImage,
|
||||
buttonBackground: button.backgroundColor,
|
||||
buttonBorderBottomWidth: button.borderBottomWidth,
|
||||
navigationBackground: navigation.backgroundColor,
|
||||
secondaryBackground: secondary.backgroundColor,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -185,14 +190,42 @@ test.describe('inheritance management legacy parity', () => {
|
||||
expect(desktop.fontSize).toBe('14px');
|
||||
expect(desktop.backgroundImage).toContain('back_walnut.jpg');
|
||||
expect(desktop.titleBackgroundImage).toContain('back_green.jpg');
|
||||
expect(desktop.buttonBackground).toBe('rgb(55, 90, 127)');
|
||||
expect(desktop.buttonBorderBottomWidth).toBe('4px');
|
||||
expect(desktop.navigationBackground).toBe('rgb(0, 88, 44)');
|
||||
expect(desktop.secondaryBackground).toBe('rgb(68, 68, 68)');
|
||||
|
||||
const buyButton = page.locator('.buy-button').first();
|
||||
const beforeHover = await buyButton.evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
const beforeHover = await buyButton.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
return { background: style.backgroundColor, borderBottomWidth: style.borderBottomWidth };
|
||||
});
|
||||
await buyButton.hover();
|
||||
const afterHover = await buyButton.evaluate((element) => getComputedStyle(element).backgroundColor);
|
||||
expect(afterHover).not.toBe(beforeHover);
|
||||
const afterHover = await buyButton.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
return { background: style.backgroundColor, borderBottomWidth: style.borderBottomWidth };
|
||||
});
|
||||
expect(afterHover.background).toBe(beforeHover.background);
|
||||
expect(afterHover.borderBottomWidth).toBe('3px');
|
||||
|
||||
await buyButton.hover({ position: { x: 70, y: 20 } });
|
||||
await page.mouse.down();
|
||||
await expect
|
||||
.poll(() => buyButton.evaluate((element) => getComputedStyle(element).borderBottomWidth))
|
||||
.toBe('2px');
|
||||
await page.mouse.up();
|
||||
|
||||
await buyButton.focus();
|
||||
await expect(buyButton).toBeFocused();
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await expect(buyButton).toBeFocused();
|
||||
await expect.poll(() => buyButton.evaluate((element) => getComputedStyle(element).outlineStyle)).toBe('solid');
|
||||
|
||||
await buyButton.evaluate((element) => element.setAttribute('disabled', ''));
|
||||
await expect.poll(() => buyButton.evaluate((element) => getComputedStyle(element).opacity)).toBe('0.65');
|
||||
await buyButton.evaluate((element) => element.removeAttribute('disabled'));
|
||||
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
|
||||
|
||||
if (artifactRoot) {
|
||||
await page.screenshot({ path: resolve(artifactRoot, 'inherit-core-desktop.png'), fullPage: true });
|
||||
@@ -214,6 +247,10 @@ test.describe('inheritance management legacy parity', () => {
|
||||
expect(mobile.containerWidth).toBe(500);
|
||||
expect(mobile.firstWidth).toBeCloseTo(482, 0);
|
||||
expect(mobile.stacked).toBe(true);
|
||||
|
||||
if (artifactRoot) {
|
||||
await page.screenshot({ path: resolve(artifactRoot, 'inherit-core-mobile.png'), fullPage: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('submits a legacy buff purchase and refreshes status and logs', async ({ page }) => {
|
||||
|
||||
@@ -1014,6 +1014,12 @@ test.describe('survey legacy parity', () => {
|
||||
fontSize: getComputedStyle(title).fontSize,
|
||||
backgroundImage: getComputedStyle(title).backgroundImage,
|
||||
},
|
||||
voteButton: {
|
||||
backgroundColor: getComputedStyle(document.querySelector<HTMLElement>('.vote-submit')!)
|
||||
.backgroundColor,
|
||||
borderBottomWidth: getComputedStyle(document.querySelector<HTMLElement>('.vote-submit')!)
|
||||
.borderBottomWidth,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1027,6 +1033,10 @@ test.describe('survey legacy parity', () => {
|
||||
expect(geometry.title.height).toBeCloseTo(37.8, 0);
|
||||
expect(geometry.title.fontSize).toBe('25.2px');
|
||||
expect(geometry.title.backgroundImage).toContain('back_blue.jpg');
|
||||
expect(geometry.voteButton).toEqual({
|
||||
backgroundColor: 'rgb(68, 68, 68)',
|
||||
borderBottomWidth: '4px',
|
||||
});
|
||||
|
||||
const secondOption = page.locator('#v-vote-1');
|
||||
await secondOption.check();
|
||||
@@ -1035,9 +1045,9 @@ test.describe('survey legacy parity', () => {
|
||||
await expect(secondOption).toBeFocused();
|
||||
|
||||
const voteButton = page.getByRole('button', { name: '투표', exact: true });
|
||||
const beforeHover = await voteButton.evaluate((element) => getComputedStyle(element).filter);
|
||||
const beforeHover = await voteButton.evaluate((element) => getComputedStyle(element).borderBottomWidth);
|
||||
await voteButton.hover();
|
||||
const afterHover = await voteButton.evaluate((element) => getComputedStyle(element).filter);
|
||||
const afterHover = await voteButton.evaluate((element) => getComputedStyle(element).borderBottomWidth);
|
||||
expect(afterHover).not.toBe(beforeHover);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user