merge: 최신 main을 세력일람 장수 hover 작업에 통합
This commit is contained in:
@@ -7,6 +7,7 @@ import { formatLocalTimeSeconds } from '../../utils/legacyDateTime';
|
||||
import { legacyExperiencePercent, ratioPercent } from '../../utils/legacyProgress';
|
||||
import { DEFAULT_GENERAL_ICON_URL, resolveGeneralIconBackgroundImage } from '../../utils/generalIcon';
|
||||
import { configuredGameAssetUrl } from '../../utils/imageAssets';
|
||||
import { legacyLuminanceTextColor } from '../../utils/legacyNationColor';
|
||||
|
||||
interface GeneralStats {
|
||||
leadership: number;
|
||||
@@ -155,19 +156,11 @@ const injuryInfo = computed(() => {
|
||||
return { text: '건강', color: '#ffffff' };
|
||||
});
|
||||
|
||||
const isBrightColor = (color: string): boolean => {
|
||||
const normalized = /^#[0-9a-f]{6}$/iu.test(color) ? color.slice(1) : '173d27';
|
||||
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
||||
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
||||
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
||||
return (red * 299 + green * 587 + blue * 114) / 1000 >= 150;
|
||||
};
|
||||
|
||||
const titleStyle = computed(() => {
|
||||
const backgroundColor = props.nationColor || '#173d27';
|
||||
return {
|
||||
backgroundColor,
|
||||
color: isBrightColor(backgroundColor) ? '#000000' : '#ffffff',
|
||||
color: legacyLuminanceTextColor(backgroundColor),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue';
|
||||
import type { MessageType } from '@sammo-ts/logic';
|
||||
import { legacyLuminanceTextColor } from '../../utils/legacyNationColor';
|
||||
import SkeletonLines from '../ui/SkeletonLines.vue';
|
||||
import MessagePlate from './MessagePlate.vue';
|
||||
|
||||
@@ -172,14 +173,20 @@ const forwardResponse = (messageId: number, response: boolean) => {
|
||||
v-for="group in mailboxGroups"
|
||||
:key="group.label"
|
||||
:label="group.label"
|
||||
:style="{ backgroundColor: group.color ?? '#000000', color: '#ffffff' }"
|
||||
:style="{
|
||||
backgroundColor: group.color ?? '#000000',
|
||||
color: legacyLuminanceTextColor(group.color ?? '#000000'),
|
||||
}"
|
||||
>
|
||||
<option
|
||||
v-for="option in group.options"
|
||||
:key="`${group.label}-${option.value}`"
|
||||
:value="option.value"
|
||||
:disabled="option.disabled"
|
||||
:style="{ backgroundColor: option.color ?? '#000000', color: '#ffffff' }"
|
||||
:style="{
|
||||
backgroundColor: option.color ?? '#000000',
|
||||
color: legacyLuminanceTextColor(option.color ?? '#000000'),
|
||||
}"
|
||||
>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import type { MessageType } from '@sammo-ts/logic';
|
||||
import { DEFAULT_GENERAL_ICON_URL, resolveMessageGeneralIconUrl, useDefaultGeneralIcon } from '../../utils/generalIcon';
|
||||
import { isLegacyNationColorBright } from '../../utils/legacyNationColor';
|
||||
|
||||
interface MessageTarget {
|
||||
generalId: number;
|
||||
@@ -95,25 +96,13 @@ const scheduleDeleteExpiry = () => {
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const isBright = (color: string): boolean => {
|
||||
const match = /^#([0-9a-f]{6})$/i.exec(color);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
const value = Number.parseInt(match[1]!, 16);
|
||||
const red = (value >> 16) & 0xff;
|
||||
const green = (value >> 8) & 0xff;
|
||||
const blue = value & 0xff;
|
||||
return red * 0.299 + green * 0.587 + blue * 0.114 > 160;
|
||||
};
|
||||
|
||||
const iconUrl = computed(() => resolveMessageGeneralIconUrl(props.message.src.icon));
|
||||
const canReplyToGeneral = (target: MessageTarget): boolean => props.replyableGeneralIds.includes(target.generalId);
|
||||
|
||||
const targetClass = (target: MessageTarget) => ({
|
||||
'msg-target': true,
|
||||
'msg-bright': isBright(target.color),
|
||||
'msg-dark': !isBright(target.color),
|
||||
'msg-bright': isLegacyNationColorBright(target.color),
|
||||
'msg-dark': !isLegacyNationColorBright(target.color),
|
||||
});
|
||||
|
||||
const setTarget = (target: MessageTarget) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import SkeletonLines from '../ui/SkeletonLines.vue';
|
||||
import { legacyNationTextColor } from '../../utils/legacyNationColor';
|
||||
import { legacyLuminanceTextColor } from '../../utils/legacyNationColor';
|
||||
import { formatOfficerLevelText } from '../../utils/nationFormat';
|
||||
import { getNpcColor } from '../../utils/npcColor';
|
||||
|
||||
@@ -60,7 +60,7 @@ const displayChiefName = (chief: NationChief | undefined): string => {
|
||||
<div v-else class="nation-grid">
|
||||
<div
|
||||
class="title"
|
||||
:style="{ backgroundColor: props.nation.color, color: legacyNationTextColor(props.nation.color) }"
|
||||
:style="{ backgroundColor: props.nation.color, color: legacyLuminanceTextColor(props.nation.color) }"
|
||||
>
|
||||
{{ props.nation.name }}
|
||||
</div>
|
||||
|
||||
@@ -21,3 +21,17 @@ const lightTextBackgrounds = new Set([
|
||||
|
||||
export const legacyNationTextColor = (backgroundColor: string): '#FFFFFF' | '#000000' =>
|
||||
lightTextBackgrounds.has(backgroundColor.toUpperCase()) ? '#FFFFFF' : '#000000';
|
||||
|
||||
/** Mirrors Ref `isBrightColor()`, used by Vue nation labels and message targets. */
|
||||
export const isLegacyNationColorBright = (backgroundColor: string): boolean => {
|
||||
const normalized = backgroundColor.trim().replace(/^#/u, '');
|
||||
if (!/^[0-9a-f]{6}$/iu.test(normalized)) return false;
|
||||
|
||||
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
||||
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
||||
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
||||
return red * 0.299 + green * 0.587 + blue * 0.114 > 140;
|
||||
};
|
||||
|
||||
export const legacyLuminanceTextColor = (backgroundColor: string): '#000000' | '#FFFFFF' =>
|
||||
isLegacyNationColorBright(backgroundColor) ? '#000000' : '#FFFFFF';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { cityLevelMap, formatOfficerLevelText, regionMap } from '../utils/nationFormat';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.world.getCurrentCity.query>>;
|
||||
@@ -57,31 +58,11 @@ const populationRate = computed(() => {
|
||||
if (!city.value || city.value.population === null) return '?';
|
||||
return String(Math.round((city.value.population / city.value.populationMax) * 10_000) / 100);
|
||||
});
|
||||
const contrastColors = new Set([
|
||||
'',
|
||||
'#330000',
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#6495ED',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#800080',
|
||||
'#A9A9A9',
|
||||
'#000000',
|
||||
]);
|
||||
const cityTitleStyle = computed(() => {
|
||||
const backgroundColor = city.value?.nationColor.toUpperCase() ?? '#000000';
|
||||
return {
|
||||
backgroundColor,
|
||||
color: contrastColors.has(backgroundColor) ? '#FFFFFF' : '#000000',
|
||||
color: legacyNationTextColor(backgroundColor),
|
||||
};
|
||||
});
|
||||
const woundedStat = (value: number, injury: number) =>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useRouter } from 'vue-router';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { resolveGeneralIconUrl } from '../utils/generalIcon';
|
||||
import { formatSeoulDateTime } from '../utils/legacyDateTime';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
|
||||
type DiplomacyResponse = Awaited<ReturnType<typeof trpc.diplomacy.getLetters.query>>;
|
||||
type DiplomacyLetter = DiplomacyResponse['letters'][number];
|
||||
@@ -212,18 +213,9 @@ const stateOptionLabelMap: Record<string, string> = {
|
||||
const targetNation = (letter: DiplomacyLetter) =>
|
||||
letter.src.nationId === data.value?.myNationId ? letter.dest : letter.src;
|
||||
|
||||
const isBrightColor = (color: string): boolean => {
|
||||
const normalized = color.trim().replace(/^#/u, '');
|
||||
if (!/^[0-9a-f]{6}$/iu.test(normalized)) return false;
|
||||
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
||||
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
||||
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
||||
return red * 0.299 + green * 0.587 + blue * 0.114 > 170;
|
||||
};
|
||||
|
||||
const nationStyle = (color: string) => ({
|
||||
backgroundColor: color || '#315f86',
|
||||
color: isBrightColor(color) ? '#000' : '#fff',
|
||||
color: legacyLuminanceTextColor(color || '#315f86'),
|
||||
});
|
||||
|
||||
const signerIcon = (signer: DiplomacyLetter['src'] | DiplomacyLetter['dest']): string | null => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import MapViewer from '../components/main/MapViewer.vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.world.getGlobalInfo.query>>;
|
||||
type Layout = Awaited<ReturnType<typeof trpc.world.getMapLayout.query>>;
|
||||
@@ -19,7 +19,7 @@ const stateClass = (value: number) => `state-${value}`;
|
||||
const nationMap = computed(() => new Map(data.value?.nations.map((nation) => [nation.id, nation]) ?? []));
|
||||
const nationNameStyle = (color: string) => ({
|
||||
backgroundColor: color,
|
||||
color: legacyNationTextColor(color),
|
||||
color: legacyLuminanceTextColor(color),
|
||||
});
|
||||
watch(
|
||||
matrixElement,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getNpcColor } from '../utils/npcColor';
|
||||
import { formatSeoulDateTime } from '../utils/legacyDateTime';
|
||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||
import { abilityLeadint, abilityLeadpow, abilityPowint, abilityRand, type GeneralStats } from '../utils/generalStats';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
|
||||
type JoinConfig = Awaited<ReturnType<typeof trpc.join.getConfig.query>>;
|
||||
type JoinInput = Parameters<typeof trpc.join.createGeneral.mutate>[0];
|
||||
@@ -945,7 +946,15 @@ onUnmounted(() => {
|
||||
<div v-if="nationList.length === 0" class="muted">국가 정보가 아직 준비되지 않았습니다.</div>
|
||||
<div v-else class="nation-list">
|
||||
<article v-for="nation in nationList" :key="nation.id" class="nation-card">
|
||||
<h3 class="nation-name" :style="{ backgroundColor: nation.color }">{{ nation.name }}</h3>
|
||||
<h3
|
||||
class="nation-name"
|
||||
:style="{
|
||||
backgroundColor: nation.color,
|
||||
color: legacyLuminanceTextColor(nation.color),
|
||||
}"
|
||||
>
|
||||
{{ nation.name }}
|
||||
</h3>
|
||||
<p class="nation-message">{{ nation.scoutMessage ?? '권유문 없음' }}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import GeneralDirectoryTable from '../components/directory/GeneralDirectoryTable
|
||||
import type { GeneralDirectoryGeneral } from '../types/directory';
|
||||
import { formatNationLevelText, formatOfficerLevelText } from '../utils/nationFormat';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Directory = Awaited<ReturnType<typeof trpc.world.getNationDirectory.query>>;
|
||||
@@ -27,27 +28,6 @@ const activeGeneral = computed(() =>
|
||||
activeGeneralId.value === null ? null : (generalDetailsById.value.get(activeGeneralId.value) ?? null)
|
||||
);
|
||||
|
||||
const whiteTextColors = new Set([
|
||||
'',
|
||||
'#330000',
|
||||
'#ff0000',
|
||||
'#800000',
|
||||
'#a0522d',
|
||||
'#ff6347',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2e8b57',
|
||||
'#008080',
|
||||
'#6495ed',
|
||||
'#0000ff',
|
||||
'#000080',
|
||||
'#483d8b',
|
||||
'#7b68ee',
|
||||
'#800080',
|
||||
'#a9a9a9',
|
||||
'#000000',
|
||||
]);
|
||||
|
||||
const loadDirectory = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
@@ -60,8 +40,6 @@ const loadDirectory = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const headerTextColor = (color: string): string => (whiteTextColors.has(color.toLowerCase()) ? '#ffffff' : '#000000');
|
||||
|
||||
const officerName = (nation: Nation, officerLevel: number) =>
|
||||
nation.officers.find((officer) => officer.officerLevel === officerLevel)?.general;
|
||||
const displayGeneralName = (general: { name: string; npcState: number }) =>
|
||||
@@ -149,7 +127,7 @@ onMounted(() => {
|
||||
<td
|
||||
colspan="8"
|
||||
class="center nation-title"
|
||||
:style="{ color: headerTextColor(nation.color), backgroundColor: nation.color }"
|
||||
:style="{ color: legacyNationTextColor(nation.color), backgroundColor: nation.color }"
|
||||
>
|
||||
【 {{ nation.name }} 】
|
||||
</td>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
|
||||
import PanelCard from '../components/ui/PanelCard.vue';
|
||||
import SkeletonLines from '../components/ui/SkeletonLines.vue';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Archive = Awaited<ReturnType<typeof trpc.archive.myPastPlays.query>>;
|
||||
@@ -282,7 +283,10 @@ onMounted(() => {
|
||||
<td>
|
||||
<span
|
||||
class="nation-name"
|
||||
:style="{ backgroundColor: general.nationColor, color: '#fff' }"
|
||||
:style="{
|
||||
backgroundColor: general.nationColor,
|
||||
color: legacyLuminanceTextColor(general.nationColor),
|
||||
}"
|
||||
>
|
||||
{{ general.nationName }}
|
||||
</span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useGameFeedback } from '../composables/useGameFeedback';
|
||||
import { useSessionStore } from '../stores/session';
|
||||
import { formatSeoulDateTime } from '../utils/legacyDateTime';
|
||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type JoinConfig = Awaited<ReturnType<typeof trpc.join.getConfig.query>>;
|
||||
@@ -144,29 +145,6 @@ const personalityName = (key: string | null): string | null => {
|
||||
const personalityInfo = (key: string | null): string =>
|
||||
key ? (personalities.value.find((entry) => entry.key === key)?.info ?? '') : '';
|
||||
|
||||
const lightTextNationColors = new Set([
|
||||
'',
|
||||
'#330000',
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#6495ED',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#800080',
|
||||
'#A9A9A9',
|
||||
'#000000',
|
||||
]);
|
||||
const nationTextColor = (color: string): string =>
|
||||
lightTextNationColors.has(color.toUpperCase()) ? '#FFFFFF' : '#000000';
|
||||
|
||||
const selectCandidate = async (candidate: Candidate): Promise<void> => {
|
||||
if (!hasGeneral.value) {
|
||||
selectedUniqueName.value = candidate.uniqueName;
|
||||
@@ -310,7 +288,7 @@ onBeforeUnmount(() => {
|
||||
v-for="nation in nations"
|
||||
:key="nation.id"
|
||||
:style="{
|
||||
color: nationTextColor(nation.color),
|
||||
color: legacyLuminanceTextColor(nation.color),
|
||||
backgroundColor: nation.color,
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import MapViewer from '../components/main/MapViewer.vue';
|
||||
import { formatLog } from '../utils/formatLog';
|
||||
import { legacyLuminanceTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type YearbookRange = Awaited<ReturnType<typeof trpc.yearbook.getRange.query>>;
|
||||
@@ -201,7 +202,13 @@ onMounted(async () => {
|
||||
:key="nation.id"
|
||||
>
|
||||
<td>
|
||||
<span :style="{ backgroundColor: nation.color }">{{ nation.name }}</span>
|
||||
<span
|
||||
:style="{
|
||||
backgroundColor: nation.color,
|
||||
color: legacyLuminanceTextColor(nation.color),
|
||||
}"
|
||||
>{{ nation.name }}</span
|
||||
>
|
||||
</td>
|
||||
<td>{{ nation.power.toLocaleString() }}</td>
|
||||
<td>{{ nation.generalCount.toLocaleString() }}</td>
|
||||
|
||||
Reference in New Issue
Block a user