merge: 최신 main을 게임 버전 표시에 통합

This commit is contained in:
2026-08-21 01:28:58 +00:00
17 changed files with 765 additions and 66 deletions
+9
View File
@@ -63,6 +63,15 @@ textarea {
font: inherit;
}
/*
* Firefox for Android may zoom to a focused search field. `manipulation`
* suppresses that focus-only zoom while retaining ordinary pan and pinch zoom.
*/
input[type='search'],
input[inputmode='search'] {
touch-action: manipulation;
}
/*
* Ref's `.bg0/.bg1/.bg2` set a background image and nothing else, so the
* element stays transparent where the texture does not cover it. Adding a
+2
View File
@@ -4,8 +4,10 @@ import App from './App.vue';
import router from './router';
import './assets/main.css';
import { installImageAssetCssVariables } from './utils/imageAssets';
import { installScreenModeViewport } from './utils/screenModeViewport';
installImageAssetCssVariables();
installScreenModeViewport();
const app = createApp(App);
@@ -220,7 +220,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
label: '외교메시지',
color: '#000000',
options: contacts
.filter((nation) => nation.mailbox !== ownMailbox && nation.nationId > 0)
.filter((nation) => nation.mailbox !== ownMailbox)
.map((nation) => ({
label: nation.name,
value: nation.mailbox,
@@ -0,0 +1,78 @@
export const SCREEN_MODE_KEY = 'sam.screenMode';
export const SCREEN_MODE_CHANGE_EVENT = 'tryChangeScreenMode';
export type ScreenMode = 'auto' | '500px' | '1000px';
export type AutoViewportMeasurements = {
deviceWidth: number;
viewportHeight: number;
targetHeight?: number;
};
export const normalizeScreenMode = (value: string | null): ScreenMode =>
value === '500px' || value === '1000px' ? value : 'auto';
export const resolveAutoViewportContent = ({
deviceWidth,
viewportHeight,
targetHeight = 700,
}: AutoViewportMeasurements): string => {
if (deviceWidth < 500) {
return 'width=500';
}
if (viewportHeight < targetHeight) {
const widthAtTargetHeight = (deviceWidth / viewportHeight) * targetHeight;
return widthAtTargetHeight >= 700 ? 'width=1000' : `height=${Math.ceil(targetHeight)}`;
}
return deviceWidth >= 700 ? 'width=1000' : 'width=device-width, initial-scale=1';
};
export const resolveViewportContent = (mode: ScreenMode, measurements: AutoViewportMeasurements): string => {
if (mode === '500px') return 'width=500';
if (mode === '1000px') return 'width=1000';
return resolveAutoViewportContent(measurements);
};
const findOrCreateViewportMeta = (): HTMLMetaElement => {
const existing = document.querySelector<HTMLMetaElement>('meta[name="viewport"]');
if (existing) return existing;
const viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
document.head.appendChild(viewportMeta);
return viewportMeta;
};
export const installScreenModeViewport = (targetHeight = 700): void => {
if (typeof window === 'undefined' || typeof document === 'undefined') return;
const viewportMeta = findOrCreateViewportMeta();
let previousMode: ScreenMode | null = null;
let previousDeviceWidth: number | null = null;
const adjustViewport = () => {
const mode = normalizeScreenMode(window.localStorage.getItem(SCREEN_MODE_KEY));
const deviceWidth = window.screen.availWidth;
if (mode === previousMode && mode === 'auto' && deviceWidth === previousDeviceWidth) return;
if (mode === previousMode && mode !== 'auto') return;
previousMode = mode;
previousDeviceWidth = deviceWidth;
viewportMeta.content = resolveViewportContent(mode, {
deviceWidth,
viewportHeight: window.innerHeight,
targetHeight,
});
};
adjustViewport();
window.addEventListener('resize', adjustViewport);
window.addEventListener('orientationchange', adjustViewport);
window.addEventListener('storage', (event) => {
if (event.key === SCREEN_MODE_KEY) adjustViewport();
});
document.addEventListener(SCREEN_MODE_CHANGE_EVENT, adjustViewport);
};
+2 -3
View File
@@ -10,6 +10,7 @@ import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIc
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
import GeneralBasicCard from '../components/main/GeneralBasicCard.vue';
import { useGameFeedback } from '../composables/useGameFeedback';
import { SCREEN_MODE_CHANGE_EVENT, SCREEN_MODE_KEY, type ScreenMode } from '../utils/screenModeViewport';
import {
DEFAULT_MOBILE_MAIN_PANEL_ORDER,
loadMobileMainPanelOrder,
@@ -19,11 +20,9 @@ import {
type MobileMainPanelId,
} from '../utils/mobileMainPanelOrder';
const SCREEN_MODE_KEY = 'sam.screenMode';
const CUSTOM_CSS_KEY = 'sam_customCSS';
const PENDING_DIE_ON_PRESTART_KEY = 'sam.pending.dieOnPrestart';
const { success: showSuccessToast, error: showErrorToast, showDialog } = useGameFeedback();
type ScreenMode = 'auto' | '500px' | '1000px';
type LogType = 'generalHistory' | 'battleDetail' | 'battleResult' | 'generalAction';
type ItemSlotKey = 'horse' | 'weapon' | 'book' | 'item';
type MyGeneralResponse = Awaited<ReturnType<typeof trpc.general.me.query>>;
@@ -373,7 +372,7 @@ const dropItem = (item: { key: ItemSlotKey; slotName: string; displayName: strin
watch(screenMode, (mode) => {
localStorage.setItem(SCREEN_MODE_KEY, mode);
document.dispatchEvent(new CustomEvent('tryChangeScreenMode'));
document.dispatchEvent(new CustomEvent(SCREEN_MODE_CHANGE_EVENT));
});
watch(customCss, (text) => {