diff --git a/app/app.vue b/app/app.vue
index 9fab093..06ea2f6 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -2,11 +2,14 @@
import { useNuxtApp } from 'nuxt/app'
import LoadingFull from '#layers/components/blocks/loading/Full.vue'
import LoadingLocal from '#layers/components/blocks/loading/Local.vue'
+import BlocksModalYouTube from '#layers/components/blocks/modal/YouTube.vue'
import type { GameDataMetaTag, GameDataValue } from '#layers/types/api/gameData'
const nuxtApp = useNuxtApp()
const gameDataStore = useGameDataStore()
+const modalStore = useModalStore()
+const { youtube, handleResetYoutube } = modalStore
const { setGameData } = gameDataStore
const { gameData } = storeToRefs(gameDataStore)
@@ -114,7 +117,15 @@ if (serverGameData) {
{{ gameData?.game_name }}
-
+
+
+
+
diff --git a/layers/assets/css/base/_reset.css b/layers/assets/css/base/_reset.css
index 17f96e2..9df8a16 100644
--- a/layers/assets/css/base/_reset.css
+++ b/layers/assets/css/base/_reset.css
@@ -3,4 +3,12 @@
body {
background-color: #000;
}
+ body.scroll-lock {
+ overflow: hidden;
+ }
+
+ button,
+ a {
+ outline: none;
+ }
}
diff --git a/layers/components/atoms/Button.vue b/layers/components/atoms/Button.vue
index cc3cc4b..1f06333 100644
--- a/layers/components/atoms/Button.vue
+++ b/layers/components/atoms/Button.vue
@@ -9,7 +9,7 @@ import type { GameDataKeyCodeCodes } from '#layers/types/api/gameData'
const props = withDefaults(defineProps(), {
size: 'medium',
backgroundColor: 'var(--primary)',
- textColor: 'var(--text-primary)',
+ textColor: 'var(--alternative-02)',
icon: '',
disabled: false,
})
diff --git a/layers/components/atoms/icons/ArrowDown.vue b/layers/components/atoms/icons/ArrowDown.vue
index e055d8a..cd9619a 100644
--- a/layers/components/atoms/icons/ArrowDown.vue
+++ b/layers/components/atoms/icons/ArrowDown.vue
@@ -19,6 +19,7 @@ withDefaults(defineProps(), {
:height="size"
viewBox="0 0 12 12"
:fill="color"
+ :class="className"
>
+interface Props {
+ size?: number | string
+ color?: string
+ className?: string
+}
+
+withDefaults(defineProps(), {
+ size: 32,
+ color: '#EBEBEB',
+ className: '',
+})
+
+
+
+
+
diff --git a/layers/components/atoms/icons/LinkOut.vue b/layers/components/atoms/icons/LinkOut.vue
index 54ee023..c565168 100644
--- a/layers/components/atoms/icons/LinkOut.vue
+++ b/layers/components/atoms/icons/LinkOut.vue
@@ -19,6 +19,7 @@ withDefaults(defineProps(), {
:height="size"
viewBox="0 0 16 16"
:fill="color"
+ :class="className"
>
-
-
+
+
diff --git a/layers/components/widgets/VideoPlay.vue b/layers/components/widgets/VideoPlay.vue
index a6e627a..efc79eb 100644
--- a/layers/components/widgets/VideoPlay.vue
+++ b/layers/components/widgets/VideoPlay.vue
@@ -8,41 +8,24 @@ const bgStyles = getResponsiveSrc(props.resourcesData?.res_path, {
resourcesType: 'bg',
})
-// YouTube 모달 상태 관리
-const isYouTubeModalOpen = ref(false)
-const youtubeVideoId = ref('')
+// YouTube 모달 스토어 사용
+const modalStore = useModalStore()
// 비디오 플레이 버튼 클릭 핸들러
const handleVideoPlayClick = () => {
- // TODO: 실제 YouTube 비디오 ID를 설정해야 합니다
- // 예시: 'dQw4w9WgXcQ' (Rick Astley - Never Gonna Give You Up)
- youtubeVideoId.value = 'UKVsZYHxYTc' // 임시로 설정
- isYouTubeModalOpen.value = true
-}
-
-// 모달 닫기 핸들러
-const handleCloseModal = () => {
- isYouTubeModalOpen.value = false
- youtubeVideoId.value = ''
+ const youtubeId = props.resourcesData?.display?.text ?? ''
+ modalStore.handleOpenYoutube({ youtubeId })
}
-
-
- (isYouTubeModalOpen = value)"
- />
diff --git a/layers/stores/useModalStore.ts b/layers/stores/useModalStore.ts
new file mode 100644
index 0000000..6e25df9
--- /dev/null
+++ b/layers/stores/useModalStore.ts
@@ -0,0 +1,142 @@
+import { defineStore } from 'pinia'
+
+interface DialogParams {
+ isShowDimmed?: boolean
+ className?: string
+ isOutsideClose?: boolean
+ contentText: string
+ confirmButtonText?: string
+ cancelButtonText?: string
+ confirmButtonEvent?: () => void
+ cancelButtonEvent?: () => void
+ closeButtonEvent?: () => void
+}
+interface YoutubeParams {
+ youtubeId: string
+ className?: string
+}
+
+const createModalState = () => ({
+ storeIsOpen: ref(false),
+ storeIsShowDimmed: ref(false),
+ storeClassName: ref(''),
+ storeIsOutsideClose: ref(true),
+ storeContentText: ref(''),
+ storeConfirmButtonText: ref(''),
+ storeConfirmButtonEvent: ref(() => {}),
+ storeCloseButtonEvent: ref(() => {}),
+})
+
+const resetModalState = (type: ReturnType) => {
+ type.storeIsOpen.value = false
+ type.storeIsShowDimmed.value = false
+ type.storeClassName.value = ''
+ type.storeIsOutsideClose.value = true
+ type.storeContentText.value = ''
+ type.storeConfirmButtonText.value = ''
+ type.storeConfirmButtonEvent.value = () => {}
+ type.storeCloseButtonEvent.value = () => {}
+}
+
+export const useModalStore = defineStore('modalStore', () => {
+ const scrollStore = useScrollStore()
+
+ // alert ------------------
+ const alert = {
+ ...createModalState(),
+ }
+
+ const handleOpenAlert = ({
+ isShowDimmed = false,
+ className = '',
+ isOutsideClose = true,
+ contentText,
+ confirmButtonText = '',
+ confirmButtonEvent,
+ closeButtonEvent,
+ }: DialogParams) => {
+ alert.storeIsOpen.value = true
+ alert.storeIsShowDimmed.value = isShowDimmed
+ alert.storeClassName.value = className
+ alert.storeContentText.value = contentText
+ alert.storeConfirmButtonText.value = confirmButtonText
+ alert.storeIsOutsideClose.value = isOutsideClose
+ alert.storeConfirmButtonEvent.value = confirmButtonEvent ?? handleResetAlert
+ alert.storeCloseButtonEvent.value = closeButtonEvent ?? handleResetAlert
+ }
+
+ const handleResetAlert = () => {
+ resetModalState(alert)
+ }
+
+ // confirm ------------------
+ const confirm = {
+ ...createModalState(),
+ storeCancelButtonText: ref(''),
+ storeCancelButtonEvent: ref(() => {}),
+ }
+
+ const handleOpenConfirm = ({
+ isShowDimmed = false,
+ className = '',
+ isOutsideClose = true,
+ contentText,
+ confirmButtonText = '',
+ cancelButtonText = '',
+ confirmButtonEvent,
+ cancelButtonEvent,
+ closeButtonEvent,
+ }: DialogParams) => {
+ confirm.storeIsOpen.value = true
+ confirm.storeIsShowDimmed.value = isShowDimmed
+ confirm.storeClassName.value = className
+ confirm.storeContentText.value = contentText
+ confirm.storeConfirmButtonText.value = confirmButtonText
+ confirm.storeCancelButtonText.value = cancelButtonText
+ confirm.storeIsOutsideClose.value = isOutsideClose
+ confirm.storeConfirmButtonEvent.value =
+ confirmButtonEvent ?? handleResetConfirm
+ confirm.storeCancelButtonEvent.value =
+ cancelButtonEvent ?? handleResetConfirm
+ confirm.storeCloseButtonEvent.value = closeButtonEvent ?? handleResetConfirm
+ }
+
+ const handleResetConfirm = () => {
+ resetModalState(confirm)
+ confirm.storeCancelButtonText.value = ''
+ confirm.storeCancelButtonEvent.value = () => {}
+ }
+
+ // youtube ------------------
+ const youtube = {
+ storeIsOpen: ref(false),
+ storeYoutubeId: ref(''),
+ storeClassName: ref(''),
+ }
+
+ const handleOpenYoutube = ({ youtubeId, className = '' }: YoutubeParams) => {
+ youtube.storeIsOpen.value = true
+ youtube.storeYoutubeId.value = youtubeId
+ youtube.storeClassName.value = className
+ scrollStore.controlScrollLock(true)
+ }
+
+ const handleResetYoutube = () => {
+ youtube.storeIsOpen.value = false
+ youtube.storeYoutubeId.value = ''
+ youtube.storeClassName.value = ''
+ scrollStore.controlScrollLock(false)
+ }
+
+ return {
+ alert,
+ confirm,
+ youtube,
+ handleOpenAlert,
+ handleOpenConfirm,
+ handleResetAlert,
+ handleResetConfirm,
+ handleOpenYoutube,
+ handleResetYoutube,
+ }
+})
diff --git a/layers/stores/useScrollStore.ts b/layers/stores/useScrollStore.ts
new file mode 100644
index 0000000..e1dcabe
--- /dev/null
+++ b/layers/stores/useScrollStore.ts
@@ -0,0 +1,36 @@
+import { defineStore } from 'pinia'
+import { useWindowScroll } from '@vueuse/core'
+
+export const useScrollStore = defineStore('scrollStore', () => {
+ const { x: windowX, y: windowY } = useWindowScroll({ behavior: 'smooth' })
+
+ const stoveGnbHeight = 48
+ const scrollXValue = ref('0px')
+ const isPassedStoveGnb = ref(false)
+
+ const updateScrollValue = () => {
+ if (stoveGnbHeight <= windowY.value) {
+ isPassedStoveGnb.value = true
+ scrollXValue.value = `-${windowX.value}px`
+ } else {
+ isPassedStoveGnb.value = false
+ scrollXValue.value = '0px'
+ }
+ }
+
+ const controlScrollLock = (state: boolean) => {
+ if (state) {
+ document.body.classList.add('scroll-lock')
+ } else {
+ document.body.classList.remove('scroll-lock')
+ }
+ }
+
+ return {
+ scrollXValue,
+ isPassedStoveGnb,
+
+ updateScrollValue,
+ controlScrollLock,
+ }
+})