fix. 공통 스타일 분리

This commit is contained in:
clkim
2025-09-25 13:19:24 +09:00
parent 7aa2ee7f36
commit ab58a98acf
11 changed files with 70 additions and 137 deletions

View File

@@ -5,6 +5,24 @@
}
.section-content {
@apply relative h-full flex flex-col items-center justify-center gap-4 md:gap-5;
@apply relative h-full flex flex-col items-center justify-center gap-4 text-center px-[20px] sm:px-[40px] md:gap-5;
}
/* Title Utility Classes */
.title-lg {
@apply line-clamp-3 text-[24px] font-[700] leading-[34px] drop-shadow-[0_2px_2px_rgba(0,0,0,0.6)] md:text-[50px] md:leading-[70px];
}
.title-md {
@apply line-clamp-2 text-[20px] font-[700] leading-[30px] drop-shadow-[0_2px_2px_rgba(0,0,0,0.6)] md:line-clamp-1 md:text-[42px] md:leading-[56px];
}
.title-sm {
@apply line-clamp-2 text-[16px] font-[500] leading-[24px] drop-shadow-[0_2px_2px_rgba(0,0,0,0.6)] md:line-clamp-1 md:text-[24px] md:leading-[34px];
}
.title-xs {
}
/* Description Utility Classes */
.description-lg {
@apply line-clamp-3 text-[15px] font-[400] leading-[24px] drop-shadow-[0_2px_2px_rgba(0,0,0,0.6)] md:text-[20px] md:leading-[30px];
}
}

View File

@@ -42,8 +42,8 @@ const props = defineProps<Props>()
<style scoped>
.card-news {
@apply overflow-hidden relative flex items-center justify-center h-full rounded-lg
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full
after:border after:border-white/10 after:rounded-lg;
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full
after:border after:border-white/10 after:rounded-lg;
}
.card-image {
@apply transition-transform duration-300 w-full h-full object-cover;

View File

@@ -83,5 +83,6 @@ const hasImage = computed(() => {
v-else-if="displayText"
v-dompurify-html="sanitizedContent"
:style="textStyles"
class="block"
/>
</template>

View File

@@ -16,7 +16,7 @@ const props = defineProps<Props>()
const mainRef = ref<InstanceType<typeof Splide> | null>(null)
const thumbsRef = ref<InstanceType<typeof Splide> | null>(null)
const isPlaying = ref<boolean>(false)
const playingSlideIndex = ref<number | null>(null)
const mainOptions = computed<Options>(() => ({
type: 'fade',
@@ -26,6 +26,7 @@ const mainOptions = computed<Options>(() => ({
speed: 600,
arrows: false,
pagination: false,
drag: false,
}))
const thumbOptions = computed<Options>(() => ({
type: 'slide',
@@ -45,9 +46,8 @@ const thumbOptions = computed<Options>(() => ({
},
}))
// 비디오 클릭 핸들러
const handleVideoClick = () => {
isPlaying.value = true
const handleVideoClick = (index: number) => {
playingSlideIndex.value = index
}
let mainInst: SplideType | null = null
@@ -58,6 +58,10 @@ onMounted(() => {
thumbsInst = thumbsRef.value?.splide ?? null
if (mainInst && thumbsInst) {
mainInst.sync(thumbsInst)
mainInst.on('moved', () => {
playingSlideIndex.value = null
})
}
})
@@ -76,28 +80,28 @@ onBeforeUnmount(() => {
:key="item.set_order || index"
class="main-slide"
>
<template v-if="!isPlaying">
<img
:src="getMediaImgSrc(item.media)"
alt="main image"
class="slide-image"
/>
<AtomsButtonPlay
v-if="getMediaType(item.media) === 'video'"
:resources-data="videoPlay"
class="btn-play"
@click="handleVideoClick()"
/>
</template>
<template v-else>
<iframe
:src="getYouTubeEmbedUrl(getMediaText(item.media))"
class="absolute top-0 left-0 w-full h-full"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
</template>
<img
:src="getMediaImgSrc(item.media)"
alt="main image"
class="slide-image"
:class="{ 'opacity-0': playingSlideIndex === index }"
/>
<AtomsButtonPlay
v-if="
getMediaType(item.media) === 'video' && playingSlideIndex !== index
"
:resources-data="videoPlay"
class="btn-play"
@click="handleVideoClick(index)"
/>
<iframe
v-if="playingSlideIndex === index"
:src="getYouTubeEmbedUrl(getMediaText(item.media), true)"
class="absolute top-0 left-0 w-full h-full"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
</SplideSlide>
</Splide>
@@ -122,14 +126,14 @@ onBeforeUnmount(() => {
/* 비디오 iframe 전환 애니메이션 */
.thumbnail-carousel {
@apply w-full px-[20px] sm:px-[40px] md:max-w-[1024px];
@apply w-full md:max-w-[944px];
}
.main-splide {
@apply overflow-hidden mx-auto rounded-lg border border-white/10 shadow-[0_4px_20px_0_rgba(0,0,0,0.5)];
}
.main-slide {
@apply aspect-[16/9];
@apply relative aspect-[16/9];
}
.slide-image {
@apply w-full h-full object-cover;

View File

@@ -4,11 +4,11 @@ import type { PageDataResourceGroup } from '#layers/types/api/pageData'
interface Props {
resourcesData: PageDataResourceGroup
gradient?: boolean
gradient?: string
}
const props = withDefaults(defineProps<Props>(), {
gradient: false,
gradient: '',
})
const resPath = computed(() => {
@@ -72,7 +72,7 @@ const posterSrc = computed(() => {
<!-- 그라디언트 오버레이 (gradient가 true일 때만) -->
<div
v-if="props.gradient"
class="absolute bottom-0 left-0 right-0 h-[342px] md:h-[720px] bg-gradient-to-b from-[#100d0f]/0 to-[#100d0f]"
:class="`absolute bottom-0 left-0 right-0 ${props.gradient}`"
/>
</div>
</template>

View File

@@ -1,76 +0,0 @@
import { getHeader } from 'h3'
import { useRuntimeConfig, useRequestEvent } from 'nuxt/app'
/**
* 게임 별칭을 추출하는 유틸리티 함수
* @param host 호스트 문자열
* @param baseDomain 기본 도메인
* @returns 게임 별칭 또는 빈 문자열
*/
const extractGameAliasFromHost = (host: string, baseDomain: string): string => {
if (!host || !host.includes(baseDomain)) {
return ''
}
const subdomain = host.split(':')[0]
return subdomain && subdomain !== 'www' ? subdomain : ''
}
/**
* 서버 사이드에서 게임 별칭을 가져오는 함수
* @param baseDomain 기본 도메인
* @returns 게임 별칭 또는 빈 문자열
*/
const getGameAliasFromServer = (baseDomain: string): string => {
try {
const event = useRequestEvent()
if (!event) {
return ''
}
// 미들웨어에서 설정한 gameAlias가 있다면 우선 사용
if (event.context.gameAlias) {
return event.context.gameAlias
}
const host = getHeader(event, 'host') || ''
return extractGameAliasFromHost(host, baseDomain)
} catch (error) {
// eslint-disable-next-line no-console
console.error('useGetGameAlias server error:', error)
return ''
}
}
/**
* 클라이언트 사이드에서 게임 별칭을 가져오는 함수
* @param baseDomain 기본 도메인
* @returns 게임 별칭 또는 빈 문자열
*/
const getGameAliasFromClient = (baseDomain: string): string => {
try {
const host = window.location.host
return extractGameAliasFromHost(host, baseDomain)
} catch (error) {
// eslint-disable-next-line no-console
console.error('useGetGameAlias client error:', error)
return ''
}
}
/**
* 게임 별칭을 가져오는 컴포저블 함수
* 서버와 클라이언트 환경에서 모두 동작
* @returns 게임 별칭 문자열
*/
export const useGetGameAlias = (): string => {
const config = useRuntimeConfig()
const baseDomain = (config.public.baseDomain || '.onstove.com') as string
if (import.meta.client) {
return getGameAliasFromClient(baseDomain)
}
return getGameAliasFromServer(baseDomain)
}

View File

@@ -38,9 +38,8 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
})) as PageDataResponse | null
if (response?.code === 0 && 'value' in response) {
const cleanData = JSON.parse(JSON.stringify(response.value))
store.setPageData(cleanData)
store.setPageData(response.value)
console.log('🚀 ~ cleanData:', response.value)
} else {
store.clearPageData()
}

View File

@@ -4,8 +4,8 @@ import {
defineEventHandler,
getRequestURL,
} from 'h3'
import { ssrGetFinalLocale } from '../../utils/localeUtil'
import type { GameDataResponse } from '../../types/api/gameData'
export default defineEventHandler(async event => {
const url = getRequestURL(event)
@@ -59,9 +59,9 @@ export default defineEventHandler(async event => {
lang_code: langCode,
}
const response = await $fetch(apiUrl, {
const response = (await $fetch(apiUrl, {
query: queryParams,
})
})) as GameDataResponse | null
const gaId = (response as any).value?.ga_code
@@ -70,11 +70,8 @@ export default defineEventHandler(async event => {
event.context.googleAnalyticsId = gaId
}
// 타입 단언을 사용하여 response의 타입 오류를 해결
const res = response as { code?: number; value?: unknown }
if (res?.code === 0 && res && typeof res === 'object' && 'value' in res) {
event.context.gameData = res.value
if (response?.code === 0 && 'value' in response) {
event.context.gameData = response.value
}
} catch (error) {
console.error('gameData load error:', error)

View File

@@ -27,7 +27,7 @@ const videoPlayData = computed(() =>
<WidgetsMainTitle
v-if="mainTitleData"
:resources-data="mainTitleData"
class="main-title"
class="title-sm"
/>
<BlocksSlideThumbnail
:slide-item-list="slideThumbnailData"
@@ -36,11 +36,3 @@ const videoPlayData = computed(() =>
</div>
</section>
</template>
<style scoped>
.main-title {
@apply text-center text-[16px] font-medium leading-[24px] tracking-[-0.48px] md:text-[24px] md:leading-[34px] md:tracking-[-0.72px];
text-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
font-family: 'Spoqa Han Sans Neo', sans-serif;
}
</style>

View File

@@ -30,7 +30,7 @@ const buttonListData = computed(() =>
<WidgetsBackground
v-if="backgroundData"
:resources-data="backgroundData"
:gradient="true"
gradient="h-[342px] bg-[linear-gradient(180deg,rgba(16,13,15,0)_0%,#100D0F_90%)] md:h-[720px]"
/>
<div class="section-content">
<WidgetsMainTitle

View File

@@ -30,23 +30,21 @@ const props = defineProps<Props>()
v-if="hasComponentGroup(item, 'background')"
:resources-data="getComponentGroup(item, 'background')"
/>
<div
class="relative h-full flex flex-col items-center justify-center gap-[14px] text-center md:gap-5"
>
<div class="section-content">
<WidgetsSubTitle
v-if="hasComponentGroup(item, 'subTitle')"
:resources-data="getComponentGroup(item, 'subTitle')"
class="line-clamp-2 text-[16px] font-[500] leading-[24px] md:line-clamp-1 md:text-[24px] md:leading-[34px]"
class="title-sm"
/>
<WidgetsMainTitle
v-if="hasComponentGroup(item, 'mainTitle')"
:resources-data="getComponentGroup(item, 'mainTitle')"
class="line-clamp-3 text-[24px] font-[700] leading-[34px] md:text-[50px] md:leading-[70px]"
class="title-lg"
/>
<WidgetsDescription
v-if="hasComponentGroup(item, 'description')"
:resources-data="getComponentGroup(item, 'description')"
class="line-clamp-3 text-[15px] font-[400] leading-[24px] md:text-[20px] md:leading-[30px]"
class="description-lg"
/>
<WidgetsButtonList
v-if="hasComponentGroup(item, 'buttonList')"