From 4cd42d017c338e8000ab7b2f8512a8ee4cf80bb4 Mon Sep 17 00:00:00 2001 From: clkim Date: Mon, 26 Jan 2026 11:19:36 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix.=EB=AA=A8=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/atoms/Button/Circle.vue | 9 +- layers/components/atoms/Button/Play.vue | 10 +- layers/components/atoms/Button/index.vue | 27 +- layers/components/blocks/Button/Home.vue | 3 + layers/components/blocks/Button/ScrollTop.vue | 3 + .../components/blocks/Button/SlideArrows.vue | 10 + layers/components/layouts/EventNavigation.vue | 4 + layers/plugins/motion-stagger.client.ts | 247 +++++++++++------- tailwind.config.ts | 4 +- 9 files changed, 207 insertions(+), 110 deletions(-) diff --git a/layers/components/atoms/Button/Circle.vue b/layers/components/atoms/Button/Circle.vue index 0c76db0..6b143a6 100644 --- a/layers/components/atoms/Button/Circle.vue +++ b/layers/components/atoms/Button/Circle.vue @@ -21,7 +21,6 @@ const componentTag = computed((): string => { return 'button' } }) - const componentProps = computed(() => { switch (props.type) { case 'link': @@ -34,7 +33,9 @@ const componentProps = computed(() => { @@ -51,4 +52,8 @@ const componentProps = computed(() => { .btn-circle:deep(svg) { @apply w-[20px] h-[20px] md:w-[24px] md:h-[24px]; } + +.icon { + @apply transition-transform duration-300 ease-spring; +} 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/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 fd520314911e979d2fceec336917fa958743fa4b Mon Sep 17 00:00:00 2001 From: clkim Date: Wed, 21 Jan 2026 15:57:52 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix.=20lnb=20=EB=AA=A8=EC=85=98=20=EA=B8=B0?= =?UTF-8?q?=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 a67028bab966216163a4d6934d699ff34b305384 Mon Sep 17 00:00:00 2001 From: clkim Date: Wed, 21 Jan 2026 17:31:00 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix.=20=EC=95=A1=EC=85=98=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EA=B7=B8=EB=9D=BC=EB=8D=B0=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/atoms/Button/index.vue | 12 ++++++++++++ layers/components/widgets/ButtonList.vue | 1 + 2 files changed, 13 insertions(+) diff --git a/layers/components/atoms/Button/index.vue b/layers/components/atoms/Button/index.vue index 146ead8..6547678 100644 --- a/layers/components/atoms/Button/index.vue +++ b/layers/components/atoms/Button/index.vue @@ -10,6 +10,7 @@ interface props { backgroundColor?: string textColor?: string disabled?: boolean + gradient?: boolean } const props = withDefaults(defineProps(), { @@ -19,6 +20,7 @@ const props = withDefaults(defineProps(), { target: '_self', textColor: 'var(--alternative-02)', disabled: false, + gradient: false, }) const componentTag = computed((): string => { @@ -85,6 +87,7 @@ const componentProps = computed(() => { }" :disabled="props.disabled" > + { .btn-base.size-extra-small .btn-content { @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% + ); +} diff --git a/layers/components/widgets/ButtonList.vue b/layers/components/widgets/ButtonList.vue index ab5b7ee..13d32f0 100644 --- a/layers/components/widgets/ButtonList.vue +++ b/layers/components/widgets/ButtonList.vue @@ -148,6 +148,7 @@ const handleButtonClick = (button: PageDataResourceGroup) => { :background-color="getColorCodeFromData(button.btn_info, 'btn')" :text-color="getColorCodeFromData(button.btn_info, 'txt')" :disabled="button.btn_info?.detail?.btn_type === 'DEACTIVE'" + :gradient="true" @click="handleButtonClick(button)" > {{ button.btn_info?.txt_btn_name }} From 26d4d75fcdfab43023d2b39186d1576a0d5edf20 Mon Sep 17 00:00:00 2001 From: clkim Date: Thu, 22 Jan 2026 10:00:50 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix.=20pc=EC=97=90=EC=84=9C=EB=8A=94=20mobi?= =?UTF-8?q?le=20gnb=20=EB=8B=AB=ED=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/layouts/Header.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/layers/components/layouts/Header.vue b/layers/components/layouts/Header.vue index 5ff43a4..28f0cda 100644 --- a/layers/components/layouts/Header.vue +++ b/layers/components/layouts/Header.vue @@ -174,6 +174,9 @@ onMounted(() => { // 화면 크기 변경 시 오버플로우 재계산 watch(width, () => { throttledCalculateOverflow() + if (isMenuOpen.value && breakpoints.value.isDesktop) { + handleMenuClose() + } }) }) From dc1ec9a4c8019408498401e46514d1513612188c Mon Sep 17 00:00:00 2001 From: clkim Date: Thu, 22 Jan 2026 10:03:39 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix.=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=EC=97=90=EC=84=9C=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=8B=9C=20'=EB=8B=AB=ED=9E=98'=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layers/components/layouts/EventNavigation.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/layers/components/layouts/EventNavigation.vue b/layers/components/layouts/EventNavigation.vue index f023273..f286f0f 100644 --- a/layers/components/layouts/EventNavigation.vue +++ b/layers/components/layouts/EventNavigation.vue @@ -7,8 +7,8 @@ import type { const { locale } = useI18n() const { sendLog } = useAnalytics() - const gameDomain = getGameDomain() +const breakpoints = useResponsiveBreakpoints() const analytics = { action_type: 'click', @@ -54,6 +54,9 @@ const toggleEventNavigation = () => { onMounted(async () => { eventNavigationList.value = await getEventNavigation() + if (breakpoints.value.isMobile) { + isEventNavigationOpen.value = false + } }) From c04a97ca3eecd3b21c0f12ab8c0bd175bcc68dcc Mon Sep 17 00:00:00 2001 From: clkim Date: Mon, 26 Jan 2026 15:11:47 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat.=20[SWV-807]=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8(BTN,=20TXT)=20=EB=8B=A8=EC=9C=84=EB=A1=9C=20?= =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.vue | 2 +- layers/components/atoms/Button/index.vue | 56 ++++++++++++++------ layers/components/blocks/Button/Launcher.vue | 25 ++++++--- layers/components/layouts/Header.vue | 3 ++ layers/components/widgets/ButtonList.vue | 2 + layers/stores/useGameDataStore.ts | 6 +-- layers/types/api/gameData.ts | 4 +- layers/types/api/pageData.ts | 6 +-- 8 files changed, 70 insertions(+), 34 deletions(-) diff --git a/app/app.vue b/app/app.vue index 91e3e28..37b0eab 100644 --- a/app/app.vue +++ b/app/app.vue @@ -96,7 +96,7 @@ const setupGameHead = (data: GameDataValue) => { const designTheme = data.design_theme === 1 ? 'light' : 'dark' const styleLinks = createStyleLinks( data.favicon_json, - data?.game_font_key_json?.font_path + data?.game_font_json?.font_path ) const styleCss = createStyleCss(data.key_color_json) diff --git a/layers/components/atoms/Button/index.vue b/layers/components/atoms/Button/index.vue index 6547678..c7452bf 100644 --- a/layers/components/atoms/Button/index.vue +++ b/layers/components/atoms/Button/index.vue @@ -11,6 +11,7 @@ interface props { textColor?: string disabled?: boolean gradient?: boolean + useGameFont?: boolean } const props = withDefaults(defineProps(), { @@ -21,8 +22,12 @@ const props = withDefaults(defineProps(), { textColor: 'var(--alternative-02)', disabled: false, gradient: false, + useGameFont: false, }) +const gameDataStore = useGameDataStore() +const { fontFamily } = storeToRefs(gameDataStore) + const componentTag = computed((): string => { switch (props.type) { case 'external': @@ -36,12 +41,6 @@ const componentTag = computed((): string => { return 'button' } }) -const backgroundColor = computed(() => { - if (props.backgroundColor) { - return props.backgroundColor - } - return props.variant === 'filled' ? 'var(--primary)' : 'white' -}) const componentProps = computed(() => { if (props.type === 'external' || props.type === 'link') { return { @@ -72,6 +71,30 @@ const componentProps = computed(() => { 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 = { + backgroundColor: props.backgroundColor ?? backgroundColor, + '--disabled-color': textColor.value, + } + + return style +}) +const textStyle = computed(() => { + const style: Record = { + color: textColor.value, + } + + if (props.useGameFont && fontFamily.value) { + style.fontFamily = fontFamily.value + } + + return style +})