feat. 공통 이미지 아이콘 타입으로 변경

This commit is contained in:
clkim
2026-01-09 13:21:29 +09:00
parent 352d76a61c
commit 81350cec51
23 changed files with 165 additions and 131 deletions

View File

@@ -1,14 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useNuxtApp } from 'nuxt/app' import { useNuxtApp } from 'nuxt/app'
import type { import type {
GameDataFavicon,
GameDataMetaTag, GameDataMetaTag,
GameDataValue, GameDataValue,
GameDataFavicon,
GameDataKeyColors,
} from '#layers/types/api/gameData' } from '#layers/types/api/gameData'
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const { locale } = useI18n() const { locale } = useI18n()
const gameDataStore = useGameDataStore() const gameDataStore = useGameDataStore()
const modalStore = useModalStore() const modalStore = useModalStore()
const scrollStore = useScrollStore() const scrollStore = useScrollStore()
@@ -18,81 +18,109 @@ const { gameData } = storeToRefs(gameDataStore)
const { confirm, alert } = modalStore const { confirm, alert } = modalStore
const { scrollGnbPosition } = storeToRefs(scrollStore) const { scrollGnbPosition } = storeToRefs(scrollStore)
// 통합 메타데이터 설정 // favicon 링크 생성 헬퍼
const setupAllMetaData = (data: GameDataValue) => { const createStyleLinks = (faviconJson: GameDataFavicon) => {
const meta = data.meta_tag_json ?? ({} as GameDataMetaTag) const links = []
const faviconPath = data.favicon_json ?? ({} as GameDataFavicon) const iconUrl = faviconJson[0]
const theme = data.design_theme === 1 ? 'light' : 'dark' const appleTouchIconUrl = faviconJson[1]
const pngIconUrl = faviconJson[2]
// 파비콘 링크 생성 if (iconUrl) {
const faviconLinks = [ links.push({
{
rel: 'icon', rel: 'icon',
type: 'image/x-icon', type: 'image/x-icon',
href: formatPathHost(faviconPath[0]), href: formatPathHost(iconUrl),
}, })
{ }
if (appleTouchIconUrl) {
links.push({
rel: 'apple-touch-icon', rel: 'apple-touch-icon',
href: formatPathHost(faviconPath[1]), href: formatPathHost(appleTouchIconUrl),
}, })
{ }
if (pngIconUrl) {
links.push({
rel: 'icon', rel: 'icon',
type: 'image/png', type: 'image/png',
href: formatPathHost(faviconPath[2]), href: formatPathHost(pngIconUrl),
})
}
return links
}
// 메타 태그 생성 헬퍼
const createMetaTags = (metaTag: Partial<GameDataMetaTag> = {}) => {
const metaList = [
{ name: 'description', content: metaTag.page_desc },
{ property: 'og:title', content: metaTag.og_title },
{ property: 'og:description', content: metaTag.og_desc },
{
property: 'og:image',
content: formatPathHost(metaTag.og_image),
},
{ name: 'twitter:title', content: metaTag.x_title },
{ name: 'twitter:description', content: metaTag.x_desc },
{
name: 'twitter:image',
content: formatPathHost(metaTag.x_image),
}, },
] ]
// 색상 CSS 변수 생성 // content가 유효한 메타 태그만 필터링
const cssColorVariables = Object.entries(data.key_color_json ?? {}) return metaList.filter(
meta => meta.content && String(meta.content).trim() !== ''
)
}
// CSS 변수 생성 헬퍼
const createCssVariables = (
keyColorJson: GameDataKeyColors
// commImgJson: GameDataCommImg
) => {
const colorVariables = Object.entries(keyColorJson)
.filter(([key, value]) => key && value != null)
.map(([key, value]) => `--${key}: ${value};`) .map(([key, value]) => `--${key}: ${value};`)
.join('\n ') .join('\n ')
const commImgVariables = return `:root {${colorVariables}}`
data.comm_img_json?.groups }
?.map(
({ img_name, img_path }) =>
`--${img_name}: url(${formatPathHost(img_path?.comm ?? '')});`
)
.join('\n ') ?? ''
const cssContent = ` // 게임 헤드 설정
:root { const setupGameHead = (data: GameDataValue) => {
${cssColorVariables} try {
${commImgVariables} const metaTag: Partial<GameDataMetaTag> = data.meta_tag_json ?? {}
} const designTheme = data.design_theme === 1 ? 'light' : 'dark'
` const styleLinks = createStyleLinks(data.favicon_json)
const cssVariables = createCssVariables(data.key_color_json)
useHead({ useHead({
title: meta?.page_title ?? '', title: metaTag.page_title ?? '',
meta: [ meta: createMetaTags(metaTag),
{ name: 'description', content: meta.page_desc }, htmlAttrs: {
{ property: 'og:title', content: meta.og_title }, 'data-game': data.game_name ?? '',
{ property: 'og:description', content: meta.og_desc }, 'data-theme': designTheme,
{ property: 'og:image', content: formatPathHost(meta.og_image) }, lang: locale.value ?? data.default_lang_code ?? 'ko',
{ name: 'twitter:title', content: meta.x_title },
{ name: 'twitter:description', content: meta.x_desc },
{ name: 'twitter:image', content: formatPathHost(meta.x_image) },
],
htmlAttrs: {
'data-game': data.game_name || '',
'data-theme': theme,
lang: locale.value ?? data.default_lang_code,
},
link: faviconLinks,
style: [
{
innerHTML: cssContent,
id: 'game-css-variables',
}, },
], link: styleLinks,
}) style: [
{
innerHTML: cssVariables,
id: 'game-css-variables',
},
],
})
} catch (error) {
// eslint-disable-next-line no-console
console.error('[setupGameHead] Failed to setup game head:', error)
}
} }
if (import.meta.server) { if (import.meta.server) {
const gameData = nuxtApp.ssrContext?.event?.context?.gameData const gameData = nuxtApp.ssrContext?.event?.context?.gameData
if (gameData) { if (gameData) {
setGameData(gameData) setGameData(gameData)
setupAllMetaData(gameData) setupGameHead(gameData)
} }
} }

View File

@@ -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;
}
} }

View File

@@ -17,13 +17,14 @@ const handlePlayClick = () => {
<template> <template>
<button class="btn-play" @click="handlePlayClick"> <button class="btn-play" @click="handlePlayClick">
<AtomsIconsArrowRightFill />
<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 w-[60px] h-[60px] bg-[image:var(--video-play)] bg-cover bg-center bg-no-repeat md:w-[80px] md:h-[80px]
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;
} }

View File

@@ -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' : ''"
/> />

View 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>

View 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>

View File

@@ -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>

View File

@@ -39,10 +39,10 @@ const PLATFORM_ICON_MAP: Record<Platform, string> = {
} 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(() => {

View File

@@ -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>

View File

@@ -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"

View File

@@ -24,11 +24,15 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
sr-only="Previous" sr-only="Previous"
class="splide-arrow splide__arrow--prev" class="splide-arrow splide__arrow--prev"
@click="handleArrowClick('prev')" @click="handleArrowClick('prev')"
/> >
<AtomsIconsArrowRightLine />
</AtomsButtonCircle>
<AtomsButtonCircle <AtomsButtonCircle
sr-only="Next" sr-only="Next"
class="splide-arrow splide__arrow--next" class="splide-arrow splide__arrow--next"
@click="handleArrowClick('next')" @click="handleArrowClick('next')"
/> >
<AtomsIconsArrowRightLine />
</AtomsButtonCircle>
</div> </div>
</template> </template>

View File

@@ -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' }
) )
" "

View File

@@ -29,7 +29,6 @@ export interface GameDataValue {
design_theme: number design_theme: number
lang_codes: string[] lang_codes: string[]
key_color_json: GameDataKeyColors key_color_json: GameDataKeyColors
use_game_font: boolean
comm_sns_bg_color_json: { comm_sns_bg_color_json: {
display: ColorObject display: ColorObject
} }
@@ -48,7 +47,6 @@ export interface GameDataValue {
sns_json: GameDataSns sns_json: GameDataSns
url_json: Record<string, { url: string }> url_json: Record<string, { url: string }>
footer_json: string // JSON 문자열로 변경 footer_json: string // JSON 문자열로 변경
comm_img_json: GameDataCommImg
market_json: Record<string, { url: string }> market_json: Record<string, { url: string }>
event_banner: GameDataEventBanner event_banner: GameDataEventBanner
os_type: OsType os_type: OsType
@@ -111,20 +109,6 @@ export interface GameDataSns {
tiktok?: GameDataSnsItem tiktok?: GameDataSnsItem
} }
// 공통 이미지 그룹 타입
export interface GameDataCommImgGroup {
img_name: string
img_path: {
comm: string
}
group_label: string
}
// 공통 이미지 타입
export interface GameDataCommImg {
groups: GameDataCommImgGroup[]
}
// Global 설정 타입 // Global 설정 타입
export interface GameDataGlobal { export interface GameDataGlobal {
system_font: string // JSON 문자열로 변경 system_font: string // JSON 문자열로 변경

View File

@@ -27,20 +27,6 @@ export default defineNuxtConfig({
// onload: "this.onload=null;this.rel='stylesheet'", // onload: "this.onload=null;this.rel='stylesheet'",
// }, // },
], ],
noscript: [
// JavaScript 비활성화 시 폰트 CSS 로드
{
innerHTML:
'<link rel="stylesheet" href="https://static-pubcomm.onstove.com/live/common/assets/fonts/Pretendard/pretendard.css">',
},
{
innerHTML:
'<link rel="stylesheet" href="https://static-pubcomm.onstove.com/live/common/assets/fonts/Pretendard/pretendard-jp.css">',
},
// {
// innerHTML: '<link rel="stylesheet" href="https://static-cdn.onstove.com/0.0.4/font-icon/StoveFont-Icon.css">',
// },
],
script: [ script: [
{ {
type: 'text/javascript', type: 'text/javascript',

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB