feat. home 버튼 컴포넌트 추가
This commit is contained in:
@@ -41,9 +41,18 @@
|
||||
<div class="inspection-bottom-cards">
|
||||
<!-- 온스토브 카드 -->
|
||||
<div class="inspection-card inspection-stove-card">
|
||||
<h3 :class="{ 'text-center': !launchingStatus }" class="card-title text-base md:text-lg">
|
||||
<span v-if="!launchingStatus" v-dompurify-html="tm('Inspection_Game_During_Maintenance')"></span>
|
||||
<span v-else v-dompurify-html="tm('org_Inspection_During_Maintenance')"></span>
|
||||
<h3
|
||||
:class="{ 'text-center': !launchingStatus }"
|
||||
class="card-title text-base md:text-lg"
|
||||
>
|
||||
<span
|
||||
v-if="!launchingStatus"
|
||||
v-dompurify-html="tm('Inspection_Game_During_Maintenance')"
|
||||
></span>
|
||||
<span
|
||||
v-else
|
||||
v-dompurify-html="tm('org_Inspection_During_Maintenance')"
|
||||
></span>
|
||||
</h3>
|
||||
<div class="button-group justify-center">
|
||||
<!-- <a
|
||||
@@ -103,7 +112,7 @@
|
||||
{{ tm('Inspection_Txt_Download') || '게임 다운로드' }}
|
||||
</h3>
|
||||
<div class="flex flex-row gap-3">
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
v-for="(btn, index) in enabledMarkets"
|
||||
:key="index"
|
||||
:class="getButtonClass(btn.platform)"
|
||||
@@ -114,7 +123,7 @@
|
||||
variant="outlined"
|
||||
>
|
||||
{{ getButtonText(btn.platform) }}
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +140,7 @@ import { useCheckGameStart } from '#layers/composables/useGameStart'
|
||||
const config = useRuntimeConfig()
|
||||
const dataResourcesUrl = config.public.dataResourcesUrl as string
|
||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_inspection.json'
|
||||
|
||||
|
||||
// const isClient = import.meta.client
|
||||
|
||||
const inspectionStore = useInspectionStore()
|
||||
@@ -160,21 +169,21 @@ const getLocaleTimezone = (localeType: string, region) => {
|
||||
const tsStartDate = webInspectionData.value?.ts_start_date || 0
|
||||
const tsEndDate = webInspectionData.value?.ts_end_date || 0
|
||||
const currentLocale = localeType || locale.value
|
||||
|
||||
|
||||
const timezoneMap: Record<string, string> = {
|
||||
'ko': 'KST',
|
||||
ko: 'KST',
|
||||
'zh-tw': '台灣時間',
|
||||
'ja': 'JST',
|
||||
ja: 'JST',
|
||||
}
|
||||
const timezone = timezoneMap[currentLocale] || 'KST'
|
||||
const defaultRegion = currentLocale === 'ko' ? 'KR' : ''
|
||||
|
||||
const formatDate = (date: Date, loc: string, reg: string) =>
|
||||
`${globalDateFormat(date, loc, reg || defaultRegion, {useFullDate: true})}`
|
||||
|
||||
|
||||
const formatDate = (date: Date, loc: string, reg: string) =>
|
||||
`${globalDateFormat(date, loc, reg || defaultRegion, { useFullDate: true })}`
|
||||
|
||||
const localTime = `${formatDate(new Date(tsStartDate), currentLocale, region || '')} <br class="md:hidden">~ ${formatDate(new Date(tsEndDate), currentLocale, region || '')} (${timezone})`
|
||||
const utcTime = `${formatDate(new Date(tsStartDate), 'en', region || '')} <br class="md:hidden">~ ${formatDate(new Date(tsEndDate), 'en', region || '')} (UTC)`
|
||||
|
||||
|
||||
return currentLocale === 'en' ? utcTime : `${localTime}<br>${utcTime}`
|
||||
}
|
||||
|
||||
@@ -252,7 +261,6 @@ const handleGameStart = () => {
|
||||
validateLauncher()
|
||||
}
|
||||
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['inspection'],
|
||||
layout: 'only-stove',
|
||||
|
||||
50
layers/components/atoms/Button/Circle.vue
Normal file
50
layers/components/atoms/Button/Circle.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
interface props {
|
||||
type?: 'button' | 'link'
|
||||
to?: string
|
||||
target?: '_self' | '_blank'
|
||||
srOnly?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
type: 'button',
|
||||
to: '',
|
||||
target: '_self',
|
||||
srOnly: '',
|
||||
})
|
||||
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'link':
|
||||
return 'AtomsLocaleLink'
|
||||
default:
|
||||
return 'button'
|
||||
}
|
||||
})
|
||||
|
||||
const componentProps = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'link':
|
||||
return { to: props.to, target: props.target }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="componentTag" v-bind="componentProps" class="btn-circle">
|
||||
<slot />
|
||||
<span class="sr-only">{{ props.srOnly }}</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-circle {
|
||||
@apply relative flex items-center justify-center rounded-full
|
||||
w-[40px] h-[40px] md:w-[48px] md:h-[48px]
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-[rgba(255,255,255,0.06)] before:rounded-full
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-full after:opacity-0 after:transition-all after:duration-300 after:ease-in-out
|
||||
hover:after:opacity-10;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
windowY.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<button v-show="showBtn" class="btn-top" @click="handleScrollToTop">
|
||||
<span class="sr-only">top</span>
|
||||
</button>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-top {
|
||||
@apply relative rounded-full bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat
|
||||
w-[40px] h-[40px] md:w-[48px] md:h-[48px]
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-full after:opacity-0 after:transition-all after:duration-300 after:ease-in-out;
|
||||
}
|
||||
.btn-top:hover {
|
||||
@apply after:opacity-10;
|
||||
}
|
||||
</style>
|
||||
25
layers/components/atoms/icons/HomeFill.vue
Normal file
25
layers/components/atoms/icons/HomeFill.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string
|
||||
color?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 24,
|
||||
color: 'white',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
:fill="color"
|
||||
>
|
||||
<path
|
||||
d="M10.7388 3.23622C11.4736 2.63919 12.5264 2.63919 13.2612 3.23622L20.6306 9.22387C20.8643 9.41376 21 9.69885 21 9.99998L21 19C21 20.1045 20.1046 21 19 21L14 21C13.4477 21 13 20.5523 13 20V14.125C13 13.5037 12.5523 13 12 13C11.4477 13 11 13.5037 11 14.125L11 20C11 20.5523 10.5523 21 10 21H5C3.89543 21 3 20.1045 3 19L3 9.99998C3 9.69885 3.1357 9.41376 3.36941 9.22387L10.7388 3.23622Z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
26
layers/components/blocks/Button/Home.vue
Normal file
26
layers/components/blocks/Button/Home.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const scrollStore = useScrollStore()
|
||||
|
||||
const { isPassedStoveGnb } = storeToRefs(scrollStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AtomsButtonCircle
|
||||
sr-only="home"
|
||||
type="link"
|
||||
to="/"
|
||||
:class="['btn-home', { 'is-fixed': isPassedStoveGnb }]"
|
||||
>
|
||||
<AtomsIconsHomeFill />
|
||||
</AtomsButtonCircle>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-home {
|
||||
@apply absolute top-3 right-3 mt-[48px] bg-black/20 shadow-[0_1.667px_3.333px_0_rgba(0,0,0,0.06)] backdrop-blur-[12.5px] z-[100]
|
||||
sm:top-5 md:top-6 md:right-8 md:mt-[64px];
|
||||
}
|
||||
.btn-home.is-fixed {
|
||||
@apply fixed;
|
||||
}
|
||||
</style>
|
||||
26
layers/components/blocks/Button/ScrollTop.vue
Normal file
26
layers/components/blocks/Button/ScrollTop.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
windowY.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<AtomsButtonCircle
|
||||
v-show="showBtn"
|
||||
class="btn-top"
|
||||
sr-only="top"
|
||||
@click="handleScrollToTop"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-top {
|
||||
@apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat;
|
||||
}
|
||||
</style>
|
||||
@@ -42,14 +42,14 @@ const handleCopy = async () => {
|
||||
|
||||
<template>
|
||||
<div v-if="Object.keys(snsList).length > 0" class="sns-container">
|
||||
<button
|
||||
<AtomsButtonCircle
|
||||
class="btn-sns"
|
||||
sr-only="sns"
|
||||
:style="{ backgroundColor: snsBackgroundColor }"
|
||||
@click="handleMouseEnter"
|
||||
>
|
||||
<AtomsIconsShareLine class="icon-share" />
|
||||
<span class="sr-only">sns</span>
|
||||
</button>
|
||||
</AtomsButtonCircle>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="showSnsList"
|
||||
@@ -95,15 +95,6 @@ const handleCopy = async () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-sns {
|
||||
@apply relative rounded-full flex items-center justify-center
|
||||
w-[40px] h-[40px] md:w-[48px] md:h-[48px]
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-[rgba(255,255,255,0.06)] before:rounded-full
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-full after:opacity-0 after:transition-all after:duration-300 after:ease-in-out;
|
||||
}
|
||||
.btn-sns:hover {
|
||||
@apply after:opacity-10;
|
||||
}
|
||||
.btn-sns:hover .icon-share {
|
||||
@apply fill-white;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.lnb-wrap {
|
||||
@apply fixed top-1/2 right-0 py-12 pr-4 text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
@apply fixed top-1/2 right-0 py-12 pr-[42px] text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
}
|
||||
.main-list {
|
||||
@apply flex flex-col gap-4 items-end;
|
||||
@@ -163,10 +163,22 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
button {
|
||||
@apply font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
@apply relative font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button:hover,
|
||||
button.is-active {
|
||||
@apply text-[var(--lnb-active-color)];
|
||||
}
|
||||
button::before {
|
||||
@apply content-[''] absolute top-1/2 rounded-full -translate-y-1/2 bg-transparent transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button.is-active::before {
|
||||
@apply bg-[var(--lnb-active-color)];
|
||||
}
|
||||
.btn-1depth::before {
|
||||
@apply -right-4 w-1.5 h-1.5;
|
||||
}
|
||||
.btn-2depth::before {
|
||||
@apply -right-3.5 w-1 h-1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataLnbMenu } from '#layers/types/api/pageData'
|
||||
|
||||
const HEADER_HEIGHT = 64
|
||||
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
const pageDataStore = usePageDataStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
const activeSection = ref<string>('')
|
||||
const observerRef = shallowRef<IntersectionObserver | null>(null)
|
||||
|
||||
/** 1뎁스 LNB 배열로 정규화 */
|
||||
const lnbRoot = computed<PageDataLnbMenu[]>(() =>
|
||||
Object.values(pageData.value?.lnb_menus || {}).filter(Boolean)
|
||||
)
|
||||
const isShowLnb = computed(() => {
|
||||
const pageDataUseLnb = pageData.value?.use_lnb ?? false
|
||||
const isDesktop = breakpoints.value.isDesktop
|
||||
const lnbRootLength = lnbRoot.value.length
|
||||
return Boolean(pageDataUseLnb && isDesktop && lnbRootLength)
|
||||
})
|
||||
const activeColor = computed(
|
||||
() => pageData.value?.lnb_text_color_code_active || 'var(--text-primary)'
|
||||
)
|
||||
const disableColor = computed(
|
||||
() => pageData.value?.lnb_text_color_code_deactive || 'var(--text-secondary)'
|
||||
)
|
||||
|
||||
const getChildren = (item?: PageDataLnbMenu) =>
|
||||
item?.children ? Object.values(item.children) : []
|
||||
|
||||
/** 1뎁스 활성 판정(자신 또는 자식) */
|
||||
const is1DepthActive = (lnbItem: PageDataLnbMenu): boolean => {
|
||||
if (!lnbItem) return false
|
||||
if (activeSection.value === lnbItem.page_ver_tmpl_name_en) return true
|
||||
return getChildren(lnbItem).some(
|
||||
c => activeSection.value === c.page_ver_tmpl_name_en
|
||||
)
|
||||
}
|
||||
|
||||
/** 스크롤 이동 */
|
||||
const scrollToLnb = async (id: string) => {
|
||||
if (import.meta.server) return
|
||||
await nextTick()
|
||||
|
||||
const el = document.getElementById(id)
|
||||
if (!el) return
|
||||
|
||||
const rect = el.getBoundingClientRect()
|
||||
const targetY = window.scrollY + rect.top - HEADER_HEIGHT
|
||||
|
||||
windowY.value = targetY
|
||||
}
|
||||
|
||||
/** 관찰 대상 id 리스트(1뎁스 + 2뎁스) */
|
||||
const sectionIds = computed<string[]>(() => {
|
||||
const ids: string[] = []
|
||||
for (const item of lnbRoot.value) {
|
||||
if (item?.page_ver_tmpl_name_en) ids.push(item.page_ver_tmpl_name_en)
|
||||
for (const c of getChildren(item)) {
|
||||
if (c?.page_ver_tmpl_name_en) ids.push(c.page_ver_tmpl_name_en)
|
||||
}
|
||||
}
|
||||
// 중복 제거
|
||||
return Array.from(new Set(ids))
|
||||
})
|
||||
|
||||
const createObserver = () => {
|
||||
if (observerRef.value) observerRef.value.disconnect()
|
||||
|
||||
observerRef.value = new IntersectionObserver(
|
||||
entries => {
|
||||
// 보이는 섹션 중 화면 상단에 가장 가까운 요소를 활성화
|
||||
const visibles = entries
|
||||
.filter(e => e.isIntersecting)
|
||||
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
|
||||
|
||||
if (visibles.length > 0) {
|
||||
activeSection.value = (visibles[0].target as HTMLElement).id
|
||||
return
|
||||
}
|
||||
|
||||
// 가끔 빠르게 스크롤 시 entries가 비어있을 수 있으므로
|
||||
// 현재 모든 섹션 중에서 '상단 기준으로 가장 가까운' 것을 폴백 계산
|
||||
const candidates: Array<{ id: string; top: number }> = []
|
||||
for (const id of sectionIds.value) {
|
||||
const el = document.getElementById(id)
|
||||
if (!el) continue
|
||||
const top = el.getBoundingClientRect().top - HEADER_HEIGHT
|
||||
candidates.push({ id, top })
|
||||
}
|
||||
// 0에 가장 가까운 양수/음수 모두 고려(위/아래 가장 가까운)
|
||||
candidates.sort((a, b) => Math.abs(a.top) - Math.abs(b.top))
|
||||
if (candidates.length) activeSection.value = candidates[0].id
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
// 상단 20%, 하단 60% 마진은 유지하되 헤더 보정치 반영
|
||||
rootMargin: `-${Math.max(HEADER_HEIGHT, 20)}px 0px -60% 0px`,
|
||||
threshold: 0,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
|
||||
if (!lnbItem?.page_ver_tmpl_name_en) return
|
||||
scrollToLnb(lnbItem.page_ver_tmpl_name_en)
|
||||
}
|
||||
|
||||
const observeSections = () => {
|
||||
if (import.meta.server) return
|
||||
if (!observerRef.value) createObserver()
|
||||
const obs = observerRef.value!
|
||||
obs.disconnect() // 기존 관찰 해제
|
||||
|
||||
// DOM 렌더 후 관찰 등록
|
||||
requestAnimationFrame(() => {
|
||||
for (const id of sectionIds.value) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) obs.observe(el)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (import.meta.server) return
|
||||
createObserver()
|
||||
observeSections()
|
||||
})
|
||||
|
||||
// lnb 데이터/DOM이 바뀌면 재관찰
|
||||
watchEffect(async () => {
|
||||
if (!import.meta.client) return
|
||||
if (isShowLnb.value && sectionIds.value.length) {
|
||||
await nextTick()
|
||||
observeSections()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
observerRef.value?.disconnect()
|
||||
observerRef.value = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isShowLnb"
|
||||
class="lnb-wrap"
|
||||
:style="{
|
||||
'--lnb-active-color': activeColor,
|
||||
'--lnb-disable-color': disableColor,
|
||||
}"
|
||||
>
|
||||
<ul class="main-list">
|
||||
<li v-for="lnbItem in lnbRoot" :key="lnbItem.path_code">
|
||||
<button
|
||||
v-dompurify-html="lnbItem.menu_name"
|
||||
type="button"
|
||||
:class="['btn-1depth', { 'is-active': is1DepthActive(lnbItem) }]"
|
||||
@click="handleLnbClick(lnbItem)"
|
||||
></button>
|
||||
|
||||
<ul v-if="getChildren(lnbItem).length > 0" class="sub-list">
|
||||
<li v-for="subItem in getChildren(lnbItem)" :key="subItem.path_code">
|
||||
<button
|
||||
v-dompurify-html="subItem.menu_name"
|
||||
type="button"
|
||||
:class="[
|
||||
'btn-2depth',
|
||||
{
|
||||
'is-active': activeSection === subItem.page_ver_tmpl_name_en,
|
||||
},
|
||||
]"
|
||||
@click="handleLnbClick(subItem)"
|
||||
></button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lnb-wrap {
|
||||
@apply fixed top-1/2 right-0 py-12 pr-4 text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
}
|
||||
.main-list {
|
||||
@apply flex flex-col gap-4 items-end;
|
||||
}
|
||||
.btn-1depth {
|
||||
@apply text-[15px] leading-[26px] tracking-[-0.54px];
|
||||
}
|
||||
.sub-list {
|
||||
@apply flex flex-col gap-2 items-end mt-4 mb-1 pr-[46px];
|
||||
}
|
||||
.btn-2depth {
|
||||
@apply text-[14px] leading-[20px] tracking-[-0.42px];
|
||||
}
|
||||
|
||||
button {
|
||||
@apply font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button:hover,
|
||||
button.is-active {
|
||||
@apply text-[var(--lnb-active-color)];
|
||||
}
|
||||
</style>
|
||||
@@ -58,16 +58,13 @@ onMounted(async () => {
|
||||
}"
|
||||
>
|
||||
<div class="navigation-wrapper">
|
||||
<button class="btn-control" @click="toggleEventNavigation">
|
||||
<AtomsButtonCircle
|
||||
sr-only="event navigation control"
|
||||
class="btn-control"
|
||||
@click="toggleEventNavigation"
|
||||
>
|
||||
<AtomsIconsArrowRightLine size="24" color="#ffffff" />
|
||||
<span class="sr-only">
|
||||
{{
|
||||
isEventNavigationOpen
|
||||
? 'event navigation close'
|
||||
: 'event navigation open'
|
||||
}}
|
||||
</span>
|
||||
</button>
|
||||
</AtomsButtonCircle>
|
||||
<ul class="navigation-list">
|
||||
<li v-for="item in eventNavigationList" :key="item.banner_seq">
|
||||
<AtomsLocaleLink
|
||||
@@ -95,7 +92,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.event-navigation {
|
||||
@apply absolute top-0 left-0 bottom-0 pt-[48px] md:pt-[64px] z-[100] transition-transform duration-300 ease-in-out;
|
||||
@apply absolute top-0 left-0 bottom-0 mt-[48px] md:mt-[64px] z-[100] transition-transform duration-300 ease-in-out;
|
||||
}
|
||||
.event-navigation.is-fixed {
|
||||
@apply fixed;
|
||||
@@ -116,9 +113,9 @@ onMounted(async () => {
|
||||
@apply block mt-2 text-center line-clamp-2 text-[#ebebeb] text-[14px] font-normal leading-[20px] tracking-[-0.42px] opacity-50;
|
||||
}
|
||||
.btn-control {
|
||||
@apply absolute top-3 right-[-40px] flex items-center justify-center w-[40px] h-[40px] transition-transform duration-300 ease-in-out
|
||||
bg-black/20 shadow-[0_1.667px_3.333px_0_rgba(0,0,0,0.06)] backdrop-blur-[12.5px] rounded-full
|
||||
sm:top-5 md:top-6 md:right-[-48px] md:w-[48px] md:h-[48px];
|
||||
@apply absolute top-3 right-[-40px]
|
||||
bg-black/20 shadow-[0_1.667px_3.333px_0_rgba(0,0,0,0.06)] backdrop-blur-[12.5px]
|
||||
sm:top-5 md:top-6 md:right-[-48px];
|
||||
}
|
||||
|
||||
.event-navigation.is-closed {
|
||||
|
||||
@@ -317,7 +317,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</nav>
|
||||
<div ref="startRef" class="btn-start">
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
type="custom"
|
||||
platform="pc"
|
||||
:background-color="
|
||||
@@ -334,13 +334,13 @@ onBeforeUnmount(() => {
|
||||
"
|
||||
>
|
||||
{{ gnb1depthButtonData?.btn_info?.txt_btn_name }}
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
<div v-if="gnb2depthButtonData" class="nav-2depth hidden md:block">
|
||||
<ul>
|
||||
<li v-for="(item, key) in gnb2depthButtonData" :key="key">
|
||||
<AtomsButtonLauncher type="custom" :platform="key">
|
||||
<BlocksButtonLauncher type="custom" :platform="key">
|
||||
{{ item.btn_info?.txt_btn_name }}
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -361,7 +361,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
@apply bg-theme-foreground text-theme-foreground-reversal relative z-[110];
|
||||
@apply bg-theme-foreground text-theme-foreground-reversal relative z-[100];
|
||||
}
|
||||
.game-wrap {
|
||||
@apply absolute flex w-full h-[48px] items-center whitespace-nowrap px-[52px] bg-theme-foreground sm:px-[72px] md:h-16 md:pl-0 md:pr-[40px]
|
||||
|
||||
@@ -52,7 +52,7 @@ const setupSeoMeta = (metaTag: PageDataMetaTag) => {
|
||||
|
||||
onMounted(() => {
|
||||
const { sendLog } = useAnalytics()
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
|
||||
// sendLog(locale.value, useAnalyticsLogDataDirect('view', 1))
|
||||
})
|
||||
|
||||
// 메타 태그 설정 감시
|
||||
@@ -82,8 +82,8 @@ watchEffect(() => {
|
||||
v-if="isShowTopBtn || isShowSnsBtn"
|
||||
:class="['utile-wrap', { 'is-stop': pinToMain }]"
|
||||
>
|
||||
<AtomsButtonScrollTop v-if="isShowTopBtn" />
|
||||
<AtomsButtonSns v-if="isShowSnsBtn" />
|
||||
<BlocksButtonScrollTop v-if="isShowTopBtn" />
|
||||
<BlocksButtonSns v-if="isShowSnsBtn" />
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { locale } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const getBtnType = (item?: PageDataResourceGroupBtnInfo): ButtonType => {
|
||||
@@ -35,11 +36,16 @@ const getTextColor = (item?: PageDataResourceGroupBtnInfo): string =>
|
||||
colorCode: item?.color_code_txt,
|
||||
})
|
||||
|
||||
const handleLogClick = (index: number) => {
|
||||
sendLog(
|
||||
locale.value,
|
||||
useAnalyticsLogDataDirect(props.resourcesData[index], props.pageVerTmplSeq)
|
||||
)
|
||||
const handleLogClick = (button: PageDataResourceGroup) => {
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(button, props.pageVerTmplSeq))
|
||||
if (button.btn_info?.detail?.btn_type === 'POP') {
|
||||
const popupSize = button.btn_info?.detail?.size_info
|
||||
const popupTitle = button.btn_info?.detail?.title
|
||||
const popupContent = button.btn_info?.detail?.tab_info[0].title
|
||||
modalStore.handleOpenAlert({
|
||||
contentText: popupContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 편의상
|
||||
@@ -52,17 +58,17 @@ const buttonList = computed(() => props.resourcesData || [])
|
||||
class="flex flex-wrap justify-center gap-3 md:gap-4"
|
||||
>
|
||||
<template v-for="(button, index) in buttonList" :key="index">
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
v-if="button.btn_info?.detail?.btn_type === 'RUN'"
|
||||
type="duplication"
|
||||
:platform="button.btn_info?.detail?.market_type"
|
||||
:background-color="getBgColor(button.btn_info)"
|
||||
:text-color="getTextColor(button.btn_info)"
|
||||
:disabled="button?.btn_info?.disabled"
|
||||
@click="handleLogClick(index)"
|
||||
@click="handleLogClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
<AtomsButton
|
||||
v-else
|
||||
:type="getBtnType(button.btn_info)"
|
||||
@@ -72,7 +78,7 @@ const buttonList = computed(() => props.resourcesData || [])
|
||||
:background-color="getBgColor(button.btn_info)"
|
||||
:text-color="getTextColor(button.btn_info)"
|
||||
:disabled="button?.btn_info?.disabled"
|
||||
@click="handleLogClick(index)"
|
||||
@click="handleLogClick(button)"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</AtomsButton>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useAnalyticsLogDataDirect = (
|
||||
logData.viewArea = pageData.page_name_en
|
||||
logData.viewType = 'pageView'
|
||||
}
|
||||
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
@@ -53,15 +53,15 @@ export const useAnalyticsLogDataDirect = (
|
||||
eventCategory: `${pageData.page_name}_${pageDataTrack?.click_sarea}_${pageDataTrack?.click_item}`,
|
||||
} as unknown as AnalyticsDetailType
|
||||
|
||||
if(pageDataTrack.action_type === 'click') {
|
||||
if (pageDataTrack.action_type === 'click') {
|
||||
logData.clickArea = pageData.page_name_en
|
||||
logData.clickSarea = pageDataTrack.click_sarea
|
||||
logData.clickItem = pageDataTrack.click_item
|
||||
} else if(pageDataTrack.action_type === 'view') {
|
||||
} else if (pageDataTrack.action_type === 'view') {
|
||||
logData.viewArea = pageData.page_name_en
|
||||
logData.viewType = 'view_frame'
|
||||
}
|
||||
|
||||
|
||||
return logData
|
||||
}
|
||||
|
||||
@@ -225,8 +225,6 @@ const sendSA = (
|
||||
* @param {AnalyticsDetailType} analytics
|
||||
*/
|
||||
const sendLog = (locale: string, analytics: AnalyticsDetailType) => {
|
||||
console.log('🚀 ~ sendLog ~ analytics:', analytics)
|
||||
|
||||
// 언어 코드 대문자 변환
|
||||
analytics.eventLocale = locale.toUpperCase()
|
||||
|
||||
|
||||
@@ -12,14 +12,13 @@ export const useGetGameDataExternal = () => {
|
||||
const webGameData = ref<GameDataResponse | null>(null)
|
||||
|
||||
const getGameDataExternal = async (req: GameDataRequest) => {
|
||||
console.log('🚀 ~ getGameDataExternal ~ req:', req)
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
|
||||
try {
|
||||
const response = (await commonFetch('GET', apiUrl)) as GameDataResponse
|
||||
console.log('🚀 ~ getGameDataExternal ~ response:', response)
|
||||
console.log('🚀 ~ getGameDataExternal:', response.value)
|
||||
|
||||
// FIXME: 테스트용 데이터 ---------------------------------------------------
|
||||
/* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) {
|
||||
|
||||
@@ -15,6 +15,7 @@ provide('pinToMain', pinToMain)
|
||||
<template>
|
||||
<LayoutsHeader />
|
||||
<main id="LayoutsMain" ref="mainRef" class="relative">
|
||||
<BlocksButtonHome />
|
||||
<LayoutsEventNavigation />
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
@@ -2,10 +2,9 @@ export default defineNuxtRouteMiddleware(async to => {
|
||||
try {
|
||||
if (import.meta.client) {
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
console.log('🚀 ~ 00000 gameData:', gameData.value)
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
// const baseDomain = `${runtimeConfig.public.baseDomain}`
|
||||
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
||||
const stoveGameId = gameData.value.game_id
|
||||
|
||||
@@ -217,13 +217,13 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
>
|
||||
<template v-if="platform === 'MOBILE'">
|
||||
<template v-for="os in mobileOSList" :key="os.id">
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
v-if="device.isMobile ? os.isValue : true"
|
||||
:platform="`${os.platformCode as Platform}`"
|
||||
class="!w-full"
|
||||
>
|
||||
<span>{{ os.platformText }}</span>
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -238,13 +238,13 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
||||
>
|
||||
<span>{{ tm(`Download_Button_${platform}_Mobile`) }}</span>
|
||||
</AtomsButton>
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
v-else-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||
:platform="`${platform.toLowerCase() as Platform}`"
|
||||
class="!w-full"
|
||||
>
|
||||
<span>{{ tm(`Download_Button_${platform}`) }}</span>
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
</template>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
|
||||
@@ -270,7 +270,7 @@ const handlePreregistClick = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5">
|
||||
<AtomsButtonLauncher
|
||||
<BlocksButtonLauncher
|
||||
type="duplication"
|
||||
platform="stove"
|
||||
:background-color="buttonColors.backgroundColor"
|
||||
@@ -278,8 +278,8 @@ const handlePreregistClick = () => {
|
||||
@click="handlePreregistClick"
|
||||
>
|
||||
{{ tm('Preregist_Btn_Preegist') }}
|
||||
</AtomsButtonLauncher>
|
||||
<AtomsButtonLauncher
|
||||
</BlocksButtonLauncher>
|
||||
<BlocksButtonLauncher
|
||||
v-for="platform in platformButtons"
|
||||
:key="`preregist-${platform}`"
|
||||
type="duplication"
|
||||
@@ -288,7 +288,7 @@ const handlePreregistClick = () => {
|
||||
:text-color="buttonColors.textColor"
|
||||
>
|
||||
{{ tm('Preregist_Btn_Preegist') }}
|
||||
</AtomsButtonLauncher>
|
||||
</BlocksButtonLauncher>
|
||||
</div>
|
||||
<WidgetsDescription
|
||||
v-if="preDescriptionData"
|
||||
|
||||
Reference in New Issue
Block a user