Merge branch 'main' into feature/traffic-period-projection
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 === 'turns.getCommandTable') return response({ general: [], nation: [] });
|
||||||
if (operation === 'messages.getRecent') return response(emptyMessages);
|
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}`);
|
return errorResponse(operation, `Unhandled board fixture operation: ${operation}`);
|
||||||
});
|
});
|
||||||
await route.fulfill({
|
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;
|
state.requests.length = 0;
|
||||||
await page.goto('');
|
await page.goto('');
|
||||||
await expect(page.getByRole('link', { name: '회의실', exact: true })).toBeVisible();
|
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);
|
await expect(page.getByRole('link', { name: '기밀실', exact: true })).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,22 @@ const generalContext = {
|
|||||||
};
|
};
|
||||||
const turns = (count: number) =>
|
const turns = (count: number) =>
|
||||||
Array.from({ length: count }, (_, index) => ({ index, action: '휴식', args: {} }));
|
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 install = async (page: Page, rejectGeneral = false) => {
|
||||||
const requests: unknown[] = [];
|
const requests: unknown[] = [];
|
||||||
@@ -115,8 +131,20 @@ const install = async (page: Page, rejectGeneral = false) => {
|
|||||||
spyList: {}, shownByGeneralList: [], myCity: 1, myNation: 1,
|
spyList: {}, shownByGeneralList: [], myCity: 1, myNation: 1,
|
||||||
});
|
});
|
||||||
if (name === 'turns.getCommandTable') return response(commandTable);
|
if (name === 'turns.getCommandTable') return response(commandTable);
|
||||||
if (name === 'turns.reserved.getGeneral') return response(turns(30));
|
if (name === 'nation.getChiefCenter') return response(chiefCenter);
|
||||||
if (name === 'turns.reserved.getNation') return response(turns(12));
|
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({
|
if (name === 'messages.getRecent') return response({
|
||||||
private: [], national: [], public: [], diplomacy: [], sequence: -1,
|
private: [], national: [], public: [], diplomacy: [], sequence: -1,
|
||||||
hasMore: { private: false, national: false, public: false, diplomacy: false },
|
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: '일반 예턴' })
|
await page.locator('.reserved-section').filter({ hasText: '일반 예턴' })
|
||||||
.getByRole('button', { name: '배치' }).first().click();
|
.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');
|
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: [] };
|
const member: FixtureState = { permission: 'member', myset: 3, settingMutations: [], accessPages: [] };
|
||||||
await install(page, member);
|
await install(page, member);
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await expect(page.locator('.error')).toContainText('권한이 부족합니다.');
|
await expect(page.getByRole('alert')).toContainText('권한이 부족합니다.');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
@import './styles/tokens.css';
|
||||||
|
@import './styles/game-shell.css';
|
||||||
|
@import './styles/ref-shell.css';
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-sammo-ink: #000;
|
--color-sammo-ink: #000;
|
||||||
@@ -15,7 +18,7 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
@@ -29,17 +32,17 @@ textarea {
|
|||||||
|
|
||||||
.legacy-bg0 {
|
.legacy-bg0 {
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg1 {
|
.legacy-bg1 {
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg2 {
|
.legacy-bg2 {
|
||||||
background-color: #172a52;
|
background-color: #172a52;
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-button {
|
.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);
|
width: calc(100% + 48px);
|
||||||
margin-left: -24px;
|
margin-left: -24px;
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
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-columns: minmax(0, 1fr) minmax(0, 4fr) minmax(0, 1fr);
|
||||||
grid-template-areas: 'mailbox input submit';
|
grid-template-areas: 'mailbox input submit';
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mailbox_list-col {
|
#mailbox_list-col {
|
||||||
@@ -347,7 +347,7 @@ const forwardResponse = (messageId: number, response: boolean) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
outline: 1px solid gray;
|
outline: 1px solid gray;
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ defineProps<{
|
|||||||
border-top: 1px solid gray;
|
border-top: 1px solid gray;
|
||||||
border-bottom: 1px solid gray;
|
border-bottom: 1px solid gray;
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ defineProps<Props>();
|
|||||||
.panel-card {
|
.panel-card {
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
@@ -38,7 +38,7 @@ defineProps<Props>();
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-bottom: 1px solid gray;
|
border-bottom: 1px solid gray;
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -478,21 +478,21 @@ onMounted(() => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
}
|
}
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.bg1 {
|
.bg1 {
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.bg2 {
|
.bg2 {
|
||||||
background-color: #172a52;
|
background-color: #172a52;
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.top-back-bar {
|
.top-back-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -228,26 +228,26 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="battle-page">
|
<main class="ref-shell battle-page">
|
||||||
<header class="page-header">
|
<header class="ref-shell__topbar">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="page-title">감찰부</h1>
|
<h1 class="ref-shell__title">감찰부</h1>
|
||||||
<p class="page-subtitle">{{ statusLine }}</p>
|
<p class="ref-shell__subtitle">{{ statusLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="ref-shell__actions">
|
||||||
<RouterLink class="ghost" to="/">메인</RouterLink>
|
<RouterLink class="ref-shell__control" to="/">메인</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation/finance">내무부</RouterLink>
|
<RouterLink class="ref-shell__control" to="/nation/finance">내무부</RouterLink>
|
||||||
<button class="ghost" @click="loadBattleCenter">새로고침</button>
|
<button class="ref-shell__control" @click="loadBattleCenter">새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<section class="layout-grid">
|
||||||
<div class="stack">
|
<div class="stack">
|
||||||
<PanelCard title="대상 선택" subtitle="정렬 기준과 장수를 선택합니다.">
|
<PanelCard title="대상 선택" subtitle="정렬 기준과 장수를 선택합니다.">
|
||||||
<div class="selector-row">
|
<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">
|
<select v-model="orderBy" class="select-input">
|
||||||
<option v-for="option in orderOptions" :key="option.key" :value="option.key">
|
<option v-for="option in orderOptions" :key="option.key" :value="option.key">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
@@ -263,7 +263,7 @@ onMounted(() => {
|
|||||||
{{ formatGeneralLabel(general) }}
|
{{ formatGeneralLabel(general) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="ghost" @click="changeTargetByOffset(1)">다음 ▶</button>
|
<button class="ref-shell__control" @click="changeTargetByOffset(1)">다음 ▶</button>
|
||||||
</div>
|
</div>
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
|
|
||||||
@@ -315,56 +315,6 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.layout-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
@@ -393,17 +343,6 @@ onMounted(() => {
|
|||||||
font: inherit;
|
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 {
|
.general-meta {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
@@ -415,7 +354,7 @@ onMounted(() => {
|
|||||||
.battle-general-card {
|
.battle-general-card {
|
||||||
min-height: 292px;
|
min-height: 292px;
|
||||||
background-color: #172a52;
|
background-color: #172a52;
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.battle-general-name {
|
.battle-general-name {
|
||||||
@@ -485,13 +424,6 @@ onMounted(() => {
|
|||||||
color: #999;
|
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
|
/* PanelCard is retained as a data wrapper, but its presentation follows the
|
||||||
flat bootstrap rows used by the reference page. */
|
flat bootstrap rows used by the reference page. */
|
||||||
:deep(.panel-card) {
|
:deep(.panel-card) {
|
||||||
@@ -499,7 +431,7 @@ onMounted(() => {
|
|||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.stack:first-child :deep(.panel-card:first-child) {
|
.stack:first-child :deep(.panel-card:first-child) {
|
||||||
@@ -531,13 +463,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
:deep(.panel-header),
|
:deep(.panel-header),
|
||||||
.log-title {
|
.log-title {
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
.battle-page {
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
.layout-grid {
|
.layout-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ watch(viewMode, () => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto 100px;
|
margin: 0 auto 100px;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ const placeBet = async (targetId: number) => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -228,10 +228,10 @@ const placeBet = async (targetId: number) => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background: #3a2118 url('/image/game/back_walnut.jpg');
|
background: #3a2118 var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.bg2 {
|
.bg2 {
|
||||||
background: #142b42 url('/image/game/back_blue.jpg');
|
background: #142b42 var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
height: 55.6875px;
|
height: 55.6875px;
|
||||||
|
|||||||
@@ -233,21 +233,21 @@ onMounted(() => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #000;
|
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;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg1 {
|
.bg1 {
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg2 {
|
.bg2 {
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
|||||||
@@ -388,19 +388,19 @@ const shiftTurns = async (amount: number) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="chief-page">
|
<main class="game-shell chief-page">
|
||||||
<header class="page-header">
|
<header class="game-shell__header">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="page-title">사령부</h1>
|
<h1 class="game-shell__title">사령부</h1>
|
||||||
<p class="page-subtitle">{{ statusLine }}</p>
|
<p class="game-shell__subtitle">{{ statusLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="game-shell__actions">
|
||||||
<RouterLink class="ghost" to="/">메인</RouterLink>
|
<RouterLink class="game-shell__action" to="/">메인</RouterLink>
|
||||||
<button class="ghost" @click="loadChiefCenter">새로고침</button>
|
<button class="game-shell__action" @click="loadChiefCenter">새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<section v-if="loading && !data" class="loading-panel">
|
||||||
<PanelCard title="사령부 로딩">
|
<PanelCard title="사령부 로딩">
|
||||||
@@ -558,53 +558,6 @@ const shiftTurns = async (amount: number) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.layout-desktop {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 2.2fr) minmax(280px, 1fr);
|
grid-template-columns: minmax(0, 2.2fr) minmax(280px, 1fr);
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ const generalImage = (general: General) => {
|
|||||||
.stats th,
|
.stats th,
|
||||||
.generals th {
|
.generals th {
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.city-title {
|
.city-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ onMounted(() => {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.general-icon {
|
.general-icon {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ onMounted(async () => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto 100px;
|
margin: 0 auto 100px;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,67 +113,67 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="main-page">
|
<main class="game-shell main-page">
|
||||||
<header class="page-header">
|
<header class="game-shell__header">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="page-title">전장 현황</h1>
|
<h1 class="game-shell__title">전장 현황</h1>
|
||||||
<p class="page-subtitle">{{ statusLine }}</p>
|
<p class="game-shell__subtitle">{{ statusLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="game-shell__actions">
|
||||||
<RouterLink v-if="boardAccess?.canMeeting" class="ghost" to="/board">회의실</RouterLink>
|
<RouterLink v-if="boardAccess?.canMeeting" class="game-shell__action" to="/board">회의실</RouterLink>
|
||||||
<span v-else class="ghost disabled" aria-disabled="true">회의실</span>
|
<span v-else class="game-shell__action disabled" aria-disabled="true">회의실</span>
|
||||||
<RouterLink v-if="boardAccess?.canSecret" class="ghost" to="/board/secret">기밀실</RouterLink>
|
<RouterLink v-if="boardAccess?.canSecret" class="game-shell__action" to="/board/secret">기밀실</RouterLink>
|
||||||
<span v-else class="ghost disabled" aria-disabled="true">기밀실</span>
|
<span v-else class="game-shell__action disabled" aria-disabled="true">기밀실</span>
|
||||||
<RouterLink class="ghost" to="/nation/info">세력 정보</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation/info">세력 정보</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation/cities">세력 도시</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation/cities">세력 도시</RouterLink>
|
||||||
<RouterLink class="ghost" to="/global-info">중원 정보</RouterLink>
|
<RouterLink class="game-shell__action" to="/global-info">중원 정보</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation-list">세력일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation-list">세력일람</RouterLink>
|
||||||
<RouterLink class="ghost" to="/general-list">장수일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/general-list">장수일람</RouterLink>
|
||||||
<RouterLink class="ghost" to="/current-city">현재 도시</RouterLink>
|
<RouterLink class="game-shell__action" to="/current-city">현재 도시</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation/generals">세력 장수</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation/generals">세력 장수</RouterLink>
|
||||||
<RouterLink v-if="(boardAccess?.permission ?? -1) >= 1" class="ghost" to="/nation/secret"
|
<RouterLink v-if="(boardAccess?.permission ?? -1) >= 1" class="game-shell__action" to="/nation/secret"
|
||||||
>암행부</RouterLink
|
>암행부</RouterLink
|
||||||
>
|
>
|
||||||
<span v-else class="ghost disabled" aria-disabled="true">암행부</span>
|
<span v-else class="game-shell__action disabled" aria-disabled="true">암행부</span>
|
||||||
<RouterLink class="ghost" to="/nation/personnel">인사부</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation/personnel">인사부</RouterLink>
|
||||||
<RouterLink class="ghost" to="/troop">부대 편성</RouterLink>
|
<RouterLink class="game-shell__action" to="/troop">부대 편성</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation/finance">내무부</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation/finance">내무부</RouterLink>
|
||||||
<RouterLink class="ghost" to="/diplomacy">외교부</RouterLink>
|
<RouterLink class="game-shell__action" to="/diplomacy">외교부</RouterLink>
|
||||||
<RouterLink class="ghost" to="/chief-center">사령부</RouterLink>
|
<RouterLink class="game-shell__action" to="/chief-center">사령부</RouterLink>
|
||||||
<RouterLink class="ghost" to="/battle-center">감찰부</RouterLink>
|
<RouterLink class="game-shell__action" to="/battle-center">감찰부</RouterLink>
|
||||||
<RouterLink class="ghost" to="/best-general">명장일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/best-general">명장일람</RouterLink>
|
||||||
<RouterLink class="ghost" to="/hall-of-fame">명예의 전당</RouterLink>
|
<RouterLink class="game-shell__action" to="/hall-of-fame">명예의 전당</RouterLink>
|
||||||
<RouterLink class="ghost" to="/dynasty">왕조일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/dynasty">왕조일람</RouterLink>
|
||||||
<RouterLink class="ghost" to="/yearbook">연감</RouterLink>
|
<RouterLink class="game-shell__action" to="/yearbook">연감</RouterLink>
|
||||||
<RouterLink class="ghost" to="/nation-betting">천통국 베팅</RouterLink>
|
<RouterLink class="game-shell__action" to="/nation-betting">천통국 베팅</RouterLink>
|
||||||
<RouterLink class="ghost" to="/traffic">접속량정보</RouterLink>
|
<RouterLink class="game-shell__action" to="/traffic">접속량정보</RouterLink>
|
||||||
<RouterLink class="ghost" to="/npc-list">빙의일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/npc-list">빙의일람</RouterLink>
|
||||||
<a class="ghost" href="/xe/community" target="_blank" rel="noopener">게시판</a>
|
<a class="game-shell__action" href="/xe/community" target="_blank" rel="noopener">게시판</a>
|
||||||
<RouterLink class="ghost" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
<RouterLink class="game-shell__action" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||||
<RouterLink class="ghost" to="/my-page">내 정보&설정</RouterLink>
|
<RouterLink class="game-shell__action" to="/my-page">내 정보&설정</RouterLink>
|
||||||
<RouterLink class="ghost" to="/past-plays">내 지난 플레이</RouterLink>
|
<RouterLink class="game-shell__action" to="/past-plays">내 지난 플레이</RouterLink>
|
||||||
<RouterLink class="ghost" :class="{ highlight: tournamentStage === 1 }" to="/tournament"
|
<RouterLink class="game-shell__action" :class="{ highlight: tournamentStage === 1 }" to="/tournament"
|
||||||
>토너먼트</RouterLink
|
>토너먼트</RouterLink
|
||||||
>
|
>
|
||||||
<RouterLink class="ghost" :class="{ highlight: tournamentStage === 6 }" to="/betting"
|
<RouterLink class="game-shell__action" :class="{ highlight: tournamentStage === 6 }" to="/betting"
|
||||||
>베팅장</RouterLink
|
>베팅장</RouterLink
|
||||||
>
|
>
|
||||||
<RouterLink class="ghost" to="/auction">거래장</RouterLink>
|
<RouterLink class="game-shell__action" to="/auction">거래장</RouterLink>
|
||||||
<RouterLink class="ghost" to="/survey">설문조사</RouterLink>
|
<RouterLink class="game-shell__action" to="/survey">설문조사</RouterLink>
|
||||||
<RouterLink class="ghost" to="/npc-control">NPC 정책</RouterLink>
|
<RouterLink class="game-shell__action" to="/npc-control">NPC 정책</RouterLink>
|
||||||
<RouterLink class="ghost" to="/inherit">유산 강화</RouterLink>
|
<RouterLink class="game-shell__action" to="/inherit">유산 강화</RouterLink>
|
||||||
<button
|
<button
|
||||||
class="toggle"
|
class="game-shell__action toggle"
|
||||||
:class="{ active: realtimeEnabled }"
|
:class="{ active: realtimeEnabled }"
|
||||||
@click="dashboard.setRealtimeEnabled(!realtimeEnabled)"
|
@click="dashboard.setRealtimeEnabled(!realtimeEnabled)"
|
||||||
>
|
>
|
||||||
실시간 동기화: {{ realtimeLabel }}
|
실시간 동기화: {{ realtimeLabel }}
|
||||||
</button>
|
</button>
|
||||||
<button class="ghost" @click="loadMainData">새로고침</button>
|
<button class="game-shell__action" @click="loadMainData">새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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="frontStatusError" class="front-status-error" role="alert">{{ frontStatusError }}</div>
|
||||||
|
|
||||||
<div v-if="session.needsGeneral" class="warning">
|
<div v-if="session.needsGeneral" class="warning">
|
||||||
@@ -413,39 +413,6 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
button {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
background: none;
|
background: none;
|
||||||
@@ -453,38 +420,21 @@ button {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle,
|
|
||||||
.ghost {
|
|
||||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle.active {
|
.toggle.active {
|
||||||
background: rgba(201, 164, 90, 0.2);
|
background: rgba(201, 164, 90, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost {
|
.game-shell__action.highlight {
|
||||||
background: rgba(16, 16, 16, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ghost.highlight {
|
|
||||||
border-color: #f39c12;
|
border-color: #f39c12;
|
||||||
background: #8a5b13;
|
background: #8a5b13;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost.disabled {
|
.game-shell__action.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #f5b7b1;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.front-status-error {
|
.front-status-error {
|
||||||
color: #ff8a80;
|
color: #ff8a80;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
|||||||
@@ -448,8 +448,8 @@ onMounted(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', sans-serif;
|
font-family: var(--sammo-font-sans);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
@@ -522,7 +522,7 @@ button:disabled {
|
|||||||
.settings-column,
|
.settings-column,
|
||||||
.log-panel {
|
.log-panel {
|
||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.section-title,
|
.section-title,
|
||||||
.log-panel h2 {
|
.log-panel h2 {
|
||||||
@@ -533,7 +533,7 @@ button:disabled {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-bottom: 1px solid #666;
|
border-bottom: 1px solid #666;
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -545,7 +545,7 @@ button:disabled {
|
|||||||
grid-template-columns: 150px 1fr;
|
grid-template-columns: 150px 1fr;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #172a52;
|
background-color: #172a52;
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.portrait-cell {
|
.portrait-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ onMounted(async () => {
|
|||||||
.city th {
|
.city th {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.city td {
|
.city td {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ td {
|
|||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background: #14241b url('/image/game/back_green.jpg');
|
background: #14241b var(--sammo-texture-green);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
tbody tr {
|
tbody tr {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ onMounted(async () => {
|
|||||||
.info-table th {
|
.info-table th {
|
||||||
width: 98px;
|
width: 98px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.info-table td {
|
.info-table td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ onMounted(() => {
|
|||||||
width: 80px;
|
width: 80px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.value-wide {
|
.value-wide {
|
||||||
width: 170px;
|
width: 170px;
|
||||||
@@ -273,7 +273,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
.neutral-label {
|
.neutral-label {
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.center {
|
.center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ onMounted(() => void loadPersonnel());
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
background: url('/image/game/back_walnut.jpg');
|
background: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.legacy-table td {
|
.legacy-table td {
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
@@ -618,10 +618,10 @@ select[multiple] {
|
|||||||
.green-cell,
|
.green-cell,
|
||||||
.city-header,
|
.city-header,
|
||||||
.region-heading {
|
.region-heading {
|
||||||
background: url('/image/game/back_green.jpg');
|
background: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.blue-cell {
|
.blue-cell {
|
||||||
background: url('/image/game/back_blue.jpg');
|
background: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.spacer {
|
.spacer {
|
||||||
height: 5px;
|
height: 5px;
|
||||||
@@ -700,7 +700,7 @@ select[multiple] {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
background: url('/image/game/back_walnut.jpg');
|
background: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
color: #ff8080;
|
color: #ff8080;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ select {
|
|||||||
}
|
}
|
||||||
.summary th,
|
.summary th,
|
||||||
.list th {
|
.list th {
|
||||||
background: #14241b url('/image/game/back_green.jpg');
|
background: #14241b var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.summary th {
|
.summary th {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ onMounted(() => void loadStratFinan());
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: url('/image/game/back_walnut.jpg');
|
background: var(--sammo-texture-walnut);
|
||||||
font:
|
font:
|
||||||
14px/1.3 Pretendard,
|
14px/1.3 Pretendard,
|
||||||
'Apple SD Gothic Neo',
|
'Apple SD Gothic Neo',
|
||||||
@@ -502,14 +502,14 @@ textarea:focus-visible {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.diplomacy-header {
|
.diplomacy-header {
|
||||||
background: url('/image/game/back_green.jpg');
|
background: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.green-header {
|
.green-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: url('/image/game/back_green.jpg');
|
background: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.message-preview,
|
.message-preview,
|
||||||
textarea {
|
textarea {
|
||||||
@@ -545,7 +545,7 @@ textarea {
|
|||||||
.blue-heading {
|
.blue-heading {
|
||||||
height: 18.19px;
|
height: 18.19px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: url('/image/game/back_blue.jpg');
|
background: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.budget-row,
|
.budget-row,
|
||||||
.policy-cell {
|
.policy-cell {
|
||||||
@@ -556,7 +556,7 @@ textarea {
|
|||||||
}
|
}
|
||||||
.budget-row span:first-child,
|
.budget-row span:first-child,
|
||||||
.green-label {
|
.green-label {
|
||||||
background: url('/image/game/back_green.jpg');
|
background: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
.budget-row > span,
|
.budget-row > span,
|
||||||
.green-label,
|
.green-label,
|
||||||
|
|||||||
@@ -560,7 +560,7 @@ const dropPriority = (event: DragEvent, section: PrioritySectionKey, bucket: Pri
|
|||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #000;
|
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;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
}
|
}
|
||||||
@@ -573,21 +573,21 @@ const dropPriority = (event: DragEvent, section: PrioritySectionKey, bucket: Pri
|
|||||||
.npc-page {
|
.npc-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg0 {
|
.legacy-bg0 {
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg1 {
|
.legacy-bg1 {
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg2 {
|
.legacy-bg2 {
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-back-bar {
|
.top-back-bar {
|
||||||
|
|||||||
@@ -97,24 +97,24 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="public-page">
|
<main class="game-shell public-page">
|
||||||
<header class="page-header">
|
<header class="game-shell__header">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="page-title">공개 동향</h1>
|
<h1 class="game-shell__title">공개 동향</h1>
|
||||||
<p class="page-subtitle">{{ trendSummary }}</p>
|
<p class="game-shell__subtitle">{{ trendSummary }}</p>
|
||||||
<p class="page-hint">지도/정세는 10분 캐시된 정보로 제공됩니다.</p>
|
<p class="page-hint">지도/정세는 10분 캐시된 정보로 제공됩니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="game-shell__actions">
|
||||||
<RouterLink v-if="!session.isAuthed" class="ghost" to="/login">로그인</RouterLink>
|
<RouterLink v-if="!session.isAuthed" class="game-shell__action" to="/login">로그인</RouterLink>
|
||||||
<RouterLink v-else-if="session.needsGeneral" class="ghost" to="/join">장수 생성/빙의</RouterLink>
|
<RouterLink v-else-if="session.needsGeneral" class="game-shell__action" to="/join">장수 생성/빙의</RouterLink>
|
||||||
<RouterLink v-else class="ghost" to="/">메인으로</RouterLink>
|
<RouterLink v-else class="game-shell__action" to="/">메인으로</RouterLink>
|
||||||
<RouterLink class="ghost" to="/npc-list">빙의일람</RouterLink>
|
<RouterLink class="game-shell__action" to="/npc-list">빙의일람</RouterLink>
|
||||||
<RouterLink class="ghost" to="/traffic">접속량정보</RouterLink>
|
<RouterLink class="game-shell__action" to="/traffic">접속량정보</RouterLink>
|
||||||
<button class="ghost" @click="refreshPublicData">새로고침</button>
|
<button class="game-shell__action" @click="refreshPublicData">새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<section v-if="isMobile" class="layout-mobile">
|
||||||
<PanelCard title="캐시 지도">
|
<PanelCard title="캐시 지도">
|
||||||
@@ -256,60 +256,12 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.page-hint {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: rgba(232, 221, 196, 0.5);
|
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 {
|
.layout-desktop {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(320px, 1.3fr) minmax(320px, 1fr);
|
grid-template-columns: minmax(320px, 1.3fr) minmax(320px, 1fr);
|
||||||
|
|||||||
@@ -416,15 +416,15 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg1 {
|
.bg1 {
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg2 {
|
.bg2 {
|
||||||
background-image: url('/image/game/back_blue.jpg');
|
background-image: var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back_bar {
|
.back_bar {
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ const start = async () => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -277,10 +277,10 @@ const start = async () => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background: #3a2118 url('/image/game/back_walnut.jpg');
|
background: #3a2118 var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
.bg2 {
|
.bg2 {
|
||||||
background: #142b42 url('/image/game/back_blue.jpg');
|
background: #142b42 var(--sammo-texture-blue);
|
||||||
}
|
}
|
||||||
.legacy-title {
|
.legacy-title {
|
||||||
height: 55.6875px;
|
height: 55.6875px;
|
||||||
@@ -420,7 +420,7 @@ caption {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
background: #154b2a url('/image/game/back_green.jpg');
|
background: #154b2a var(--sammo-texture-green);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
th,
|
th,
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ onMounted(() => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.legacy-bg0 {
|
.legacy-bg0 {
|
||||||
background-color: #302016;
|
background-color: #302016;
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-bg1 {
|
.legacy-bg1 {
|
||||||
@@ -213,7 +213,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.legacy-bg2 {
|
.legacy-bg2 {
|
||||||
background-color: #14241b;
|
background-color: #14241b;
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-table,
|
.title-table,
|
||||||
|
|||||||
@@ -334,11 +334,11 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg0 {
|
.bg0 {
|
||||||
background-image: url('/image/game/back_walnut.jpg');
|
background-image: var(--sammo-texture-walnut);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg1 {
|
.bg1 {
|
||||||
background-image: url('/image/game/back_green.jpg');
|
background-image: var(--sammo-texture-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ onMounted(async () => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: #fff;
|
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;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,5 +158,8 @@
|
|||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- Public 상태 동향 범위는 캐싱된 지도, 중원 정세, 세력일람으로 제한한다. 장수일람은 실시간 제공하되 이름/NPC 여부/국가/기본 능력치만 노출한다. 그 외 장수 정보는 캐싱된 자료에 기반하며, 빈번한 접근 제한 우회를 막기 위해 캐싱 전략을 유지한다.
|
- 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