Merge branch 'feature/20250206_all' into feature/20260120_gil_custom
@@ -57,6 +57,7 @@ const createStyleLinks = (faviconJson: GameDataImg, fontPath: string = '') => {
|
|||||||
|
|
||||||
// 메타 태그 생성 헬퍼
|
// 메타 태그 생성 헬퍼
|
||||||
const createMetaTags = (metaTag: Partial<GameDataMetaTag> = {}) => {
|
const createMetaTags = (metaTag: Partial<GameDataMetaTag> = {}) => {
|
||||||
|
if (!metaTag) return []
|
||||||
const metaList = [
|
const metaList = [
|
||||||
{ name: 'description', content: metaTag.page_desc },
|
{ name: 'description', content: metaTag.page_desc },
|
||||||
{ property: 'og:title', content: metaTag.og_title },
|
{ property: 'og:title', content: metaTag.og_title },
|
||||||
@@ -92,11 +93,11 @@ const createStyleCss = (keyColorJson: GameDataKeyColors) => {
|
|||||||
// 게임 헤드 설정
|
// 게임 헤드 설정
|
||||||
const setupGameHead = (data: GameDataValue) => {
|
const setupGameHead = (data: GameDataValue) => {
|
||||||
try {
|
try {
|
||||||
const metaTag: Partial<GameDataMetaTag> = data.meta_tag_json ?? {}
|
const metaTag: Partial<GameDataMetaTag> = data?.meta_tag_json ?? {}
|
||||||
const designTheme = data.design_theme === 1 ? 'light' : 'dark'
|
const designTheme = data.design_theme === 1 ? 'light' : 'dark'
|
||||||
const styleLinks = createStyleLinks(
|
const styleLinks = createStyleLinks(
|
||||||
data.favicon_json,
|
data.favicon_json,
|
||||||
data?.game_font?.font_path
|
data?.game_font_json?.font_path
|
||||||
)
|
)
|
||||||
const styleCss = createStyleCss(data.key_color_json)
|
const styleCss = createStyleCss(data.key_color_json)
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ const enabledMarkets = computed(() => {
|
|||||||
const logoImgUrl = computed(() => {
|
const logoImgUrl = computed(() => {
|
||||||
const currentLocale = locale.value || 'ko'
|
const currentLocale = locale.value || 'ko'
|
||||||
const localeData = (webInspectionData.value as any)?.[currentLocale]
|
const localeData = (webInspectionData.value as any)?.[currentLocale]
|
||||||
return formatPathHost(localeData?.img_json.bi_large)
|
return formatPathHost(localeData?.img_json?.bi_large)
|
||||||
})
|
})
|
||||||
|
|
||||||
const communityUrl = computed(() => {
|
const communityUrl = computed(() => {
|
||||||
|
|||||||
@@ -38,8 +38,4 @@
|
|||||||
.type-full .splide__arrow--next {
|
.type-full .splide__arrow--next {
|
||||||
@apply right-10;
|
@apply right-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splide-arrow svg {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const componentTag = computed((): string => {
|
|||||||
return 'button'
|
return 'button'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const componentProps = computed(() => {
|
const componentProps = computed(() => {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'link':
|
case 'link':
|
||||||
@@ -34,7 +33,9 @@ const componentProps = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="componentTag" v-bind="componentProps" class="btn-circle">
|
<component :is="componentTag" v-bind="componentProps" class="btn-circle">
|
||||||
<slot />
|
<span class="icon">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
<span class="sr-only">{{ props.srOnly }}</span>
|
<span class="sr-only">{{ props.srOnly }}</span>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
@@ -51,4 +52,8 @@ const componentProps = computed(() => {
|
|||||||
.btn-circle:deep(svg) {
|
.btn-circle:deep(svg) {
|
||||||
@apply w-[20px] h-[20px] md:w-[24px] md:h-[24px];
|
@apply w-[20px] h-[20px] md:w-[24px] md:h-[24px];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply transition-transform duration-300 ease-spring;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type { TrackingObject } from '#layers/types/api/common'
|
import type { TrackingObject } from '#layers/types/api/common'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
bgColor?: string
|
||||||
tracking: TrackingObject
|
tracking: TrackingObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,15 +17,29 @@ const handlePlayClick = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="btn-play" @click="handlePlayClick">
|
<button
|
||||||
|
class="btn-play"
|
||||||
|
:style="{ backgroundColor: props.bgColor }"
|
||||||
|
@click="handlePlayClick"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<AtomsIconsArrowRightFill />
|
||||||
|
</span>
|
||||||
<span class="sr-only">Play</span>
|
<span class="sr-only">Play</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.btn-play {
|
.btn-play {
|
||||||
@apply relative w-[60px] h-[60px] bg-[image:var(--video-play)] bg-cover bg-center bg-no-repeat md:w-[80px] md:h-[80px]
|
@apply relative flex items-center justify-center rounded-full w-[60px] h-[60px] md:w-[80px] md:h-[80px]
|
||||||
|
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.5)] before:rounded-full
|
||||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-[50%] after:opacity-0 after:transition-opacity after:duration-300 after:ease-in-out
|
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-[50%] after:opacity-0 after:transition-opacity after:duration-300 after:ease-in-out
|
||||||
hover:after:opacity-10;
|
hover:after:opacity-10;
|
||||||
}
|
}
|
||||||
|
.btn-play:hover .icon {
|
||||||
|
@apply scale-[1.08];
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
@apply transition-transform duration-300 ease-spring;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ interface props {
|
|||||||
backgroundColor?: string
|
backgroundColor?: string
|
||||||
textColor?: string
|
textColor?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
gradient?: boolean
|
||||||
|
useGameFont?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<props>(), {
|
const props = withDefaults(defineProps<props>(), {
|
||||||
@@ -19,8 +21,13 @@ const props = withDefaults(defineProps<props>(), {
|
|||||||
target: '_self',
|
target: '_self',
|
||||||
textColor: 'var(--alternative-02)',
|
textColor: 'var(--alternative-02)',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
gradient: false,
|
||||||
|
useGameFont: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const gameDataStore = useGameDataStore()
|
||||||
|
const { fontFamily } = storeToRefs(gameDataStore)
|
||||||
|
|
||||||
const componentTag = computed((): string => {
|
const componentTag = computed((): string => {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'external':
|
case 'external':
|
||||||
@@ -29,17 +36,11 @@ const componentTag = computed((): string => {
|
|||||||
case 'download':
|
case 'download':
|
||||||
return props.href ? 'a' : 'button'
|
return props.href ? 'a' : 'button'
|
||||||
case 'internal':
|
case 'internal':
|
||||||
return 'AtomsLocaleLink'
|
return props.href ? 'AtomsLocaleLink' : 'button'
|
||||||
default:
|
default:
|
||||||
return 'button'
|
return 'button'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const backgroundColor = computed(() => {
|
|
||||||
if (props.backgroundColor) {
|
|
||||||
return props.backgroundColor
|
|
||||||
}
|
|
||||||
return props.variant === 'filled' ? 'var(--primary)' : 'white'
|
|
||||||
})
|
|
||||||
const componentProps = computed(() => {
|
const componentProps = computed(() => {
|
||||||
if (props.type === 'external' || props.type === 'link') {
|
if (props.type === 'external' || props.type === 'link') {
|
||||||
return {
|
return {
|
||||||
@@ -49,9 +50,12 @@ const componentProps = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (props.type === 'internal') {
|
if (props.type === 'internal') {
|
||||||
return {
|
if (props.href) {
|
||||||
to: props.href,
|
return {
|
||||||
|
to: props.href,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.type === 'download') {
|
if (props.type === 'download') {
|
||||||
@@ -67,6 +71,30 @@ const componentProps = computed(() => {
|
|||||||
|
|
||||||
return {}
|
return {}
|
||||||
})
|
})
|
||||||
|
const textColor = computed(() => {
|
||||||
|
return props.textColor ? props.textColor : 'var(--text-secondary)'
|
||||||
|
})
|
||||||
|
const buttonStyle = computed(() => {
|
||||||
|
const backgroundColor =
|
||||||
|
props.variant === 'filled' ? 'var(--primary)' : 'white'
|
||||||
|
const style: Record<string, string> = {
|
||||||
|
backgroundColor: props.backgroundColor ?? backgroundColor,
|
||||||
|
'--disabled-color': textColor.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
const textStyle = computed(() => {
|
||||||
|
const style: Record<string, string> = {
|
||||||
|
color: textColor.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.useGameFont && fontFamily.value) {
|
||||||
|
style.fontFamily = fontFamily.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return style
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -75,29 +103,26 @@ const componentProps = computed(() => {
|
|||||||
v-bind="{ ...componentProps }"
|
v-bind="{ ...componentProps }"
|
||||||
:class="['btn-base', props.size, { disabled: props.disabled }]"
|
:class="['btn-base', props.size, { disabled: props.disabled }]"
|
||||||
:data-variant="props.variant"
|
:data-variant="props.variant"
|
||||||
:style="{
|
:style="buttonStyle"
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
color: props.textColor ? props.textColor : 'var(--text-secondary)',
|
|
||||||
'--text-color': props.textColor,
|
|
||||||
}"
|
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
>
|
>
|
||||||
<span class="btn-content">
|
<i v-if="props.gradient" class="btn-gradient"></i>
|
||||||
|
<span class="btn-content" :style="textStyle">
|
||||||
<slot />
|
<slot />
|
||||||
<AtomsIconsLongArrowRightLine
|
<AtomsIconsLongArrowRightLine
|
||||||
v-if="props.type === 'internal'"
|
v-if="props.type === 'internal'"
|
||||||
:color="props.textColor"
|
:color="props.textColor"
|
||||||
class="icon"
|
class="icon icon-internal"
|
||||||
/>
|
/>
|
||||||
<AtomsIconsWebLinkLine
|
<AtomsIconsWebLinkLine
|
||||||
v-if="props.type === 'external'"
|
v-if="props.type === 'external'"
|
||||||
:color="props.textColor"
|
:color="props.textColor"
|
||||||
class="icon"
|
class="icon icon-external"
|
||||||
/>
|
/>
|
||||||
<AtomsIconsDownloadLine
|
<AtomsIconsDownloadLine
|
||||||
v-if="props.type === 'download'"
|
v-if="props.type === 'download'"
|
||||||
:color="props.textColor"
|
:color="props.textColor"
|
||||||
class="icon"
|
class="icon icon-download"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</component>
|
</component>
|
||||||
@@ -108,11 +133,6 @@ const componentProps = computed(() => {
|
|||||||
@apply overflow-hidden relative inline-flex items-center justify-center font-medium cursor-pointer
|
@apply overflow-hidden relative inline-flex items-center justify-center font-medium cursor-pointer
|
||||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-white/10;
|
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-white/10;
|
||||||
}
|
}
|
||||||
.btn-base.disabled {
|
|
||||||
@apply cursor-default pointer-events-none
|
|
||||||
after:bg-[var(--text-color)] after:opacity-20 after:z-[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-base[data-variant='filled'] {
|
.btn-base[data-variant='filled'] {
|
||||||
@apply after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0
|
@apply after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0
|
||||||
hover:after:opacity-20;
|
hover:after:opacity-20;
|
||||||
@@ -121,6 +141,26 @@ const componentProps = computed(() => {
|
|||||||
@apply before:border-[rgba(0,0,0,0.1)]
|
@apply before:border-[rgba(0,0,0,0.1)]
|
||||||
hover:before:border-[#999];
|
hover:before:border-[#999];
|
||||||
}
|
}
|
||||||
|
.btn-base.disabled {
|
||||||
|
@apply cursor-default pointer-events-none
|
||||||
|
after:opacity-20 after:z-[2];
|
||||||
|
}
|
||||||
|
.btn-base.disabled::after {
|
||||||
|
background-color: var(--disabled-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply transition-transform duration-300 ease-spring;
|
||||||
|
}
|
||||||
|
.btn-base:hover .icon-internal {
|
||||||
|
@apply translate-x-[3px];
|
||||||
|
}
|
||||||
|
.btn-base:hover .icon-external {
|
||||||
|
@apply scale-[1.08];
|
||||||
|
}
|
||||||
|
.btn-base:hover .icon-download {
|
||||||
|
@apply translate-y-[3px];
|
||||||
|
}
|
||||||
|
|
||||||
.btn-base.disabled .btn-content {
|
.btn-base.disabled .btn-content {
|
||||||
@apply opacity-50;
|
@apply opacity-50;
|
||||||
@@ -131,4 +171,13 @@ const componentProps = computed(() => {
|
|||||||
.btn-base.size-extra-small .btn-content {
|
.btn-base.size-extra-small .btn-content {
|
||||||
@apply gap-0.5;
|
@apply gap-0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-gradient {
|
||||||
|
@apply absolute top-0 left-0 w-full h-full opacity-[0.7] mix-blend-soft-light;
|
||||||
|
background: radial-gradient(
|
||||||
|
68.19% 81.25% at 50.35% 100%,
|
||||||
|
#fff 20%,
|
||||||
|
rgba(255, 255, 255, 0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,11 +50,9 @@ const onSelectOption = (option: { [key: string | number]: any }): void => {
|
|||||||
>
|
>
|
||||||
{{ selectedOption }}
|
{{ selectedOption }}
|
||||||
</span>
|
</span>
|
||||||
<i
|
<i class="inline-flex items-center justify-center shrink-0">
|
||||||
class="inline-flex items-center justify-center w-[14px] h-[14px] shrink-0"
|
<AtomsIconsArrowDownFill
|
||||||
>
|
:size="14"
|
||||||
<AtomsIconsSelectArrowDownFill
|
|
||||||
:size="12"
|
|
||||||
color="#333333"
|
color="#333333"
|
||||||
:class="isActive ? 'rotate-180' : ''"
|
:class="isActive ? 'rotate-180' : ''"
|
||||||
/>
|
/>
|
||||||
|
|||||||
31
layers/components/atoms/icons/ArrowControlTopLine.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
size?: number | string
|
||||||
|
color?: string
|
||||||
|
fillOpacity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
size: 24,
|
||||||
|
color: '#ffffff',
|
||||||
|
fillOpacity: 0.5,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
:fill="color"
|
||||||
|
:fill-opacity="fillOpacity"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M13 9.41422L17.2929 13.7071C17.6834 14.0976 18.3166 14.0976 18.7071 13.7071C19.0976 13.3166 19.0976 12.6834 18.7071 12.2929L12.7078 6.29361C12.5289 6.1143 12.2822 6.00257 12.0094 6.00005C12.0063 6.00002 12.0032 6.00001 12 6.00001C11.9968 6.00001 11.9937 6.00002 11.9906 6.00005C11.7269 6.00249 11.4877 6.10694 11.3104 6.27585C11.3045 6.28145 11.2987 6.28714 11.2929 6.2929L5.29289 12.2929C4.90237 12.6834 4.90237 13.3166 5.29289 13.7071C5.68341 14.0976 6.31658 14.0976 6.7071 13.7071L11 9.41422L11 20C11 20.5523 11.4477 21 12 21C12.5523 21 13 20.5523 13 20L13 9.41422Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M19.5 4.00001C19.5 4.55229 19.0523 5 18.5 5L5.5 5C4.94771 5 4.5 4.55228 4.5 4C4.5 3.44772 4.94771 3 5.5 3L18.5 3C19.0523 3 19.5 3.44772 19.5 4.00001Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
30
layers/components/atoms/icons/ArrowRightFill.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
size?: number | string
|
||||||
|
color?: string
|
||||||
|
fillOpacity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
size: 32,
|
||||||
|
color: '#ffffff',
|
||||||
|
fillOpacity: 0.5,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
:fill="color"
|
||||||
|
:fill-opacity="fillOpacity"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M14.4839 25.0812C12.7639 26.0629 11.079 26.9852 9.42912 27.8405C8.49538 28.3239 7.40719 27.6545 7.33699 26.5612C7.21062 24.62 7.11935 22.6119 7.06318 20.5443C7.02106 19.0568 7 17.5396 7 16C7 14.4604 7.02106 12.9506 7.06318 11.4557C7.11935 9.38809 7.21062 7.37997 7.33699 5.43879C7.40719 4.34548 8.49538 3.6761 9.42912 4.15954C11.079 5.01485 12.7709 5.9371 14.4839 6.91884C15.7196 7.6254 16.9692 8.36171 18.2259 9.13521C19.4826 9.90871 20.7112 10.6822 21.9047 11.4631C23.5686 12.549 25.1622 13.6349 26.6857 14.7208C27.5492 15.3306 27.5492 16.6694 26.6857 17.2793C25.1622 18.3651 23.5615 19.4584 21.9047 20.5369C20.7112 21.3178 19.4826 22.0987 18.2259 22.8648C16.9692 23.6383 15.7196 24.3672 14.4839 25.0812Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
size?: number | string
|
|
||||||
color?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
|
||||||
size: 32,
|
|
||||||
color: '#EBEBEB',
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
:width="size"
|
|
||||||
:height="size"
|
|
||||||
viewBox="0 0 10 6"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M4.06454 4.95333L0.204544 1.16667C-0.228789 0.74 0.0712106 0 0.684544 0L9.31787 0C9.9312 0 10.2312 0.74 9.79787 1.16667L5.93787 4.95333C5.41787 5.46 4.59121 5.46 4.07121 4.95333H4.06454Z"
|
|
||||||
:fill="color"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -31,4 +31,7 @@ const analytics = {
|
|||||||
@apply fixed top-3 right-3 mt-[calc(var(--scroll-position,48px)+48px)] bg-black/20 shadow-[0_1.667px_3.333px_0_rgba(0,0,0,0.06)] backdrop-blur-[12.5px] z-[100]
|
@apply fixed top-3 right-3 mt-[calc(var(--scroll-position,48px)+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-[calc(var(--scroll-position,64px)+64px)];
|
sm:top-5 md:top-6 md:right-8 md:mt-[calc(var(--scroll-position,64px)+64px)];
|
||||||
}
|
}
|
||||||
|
.btn-home:hover :deep(.icon) {
|
||||||
|
@apply scale-[1.08];
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ interface Props {
|
|||||||
iconComponent?: Component
|
iconComponent?: Component
|
||||||
iconProps?: Record<string, any>
|
iconProps?: Record<string, any>
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
useGameFont?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
variant: 'filled',
|
variant: 'filled',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
useGameFont: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
@@ -31,7 +33,7 @@ const gameDataStore = useGameDataStore()
|
|||||||
const modalStore = useModalStore()
|
const modalStore = useModalStore()
|
||||||
const { isProcessing, validateLauncher } = useCheckGameStart()
|
const { isProcessing, validateLauncher } = useCheckGameStart()
|
||||||
|
|
||||||
const { gameName, platformType, osType, marketJson } =
|
const { gameName, platformType, osType, marketJson, fontFamily } =
|
||||||
storeToRefs(gameDataStore)
|
storeToRefs(gameDataStore)
|
||||||
|
|
||||||
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
||||||
@@ -40,12 +42,11 @@ const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
|||||||
pc: 'AtomsIconsLogoWindow',
|
pc: 'AtomsIconsLogoWindow',
|
||||||
stove: 'AtomsIconsLogoStove',
|
stove: 'AtomsIconsLogoStove',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const DUP_IMAGE_MAP: Record<Platform, string> = {
|
const DUP_IMAGE_MAP: Record<Platform, string> = {
|
||||||
google_play: '/images/common/btn_logo-google.svg',
|
google_play: '/images/common/btn_launcher/btn_logo-google.svg',
|
||||||
app_store: '/images/common/btn_logo-app.svg',
|
app_store: '/images/common/btn_launcher/btn_logo-app.svg',
|
||||||
pc: '/images/common/btn_logo-pc.svg',
|
pc: '/images/common/btn_launcher/btn_logo-pc.svg',
|
||||||
stove: '/images/common/btn_logo-stove.svg',
|
stove: '/images/common/btn_launcher/btn_logo-stove.svg',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const componentTag = computed(() => {
|
const componentTag = computed(() => {
|
||||||
@@ -54,7 +55,17 @@ const componentTag = computed(() => {
|
|||||||
}
|
}
|
||||||
return 'button'
|
return 'button'
|
||||||
})
|
})
|
||||||
const isSingle = computed(() => props.type === 'single')
|
const shouldShowPlatformIcon = computed(
|
||||||
|
() =>
|
||||||
|
(props.type === 'default' && props.variant !== 'custom') ||
|
||||||
|
props.type === 'single'
|
||||||
|
)
|
||||||
|
const shouldShowDownloadIcon = computed(
|
||||||
|
() =>
|
||||||
|
props.platform === 'pc' &&
|
||||||
|
props.type === 'default' &&
|
||||||
|
props.variant !== 'custom'
|
||||||
|
)
|
||||||
const supportedPlatforms = computed(
|
const supportedPlatforms = computed(
|
||||||
() =>
|
() =>
|
||||||
getSupportedPlatforms(
|
getSupportedPlatforms(
|
||||||
@@ -63,18 +74,27 @@ const supportedPlatforms = computed(
|
|||||||
) as PlatformTransformType[]
|
) as PlatformTransformType[]
|
||||||
)
|
)
|
||||||
const platformIcon = computed(() => PLATFORM_ICON_MAP[props.platform])
|
const platformIcon = computed(() => PLATFORM_ICON_MAP[props.platform])
|
||||||
const inlineStyle = computed<CSSProperties>(() => {
|
const buttonStyle = computed<CSSProperties>(() => {
|
||||||
const style: CSSProperties = {}
|
const style: CSSProperties = {}
|
||||||
|
|
||||||
if (props.backgroundColor) {
|
if (props.backgroundColor) {
|
||||||
style.backgroundColor = props.backgroundColor
|
style.backgroundColor = props.backgroundColor
|
||||||
}
|
}
|
||||||
if (props.textColor) {
|
|
||||||
style.color = props.textColor
|
|
||||||
}
|
|
||||||
if (props.type === 'duplication') {
|
if (props.type === 'duplication') {
|
||||||
style.backgroundImage = `url(${formatPathHost(DUP_IMAGE_MAP[props.platform], { imageType: 'common' })})`
|
style.backgroundImage = `url(${formatPathHost(DUP_IMAGE_MAP[props.platform], { imageType: 'common' })})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
const textStyle = computed<CSSProperties>(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
if (props.textColor) {
|
||||||
|
style.color = props.textColor
|
||||||
|
}
|
||||||
|
if (props.useGameFont && fontFamily.value) {
|
||||||
|
style.fontFamily = fontFamily.value
|
||||||
|
}
|
||||||
|
|
||||||
return style
|
return style
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,21 +160,19 @@ const handleClick = () => {
|
|||||||
:class="[
|
:class="[
|
||||||
'btn-base',
|
'btn-base',
|
||||||
props.type,
|
props.type,
|
||||||
{ 'no-text': isSingle && !$slots.default },
|
{ 'no-text': props.type === 'single' && !$slots.default },
|
||||||
]"
|
]"
|
||||||
:data-variant="props.variant"
|
:data-variant="props.variant"
|
||||||
:data-platform="props.platform"
|
:data-platform="props.platform"
|
||||||
:style="inlineStyle"
|
:style="buttonStyle"
|
||||||
:disabled="disabled || isProcessing"
|
:disabled="disabled || isProcessing"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
<span class="btn-content">
|
<span class="btn-content">
|
||||||
<component
|
<span v-if="shouldShowPlatformIcon" class="icon-platform">
|
||||||
:is="platformIcon"
|
<component :is="platformIcon" />
|
||||||
v-if="props.type !== 'duplication'"
|
</span>
|
||||||
class="icon-platform"
|
<span class="text" :style="textStyle">
|
||||||
/>
|
|
||||||
<span class="text">
|
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
<component
|
<component
|
||||||
@@ -162,10 +180,7 @@ const handleClick = () => {
|
|||||||
v-if="props.iconComponent"
|
v-if="props.iconComponent"
|
||||||
v-bind="props.iconProps"
|
v-bind="props.iconProps"
|
||||||
/>
|
/>
|
||||||
<span
|
<span v-if="shouldShowDownloadIcon" class="icon-download">
|
||||||
v-if="props.platform === 'pc' && props.type === 'default'"
|
|
||||||
class="icon-download"
|
|
||||||
>
|
|
||||||
<AtomsIconsDownloadLine />
|
<AtomsIconsDownloadLine />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -180,7 +195,10 @@ const handleClick = () => {
|
|||||||
@apply relative flex items-center w-full z-[1];
|
@apply relative flex items-center w-full z-[1];
|
||||||
}
|
}
|
||||||
.icon-platform {
|
.icon-platform {
|
||||||
@apply w-5 h-5 flex-shrink-0;
|
@apply w-5 h-5 mr-2 flex-shrink-0;
|
||||||
|
}
|
||||||
|
.icon-download {
|
||||||
|
@apply ml-auto pl-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-base[data-variant='filled'] {
|
.btn-base[data-variant='filled'] {
|
||||||
@@ -212,12 +230,6 @@ const handleClick = () => {
|
|||||||
@apply line-clamp-2 text-[14px]
|
@apply line-clamp-2 text-[14px]
|
||||||
md:text-[16px];
|
md:text-[16px];
|
||||||
}
|
}
|
||||||
.btn-base.default .icon-platform + .text {
|
|
||||||
@apply pl-2;
|
|
||||||
}
|
|
||||||
.btn-base.default .icon-download {
|
|
||||||
@apply ml-auto pl-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-base.default[data-variant='outlined'] .icon-download {
|
.btn-base.default[data-variant='outlined'] .icon-download {
|
||||||
@apply border-black/10;
|
@apply border-black/10;
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ const handleScrollToTop = () => {
|
|||||||
class="btn-top"
|
class="btn-top"
|
||||||
sr-only="top"
|
sr-only="top"
|
||||||
@click="handleScrollToTop"
|
@click="handleScrollToTop"
|
||||||
/>
|
>
|
||||||
|
<AtomsIconsArrowControlTopLine />
|
||||||
|
</AtomsButtonCircle>
|
||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -34,4 +36,7 @@ const handleScrollToTop = () => {
|
|||||||
.btn-top {
|
.btn-top {
|
||||||
@apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat;
|
@apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat;
|
||||||
}
|
}
|
||||||
|
.btn-top:hover :deep(.icon) {
|
||||||
|
@apply -translate-y-[3px];
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ const props = defineProps<Props>()
|
|||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const { sendLog } = useAnalytics()
|
const { sendLog } = useAnalytics()
|
||||||
|
|
||||||
|
const getArrowBgColor = (direction: 'prev' | 'next') => {
|
||||||
|
return getColorCodeFromData(
|
||||||
|
props.arrowsData?.[direction === 'prev' ? 0 : 1]?.display,
|
||||||
|
'none'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleArrowClick = (direction: 'prev' | 'next') => {
|
const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||||
if (props.arrowsData) {
|
if (props.arrowsData) {
|
||||||
const arrowIndex = direction === 'prev' ? 0 : 1
|
const arrowIndex = direction === 'prev' ? 0 : 1
|
||||||
@@ -23,12 +30,28 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
|
|||||||
<AtomsButtonCircle
|
<AtomsButtonCircle
|
||||||
sr-only="Previous"
|
sr-only="Previous"
|
||||||
class="splide-arrow splide__arrow--prev"
|
class="splide-arrow splide__arrow--prev"
|
||||||
|
:style="{ backgroundColor: getArrowBgColor('prev') }"
|
||||||
@click="handleArrowClick('prev')"
|
@click="handleArrowClick('prev')"
|
||||||
/>
|
>
|
||||||
|
<AtomsIconsArrowRightLine color="#ffffff" />
|
||||||
|
</AtomsButtonCircle>
|
||||||
<AtomsButtonCircle
|
<AtomsButtonCircle
|
||||||
sr-only="Next"
|
sr-only="Next"
|
||||||
class="splide-arrow splide__arrow--next"
|
class="splide-arrow splide__arrow--next"
|
||||||
|
:style="{ backgroundColor: getArrowBgColor('next') }"
|
||||||
@click="handleArrowClick('next')"
|
@click="handleArrowClick('next')"
|
||||||
/>
|
>
|
||||||
|
<AtomsIconsArrowRightLine color="#ffffff" />
|
||||||
|
</AtomsButtonCircle>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.splide__arrow--prev:hover :deep(.icon) {
|
||||||
|
@apply -translate-x-[3px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.splide__arrow--next:hover :deep(.icon) {
|
||||||
|
@apply translate-x-[3px];
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
{{ t('Text_MonthYear', { month: month + 1, year: year }) }}
|
{{ t('Text_MonthYear', { month: month + 1, year: year }) }}
|
||||||
</span>
|
</span>
|
||||||
<AtomsIconsSelectArrowDownFill :size="10" color="#333333" />
|
<AtomsIconsArrowDownFill :size="16" color="#333333" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<svg
|
<svg
|
||||||
v-else
|
v-else
|
||||||
class="w-3 h-3 text-gray-300 transition-transform duration-200"
|
class="w-3 h-3 text-gray-300 transition-transform duration-200"
|
||||||
:class="{ 'rotate-180': isDropdownOpen }"
|
:class="{ 'rotate-180': !isDropdownOpen }"
|
||||||
viewBox="0 0 12 12"
|
viewBox="0 0 12 12"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -125,7 +125,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
interface LanguageOrder {
|
interface LanguageOrder {
|
||||||
languageOrder?: any[]
|
languageOrder?: any[]
|
||||||
}
|
}
|
||||||
@@ -154,8 +153,8 @@ const availableLanguages = computed(() => {
|
|||||||
if (filteredLanguages.length === 0) {
|
if (filteredLanguages.length === 0) {
|
||||||
return [{ code: 'ko', name: '한국어' }]
|
return [{ code: 'ko', name: '한국어' }]
|
||||||
}
|
}
|
||||||
//languageOrder 값이 있는 경우 정렬, 없는 경우 기본 순서 유지
|
//languageOrder 값이 있는 경우 정렬, 없는 경우 기본 순서 유지
|
||||||
const defaultLanguageOrder = [ 'ko', 'en', 'ja', 'zh-cn', 'zh-tw', 'th' ]
|
const defaultLanguageOrder = ['ko', 'en', 'ja', 'zh-cn', 'zh-tw', 'th']
|
||||||
const languageOrder = props.languageOrder || defaultLanguageOrder
|
const languageOrder = props.languageOrder || defaultLanguageOrder
|
||||||
// 정렬: 우선순위 언어 먼저, 그 다음 나머지
|
// 정렬: 우선순위 언어 먼저, 그 다음 나머지
|
||||||
const sortedLanguages = filteredLanguages.sort((a, b) => {
|
const sortedLanguages = filteredLanguages.sort((a, b) => {
|
||||||
@@ -230,7 +229,6 @@ const switchLanguage = async () => {
|
|||||||
const localeCookie = useCookie('LOCALE', {
|
const localeCookie = useCookie('LOCALE', {
|
||||||
domain: baseDomain,
|
domain: baseDomain,
|
||||||
path: '/',
|
path: '/',
|
||||||
maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위)
|
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
})
|
})
|
||||||
localeCookie.value = selectedLocale.value.toLowerCase()
|
localeCookie.value = selectedLocale.value.toLowerCase()
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ const OBSERVER_OPTIONS = {
|
|||||||
rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진
|
rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
} as const
|
} as const
|
||||||
|
const AUTO_HIDE_MS = 5000
|
||||||
|
|
||||||
const isShowLnbWithScroll = ref(false)
|
let autoHideTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
const isShowLnbWithScroll = ref(true)
|
||||||
const activeSection = ref<string>('')
|
const activeSection = ref<string>('')
|
||||||
|
|
||||||
const lnbList = computed<Record<string, PageDataLnbMenu>>(
|
const lnbList = computed<Record<string, PageDataLnbMenu>>(
|
||||||
@@ -72,6 +74,20 @@ const handleIntersection = (entries: IntersectionObserverEntry[]) => {
|
|||||||
|
|
||||||
const observer = new IntersectionObserver(handleIntersection, OBSERVER_OPTIONS)
|
const observer = new IntersectionObserver(handleIntersection, OBSERVER_OPTIONS)
|
||||||
|
|
||||||
|
const clearAutoHide = () => {
|
||||||
|
if (autoHideTimer) {
|
||||||
|
clearTimeout(autoHideTimer)
|
||||||
|
autoHideTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleAutoHide = () => {
|
||||||
|
clearAutoHide()
|
||||||
|
autoHideTimer = setTimeout(() => {
|
||||||
|
isShowLnbWithScroll.value = false
|
||||||
|
}, AUTO_HIDE_MS)
|
||||||
|
}
|
||||||
|
|
||||||
// 요소 관찰 헬퍼 함수
|
// 요소 관찰 헬퍼 함수
|
||||||
const observeElement = (elementId: string) => {
|
const observeElement = (elementId: string) => {
|
||||||
const element = document.getElementById(elementId)
|
const element = document.getElementById(elementId)
|
||||||
@@ -112,15 +128,28 @@ const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(directions, newVal => {
|
watch(directions, newVal => {
|
||||||
// 스크롤 업일 때만 표시, 다운이거나 멈춘 상태에서는 숨김
|
// 스크롤 위로: 즉시 노출 + 5초 후 자동 숨김
|
||||||
isShowLnbWithScroll.value = newVal.top === true
|
if (newVal.top === true) {
|
||||||
|
isShowLnbWithScroll.value = true
|
||||||
|
scheduleAutoHide()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 스크롤 아래로: 즉시 숨김 (딜레이 없음)
|
||||||
|
if (newVal.bottom === true) {
|
||||||
|
clearAutoHide()
|
||||||
|
isShowLnbWithScroll.value = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
observeSections()
|
observeSections()
|
||||||
|
isShowLnbWithScroll.value = true
|
||||||
|
scheduleAutoHide()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
clearAutoHide()
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -193,7 +222,7 @@ onUnmounted(() => {
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
.lnb-wrap.is-hidden {
|
.lnb-wrap.is-hidden {
|
||||||
@apply translate-x-[110%] delay-[5s];
|
@apply translate-x-[110%];
|
||||||
}
|
}
|
||||||
.lnb-main {
|
.lnb-main {
|
||||||
@apply flex flex-col gap-4 items-end;
|
@apply flex flex-col gap-4 items-end;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const handlePagination = (page: number) => {
|
|||||||
:class="[
|
:class="[
|
||||||
'!w-full !h-full p-0 rounded-full text-center text-[14px] font-[500] leading-[24px] tracking-[-0.42px]',
|
'!w-full !h-full p-0 rounded-full text-center text-[14px] font-[500] leading-[24px] tracking-[-0.42px]',
|
||||||
page === currentPage
|
page === currentPage
|
||||||
? '!bg-[#C7AE8B] !text-white cursor-default'
|
? '!bg-[var(--primary)] !text-white cursor-default'
|
||||||
: '',
|
: '',
|
||||||
]"
|
]"
|
||||||
@click="handlePagination(page)"
|
@click="handlePagination(page)"
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ const handleCopy = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="Object.keys(snsJson).length > 0" class="sns-container">
|
<div v-if="Object.keys(snsJson).length > 0" class="sns-wrap">
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<AtomsButtonCircle
|
<AtomsButtonCircle
|
||||||
v-show="!showSnsList"
|
v-show="!showSnsList"
|
||||||
class="btn-sns"
|
class="btn-more"
|
||||||
sr-only="sns"
|
sr-only="sns"
|
||||||
:style="{ backgroundColor: snsBackgroundColor }"
|
:style="{ backgroundColor: snsBackgroundColor }"
|
||||||
@click="handleControlForce(true)"
|
@click="handleControlForce(true)"
|
||||||
@@ -55,36 +55,45 @@ const handleCopy = async () => {
|
|||||||
</AtomsButtonCircle>
|
</AtomsButtonCircle>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div v-show="showSnsList" class="sns-list">
|
||||||
v-show="showSnsList"
|
|
||||||
class="sns-list"
|
|
||||||
:style="{ backgroundColor: snsBackgroundColor }"
|
|
||||||
>
|
|
||||||
<template v-for="(item, key) in snsJson" :key="key">
|
<template v-for="(item, key) in snsJson" :key="key">
|
||||||
<a
|
<AtomsButtonCircle
|
||||||
v-if="item.use_yn === 1 && item.url"
|
v-if="item.use_yn === 1 && item.url"
|
||||||
:href="item.url"
|
type="link"
|
||||||
|
:to="item.url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="sns-item"
|
:class="['btn-sns', key]"
|
||||||
rel="noopener noreferrer"
|
:sr-only="key"
|
||||||
:style="{
|
|
||||||
backgroundImage: `url(${formatPathHost(`/images/common/ic-v2-logo-${key}-fill.png`, { imageType: 'common' })})`,
|
|
||||||
}"
|
|
||||||
@click="sendLog(locale, { ...analytics, click_item: key })"
|
@click="sendLog(locale, { ...analytics, click_item: key })"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{ key }}</span>
|
<img
|
||||||
</a>
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
:src="
|
||||||
|
formatPathHost(`/images/common/ic-v2-logo-${key}-fill.png`, {
|
||||||
|
imageType: 'common',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:alt="key"
|
||||||
|
/>
|
||||||
|
</AtomsButtonCircle>
|
||||||
</template>
|
</template>
|
||||||
<button
|
<AtomsButtonCircle
|
||||||
type="button"
|
class="btn-sns link"
|
||||||
class="sns-item"
|
sr-only="copy"
|
||||||
:style="{
|
|
||||||
backgroundImage: `url(${formatPathHost('/images/common/ic-v2-community-link-line.png', { imageType: 'common' })})`,
|
|
||||||
}"
|
|
||||||
@click="handleCopy"
|
@click="handleCopy"
|
||||||
>
|
>
|
||||||
<span class="sr-only">copy</span>
|
<img
|
||||||
</button>
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
:src="
|
||||||
|
formatPathHost('/images/common/ic-v2-community-link-line.png', {
|
||||||
|
imageType: 'common',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
alt="copy"
|
||||||
|
/>
|
||||||
|
</AtomsButtonCircle>
|
||||||
<div class="close-container">
|
<div class="close-container">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -101,17 +110,47 @@ const handleCopy = async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sns-container {
|
.sns-wrap {
|
||||||
@apply relative h-[40px] md:h-[48px];
|
@apply fixed bottom-3 left-3 h-[40px] md:bottom-8 md:left-8 md:h-[48px] z-[100];
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sns:hover .icon-share {
|
.btn-more:hover .icon-share {
|
||||||
@apply fill-white;
|
@apply fill-white;
|
||||||
}
|
}
|
||||||
|
.btn-sns {
|
||||||
|
@apply bg-center bg-no-repeat bg-[rgba(255,255,255,0.6)] after:hidden;
|
||||||
|
}
|
||||||
|
.btn-sns.kakao:hover {
|
||||||
|
@apply bg-[#FAE100];
|
||||||
|
}
|
||||||
|
.btn-sns.kakao:hover img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(13%) sepia(2%) saturate(0%)
|
||||||
|
hue-rotate(0deg) brightness(100%) contrast(100%);
|
||||||
|
}
|
||||||
|
.btn-sns.tiktok:hover {
|
||||||
|
@apply bg-[#000];
|
||||||
|
}
|
||||||
|
.btn-sns.discord:hover {
|
||||||
|
@apply bg-[#000];
|
||||||
|
}
|
||||||
|
.btn-sns.twitter:hover {
|
||||||
|
@apply bg-[#000];
|
||||||
|
}
|
||||||
|
.btn-sns.youtube:hover {
|
||||||
|
@apply bg-[#FF0000];
|
||||||
|
}
|
||||||
|
.btn-sns.facebook:hover {
|
||||||
|
@apply bg-[#1977F2];
|
||||||
|
}
|
||||||
|
.btn-sns.instagram:hover {
|
||||||
|
@apply bg-[#000];
|
||||||
|
}
|
||||||
|
.btn-sns.link:hover {
|
||||||
|
@apply bg-[#000];
|
||||||
|
}
|
||||||
|
|
||||||
.sns-list {
|
.sns-list {
|
||||||
@apply absolute bottom-0 right-0 flex items-center justify-center gap-4 rounded-full h-full pl-4 pr-3
|
@apply absolute bottom-0 left-0 flex items-center justify-center gap-4 rounded-full h-full pl-4 pr-3;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
.sns-item {
|
.sns-item {
|
||||||
@apply w-[24px] h-[24px] bg-center bg-cover bg-no-repeat opacity-50 z-[1]
|
@apply w-[24px] h-[24px] bg-center bg-cover bg-no-repeat opacity-50 z-[1]
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ watch(
|
|||||||
:class="[
|
:class="[
|
||||||
'modal-wrap',
|
'modal-wrap',
|
||||||
{ 'is-open': content.storeIsOpen },
|
{ 'is-open': content.storeIsOpen },
|
||||||
|
{ dimmed: content.storeIsShowDimmed },
|
||||||
content.storeModalName,
|
content.storeModalName,
|
||||||
]"
|
]"
|
||||||
@click="handleOutsideClick"
|
@click="handleOutsideClick"
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import type {
|
|||||||
|
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const { sendLog } = useAnalytics()
|
const { sendLog } = useAnalytics()
|
||||||
|
|
||||||
const gameDomain = getGameDomain()
|
const gameDomain = getGameDomain()
|
||||||
|
const breakpoints = useResponsiveBreakpoints()
|
||||||
|
|
||||||
const analytics = {
|
const analytics = {
|
||||||
action_type: 'click',
|
action_type: 'click',
|
||||||
click_sarea: 'EventNavigation',
|
click_sarea: 'EventNavigation',
|
||||||
@@ -53,18 +54,21 @@ const toggleEventNavigation = () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
eventNavigationList.value = await getEventNavigation()
|
eventNavigationList.value = await getEventNavigation()
|
||||||
|
if (breakpoints.value.isMobile) {
|
||||||
|
isEventNavigationOpen.value = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="Object.keys(eventNavigationList).length > 1"
|
v-if="Object.keys(eventNavigationList).length > 1"
|
||||||
class="event-navigation"
|
class="navigation-wrap"
|
||||||
:class="{
|
:class="{
|
||||||
'is-closed': !isEventNavigationOpen,
|
'is-closed': !isEventNavigationOpen,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="navigation-wrapper">
|
<div class="navigation-container">
|
||||||
<AtomsButtonCircle
|
<AtomsButtonCircle
|
||||||
sr-only="event navigation control"
|
sr-only="event navigation control"
|
||||||
class="btn-control"
|
class="btn-control"
|
||||||
@@ -105,13 +109,17 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.empty-game + main .event-navigation {
|
.empty-game + main .navigation-wrap {
|
||||||
@apply mt-[var(--scroll-position,48px)];
|
@apply mt-[var(--scroll-position,48px)];
|
||||||
}
|
}
|
||||||
.event-navigation {
|
.sns-wrap ~ .navigation-wrap {
|
||||||
@apply fixed top-0 left-0 bottom-0 mt-[calc(var(--scroll-position,48px)+48px)] md:mt-[calc(var(--scroll-position,64px)+64px)] z-[100] transition-transform duration-300 ease-in-out;
|
@apply pb-[78px];
|
||||||
}
|
}
|
||||||
.navigation-wrapper {
|
|
||||||
|
.navigation-wrap {
|
||||||
|
@apply fixed top-0 left-0 bottom-0 mt-[calc(var(--scroll-position,48px)+48px)] md:mt-[calc(var(--scroll-position,64px)+64px)] z-[90] transition-transform duration-300 ease-in-out;
|
||||||
|
}
|
||||||
|
.navigation-container {
|
||||||
@apply relative h-full p-3 sm:p-5 sm:pr-3
|
@apply relative h-full p-3 sm:p-5 sm:pr-3
|
||||||
md:p-8 md:pt-6 md:pr-4;
|
md:p-8 md:pt-6 md:pr-4;
|
||||||
}
|
}
|
||||||
@@ -132,14 +140,17 @@ onMounted(async () => {
|
|||||||
@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] rotate-180
|
@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] rotate-180
|
||||||
sm:top-5 md:top-6 md:right-[-48px];
|
sm:top-5 md:top-6 md:right-[-48px];
|
||||||
}
|
}
|
||||||
|
.btn-control:hover :deep(.icon) {
|
||||||
|
@apply translate-x-[3px];
|
||||||
|
}
|
||||||
|
|
||||||
.event-navigation.is-closed {
|
.navigation-wrap.is-closed {
|
||||||
@apply translate-x-[calc(-100%+20px)] sm:translate-x-[calc(-100%+40px)];
|
@apply translate-x-[calc(-100%+20px)] sm:translate-x-[calc(-100%+40px)];
|
||||||
}
|
}
|
||||||
.event-navigation.is-closed .btn-control {
|
.navigation-wrap.is-closed .btn-control {
|
||||||
@apply rotate-0;
|
@apply rotate-0;
|
||||||
}
|
}
|
||||||
.event-navigation.is-closed .navigation-list {
|
.navigation-wrap.is-closed .navigation-list {
|
||||||
@apply pointer-events-none opacity-0;
|
@apply pointer-events-none opacity-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,33 +20,6 @@ const { tm, locale }: any = useI18n({
|
|||||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Footer_caution 값이 있고 빈 객체가 아닌지 체크
|
|
||||||
const hasCautionText = computed(() => {
|
|
||||||
const value = tm('Footer_caution')
|
|
||||||
|
|
||||||
// null, undefined 체크
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 빈 객체 체크
|
|
||||||
if (
|
|
||||||
typeof value === 'object' &&
|
|
||||||
!Array.isArray(value) &&
|
|
||||||
Object.keys(value).length === 0
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 문자열로 변환하여 빈 문자열 또는 '{}' 문자열 체크
|
|
||||||
const stringValue = String(value).trim()
|
|
||||||
if (stringValue === '' || stringValue === '{}') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
const gameDataStore = useGameDataStore()
|
const gameDataStore = useGameDataStore()
|
||||||
const { gameData } = storeToRefs(gameDataStore)
|
const { gameData } = storeToRefs(gameDataStore)
|
||||||
// const path = ref<string>(`${staticUrl}/local/template/${gameData.value.s3_folder_name}`)
|
// const path = ref<string>(`${staticUrl}/local/template/${gameData.value.s3_folder_name}`)
|
||||||
@@ -180,35 +153,29 @@ const footerAgeRatingInfo = computed((): string[] => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer id="footer" ref="footerRef" class="relative bg-blac">
|
<footer id="footer" class="relative px-5 sm:px-10">
|
||||||
<div
|
<div
|
||||||
class="inner relative max-w-7xl mx-auto px-5 py-4 text-[12px] text-gray-400 md:px-4 md:py-9 md:text-[12px]"
|
class="relative max-w-[1300px] mx-auto pt-4 pb-10 text-[13px] text-white/50"
|
||||||
>
|
>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<div class="menu-area py-4 pb-4">
|
<div class="py-4 border-b border-white/10">
|
||||||
<ul class="flex items-center flex-wrap gap-x-6 gap-y-2">
|
<ul
|
||||||
<li
|
class="flex items-center flex-wrap gap-x-6 gap-y-2 text-[15px] tracking-[-0.5px]"
|
||||||
v-for="(footerMenuItem, index) in footerLinks"
|
>
|
||||||
:key="index"
|
<li v-for="(footerMenuItem, index) in footerLinks" :key="index">
|
||||||
class="text-[15px] text-white/50 md:tracking-[-0.5px] relative flex items-center"
|
|
||||||
>
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="footerMenuItem.url"
|
:to="footerMenuItem.url"
|
||||||
:target="footerMenuItem.target"
|
:target="footerMenuItem.target"
|
||||||
:class="[
|
:class="[
|
||||||
footerMenuItem.active === 'y' && 'text-white/50',
|
footerMenuItem.active === 'y' && 'text-white/50',
|
||||||
index === 2 && 'text-[#fff]',
|
index === 2 && 'text-white',
|
||||||
'hover:text-gray-600 transition-colors',
|
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{ footerMenuItem.title }}
|
{{ footerMenuItem.title }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="useGameRating" class="relative">
|
<li v-if="useGameRating" class="relative">
|
||||||
<button
|
<button @click="toggleAgeRating">
|
||||||
class="text-[15px] text-white/50 hover:text-gray-600 transition-colors"
|
|
||||||
@click="toggleAgeRating"
|
|
||||||
>
|
|
||||||
<em v-dompurify-html="tm('Footer_AgeRating')"></em>
|
<em v-dompurify-html="tm('Footer_AgeRating')"></em>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
@@ -335,37 +302,26 @@ const footerAgeRatingInfo = computed((): string[] => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="address-area mt-4 hidden sm:block">
|
|
||||||
<address class="not-italic text-white/50">
|
|
||||||
<div class="row my-1.5 leading-5">
|
|
||||||
<span
|
|
||||||
v-dompurify-html="tm('Footer_Address')"
|
|
||||||
class="text-[13px] [&_a]:cursor-pointer [&_a]:text-white/50 [&_a]:underline"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="language-area static md:absolute bottom-7 right-10 text-white mt-5 md:mt-0 md:bottom-5.5 md:right-4"
|
class="flex flex-col items-start mt-4 gap-4 sm:gap-5 md:gap-6 md:flex-row md:justify-between"
|
||||||
>
|
>
|
||||||
|
<address class="hidden not-italic leading-5 sm:block">
|
||||||
|
<div
|
||||||
|
v-dompurify-html="tm('Footer_Address')"
|
||||||
|
class="text-[13px] [&_a]:cursor-pointer [&_a]:text-white/50 [&_a]:underline"
|
||||||
|
/>
|
||||||
|
</address>
|
||||||
<BlocksLanguageSwitcher
|
<BlocksLanguageSwitcher
|
||||||
|
v-if="gameData?.lang_codes?.length > 1"
|
||||||
:language-order="tm('Footer_Language_Order')"
|
:language-order="tm('Footer_Language_Order')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="hasCautionText" class="mt-6 md:mt-6 hidden sm:block">
|
<div class="mt-5">
|
||||||
<div
|
|
||||||
v-dompurify-html="tm('Footer_caution')"
|
|
||||||
class="text-xs text-white/30"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="copyright-area mt-6 text-[13px] text-white/50 md:mt-4">
|
|
||||||
<span v-dompurify-html="tm('Footer_Copyright')"></span>
|
<span v-dompurify-html="tm('Footer_Copyright')"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logo-area flex items-center gap-7 mt-6 md:mt-6">
|
<div class="flex items-center gap-7 mt-6 md:mt-6">
|
||||||
<span>
|
<span>
|
||||||
<a
|
<a
|
||||||
:href="tm('Footer_Smilegate_Link')"
|
:href="tm('Footer_Smilegate_Link')"
|
||||||
@@ -378,8 +334,8 @@ const footerAgeRatingInfo = computed((): string[] => {
|
|||||||
imageType: 'common',
|
imageType: 'common',
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
width="114px"
|
||||||
alt="스마일게이트 로고"
|
alt="스마일게이트 로고"
|
||||||
class="w-auto h-auto"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ onMounted(() => {
|
|||||||
// 화면 크기 변경 시 오버플로우 재계산
|
// 화면 크기 변경 시 오버플로우 재계산
|
||||||
watch(width, () => {
|
watch(width, () => {
|
||||||
throttledCalculateOverflow()
|
throttledCalculateOverflow()
|
||||||
|
if (isMenuOpen.value && breakpoints.value.isDesktop) {
|
||||||
|
handleMenuClose()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -375,12 +378,16 @@ onMounted(() => {
|
|||||||
<template v-if="start1depthData">
|
<template v-if="start1depthData">
|
||||||
<BlocksButtonLauncher
|
<BlocksButtonLauncher
|
||||||
platform="pc"
|
platform="pc"
|
||||||
|
variant="custom"
|
||||||
:background-color="
|
:background-color="
|
||||||
getColorCodeFromData(start1depthData?.btn_info, 'btn')
|
getColorCodeFromData(start1depthData?.btn_info, 'btn')
|
||||||
"
|
"
|
||||||
:text-color="
|
:text-color="
|
||||||
getColorCodeFromData(start1depthData?.btn_info, 'txt')
|
getColorCodeFromData(start1depthData?.btn_info, 'txt')
|
||||||
"
|
"
|
||||||
|
:use-game-font="
|
||||||
|
start1depthData?.btn_info?.use_game_font === 1
|
||||||
|
"
|
||||||
@click="sendLog(locale, start1depthData.tracking)"
|
@click="sendLog(locale, start1depthData.tracking)"
|
||||||
>
|
>
|
||||||
{{ start1depthData?.btn_info?.txt_btn_name }}
|
{{ start1depthData?.btn_info?.txt_btn_name }}
|
||||||
@@ -573,12 +580,9 @@ onMounted(() => {
|
|||||||
.btn-start:hover .nav-2depth {
|
.btn-start:hover .nav-2depth {
|
||||||
@apply md:block;
|
@apply md:block;
|
||||||
}
|
}
|
||||||
.btn-start:deep(.btn-base[data-variant='filled']) {
|
.btn-start:deep(.btn-base.default[data-variant='custom']) {
|
||||||
@apply w-full h-[48px] px-10 font-[700] text-[16px];
|
@apply w-full h-[48px] px-10 font-[700] text-[16px];
|
||||||
}
|
}
|
||||||
.btn-start:deep(.btn-base[data-variant='filled']) svg {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-start .nav-2depth {
|
.btn-start .nav-2depth {
|
||||||
@apply left-[unset] right-[-40px];
|
@apply left-[unset] right-[-40px];
|
||||||
@@ -593,9 +597,6 @@ onMounted(() => {
|
|||||||
.btn-start .nav-2depth:deep(.btn-base) .text {
|
.btn-start .nav-2depth:deep(.btn-base) .text {
|
||||||
@apply pl-1.5 text-[15px] text-theme-foreground-reversal;
|
@apply pl-1.5 text-[15px] text-theme-foreground-reversal;
|
||||||
}
|
}
|
||||||
.btn-start:deep(.nav-2depth .icon-download) {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme='light'] {
|
[data-theme='light'] {
|
||||||
.btn-start .nav-2depth:deep(.btn-base) .icon-platform {
|
.btn-start .nav-2depth:deep(.btn-base) .icon-platform {
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const mainContentRef = ref<HTMLElement>()
|
|
||||||
|
|
||||||
const { tm, locale } = useI18n()
|
const { tm, locale } = useI18n()
|
||||||
const { height: viewportH } = useWindowSize()
|
|
||||||
const { bottom: mainBottom } = useElementBounding(mainContentRef)
|
|
||||||
const { getTemplateComponent } = useTemplateRegistry()
|
const { getTemplateComponent } = useTemplateRegistry()
|
||||||
const loadingStore = useLoadingStore()
|
const loadingStore = useLoadingStore()
|
||||||
const modalStore = useModalStore()
|
const modalStore = useModalStore()
|
||||||
@@ -26,11 +22,6 @@ const { isPAssApiLoading, hasApiCallStarted } = storeToRefs(loadingStore)
|
|||||||
// 개별 메타 태그 표시 여부 확인
|
// 개별 메타 태그 표시 여부 확인
|
||||||
const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2)
|
const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2)
|
||||||
|
|
||||||
const pinToMain = computed(() => {
|
|
||||||
if (!mainBottom.value) return false
|
|
||||||
return mainBottom.value <= viewportH.value
|
|
||||||
})
|
|
||||||
|
|
||||||
// 템플릿 표시 여부 확인
|
// 템플릿 표시 여부 확인
|
||||||
const isTemplateVisible = (template: PageDataTemplate): boolean => {
|
const isTemplateVisible = (template: PageDataTemplate): boolean => {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
@@ -96,7 +87,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="mainContentRef" class="main-content">
|
<div class="content-wrap">
|
||||||
<template
|
<template
|
||||||
v-for="(template, index) in visibleTemplates"
|
v-for="(template, index) in visibleTemplates"
|
||||||
:key="template.template_code ?? index"
|
:key="template.template_code ?? index"
|
||||||
@@ -112,33 +103,27 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<BlocksLnb v-if="isShowLnb" />
|
<BlocksLnb v-if="isShowLnb" />
|
||||||
<div
|
<div v-if="isShowTopBtn" class="utile-wrap">
|
||||||
v-if="isShowTopBtn || isShowSnsBtn"
|
|
||||||
:class="['utile-wrap', { 'is-stop': pinToMain }]"
|
|
||||||
>
|
|
||||||
<BlocksButtonScrollTop v-if="isShowTopBtn" />
|
<BlocksButtonScrollTop v-if="isShowTopBtn" />
|
||||||
<BlocksSns v-if="isShowSnsBtn" />
|
|
||||||
</div>
|
</div>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
<BlocksSns v-if="isShowSnsBtn" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.empty-game + main .main-content {
|
.empty-game + main .content-wrap {
|
||||||
@apply pt-0;
|
@apply pt-0;
|
||||||
}
|
}
|
||||||
.main-content {
|
.content-wrap {
|
||||||
@apply relative pt-[48px] md:pt-[64px];
|
@apply relative pt-[48px] md:pt-[64px];
|
||||||
}
|
}
|
||||||
.utile-wrap {
|
.utile-wrap {
|
||||||
@apply fixed flex flex-col items-end z-[100]
|
@apply fixed flex flex-col items-end z-[100]
|
||||||
bottom-[12px] right-[12px] gap-2 md:bottom-[40px] md:right-[40px] md:gap-3;
|
bottom-[12px] right-[12px] gap-2 md:bottom-[40px] md:right-[40px] md:gap-3;
|
||||||
}
|
}
|
||||||
.utile-wrap.is-stop {
|
|
||||||
@apply absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme='light'] {
|
[data-theme='light'] {
|
||||||
.main-content {
|
.content-wrap {
|
||||||
@apply bg-theme-foreground;
|
@apply bg-theme-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ const handleButtonClick = (button: PageDataResourceGroup) => {
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="buttonList.length"
|
v-if="buttonList.length"
|
||||||
class="flex flex-wrap justify-center items-center gap-3 md:gap-4"
|
v-motion-stagger
|
||||||
|
class="flex flex-wrap justify-center gap-3 md:gap-4"
|
||||||
>
|
>
|
||||||
<template v-for="(button, index) in buttonList" :key="index">
|
<template v-for="(button, index) in buttonList" :key="index">
|
||||||
<template v-if="button.btn_info?.detail?.btn_type === 'RUN'">
|
<template v-if="button.btn_info?.detail?.btn_type === 'RUN'">
|
||||||
@@ -132,6 +133,7 @@ const handleButtonClick = (button: PageDataResourceGroup) => {
|
|||||||
:platform="button.btn_info?.detail?.market_type"
|
:platform="button.btn_info?.detail?.market_type"
|
||||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||||
|
:use-game-font="button.btn_info?.use_game_font === 1"
|
||||||
@click="handleButtonClick(button)"
|
@click="handleButtonClick(button)"
|
||||||
>
|
>
|
||||||
{{ button.btn_info?.txt_btn_name }}
|
{{ button.btn_info?.txt_btn_name }}
|
||||||
@@ -147,6 +149,8 @@ const handleButtonClick = (button: PageDataResourceGroup) => {
|
|||||||
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
:background-color="getColorCodeFromData(button.btn_info, 'btn')"
|
||||||
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
:text-color="getColorCodeFromData(button.btn_info, 'txt')"
|
||||||
:disabled="button.btn_info?.detail?.btn_type === 'DEACTIVE'"
|
:disabled="button.btn_info?.detail?.btn_type === 'DEACTIVE'"
|
||||||
|
:gradient="true"
|
||||||
|
:use-game-font="button.btn_info?.use_game_font === 1"
|
||||||
@click="handleButtonClick(button)"
|
@click="handleButtonClick(button)"
|
||||||
>
|
>
|
||||||
{{ button.btn_info?.txt_btn_name }}
|
{{ button.btn_info?.txt_btn_name }}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<p class="description">
|
<p v-motion-stagger class="description">
|
||||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h2>
|
<h2 v-motion-stagger>
|
||||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||||
</h2>
|
</h2>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="props.tag">
|
<component :is="props.tag" v-motion-stagger>
|
||||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const modalStore = useModalStore()
|
const modalStore = useModalStore()
|
||||||
|
|
||||||
|
const bgColor = computed(() => {
|
||||||
|
return getColorCodeFromData(props.resourcesData.display, 'none')
|
||||||
|
})
|
||||||
|
|
||||||
// 비디오 플레이 버튼 클릭 핸들러
|
// 비디오 플레이 버튼 클릭 핸들러
|
||||||
const handleVideoPlayClick = () => {
|
const handleVideoPlayClick = () => {
|
||||||
const youtubeUrl = props.resourcesData?.display?.text ?? ''
|
const youtubeUrl = props.resourcesData?.display?.text ?? ''
|
||||||
@@ -18,6 +22,8 @@ const handleVideoPlayClick = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AtomsButtonPlay
|
<AtomsButtonPlay
|
||||||
|
v-motion-stagger
|
||||||
|
:bg-color="bgColor"
|
||||||
:tracking="props.resourcesData.tracking"
|
:tracking="props.resourcesData.tracking"
|
||||||
@click="handleVideoPlayClick"
|
@click="handleVideoPlayClick"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutsHeader />
|
<LayoutsHeader />
|
||||||
<main class="main-promotion relative">
|
<main class="main-promotion relative">
|
||||||
<BlocksButtonHome />
|
|
||||||
<LayoutsEventNavigation />
|
|
||||||
<slot />
|
<slot />
|
||||||
|
<LayoutsEventNavigation />
|
||||||
|
<BlocksButtonHome />
|
||||||
</main>
|
</main>
|
||||||
<LayoutsFooter />
|
<LayoutsFooter />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
const pageDataStore = usePageDataStore()
|
const pageDataStore = usePageDataStore()
|
||||||
const loadingStore = useLoadingStore()
|
const loadingStore = useLoadingStore()
|
||||||
|
|
||||||
const { langCodes } = storeToRefs(gameDataStore)
|
const { langCodes, defaultLangCode } = storeToRefs(gameDataStore)
|
||||||
|
|
||||||
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl
|
||||||
const accessToken = csrGetAccessToken()
|
const accessToken = csrGetAccessToken()
|
||||||
@@ -113,7 +113,19 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
}
|
}
|
||||||
// 91002 (Invalid LangCode): 미지원 언어로 접근
|
// 91002 (Invalid LangCode): 미지원 언어로 접근
|
||||||
if (pageDataResponse?.code === 91002) {
|
if (pageDataResponse?.code === 91002) {
|
||||||
return navigateTo(`/${langCode}/home`)
|
// 페이지별 지원 언어에 현재 langCode가 포함되지 않는 경우
|
||||||
|
// 서비스 기본 언어(게임 기본 언어 또는 ko)로 한 번만 리다이렉트
|
||||||
|
const fallbackLang =
|
||||||
|
defaultLangCode.value ||
|
||||||
|
// defaultLangCode가 없으면 ko 우선, 없으면 첫 번째 언어, 그래도 없으면 ko
|
||||||
|
(langCodes.value?.includes('ko') ? 'ko' : langCodes.value?.[0] || 'ko')
|
||||||
|
|
||||||
|
// 이미 fallback 언어로 접근 중이라면 더 이상 리다이렉트하지 않음 (무한 루프 방지)
|
||||||
|
if (langCode === fallbackLang) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigateTo(`/${fallbackLang}/home`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [TODO]
|
// [TODO]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default defineNuxtPlugin(async nuxtApp => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const url = `${dataResourcesUrl}/multilingual/${commonTranslations}`
|
const url = `${dataResourcesUrl}/multilingual/${commonTranslations}`
|
||||||
const translations = await commonFetch('GET', url)
|
const translations = await commonFetch('GET', url, { loading: false })
|
||||||
|
|
||||||
if (!translations || typeof translations !== 'object') {
|
if (!translations || typeof translations !== 'object') {
|
||||||
return
|
return
|
||||||
|
|||||||
199
layers/plugins/motion-stagger.client.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { animate, stagger } from 'motion-v'
|
||||||
|
import type { DOMKeyframesDefinition, AnimationOptions } from 'motion-v'
|
||||||
|
|
||||||
|
// 상수 정의
|
||||||
|
const ANIMATION_CONFIG = {
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
translateY: '40px',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
stagger: 0.1,
|
||||||
|
duration: 0.6,
|
||||||
|
easing: 'ease',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const INTERSECTION_CONFIG = {
|
||||||
|
threshold: 0.2,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const RETRY_DELAYS = [0, 100, 300] as const
|
||||||
|
|
||||||
|
// 타입 정의
|
||||||
|
type SectionElement = HTMLElement & { tagName: 'SECTION' }
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(nuxtApp => {
|
||||||
|
// 전역 상태 관리
|
||||||
|
const animatedItems = new Set<HTMLElement>()
|
||||||
|
const sectionObservers = new Map<SectionElement, IntersectionObserver>()
|
||||||
|
const sectionItems = new Map<SectionElement, Set<HTMLElement>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션의 motion-item들을 애니메이션
|
||||||
|
*/
|
||||||
|
const animateSectionItems = (section: SectionElement): void => {
|
||||||
|
const items = sectionItems.get(section)
|
||||||
|
if (!items?.size) return
|
||||||
|
|
||||||
|
const newItems = Array.from(items).filter(item => !animatedItems.has(item))
|
||||||
|
if (!newItems.length) return
|
||||||
|
|
||||||
|
// 애니메이션 실행
|
||||||
|
newItems.forEach(item => animatedItems.add(item))
|
||||||
|
|
||||||
|
animate(
|
||||||
|
newItems,
|
||||||
|
ANIMATION_CONFIG.target as DOMKeyframesDefinition,
|
||||||
|
{
|
||||||
|
delay: stagger(ANIMATION_CONFIG.stagger),
|
||||||
|
duration: ANIMATION_CONFIG.duration,
|
||||||
|
easing: ANIMATION_CONFIG.easing,
|
||||||
|
} as AnimationOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션이 viewport에 있는지 확인
|
||||||
|
*/
|
||||||
|
const isSectionInViewport = (section: SectionElement): boolean => {
|
||||||
|
const rect = section.getBoundingClientRect()
|
||||||
|
const windowHeight =
|
||||||
|
window.innerHeight || document.documentElement.clientHeight
|
||||||
|
const visibleHeight =
|
||||||
|
Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)
|
||||||
|
const sectionHeight = rect.height
|
||||||
|
|
||||||
|
return visibleHeight / sectionHeight >= INTERSECTION_CONFIG.threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션에 IntersectionObserver 등록
|
||||||
|
*/
|
||||||
|
const observeSection = (section: SectionElement): void => {
|
||||||
|
if (sectionObservers.has(section)) return
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
animateSectionItems(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, INTERSECTION_CONFIG)
|
||||||
|
|
||||||
|
observer.observe(section)
|
||||||
|
sectionObservers.set(section, observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션에 아이템 등록 및 초기 애니메이션 체크
|
||||||
|
*/
|
||||||
|
const registerItem = (el: HTMLElement, section: SectionElement): void => {
|
||||||
|
// 섹션의 아이템 목록에 추가
|
||||||
|
if (!sectionItems.has(section)) {
|
||||||
|
sectionItems.set(section, new Set())
|
||||||
|
observeSection(section)
|
||||||
|
}
|
||||||
|
sectionItems.get(section)!.add(el)
|
||||||
|
|
||||||
|
// 이미 viewport에 있는 경우 즉시 체크 (여러 번 체크)
|
||||||
|
if (!isSectionInViewport(section)) return
|
||||||
|
|
||||||
|
// 즉시 실행 및 재확인
|
||||||
|
for (const delay of RETRY_DELAYS) {
|
||||||
|
if (delay === 0) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (isSectionInViewport(section)) {
|
||||||
|
animateSectionItems(section)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isSectionInViewport(section)) {
|
||||||
|
animateSectionItems(section)
|
||||||
|
}
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 섹션에서 아이템 제거 및 정리
|
||||||
|
*/
|
||||||
|
const unregisterItem = (el: HTMLElement, section: SectionElement): void => {
|
||||||
|
const items = sectionItems.get(section)
|
||||||
|
if (!items) return
|
||||||
|
|
||||||
|
items.delete(el)
|
||||||
|
animatedItems.delete(el)
|
||||||
|
|
||||||
|
// 섹션에 아이템이 없으면 observer 정리
|
||||||
|
if (items.size === 0) {
|
||||||
|
const observer = sectionObservers.get(section)
|
||||||
|
observer?.disconnect()
|
||||||
|
sectionObservers.delete(section)
|
||||||
|
sectionItems.delete(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 초기 스타일 설정
|
||||||
|
*/
|
||||||
|
const setInitialStyles = (el: HTMLElement): void => {
|
||||||
|
el.style.opacity = ANIMATION_CONFIG.initial.opacity.toString()
|
||||||
|
el.style.transform = `translateY(${ANIMATION_CONFIG.initial.translateY})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 가장 가까운 section 찾기
|
||||||
|
*/
|
||||||
|
const findSection = (el: HTMLElement): SectionElement | null => {
|
||||||
|
const section = el.closest('section')
|
||||||
|
if (!section) {
|
||||||
|
if (import.meta.dev) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[motion-stagger] No section found for element:', el)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return section as SectionElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// v-motion-stagger 디렉티브
|
||||||
|
nuxtApp.vueApp.directive('motion-stagger', {
|
||||||
|
mounted(el: HTMLElement) {
|
||||||
|
setInitialStyles(el)
|
||||||
|
|
||||||
|
const section = findSection(el)
|
||||||
|
if (!section) return
|
||||||
|
|
||||||
|
registerItem(el, section)
|
||||||
|
},
|
||||||
|
|
||||||
|
unmounted(el: HTMLElement) {
|
||||||
|
const section = el.closest('section') as SectionElement | null
|
||||||
|
if (!section) return
|
||||||
|
|
||||||
|
unregisterItem(el, section)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 페이지 이동 시 정리
|
||||||
|
if (import.meta.client) {
|
||||||
|
nuxtApp.hook('page:finish', () => {
|
||||||
|
const currentSections = new Set<SectionElement>(
|
||||||
|
Array.from(document.querySelectorAll('section')) as SectionElement[]
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const [section, observer] of sectionObservers.entries()) {
|
||||||
|
if (!currentSections.has(section)) {
|
||||||
|
observer.disconnect()
|
||||||
|
sectionObservers.delete(section)
|
||||||
|
sectionItems.delete(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -163,7 +163,7 @@ export default defineEventHandler(async event => {
|
|||||||
event.context.gameDomain = gameDomain
|
event.context.gameDomain = gameDomain
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('gameData load error:', error)
|
console.error('[API Error] gameData load error:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameDataResponse?.code === 0 && 'value' in gameDataResponse) {
|
if (gameDataResponse?.code === 0 && 'value' in gameDataResponse) {
|
||||||
@@ -344,6 +344,10 @@ export default defineEventHandler(async event => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ### 에러 응답 처리 -------------------------------------------------------------
|
// ### 에러 응답 처리 -------------------------------------------------------------
|
||||||
|
// API 에러 코드를 명확하게 로깅하여 타입 에러와 구분
|
||||||
|
const apiErrorCode = gameDataResponse?.code
|
||||||
|
const apiErrorMessage = gameDataResponse?.message
|
||||||
|
|
||||||
// 언어 코드 추출 시도
|
// 언어 코드 추출 시도
|
||||||
let errorLocale = 'ko' // 기본값
|
let errorLocale = 'ko' // 기본값
|
||||||
try {
|
try {
|
||||||
@@ -359,7 +363,7 @@ export default defineEventHandler(async event => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 91001 에러인 경우 바로 리다이렉트
|
// 91001 에러인 경우 바로 리다이렉트
|
||||||
if (gameDataResponse?.code === 91001) {
|
if (apiErrorCode === 91001) {
|
||||||
const errorPath = `/${errorLocale}/error`
|
const errorPath = `/${errorLocale}/error`
|
||||||
event.node.res.statusCode = 302
|
event.node.res.statusCode = 302
|
||||||
event.node.res.setHeader('Location', errorPath)
|
event.node.res.setHeader('Location', errorPath)
|
||||||
@@ -369,8 +373,8 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
// 다른 에러는 기존대로 throw
|
// 다른 에러는 기존대로 throw
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: gameDataResponse?.code || 500,
|
statusCode: apiErrorCode || 500,
|
||||||
statusMessage: gameDataResponse?.message,
|
statusMessage: apiErrorMessage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const useGameDataStore = defineStore('gameData', () => {
|
|||||||
snsJson: null as GameDataValue['sns_json'] | null,
|
snsJson: null as GameDataValue['sns_json'] | null,
|
||||||
urlJson: null as GameDataValue['url_json'] | null,
|
urlJson: null as GameDataValue['url_json'] | null,
|
||||||
marketJson: null as GameDataValue['market_json'] | null,
|
marketJson: null as GameDataValue['market_json'] | null,
|
||||||
fontFamily: null as GameDataValue['game_font']['font_family'] | null,
|
fontFamily: null as GameDataValue['game_font_json']['font_family'] | null,
|
||||||
gnb: null as GameDataValue['gnb'] | null,
|
gnb: null as GameDataValue['gnb'] | null,
|
||||||
eventBanner: null as GameDataValue['event_banner'] | null,
|
eventBanner: null as GameDataValue['event_banner'] | null,
|
||||||
})
|
})
|
||||||
@@ -42,7 +42,7 @@ export const useGameDataStore = defineStore('gameData', () => {
|
|||||||
state.snsJson = data?.sns_json
|
state.snsJson = data?.sns_json
|
||||||
state.urlJson = data?.url_json
|
state.urlJson = data?.url_json
|
||||||
state.marketJson = data?.market_json
|
state.marketJson = data?.market_json
|
||||||
state.fontFamily = data?.game_font?.font_family
|
state.fontFamily = data?.game_font_json?.font_family
|
||||||
state.gnb = data?.gnb
|
state.gnb = data?.gnb
|
||||||
state.eventBanner = data?.event_banner
|
state.eventBanner = data?.event_banner
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export const useModalStore = defineStore('modalStore', () => {
|
|||||||
// content ------------------
|
// content ------------------
|
||||||
const content = {
|
const content = {
|
||||||
storeIsOpen: ref(false),
|
storeIsOpen: ref(false),
|
||||||
|
storeIsShowDimmed: ref(true),
|
||||||
storeModalName: ref(''),
|
storeModalName: ref(''),
|
||||||
storeIsOutsideClose: ref(false),
|
storeIsOutsideClose: ref(false),
|
||||||
storeContentTitle: ref(''),
|
storeContentTitle: ref(''),
|
||||||
@@ -140,12 +141,14 @@ export const useModalStore = defineStore('modalStore', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOpenContent = ({
|
const handleOpenContent = ({
|
||||||
|
isShowDimmed = true,
|
||||||
isOutsideClose = false,
|
isOutsideClose = false,
|
||||||
modalName = '',
|
modalName = '',
|
||||||
contentTitle,
|
contentTitle,
|
||||||
tabInfo,
|
tabInfo,
|
||||||
tabActiveIndex = 0,
|
tabActiveIndex = 0,
|
||||||
}: ContentParams) => {
|
}: ContentParams) => {
|
||||||
|
content.storeIsShowDimmed.value = isShowDimmed
|
||||||
content.storeIsOpen.value = true
|
content.storeIsOpen.value = true
|
||||||
content.storeModalName.value = modalName
|
content.storeModalName.value = modalName
|
||||||
content.storeIsOutsideClose.value = isOutsideClose
|
content.storeIsOutsideClose.value = isOutsideClose
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
|||||||
<img
|
<img
|
||||||
:src="
|
:src="
|
||||||
formatPathHost(
|
formatPathHost(
|
||||||
`/images/common/grades_driver/Type-${driver.driverCode}.svg`,
|
`/images/common/download_driver/Type-${driver.driverCode}.svg`,
|
||||||
{ imageType: 'common' }
|
{ imageType: 'common' }
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ const handlePreregistClick = () => {
|
|||||||
:resources-data="preSubTitleData"
|
:resources-data="preSubTitleData"
|
||||||
class="title-sm mt-2"
|
class="title-sm mt-2"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col gap-4 mt-8 sm:flex-row">
|
<div v-motion-stagger class="flex flex-col gap-4 mt-8 sm:flex-row">
|
||||||
<div
|
<div
|
||||||
v-if="preImgPreregistdData"
|
v-if="preImgPreregistdData"
|
||||||
class="max-w-[336px] md:max-w-[446px]"
|
class="max-w-[336px] md:max-w-[446px]"
|
||||||
@@ -295,7 +295,10 @@ const handlePreregistClick = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5">
|
<div
|
||||||
|
v-motion-stagger
|
||||||
|
class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5"
|
||||||
|
>
|
||||||
<BlocksButtonLauncher
|
<BlocksButtonLauncher
|
||||||
type="duplication"
|
type="duplication"
|
||||||
platform="stove"
|
platform="stove"
|
||||||
@@ -355,6 +358,7 @@ const handlePreregistClick = () => {
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="rewardImages.length"
|
v-if="rewardImages.length"
|
||||||
|
v-motion-stagger
|
||||||
class="overflow-hidden w-[calc(100%+40px)] min-h-[228px] mt-6 mx-[-20px] sm:w-[calc(100%+80px)] sm:mx-[-40px] md:w-full md:min-h-[281px] md:mt-8 md:mx-auto"
|
class="overflow-hidden w-[calc(100%+40px)] min-h-[228px] mt-6 mx-[-20px] sm:w-[calc(100%+80px)] sm:mx-[-40px] md:w-full md:min-h-[281px] md:mt-8 md:mx-auto"
|
||||||
>
|
>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
|
|||||||
@@ -50,20 +50,15 @@ const analytics = {
|
|||||||
action_type: 'click',
|
action_type: 'click',
|
||||||
click_sarea: props.pageVerTmplNameEn,
|
click_sarea: props.pageVerTmplNameEn,
|
||||||
} as TrackingObject
|
} as TrackingObject
|
||||||
const arrowsData: PageDataResourceGroups = [
|
const arrowsData: PageDataResourceGroups = ['좌', '우'].map(direction => ({
|
||||||
{
|
display: {
|
||||||
tracking: {
|
color_code: '#383838',
|
||||||
...analytics,
|
|
||||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(좌)',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
tracking: {
|
||||||
tracking: {
|
...analytics,
|
||||||
...analytics,
|
click_item: `1. 컨텐츠 리스트 - 네비게이션(${direction})`,
|
||||||
click_item: '1. 컨텐츠 리스트 - 네비게이션(우)',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
}))
|
||||||
|
|
||||||
const recommendHover = ref(false)
|
const recommendHover = ref(false)
|
||||||
|
|
||||||
@@ -291,21 +286,19 @@ const handleLoadMoreRecent = () => {
|
|||||||
.splide {
|
.splide {
|
||||||
@apply pb-[68px] sm:pb-[0];
|
@apply pb-[68px] sm:pb-[0];
|
||||||
}
|
}
|
||||||
.splide:deep(.splide__arrows) {
|
|
||||||
@apply block;
|
|
||||||
}
|
|
||||||
.splide:deep(.splide__arrows) .splide-arrow {
|
.splide:deep(.splide__arrows) .splide-arrow {
|
||||||
@apply block top-[unset] bottom-[20px] translate-y-0 bg-cover bg-center bg-no-repeat
|
@apply top-[unset] bottom-[20px] translate-y-0 bg-cover bg-center bg-no-repeat
|
||||||
sm:bottom-[24px] md:bottom-[36px] lg:bottom-[60px];
|
sm:bottom-[24px] md:bottom-[36px] lg:bottom-[60px];
|
||||||
}
|
}
|
||||||
.splide:deep(.splide__arrows) .splide__arrow--prev {
|
.splide:deep(.splide__arrows) .splide__arrow--prev {
|
||||||
@apply left-[20px] bg-[image:url('/images/common/btn_system_arrow_prev.png')]
|
@apply left-[20px]
|
||||||
sm:left-[calc(60.3%+21px)]
|
sm:left-[calc(60.3%+21px)]
|
||||||
md:left-[calc(56%+39px)]
|
md:left-[calc(56%+39px)]
|
||||||
lg:left-[790px];
|
lg:left-[790px];
|
||||||
}
|
}
|
||||||
.splide:deep(.splide__arrows) .splide__arrow--next {
|
.splide:deep(.splide__arrows) .splide__arrow--next {
|
||||||
@apply right-[20px] bg-[image:url('/images/common/btn_system_arrow_next.png')]
|
@apply right-[20px]
|
||||||
sm:right-[28px]
|
sm:right-[28px]
|
||||||
md:right-[unset] md:left-[calc(56%+99px)]
|
md:right-[unset] md:left-[calc(56%+99px)]
|
||||||
lg:left-[850px];
|
lg:left-[850px];
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ const getArticleUrl = (articleId: string) => {
|
|||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<WidgetsSlideDefault
|
<WidgetsSlideDefault
|
||||||
v-if="slideLength > 0"
|
v-if="slideLength > 0"
|
||||||
|
v-motion-stagger
|
||||||
v-bind="splideOptions"
|
v-bind="splideOptions"
|
||||||
:slide-item-length="slideLength"
|
:slide-item-length="slideLength"
|
||||||
:arrows-data="arrowsData"
|
:arrows-data="arrowsData"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const buttonListData = computed(() => {
|
|||||||
:resources-data="subTitleData"
|
:resources-data="subTitleData"
|
||||||
class="title-sm max-w-[944px] mt-2"
|
class="title-sm max-w-[944px] mt-2"
|
||||||
/>
|
/>
|
||||||
<div v-if="imgListData" class="img-container">
|
<div v-if="imgListData" v-motion-stagger class="img-container">
|
||||||
<div v-for="(item, index) in imgListData" :key="index" class="img-item">
|
<div v-for="(item, index) in imgListData" :key="index" class="img-item">
|
||||||
<AtomsImg
|
<AtomsImg
|
||||||
v-if="getResourceSrc(item)"
|
v-if="getResourceSrc(item)"
|
||||||
@@ -71,9 +71,9 @@ const buttonListData = computed(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.img-container {
|
.img-container {
|
||||||
@apply flex flex-col items-center justify-center gap-4 box-content mx-auto mt-[32px]
|
@apply flex flex-col items-center justify-center gap-4 box-content mx-auto mt-[32px] w-full
|
||||||
w-[440px]
|
max-w-[440px]
|
||||||
md:w-[944px];
|
md:max-w-[944px];
|
||||||
}
|
}
|
||||||
.img-item {
|
.img-item {
|
||||||
@apply w-full;
|
@apply w-full;
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ const getVideoSrc = (item: PageDataTemplateComponent) => {
|
|||||||
/>
|
/>
|
||||||
<AtomsVideo
|
<AtomsVideo
|
||||||
v-if="hasComponentGroup(item, 'video')"
|
v-if="hasComponentGroup(item, 'video')"
|
||||||
|
v-motion-stagger
|
||||||
:src="getVideoSrc(item)"
|
:src="getVideoSrc(item)"
|
||||||
:play="currentSlideIndex === index"
|
:play="currentSlideIndex === index"
|
||||||
class="aspect-[16/10] w-[258px] mt-8 md:w-[496px] md:mt-10"
|
class="aspect-[16/10] w-[258px] mt-8 md:w-[496px] md:mt-10"
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ const arrowsData = computed(() => {
|
|||||||
const paginationData = computed(() => {
|
const paginationData = computed(() => {
|
||||||
return getComponentGroupAry(props.components, 'pagination')
|
return getComponentGroupAry(props.components, 'pagination')
|
||||||
})
|
})
|
||||||
|
const videoPlayData = computed(() => {
|
||||||
|
return getComponentGroup(props.components, 'videoPlay')
|
||||||
|
})
|
||||||
|
|
||||||
const getVideoPlayTracking = (item: string) => {
|
const getVideoPlayTracking = (item: string) => {
|
||||||
return {
|
return {
|
||||||
@@ -89,6 +92,7 @@ onBeforeUnmount(() => {
|
|||||||
class="title-md max-w-[944px]"
|
class="title-md max-w-[944px]"
|
||||||
/>
|
/>
|
||||||
<WidgetsSlideThumbnail
|
<WidgetsSlideThumbnail
|
||||||
|
v-motion-stagger
|
||||||
:thumbnail-data="slideData"
|
:thumbnail-data="slideData"
|
||||||
:pagination-data="paginationData"
|
:pagination-data="paginationData"
|
||||||
:drag="false"
|
:drag="false"
|
||||||
@@ -115,6 +119,7 @@ onBeforeUnmount(() => {
|
|||||||
<AtomsButtonPlay
|
<AtomsButtonPlay
|
||||||
v-if="playingSlideIndex !== index"
|
v-if="playingSlideIndex !== index"
|
||||||
class="btn-play"
|
class="btn-play"
|
||||||
|
:bg-color="getColorCodeFromData(videoPlayData?.display, 'none')"
|
||||||
:tracking="getVideoPlayTracking(item?.group_label)"
|
:tracking="getVideoPlayTracking(item?.group_label)"
|
||||||
@click="handleVideoClick(index)"
|
@click="handleVideoClick(index)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
|||||||
/>
|
/>
|
||||||
<WidgetsSlideCenterFocus
|
<WidgetsSlideCenterFocus
|
||||||
v-if="slideData"
|
v-if="slideData"
|
||||||
|
v-motion-stagger
|
||||||
:slide-item-size="slideItemSize"
|
:slide-item-size="slideItemSize"
|
||||||
:slide-item-length="slideData?.length"
|
:slide-item-length="slideData?.length"
|
||||||
:arrows-data="arrowsData"
|
:arrows-data="arrowsData"
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const handleSplideMove = (
|
|||||||
/>
|
/>
|
||||||
<WidgetsSlideCenterHighlight
|
<WidgetsSlideCenterHighlight
|
||||||
v-if="slideData"
|
v-if="slideData"
|
||||||
|
v-motion-stagger
|
||||||
:slide-item-size="slideItemSize"
|
:slide-item-size="slideItemSize"
|
||||||
:slide-item-length="slideData?.length"
|
:slide-item-length="slideData?.length"
|
||||||
:arrows-data="arrowsData"
|
:arrows-data="arrowsData"
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ const slideItemSize = {
|
|||||||
<WidgetsVideoPlay v-if="videoPlayData" :resources-data="videoPlayData" />
|
<WidgetsVideoPlay v-if="videoPlayData" :resources-data="videoPlayData" />
|
||||||
<WidgetsSlideCenterHighlight
|
<WidgetsSlideCenterHighlight
|
||||||
v-if="slideData && slideData.length > 0"
|
v-if="slideData && slideData.length > 0"
|
||||||
|
v-motion-stagger
|
||||||
:slide-item-size="slideItemSize"
|
:slide-item-size="slideItemSize"
|
||||||
:slide-item-length="slideData.length"
|
:slide-item-length="slideData.length"
|
||||||
:arrows-data="arrowsData"
|
:arrows-data="arrowsData"
|
||||||
|
|||||||
@@ -32,24 +32,24 @@ export interface GameDataValue {
|
|||||||
comm_sns_bg_color_json: {
|
comm_sns_bg_color_json: {
|
||||||
display: ColorObject
|
display: ColorObject
|
||||||
}
|
}
|
||||||
comm_multilang_filename: string
|
comm_multilang_filename?: string
|
||||||
footer_dev_ci_img_yn: boolean
|
footer_dev_ci_img_yn: boolean
|
||||||
footer_dev_ci_img_path: string
|
footer_dev_ci_img_path: string
|
||||||
default_lang_code?: string
|
default_lang_code?: string
|
||||||
game_font: GameDataGameFont
|
game_font_json?: GameDataGameFont
|
||||||
globals: GameDataGlobal[]
|
globals: GameDataGlobal[]
|
||||||
gnb: GameDataGnb
|
gnb: GameDataGnb
|
||||||
intro: GameDataIntro
|
intro: GameDataIntro
|
||||||
inspection: Record<string, any>
|
inspection: Record<string, any>
|
||||||
stove_gnb_json: GameDataStoveGnb
|
stove_gnb_json: GameDataStoveGnb
|
||||||
favicon_json: GameDataImg
|
favicon_json: GameDataImg
|
||||||
meta_tag_json: GameDataMetaTag
|
meta_tag_json?: GameDataMetaTag
|
||||||
sns_json: GameDataSns
|
sns_json?: GameDataSns
|
||||||
url_json: Record<string, string>
|
url_json?: Record<string, string>
|
||||||
footer_json: string // JSON 문자열로 변경
|
footer_json: string // JSON 문자열로 변경
|
||||||
img_json: GameDataImg
|
img_json?: GameDataImg
|
||||||
market_json: Record<string, { url: string }>
|
market_json: Record<string, { url: string }>
|
||||||
event_banner: GameDataEventBanner
|
event_banner?: GameDataEventBanner
|
||||||
os_type: OsType
|
os_type: OsType
|
||||||
platform_type: PlatformType
|
platform_type: PlatformType
|
||||||
}
|
}
|
||||||
@@ -119,6 +119,7 @@ export interface GameDataResourceGroupBtnInfo extends ColorObject {
|
|||||||
disabled: boolean
|
disabled: boolean
|
||||||
txt_btn_name: string
|
txt_btn_name: string
|
||||||
detail: Record<string, any>
|
detail: Record<string, any>
|
||||||
|
use_game_font: 0 | 1 // 0: 사용하지 않음, 1: 사용함
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameDataResourceGroup {
|
export interface GameDataResourceGroup {
|
||||||
@@ -162,6 +163,7 @@ export interface GameDataGnb {
|
|||||||
lang_codes: string // JSON 문자열로 변경
|
lang_codes: string // JSON 문자열로 변경
|
||||||
buttons: GameDataButton[]
|
buttons: GameDataButton[]
|
||||||
menus: GameDataMenuChildren
|
menus: GameDataMenuChildren
|
||||||
|
use_game_font: 0 | 1 // 0: 사용하지 않음, 1: 사용함
|
||||||
}
|
}
|
||||||
|
|
||||||
// 인트로 타입
|
// 인트로 타입
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export interface OperateResourcesResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface getOperateResourcesParams {
|
export interface getOperateResourcesParams {
|
||||||
pageSeq: string
|
pageSeq: number
|
||||||
pageVer: string
|
pageVer: number
|
||||||
pageVerTmplSeq: number
|
pageVerTmplSeq: number
|
||||||
langCode: string
|
langCode: string
|
||||||
q?: string
|
q?: string
|
||||||
|
|||||||
@@ -25,20 +25,19 @@ export interface PageDataResponse {
|
|||||||
|
|
||||||
// API 응답의 value 객체 타입
|
// API 응답의 value 객체 타입
|
||||||
export interface PageDataValue {
|
export interface PageDataValue {
|
||||||
page_seq: string
|
page_seq: number
|
||||||
page_type: number
|
page_type: number
|
||||||
page_name: string
|
page_name: string
|
||||||
page_name_en: string
|
page_name_en: string
|
||||||
page_ver: string
|
page_ver: number
|
||||||
is_login_required: number
|
is_login_required: number
|
||||||
meta_tag_type: number
|
meta_tag_type: number
|
||||||
fit_page_height: boolean
|
|
||||||
use_top_btn: boolean
|
use_top_btn: boolean
|
||||||
use_sns_btn: boolean
|
use_sns_btn: boolean
|
||||||
use_lnb: boolean
|
use_lnb: boolean
|
||||||
lnb_text_color_code_active: string
|
lnb_text_color_code_active?: string
|
||||||
lnb_text_color_code_deactive: string
|
lnb_text_color_code_deactive?: string
|
||||||
lnb_menus: Record<string, PageDataLnbMenu>
|
lnb_menus?: Record<string, PageDataLnbMenu>
|
||||||
meta_tag_json: PageDataMetaTag
|
meta_tag_json: PageDataMetaTag
|
||||||
templates: Record<string, PageDataTemplate>
|
templates: Record<string, PageDataTemplate>
|
||||||
}
|
}
|
||||||
@@ -84,16 +83,16 @@ export interface PageDataResourceGroupResPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PageDataResourceGroupBtnInfo extends ColorObject {
|
export interface PageDataResourceGroupBtnInfo extends ColorObject {
|
||||||
disabled: boolean
|
|
||||||
txt_btn_name: string
|
txt_btn_name: string
|
||||||
detail: Record<string, any>
|
detail: Record<string, any>
|
||||||
use_game_font: 0 | 1
|
disabled?: boolean
|
||||||
|
use_game_font: 0 | 1 // 0: 사용하지 않음, 1: 사용함
|
||||||
}
|
}
|
||||||
|
|
||||||
// 리소스 그룹 타입
|
// 리소스 그룹 타입
|
||||||
export interface PageDataResourceGroupDisplay extends ColorObject {
|
export interface PageDataResourceGroupDisplay extends ColorObject {
|
||||||
text: string
|
text?: string
|
||||||
use_game_font: 0 | 1
|
use_game_font?: 0 | 1 // 0: 사용하지 않음, 1: 사용함
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageDataResourceGroup {
|
export interface PageDataResourceGroup {
|
||||||
@@ -127,7 +126,6 @@ export type PageDataTemplateComponents =
|
|||||||
| PageDataTemplateComponent // 단일 컴포넌트 패턴
|
| PageDataTemplateComponent // 단일 컴포넌트 패턴
|
||||||
| {
|
| {
|
||||||
group_sets: PageDataTemplateComponentSet[]
|
group_sets: PageDataTemplateComponentSet[]
|
||||||
arrow: PageDataArrowComponent
|
|
||||||
} // 그룹 세트 패턴
|
} // 그룹 세트 패턴
|
||||||
|
|
||||||
// 템플릿 타입
|
// 템플릿 타입
|
||||||
@@ -172,8 +170,3 @@ export interface PageDataApiResult {
|
|||||||
data: PageDataResponse | null
|
data: PageDataResponse | null
|
||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrow 컴포넌트 타입
|
|
||||||
export type PageDataArrowComponent = PageDataTemplateComponent & {
|
|
||||||
groups: PageDataResourceGroups
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface ToastParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentParams {
|
export interface ContentParams {
|
||||||
|
isShowDimmed?: boolean
|
||||||
isOutsideClose?: boolean
|
isOutsideClose?: boolean
|
||||||
modalName?: string
|
modalName?: string
|
||||||
contentTitle: string
|
contentTitle: string
|
||||||
|
|||||||
@@ -55,8 +55,13 @@ const buildRequestOptions = (
|
|||||||
let callerDetail = ''
|
let callerDetail = ''
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
const gameDataStore = useGameDataStore()
|
try {
|
||||||
stoveGameId = gameDataStore.gameData?.game_id || ''
|
const gameDataStore = useGameDataStore()
|
||||||
|
stoveGameId = gameDataStore.gameData?.game_id || ''
|
||||||
|
} catch {
|
||||||
|
stoveGameId = ''
|
||||||
|
}
|
||||||
|
|
||||||
callerDetail = useCookie('sgs_da_uuid').value || ''
|
callerDetail = useCookie('sgs_da_uuid').value || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +121,12 @@ export const commonFetch = async (
|
|||||||
options: FetchOptions = {}
|
options: FetchOptions = {}
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
let loadingStore = null
|
let loadingStore = null
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
loadingStore = useLoadingStore()
|
try {
|
||||||
|
loadingStore = useLoadingStore()
|
||||||
|
} catch {
|
||||||
|
loadingStore = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startLoading(loadingStore, options.loading)
|
startLoading(loadingStore, options.loading)
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const ssrGetFinalLocale = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 서비스 기본 언어
|
// 4. 서비스 기본 언어
|
||||||
finalLocale = defaultLocale
|
finalLocale = defaultLocale
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
finalLocale = defaultLocale
|
finalLocale = defaultLocale
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const getColorCode = ({
|
|||||||
* @returns 색상 값
|
* @returns 색상 값
|
||||||
*/
|
*/
|
||||||
export const getColorCodeFromData = (
|
export const getColorCodeFromData = (
|
||||||
data: ColorObject,
|
data: ColorObject | undefined,
|
||||||
type: 'btn' | 'txt' | 'none' = 'txt'
|
type: 'btn' | 'txt' | 'none' = 'txt'
|
||||||
) => {
|
) => {
|
||||||
const suffixMap: Record<'btn' | 'txt' | 'none', string> = {
|
const suffixMap: Record<'btn' | 'txt' | 'none', string> = {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default defineNuxtConfig({
|
|||||||
'@nuxtjs/tailwindcss',
|
'@nuxtjs/tailwindcss',
|
||||||
'nuxt-gtag',
|
'nuxt-gtag',
|
||||||
'@nuxtjs/device',
|
'@nuxtjs/device',
|
||||||
|
'motion-v/nuxt',
|
||||||
],
|
],
|
||||||
extends: ['./layers'],
|
extends: ['./layers'],
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"@amplitude/analytics-browser": "^2.24.0",
|
"@amplitude/analytics-browser": "^2.24.0",
|
||||||
"@amplitude/analytics-node": "^1.5.9",
|
"@amplitude/analytics-node": "^1.5.9",
|
||||||
"@nuxtjs/device": "^3.2.4",
|
"@nuxtjs/device": "^3.2.4",
|
||||||
"@nuxtjs/i18n": "^10.0.6",
|
"@nuxtjs/i18n": "^9.0.0",
|
||||||
"@pinia/nuxt": "^0.6.1",
|
"@pinia/nuxt": "^0.6.1",
|
||||||
"@seed-next/date": "^0.0.0",
|
"@seed-next/date": "^0.0.0",
|
||||||
"@splidejs/splide": "^4.1.4",
|
"@splidejs/splide": "^4.1.4",
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
"@vueuse/core": "^13.6.0",
|
"@vueuse/core": "^13.6.0",
|
||||||
"@vueuse/nuxt": "^13.6.0",
|
"@vueuse/nuxt": "^13.6.0",
|
||||||
"h3": "^1.15.4",
|
"h3": "^1.15.4",
|
||||||
|
"motion-v": "^1.8.1",
|
||||||
"nuxt": "^4.0.3",
|
"nuxt": "^4.0.3",
|
||||||
"nuxt-gtag": "^4.0.0",
|
"nuxt-gtag": "^4.0.0",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
|
|||||||
661
pnpm-lock.yaml
generated
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -11,7 +11,6 @@ export default {
|
|||||||
md: '1024px', // PC: 1024px ~ 1439px
|
md: '1024px', // PC: 1024px ~ 1439px
|
||||||
lg: '1440px', // Large PC: 1440px+
|
lg: '1440px', // Large PC: 1440px+
|
||||||
},
|
},
|
||||||
spacing: {},
|
|
||||||
colors: {
|
colors: {
|
||||||
'theme-foreground': 'var(--foreground)',
|
'theme-foreground': 'var(--foreground)',
|
||||||
'theme-foreground-10': 'var(--foreground-10)',
|
'theme-foreground-10': 'var(--foreground-10)',
|
||||||
@@ -26,6 +25,9 @@ export default {
|
|||||||
'theme-foreground-gray-750': 'var(--foreground-gray-750)',
|
'theme-foreground-gray-750': 'var(--foreground-gray-750)',
|
||||||
'theme-foreground-gray-500': 'var(--foreground-gray-500)',
|
'theme-foreground-gray-500': 'var(--foreground-gray-500)',
|
||||||
},
|
},
|
||||||
|
transitionTimingFunction: {
|
||||||
|
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies Config
|
} satisfies Config
|
||||||
|
|||||||