Structure legacy-compatible frontend CSS
This commit is contained in:
@@ -220,7 +220,21 @@ const installApi = async (page: Page, state: BoardFixture) => {
|
||||
}
|
||||
if (operation === 'turns.getCommandTable') return response({ general: [], nation: [] });
|
||||
if (operation === 'messages.getRecent') return response(emptyMessages);
|
||||
if (operation === 'turns.reserved.getGeneral') return response([]);
|
||||
if (operation === 'messages.getContacts') return response({ nation: [] });
|
||||
if (operation === 'turns.reserved.getGeneral') return response({ turns: [], revision: 0 });
|
||||
if (operation === 'turns.reserved.getNation') return response({ turns: [], revision: 0 });
|
||||
if (operation === 'general.getRecentRecords')
|
||||
return response({ global: [], general: [], history: [] });
|
||||
if (operation === 'general.getFrontStatus')
|
||||
return response({
|
||||
onlineUserCount: 1,
|
||||
onlineNations: '아국(1)',
|
||||
onlineGenerals: '유비',
|
||||
nationNotice: '',
|
||||
lastExecuted: null,
|
||||
latestVote: null,
|
||||
});
|
||||
if (operation === 'tournament.getState') return response({ stage: 0 });
|
||||
return errorResponse(operation, `Unhandled board fixture operation: ${operation}`);
|
||||
});
|
||||
await route.fulfill({
|
||||
@@ -426,7 +440,7 @@ test('denies direct secret-room rendering and disables its in-game menu for an o
|
||||
state.requests.length = 0;
|
||||
await page.goto('');
|
||||
await expect(page.getByRole('link', { name: '회의실', exact: true })).toBeVisible();
|
||||
await expect(page.locator('.header-actions [aria-disabled="true"]').filter({ hasText: '기밀실' })).toBeVisible();
|
||||
await expect(page.locator('.game-shell__actions [aria-disabled="true"]').filter({ hasText: '기밀실' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: '기밀실', exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -85,6 +85,22 @@ const generalContext = {
|
||||
};
|
||||
const turns = (count: number) =>
|
||||
Array.from({ length: count }, (_, index) => ({ index, action: '휴식', args: {} }));
|
||||
const chiefCenter = {
|
||||
me: { id: 1, officerLevel: 5, nationId: 1 },
|
||||
nation: { id: 1, name: '아국', level: 1 },
|
||||
currentYear: 200,
|
||||
currentMonth: 1,
|
||||
turnTermMinutes: 10,
|
||||
maxTurns: 12,
|
||||
chiefs: [12, 10, 8, 6, 11, 9, 7, 5].map((officerLevel) => ({
|
||||
officerLevel,
|
||||
name: officerLevel === 5 ? '장수' : null,
|
||||
npcState: officerLevel === 5 ? 0 : null,
|
||||
turnTime: null,
|
||||
revision: 0,
|
||||
turns: turns(12),
|
||||
})),
|
||||
};
|
||||
|
||||
const install = async (page: Page, rejectGeneral = false) => {
|
||||
const requests: unknown[] = [];
|
||||
@@ -115,8 +131,20 @@ const install = async (page: Page, rejectGeneral = false) => {
|
||||
spyList: {}, shownByGeneralList: [], myCity: 1, myNation: 1,
|
||||
});
|
||||
if (name === 'turns.getCommandTable') return response(commandTable);
|
||||
if (name === 'turns.reserved.getGeneral') return response(turns(30));
|
||||
if (name === 'turns.reserved.getNation') return response(turns(12));
|
||||
if (name === 'nation.getChiefCenter') return response(chiefCenter);
|
||||
if (name === 'turns.reserved.getGeneral') return response({ turns: turns(30), revision: 0 });
|
||||
if (name === 'turns.reserved.getNation') return response({ turns: turns(12), revision: 0 });
|
||||
if (name === 'general.getRecentRecords')
|
||||
return response({ global: [], general: [], history: [] });
|
||||
if (name === 'general.getFrontStatus')
|
||||
return response({
|
||||
onlineUserCount: 1,
|
||||
onlineNations: '아국(1)',
|
||||
onlineGenerals: '장수',
|
||||
nationNotice: '',
|
||||
lastExecuted: null,
|
||||
latestVote: null,
|
||||
});
|
||||
if (name === 'messages.getRecent') return response({
|
||||
private: [], national: [], public: [], diplomacy: [], sequence: -1,
|
||||
hasMore: { private: false, national: false, public: false, diplomacy: false },
|
||||
@@ -197,6 +225,70 @@ test('keeps the entered command visible and reports a server validation error',
|
||||
await page.locator('.reserved-section').filter({ hasText: '일반 예턴' })
|
||||
.getByRole('button', { name: '배치' }).first().click();
|
||||
|
||||
await expect(page.locator('.error')).toContainText('대상 도시를 선택할 수 없습니다.');
|
||||
await expect(page.getByRole('alert')).toContainText('대상 도시를 선택할 수 없습니다.');
|
||||
await expect(page.getByTestId('command-argument-form').locator('select')).toHaveValue('2');
|
||||
});
|
||||
|
||||
test('keeps the shared main and chief shell geometry and interaction states', async ({ page }) => {
|
||||
await install(page);
|
||||
await page.setViewportSize({ width: 1000, height: 900 });
|
||||
await page.goto('/');
|
||||
await expect(page.getByRole('heading', { name: '전장 현황' })).toBeVisible();
|
||||
|
||||
const mainGeometry = await page.locator('.main-page').evaluate((element) => {
|
||||
const header = element.querySelector<HTMLElement>('.game-shell__header')!;
|
||||
const title = element.querySelector<HTMLElement>('.game-shell__title')!;
|
||||
const subtitle = element.querySelector<HTMLElement>('.game-shell__subtitle')!;
|
||||
const action = element.querySelector<HTMLElement>('.game-shell__action')!;
|
||||
return {
|
||||
width: element.getBoundingClientRect().width,
|
||||
padding: getComputedStyle(element).padding,
|
||||
gap: getComputedStyle(element).gap,
|
||||
headerWidth: header.getBoundingClientRect().width,
|
||||
headerGap: getComputedStyle(header).gap,
|
||||
headerBorder: getComputedStyle(header).borderBottomWidth,
|
||||
headerPadding: getComputedStyle(header).paddingBottom,
|
||||
titleFontSize: getComputedStyle(title).fontSize,
|
||||
subtitleFontSize: getComputedStyle(subtitle).fontSize,
|
||||
actionPadding: getComputedStyle(action).padding,
|
||||
actionFontSize: getComputedStyle(action).fontSize,
|
||||
};
|
||||
});
|
||||
expect(mainGeometry).toEqual({
|
||||
width: 1000,
|
||||
padding: '24px',
|
||||
gap: '16px',
|
||||
headerWidth: 952,
|
||||
headerGap: '12px',
|
||||
headerBorder: '1px',
|
||||
headerPadding: '12px',
|
||||
titleFontSize: '22.4px',
|
||||
subtitleFontSize: '11.9px',
|
||||
actionPadding: '6px 12px',
|
||||
actionFontSize: '11.2px',
|
||||
});
|
||||
|
||||
const mainAction = page.getByRole('link', { name: '세력 정보' });
|
||||
await mainAction.hover();
|
||||
expect(await mainAction.evaluate((element) => getComputedStyle(element).cursor)).toBe('pointer');
|
||||
await mainAction.focus();
|
||||
expect(await mainAction.evaluate((element) => document.activeElement === element)).toBe(true);
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
await page.goto('chief-center');
|
||||
await expect(page.getByRole('heading', { name: '사령부', exact: true })).toBeVisible();
|
||||
const chiefDesktop = await page.locator('.chief-page').evaluate((element) => ({
|
||||
width: element.getBoundingClientRect().width,
|
||||
padding: getComputedStyle(element).padding,
|
||||
headerWidth: element.querySelector<HTMLElement>('.game-shell__header')!.getBoundingClientRect().width,
|
||||
}));
|
||||
expect(chiefDesktop).toEqual({ width: 1200, padding: '24px', headerWidth: 1152 });
|
||||
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
const chiefMobile = await page.locator('.chief-page').evaluate((element) => ({
|
||||
width: element.getBoundingClientRect().width,
|
||||
padding: getComputedStyle(element).padding,
|
||||
headerWidth: element.querySelector<HTMLElement>('.game-shell__header')!.getBoundingClientRect().width,
|
||||
}));
|
||||
expect(chiefMobile).toEqual({ width: 500, padding: '16px', headerWidth: 468 });
|
||||
});
|
||||
|
||||
@@ -392,5 +392,5 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
||||
const member: FixtureState = { permission: 'member', myset: 3, settingMutations: [], accessPages: [] };
|
||||
await install(page, member);
|
||||
await page.reload();
|
||||
await expect(page.locator('.error')).toContainText('권한이 부족합니다.');
|
||||
await expect(page.getByRole('alert')).toContainText('권한이 부족합니다.');
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@import 'tailwindcss';
|
||||
@import './styles/tokens.css';
|
||||
@import './styles/game-shell.css';
|
||||
@import './styles/ref-shell.css';
|
||||
|
||||
@theme {
|
||||
--color-sammo-ink: #000;
|
||||
@@ -15,7 +18,7 @@ body {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@@ -29,17 +32,17 @@ textarea {
|
||||
|
||||
.legacy-bg0 {
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.legacy-bg1 {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.legacy-bg2 {
|
||||
background-color: #172a52;
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.legacy-button {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Shared shell for the main dashboard family.
|
||||
*
|
||||
* Keep geometry here limited to declarations that are identical on the main,
|
||||
* chief-center, and public screens. Page-specific grids remain scoped in their
|
||||
* owning SFC so a generic class cannot silently change a ref measurement.
|
||||
*/
|
||||
.game-shell {
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.game-shell__header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--sammo-color-border);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.game-shell__title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.game-shell__subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: var(--sammo-color-text-muted);
|
||||
}
|
||||
|
||||
.game-shell__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.game-shell__action {
|
||||
border: 1px solid var(--sammo-color-border);
|
||||
padding: 6px 12px;
|
||||
background: var(--sammo-color-action-bg);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-feedback--error {
|
||||
color: var(--sammo-color-error);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Fixed ref geometry. This namespace is intentionally separate from
|
||||
* .game-shell: its 1000/500px contract must win over generic responsive styles.
|
||||
*/
|
||||
.ref-shell {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
max-width: 1000px;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
background-color: #302016;
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
color: var(--sammo-color-text);
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ref-shell__topbar {
|
||||
position: relative;
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #666;
|
||||
background-color: #302016;
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.ref-shell__title {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ref-shell__subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ref-shell__actions {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ref-shell__control {
|
||||
min-height: 32px;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0;
|
||||
background: #303030;
|
||||
color: inherit;
|
||||
padding: 4px 8px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ref-feedback--error {
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #a33;
|
||||
color: #ff7777;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.ref-shell {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
:root {
|
||||
--sammo-font-sans: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
--sammo-color-text: #fff;
|
||||
--sammo-color-text-muted: rgba(232, 221, 196, 0.7);
|
||||
--sammo-color-accent: #f39c12;
|
||||
--sammo-color-border: rgba(201, 164, 90, 0.4);
|
||||
--sammo-color-action-bg: rgba(16, 16, 16, 0.6);
|
||||
--sammo-color-error: #f5b7b1;
|
||||
--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');
|
||||
}
|
||||
@@ -39,7 +39,7 @@ defineProps<{
|
||||
width: calc(100% + 48px);
|
||||
margin-left: -24px;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -266,7 +266,7 @@ const forwardResponse = (messageId: number, response: boolean) => {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 4fr) minmax(0, 1fr);
|
||||
grid-template-areas: 'mailbox input submit';
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
#mailbox_list-col {
|
||||
@@ -347,7 +347,7 @@ const forwardResponse = (messageId: number, response: boolean) => {
|
||||
align-items: center;
|
||||
outline: 1px solid gray;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ defineProps<{
|
||||
border-top: 1px solid gray;
|
||||
border-bottom: 1px solid gray;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
text-align: center;
|
||||
|
||||
@@ -28,7 +28,7 @@ defineProps<Props>();
|
||||
.panel-card {
|
||||
border: 1px solid gray;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -38,7 +38,7 @@ defineProps<Props>();
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid gray;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -478,21 +478,21 @@ onMounted(() => {
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
}
|
||||
.bg0 {
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
.bg1 {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.bg2 {
|
||||
background-color: #172a52;
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
.top-back-bar {
|
||||
width: 100%;
|
||||
|
||||
@@ -228,26 +228,26 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="battle-page">
|
||||
<header class="page-header">
|
||||
<main class="ref-shell battle-page">
|
||||
<header class="ref-shell__topbar">
|
||||
<div>
|
||||
<h1 class="page-title">감찰부</h1>
|
||||
<p class="page-subtitle">{{ statusLine }}</p>
|
||||
<h1 class="ref-shell__title">감찰부</h1>
|
||||
<p class="ref-shell__subtitle">{{ statusLine }}</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<RouterLink class="ghost" to="/">메인</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/finance">내무부</RouterLink>
|
||||
<button class="ghost" @click="loadBattleCenter">새로고침</button>
|
||||
<div class="ref-shell__actions">
|
||||
<RouterLink class="ref-shell__control" to="/">메인</RouterLink>
|
||||
<RouterLink class="ref-shell__control" to="/nation/finance">내무부</RouterLink>
|
||||
<button class="ref-shell__control" @click="loadBattleCenter">새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="error" class="ref-feedback ref-feedback--error" role="alert">{{ error }}</div>
|
||||
|
||||
<section class="layout-grid">
|
||||
<div class="stack">
|
||||
<PanelCard title="대상 선택" subtitle="정렬 기준과 장수를 선택합니다.">
|
||||
<div class="selector-row">
|
||||
<button class="ghost" @click="changeTargetByOffset(-1)">◀ 이전</button>
|
||||
<button class="ref-shell__control" @click="changeTargetByOffset(-1)">◀ 이전</button>
|
||||
<select v-model="orderBy" class="select-input">
|
||||
<option v-for="option in orderOptions" :key="option.key" :value="option.key">
|
||||
{{ option.label }}
|
||||
@@ -263,7 +263,7 @@ onMounted(() => {
|
||||
{{ formatGeneralLabel(general) }}
|
||||
</option>
|
||||
</select>
|
||||
<button class="ghost" @click="changeTargetByOffset(1)">다음 ▶</button>
|
||||
<button class="ref-shell__control" @click="changeTargetByOffset(1)">다음 ▶</button>
|
||||
</div>
|
||||
</PanelCard>
|
||||
|
||||
@@ -315,56 +315,6 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.battle-page {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
max-width: 1000px;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
color: #fff;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: relative;
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #666;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.layout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -393,17 +343,6 @@ onMounted(() => {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
min-height: 32px;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0;
|
||||
background: #303030;
|
||||
color: inherit;
|
||||
padding: 4px 8px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.general-meta {
|
||||
margin: 0;
|
||||
padding: 6px 8px;
|
||||
@@ -415,7 +354,7 @@ onMounted(() => {
|
||||
.battle-general-card {
|
||||
min-height: 292px;
|
||||
background-color: #172a52;
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.battle-general-name {
|
||||
@@ -485,13 +424,6 @@ onMounted(() => {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 5px 8px;
|
||||
color: #ff7777;
|
||||
border: 1px solid #a33;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* PanelCard is retained as a data wrapper, but its presentation follows the
|
||||
flat bootstrap rows used by the reference page. */
|
||||
:deep(.panel-card) {
|
||||
@@ -499,7 +431,7 @@ onMounted(() => {
|
||||
border: 1px solid #666;
|
||||
border-radius: 0;
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
box-shadow: none;
|
||||
}
|
||||
.stack:first-child :deep(.panel-card:first-child) {
|
||||
@@ -531,13 +463,10 @@ onMounted(() => {
|
||||
}
|
||||
:deep(.panel-header),
|
||||
.log-title {
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.battle-page {
|
||||
width: 500px;
|
||||
}
|
||||
.layout-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ watch(viewMode, () => {
|
||||
min-height: 100vh;
|
||||
margin: 0 auto 100px;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ const placeBet = async (targetId: number) => {
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
@@ -228,10 +228,10 @@ const placeBet = async (targetId: number) => {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bg0 {
|
||||
background: #3a2118 url('/image/game/back_walnut.jpg');
|
||||
background: #3a2118 var(--sammo-texture-walnut);
|
||||
}
|
||||
.bg2 {
|
||||
background: #142b42 url('/image/game/back_blue.jpg');
|
||||
background: #142b42 var(--sammo-texture-blue);
|
||||
}
|
||||
.title {
|
||||
height: 55.6875px;
|
||||
|
||||
@@ -233,21 +233,21 @@ onMounted(() => {
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.bg0 {
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.bg2 {
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.center {
|
||||
|
||||
@@ -388,19 +388,19 @@ const shiftTurns = async (amount: number) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="chief-page">
|
||||
<header class="page-header">
|
||||
<main class="game-shell chief-page">
|
||||
<header class="game-shell__header">
|
||||
<div>
|
||||
<h1 class="page-title">사령부</h1>
|
||||
<p class="page-subtitle">{{ statusLine }}</p>
|
||||
<h1 class="game-shell__title">사령부</h1>
|
||||
<p class="game-shell__subtitle">{{ statusLine }}</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<RouterLink class="ghost" to="/">메인</RouterLink>
|
||||
<button class="ghost" @click="loadChiefCenter">새로고침</button>
|
||||
<div class="game-shell__actions">
|
||||
<RouterLink class="game-shell__action" to="/">메인</RouterLink>
|
||||
<button class="game-shell__action" @click="loadChiefCenter">새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="error" class="game-feedback game-feedback--error" role="alert">{{ error }}</div>
|
||||
|
||||
<section v-if="loading && !data" class="loading-panel">
|
||||
<PanelCard title="사령부 로딩">
|
||||
@@ -558,53 +558,6 @@ const shiftTurns = async (amount: number) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chief-page {
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(232, 221, 196, 0.7);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
background: rgba(16, 16, 16, 0.6);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f5b7b1;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.layout-desktop {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2.2fr) minmax(280px, 1fr);
|
||||
|
||||
@@ -379,7 +379,7 @@ const generalImage = (general: General) => {
|
||||
.stats th,
|
||||
.generals th {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.city-title {
|
||||
text-align: center;
|
||||
|
||||
@@ -239,7 +239,7 @@ onMounted(() => {
|
||||
height: 18px;
|
||||
text-align: center;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.general-icon {
|
||||
display: inline;
|
||||
|
||||
@@ -189,7 +189,7 @@ onMounted(async () => {
|
||||
min-height: 100vh;
|
||||
margin: 0 auto 100px;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,67 +113,67 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="main-page">
|
||||
<header class="page-header">
|
||||
<main class="game-shell main-page">
|
||||
<header class="game-shell__header">
|
||||
<div>
|
||||
<h1 class="page-title">전장 현황</h1>
|
||||
<p class="page-subtitle">{{ statusLine }}</p>
|
||||
<h1 class="game-shell__title">전장 현황</h1>
|
||||
<p class="game-shell__subtitle">{{ statusLine }}</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<RouterLink v-if="boardAccess?.canMeeting" class="ghost" to="/board">회의실</RouterLink>
|
||||
<span v-else class="ghost disabled" aria-disabled="true">회의실</span>
|
||||
<RouterLink v-if="boardAccess?.canSecret" class="ghost" to="/board/secret">기밀실</RouterLink>
|
||||
<span v-else class="ghost disabled" aria-disabled="true">기밀실</span>
|
||||
<RouterLink class="ghost" to="/nation/info">세력 정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/cities">세력 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/global-info">중원 정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation-list">세력일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/general-list">장수일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/current-city">현재 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/generals">세력 장수</RouterLink>
|
||||
<RouterLink v-if="(boardAccess?.permission ?? -1) >= 1" class="ghost" to="/nation/secret"
|
||||
<div class="game-shell__actions">
|
||||
<RouterLink v-if="boardAccess?.canMeeting" class="game-shell__action" to="/board">회의실</RouterLink>
|
||||
<span v-else class="game-shell__action disabled" aria-disabled="true">회의실</span>
|
||||
<RouterLink v-if="boardAccess?.canSecret" class="game-shell__action" to="/board/secret">기밀실</RouterLink>
|
||||
<span v-else class="game-shell__action disabled" aria-disabled="true">기밀실</span>
|
||||
<RouterLink class="game-shell__action" to="/nation/info">세력 정보</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/nation/cities">세력 도시</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/global-info">중원 정보</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/nation-list">세력일람</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/general-list">장수일람</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/current-city">현재 도시</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/nation/generals">세력 장수</RouterLink>
|
||||
<RouterLink v-if="(boardAccess?.permission ?? -1) >= 1" class="game-shell__action" to="/nation/secret"
|
||||
>암행부</RouterLink
|
||||
>
|
||||
<span v-else class="ghost disabled" aria-disabled="true">암행부</span>
|
||||
<RouterLink class="ghost" to="/nation/personnel">인사부</RouterLink>
|
||||
<RouterLink class="ghost" to="/troop">부대 편성</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/finance">내무부</RouterLink>
|
||||
<RouterLink class="ghost" to="/diplomacy">외교부</RouterLink>
|
||||
<RouterLink class="ghost" to="/chief-center">사령부</RouterLink>
|
||||
<RouterLink class="ghost" to="/battle-center">감찰부</RouterLink>
|
||||
<RouterLink class="ghost" to="/best-general">명장일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/hall-of-fame">명예의 전당</RouterLink>
|
||||
<RouterLink class="ghost" to="/dynasty">왕조일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/yearbook">연감</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation-betting">천통국 베팅</RouterLink>
|
||||
<RouterLink class="ghost" to="/traffic">접속량정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/npc-list">빙의일람</RouterLink>
|
||||
<a class="ghost" href="/xe/community" target="_blank" rel="noopener">게시판</a>
|
||||
<RouterLink class="ghost" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||
<RouterLink class="ghost" to="/my-page">내 정보&설정</RouterLink>
|
||||
<RouterLink class="ghost" to="/past-plays">내 지난 플레이</RouterLink>
|
||||
<RouterLink class="ghost" :class="{ highlight: tournamentStage === 1 }" to="/tournament"
|
||||
<span v-else class="game-shell__action disabled" aria-disabled="true">암행부</span>
|
||||
<RouterLink class="game-shell__action" to="/nation/personnel">인사부</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/troop">부대 편성</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/nation/finance">내무부</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/diplomacy">외교부</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/chief-center">사령부</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/battle-center">감찰부</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/best-general">명장일람</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/hall-of-fame">명예의 전당</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/dynasty">왕조일람</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/yearbook">연감</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/nation-betting">천통국 베팅</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/traffic">접속량정보</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/npc-list">빙의일람</RouterLink>
|
||||
<a class="game-shell__action" href="/xe/community" target="_blank" rel="noopener">게시판</a>
|
||||
<RouterLink class="game-shell__action" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/my-page">내 정보&설정</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/past-plays">내 지난 플레이</RouterLink>
|
||||
<RouterLink class="game-shell__action" :class="{ highlight: tournamentStage === 1 }" to="/tournament"
|
||||
>토너먼트</RouterLink
|
||||
>
|
||||
<RouterLink class="ghost" :class="{ highlight: tournamentStage === 6 }" to="/betting"
|
||||
<RouterLink class="game-shell__action" :class="{ highlight: tournamentStage === 6 }" to="/betting"
|
||||
>베팅장</RouterLink
|
||||
>
|
||||
<RouterLink class="ghost" to="/auction">거래장</RouterLink>
|
||||
<RouterLink class="ghost" to="/survey">설문조사</RouterLink>
|
||||
<RouterLink class="ghost" to="/npc-control">NPC 정책</RouterLink>
|
||||
<RouterLink class="ghost" to="/inherit">유산 강화</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/auction">거래장</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/survey">설문조사</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/npc-control">NPC 정책</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/inherit">유산 강화</RouterLink>
|
||||
<button
|
||||
class="toggle"
|
||||
class="game-shell__action toggle"
|
||||
:class="{ active: realtimeEnabled }"
|
||||
@click="dashboard.setRealtimeEnabled(!realtimeEnabled)"
|
||||
>
|
||||
실시간 동기화: {{ realtimeLabel }}
|
||||
</button>
|
||||
<button class="ghost" @click="loadMainData">새로고침</button>
|
||||
<button class="game-shell__action" @click="loadMainData">새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="error" class="game-feedback game-feedback--error" role="alert">{{ error }}</div>
|
||||
<div v-if="frontStatusError" class="front-status-error" role="alert">{{ frontStatusError }}</div>
|
||||
|
||||
<div v-if="session.needsGeneral" class="warning">
|
||||
@@ -413,39 +413,6 @@ watch(
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.main-page {
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(232, 221, 196, 0.7);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
background: none;
|
||||
@@ -453,38 +420,21 @@ button {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.toggle,
|
||||
.ghost {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle.active {
|
||||
background: rgba(201, 164, 90, 0.2);
|
||||
}
|
||||
|
||||
.ghost {
|
||||
background: rgba(16, 16, 16, 0.6);
|
||||
}
|
||||
|
||||
.ghost.highlight {
|
||||
.game-shell__action.highlight {
|
||||
border-color: #f39c12;
|
||||
background: #8a5b13;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ghost.disabled {
|
||||
.game-shell__action.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f5b7b1;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.front-status-error {
|
||||
color: #ff8a80;
|
||||
font-size: 0.85rem;
|
||||
|
||||
@@ -448,8 +448,8 @@ onMounted(() => {
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
background-color: #111;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@@ -522,7 +522,7 @@ button:disabled {
|
||||
.settings-column,
|
||||
.log-panel {
|
||||
border: 1px solid #666;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
.section-title,
|
||||
.log-panel h2 {
|
||||
@@ -533,7 +533,7 @@ button:disabled {
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #666;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
font-size: 1.25em;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -545,7 +545,7 @@ button:disabled {
|
||||
grid-template-columns: 150px 1fr;
|
||||
padding: 0;
|
||||
background-color: #172a52;
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
.portrait-cell {
|
||||
display: flex;
|
||||
|
||||
@@ -150,7 +150,7 @@ onMounted(async () => {
|
||||
.city th {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.city td {
|
||||
width: 140px;
|
||||
|
||||
@@ -199,7 +199,7 @@ td {
|
||||
}
|
||||
th {
|
||||
height: 30px;
|
||||
background: #14241b url('/image/game/back_green.jpg');
|
||||
background: #14241b var(--sammo-texture-green);
|
||||
font-weight: 400;
|
||||
}
|
||||
tbody tr {
|
||||
|
||||
@@ -140,7 +140,7 @@ onMounted(async () => {
|
||||
.info-table th {
|
||||
width: 98px;
|
||||
text-align: center;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.info-table td {
|
||||
text-align: center;
|
||||
|
||||
@@ -247,7 +247,7 @@ onMounted(() => {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.value-wide {
|
||||
width: 170px;
|
||||
@@ -273,7 +273,7 @@ onMounted(() => {
|
||||
}
|
||||
.neutral-label {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
|
||||
@@ -501,7 +501,7 @@ onMounted(() => void loadPersonnel());
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
background: url('/image/game/back_walnut.jpg');
|
||||
background: var(--sammo-texture-walnut);
|
||||
}
|
||||
.legacy-table td {
|
||||
border: 1px solid gray;
|
||||
@@ -618,10 +618,10 @@ select[multiple] {
|
||||
.green-cell,
|
||||
.city-header,
|
||||
.region-heading {
|
||||
background: url('/image/game/back_green.jpg');
|
||||
background: var(--sammo-texture-green);
|
||||
}
|
||||
.blue-cell {
|
||||
background: url('/image/game/back_blue.jpg');
|
||||
background: var(--sammo-texture-blue);
|
||||
}
|
||||
.spacer {
|
||||
height: 5px;
|
||||
@@ -700,7 +700,7 @@ select[multiple] {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid gray;
|
||||
padding: 6px 8px;
|
||||
background: url('/image/game/back_walnut.jpg');
|
||||
background: var(--sammo-texture-walnut);
|
||||
}
|
||||
.error {
|
||||
color: #ff8080;
|
||||
|
||||
@@ -176,7 +176,7 @@ select {
|
||||
}
|
||||
.summary th,
|
||||
.list th {
|
||||
background: #14241b url('/image/game/back_green.jpg');
|
||||
background: #14241b var(--sammo-texture-green);
|
||||
}
|
||||
.summary th {
|
||||
width: 120px;
|
||||
|
||||
@@ -414,7 +414,7 @@ onMounted(() => void loadStratFinan());
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
background: url('/image/game/back_walnut.jpg');
|
||||
background: var(--sammo-texture-walnut);
|
||||
font:
|
||||
14px/1.3 Pretendard,
|
||||
'Apple SD Gothic Neo',
|
||||
@@ -502,14 +502,14 @@ textarea:focus-visible {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.diplomacy-header {
|
||||
background: url('/image/game/back_green.jpg');
|
||||
background: var(--sammo-texture-green);
|
||||
}
|
||||
.green-header {
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: url('/image/game/back_green.jpg');
|
||||
background: var(--sammo-texture-green);
|
||||
}
|
||||
.message-preview,
|
||||
textarea {
|
||||
@@ -545,7 +545,7 @@ textarea {
|
||||
.blue-heading {
|
||||
height: 18.19px;
|
||||
text-align: center;
|
||||
background: url('/image/game/back_blue.jpg');
|
||||
background: var(--sammo-texture-blue);
|
||||
}
|
||||
.budget-row,
|
||||
.policy-cell {
|
||||
@@ -556,7 +556,7 @@ textarea {
|
||||
}
|
||||
.budget-row span:first-child,
|
||||
.green-label {
|
||||
background: url('/image/game/back_green.jpg');
|
||||
background: var(--sammo-texture-green);
|
||||
}
|
||||
.budget-row > span,
|
||||
.green-label,
|
||||
|
||||
@@ -560,7 +560,7 @@ const dropPriority = (event: DragEvent, section: PrioritySectionKey, bucket: Pri
|
||||
min-width: 500px;
|
||||
margin: 0;
|
||||
background: #000;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
}
|
||||
@@ -573,21 +573,21 @@ const dropPriority = (event: DragEvent, section: PrioritySectionKey, bucket: Pri
|
||||
.npc-page {
|
||||
min-height: 100vh;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.legacy-bg0 {
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.legacy-bg1 {
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.legacy-bg2 {
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.top-back-bar {
|
||||
|
||||
@@ -97,24 +97,24 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="public-page">
|
||||
<header class="page-header">
|
||||
<main class="game-shell public-page">
|
||||
<header class="game-shell__header">
|
||||
<div>
|
||||
<h1 class="page-title">공개 동향</h1>
|
||||
<p class="page-subtitle">{{ trendSummary }}</p>
|
||||
<h1 class="game-shell__title">공개 동향</h1>
|
||||
<p class="game-shell__subtitle">{{ trendSummary }}</p>
|
||||
<p class="page-hint">지도/정세는 10분 캐시된 정보로 제공됩니다.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<RouterLink v-if="!session.isAuthed" class="ghost" to="/login">로그인</RouterLink>
|
||||
<RouterLink v-else-if="session.needsGeneral" class="ghost" to="/join">장수 생성/빙의</RouterLink>
|
||||
<RouterLink v-else class="ghost" to="/">메인으로</RouterLink>
|
||||
<RouterLink class="ghost" to="/npc-list">빙의일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/traffic">접속량정보</RouterLink>
|
||||
<button class="ghost" @click="refreshPublicData">새로고침</button>
|
||||
<div class="game-shell__actions">
|
||||
<RouterLink v-if="!session.isAuthed" class="game-shell__action" to="/login">로그인</RouterLink>
|
||||
<RouterLink v-else-if="session.needsGeneral" class="game-shell__action" to="/join">장수 생성/빙의</RouterLink>
|
||||
<RouterLink v-else class="game-shell__action" to="/">메인으로</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/npc-list">빙의일람</RouterLink>
|
||||
<RouterLink class="game-shell__action" to="/traffic">접속량정보</RouterLink>
|
||||
<button class="game-shell__action" @click="refreshPublicData">새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="error" class="game-feedback game-feedback--error" role="alert">{{ error }}</div>
|
||||
|
||||
<section v-if="isMobile" class="layout-mobile">
|
||||
<PanelCard title="캐시 지도">
|
||||
@@ -256,60 +256,12 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(232, 221, 196, 0.7);
|
||||
}
|
||||
|
||||
.page-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(232, 221, 196, 0.5);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: rgba(16, 16, 16, 0.6);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f5b7b1;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.layout-desktop {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 1.3fr) minmax(320px, 1fr);
|
||||
|
||||
@@ -416,15 +416,15 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.bg0 {
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.bg2 {
|
||||
background-image: url('/image/game/back_blue.jpg');
|
||||
background-image: var(--sammo-texture-blue);
|
||||
}
|
||||
|
||||
.back_bar {
|
||||
|
||||
@@ -267,7 +267,7 @@ const start = async () => {
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
@@ -277,10 +277,10 @@ const start = async () => {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bg0 {
|
||||
background: #3a2118 url('/image/game/back_walnut.jpg');
|
||||
background: #3a2118 var(--sammo-texture-walnut);
|
||||
}
|
||||
.bg2 {
|
||||
background: #142b42 url('/image/game/back_blue.jpg');
|
||||
background: #142b42 var(--sammo-texture-blue);
|
||||
}
|
||||
.legacy-title {
|
||||
height: 55.6875px;
|
||||
@@ -420,7 +420,7 @@ caption {
|
||||
color: #fff;
|
||||
}
|
||||
th {
|
||||
background: #154b2a url('/image/game/back_green.jpg');
|
||||
background: #154b2a var(--sammo-texture-green);
|
||||
font-weight: 400;
|
||||
}
|
||||
th,
|
||||
|
||||
@@ -181,7 +181,7 @@ onMounted(() => {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
line-height: normal;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ onMounted(() => {
|
||||
|
||||
.legacy-bg0 {
|
||||
background-color: #302016;
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.legacy-bg1 {
|
||||
@@ -213,7 +213,7 @@ onMounted(() => {
|
||||
|
||||
.legacy-bg2 {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.title-table,
|
||||
|
||||
@@ -334,11 +334,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.bg0 {
|
||||
background-image: url('/image/game/back_walnut.jpg');
|
||||
background-image: var(--sammo-texture-walnut);
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
background-image: var(--sammo-texture-green);
|
||||
}
|
||||
|
||||
.center {
|
||||
|
||||
@@ -206,7 +206,7 @@ onMounted(async () => {
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
||||
font-family: var(--sammo-font-sans);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,5 +158,8 @@
|
||||
## Open Questions
|
||||
|
||||
- Public 상태 동향 범위는 캐싱된 지도, 중원 정세, 세력일람으로 제한한다. 장수일람은 실시간 제공하되 이름/NPC 여부/국가/기본 능력치만 노출한다. 그 외 장수 정보는 캐싱된 자료에 기반하며, 빈번한 접근 제한 우회를 막기 위해 캐싱 전략을 유지한다.
|
||||
- UI 스타일은 당분간 \"고전 게임\" 감성을 유지한다. 전체 이식이 완료된 뒤 현대화하며, 새 UI는 Tailwind 등 CSS 라이브러리를 적극 활용한다.
|
||||
- UI 스타일은 ref의 렌더링 계약을 유지한다. CSS 구조화는 공통 token과
|
||||
검증된 shell 단위로 수행하되, ref의 computed DOM 치수·typography·texture·
|
||||
interaction과 충돌하면 페이지별 ref 규칙을 우선한다. 새로운 디자인으로의
|
||||
현대화는 이 이관의 기본 후속 단계로 간주하지 않는다.
|
||||
- 실시간 업데이트는 메인 화면에 한정한다. 대상: 지도, 명령 목록, 현재 도시 정보, 소속 국가 정보, 장수 스탯, 장수 동향, 개인 기록, 중원 정세, 메시지함. 메인 화면에는 \"실시간 동기화 켬/끔\" 토글이 필요하다.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Game frontend CSS architecture
|
||||
|
||||
The game frontend preserves the rendered contract of `ref/sam`; CSS reuse is
|
||||
not a reason to normalize a page's width, height, typography, texture, or
|
||||
interaction states. When reuse and the reference geometry conflict, the
|
||||
reference geometry wins.
|
||||
|
||||
## Layers
|
||||
|
||||
`app/game-frontend/src/assets/main.css` is the single global entry point. It
|
||||
loads the following layers:
|
||||
|
||||
1. `styles/tokens.css`: exact shared font, color, and `/image/game` texture
|
||||
values. These are value aliases only and must resolve to the same computed
|
||||
value as the ref page.
|
||||
2. `styles/game-shell.css`: the flexible shell shared by the main dashboard,
|
||||
public dashboard, and chief center. Only declarations proven identical
|
||||
across those screens belong here.
|
||||
3. `styles/ref-shell.css`: fixed ref geometry, including the 1000px desktop /
|
||||
500px mobile family used by the battle center. Its namespace stays separate
|
||||
from the flexible shell so a generic responsive rule cannot override it.
|
||||
4. Scoped SFC styles: page-specific grids, fixed table dimensions, selectors,
|
||||
and state styling. These remain closest to the DOM contract they implement.
|
||||
|
||||
## Class naming
|
||||
|
||||
- `.game-shell`, `.game-shell__header`, `.game-shell__actions`: flexible
|
||||
application shell.
|
||||
- `.ref-shell`, `.ref-shell__topbar`, `.ref-shell__control`: measured legacy
|
||||
shell and controls.
|
||||
- `.game-feedback--error`, `.ref-feedback--error`: feedback scoped to its
|
||||
visual family.
|
||||
- Feature-specific classes stay namespaced by their feature or component.
|
||||
Generic names such as `.title`, `.error`, `.ghost`, `.stack`, and
|
||||
`.layout-grid` must not be promoted from a scoped SFC merely because the same
|
||||
spelling appears elsewhere.
|
||||
|
||||
Existing feature hooks may remain while a screen is migrated, but new shared
|
||||
presentation must be selected through one of the explicit shell namespaces.
|
||||
|
||||
## Consolidation rule
|
||||
|
||||
Before moving declarations out of an SFC:
|
||||
|
||||
1. Compare every same-named selector's declarations and semantic role.
|
||||
2. Confirm the affected pages use the same layout family.
|
||||
3. Record desktop and mobile `getBoundingClientRect()` and
|
||||
`getComputedStyle()` values before the move.
|
||||
4. Move only identical declarations; keep exceptions in the owning SFC.
|
||||
5. Re-run Chromium geometry plus hover, focus, active, and disabled states.
|
||||
|
||||
The main page and chief center are the flexible-shell references. The battle
|
||||
center is the fixed ref-shell reference. If another page has a measured ref
|
||||
contract that differs from both, preserve that page's local contract rather
|
||||
than forcing it into either family.
|
||||
|
||||
## Asset boundary
|
||||
|
||||
The CSS variables contain `/image/game/*` URLs but do not import or copy image
|
||||
files. Caddy continues to own `/image/*`; Vite must not rewrite the image tree
|
||||
as application assets.
|
||||
Reference in New Issue
Block a user