merge: 최신 main을 가오픈 서버 시계 수정에 통합한다
This commit is contained in:
@@ -144,7 +144,7 @@
|
||||
/*
|
||||
* Ref Bootstrap 5.2 + Lumen button family. This class owns the common raised
|
||||
* edge and pressed movement. Semantic modifiers below only select face, edge,
|
||||
* and text colors; width and fixed-height compensation stay with the owner.
|
||||
* and text colors; width and the optional fixed-height value stay with the owner.
|
||||
* Existing semantic modifiers also opt in for backward compatibility.
|
||||
*/
|
||||
.legacy-button:is(
|
||||
@@ -173,6 +173,15 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Top bars and other fixed rows keep their owner-provided height while using
|
||||
* the same Lumen edge movement. Shrinking the box with the edge keeps its
|
||||
* bottom coordinate fixed instead of moving the whole control down.
|
||||
*/
|
||||
.legacy-button.legacy-button--fixed-height {
|
||||
height: var(--legacy-button-height);
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--secondary {
|
||||
--legacy-button-bg: var(--sammo-button-secondary-bg);
|
||||
--legacy-button-border: var(--sammo-button-secondary-border);
|
||||
@@ -213,6 +222,10 @@
|
||||
background: var(--legacy-button-bg);
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--fixed-height:not(:disabled, [aria-disabled='true']):is(:hover, [aria-expanded='true']) {
|
||||
height: calc(var(--legacy-button-height) - 1px);
|
||||
}
|
||||
|
||||
.legacy-button:is(
|
||||
.legacy-button--lumen,
|
||||
.legacy-button--primary,
|
||||
@@ -244,6 +257,10 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.legacy-button.legacy-button--fixed-height:not(:disabled, [aria-disabled='true']):active {
|
||||
height: calc(var(--legacy-button-height) - 2px);
|
||||
}
|
||||
|
||||
.legacy-button:is(
|
||||
.legacy-button--lumen,
|
||||
.legacy-button--primary,
|
||||
|
||||
@@ -588,7 +588,13 @@ const clickOutsideMenu = (event: Event) => {
|
||||
</button>
|
||||
</div>
|
||||
</details>
|
||||
<button type="button" class="select-command" @click="togglePicker()">명령 선택 ▾</button>
|
||||
<button
|
||||
type="button"
|
||||
class="legacy-button legacy-button--info legacy-button--fixed-height select-command"
|
||||
@click="togglePicker()"
|
||||
>
|
||||
명령 선택 ▾
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="queue-area">
|
||||
@@ -806,8 +812,7 @@ const clickOutsideMenu = (event: Event) => {
|
||||
}
|
||||
.control-pad > button,
|
||||
.clock,
|
||||
.legacy-menu > summary,
|
||||
.select-command {
|
||||
.legacy-menu > summary {
|
||||
box-sizing: border-box;
|
||||
min-height: 34px;
|
||||
border: 0;
|
||||
@@ -822,6 +827,12 @@ const clickOutsideMenu = (event: Event) => {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
.select-command {
|
||||
--legacy-button-height: 34px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 4px;
|
||||
}
|
||||
.clock {
|
||||
background: #345c85;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -1024,9 +1035,6 @@ const clickOutsideMenu = (event: Event) => {
|
||||
grid-template-columns: 5fr 7fr;
|
||||
order: 1;
|
||||
}
|
||||
.advanced-actions > * {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.bottom-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@@ -61,11 +61,12 @@ const killRate = computed(() => {
|
||||
</div>
|
||||
<template v-else>
|
||||
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
|
||||
><strong>{{ summary.dedicationText || '-' }}</strong> <span>전투</span
|
||||
><strong>{{ summary.dedicationText || '-' }}</strong>
|
||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||
<span>전투</span
|
||||
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null">회</template></strong>
|
||||
<span>승리</span><strong>{{ numberText(summary.wins) }}</strong> <span>패배</span
|
||||
><strong>{{ numberText(summary.losses) }}</strong> <span>계략</span
|
||||
><strong>{{ numberText(summary.strategies) }}</strong>
|
||||
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
|
||||
<template v-if="summary.serviceYears !== undefined">
|
||||
<span>사관</span
|
||||
><strong
|
||||
@@ -73,11 +74,28 @@ const killRate = computed(() => {
|
||||
}}<template v-if="summary.serviceYears != null">년</template></strong
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||
</template>
|
||||
<template v-if="showWinRate">
|
||||
<span>승률</span><strong>{{ winRate }}</strong>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||
</template>
|
||||
<span>승리</span><strong>{{ numberText(summary.wins) }}</strong> <span>패배</span
|
||||
><strong>{{ numberText(summary.losses) }}</strong>
|
||||
<template v-if="showWinRate">
|
||||
<span>살상률</span><strong>{{ killRate }}</strong>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||
</template>
|
||||
<span>사살</span><strong>{{ numberText(summary.killCrew) }}</strong> <span>피살</span
|
||||
><strong>{{ numberText(summary.deathCrew) }}</strong>
|
||||
<template v-if="showWinRate">
|
||||
<span>승률</span><strong>{{ winRate }}</strong> <span>살상률</span><strong>{{ killRate }}</strong>
|
||||
</template>
|
||||
<span class="battle-general-extra__recent-label">최근 전투</span>
|
||||
<strong class="battle-general-extra__recent-value">
|
||||
{{ formatServerDateTime(summary.recentWar, { format: 'monthDayTime', fallback: '-' }) }}
|
||||
@@ -113,6 +131,10 @@ const killRate = computed(() => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.battle-general-extra > .battle-general-extra__empty {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.battle-general-extra > .battle-general-extra__recent-label {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
>
|
||||
<div class="bottom-item">
|
||||
<button
|
||||
class="bottom-trigger legacy-button legacy-button--navigation"
|
||||
class="bottom-trigger legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
data-bottom-menu="global"
|
||||
:aria-expanded="openId === 'global'"
|
||||
@@ -132,7 +132,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
|
||||
<div class="bottom-item nation-bottom-item">
|
||||
<button
|
||||
class="bottom-trigger nation-trigger legacy-button legacy-button--lumen"
|
||||
class="bottom-trigger nation-trigger legacy-button legacy-button--lumen legacy-button--fixed-height"
|
||||
:style="{
|
||||
'--legacy-button-bg': nationMenuColor,
|
||||
'--legacy-button-border': 'color-mix(in srgb, var(--nation-menu-color) 90%, #000)',
|
||||
@@ -178,7 +178,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
|
||||
<div class="bottom-item">
|
||||
<button
|
||||
class="bottom-trigger quick-trigger legacy-button legacy-button--dark"
|
||||
class="bottom-trigger quick-trigger legacy-button legacy-button--dark legacy-button--fixed-height"
|
||||
type="button"
|
||||
data-bottom-menu="quick"
|
||||
:aria-expanded="openId === 'quick'"
|
||||
@@ -221,7 +221,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
|
||||
<div class="bottom-refresh-controls">
|
||||
<button
|
||||
class="bottom-trigger auto-refresh-trigger legacy-button legacy-button--navigation"
|
||||
class="bottom-trigger auto-refresh-trigger legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
:class="{ active: realtimeEnabled }"
|
||||
type="button"
|
||||
data-bottom-menu="auto-refresh"
|
||||
@@ -232,7 +232,7 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
<strong>{{ realtimeEnabled ? 'ON' : 'OFF' }}</strong>
|
||||
</button>
|
||||
<button
|
||||
class="bottom-trigger manual-refresh-trigger legacy-button legacy-button--dark"
|
||||
class="bottom-trigger manual-refresh-trigger legacy-button legacy-button--dark legacy-button--fixed-height"
|
||||
type="button"
|
||||
data-bottom-menu="manual-refresh"
|
||||
aria-label="직접 갱신"
|
||||
@@ -279,9 +279,9 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
}
|
||||
|
||||
.bottom-trigger {
|
||||
--legacy-button-height: 45px;
|
||||
box-sizing: border-box;
|
||||
width: 125px;
|
||||
height: 45px;
|
||||
padding: 6px 4px;
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
@@ -316,15 +316,6 @@ const onAction = (action: NonNullable<MainNavigationLinkItem['action']>) => {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true']):hover,
|
||||
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true'])[aria-expanded='true'] {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.bottom-trigger.legacy-button:not(:disabled, [aria-disabled='true']):active {
|
||||
height: 43px;
|
||||
}
|
||||
|
||||
.dropup-caret {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useId } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onClickOutside, useElementSize, useMediaQuery, useMouseInElement } from '@vueuse/core';
|
||||
import SkeletonLines from '../ui/SkeletonLines.vue';
|
||||
@@ -7,6 +7,7 @@ import MapCityBasic from './MapCityBasic.vue';
|
||||
import MapCityDetail from './MapCityDetail.vue';
|
||||
import { useMapViewerStore } from '../../stores/mapViewer';
|
||||
import { buildAssetUrl } from '../../utils/mapAssets';
|
||||
import { resolveMapBackgroundPath, resolveMapSeason, resolveNextMapSeason } from '../../utils/mapBackground';
|
||||
import { configuredGameAssetUrl } from '../../utils/imageAssets';
|
||||
|
||||
interface MapSummary {
|
||||
@@ -90,6 +91,45 @@ const emit = defineEmits<{
|
||||
const BASE_MAP_WIDTH = 700;
|
||||
const BASE_MAP_HEIGHT = 500;
|
||||
const SMALL_MAP_SCALE = 5 / 7;
|
||||
const MAP_BACKGROUND_TRANSITION_MS = 480;
|
||||
|
||||
const decodedImageCache = new Map<string, Promise<void>>();
|
||||
const decodedImageElements = new Map<string, HTMLImageElement>();
|
||||
|
||||
const preloadDecodedImage = (url: string): Promise<void> => {
|
||||
const cached = decodedImageCache.get(url);
|
||||
if (cached) return cached;
|
||||
|
||||
const pending = new Promise<void>((resolve, reject) => {
|
||||
if (typeof Image === 'undefined') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const image = new Image();
|
||||
image.decoding = 'async';
|
||||
image.onload = () => {
|
||||
if (typeof image.decode !== 'function') {
|
||||
decodedImageElements.set(url, image);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
void image.decode().then(() => {
|
||||
decodedImageElements.set(url, image);
|
||||
resolve();
|
||||
}, reject);
|
||||
};
|
||||
image.onerror = () => reject(new Error(`map background image load failed: ${url}`));
|
||||
image.src = url;
|
||||
}).catch((error: unknown) => {
|
||||
decodedImageCache.delete(url);
|
||||
decodedImageElements.delete(url);
|
||||
throw error;
|
||||
});
|
||||
|
||||
decodedImageCache.set(url, pending);
|
||||
return pending;
|
||||
};
|
||||
|
||||
const isWide = useMediaQuery('(min-width: 1024px)');
|
||||
const mapStore = useMapViewerStore();
|
||||
@@ -101,6 +141,7 @@ const {
|
||||
selectedCityId: storeSelectedCityId,
|
||||
} = storeToRefs(mapStore);
|
||||
const hasTouchInput = useMediaQuery('(any-pointer: coarse)');
|
||||
const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
|
||||
|
||||
const mapArea = ref<HTMLElement | null>(null);
|
||||
const mapBody = ref<HTMLElement | null>(null);
|
||||
@@ -110,19 +151,6 @@ const mapOptionsMenuId = `map-options-${useId()}`;
|
||||
const { width: mapBodyWidth } = useElementSize(mapBody);
|
||||
const { elementX, elementY } = useMouseInElement(mapArea);
|
||||
|
||||
const resolveSeason = (month: number): string => {
|
||||
if (month <= 3) {
|
||||
return 'spring';
|
||||
}
|
||||
if (month <= 6) {
|
||||
return 'summer';
|
||||
}
|
||||
if (month <= 9) {
|
||||
return 'fall';
|
||||
}
|
||||
return 'winter';
|
||||
};
|
||||
|
||||
const resolveStateClass = (state: number): CityStateClass => {
|
||||
if (state < 10) {
|
||||
return 'good';
|
||||
@@ -242,7 +270,7 @@ const mapSeason = computed(() => {
|
||||
if (!props.mapData) {
|
||||
return 'spring';
|
||||
}
|
||||
return resolveSeason(props.mapData.month);
|
||||
return resolveMapSeason(props.mapData.month);
|
||||
});
|
||||
|
||||
const mapTheme = computed(() => props.mapLayout?.mapName ?? 'che');
|
||||
@@ -330,24 +358,14 @@ const mapSeasonClass = computed(() => {
|
||||
return `map-season-${mapSeason.value}`;
|
||||
});
|
||||
|
||||
const mapBackgroundImage = computed(() => {
|
||||
const theme = mapTheme.value;
|
||||
const season = mapSeason.value;
|
||||
const mapBackground = computed(() => resolveMapBackgroundPath(mapTheme.value, mapSeason.value));
|
||||
|
||||
if (theme === 'ludo_rathowm') {
|
||||
return resolveAsset('map/ludo_rathowm/back.jpg');
|
||||
}
|
||||
if (theme === 'chess') {
|
||||
return resolveAsset('map/chess/chessboard.png');
|
||||
}
|
||||
if (theme === 'pokemon_v1') {
|
||||
return resolveAsset('map/pokemon_v1/back_pal8.png');
|
||||
}
|
||||
if (theme === 'cr') {
|
||||
return resolveAsset('map/cr/bg-fs8.png');
|
||||
}
|
||||
const mapBackgroundImage = computed(() => resolveAsset(mapBackground.value.path));
|
||||
|
||||
return resolveAsset(`map/che/bg_${season}.jpg`);
|
||||
const nextSeasonBackgroundImage = computed(() => {
|
||||
if (!mapBackground.value.seasonal) return null;
|
||||
const nextSeason = resolveNextMapSeason(mapSeason.value);
|
||||
return resolveAsset(resolveMapBackgroundPath(mapTheme.value, nextSeason).path);
|
||||
});
|
||||
|
||||
const mapRoadImage = computed(() => {
|
||||
@@ -364,16 +382,165 @@ const mapRoadImage = computed(() => {
|
||||
return null;
|
||||
});
|
||||
|
||||
const mapBackgroundStyle = computed(() => ({
|
||||
backgroundImage: mapBackgroundImage.value ? `url('${mapBackgroundImage.value}')` : 'none',
|
||||
backgroundSize: '100% 100%',
|
||||
}));
|
||||
const renderedBackgroundImage = ref<string | null>(null);
|
||||
const outgoingBackgroundImage = ref<string | null>(null);
|
||||
const renderedBackgroundElement = ref<HTMLImageElement | null>(null);
|
||||
const backgroundReady = ref(false);
|
||||
const outgoingBackgroundVisible = ref(false);
|
||||
const backgroundTransitioning = ref(false);
|
||||
|
||||
const mapRoadStyle = computed(() => ({
|
||||
backgroundImage: mapRoadImage.value ? `url('${mapRoadImage.value}')` : 'none',
|
||||
backgroundSize: '100% 100%',
|
||||
}));
|
||||
|
||||
type BackgroundRequest = {
|
||||
imageUrl: string;
|
||||
roadUrl: string | null;
|
||||
nextSeasonUrl: string | null;
|
||||
};
|
||||
|
||||
const backgroundRequest = computed<BackgroundRequest | null>(() => {
|
||||
if (!props.mapData || !props.mapLayout) return null;
|
||||
return {
|
||||
imageUrl: mapBackgroundImage.value,
|
||||
roadUrl: mapRoadImage.value,
|
||||
nextSeasonUrl: nextSeasonBackgroundImage.value,
|
||||
};
|
||||
});
|
||||
|
||||
const backgroundRequestKey = computed(() => {
|
||||
const request = backgroundRequest.value;
|
||||
return request ? `${request.imageUrl}\u0000${request.roadUrl ?? ''}` : '';
|
||||
});
|
||||
|
||||
let pendingBackgroundRequest: BackgroundRequest | null = null;
|
||||
let backgroundWorkerRunning = false;
|
||||
let backgroundWorkerDisposed = false;
|
||||
|
||||
const waitForPaint = () =>
|
||||
new Promise<void>((resolve) => {
|
||||
let settled = false;
|
||||
const done = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
window.clearTimeout(timeoutId);
|
||||
resolve();
|
||||
};
|
||||
const timeoutId = window.setTimeout(done, 80);
|
||||
window.requestAnimationFrame(() => window.requestAnimationFrame(done));
|
||||
});
|
||||
|
||||
const waitForBackgroundTransition = () =>
|
||||
new Promise<void>((resolve) => window.setTimeout(resolve, MAP_BACKGROUND_TRANSITION_MS));
|
||||
|
||||
const prefetchUpcomingImages = (request: BackgroundRequest) => {
|
||||
if (request.nextSeasonUrl) {
|
||||
void preloadDecodedImage(request.nextSeasonUrl).catch(() => undefined);
|
||||
}
|
||||
const nextSeasonIcon = resolveAsset(`${resolveNextMapSeason(mapSeason.value)}.gif`);
|
||||
void preloadDecodedImage(nextSeasonIcon).catch(() => undefined);
|
||||
};
|
||||
|
||||
const showBackground = async (request: BackgroundRequest) => {
|
||||
const roadPromise = request.roadUrl
|
||||
? preloadDecodedImage(request.roadUrl).catch(() => undefined)
|
||||
: Promise.resolve();
|
||||
|
||||
try {
|
||||
await preloadDecodedImage(request.imageUrl);
|
||||
await roadPromise;
|
||||
} catch {
|
||||
if (!renderedBackgroundImage.value) {
|
||||
// Preserve the old initial-load fallback: reveal the map and let the
|
||||
// actual img make its normal request instead of leaving a skeleton.
|
||||
renderedBackgroundImage.value = request.imageUrl;
|
||||
backgroundReady.value = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (backgroundWorkerDisposed) return;
|
||||
if (pendingBackgroundRequest && pendingBackgroundRequest.imageUrl !== request.imageUrl) return;
|
||||
|
||||
const currentImage = renderedBackgroundImage.value;
|
||||
if (!currentImage) {
|
||||
renderedBackgroundImage.value = request.imageUrl;
|
||||
backgroundReady.value = true;
|
||||
prefetchUpcomingImages(request);
|
||||
return;
|
||||
}
|
||||
if (currentImage === request.imageUrl) {
|
||||
backgroundReady.value = true;
|
||||
prefetchUpcomingImages(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reduceMotion.value) {
|
||||
renderedBackgroundImage.value = request.imageUrl;
|
||||
outgoingBackgroundImage.value = null;
|
||||
outgoingBackgroundVisible.value = false;
|
||||
backgroundTransitioning.value = false;
|
||||
prefetchUpcomingImages(request);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the old decoded image fully covering the new background for one
|
||||
// paint, then fade only that outgoing layer. Road/city/control DOM remains.
|
||||
outgoingBackgroundImage.value = currentImage;
|
||||
outgoingBackgroundVisible.value = true;
|
||||
backgroundTransitioning.value = false;
|
||||
renderedBackgroundImage.value = request.imageUrl;
|
||||
await nextTick();
|
||||
try {
|
||||
await renderedBackgroundElement.value?.decode();
|
||||
} catch {
|
||||
renderedBackgroundImage.value = currentImage;
|
||||
outgoingBackgroundImage.value = null;
|
||||
outgoingBackgroundVisible.value = false;
|
||||
return;
|
||||
}
|
||||
await waitForPaint();
|
||||
if (backgroundWorkerDisposed) return;
|
||||
|
||||
backgroundTransitioning.value = true;
|
||||
outgoingBackgroundVisible.value = false;
|
||||
await waitForBackgroundTransition();
|
||||
if (backgroundWorkerDisposed) return;
|
||||
|
||||
outgoingBackgroundImage.value = null;
|
||||
backgroundTransitioning.value = false;
|
||||
prefetchUpcomingImages(request);
|
||||
};
|
||||
|
||||
const runBackgroundWorker = async () => {
|
||||
if (backgroundWorkerRunning) return;
|
||||
backgroundWorkerRunning = true;
|
||||
try {
|
||||
while (pendingBackgroundRequest && !backgroundWorkerDisposed) {
|
||||
const request = pendingBackgroundRequest;
|
||||
pendingBackgroundRequest = null;
|
||||
await showBackground(request);
|
||||
}
|
||||
} finally {
|
||||
backgroundWorkerRunning = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
backgroundRequestKey,
|
||||
() => {
|
||||
pendingBackgroundRequest = backgroundRequest.value;
|
||||
void runBackgroundWorker();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
backgroundWorkerDisposed = true;
|
||||
pendingBackgroundRequest = null;
|
||||
});
|
||||
|
||||
const detailProps = computed(() =>
|
||||
effectiveDetailMode.value
|
||||
? {
|
||||
@@ -463,7 +630,7 @@ const selectCity = (cityId: number) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.loading">
|
||||
<div v-if="props.loading || (props.mapData && props.mapLayout && !backgroundReady)">
|
||||
<SkeletonLines :lines="4" />
|
||||
</div>
|
||||
<div v-else-if="!props.mapData || !props.mapLayout" class="map-empty">지도 데이터를 불러오지 못했습니다.</div>
|
||||
@@ -478,8 +645,32 @@ const selectCity = (cityId: number) => {
|
||||
closeMapOptions();
|
||||
"
|
||||
>
|
||||
<div class="map-layer map-bglayer1" :style="mapBackgroundStyle" />
|
||||
<div class="map-layer map-bglayer2" />
|
||||
<div class="map-layer map-bglayer1" data-map-background-layer="current">
|
||||
<img
|
||||
v-if="renderedBackgroundImage"
|
||||
ref="renderedBackgroundElement"
|
||||
class="map-background-image"
|
||||
:src="renderedBackgroundImage"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="map-layer map-bglayer2"
|
||||
data-map-background-layer="outgoing"
|
||||
:class="{
|
||||
'is-visible': outgoingBackgroundVisible,
|
||||
'is-transitioning': backgroundTransitioning,
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="outgoingBackgroundImage"
|
||||
class="map-background-image"
|
||||
:src="outgoingBackgroundImage"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="mapRoadImage" class="map-layer map-bgroad" :style="mapRoadStyle" />
|
||||
<component
|
||||
:is="effectiveDetailMode ? MapCityDetail : MapCityBasic"
|
||||
@@ -738,6 +929,27 @@ const selectCity = (cityId: number) => {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.map-bglayer2 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.map-bglayer2.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.map-bglayer2.is-transitioning {
|
||||
transition: opacity 480ms ease-in-out;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.map-background-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.map-tooltip {
|
||||
position: absolute;
|
||||
z-index: 16;
|
||||
@@ -799,4 +1011,10 @@ const selectCity = (cityId: number) => {
|
||||
.map-empty {
|
||||
color: rgba(232, 221, 196, 0.6);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.map-bglayer2.is-transitioning {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,6 +14,7 @@ defineProps<{
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="legacy-button legacy-button--dark legacy-button--fixed-height"
|
||||
:aria-selected="activePage === 'tournament'"
|
||||
:class="{ active: activePage === 'tournament' }"
|
||||
@click="navigate"
|
||||
@@ -25,6 +26,7 @@ defineProps<{
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="legacy-button legacy-button--dark legacy-button--fixed-height"
|
||||
:aria-selected="activePage === 'betting'"
|
||||
:class="{ active: activePage === 'betting' }"
|
||||
@click="navigate"
|
||||
@@ -34,7 +36,13 @@ defineProps<{
|
||||
</RouterLink>
|
||||
</nav>
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
|
||||
type="button"
|
||||
@click="navigate"
|
||||
>
|
||||
창 닫기
|
||||
</button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</header>
|
||||
@@ -64,33 +72,21 @@ defineProps<{
|
||||
gap: 4px;
|
||||
}
|
||||
button {
|
||||
height: 44px;
|
||||
--legacy-button-height: 44px;
|
||||
margin: 0;
|
||||
border: 1px solid #666;
|
||||
border-radius: 5.25px;
|
||||
background: #444;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tournament-page-tabs button {
|
||||
min-width: 72px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.tournament-page-tabs button.active {
|
||||
border-color: #f39c12;
|
||||
background: #8a5b13;
|
||||
--legacy-button-bg: #8a5b13;
|
||||
--legacy-button-border: #704a0f;
|
||||
}
|
||||
.close-button {
|
||||
width: 88px;
|
||||
padding: 10px 16px;
|
||||
border-color: #375a7f;
|
||||
background: #375a7f;
|
||||
}
|
||||
button:hover,
|
||||
button:focus {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: 2px solid #f39c12;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export type MapSeason = 'spring' | 'summer' | 'fall' | 'winter';
|
||||
|
||||
const FIXED_BACKGROUND_PATHS: Readonly<Record<string, string>> = {
|
||||
ludo_rathowm: 'map/ludo_rathowm/back.jpg',
|
||||
chess: 'map/chess/chessboard.png',
|
||||
pokemon_v1: 'map/pokemon_v1/back_pal8.png',
|
||||
cr: 'map/cr/bg-fs8.png',
|
||||
};
|
||||
|
||||
const NEXT_SEASON: Readonly<Record<MapSeason, MapSeason>> = {
|
||||
spring: 'summer',
|
||||
summer: 'fall',
|
||||
fall: 'winter',
|
||||
winter: 'spring',
|
||||
};
|
||||
|
||||
export const resolveMapSeason = (month: number): MapSeason => {
|
||||
if (month <= 3) return 'spring';
|
||||
if (month <= 6) return 'summer';
|
||||
if (month <= 9) return 'fall';
|
||||
return 'winter';
|
||||
};
|
||||
|
||||
export const resolveNextMapSeason = (season: MapSeason): MapSeason => NEXT_SEASON[season];
|
||||
|
||||
export const resolveMapBackgroundPath = (theme: string, season: MapSeason): { path: string; seasonal: boolean } => {
|
||||
const fixedPath = FIXED_BACKGROUND_PATHS[theme];
|
||||
if (fixedPath) {
|
||||
return { path: fixedPath, seasonal: false };
|
||||
}
|
||||
|
||||
// Ref uses the CHE seasonal backgrounds for CHE, mini-CHE variants, and
|
||||
// unknown legacy-compatible themes that do not define their own backdrop.
|
||||
return { path: `map/che/bg_${season}.jpg`, seasonal: true };
|
||||
};
|
||||
@@ -197,20 +197,31 @@ onMounted(() => {
|
||||
:class="activeTab === 'resource' ? 'resource-page' : 'unique-page'"
|
||||
>
|
||||
<header class="top-back-bar bg0">
|
||||
<button class="legacy-button close-button" type="button" @click="closeWindow">창 닫기</button>
|
||||
<button class="legacy-button reload-button" type="button" :disabled="loading" @click="loadOverview">
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
|
||||
type="button"
|
||||
@click="closeWindow"
|
||||
>
|
||||
창 닫기
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height reload-button"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="loadOverview"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<h1>{{ activeTab === 'resource' ? '경매장' : '유니크 경매장' }}</h1>
|
||||
<button
|
||||
class="legacy-button tab-button"
|
||||
class="legacy-button legacy-button--dark tab-button"
|
||||
:aria-pressed="activeTab === 'resource'"
|
||||
@click="activeTab = 'resource'"
|
||||
>
|
||||
금/쌀
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button tab-button"
|
||||
class="legacy-button legacy-button--dark tab-button"
|
||||
:aria-pressed="activeTab === 'unique'"
|
||||
@click="activeTab = 'unique'"
|
||||
>
|
||||
@@ -318,7 +329,12 @@ onMounted(() => {
|
||||
step="10"
|
||||
required
|
||||
/>
|
||||
<button class="legacy-button" :disabled="actionBusy || selectedResource.isCallerHost">입찰</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--dark"
|
||||
:disabled="actionBusy || selectedResource.isCallerHost"
|
||||
>
|
||||
입찰
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h3 class="subsection-title">경매 등록</h3>
|
||||
@@ -331,7 +347,7 @@ onMounted(() => {
|
||||
<legend>매물</legend>
|
||||
<div class="item-toggle">
|
||||
<button
|
||||
class="legacy-button"
|
||||
class="legacy-button legacy-button--dark"
|
||||
type="button"
|
||||
:aria-pressed="openForm.type === 'BUY_RICE'"
|
||||
@click="openForm.type = 'BUY_RICE'"
|
||||
@@ -339,7 +355,7 @@ onMounted(() => {
|
||||
쌀
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button"
|
||||
class="legacy-button legacy-button--dark"
|
||||
type="button"
|
||||
:aria-pressed="openForm.type === 'SELL_RICE'"
|
||||
@click="openForm.type = 'SELL_RICE'"
|
||||
@@ -363,7 +379,7 @@ onMounted(() => {
|
||||
<span>마감가 ({{ openForm.type === 'BUY_RICE' ? '금' : '쌀' }})</span>
|
||||
<input v-model.number="openForm.finishBidAmount" type="number" min="100" max="10000" step="10" />
|
||||
</label>
|
||||
<button class="legacy-button register-button" :disabled="actionBusy">등록</button>
|
||||
<button class="legacy-button legacy-button--dark register-button" :disabled="actionBusy">등록</button>
|
||||
</form>
|
||||
|
||||
<h3 class="subsection-title">이전 경매(최근 20건)</h3>
|
||||
@@ -408,7 +424,7 @@ onMounted(() => {
|
||||
유산포인트 (잔여: {{ formatNumber(uniqueDetail.remainPoint) }}포인트)
|
||||
</label>
|
||||
<input id="unique-bid" v-model.number="bidAmount" type="number" min="1" required />
|
||||
<button class="legacy-button" :disabled="actionBusy">입찰</button>
|
||||
<button class="legacy-button legacy-button--dark" :disabled="actionBusy">입찰</button>
|
||||
</form>
|
||||
</template>
|
||||
</section>
|
||||
@@ -480,7 +496,9 @@ onMounted(() => {
|
||||
</section>
|
||||
|
||||
<footer class="bottom-bar bg0">
|
||||
<button class="legacy-button close-button" type="button" @click="closeWindow">창 닫기</button>
|
||||
<button class="legacy-button legacy-button--navigation close-button" type="button" @click="closeWindow">
|
||||
창 닫기
|
||||
</button>
|
||||
</footer>
|
||||
</main>
|
||||
</template>
|
||||
@@ -530,66 +548,17 @@ onMounted(() => {
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
.legacy-button {
|
||||
box-sizing: border-box;
|
||||
border: solid #3d3d3d;
|
||||
border-width: 0 1px 4px;
|
||||
border-radius: 5.25px;
|
||||
padding: 5.25px 10.5px;
|
||||
color: #fff;
|
||||
background: #444;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
line-height: 21px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.legacy-button:hover,
|
||||
.legacy-button:focus {
|
||||
border-color: #353535;
|
||||
background: #393939;
|
||||
}
|
||||
.legacy-button:focus-visible {
|
||||
outline: 2px solid #8ab4f8;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.legacy-button:active,
|
||||
.legacy-button[aria-pressed='true'] {
|
||||
border-color: #303030;
|
||||
background: #333;
|
||||
}
|
||||
.legacy-button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.close-button,
|
||||
.reload-button {
|
||||
margin-right: 2px;
|
||||
border-color: #004f28;
|
||||
background: #00582c;
|
||||
}
|
||||
.close-button:hover,
|
||||
.close-button:focus,
|
||||
.reload-button:hover,
|
||||
.reload-button:focus {
|
||||
border-color: #004523;
|
||||
background: #004a25;
|
||||
}
|
||||
.top-back-bar .close-button,
|
||||
.top-back-bar .reload-button {
|
||||
height: 32px;
|
||||
}
|
||||
.tab-button {
|
||||
border-color: #3d3d3d;
|
||||
background: #444;
|
||||
--legacy-button-height: 32px;
|
||||
}
|
||||
.tab-button[aria-pressed='true'] {
|
||||
border-color: #3d3d3d;
|
||||
background: #444;
|
||||
}
|
||||
.tab-button:hover,
|
||||
.tab-button:focus {
|
||||
border-color: #3d3d3d;
|
||||
background: #444;
|
||||
--legacy-button-bg: #333;
|
||||
--legacy-button-border: #303030;
|
||||
}
|
||||
.section-title,
|
||||
.subsection-title,
|
||||
|
||||
@@ -236,9 +236,20 @@ onMounted(() => {
|
||||
<main class="ref-shell battle-page">
|
||||
<header class="battle-top legacy-bg0">
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="battle-nav" type="button" @click="navigate">창 닫기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height battle-nav"
|
||||
type="button"
|
||||
@click="navigate"
|
||||
>
|
||||
창 닫기
|
||||
</button>
|
||||
</RouterLink>
|
||||
<button class="battle-nav" @click="loadBattleCenter">갱신</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height battle-nav"
|
||||
@click="loadBattleCenter"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<h1>감찰부</h1>
|
||||
<div></div>
|
||||
<div></div>
|
||||
@@ -305,7 +316,9 @@ onMounted(() => {
|
||||
</section>
|
||||
<footer class="battle-footer legacy-bg0">
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="battle-nav" type="button" @click="navigate">창 닫기</button>
|
||||
<button class="legacy-button legacy-button--navigation battle-nav" type="button" @click="navigate">
|
||||
창 닫기
|
||||
</button>
|
||||
</RouterLink>
|
||||
</footer>
|
||||
</main>
|
||||
@@ -418,17 +431,11 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
}
|
||||
.battle-nav {
|
||||
--legacy-button-height: 32px;
|
||||
box-sizing: border-box;
|
||||
height: 32px;
|
||||
margin-right: 2px;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #00582c;
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
.battle-footer {
|
||||
|
||||
@@ -130,7 +130,13 @@ const placeBet = async () => {
|
||||
<main id="tournament-betting-container" class="betting-page">
|
||||
<TournamentPageHeader class="bg0" active-page="betting" title="베 팅 장" />
|
||||
<section class="toolbar bg0">
|
||||
<button type="button" @click="load">갱신</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--secondary legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="load"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<span v-if="loading">불러오는 중...</span>
|
||||
<span v-if="message" role="status">{{ message }}</span>
|
||||
</section>
|
||||
@@ -159,12 +165,7 @@ const placeBet = async () => {
|
||||
@request-bet="openBetDialog"
|
||||
/>
|
||||
|
||||
<dialog
|
||||
ref="betDialog"
|
||||
class="bet-dialog"
|
||||
aria-labelledby="bet-dialog-title"
|
||||
@close="selectedTarget = null"
|
||||
>
|
||||
<dialog ref="betDialog" class="bet-dialog" aria-labelledby="bet-dialog-title" @close="selectedTarget = null">
|
||||
<form v-if="selectedTarget" class="bet-dialog-content" @submit.prevent="placeBet">
|
||||
<header>
|
||||
<h2 id="bet-dialog-title">베팅하기</h2>
|
||||
@@ -194,9 +195,13 @@ const placeBet = async () => {
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="gold-color">금{{ selectedAmount }}</span>
|
||||
<span aria-hidden="true">=</span>
|
||||
<strong class="return-color">예상 환수금 {{ selectedExpectedReturn.toLocaleString('ko-KR') }}</strong>
|
||||
<strong class="return-color"
|
||||
>예상 환수금 {{ selectedExpectedReturn.toLocaleString('ko-KR') }}</strong
|
||||
>
|
||||
</output>
|
||||
<p class="bet-preview-note">현재 배당 기준 예상값이며, 베팅 상황에 따라 최종 배당은 달라질 수 있습니다.</p>
|
||||
<p class="bet-preview-note">
|
||||
현재 배당 기준 예상값이며, 베팅 상황에 따라 최종 배당은 달라질 수 있습니다.
|
||||
</p>
|
||||
<p v-if="betError" class="bet-dialog-error" role="alert">{{ betError }}</p>
|
||||
<footer>
|
||||
<button type="button" :disabled="placingBet" @click="closeBetDialog">취소</button>
|
||||
@@ -289,7 +294,9 @@ const placeBet = async () => {
|
||||
</section>
|
||||
<footer class="betting-footer bg0">
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
||||
<button class="legacy-button legacy-button--navigation close-button" type="button" @click="navigate">
|
||||
창 닫기
|
||||
</button>
|
||||
</RouterLink>
|
||||
<small>
|
||||
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit
|
||||
@@ -334,8 +341,8 @@ const placeBet = async () => {
|
||||
text-align: left;
|
||||
}
|
||||
.toolbar button {
|
||||
--legacy-button-height: 44px;
|
||||
min-width: 72px;
|
||||
height: 44px;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -380,7 +387,7 @@ select {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
button {
|
||||
button:not(.legacy-button) {
|
||||
height: 35.5px;
|
||||
color: #fff;
|
||||
background: #444;
|
||||
@@ -388,16 +395,16 @@ button {
|
||||
border-radius: 5.25px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover,
|
||||
button:focus {
|
||||
button:not(.legacy-button):hover,
|
||||
button:not(.legacy-button):focus {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
button:focus-visible,
|
||||
button:not(.legacy-button):focus-visible,
|
||||
select:focus-visible {
|
||||
outline: 2px solid #f39c12;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
button:disabled,
|
||||
button:not(.legacy-button):disabled,
|
||||
select:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -126,7 +126,13 @@ onMounted(() => {
|
||||
<div v-if="accessChecked && !canAccess" class="legacy-raw-access-error" role="alert">{{ errorMessage }}</div>
|
||||
<main v-else id="container" class="legacy-board-page">
|
||||
<header class="top-back-bar bg0">
|
||||
<button class="legacy-button back-button" type="button" @click="closeBoard">돌아가기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button"
|
||||
type="button"
|
||||
@click="closeBoard"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
<div></div>
|
||||
<h1>{{ title }}</h1>
|
||||
<div></div>
|
||||
@@ -163,7 +169,7 @@ onMounted(() => {
|
||||
<div></div>
|
||||
<button
|
||||
id="submitArticle"
|
||||
class="legacy-button legacy-button--secondary"
|
||||
class="legacy-button legacy-button--secondary legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="submitArticle"
|
||||
>
|
||||
@@ -222,7 +228,7 @@ onMounted(() => {
|
||||
@keyup.enter="submitComment(article.id)"
|
||||
/>
|
||||
<button
|
||||
class="legacy-button submit-comment"
|
||||
class="legacy-button legacy-button--dark legacy-button--fixed-height submit-comment"
|
||||
type="button"
|
||||
@click="submitComment(article.id)"
|
||||
>
|
||||
@@ -235,7 +241,13 @@ onMounted(() => {
|
||||
</section>
|
||||
|
||||
<footer class="bottom-bar bg0">
|
||||
<button class="legacy-button back-button" type="button" @click="closeBoard">돌아가기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button"
|
||||
type="button"
|
||||
@click="closeBoard"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
</footer>
|
||||
</template>
|
||||
</main>
|
||||
@@ -296,47 +308,9 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.legacy-button {
|
||||
min-height: 31px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 4px;
|
||||
padding: 4px 12px;
|
||||
color: #fff;
|
||||
background: #444;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legacy-button:hover {
|
||||
border-color: #3d3d3d;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.legacy-button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.legacy-button:active {
|
||||
border-color: #3d3d3d;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
margin-right: 2px;
|
||||
border-color: #004f28;
|
||||
background: #00582c;
|
||||
}
|
||||
|
||||
.back-button:hover,
|
||||
.back-button:focus {
|
||||
border-color: #004523;
|
||||
background: #004a25;
|
||||
}
|
||||
|
||||
.board-state {
|
||||
@@ -414,17 +388,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.article-submit-row .legacy-button {
|
||||
--legacy-button-height: 35.5px;
|
||||
width: auto;
|
||||
min-height: 35.5px;
|
||||
margin-right: 10.5px;
|
||||
margin-left: 10.5px;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.article-submit-row .legacy-button:hover,
|
||||
.article-submit-row .legacy-button:focus,
|
||||
.article-submit-row .legacy-button:active {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.article-frame {
|
||||
@@ -513,8 +480,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.submit-comment {
|
||||
--legacy-button-height: 29.375px;
|
||||
width: 83.333px;
|
||||
min-height: 29.375px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
flex: 0 0 auto;
|
||||
@@ -529,9 +496,9 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.bottom-bar .back-button {
|
||||
--legacy-button-height: 35.5px;
|
||||
display: inline-block;
|
||||
width: 71px;
|
||||
height: 35.5px;
|
||||
margin: 0;
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
|
||||
@@ -318,8 +318,19 @@ const repeatTurns = async (amount: number) => {
|
||||
<template>
|
||||
<main class="chief-page">
|
||||
<header class="chief-top legacy-bg0">
|
||||
<button class="chief-nav" type="button" @click="router.push('/')">돌아가기</button>
|
||||
<button class="chief-nav" @click="loadChiefCenter">갱신</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height chief-nav"
|
||||
type="button"
|
||||
@click="router.push('/')"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height chief-nav"
|
||||
@click="loadChiefCenter"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<h1>사령부</h1>
|
||||
<div></div>
|
||||
<div></div>
|
||||
@@ -442,7 +453,9 @@ const repeatTurns = async (amount: number) => {
|
||||
</template>
|
||||
</div>
|
||||
<footer class="chief-footer legacy-bg0">
|
||||
<button class="chief-nav" type="button" @click="router.push('/')">돌아가기</button>
|
||||
<button class="legacy-button legacy-button--navigation chief-nav" type="button" @click="router.push('/')">
|
||||
돌아가기
|
||||
</button>
|
||||
</footer>
|
||||
</main>
|
||||
</template>
|
||||
@@ -643,19 +656,12 @@ const repeatTurns = async (amount: number) => {
|
||||
text-align: center;
|
||||
}
|
||||
.chief-nav {
|
||||
--legacy-button-height: 32px;
|
||||
box-sizing: border-box;
|
||||
height: 32px;
|
||||
margin-right: 2px;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #00582c;
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.layout-desktop {
|
||||
display: block;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { formatReservedCommandBrief } from '../components/command/reservedCommandBrief';
|
||||
import type { CommandTable } from '../components/command/types';
|
||||
import { cityLevelMap, formatOfficerLevelText, regionMap } from '../utils/nationFormat';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||
@@ -9,10 +11,12 @@ import { trpc } from '../utils/trpc';
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.world.getCurrentCity.query>>;
|
||||
type General = Result['generals'][number];
|
||||
type ReservedCommand = General['turns'][number];
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const data = ref<Result | null>(null);
|
||||
const commandTable = ref<CommandTable | null>(null);
|
||||
const error = ref('');
|
||||
const selected = ref<number>();
|
||||
let loadSequence = 0;
|
||||
@@ -29,8 +33,10 @@ const load = async (cityId?: number) => {
|
||||
const sequence = ++loadSequence;
|
||||
try {
|
||||
const result = await trpc.world.getCurrentCity.query(cityId ? { cityId } : undefined);
|
||||
const table = await trpc.turns.getCommandTable.query({ generalId: result.me.id });
|
||||
if (sequence !== loadSequence) return;
|
||||
data.value = result;
|
||||
commandTable.value = table;
|
||||
selected.value = result.city.id;
|
||||
error.value = '';
|
||||
} catch (cause) {
|
||||
@@ -76,6 +82,8 @@ const defenceTrainText = (value: number | null) => {
|
||||
return '△';
|
||||
};
|
||||
const generalImage = (general: General): string => resolveGeneralIconUrl(general);
|
||||
const commandBrief = (command: ReservedCommand): string =>
|
||||
formatReservedCommandBrief('general', command.action, command.args, commandTable.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -282,8 +290,12 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
||||
<td>{{ general.atmos ?? '?' }}</td>
|
||||
<td class="turns" :class="{ 'turns--reserved': general.turns.length > 0 }">
|
||||
<template v-if="general.turns.length">
|
||||
<span v-for="(turn, index) in general.turns" :key="index" class="turn-line"
|
||||
>{{ index + 1 }} : {{ turn }}</span
|
||||
<span
|
||||
v-for="(turn, index) in general.turns"
|
||||
:key="index"
|
||||
class="turn-line"
|
||||
:title="commandBrief(turn)"
|
||||
>{{ index + 1 }} : {{ commandBrief(turn) }}</span
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="general.npcState > 1">NPC 장수</template>
|
||||
|
||||
@@ -53,7 +53,13 @@ onMounted(async () => {
|
||||
<template>
|
||||
<main class="global-page legacy-bg0">
|
||||
<header class="legacy-title">
|
||||
<button type="button" @click="goBack">돌아가기</button><strong>중원 정보</strong>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="goBack"
|
||||
>
|
||||
돌아가기</button
|
||||
><strong>중원 정보</strong>
|
||||
</header>
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<section v-if="data" class="section">
|
||||
@@ -147,7 +153,15 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="legacy-title footer"><button type="button" @click="goBack">돌아가기</button></footer>
|
||||
<footer class="legacy-title footer">
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="goBack"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
</footer>
|
||||
<button class="legacy-compat-button" type="button" tabindex="-1" aria-hidden="true" />
|
||||
</main>
|
||||
</template>
|
||||
@@ -178,15 +192,10 @@ onMounted(async () => {
|
||||
line-height: 32px;
|
||||
}
|
||||
.legacy-title button {
|
||||
--legacy-button-height: 32px;
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 88px;
|
||||
border: 1px solid #0a9960;
|
||||
border-radius: 0 0 4px;
|
||||
background: #087f45;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
.legacy-compat-button {
|
||||
display: none;
|
||||
|
||||
@@ -449,10 +449,12 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<header class="top-back-bar legacy-bg0">
|
||||
<RouterLink class="top-button legacy-button legacy-button--navigation" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="top-button legacy-button legacy-button--navigation legacy-button--fixed-height" to="/"
|
||||
>돌아가기</RouterLink
|
||||
>
|
||||
<strong>유산 관리</strong>
|
||||
<button
|
||||
class="top-button legacy-button legacy-button--navigation"
|
||||
class="top-button legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="loadStatus"
|
||||
@@ -786,8 +788,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.top-button {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
padding: 2px 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -338,7 +338,13 @@ onMounted(() => {
|
||||
<main id="nation-betting-container" class="nation-betting-page legacy-bg0">
|
||||
<header class="legacy-top-bar">
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="legacy-button legacy-button--navigation" type="button" @click="navigate">돌아가기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="navigate"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
</RouterLink>
|
||||
<div></div>
|
||||
<h1>국가 베팅장</h1>
|
||||
@@ -395,7 +401,7 @@ onMounted(() => {
|
||||
<div>사용 포인트: {{ usedAmount.toLocaleString('ko-KR') }}</div>
|
||||
<div>대상: {{ selectionLabel(selectedKey) }}</div>
|
||||
<input v-model.number="amount" aria-label="베팅 금액" type="number" min="10" max="1000" step="10" />
|
||||
<button type="submit" :disabled="submitting">베팅</button>
|
||||
<button class="legacy-button legacy-button--primary" type="submit" :disabled="submitting">베팅</button>
|
||||
</form>
|
||||
|
||||
<div class="payout-table">
|
||||
@@ -475,33 +481,10 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.legacy-nav-button,
|
||||
.betting-form button {
|
||||
height: 32px;
|
||||
border: 0;
|
||||
background: #00582c;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
.legacy-top-bar .legacy-button {
|
||||
--legacy-button-height: 32px;
|
||||
}
|
||||
|
||||
.legacy-nav-button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.legacy-nav-button:hover,
|
||||
.legacy-nav-button:focus,
|
||||
.betting-form button:hover,
|
||||
.betting-form button:focus {
|
||||
filter: brightness(1.18);
|
||||
}
|
||||
|
||||
.legacy-nav-button:focus-visible,
|
||||
.betting-form button:focus-visible,
|
||||
.betting-candidate:focus-visible,
|
||||
.betting-item:focus-visible {
|
||||
outline: 2px solid #f39c12;
|
||||
@@ -662,9 +645,8 @@ onMounted(() => {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.betting-footer .legacy-nav-button {
|
||||
.betting-footer .legacy-button {
|
||||
width: 90px;
|
||||
height: 35.5px;
|
||||
}
|
||||
|
||||
.betting-notice,
|
||||
|
||||
@@ -536,14 +536,25 @@ onBeforeUnmount(() => {
|
||||
<main class="general-page legacy-bg0">
|
||||
<header class="top-bar">
|
||||
<span class="left-actions">
|
||||
<button class="top-button nation-button" @click="router.push('/')">돌아가기</button>
|
||||
<button class="top-button nation-button" :disabled="loading" @click="load">갱신</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height top-button nation-button"
|
||||
@click="router.push('/')"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height top-button nation-button"
|
||||
:disabled="loading"
|
||||
@click="load"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
</span>
|
||||
<strong>세력 장수</strong>
|
||||
<span class="right-actions">
|
||||
<span class="dropdown">
|
||||
<button
|
||||
class="top-button mode-button"
|
||||
class="legacy-button legacy-button--primary legacy-button--fixed-height top-button mode-button"
|
||||
:aria-expanded="viewMenuOpen"
|
||||
@click="
|
||||
viewMenuOpen = !viewMenuOpen;
|
||||
@@ -576,7 +587,7 @@ onBeforeUnmount(() => {
|
||||
</span>
|
||||
<span class="dropdown">
|
||||
<button
|
||||
class="top-button columns-button"
|
||||
class="legacy-button legacy-button--info legacy-button--fixed-height top-button columns-button"
|
||||
:aria-expanded="columnMenuOpen"
|
||||
@click="
|
||||
columnMenuOpen = !columnMenuOpen;
|
||||
@@ -850,45 +861,30 @@ onBeforeUnmount(() => {
|
||||
right: 0;
|
||||
}
|
||||
.top-button {
|
||||
--legacy-button-height: 32px;
|
||||
display: inline-flex;
|
||||
width: 89px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-right: 1px solid #151515;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nation-button {
|
||||
background: #006c48;
|
||||
}
|
||||
.nation-button:hover {
|
||||
background: #00855a;
|
||||
--legacy-button-bg: #006c48;
|
||||
--legacy-button-border: #005d3e;
|
||||
}
|
||||
.mode-button {
|
||||
background: #375a7f;
|
||||
border-bottom: 0 solid #325172;
|
||||
}
|
||||
.mode-button:hover,
|
||||
.mode-button[aria-expanded='true'],
|
||||
.mode-button:active {
|
||||
border-bottom-width: 3px;
|
||||
--legacy-button-bg: #375a7f;
|
||||
--legacy-button-border: #325172;
|
||||
}
|
||||
.mode-button,
|
||||
.columns-button {
|
||||
width: 90px;
|
||||
}
|
||||
.columns-button {
|
||||
background: #3297cf;
|
||||
}
|
||||
.columns-button:hover {
|
||||
filter: brightness(1.12);
|
||||
--legacy-button-bg: #3297cf;
|
||||
--legacy-button-border: #2b80b0;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
|
||||
@@ -173,7 +173,9 @@ onMounted(() => void loadStratFinan());
|
||||
<template>
|
||||
<main id="finance-container" class="page-finance">
|
||||
<nav class="top-back-bar">
|
||||
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="legacy-button legacy-button--navigation legacy-button--fixed-height" to="/"
|
||||
>돌아가기</RouterLink
|
||||
>
|
||||
<span />
|
||||
<strong>내무부</strong>
|
||||
<span />
|
||||
@@ -224,7 +226,7 @@ onMounted(() => void loadStratFinan());
|
||||
<span>
|
||||
<button
|
||||
v-if="canEdit && !editingNationMsg"
|
||||
class="message-button"
|
||||
class="legacy-button legacy-button--secondary message-button"
|
||||
type="button"
|
||||
@click="enableEditNationMsg"
|
||||
>
|
||||
@@ -232,7 +234,7 @@ onMounted(() => void loadStratFinan());
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit && editingNationMsg"
|
||||
class="policy-submit"
|
||||
class="legacy-button legacy-button--primary policy-submit"
|
||||
type="button"
|
||||
@click="saveNationMsg"
|
||||
>
|
||||
@@ -240,7 +242,7 @@ onMounted(() => void loadStratFinan());
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit && editingNationMsg"
|
||||
class="policy-cancel"
|
||||
class="legacy-button legacy-button--secondary policy-cancel"
|
||||
type="button"
|
||||
@click="rollbackNationMsg"
|
||||
>
|
||||
@@ -249,24 +251,15 @@ onMounted(() => void loadStratFinan());
|
||||
</span>
|
||||
</header>
|
||||
<div v-if="!editingNationMsg" class="message-preview" v-html="nationMsg || '내용 없음'" />
|
||||
<LegacyHtmlEditor
|
||||
v-else
|
||||
v-model="nationMsgDraft"
|
||||
:max-length="16384"
|
||||
aria-label="국가 방침"
|
||||
/>
|
||||
<LegacyHtmlEditor v-else v-model="nationMsgDraft" :max-length="16384" aria-label="국가 방침" />
|
||||
</section>
|
||||
<section
|
||||
id="scout-message-form"
|
||||
class="message-form"
|
||||
:class="{ 'message-form--editing': editingScoutMsg }"
|
||||
>
|
||||
<section id="scout-message-form" class="message-form" :class="{ 'message-form--editing': editingScoutMsg }">
|
||||
<header class="green-header">
|
||||
<span>임관 권유</span>
|
||||
<span>
|
||||
<button
|
||||
v-if="canEdit && !editingScoutMsg"
|
||||
class="message-button"
|
||||
class="legacy-button legacy-button--secondary message-button"
|
||||
type="button"
|
||||
@click="enableEditScoutMsg"
|
||||
>
|
||||
@@ -274,7 +267,7 @@ onMounted(() => void loadStratFinan());
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit && editingScoutMsg"
|
||||
class="policy-submit"
|
||||
class="legacy-button legacy-button--primary policy-submit"
|
||||
type="button"
|
||||
@click="saveScoutMsg"
|
||||
>
|
||||
@@ -282,7 +275,7 @@ onMounted(() => void loadStratFinan());
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit && editingScoutMsg"
|
||||
class="policy-cancel"
|
||||
class="legacy-button legacy-button--secondary policy-cancel"
|
||||
type="button"
|
||||
@click="rollbackScoutMsg"
|
||||
>
|
||||
@@ -292,12 +285,7 @@ onMounted(() => void loadStratFinan());
|
||||
</header>
|
||||
<div class="scout-limit">870px x 200px를 넘어서는 내용은 표시되지 않습니다.</div>
|
||||
<div v-if="!editingScoutMsg" class="message-preview scout-preview" v-html="scoutMsg || '내용 없음'" />
|
||||
<LegacyHtmlEditor
|
||||
v-else
|
||||
v-model="scoutMsgDraft"
|
||||
:max-length="1000"
|
||||
aria-label="임관 권유"
|
||||
/>
|
||||
<LegacyHtmlEditor v-else v-model="scoutMsgDraft" :max-length="1000" aria-label="임관 권유" />
|
||||
</section>
|
||||
|
||||
<div class="finance-title">예산&정책</div>
|
||||
@@ -362,10 +350,17 @@ onMounted(() => void loadStratFinan());
|
||||
<input v-model.number="policy.rate" aria-label="세율" type="number" min="5" max="30" /><span
|
||||
>%</span
|
||||
>
|
||||
<button v-if="canEdit" class="policy-submit" type="button" @click="setRate">변경</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="policy-cancel"
|
||||
class="legacy-button legacy-button--primary policy-submit"
|
||||
type="button"
|
||||
@click="setRate"
|
||||
>
|
||||
변경
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="legacy-button legacy-button--secondary policy-cancel"
|
||||
type="button"
|
||||
@click="policy.rate = oldPolicy.rate"
|
||||
>
|
||||
@@ -379,10 +374,17 @@ onMounted(() => void loadStratFinan());
|
||||
<input v-model.number="policy.bill" aria-label="지급률" type="number" min="20" max="200" /><span
|
||||
>%</span
|
||||
>
|
||||
<button v-if="canEdit" class="policy-submit" type="button" @click="setBill">변경</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="policy-cancel"
|
||||
class="legacy-button legacy-button--primary policy-submit"
|
||||
type="button"
|
||||
@click="setBill"
|
||||
>
|
||||
변경
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="legacy-button legacy-button--secondary policy-cancel"
|
||||
type="button"
|
||||
@click="policy.bill = oldPolicy.bill"
|
||||
>
|
||||
@@ -400,10 +402,17 @@ onMounted(() => void loadStratFinan());
|
||||
min="1"
|
||||
max="99"
|
||||
/><span>년</span>
|
||||
<button v-if="canEdit" class="policy-submit" type="button" @click="setSecretLimit">변경</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="policy-cancel"
|
||||
class="legacy-button legacy-button--primary policy-submit"
|
||||
type="button"
|
||||
@click="setSecretLimit"
|
||||
>
|
||||
변경
|
||||
</button>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="legacy-button legacy-button--secondary policy-cancel"
|
||||
type="button"
|
||||
@click="policy.secretLimit = oldPolicy.secretLimit"
|
||||
>
|
||||
@@ -438,7 +447,7 @@ onMounted(() => void loadStratFinan());
|
||||
<input v-for="index in 4" :key="`compat-input-${index}`" type="hidden" />
|
||||
</div>
|
||||
<footer class="bottom-bar">
|
||||
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="legacy-button legacy-button--navigation" to="/">돌아가기</RouterLink>
|
||||
</footer>
|
||||
</template>
|
||||
</main>
|
||||
@@ -472,32 +481,8 @@ onMounted(() => void loadStratFinan());
|
||||
.top-back-bar button {
|
||||
grid-column: 5;
|
||||
}
|
||||
.legacy-button,
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #00502a;
|
||||
border-radius: 4px;
|
||||
padding: 5.25px 10.5px;
|
||||
color: #fff;
|
||||
background: #00582c;
|
||||
font: inherit;
|
||||
line-height: 21px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.message-button,
|
||||
.policy-cancel {
|
||||
border-color: #6c757d;
|
||||
background: #6c757d;
|
||||
}
|
||||
.policy-submit {
|
||||
border-color: #325172;
|
||||
background: #375a7f;
|
||||
}
|
||||
button:hover,
|
||||
.legacy-button:hover {
|
||||
filter: brightness(1.2);
|
||||
.top-back-bar .legacy-button {
|
||||
--legacy-button-height: 32px;
|
||||
}
|
||||
button:focus-visible,
|
||||
.legacy-button:focus-visible,
|
||||
|
||||
@@ -355,13 +355,14 @@ const submitPriority = async (section: PrioritySectionKey) => {
|
||||
error.value = `설정하지 못했습니다: ${resolveErrorMessage(caught)}`;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main id="npc-policy-page" class="npc-page">
|
||||
<nav class="top-back-bar legacy-bg0">
|
||||
<RouterLink class="back-button" to="/">돌아가기</RouterLink>
|
||||
<RouterLink class="legacy-button legacy-button--navigation legacy-button--fixed-height back-button" to="/"
|
||||
>돌아가기</RouterLink
|
||||
>
|
||||
<strong>NPC 정책</strong>
|
||||
</nav>
|
||||
|
||||
@@ -579,21 +580,14 @@ const submitPriority = async (section: PrioritySectionKey) => {
|
||||
}
|
||||
|
||||
.back-button {
|
||||
--legacy-button-height: 32px;
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 88px;
|
||||
color: #fff;
|
||||
background: #087f45;
|
||||
border: 1px solid #0a9960;
|
||||
border-radius: 0 0 4px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.back-button:hover,
|
||||
.back-button:focus-visible {
|
||||
background: #0a9960;
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
@@ -209,11 +209,15 @@ onMounted(() => {
|
||||
<template>
|
||||
<main id="container" class="pageVote bg0">
|
||||
<header class="back_bar bg0">
|
||||
<button class="legacy-button legacy-button--navigation back_btn" type="button" @click="router.push('/')">
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height back_btn"
|
||||
type="button"
|
||||
@click="router.push('/')"
|
||||
>
|
||||
창 닫기
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation reload_btn"
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height reload_btn"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="reloadVote"
|
||||
@@ -453,8 +457,7 @@ onMounted(() => {
|
||||
|
||||
.back_btn,
|
||||
.reload_btn {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
margin-right: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -190,10 +190,16 @@ const start = async () => {
|
||||
<TournamentPageHeader class="bg0" active-page="tournament" title="삼모전 토너먼트" />
|
||||
|
||||
<section class="toolbar bg0">
|
||||
<button type="button" @click="load">갱신</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--secondary legacy-button--fixed-height"
|
||||
type="button"
|
||||
@click="load"
|
||||
>
|
||||
갱신
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="join-button"
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height join-button"
|
||||
:disabled="snapshot?.state?.stage !== 1 || isParticipant"
|
||||
@click="join"
|
||||
>
|
||||
@@ -382,7 +388,9 @@ const start = async () => {
|
||||
<input type="hidden" name="tournamentAction" value="join" />
|
||||
<footer class="tournament-footer bg0">
|
||||
<RouterLink v-slot="{ navigate }" custom to="/">
|
||||
<button class="close-button" type="button" @click="navigate">창 닫기</button>
|
||||
<button class="legacy-button legacy-button--navigation close-button" type="button" @click="navigate">
|
||||
창 닫기
|
||||
</button>
|
||||
</RouterLink>
|
||||
<small>
|
||||
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com) / Credit
|
||||
@@ -439,8 +447,8 @@ const start = async () => {
|
||||
padding: 1px;
|
||||
}
|
||||
.toolbar button {
|
||||
--legacy-button-height: 44px;
|
||||
min-width: 72px;
|
||||
height: 44px;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -451,7 +459,7 @@ const start = async () => {
|
||||
min-height: 32px;
|
||||
padding: 5px;
|
||||
}
|
||||
button {
|
||||
button:not(.legacy-button) {
|
||||
height: 35.5px;
|
||||
margin: 0 2px;
|
||||
border: 1px solid #666;
|
||||
@@ -460,16 +468,17 @@ button {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover,
|
||||
button:focus {
|
||||
button:not(.legacy-button):hover,
|
||||
button:not(.legacy-button):focus {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
button:focus-visible {
|
||||
button:not(.legacy-button):focus-visible {
|
||||
outline: 2px solid #f39c12;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.join-button {
|
||||
background: #8a5b13;
|
||||
--legacy-button-bg: #8a5b13;
|
||||
--legacy-button-border: #704a0f;
|
||||
}
|
||||
.operator-row span {
|
||||
color: orange;
|
||||
|
||||
@@ -179,14 +179,14 @@ onMounted(() => {
|
||||
<main id="container" class="legacy-troop-page">
|
||||
<header class="topBackBar bg0">
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacyNavButton backLink"
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height legacyNavButton backLink"
|
||||
type="button"
|
||||
@click="router.push('/')"
|
||||
>
|
||||
돌아가기
|
||||
</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacyNavButton reloadButton"
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height legacyNavButton reloadButton"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
@click="refresh"
|
||||
@@ -395,8 +395,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.legacyNavButton {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
margin-right: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -137,11 +137,18 @@ onMounted(async () => {
|
||||
<main id="yearbook-container" class="yearbook-page legacy-bg0">
|
||||
<header class="yearbook-title legacy-bg0">
|
||||
<strong>연 감</strong>
|
||||
<button class="legacy-button close-button" type="button" @click="closePage">창 닫기</button>
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height close-button"
|
||||
type="button"
|
||||
@click="closePage"
|
||||
>
|
||||
창 닫기
|
||||
</button>
|
||||
<span class="settings-menu">
|
||||
<button
|
||||
class="legacy-button legacy-button--navigation"
|
||||
class="legacy-button legacy-button--navigation legacy-button--fixed-height"
|
||||
type="button"
|
||||
:aria-expanded="settingsOpen"
|
||||
@click="settingsOpen = !settingsOpen"
|
||||
>
|
||||
⚙ 설정⌄
|
||||
@@ -235,7 +242,7 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<footer class="yearbook-footer">
|
||||
<button class="legacy-button" type="button" @click="closePage">창 닫기</button>
|
||||
<button class="legacy-button legacy-button--navigation" type="button" @click="closePage">창 닫기</button>
|
||||
</footer>
|
||||
<div class="dropdown-compat-buttons" aria-hidden="true">
|
||||
<button type="button" tabindex="-1" /><button type="button" tabindex="-1" />
|
||||
@@ -286,14 +293,14 @@ onMounted(async () => {
|
||||
}
|
||||
.yearbook-title .close-button {
|
||||
left: 0;
|
||||
height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
}
|
||||
.settings-menu {
|
||||
right: 0;
|
||||
height: 32px;
|
||||
}
|
||||
.settings-menu > .legacy-button {
|
||||
height: 32px;
|
||||
--legacy-button-height: 32px;
|
||||
}
|
||||
.settings-item {
|
||||
position: absolute;
|
||||
@@ -393,16 +400,6 @@ onMounted(async () => {
|
||||
.dropdown-compat-buttons {
|
||||
display: none;
|
||||
}
|
||||
.year-selector .legacy-button {
|
||||
border: 0;
|
||||
background: #444;
|
||||
}
|
||||
.settings-menu > .legacy-button,
|
||||
.yearbook-title .close-button {
|
||||
border: 0;
|
||||
background: #00582c;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
min-height: 72px;
|
||||
padding: 1px 0;
|
||||
|
||||
Reference in New Issue
Block a user