fix. lnb 디자인 수정 적용

This commit is contained in:
clkim
2025-11-18 13:15:04 +09:00
parent 2550d609d1
commit d83e4058ad
3 changed files with 127 additions and 75 deletions

View File

@@ -2,6 +2,7 @@
@layer base { @layer base {
body { body {
@apply min-w-[320px] bg-black; @apply min-w-[320px] bg-black;
-webkit-font-smoothing: antialiased;
} }
body.scroll-lock { body.scroll-lock {
@apply overflow-hidden; @apply overflow-hidden;

View File

@@ -28,9 +28,6 @@
.use-base th { .use-base th {
@apply font-semibold bg-gray-50; @apply font-semibold bg-gray-50;
} }
.use-base tbody tr:nth-child(even) {
@apply bg-gray-50;
}
.use-base blockquote { .use-base blockquote {
@apply border-l-4 border-gray-300 pl-4 italic text-gray-700; @apply border-l-4 border-gray-300 pl-4 italic text-gray-700;

View File

@@ -1,17 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PageDataLnbMenu } from '#layers/types/api/pageData' import type { PageDataLnbMenu } from '#layers/types/api/pageData'
const { y: windowY } = useWindowScroll({ behavior: 'smooth' }) const { y: windowY, directions } = useWindowScroll({ behavior: 'smooth' })
const pageDataStore = usePageDataStore() const pageDataStore = usePageDataStore()
const breakpoints = useResponsiveBreakpoints() const breakpoints = useResponsiveBreakpoints()
const { pageData } = storeToRefs(pageDataStore) const { pageData } = storeToRefs(pageDataStore)
const observerOptions = { // 상수 정의
const HEADER_HEIGHT = 64
const OBSERVER_OPTIONS = {
root: null, root: null,
rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진 rootMargin: '-20% 0px -60% 0px', // 상단 20%, 하단 60% 마진
threshold: 0, threshold: 0,
} } as const
const isShowLnbWithScroll = ref(false)
const activeSection = ref<string>('') const activeSection = ref<string>('')
const lnbList = computed<Record<string, PageDataLnbMenu>>( const lnbList = computed<Record<string, PageDataLnbMenu>>(
@@ -39,8 +43,9 @@ const is1DepthActive = (lnbItem: PageDataLnbMenu): boolean => {
} }
// children 중 하나가 활성화된 경우 // children 중 하나가 활성화된 경우
if (lnbItem.children && Object.keys(lnbItem.children).length > 0) { const children = lnbItem.children
return Object.values(lnbItem.children).some( if (children && Object.keys(children).length > 0) {
return Object.values(children).some(
child => activeSection.value === child.page_ver_tmpl_name_en child => activeSection.value === child.page_ver_tmpl_name_en
) )
} }
@@ -48,54 +53,73 @@ const is1DepthActive = (lnbItem: PageDataLnbMenu): boolean => {
return false return false
} }
const observer = new IntersectionObserver(entries => { // IntersectionObserver 콜백: 교차하는 섹션들 중 가장 위에 있는 것을 활성화
const handleIntersection = (entries: IntersectionObserverEntry[]) => {
if (import.meta.server) return if (import.meta.server) return
// 교차하는 섹션들 중 가장 위에 있는 것을 활성화
const visibleEntries = entries const visibleEntries = entries
.filter(entry => entry.isIntersecting) .filter(entry => entry.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top) .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
if (visibleEntries.length > 0) { if (visibleEntries.length > 0) {
activeSection.value = visibleEntries[0].target.id const topEntry = visibleEntries[0]
activeSection.value = topEntry.target.id
}
} }
}, observerOptions)
const observer = new IntersectionObserver(handleIntersection, OBSERVER_OPTIONS)
// 요소 관찰 헬퍼 함수
const observeElement = (elementId: string) => {
const element = document.getElementById(elementId)
if (element) {
observer.observe(element)
}
}
// 섹션들을 관찰 시작
const observeSections = () => { const observeSections = () => {
if (import.meta.server) return if (import.meta.server) return
Object.values(lnbList.value).forEach(lnbItem => { Object.values(lnbList.value).forEach(lnbItem => {
// 1depth 관찰 // 1depth 관찰
const el = document.getElementById(lnbItem.page_ver_tmpl_name_en) observeElement(lnbItem.page_ver_tmpl_name_en)
if (el) {
observer.observe(el)
}
// 2depth 관찰 // 2depth 관찰
if (lnbItem.children && Object.keys(lnbItem.children).length > 0) { const children = lnbItem.children
Object.values(lnbItem.children).forEach(childItem => { if (children && Object.keys(children).length > 0) {
const childEl = document.getElementById(childItem.page_ver_tmpl_name_en) Object.values(children).forEach(childItem => {
if (childEl) { observeElement(childItem.page_ver_tmpl_name_en)
observer.observe(childEl)
}
}) })
} }
}) })
} }
// LNB 클릭 핸들러: 해당 섹션으로 스크롤
const handleLnbClick = (lnbItem: PageDataLnbMenu) => { const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
if (import.meta.server) return if (import.meta.server) return
const id = lnbItem.page_ver_tmpl_name_en const targetId =
const el = document.getElementById(id) lnbItem.page_ver_tmpl_name_en === ''
if (!el) return ? lnbItem?.children?.['1']?.page_ver_tmpl_name_en
: lnbItem.page_ver_tmpl_name_en
const elementTop = el.getBoundingClientRect().top const targetElement = document.getElementById(targetId)
if (!targetElement) return
// 헤더 높이를 고려한 스크롤 위치 계산
const elementTop = targetElement.getBoundingClientRect().top
const currentScrollY = window.scrollY const currentScrollY = window.scrollY
const headerHeight = 64 const targetScrollY = currentScrollY + elementTop - HEADER_HEIGHT
const targetScrollY = currentScrollY + elementTop - headerHeight
windowY.value = targetScrollY windowY.value = targetScrollY
} }
watch(directions, newVal => {
// 스크롤 업일 때만 표시, 다운이거나 멈춘 상태에서는 숨김
isShowLnbWithScroll.value = newVal.top === true
})
onMounted(() => { onMounted(() => {
observeSections() observeSections()
}) })
@@ -106,15 +130,15 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<template v-if="isShowLnb">
<div <div
v-if="isShowLnb" :class="['lnb-wrap', { 'is-hidden': !isShowLnbWithScroll }]"
class="lnb-wrap"
:style="{ :style="{
'--lnb-active-color': activeColor, '--lnb-active-color': activeColor,
'--lnb-disable-color': disableColor, '--lnb-disable-color': disableColor,
}" }"
> >
<ul class="main-list"> <ul class="lnb-main">
<li v-for="lnbItem in lnbList" :key="lnbItem.path_code"> <li v-for="lnbItem in lnbList" :key="lnbItem.path_code">
<button <button
v-dompurify-html="lnbItem.menu_name" v-dompurify-html="lnbItem.menu_name"
@@ -124,7 +148,7 @@ onUnmounted(() => {
></button> ></button>
<ul <ul
v-if="Object.keys(lnbItem.children || {}).length > 0" v-if="Object.keys(lnbItem.children || {}).length > 0"
class="sub-list" class="lnb-sub"
> >
<li v-for="subItem in lnbItem.children" :key="subItem.path_code"> <li v-for="subItem in lnbItem.children" :key="subItem.path_code">
<button <button
@@ -133,7 +157,8 @@ onUnmounted(() => {
:class="[ :class="[
'btn-2depth', 'btn-2depth',
{ {
'is-active': activeSection === subItem.page_ver_tmpl_name_en, 'is-active':
activeSection === subItem.page_ver_tmpl_name_en,
}, },
]" ]"
@click="handleLnbClick(subItem)" @click="handleLnbClick(subItem)"
@@ -144,41 +169,70 @@ onUnmounted(() => {
</ul> </ul>
</div> </div>
</template> </template>
</template>
<style scoped> <style scoped>
.lnb-wrap { .lnb-wrap {
@apply fixed top-1/2 right-0 py-12 pr-[42px] text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50; @apply fixed top-0 right-0 mt-[calc(var(--scroll-position,48px)+64px)] py-8 pr-10 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.4)_25%,rgba(0,0,0,0)_100%)] transition-transform duration-300 ease-in-out z-50;
} }
.main-list { .lnb-wrap:before {
content: '';
position: absolute;
top: 0;
left: -5%;
width: 105%;
height: 100%;
backdrop-filter: blur(5px);
mask-image: radial-gradient(
circle at right center,
#000 0%,
#000 40%,
transparent 80%
);
mask-size: 100% 100%;
mask-repeat: no-repeat;
z-index: -1;
}
.lnb-wrap.is-hidden {
@apply translate-x-[100%] delay-[800ms];
}
.lnb-main {
@apply flex flex-col gap-4 items-end; @apply flex flex-col gap-4 items-end;
} }
.lnb-main > li {
@apply flex flex-col items-end;
}
.btn-1depth { .btn-1depth {
@apply text-[15px] leading-[26px] tracking-[-0.54px]; @apply text-[15px] leading-[26px] tracking-[-0.54px];
} }
.sub-list { .lnb-sub {
@apply flex flex-col gap-2 items-end mt-4 mb-1 pr-[46px]; @apply flex flex-col gap-2 items-end mt-4 mb-1 pr-[16px];
} }
.btn-2depth { .btn-2depth {
@apply text-[14px] leading-[20px] tracking-[-0.42px]; @apply text-[14px] leading-[20px] tracking-[-0.42px];
} }
button { button {
@apply relative font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out; @apply flex items-center font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
} }
button:hover, button:hover,
button.is-active { button.is-active {
@apply text-[var(--lnb-active-color)]; @apply text-[var(--lnb-active-color)];
} }
button::before { button::after {
@apply content-[''] absolute top-1/2 rounded-full -translate-y-1/2 bg-transparent transition-all duration-300 ease-in-out; @apply content-[''] rounded-full ml-2 bg-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
} }
button.is-active::before { button.is-active::after {
@apply bg-[var(--lnb-active-color)]; @apply bg-[var(--lnb-active-color)];
} }
.btn-1depth::before { .btn-1depth::after {
@apply -right-4 w-1.5 h-1.5; @apply -right-4 w-1.5 h-1.5;
} }
.btn-2depth::before { .btn-2depth::after {
@apply -right-3.5 w-1 h-1; @apply -right-3.5 w-1 h-1;
} }
.main-promotion .lnb-wrap {
@apply mt-[calc(var(--scroll-position,48px)+64px+72px)];
}
</style> </style>