first commit

This commit is contained in:
2026-05-30 15:02:45 +09:00
commit 005719edee
212 changed files with 38830 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
// v-reveal 디렉티브 — 요소가 뷰포트에 들어오면 등장 애니메이션 트리거
// 사용법:
// v-reveal → 기본(아래→위 fade-up)
// v-reveal:scale → 확대 등장
// v-reveal:fade → 투명도만
// v-reveal="{ delay: 120 }" → 등장 지연(ms) — 스태거용
// v-reveal:up="{ delay: i*90 }" → 타입 + 지연 조합
//
// 디렉티브는 서버/클라이언트 양쪽에 등록(미등록 시 SSR 렌더 크래시)하되,
// IntersectionObserver 등 실제 동작은 클라이언트에서만 수행한다.
// getSSRProps 를 제공해 SSR 단계에서 안전하게 통과시킨다.
type RevealType = 'up' | 'scale' | 'fade'
interface RevealValue {
type?: RevealType
delay?: number
}
export default defineNuxtPlugin((nuxtApp) => {
let observer: IntersectionObserver | null = null
let reduceMotion = false
if (import.meta.client) {
reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add('is-revealed')
observer?.unobserve(entry.target)
}
}
},
// 요소가 화면 하단에서 약간 올라온 시점에 트리거
{ threshold: 0.15, rootMargin: '0px 0px -8% 0px' },
)
}
nuxtApp.vueApp.directive<HTMLElement, RevealValue | number | undefined>('reveal', {
// SSR: 추가 속성 없이 통과 (디렉티브 미등록으로 인한 크래시 방지)
getSSRProps: () => ({}),
mounted(el, binding) {
// 서버에서는 호출되지 않음. 모션 최소화 선호 시 애니메이션 없이 그대로 노출.
if (reduceMotion || !observer) return
const value = binding.value
const type: RevealType =
(binding.arg as RevealType) ||
(typeof value === 'object' && value?.type) ||
'up'
const delay = typeof value === 'number' ? value : value?.delay ?? 0
el.classList.add('reveal', `reveal-${type}`)
if (delay) el.style.setProperty('--reveal-delay', `${delay}ms`)
observer.observe(el)
},
unmounted(el) {
observer?.unobserve(el)
},
})
})