feat. 게임시작 로직 적용

This commit is contained in:
clkim
2025-10-16 17:09:07 +09:00
parent 6dff3787b6
commit 7b72319377
34 changed files with 334 additions and 282 deletions

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 12,
color: 'var(--foreground-gray-500)',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 12 12"
:fill="color"
:class="className"
>
<path
d="M5.29499 7.715L2.39999 4.875C2.07499 4.555 2.29999 4 2.75999 4L9.23499 4C9.69499 4 9.91999 4.555 9.59499 4.875L6.69999 7.715C6.30999 8.095 5.68999 8.095 5.29999 7.715H5.29499Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 32,
color: '#EBEBEB',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 32 32"
fill="none"
:class="className"
>
<path
d="M26.2768 8.10947C26.7975 7.58877 26.7975 6.74455 26.2768 6.22385C25.7561 5.70315 24.9119 5.70315 24.3912 6.22385L16.0007 14.6144L7.61013 6.22385C7.08943 5.70315 6.24521 5.70315 5.72451 6.22385C5.20381 6.74455 5.20381 7.58877 5.72451 8.10947L14.115 16.5L5.72451 24.8905C5.20381 25.4112 5.20381 26.2554 5.72451 26.7761C6.24521 27.2968 7.08943 27.2968 7.61013 26.7761L16.0007 18.3856L24.3912 26.7761C24.9119 27.2968 25.7561 27.2968 26.2768 26.7761C26.7975 26.2554 26.7975 25.4112 26.2768 24.8905L17.8863 16.5L26.2768 8.10947Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: '#EBEBEB',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 24 24"
fill="none"
:class="className"
>
<path
d="M10.75 3.25L10.75 12.2322L6.88391 8.36611C6.39576 7.87796 5.6043 7.87796 5.11615 8.36611C4.62799 8.85427 4.62799 9.64573 5.11615 10.1339L11.1161 16.1339C11.3506 16.3683 11.6685 16.5 12 16.5C12.3316 16.5 12.6495 16.3683 12.8839 16.1339L18.8839 10.1339C19.3721 9.64573 19.3721 8.85427 18.8839 8.36611C18.3958 7.87796 17.6043 7.87796 17.1161 8.36611L13.25 12.2322L13.25 3.25C13.25 2.55964 12.6904 2 12 2C11.3097 2 10.75 2.55964 10.75 3.25Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: '#EBEBEB',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 24 24"
fill="none"
:class="className"
>
<path
d="M11.7929 18.2929C11.4024 18.6834 11.4024 19.3166 11.7929 19.7071C12.1834 20.0976 12.8166 20.0976 13.2071 19.7071L20.2071 12.7071C20.5976 12.3166 20.5976 11.6834 20.2071 11.2929L13.2071 4.29289C12.8166 3.90237 12.1834 3.90237 11.7929 4.29289C11.4024 4.68342 11.4024 5.31658 11.7929 5.70711L17.0858 11L4.5 11C3.94771 11 3.5 11.4477 3.5 12C3.5 12.5523 3.94771 13 4.5 13L17.0858 13L11.7929 18.2929Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: 'var(--foreground-reversal)',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 24 24"
fill="none"
:class="className"
>
<path
d="M20 16C20.7594 16 21.375 16.6156 21.375 17.375C21.375 18.1344 20.7594 18.75 20 18.75H4C3.24061 18.75 2.625 18.1344 2.625 17.375C2.625 16.6156 3.24061 16 4 16H20ZM20 10.5C20.7594 10.5 21.375 11.1156 21.375 11.875C21.375 12.6344 20.7594 13.25 20 13.25H4C3.24061 13.25 2.625 12.6344 2.625 11.875C2.625 11.1156 3.24061 10.5 4 10.5H20ZM20 5C20.7594 5 21.375 5.61561 21.375 6.375C21.375 7.13439 20.7594 7.75 20 7.75H4C3.24061 7.75 2.625 7.13439 2.625 6.375C2.625 5.61561 3.24061 5 4 5H20Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 16,
color: 'var(--foreground-reversal)',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 16 16"
fill="none"
:class="className"
>
<path
d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L8 6.58579L1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292894 0.292893C-0.0976304 0.683417 -0.0976304 1.31658 0.292894 1.70711L6.58579 8L0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L8 9.41421L14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L9.41421 8L15.7071 1.70711Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 24,
color: 'var(--foreground-reversal)',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 24 24"
fill="none"
:class="className"
>
<path
d="M4.5 10.5C3.675 10.5 3 11.175 3 12C3 12.825 3.675 13.5 4.5 13.5C5.325 13.5 6 12.825 6 12C6 11.175 5.325 10.5 4.5 10.5ZM19.5 10.5C18.675 10.5 18 11.175 18 12C18 12.825 18.675 13.5 19.5 13.5C20.325 13.5 21 12.825 21 12C21 11.175 20.325 10.5 19.5 10.5ZM12 10.5C11.175 10.5 10.5 11.175 10.5 12C10.5 12.825 11.175 13.5 12 13.5C12.825 13.5 13.5 12.825 13.5 12C13.5 11.175 12.825 10.5 12 10.5Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 12,
color: '#FD3886',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 12 12"
fill="none"
:class="className"
>
<path
d="M6.37364 3.24966C6.22658 2.91678 5.77344 2.91678 5.62638 3.24966L4.95508 4.76916L3.36352 4.96618C3.01484 5.00934 2.87482 5.4593 3.1326 5.7082L4.30928 6.84431L3.99693 8.48558C3.9285 8.84514 4.2951 9.12323 4.60148 8.94417L6.00001 8.12684L7.39853 8.94417C7.70491 9.12323 8.07151 8.84514 8.00308 8.48558L7.69074 6.84431L8.8674 5.70819C9.12518 5.4593 8.98515 5.00934 8.63648 4.96618L7.04492 4.76916L6.37364 3.24966Z"

View File

@@ -2,13 +2,11 @@
interface Props {
size?: number | string
color?: string
className?: string
}
withDefaults(defineProps<Props>(), {
size: 16,
color: 'var(--foreground-gray-500)',
className: '',
})
</script>
@@ -19,7 +17,6 @@ withDefaults(defineProps<Props>(), {
:height="size"
viewBox="0 0 16 16"
fill="none"
:class="className"
>
<path
d="M3.63636 3.33333C3.469 3.33333 3.33333 3.469 3.33333 3.63636L3.33333 12.3636C3.33333 12.531 3.469 12.6667 3.63636 12.6667H12.3636C12.531 12.6667 12.6667 12.531 12.6667 12.3636V9.93939C12.6667 9.5712 12.9651 9.27273 13.3333 9.27273C13.7015 9.27273 14 9.5712 14 9.93939V12.3636C14 13.2674 13.2674 14 12.3636 14H3.63636C2.73262 14 2 13.2674 2 12.3636L2 3.63636C2 2.73263 2.73262 2 3.63636 2L6.06061 2C6.4288 2 6.72727 2.29848 6.72727 2.66667C6.72727 3.03486 6.4288 3.33333 6.06061 3.33333H3.63636Z"

View File

@@ -0,0 +1,85 @@
<script setup>
const {
isProcessing,
isShowCheckLauncher,
isShowDownloadLauncher,
validateLauncher,
downloadLauncher,
} = useCheckGameStart()
</script>
<template>
<AtomsButton
:class="$attrs?.class"
:disabled="isProcessing"
@click="validateLauncher"
>
<slot />
</AtomsButton>
<ClientOnly>
<Teleport to="#teleports">
<BlocksModalLayer
v-model:is-open="isShowCheckLauncher"
:is-show-dimmed="true"
:is-outside-close="false"
:modal-name="'launcher'"
area-class="max-w-[480px] pt-[56px] px-[24px] pb-[24px] rounded-[8px]"
close-class="absolute top-[16px] right-[24px]"
>
<span class="ico-loading"></span>
<!-- [TODO] i18n 적용 -->
<!-- <p class="text-check">{{ tm('Common_Message_Check_Client').txt }}</p> -->
<p class="text-check">pc 클라이언트 실행 ...</p>
<Transition name="fade">
<div v-if="isShowDownloadLauncher" class="client-area">
<!-- <p
v-dompurify-html="tm('Common_Message_Download_Client').txt"
class="text-info"
></p>
<button type="button" class="btn-download" @click="downloadLauncher">
{{ tm('Common_Message_Install').txt }}
</button>
<p
v-dompurify-html="tm('Common_Message_Download_Close').txt"
class="text-tip"
></p> -->
<p class="text-info">
PC 클라이언트가 실행되지 않나요?
<br />
다운로드 전이라면 다운로드 진행해주세요
</p>
<AtomsButtonVariant class="max-w-[300px]" @click="downloadLauncher">
다운로드
</AtomsButtonVariant>
<p
v-dompurify-html="
'*PC 클라이언트가 정상 실행되었다면 팝업을 닫아 주세요.'
"
class="text-tip"
></p>
</div>
</Transition>
</BlocksModalLayer>
</Teleport>
</ClientOnly>
</template>
<style scoped>
.ico-loading {
@apply block mx-auto mb-4 w-[80px] h-[80px] bg-[url('/images/common/stove_loading_light.png')] bg-contain bg-center bg-no-repeat;
}
.text-check {
@apply mb-6 text-center text-[20px] font-bold leading-[30px] tracking-[-0.6px] text-[#333333];
}
.client-area {
@apply pt-4 border-t border-[rgba(0,0,0,0.08)] text-center;
}
.text-info {
@apply mb-3 text-[14px] font-medium leading-[24px] tracking-[-0.42px] text-[#333333];
}
.text-tip {
@apply mt-4 text-[14px] leading-[20px] tracking-[-0.42px] text-[#999999];
}
</style>

View File

@@ -4,12 +4,12 @@ interface props {
contentText?: string
confirmButtonText?: string
isOutsideClose?: boolean
className?: string
modalName?: string
}
const props = withDefaults(defineProps<props>(), {
isShowDimmed: false,
isOutsideClose: true,
isOutsideClose: false,
})
const emit = defineEmits(['confirmButtonEvent'])
@@ -31,10 +31,10 @@ const handleOutsideClick = () => {
</script>
<template>
<Transition name="opacity-on">
<Transition name="fade">
<div
v-if="isOpen"
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.className]"
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
@click="handleOutsideClick"
>
<div class="modal-area" @click.stop>
@@ -57,3 +57,9 @@ const handleOutsideClick = () => {
</div>
</Transition>
</template>
<style scoped>
.modal-area {
@apply max-w-[312px] p-6 bg-white rounded-[20px];
}
</style>

View File

@@ -5,21 +5,21 @@ interface props {
confirmButtonText?: string
cancelButtonText?: string
isOutsideClose?: boolean
className?: string
modalName?: string
}
const props = withDefaults(defineProps<props>(), {
isShowDimmed: false,
isOutsideClose: true,
isOutsideClose: false,
})
const emit = defineEmits(['cancelButtonEvent', 'confirmButtonEvent'])
const isOpen = defineModel<boolean>('isOpen', { default: false })
const setButtonEvent = (event: () => void | void) => {
const setButtonEvent = (event?: () => void) => {
if (event) {
return event()
event()
}
isOpen.value = false
}
@@ -32,10 +32,10 @@ const handleOutsideClick = () => {
</script>
<template>
<Transition name="opacity-on">
<Transition name="fade">
<div
v-if="isOpen"
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.className]"
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
@click="handleOutsideClick"
>
<div class="modal-area" @click.stop>
@@ -64,3 +64,9 @@ const handleOutsideClick = () => {
</div>
</Transition>
</template>
<style scoped>
.modal-area {
@apply max-w-[312px] p-6 bg-white rounded-[20px];
}
</style>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
interface props {
isShowDimmed?: boolean
isOutsideClose?: boolean
modalName?: string
areaClass?: string
closeClass?: string
}
const props = withDefaults(defineProps<props>(), {
isShowDimmed: false,
isOutsideClose: false,
})
const isOpen = defineModel<boolean>('isOpen', { default: false })
const handleCloseModal = () => {
isOpen.value = false
}
const handleOutsideClick = () => {
if (props.isOutsideClose) {
handleCloseModal()
}
}
</script>
<template>
<Transition name="fade">
<div
v-if="isOpen"
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
@click="handleOutsideClick"
>
<div :class="['modal-area', props.areaClass]" @click.stop>
<div class="modal-content">
<slot></slot>
</div>
<button
type="button"
:class="['modal-close', props.closeClass]"
@click="handleCloseModal"
>
<span class="sr-only">close</span>
<AtomsIconsCloseLine size="24" color="#333333" />
</button>
</div>
</div>
</Transition>
</template>

View File

@@ -4,12 +4,12 @@ import { getYouTubeEmbedUrl } from '#layers/utils/youtube'
interface Props {
youtubeUrl: string
isOutsideClose?: boolean
className?: string
modalName?: string
}
const props = withDefaults(defineProps<Props>(), {
youtubeUrl: '',
isOutsideClose: true,
isOutsideClose: false,
})
const emit = defineEmits(['closeButtonEvent'])
@@ -48,18 +48,11 @@ onUnmounted(() => {
</script>
<template>
<Transition
enter-active-class="transition duration-300 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<Transition name="fade">
<div
v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75"
:class="props.className"
:class="props.modalName"
@click="handleOutsideClick"
>
<div
@@ -72,11 +65,7 @@ onUnmounted(() => {
>
<!-- 헤더 -->
<div class="flex justify-end mb-3 md:mb-4">
<button
class="text-white rounded-full transition-colors"
aria-label="모달 닫기"
@click="handleCloseModal"
>
<button type="button" @click="handleCloseModal">
<AtomsIconsCloseLine />
</button>
</div>

View File

@@ -59,7 +59,7 @@ const handleMenuClose = () => (isMenuOpen.value = false)
// navAreaRef의 넓이를 구하는 함수
const calculateNavWidth = () => {
if (!navAreaRef.value) return 0
if (!navAreaRef.value || !gnbData) return 0
const navAreaWidth = navAreaRef.value.offsetWidth
const moreWidth = 72 // 더보기 버튼 넓이 + 마진
@@ -68,7 +68,7 @@ const calculateNavWidth = () => {
// startRef의 넓이를 구하는 함수
const calculateStartWidth = () => {
if (!startRef.value) return 0
if (!startRef.value || !gnbData) return 0
const startWidth = startRef.value.offsetWidth
const headerRightPadding = 40 // 헤더 오른쪽 마진
@@ -98,7 +98,7 @@ const calculateOfficialItemWidths = () => {
// 오버플로우 계산 함수
const calculateOverflow = () => {
if (!navAreaRef.value) return
if (!navAreaRef.value || !startRef.value) return
const totalNavWidth = navWidth.value + startWidth.value
const screenWidth = width.value
@@ -129,7 +129,10 @@ const calculateOverflow = () => {
}
}
onClickOutside(navAreaRef, () => (isMenuOpen.value = false))
const stopClickOutside = onClickOutside(
navAreaRef,
() => (isMenuOpen.value = false)
)
// 화면 크기 변경 시 오버플로우 재계산
watch(width, () => {
@@ -147,6 +150,12 @@ onMounted(() => {
}
})
})
onBeforeUnmount(() => {
if (stopClickOutside) {
stopClickOutside()
}
})
</script>
<template>
@@ -171,7 +180,7 @@ onMounted(() => {
>
<div ref="navAreaRef" class="nav-area">
<div class="nav-logo">
<AtomsLocaleLink to="/brand">
<AtomsLocaleLink to="/brand" @click="handleMenuClose">
<img
:src="gnbData?.bi_path"
:alt="gameData?.game_name"
@@ -283,9 +292,9 @@ onMounted(() => {
</div>
</nav>
<div ref="startRef" class="btn-start">
<AtomsButton size="small" class="w-full md:w-auto">
<BlocksButtonLuncher class="w-full md:w-auto">
게임 시작
</AtomsButton>
</BlocksButtonLuncher>
</div>
<button class="btn-close" @click="handleMenuClose">
<AtomsIconsMenuCloseLine class="mx-auto" />
@@ -341,7 +350,7 @@ onMounted(() => {
}
.nav-area {
@apply flex flex-col w-[360px] bg-theme-foreground-10 translate-x-[-100%]
@apply flex flex-col w-[100vw] max-w-[360px] min-w-[320px] bg-theme-foreground-10 translate-x-[-100%]
md:inline-flex md:flex-row md:w-auto md:h-full md:pl-[40px] md:items-center md:bg-transparent md:transform-none;
}