feat: 고정형페이지-다운로드

This commit is contained in:
최만억 (Jo)
2025-10-31 08:30:58 +00:00
committed by 김채린
parent 83124d56eb
commit 8a281033b4
20 changed files with 826 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
const props = defineProps<{
title: string
resourcesData: PageDataResourceGroup
}>()
const { getCurrentSrc } = useResponsiveSrc()
const resPath = computed(() => props.resourcesData?.res_path)
const imageSrc = computed(() => {
return getCurrentSrc(resPath.value)
})
</script>
<template>
<div
class="flex justify-center items-center w-full h-[120px] bg-center px-[20px] bg-no-repeat bg-cover sm:px-[40px] md:h-[180px]"
:style="{ backgroundImage: `url(${imageSrc})` }"
>
<h2
class="flex justify-center items-center w-full max-w-full md:justify-start md:max-w-[1300px]"
>
<span
class="flex justify-center items-center text-white text-[20px] font-bold tracking-[-0.6px] leading-[30px] md:text-[32px] md:leading-[44px] md:tracking-[-0.96px]"
>
{{ props.title }}
</span>
</h2>
</div>
</template>

View File

@@ -0,0 +1,72 @@
<script setup lang="ts">
interface Props {
title: string
description?: string
link?: string
target?: '_self' | '_blank'
}
const props = withDefaults(defineProps<Props>(), {
target: '_blank',
})
const componentTag = computed((): string => {
switch (props.target) {
case '_self':
return 'AtomsLocaleLink'
case '_blank':
return 'a'
default:
return 'a'
}
})
const componentProps = computed(() => {
if (props.target === '_self') {
return {
to: props.link,
}
}
if (props.target === '_blank') {
return {
href: props.link,
target: props.target,
rel: 'noopener noreferrer',
}
}
})
</script>
<template>
<div class="flex flex-wrap items-end justify-between">
<h3
class="text-[#1F1F1F] text-[18px] font-bold leading-[26px] tracking-[-0.54px] md:text-[24px] md:leading-[34px] md:tracking-[0.72px]"
>
<span>{{ props.title }}</span>
</h3>
<div class="flex items-center justify-between">
<slot />
<p
v-if="props.description && !props.link"
class="text-[#666666] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] md:text-[14px] md:leading-[24px] md:tracking-[-0.421px]"
>
{{ props.description }}
</p>
<component
:is="componentTag"
v-else-if="props.description && props.link"
v-bind="componentProps"
class="relative flex items-center justify-center gap-[4px] w-auto h-auto text-[#3C75FF] text-[14px] font-[500] leading-[20px] tracking-[-0.42px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-0 before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
>
<span>{{ props.description }}</span>
<AtomsIconsWebLinkLine
v-if="props.target === '_blank'"
:size="20"
color="#3C75FF"
class="icon"
/>
</component>
</div>
</div>
</template>

View File

@@ -0,0 +1,189 @@
import { useTokenValidation } from '#layers/composables/useTokenValidation'
import { csrGoStoveLogin } from '#layers/utils/stoveUtil'
interface ReqCheckSpec {
schemeFormat: string
setupUrl: string
gameNo: string
locale: string
}
export const useCheckPCSpec = (tm: Function) => {
const { isTokenValid, validateToken } = useTokenValidation()
// Store
const modalStore = useModalStore()
const { handleOpenConfirm } = modalStore
const isButtonDisabled = ref(false) // 버튼 비활성화 여부
/**
* STOVE 미로그인 상태일 경우 로그인 모달을 표시합니다.
* 케이스에 따라 모달 내용을 변경할 수 있습니다.
* @param content<string> 모달 내용(로그인 안내 메시지)
*/
const showLoginModal = (content: string) => {
handleOpenConfirm({
contentText: content,
confirmButtonText: tm('Download_Text_StoveLogin'),
modalName: 'modal-login',
confirmButtonEvent: () => {
csrGoStoveLogin()
},
})
}
/**
* PC사양 프로그램이 미설치일 경우 설치 안내 모달을 표시합니다.
* @param {string} setupUrl 설치 프로그램 URL
*/
const showInstallGuideModal = (setupUrl: string) => {
isButtonDisabled.value = false
handleOpenConfirm({
contentText: tm('Download_Alert_InstallGuide'),
confirmButtonText: tm('Download_Text_Download'),
modalName: 'modal-download',
isShowDimmed: true,
confirmButtonEvent: () => {
location.href = String(setupUrl)
},
})
}
/**
* iframe을 생성합니다.
* @returns {iframe<HTMLIFrameElement>} 생성된 iframe
*/
const createHiddenIframe = () => {
const iframe = document.createElement('iframe')
iframe.src = 'about:blank'
iframe.id = 'hiddenIframe'
iframe.style.display = 'none'
document.body.appendChild(iframe)
return iframe
}
/**
* Firefox 브라우저에서 URI를 열기 위한 함수입니다.
* @param {string} uri URI
* @param {Function} successCb 성공 콜백
* @param {Function} failCb 실패 콜백
*/
const openUriUsingFirefox = (
uri: string,
successCb: Function,
failCb: Function
) => {
let iframe = document.querySelector(
'#hiddenIframe'
) as HTMLIFrameElement | null
if (!iframe) {
iframe = createHiddenIframe() as HTMLIFrameElement
}
try {
iframe.contentWindow!.location.href = uri
successCb()
} catch (e) {
if ((e as any).name === 'NS_ERROR_UNKNOWN_PROTOCOL') {
failCb()
}
}
}
/**
* Chrome 브라우저에서 URI를 열기 위한 함수입니다.
* @param {string} uri 사양 프로그램 URI
* @param {Function} successCb 성공 콜백
* @param {Function} failCb 실패 콜백
*/
const openUriWithTimeoutHack = (
uri: string,
successCb: Function,
failCb: Function
) => {
let called = false
const timeout = setTimeout(() => {
if (!called) {
called = true
window.removeEventListener('blur', onBlur)
failCb()
}
}, 1000)
const onBlur = () => {
if (!called) {
called = true
clearTimeout(timeout)
window.removeEventListener('blur', onBlur)
successCb()
}
}
window.addEventListener('blur', onBlur)
window.location.href = uri
}
/**
* PC사양 프로그램이 설치되어 있는지 여부를 확인합니다.
* @param {string }uri 사양 프로그램 URI
* @param {string} setupUrl 설치 프로그램 URL
*/
const checkPCSpecProgramInstalled = (uri: string, setupUrl: string) => {
const device = useDevice()
const successCallback = () => {
isButtonDisabled.value = false
}
if (typeof (navigator as any).msLaunchUri === 'function') {
;(navigator as any).msLaunchUri(
uri,
successCallback,
showInstallGuideModal(setupUrl)
)
} else if (device.isFirefox) {
// 파이어폭스 브라우저일 경우
openUriUsingFirefox(uri, successCallback, () =>
showInstallGuideModal(setupUrl)
)
} else if (device.isChrome || device.isEdge) {
// 크롬 / 엣지 브라우저일 경우
openUriWithTimeoutHack(uri, successCallback, () =>
showInstallGuideModal(setupUrl)
)
} else {
// 기타 미지원 브라우저일 경우
showInstallGuideModal(setupUrl)
}
}
/**
* 시스템 사양 체크를 시작합니다.
* 최종 호출 함수입니다.
* @param {ReqCheckSpec} req {schemeFormat:스킴 포맷, setupUrl:셋업 링크, gameNo:게임넘버, locale:언어}
*/
const checkPCSpec = async (req: ReqCheckSpec) => {
const accessToken = useCookie('SUAT')
await validateToken(accessToken.value || '')
// 로그인 상태 아닐 경우
if (!isTokenValid.value) {
showLoginModal(tm('Download_Alert_StoveLogin'))
return
}
const localeValue: string = req.locale === 'ja' ? 'jp' : req.locale
const uri = `${req.schemeFormat}${accessToken.value}/${req.gameNo}/${localeValue}`
checkPCSpecProgramInstalled(uri, req.setupUrl)
isButtonDisabled.value = true
}
return {
isButtonDisabled,
checkPCSpec,
}
}

View File

@@ -9,6 +9,7 @@ import GrDetail02 from '#layers/templates/GrDetail02/index.vue'
import GrDetail03 from '#layers/templates/GrDetail03/index.vue'
import GrBoard01 from '#layers/templates/GrBoard01/index.vue'
import GrContents01 from '#layers/templates/GrContents01/index.vue'
import FxDownload01 from '#layers/templates/FxDownload01/index.vue'
const templateRegistry = {
GR_VISUAL_01: { component: GrVisual01 },
@@ -22,6 +23,7 @@ const templateRegistry = {
GR_DETAIL_02: { component: GrDetail02 },
GR_DETAIL_03: { component: GrDetail03 },
GR_CONTENTS_01: { component: GrContents01 },
FX_DOWNLOAD_01: { component: FxDownload01 },
} as const
type TemplateKey = keyof typeof templateRegistry

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,468 @@
<script setup lang="ts">
import { SplideSlide } from '@splidejs/vue-splide'
import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil'
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
// Types
type Platform = 'google_play' | 'app_store' | 'pc' | 'stove'
// Props
interface Props {
components: PageDataTemplateComponents
pageVerTmplSeq: number
}
const props = defineProps<Props>()
// Configuration
const runtimeConfig = useRuntimeConfig()
const runType = runtimeConfig.public.runType
const staticUrl = runtimeConfig.public.staticUrl
const multilingualBaseApiUrl = `${staticUrl}/${runType}/test`
const multilingualFileName = 'test_homepage_brand_download.json'
// Multilingual
const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: multilingualBaseApiUrl,
fileName: multilingualFileName,
})
const { tm, locale }: any = useI18n({
useScope: 'local',
messages: Object(resultGetMultilingual?.value?.multilingual),
})
// Composables
const device = useDevice()
const breakpoints = useResponsiveBreakpoints()
const { checkPCSpec } = useCheckPCSpec(tm)
// Store
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
// Data
const backgroundData = computed(() =>
getComponentGroup(props.components, 'background')
)
const specCheckData = computed(() =>
getComponentGroupAry(props.components, 'tablePcSpecTool')
)
const schemeFormatData = computed(
() => specCheckData.value[0]?.display?.text || ''
)
const setupUrlData = computed(() => specCheckData.value[1]?.display?.text || '')
// Refs
const specPCRef = ref<HTMLElement | null>(null)
const specMobileRef = ref<HTMLElement | null>(null)
const driverArray = ref<Array<string>>([
'DirectX',
'NVIDIA',
'AMD',
'intel',
'Visual',
])
const pcSpecArray = ref<Array<string>>([
'Os',
'Cpu',
'Vga',
'Ram',
'Directx',
'Hdd',
])
const mobileSpecArray = ref<Array<string>>(['Android', 'Ios'])
const mobileOSArray = ref<Array<string>>(['AOS', 'iOS'])
// Computed
const platformList = computed(() => {
if (breakpoints.value.isMobile) {
return ['MOBILE', 'PC', 'STOVE']
} else {
return ['PC', 'STOVE', 'MOBILE']
}
})
const driverList = computed(() =>
driverArray.value.map(driver => ({
id: `DRIVER_${driver}`,
driverCode: driver,
driverText: tm(`Download_Driver_${driver}`),
}))
)
const pcSpecList = computed(() =>
pcSpecArray.value.map(data => ({
id: `PC_TABLE_ITEM_${data}`,
itemCode: data,
itemText: tm(`Download_Table_${data}`),
itemData: getComponentGroupAry(props.components, `tablePcSpec${data}`),
}))
)
const mobileSpecList = computed(() =>
mobileSpecArray.value.map(data => ({
id: `MO_TABLE_ITEM_${data}`,
itemCode: data,
itemText: tm(`Download_Table_${data}`),
itemData: getComponentGroupAry(props.components, `tableMoSpec${data}`),
}))
)
const mobileOSList = computed(() =>
mobileOSArray.value.map(os => ({
id: `MO_OS_${os}`,
osCode: os,
osText: tm(`Download_${os}_OS`),
platformCode: tm(`Download_${os}_Platform`),
platformText: tm(`Download_${os}_Name`),
isValue: device[tm(`Download_${os}_IsValue`) as keyof typeof device],
}))
)
// Functions
/**
* 입력한 pc, mobile 인자값에 따라 해당 최소사양&권장사양 테이블로 포커스 스크롤 이동합니다.
* @param target<string> 'pc' | 'mobile'
*/
const handleMoveFocus = (target: 'pc' | 'mobile') => {
const refs: Record<string, HTMLElement | null> = {
pc: specPCRef.value,
mobile: specMobileRef.value,
}
const adjustedOffset: number = breakpoints.value.isMobile ? 80 : 100
const adjustedOffsetTop: number =
refs[target]?.getBoundingClientRect().top + window.scrollY - adjustedOffset
window.scrollTo({ top: adjustedOffsetTop, behavior: 'smooth' })
}
</script>
<template>
<WidgetsFixMainTitle
:title="tm('Download_Page_Title')"
:resourcesData="backgroundData"
/>
<div
class="overflow-hidden h-auto pt-[32px] px-[20px] pb-[80px] bg-[#F0F0F0] sm:px-[40px] md:pt-[64px] md:pb-[200px]"
>
<section class="relative flex flex-col items-center justify-start w-full">
<div class="w-full max-w-full md:max-w-[1300px]">
<WidgetsFixSubTitle :title="tm('Download_Section_Platform_Title')" />
<BlocksSlideDefault
:per-page="platformList.length"
:gap="20"
:arrows="false"
:pagination="false"
:drag="false"
:breakpoints="{
1023: {
autoWidth: true,
perPage: 'auto',
gap: 12,
focus: 0,
drag: true,
padding: { left: 0, right: 0 },
},
}"
class="w-[calc(100%+40px)] px-[20px] ml-[-20px] mt-[16px] sm:w-[calc(100%+80px)] sm:px-[40px] sm:ml-[-40px] md:w-full md:px-0 md:mt-[24px] md:ml-0"
>
<SplideSlide
v-for="platform in platformList"
:key="platform"
class="flex flex-col items-center justify-between shrink-0 whitespace-normal w-[295px] h-[280px] bg-[#FFFFFF] p-[20px] rounded-[12px] text-left md:w-[calc((100%-40px)/3)] md:h-[314px] md:p-[24px] md:rounded-[16px] lg:w-[420px] lg:h-[340px] lg:p-[32px]"
>
<div
class="flex flex-col items-start justify-start gap-[8px] w-full md:gap-[12px]"
>
<WidgetsFixSubTitle
:title="tm(`Download_Box_${platform}_Title`)"
/>
<p
v-if="
tm(`Download_Box_${platform}_Description_List`).length === 1
"
v-dompurify-html="tm(`Download_Box_${platform}_Description01`)"
class="text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] md:text-[15px] md:tracking-[-0.45px]"
></p>
<ul
v-else-if="
tm(`Download_Box_${platform}_Description_List`).length > 1
"
>
<li
v-for="description in tm(
`Download_Box_${platform}_Description_List`
)"
:key="description"
v-dompurify-html="tm(description)"
class="relative pl-[22px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#999999] text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] md:text-[15px] md:tracking-[-0.45px]"
></li>
</ul>
<AtomsButton
v-if="platform !== 'STOVE'"
type="action"
button-size="size-small"
background-color="transparent"
text-color="#1F1F1F"
class="relative w-auto h-auto px-0 text-[16px] font-[500] leading-[24px] tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
@click="
handleMoveFocus(platform.toLowerCase() as 'pc' | 'mobile')
"
>
<span>{{ tm(`Download_Box_${platform}_SpecCheck`) }}</span>
<AtomsIconsLongArrowRightLine
:size="20"
color="#1F1F1F"
class="relative rotate-90"
/>
</AtomsButton>
</div>
<div
class="flex flex-col items-center justify-center gap-[8px] w-full md:gap-[12px]"
>
<template v-if="platform === 'MOBILE'">
<template v-for="os in mobileOSList" :key="os.id">
<AtomsButtonLauncher
v-if="device.isMobile ? os.isValue : true"
:platform="`${os.platformCode as Platform}`"
class="!w-full"
>
<span>{{ os.platformText }}</span>
</AtomsButtonLauncher>
</template>
</template>
<template v-else>
<AtomsButton
v-if="breakpoints.isMobile"
type="action"
button-size="size-small"
background-color="#EBEBEB"
text-color="#999999"
:disabled="true"
class="w-full px-0 border border-solid border-[rgba(0,0,0,0.1)] cursor-default"
>
<span>{{ tm(`Download_Button_${platform}_Mobile`) }}</span>
</AtomsButton>
<AtomsButtonLauncher
v-else-if="breakpoints.isMd || breakpoints.isDesktop"
:platform="`${platform.toLowerCase() as Platform}`"
class="!w-full"
>
<span>{{ tm(`Download_Button_${platform}`) }}</span>
</AtomsButtonLauncher>
</template>
</div>
</SplideSlide>
</BlocksSlideDefault>
</div>
</section>
<section
ref="specPCRef"
class="relative flex flex-col items-center justify-start w-full mt-[80px] md:mt-[100px]"
>
<div class="w-full max-w-full md:max-w-[1300px]">
<WidgetsFixSubTitle :title="tm('Download_Section_PC_Title')" />
<div class="mt-[16px] md:mt-[24px]">
<table>
<thead>
<tr>
<th class="w-[80px] md:w-[172px]">
{{ tm('Download_Table_Item') }}
</th>
<th>{{ tm('Download_Table_Min_Spec') }}</th>
<th>{{ tm('Download_Table_Rec_Spec') }}</th>
</tr>
</thead>
<tbody>
<template v-for="tr in pcSpecList" :key="tr.id">
<tr>
<th class="w-[80px] md:w-[172px]">{{ tr.itemText }}</th>
<template v-for="td in tr.itemData" :key="td.display.text">
<td>{{ td.display.text }}</td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
<div
v-if="!breakpoints.isMobile"
class="relative flex items-end justify-between w-full mt-[24px] gap-[24px]"
>
<ul
v-if="tm('Download_Section_PC_Notice_List').length > 0"
class="relative flex flex-col items-center justify-start w-full"
>
<template
v-for="notice in tm('Download_Section_PC_Notice_List')"
:key="notice"
>
<li
v-dompurify-html="tm(notice)"
class="relative w-full pl-[26px] text-left text-[#666666] text-[16px] font-[400] leading-[26px] tracking-[-0.48px] before:content-[''] before:absolute before:top-[11px] before:left-[10px] before:w-[4px] before:h-[4px] before:bg-[#666666] before:rounded-full"
></li>
</template>
</ul>
<AtomsButton
type="action"
button-size="size-small"
background-color="#383838"
text-color="#FFFFFF"
class="shrink-0 w-[206px] px-0 text-[16px]"
@click="
checkPCSpec({
schemeFormat: schemeFormatData,
setupUrl: setupUrlData,
gameNo: gameData?.game_code?.toString(),
locale: locale as string,
})
"
>
<em
class="inline-flex items-center justify-center gap-[8px] not-italic"
>
<span
class="relative inline-flex items-center justify-center w-[20px] h-[20px]"
>
<img
:src="
getImageHost(
'/images/common/ic-v2-hardware-desktop-line.svg',
{ imageType: 'common' }
)
"
alt="Desktop-Icon"
loading="lazy"
draggable="false"
class="w-full object-contain"
/>
</span>
<span>{{ tm('Download_Button_SpecCheck') }}</span>
</em>
</AtomsButton>
</div>
</div>
</section>
<section
v-if="!breakpoints.isMobile"
class="relative flex flex-col items-center justify-start w-full mt-[80px] md:mt-[100px]"
>
<div class="w-full max-w-full md:max-w-[1300px]">
<WidgetsFixSubTitle :title="tm('Download_Section_Driver_Title')" />
<div class="mt-[16px] md:mt-[24px]">
<ul class="flex items-center justify-between gap-[20px] w-full">
<template v-for="driver in driverList" :key="driver.id">
<li class="w-full h-[250px]">
<div
class="flex flex-col items-center justify-between gap-[16px] h-[250px] rounded-[16px] p-[20px] bg-[#FFFFFF]"
>
<div class="flex items-center justify-center w-full">
<img
:src="
getImageHost(
`/images/common/grades_driver/Type-${driver.driverCode}.svg`,
{ imageType: 'common' }
)
"
:alt="driver.driverText"
loading="lazy"
draggable="false"
/>
</div>
<div
class="flex flex-col items-start justify-end gap-[16px] w-full"
>
<p
class="text-left text-[#666666] text-[16px] font-[400] leading-[26px] tracking-[-0.48px]"
>
{{ driver.driverText }}
</p>
<AtomsButton
type="download"
button-size="size-small"
background-color="#383838"
text-color="#FFFFFF"
class="w-full px-0"
target="_blank"
rel="noopener noreferrer"
:href="tm(`Download_Driver_${driver.driverCode}_Url`)"
>
<span>{{ tm('Download_Button_Download') }}</span>
</AtomsButton>
</div>
</div>
</li>
</template>
</ul>
</div>
</div>
</section>
<section
ref="specMobileRef"
class="relative flex flex-col items-center justify-start w-full mt-[80px] md:mt-[100px]"
>
<div class="w-full max-w-full md:max-w-[1300px]">
<WidgetsFixSubTitle :title="tm('Download_Section_MOBILE_Title')" />
<div class="mt-[16px] md:mt-[24px]">
<table>
<thead>
<tr>
<th rowspan="2" class="w-[80px] md:w-[172px]">
{{ tm('Download_Table_Item') }}
</th>
<th colspan="2">{{ tm('Download_Table_Min_Spec') }}</th>
<th colspan="2">{{ tm('Download_Table_Rec_Spec') }}</th>
</tr>
<tr>
<th>{{ tm('Download_Table_Device') }}</th>
<th>{{ tm('Download_Table_Os') }}</th>
<th>{{ tm('Download_Table_Device') }}</th>
<th>{{ tm('Download_Table_Os') }}</th>
</tr>
</thead>
<tbody>
<template v-for="tr in mobileSpecList" :key="tr.id">
<tr>
<th class="w-[80px] md:w-[172px]">{{ tr.itemText }}</th>
<template v-for="td in tr.itemData" :key="td.display.text">
<td>{{ td.display.text }}</td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</section>
</div>
</template>
<style scoped>
/* 고정형 페이지 Table Style */
table {
@apply w-full h-auto border-collapse border-spacing-0 table-fixed;
}
table th {
@apply py-[8px] px-[12px] border border-[#D9D9D9] bg-[#FAFAFA] text-[#1F1F1F] text-[14px] font-bold leading-[24px] tracking-[-0.42px]
md:py-[11px] md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
}
table td {
@apply h-[64px] py-[8px] px-[12px] border border-[#D9D9D9] bg-[#FFFFFF] text-[#666666] text-[14px] font-[400] leading-[24px] tracking-[-0.42px]
md:h-[80px] md:py-[14px] md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
}
/* 플랫폼별 다운로드 Mobile Overflow Visible 처리 */
.splide :deep(.splide__track) {
overflow: visible !important;
}
</style>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8333 14.238L15.2441 9.82725C15.5695 9.50181 16.0972 9.50181 16.4226 9.82725C16.748 10.1527 16.748 10.6803 16.4226 11.0058L10.5893 16.8391C10.2638 17.1645 9.73619 17.1645 9.41075 16.8391L3.57742 11.0058C3.25198 10.6803 3.25198 10.1527 3.57742 9.82725C3.90286 9.50181 4.4305 9.50181 4.75593 9.82725L9.16668 14.238L9.16668 3.74984C9.16668 3.2896 9.53977 2.9165 10 2.9165C10.4602 2.9165 10.8333 3.2896 10.8333 3.74984L10.8333 14.238Z" fill="#1F1F1F"/>
</svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.49996 10.104C7.03972 10.104 6.66663 10.4771 6.66663 10.9373C6.66663 11.3976 7.03972 11.7707 7.49996 11.7707H12.5C12.9602 11.7707 13.3333 11.3976 13.3333 10.9373C13.3333 10.4771 12.9602 10.104 12.5 10.104H7.49996Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99996 2.2915C3.15901 2.2915 1.66663 3.78389 1.66663 5.62484V11.8748C1.66663 13.7158 3.15901 15.2082 4.99996 15.2082H9.16663V15.9373H6.87496C6.41472 15.9373 6.04163 16.3104 6.04163 16.7707C6.04163 17.2309 6.41472 17.604 6.87496 17.604H13.125C13.5852 17.604 13.9583 17.2309 13.9583 16.7707C13.9583 16.3104 13.5852 15.9373 13.125 15.9373H10.8333V15.2082H15C16.8409 15.2082 18.3333 13.7158 18.3333 11.8748V5.62484C18.3333 3.78389 16.8409 2.2915 15 2.2915H4.99996ZM3.33329 5.62484C3.33329 4.70436 4.07948 3.95817 4.99996 3.95817H15C15.9204 3.95817 16.6666 4.70436 16.6666 5.62484V11.8748C16.6666 12.7953 15.9204 13.5415 15 13.5415H4.99996C4.07949 13.5415 3.33329 12.7953 3.33329 11.8748V5.62484Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.237 3.76397C12.7632 3.09926 13.1113 2.17037 13.0142 1.25C12.2613 1.28409 11.3545 1.77836 10.8121 2.44307C10.3264 3.03109 9.90542 3.97702 10.0188 4.88035C10.8526 4.94852 11.7108 4.42868 12.237 3.76397Z" fill="white"/>
<path d="M13.4325 5.21408L13.248 5.19858C12.5336 5.14288 11.8799 5.34976 11.3277 5.55942L10.7675 5.77674C10.5095 5.87346 10.2861 5.9429 10.1039 5.9429C9.90183 5.9429 9.66519 5.87108 9.40315 5.77314L8.73016 5.51131C8.31461 5.35663 7.86368 5.2224 7.40258 5.23403C6.01207 5.25175 4.73671 6.04037 4.01931 7.28977C2.57567 9.79743 3.64733 13.5013 5.05555 15.5393L5.29985 15.8859L5.46886 16.1166L5.64325 16.343C6.20438 17.0495 6.8521 17.6387 7.64171 17.6128C8.06839 17.5946 8.38546 17.4772 8.69864 17.3454L8.96859 17.2308C9.33383 17.0787 9.73346 16.9394 10.3253 16.9394C10.8395 16.9394 11.2021 17.0536 11.5273 17.186L11.934 17.3589C12.2483 17.4894 12.5744 17.5972 13.0266 17.5862C13.964 17.5713 14.6139 16.8687 15.2021 16.0508L15.3687 15.8144L15.6157 15.4515C15.6967 15.3285 15.7724 15.2063 15.8431 15.0862L15.9778 14.8492C15.9992 14.8102 16.02 14.7716 16.0402 14.7334L16.1554 14.5088C16.1735 14.4723 16.1911 14.4362 16.2082 14.4006L16.3046 14.1935L16.3888 14.0009L16.4613 13.8249L16.5485 13.5971L16.6111 13.4194L16.6667 13.2443L16.6024 13.2177L16.4408 13.1398L16.27 13.0449L16.138 12.9632L15.9949 12.8661C15.3552 12.4107 14.4871 11.5037 14.4702 9.93034C14.4579 8.4852 15.2845 7.56519 15.8136 7.12531L15.9441 7.02142C15.9645 7.00591 15.9841 6.9913 16.0029 6.97759L16.1467 6.87828L16.2416 6.82014C15.5921 5.86906 14.7104 5.48115 14.0399 5.31771L13.8455 5.27508L13.6665 5.24388L13.5054 5.22208C13.4803 5.21912 13.4559 5.21647 13.4325 5.21408Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.95837 2.70817L8.95837 10.1934L5.73661 6.9716C5.32981 6.5648 4.67027 6.5648 4.26347 6.9716C3.85667 7.3784 3.85667 8.03794 4.26347 8.44474L9.26347 13.4447C9.45882 13.6401 9.72377 13.7498 10 13.7498C10.2763 13.7498 10.5413 13.6401 10.7366 13.4447L15.7366 8.44474C16.1434 8.03794 16.1434 7.3784 15.7366 6.9716C15.3298 6.5648 14.6703 6.5648 14.2635 6.9716L11.0417 10.1934L11.0417 2.70817C11.0417 2.13287 10.5753 1.6665 10 1.6665C9.42474 1.6665 8.95837 2.13287 8.95837 2.70817Z" fill="white"/>
<path d="M16.6667 17.4998C17.242 17.4998 17.7084 17.0335 17.7084 16.4582L17.7084 14.7915C17.7084 14.2162 17.242 13.7498 16.6667 13.7498C16.0914 13.7498 15.625 14.2162 15.625 14.7915L15.625 15.4165L4.37504 15.4165L4.37504 14.7915C4.37504 14.2162 3.90867 13.7498 3.33337 13.7498C2.75808 13.7498 2.29171 14.2162 2.29171 14.7915L2.29171 16.4582C2.29171 17.0335 2.75808 17.4998 3.33337 17.4998L16.6667 17.4998Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.3334 9.99984C18.3334 14.6022 14.6024 18.3332 10 18.3332C5.39765 18.3332 1.66669 14.6022 1.66669 9.99984C1.66669 5.39746 5.39765 1.6665 10 1.6665C14.6024 1.6665 18.3334 5.39746 18.3334 9.99984ZM9.22062 8.54481L9.22274 8.54659C9.43617 8.73076 9.86272 8.84099 10.3605 8.96962C11.5754 9.28357 13.2145 9.70714 13.2145 11.5829C13.2145 13.3403 11.7347 14.1665 9.89684 14.1665C8.61826 14.1665 6.78589 13.6544 6.78589 12.5372C6.78589 12.0252 7.1098 11.5836 7.63492 11.5836C7.82936 11.5836 8.05509 11.6765 8.33163 11.7905C8.76886 11.9706 9.33312 12.203 10.1016 12.203C10.7915 12.203 11.1176 12.0509 11.1176 11.7342C11.1176 11.5829 11.0605 11.4967 10.9692 11.4036C10.7436 11.1842 10.2841 11.0419 9.75399 10.8778C8.54078 10.5021 6.95715 10.0118 6.95715 8.21965C6.95715 6.74162 8.23015 5.83317 10.0452 5.83317C10.673 5.83317 11.9519 5.96916 12.4542 6.48128C12.6483 6.67911 12.7439 6.92348 12.7439 7.23772C12.7439 7.72652 12.4171 8.09092 11.8919 8.09092C11.7487 8.09092 11.5603 8.03935 11.3312 7.97664C10.9758 7.87936 10.5225 7.75527 9.98816 7.75527C9.4402 7.75527 9.02823 7.84 9.02823 8.19226C9.02823 8.26403 9.0471 8.3333 9.10184 8.41684C9.13042 8.4605 9.17 8.50361 9.22062 8.54481Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB