From 81350cec514cedf24531cdabaf950b3c9eaa51c1 Mon Sep 17 00:00:00 2001 From: clkim Date: Fri, 9 Jan 2026 13:21:29 +0900 Subject: [PATCH 01/33] =?UTF-8?q?feat.=20=EA=B3=B5=ED=86=B5=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=95=84=EC=9D=B4=EC=BD=98=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.vue | 140 +++++++++++------- layers/assets/css/components/_splide.css | 4 - layers/components/atoms/Button/Play.vue | 3 +- layers/components/atoms/Select.vue | 8 +- .../atoms/icons/ArrowControlTopLine.vue | 31 ++++ .../components/atoms/icons/ArrowRightFill.vue | 30 ++++ .../atoms/icons/SelectArrowDownFill.vue | 26 ---- layers/components/blocks/Button/Launcher.vue | 8 +- layers/components/blocks/Button/ScrollTop.vue | 4 +- layers/components/blocks/DatePicker.vue | 2 +- layers/components/blocks/slide/Arrows.vue | 8 +- layers/templates/FxDownload01/index.vue | 2 +- layers/types/api/gameData.ts | 16 -- nuxt.config.ts | 14 -- .../{ => btn_launcher}/btn_logo-app.svg | 0 .../{ => btn_launcher}/btn_logo-google.svg | 0 .../common/{ => btn_launcher}/btn_logo-pc.svg | 0 .../{ => btn_launcher}/btn_logo-stove.svg | 0 .../Type-AMD.svg | 0 .../Type-DirectX.svg | 0 .../Type-NVIDIA.svg | 0 .../Type-Visual.svg | 0 .../Type-intel.svg | 0 23 files changed, 165 insertions(+), 131 deletions(-) create mode 100644 layers/components/atoms/icons/ArrowControlTopLine.vue create mode 100644 layers/components/atoms/icons/ArrowRightFill.vue delete mode 100644 layers/components/atoms/icons/SelectArrowDownFill.vue rename public/images/common/{ => btn_launcher}/btn_logo-app.svg (100%) rename public/images/common/{ => btn_launcher}/btn_logo-google.svg (100%) rename public/images/common/{ => btn_launcher}/btn_logo-pc.svg (100%) rename public/images/common/{ => btn_launcher}/btn_logo-stove.svg (100%) rename public/images/common/{grades_driver => download_driver}/Type-AMD.svg (100%) rename public/images/common/{grades_driver => download_driver}/Type-DirectX.svg (100%) rename public/images/common/{grades_driver => download_driver}/Type-NVIDIA.svg (100%) rename public/images/common/{grades_driver => download_driver}/Type-Visual.svg (100%) rename public/images/common/{grades_driver => download_driver}/Type-intel.svg (100%) diff --git a/app/app.vue b/app/app.vue index c0edfec..d64658f 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,14 +1,14 @@ + + diff --git a/layers/components/atoms/icons/ArrowRightFill.vue b/layers/components/atoms/icons/ArrowRightFill.vue new file mode 100644 index 0000000..8450475 --- /dev/null +++ b/layers/components/atoms/icons/ArrowRightFill.vue @@ -0,0 +1,30 @@ + + + diff --git a/layers/components/atoms/icons/SelectArrowDownFill.vue b/layers/components/atoms/icons/SelectArrowDownFill.vue deleted file mode 100644 index 87b9b74..0000000 --- a/layers/components/atoms/icons/SelectArrowDownFill.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/layers/components/blocks/Button/Launcher.vue b/layers/components/blocks/Button/Launcher.vue index 97edbc5..7afb5cb 100644 --- a/layers/components/blocks/Button/Launcher.vue +++ b/layers/components/blocks/Button/Launcher.vue @@ -39,10 +39,10 @@ const PLATFORM_ICON_MAP: Record = { } as const const DUP_IMAGE_MAP: Record = { - google_play: '/images/common/btn_logo-google.svg', - app_store: '/images/common/btn_logo-app.svg', - pc: '/images/common/btn_logo-pc.svg', - stove: '/images/common/btn_logo-stove.svg', + google_play: '/images/common/btn_launcher/btn_logo-google.svg', + app_store: '/images/common/btn_launcher/btn_logo-app.svg', + pc: '/images/common/btn_launcher/btn_logo-pc.svg', + stove: '/images/common/btn_launcher/btn_logo-stove.svg', } as const const componentTag = computed(() => { diff --git a/layers/components/blocks/Button/ScrollTop.vue b/layers/components/blocks/Button/ScrollTop.vue index 8b3d984..94fef90 100644 --- a/layers/components/blocks/Button/ScrollTop.vue +++ b/layers/components/blocks/Button/ScrollTop.vue @@ -26,7 +26,9 @@ const handleScrollToTop = () => { class="btn-top" sr-only="top" @click="handleScrollToTop" - /> + > + + diff --git a/layers/components/blocks/DatePicker.vue b/layers/components/blocks/DatePicker.vue index d68dc50..97479c3 100644 --- a/layers/components/blocks/DatePicker.vue +++ b/layers/components/blocks/DatePicker.vue @@ -436,7 +436,7 @@ onMounted(() => { > {{ t('Text_MonthYear', { month: month + 1, year: year }) }} - + @@ -34,4 +36,10 @@ const handlePlayClick = () => { 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; } +.btn-play:hover .icon { + @apply scale-[1.08]; +} +.icon { + @apply transition-transform duration-300 ease-spring; +} diff --git a/layers/components/atoms/Button/index.vue b/layers/components/atoms/Button/index.vue index 07b8b7c..146ead8 100644 --- a/layers/components/atoms/Button/index.vue +++ b/layers/components/atoms/Button/index.vue @@ -29,7 +29,7 @@ const componentTag = computed((): string => { case 'download': return props.href ? 'a' : 'button' case 'internal': - return 'AtomsLocaleLink' + return props.href ? 'AtomsLocaleLink' : 'button' default: return 'button' } @@ -49,9 +49,12 @@ const componentProps = computed(() => { } if (props.type === 'internal') { - return { - to: props.href, + if (props.href) { + return { + to: props.href, + } } + return {} } if (props.type === 'download') { @@ -87,17 +90,17 @@ const componentProps = computed(() => { @@ -121,6 +124,18 @@ const componentProps = computed(() => { @apply before:border-[rgba(0,0,0,0.1)] hover:before:border-[#999]; } +.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 { @apply opacity-50; diff --git a/layers/components/blocks/Button/Home.vue b/layers/components/blocks/Button/Home.vue index bad5983..cfd9293 100644 --- a/layers/components/blocks/Button/Home.vue +++ b/layers/components/blocks/Button/Home.vue @@ -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] 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]; +} diff --git a/layers/components/blocks/Button/ScrollTop.vue b/layers/components/blocks/Button/ScrollTop.vue index 94fef90..7e73f0d 100644 --- a/layers/components/blocks/Button/ScrollTop.vue +++ b/layers/components/blocks/Button/ScrollTop.vue @@ -36,4 +36,7 @@ const handleScrollToTop = () => { .btn-top { @apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat; } +.btn-top:hover :deep(.icon) { + @apply -translate-y-[3px]; +} diff --git a/layers/components/blocks/Button/SlideArrows.vue b/layers/components/blocks/Button/SlideArrows.vue index b96e7a9..9acdba9 100644 --- a/layers/components/blocks/Button/SlideArrows.vue +++ b/layers/components/blocks/Button/SlideArrows.vue @@ -45,3 +45,13 @@ const handleArrowClick = (direction: 'prev' | 'next') => { + + diff --git a/layers/components/layouts/EventNavigation.vue b/layers/components/layouts/EventNavigation.vue index d6120a8..f023273 100644 --- a/layers/components/layouts/EventNavigation.vue +++ b/layers/components/layouts/EventNavigation.vue @@ -9,6 +9,7 @@ const { locale } = useI18n() const { sendLog } = useAnalytics() const gameDomain = getGameDomain() + const analytics = { action_type: 'click', click_sarea: 'EventNavigation', @@ -132,6 +133,9 @@ 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 sm:top-5 md:top-6 md:right-[-48px]; } +.btn-control:hover :deep(.icon) { + @apply translate-x-[3px]; +} .event-navigation.is-closed { @apply translate-x-[calc(-100%+20px)] sm:translate-x-[calc(-100%+40px)]; diff --git a/layers/plugins/motion-stagger.client.ts b/layers/plugins/motion-stagger.client.ts index 07d0de6..55f95cb 100644 --- a/layers/plugins/motion-stagger.client.ts +++ b/layers/plugins/motion-stagger.client.ts @@ -1,152 +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() - const sectionObservers = new Map() - const sectionItems = new Map>() + const animatedItems = new Set() + const sectionObservers = new Map() + const sectionItems = new Map>() - // 섹션의 motion-item들을 애니메이션 - const animateSectionItems = (section: Element) => { + /** + * 섹션의 motion-item들을 애니메이션 + */ + const animateSectionItems = (section: SectionElement): void => { const items = sectionItems.get(section) - if (!items || items.size === 0) return + if (!items?.size) return const newItems = Array.from(items).filter(item => !animatedItems.has(item)) - if (newItems.length === 0) return + if (!newItems.length) return // 애니메이션 실행 newItems.forEach(item => animatedItems.add(item)) animate( newItems, - { opacity: 1, y: 0 } as DOMKeyframesDefinition, + ANIMATION_CONFIG.target as DOMKeyframesDefinition, { - delay: stagger(0.2), - duration: 0.5, - easing: [0.22, 1, 0.36, 1], + delay: stagger(ANIMATION_CONFIG.stagger), + duration: ANIMATION_CONFIG.duration, + easing: ANIMATION_CONFIG.easing, } as AnimationOptions ) } - // 섹션에 IntersectionObserver 등록 - const observeSection = (section: Element) => { + /** + * 섹션이 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 => { - entries.forEach(entry => { - if (entry.isIntersecting) { - animateSectionItems(section) - } - }) - }, - { threshold: 0.2 } - ) + const observer = new IntersectionObserver(entries => { + for (const entry of entries) { + if (entry.isIntersecting) { + animateSectionItems(section) + } + } + }, INTERSECTION_CONFIG) observer.observe(section) sectionObservers.set(section, observer) } - // 섹션이 viewport에 있는지 확인 - const isSectionInViewport = (section: Element): boolean => { - const rect = section.getBoundingClientRect() - const windowHeight = - window.innerHeight || document.documentElement.clientHeight - // threshold 0.2를 고려한 체크 - const visibleHeight = - Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0) - const sectionHeight = rect.height - return visibleHeight / sectionHeight >= 0.2 + /** + * 섹션에 아이템 등록 및 초기 애니메이션 체크 + */ + 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) { - // 초기 스타일 설정 (애니메이션 전) - el.style.opacity = '0' - el.style.transform = 'translateY(30px)' + setInitialStyles(el) - // 가장 가까운 section 찾기 - 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 - } - - // 섹션의 아이템 목록에 추가 - if (!sectionItems.has(section)) { - sectionItems.set(section, new Set()) - observeSection(section) - } - sectionItems.get(section)!.add(el) - - // 이미 viewport에 있는 경우 즉시 체크 (여러 번 체크) - const inViewport = isSectionInViewport(section) - - if (inViewport) { - // 즉시 실행 - requestAnimationFrame(() => { - animateSectionItems(section) - }) - - // 추가로 100ms 후 재확인 (비동기 렌더링 대응) - setTimeout(() => { - if (isSectionInViewport(section)) { - animateSectionItems(section) - } - }, 100) - - // 추가로 300ms 후 재확인 (느린 비동기 데이터 대응) - setTimeout(() => { - if (isSectionInViewport(section)) { - animateSectionItems(section) - } - }, 300) - } - }, - - unmounted(el: Element) { - const section = el.closest('section') + const section = findSection(el) if (!section) return - // 아이템 제거 - const items = sectionItems.get(section) - if (items) { - items.delete(el) - // 섹션에 아이템이 없으면 observer 정리 - if (items.size === 0) { - const observer = sectionObservers.get(section) - if (observer) { - observer.disconnect() - sectionObservers.delete(section) - } - sectionItems.delete(section) - } - } + registerItem(el, section) + }, - animatedItems.delete(el) + 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( - Array.from(document.querySelectorAll('section')) + const currentSections = new Set( + Array.from(document.querySelectorAll('section')) as SectionElement[] ) - sectionObservers.forEach((observer, section) => { + for (const [section, observer] of sectionObservers.entries()) { if (!currentSections.has(section)) { observer.disconnect() sectionObservers.delete(section) sectionItems.delete(section) } - }) + } }) } }) diff --git a/tailwind.config.ts b/tailwind.config.ts index 17fe0c2..7dd19de 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -11,7 +11,6 @@ export default { md: '1024px', // PC: 1024px ~ 1439px lg: '1440px', // Large PC: 1440px+ }, - spacing: {}, colors: { 'theme-foreground': 'var(--foreground)', 'theme-foreground-10': 'var(--foreground-10)', @@ -26,6 +25,9 @@ export default { 'theme-foreground-gray-750': 'var(--foreground-gray-750)', 'theme-foreground-gray-500': 'var(--foreground-gray-500)', }, + transitionTimingFunction: { + spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)', + }, }, }, } satisfies Config From 9ec38c3bcfad5b1764db598bba5aecf6f6b7453a Mon Sep 17 00:00:00 2001 From: clkim Date: Wed, 21 Jan 2026 15:57:52 +0900 Subject: [PATCH 12/33] =?UTF-8?q?fix.=20lnb=20=EB=AA=A8=EC=85=98=20?= =?UTF-8?q?=EA=B8=B0=ED=9A=8D=20=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/blocks/Lnb.vue | 37 ++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/layers/components/blocks/Lnb.vue b/layers/components/blocks/Lnb.vue index 7aff4f1..e949a9f 100644 --- a/layers/components/blocks/Lnb.vue +++ b/layers/components/blocks/Lnb.vue @@ -17,8 +17,10 @@ const OBSERVER_OPTIONS = { rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진 threshold: 0, } as const +const AUTO_HIDE_MS = 5000 -const isShowLnbWithScroll = ref(false) +let autoHideTimer: ReturnType | null = null +const isShowLnbWithScroll = ref(true) const activeSection = ref('') const lnbList = computed>( @@ -72,6 +74,20 @@ const handleIntersection = (entries: IntersectionObserverEntry[]) => { 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 element = document.getElementById(elementId) @@ -112,15 +128,28 @@ const handleLnbClick = (lnbItem: PageDataLnbMenu) => { } watch(directions, newVal => { - // 스크롤 업일 때만 표시, 다운이거나 멈춘 상태에서는 숨김 - isShowLnbWithScroll.value = newVal.top === true + // 스크롤 위로: 즉시 노출 + 5초 후 자동 숨김 + if (newVal.top === true) { + isShowLnbWithScroll.value = true + scheduleAutoHide() + return + } + + // 스크롤 아래로: 즉시 숨김 (딜레이 없음) + if (newVal.bottom === true) { + clearAutoHide() + isShowLnbWithScroll.value = false + } }) onMounted(() => { observeSections() + isShowLnbWithScroll.value = true + scheduleAutoHide() }) onUnmounted(() => { + clearAutoHide() observer.disconnect() }) @@ -193,7 +222,7 @@ onUnmounted(() => { z-index: -1; } .lnb-wrap.is-hidden { - @apply translate-x-[110%] delay-[5s]; + @apply translate-x-[110%]; } .lnb-main { @apply flex flex-col gap-4 items-end; From 1a842abe4bc307abe95048ce7ac7a9589e654282 Mon Sep 17 00:00:00 2001 From: clkim Date: Wed, 21 Jan 2026 17:25:04 +0900 Subject: [PATCH 13/33] =?UTF-8?q?fix.=20sns=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/blocks/Sns.vue | 97 +++++++++++++------ layers/components/layouts/EventNavigation.vue | 22 +++-- layers/layouts/promotion.vue | 4 +- 3 files changed, 83 insertions(+), 40 deletions(-) diff --git a/layers/components/blocks/Sns.vue b/layers/components/blocks/Sns.vue index fb19bd8..9bc0f07 100644 --- a/layers/components/blocks/Sns.vue +++ b/layers/components/blocks/Sns.vue @@ -42,11 +42,11 @@ const handleCopy = async () => { diff --git a/layers/components/atoms/Button/Play.vue b/layers/components/atoms/Button/Play.vue index eedc27d..946a705 100644 --- a/layers/components/atoms/Button/Play.vue +++ b/layers/components/atoms/Button/Play.vue @@ -22,7 +22,9 @@ const handlePlayClick = () => { :style="{ backgroundColor: props.bgColor }" @click="handlePlayClick" > - + + + Play @@ -34,4 +36,10 @@ const handlePlayClick = () => { 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; } +.btn-play:hover .icon { + @apply scale-[1.08]; +} +.icon { + @apply transition-transform duration-300 ease-spring; +} diff --git a/layers/components/atoms/Button/index.vue b/layers/components/atoms/Button/index.vue index 07b8b7c..146ead8 100644 --- a/layers/components/atoms/Button/index.vue +++ b/layers/components/atoms/Button/index.vue @@ -29,7 +29,7 @@ const componentTag = computed((): string => { case 'download': return props.href ? 'a' : 'button' case 'internal': - return 'AtomsLocaleLink' + return props.href ? 'AtomsLocaleLink' : 'button' default: return 'button' } @@ -49,9 +49,12 @@ const componentProps = computed(() => { } if (props.type === 'internal') { - return { - to: props.href, + if (props.href) { + return { + to: props.href, + } } + return {} } if (props.type === 'download') { @@ -87,17 +90,17 @@ const componentProps = computed(() => { @@ -121,6 +124,18 @@ const componentProps = computed(() => { @apply before:border-[rgba(0,0,0,0.1)] hover:before:border-[#999]; } +.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 { @apply opacity-50; diff --git a/layers/components/blocks/Button/Home.vue b/layers/components/blocks/Button/Home.vue index bad5983..cfd9293 100644 --- a/layers/components/blocks/Button/Home.vue +++ b/layers/components/blocks/Button/Home.vue @@ -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] 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]; +} diff --git a/layers/components/blocks/Button/ScrollTop.vue b/layers/components/blocks/Button/ScrollTop.vue index 94fef90..7e73f0d 100644 --- a/layers/components/blocks/Button/ScrollTop.vue +++ b/layers/components/blocks/Button/ScrollTop.vue @@ -36,4 +36,7 @@ const handleScrollToTop = () => { .btn-top { @apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat; } +.btn-top:hover :deep(.icon) { + @apply -translate-y-[3px]; +} diff --git a/layers/components/blocks/Button/SlideArrows.vue b/layers/components/blocks/Button/SlideArrows.vue index b96e7a9..9acdba9 100644 --- a/layers/components/blocks/Button/SlideArrows.vue +++ b/layers/components/blocks/Button/SlideArrows.vue @@ -45,3 +45,13 @@ const handleArrowClick = (direction: 'prev' | 'next') => { + + diff --git a/layers/components/layouts/EventNavigation.vue b/layers/components/layouts/EventNavigation.vue index d6120a8..f023273 100644 --- a/layers/components/layouts/EventNavigation.vue +++ b/layers/components/layouts/EventNavigation.vue @@ -9,6 +9,7 @@ const { locale } = useI18n() const { sendLog } = useAnalytics() const gameDomain = getGameDomain() + const analytics = { action_type: 'click', click_sarea: 'EventNavigation', @@ -132,6 +133,9 @@ 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 sm:top-5 md:top-6 md:right-[-48px]; } +.btn-control:hover :deep(.icon) { + @apply translate-x-[3px]; +} .event-navigation.is-closed { @apply translate-x-[calc(-100%+20px)] sm:translate-x-[calc(-100%+40px)]; diff --git a/layers/components/widgets/ButtonList.vue b/layers/components/widgets/ButtonList.vue index faf9e51..ab5b7ee 100644 --- a/layers/components/widgets/ButtonList.vue +++ b/layers/components/widgets/ButtonList.vue @@ -122,7 +122,8 @@ const handleButtonClick = (button: PageDataResourceGroup) => {