feat.[SWV-806] 템플릿 모션 적용
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
<template>
|
||||
<component :is="componentTag" v-bind="componentProps" class="btn-circle">
|
||||
<slot />
|
||||
<span class="icon">
|
||||
<slot />
|
||||
</span>
|
||||
<span class="sr-only">{{ props.srOnly }}</span>
|
||||
</component>
|
||||
</template>
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,7 +22,9 @@ const handlePlayClick = () => {
|
||||
:style="{ backgroundColor: props.bgColor }"
|
||||
@click="handlePlayClick"
|
||||
>
|
||||
<AtomsIconsArrowRightFill />
|
||||
<span class="icon">
|
||||
<AtomsIconsArrowRightFill />
|
||||
</span>
|
||||
<span class="sr-only">Play</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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(() => {
|
||||
<AtomsIconsLongArrowRightLine
|
||||
v-if="props.type === 'internal'"
|
||||
:color="props.textColor"
|
||||
class="icon"
|
||||
class="icon icon-internal"
|
||||
/>
|
||||
<AtomsIconsWebLinkLine
|
||||
v-if="props.type === 'external'"
|
||||
:color="props.textColor"
|
||||
class="icon"
|
||||
class="icon icon-external"
|
||||
/>
|
||||
<AtomsIconsDownloadLine
|
||||
v-if="props.type === 'download'"
|
||||
:color="props.textColor"
|
||||
class="icon"
|
||||
class="icon icon-download"
|
||||
/>
|
||||
</span>
|
||||
</component>
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -45,3 +45,13 @@ const handleArrowClick = (direction: 'prev' | 'next') => {
|
||||
</AtomsButtonCircle>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.splide__arrow--prev:hover :deep(.icon) {
|
||||
@apply -translate-x-[3px];
|
||||
}
|
||||
|
||||
.splide__arrow--next:hover :deep(.icon) {
|
||||
@apply translate-x-[3px];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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)];
|
||||
|
||||
@@ -122,7 +122,8 @@ const handleButtonClick = (button: PageDataResourceGroup) => {
|
||||
<template>
|
||||
<div
|
||||
v-if="buttonList.length"
|
||||
class="flex flex-wrap justify-center items-center gap-3 md:gap-4"
|
||||
v-motion-stagger
|
||||
class="flex flex-wrap justify-center gap-3 md:gap-4"
|
||||
>
|
||||
<template v-for="(button, index) in buttonList" :key="index">
|
||||
<template v-if="button.btn_info?.detail?.btn_type === 'RUN'">
|
||||
|
||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p class="description">
|
||||
<p v-motion-stagger class="description">
|
||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>
|
||||
<h2 v-motion-stagger>
|
||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
@@ -12,7 +12,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="props.tag">
|
||||
<component :is="props.tag" v-motion-stagger>
|
||||
<BlocksVisualContent :resources-data="props.resourcesData" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -22,6 +22,7 @@ const handleVideoPlayClick = () => {
|
||||
|
||||
<template>
|
||||
<AtomsButtonPlay
|
||||
v-motion-stagger
|
||||
:bg-color="bgColor"
|
||||
:tracking="props.resourcesData.tracking"
|
||||
@click="handleVideoPlayClick"
|
||||
|
||||
199
layers/plugins/motion-stagger.client.ts
Normal file
199
layers/plugins/motion-stagger.client.ts
Normal file
@@ -0,0 +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<HTMLElement>()
|
||||
const sectionObservers = new Map<SectionElement, IntersectionObserver>()
|
||||
const sectionItems = new Map<SectionElement, Set<HTMLElement>>()
|
||||
|
||||
/**
|
||||
* 섹션의 motion-item들을 애니메이션
|
||||
*/
|
||||
const animateSectionItems = (section: SectionElement): void => {
|
||||
const items = sectionItems.get(section)
|
||||
if (!items?.size) return
|
||||
|
||||
const newItems = Array.from(items).filter(item => !animatedItems.has(item))
|
||||
if (!newItems.length) return
|
||||
|
||||
// 애니메이션 실행
|
||||
newItems.forEach(item => animatedItems.add(item))
|
||||
|
||||
animate(
|
||||
newItems,
|
||||
ANIMATION_CONFIG.target as DOMKeyframesDefinition,
|
||||
{
|
||||
delay: stagger(ANIMATION_CONFIG.stagger),
|
||||
duration: ANIMATION_CONFIG.duration,
|
||||
easing: ANIMATION_CONFIG.easing,
|
||||
} as AnimationOptions
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션이 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 => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
animateSectionItems(section)
|
||||
}
|
||||
}
|
||||
}, INTERSECTION_CONFIG)
|
||||
|
||||
observer.observe(section)
|
||||
sectionObservers.set(section, observer)
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션에 아이템 등록 및 초기 애니메이션 체크
|
||||
*/
|
||||
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) {
|
||||
setInitialStyles(el)
|
||||
|
||||
const section = findSection(el)
|
||||
if (!section) return
|
||||
|
||||
registerItem(el, section)
|
||||
},
|
||||
|
||||
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<SectionElement>(
|
||||
Array.from(document.querySelectorAll('section')) as SectionElement[]
|
||||
)
|
||||
|
||||
for (const [section, observer] of sectionObservers.entries()) {
|
||||
if (!currentSections.has(section)) {
|
||||
observer.disconnect()
|
||||
sectionObservers.delete(section)
|
||||
sectionItems.delete(section)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -247,7 +247,7 @@ const handlePreregistClick = () => {
|
||||
:resources-data="preSubTitleData"
|
||||
class="title-sm mt-2"
|
||||
/>
|
||||
<div class="flex flex-col gap-4 mt-8 sm:flex-row">
|
||||
<div v-motion-stagger class="flex flex-col gap-4 mt-8 sm:flex-row">
|
||||
<div
|
||||
v-if="preImgPreregistdData"
|
||||
class="max-w-[336px] md:max-w-[446px]"
|
||||
@@ -295,7 +295,10 @@ const handlePreregistClick = () => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5">
|
||||
<div
|
||||
v-motion-stagger
|
||||
class="flex gap-3 justify-center flex-wrap mt-8 md:gap-2.5"
|
||||
>
|
||||
<BlocksButtonLauncher
|
||||
type="duplication"
|
||||
platform="stove"
|
||||
@@ -355,6 +358,7 @@ const handlePreregistClick = () => {
|
||||
/>
|
||||
<div
|
||||
v-if="rewardImages.length"
|
||||
v-motion-stagger
|
||||
class="overflow-hidden w-[calc(100%+40px)] min-h-[228px] mt-6 mx-[-20px] sm:w-[calc(100%+80px)] sm:mx-[-40px] md:w-full md:min-h-[281px] md:mt-8 md:mx-auto"
|
||||
>
|
||||
<ClientOnly>
|
||||
|
||||
@@ -124,6 +124,7 @@ const getArticleUrl = (articleId: string) => {
|
||||
<ClientOnly>
|
||||
<WidgetsSlideDefault
|
||||
v-if="slideLength > 0"
|
||||
v-motion-stagger
|
||||
v-bind="splideOptions"
|
||||
:slide-item-length="slideLength"
|
||||
:arrows-data="arrowsData"
|
||||
|
||||
@@ -44,7 +44,7 @@ const buttonListData = computed(() => {
|
||||
:resources-data="subTitleData"
|
||||
class="title-sm max-w-[944px] mt-2"
|
||||
/>
|
||||
<div v-if="imgListData" class="img-container">
|
||||
<div v-if="imgListData" v-motion-stagger class="img-container">
|
||||
<div v-for="(item, index) in imgListData" :key="index" class="img-item">
|
||||
<AtomsImg
|
||||
v-if="getResourceSrc(item)"
|
||||
@@ -71,9 +71,9 @@ const buttonListData = computed(() => {
|
||||
|
||||
<style scoped>
|
||||
.img-container {
|
||||
@apply flex flex-col items-center justify-center gap-4 box-content mx-auto mt-[32px]
|
||||
w-[440px]
|
||||
md:w-[944px];
|
||||
@apply flex flex-col items-center justify-center gap-4 box-content mx-auto mt-[32px] w-full
|
||||
max-w-[440px]
|
||||
md:max-w-[944px];
|
||||
}
|
||||
.img-item {
|
||||
@apply w-full;
|
||||
|
||||
@@ -91,6 +91,7 @@ const getVideoSrc = (item: PageDataTemplateComponent) => {
|
||||
/>
|
||||
<AtomsVideo
|
||||
v-if="hasComponentGroup(item, 'video')"
|
||||
v-motion-stagger
|
||||
:src="getVideoSrc(item)"
|
||||
:play="currentSlideIndex === index"
|
||||
class="aspect-[16/10] w-[258px] mt-8 md:w-[496px] md:mt-10"
|
||||
|
||||
@@ -92,6 +92,7 @@ onBeforeUnmount(() => {
|
||||
class="title-md max-w-[944px]"
|
||||
/>
|
||||
<WidgetsSlideThumbnail
|
||||
v-motion-stagger
|
||||
:thumbnail-data="slideData"
|
||||
:pagination-data="paginationData"
|
||||
:drag="false"
|
||||
|
||||
@@ -70,6 +70,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
||||
/>
|
||||
<WidgetsSlideCenterFocus
|
||||
v-if="slideData"
|
||||
v-motion-stagger
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData?.length"
|
||||
:arrows-data="arrowsData"
|
||||
|
||||
@@ -80,6 +80,7 @@ const handleSplideMove = (
|
||||
/>
|
||||
<WidgetsSlideCenterHighlight
|
||||
v-if="slideData"
|
||||
v-motion-stagger
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData?.length"
|
||||
:arrows-data="arrowsData"
|
||||
|
||||
@@ -101,6 +101,7 @@ const slideItemSize = {
|
||||
<WidgetsVideoPlay v-if="videoPlayData" :resources-data="videoPlayData" />
|
||||
<WidgetsSlideCenterHighlight
|
||||
v-if="slideData && slideData.length > 0"
|
||||
v-motion-stagger
|
||||
:slide-item-size="slideItemSize"
|
||||
:slide-item-length="slideData.length"
|
||||
:arrows-data="arrowsData"
|
||||
|
||||
@@ -53,6 +53,7 @@ export default defineNuxtConfig({
|
||||
'@nuxtjs/tailwindcss',
|
||||
'nuxt-gtag',
|
||||
'@nuxtjs/device',
|
||||
'motion-v/nuxt',
|
||||
],
|
||||
extends: ['./layers'],
|
||||
alias: {
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"@vueuse/nuxt": "^13.6.0",
|
||||
"h3": "^1.15.4",
|
||||
"motion-v": "^1.8.1",
|
||||
"nuxt": "^4.0.3",
|
||||
"nuxt-gtag": "^4.0.0",
|
||||
"pinia": "^2.3.1",
|
||||
|
||||
58
pnpm-lock.yaml
generated
58
pnpm-lock.yaml
generated
@@ -44,6 +44,9 @@ importers:
|
||||
h3:
|
||||
specifier: ^1.15.4
|
||||
version: 1.15.4
|
||||
motion-v:
|
||||
specifier: ^1.8.1
|
||||
version: 1.8.1(@vueuse/core@13.9.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))
|
||||
nuxt:
|
||||
specifier: ^4.0.3
|
||||
version: 4.1.1(@parcel/watcher@2.5.1)(@types/node@24.3.1)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(terser@5.44.0)(typescript@5.9.2)(vite@7.1.4(@types/node@24.3.1)(jiti@2.5.1)(terser@5.44.0)(yaml@2.8.1))(vue-tsc@3.0.7(typescript@5.9.2))(yaml@2.8.1)
|
||||
@@ -2799,6 +2802,20 @@ packages:
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
framer-motion@12.25.0:
|
||||
resolution: {integrity: sha512-mlWqd0rApIjeyhTCSNCqPYsUAEhkcUukZxH3ke6KbstBRPcxhEpuIjmiUQvB+1E9xkEm5SpNHBgHCapH/QHTWg==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2935,6 +2952,9 @@ packages:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
hey-listen@1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
@@ -3385,6 +3405,18 @@ packages:
|
||||
mocked-exports@0.1.1:
|
||||
resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==}
|
||||
|
||||
motion-dom@12.24.11:
|
||||
resolution: {integrity: sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==}
|
||||
|
||||
motion-utils@12.24.10:
|
||||
resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==}
|
||||
|
||||
motion-v@1.8.1:
|
||||
resolution: {integrity: sha512-OS6ve/vdNlrKTmCAHy+lxujIVTggjs9nbzl1auWiewy49FthkpCs5Wwpf40+55Ko3mbTajlKadkTlQYysrnL4A==}
|
||||
peerDependencies:
|
||||
'@vueuse/core': '>=10.0.0'
|
||||
vue: '>=3.0.0'
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -7688,6 +7720,12 @@ snapshots:
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@12.25.0:
|
||||
dependencies:
|
||||
motion-dom: 12.24.11
|
||||
motion-utils: 12.24.10
|
||||
tslib: 2.8.1
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
@@ -7839,6 +7877,8 @@ snapshots:
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
hey-listen@1.0.8: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
http-assert@1.5.0:
|
||||
@@ -8293,6 +8333,24 @@ snapshots:
|
||||
|
||||
mocked-exports@0.1.1: {}
|
||||
|
||||
motion-dom@12.24.11:
|
||||
dependencies:
|
||||
motion-utils: 12.24.10
|
||||
|
||||
motion-utils@12.24.10: {}
|
||||
|
||||
motion-v@1.8.1(@vueuse/core@13.9.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)):
|
||||
dependencies:
|
||||
'@vueuse/core': 13.9.0(vue@3.5.21(typescript@5.9.2))
|
||||
framer-motion: 12.25.0
|
||||
hey-listen: 1.0.8
|
||||
motion-dom: 12.24.11
|
||||
vue: 3.5.21(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- '@emotion/is-prop-valid'
|
||||
- react
|
||||
- react-dom
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user