o2o-site-AEO/solution/site/src/sections/SiteHeader.tsx
Mina Choi 4260e20a70 [fix] site: 모달 닫기 스크롤 버그 수정 — 이용안내 체크인·체크아웃 병합, UI 다듬기
모달 onClose 가 inline 함수라 부모 리렌더마다 useEffect 가 다시 걸려
scrollY 를 0으로 덮어써 X 버튼으로 닫으면 페이지가 맨 위로 튀었다.

- lib/ui/Modal.tsx: onClose 를 ref 로 들고 [open] 에만 의존하도록 수정
  (BlogSection·ReviewSection 등 Modal 쓰는 7곳 전부 적용)
- sections/EssentialInfoSection.tsx: 체크인·체크아웃 행을 한 줄로 병합
- sections/GallerySection.tsx, UnitsSection.tsx, MobileTabBar.tsx, SiteFooter.tsx,
  items/*: 표시 폭·간격·라벨 정리
- lib/ui/Carousel.tsx: loop 이음매 간격 재점검
- scripts/prerender.ts: countUniqueContent 가 socialPosts(자체 출력)를 세지
  않도록 — 콘텐츠 0건 사이트가 게이트를 우회하던 경로

tsc 통과, vitest 100/102 passed (use-live-weather 실패 2건은 기존·무관)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 17:23:20 +09:00

131 lines
5.8 KiB
TypeScript

import {Menu, Phone} from 'lucide-react';
import {useSite} from '@site/lib/site-context';
import {SongPlayer} from '@site/sections/SongPlayer';
import {isSectionEnabled, stayBookingView, unitSpec} from '@site/lib/derive';
/**
* 상단 내비.
*
* ★ 메뉴를 <nav> + <a> 로 그대로 둔다. 자바스크립트로 만드는 메뉴는
* 크롤러가 링크 그래프를 못 읽는다.
* ★ 모바일 메뉴도 <details> 다. 상태를 리액트로 들면 하이드레이션 전에는 안 열리고,
* 그 사이에 누른 손님에게는 그냥 고장난 버튼이다.
* ★ 사이트가 한 장이라 전부 앵커다.
*/
export function SiteHeader() {
const payload = useSite();
const spec = unitSpec(payload);
/* ★ 실제 페이지 순서와 같은 순서로 둔다(HomePage.tsx 가 theme.sections 순서로 그린다).
실측: 여기 목록은 '축제 → 주변 정보 → 오시는 길' 인데 화면은 '오시는 길(map) →
축제 → 주변 정보(local)' 순으로 나갔다 — 메뉴를 누르면 아래로 가야 할 항목이 위로 갔다. */
const items = [
{label: '소개', href: '#about', show: isSectionEnabled(payload, 'intro')},
{label: '이용 정보', href: '#info', show: true},
{label: spec.label, href: '#units', show: payload.units.length > 0},
{label: '오시는 길', href: '#location', show: true},
{label: '축제', href: '#festival', show: (payload.local.festivals?.length ?? 0) > 0},
{label: '주변 정보', href: '#guide', show: isSectionEnabled(payload, 'local')},
{label: 'FAQ', href: '#faq', show: payload.faqs.length > 0},
].filter((item) => item.show);
return (
<header
className="border-line safe-t sticky top-0 z-40 w-full border-b backdrop-blur-md"
style={{backgroundColor: 'color-mix(in srgb, var(--tpl-surface, #fff) 88%, transparent)'}}
>
<div className="shell flex h-16 items-center justify-between gap-3">
<a href="#top" className="flex min-w-0 items-center gap-2.5">
<span
className="serif flex size-8 shrink-0 items-center justify-center rounded-md text-[length:var(--fs-sm)]"
style={{
backgroundColor: 'var(--color-brand)',
color: 'var(--tpl-bg, #fff)',
fontWeight: 'var(--tpl-heading-weight, 700)',
}}
aria-hidden
>
{payload.place.name.slice(0, 1)}
</span>
<span className="flex min-w-0 flex-col">
{/*
★ font-bold 를 뺐다 (2026-09-04, 사장님: "폰트가 헤더랑 맞지 않는다")
워드마크와 섹션 제목은 같은 --tpl-font-heading 을 쓴다. 그런데 1안의 간판체(Gugi)는
**굵기가 400 하나뿐**이라, 700 을 요구하면 브라우저가 가짜 굵게로 획을 뭉개 그린다.
같은 서체인데 헤더만 번져 보였다. 굵기는 템플릿이 정한 값을 따른다.
*/}
<span
className="serif truncate text-[length:var(--fs-lead)] leading-tight"
style={{fontWeight: 'var(--tpl-heading-weight, 700)'}}
>
{payload.place.name}
</span>
{payload.place.englishName && (
<span className="text-muted truncate text-[length:var(--fs-xs)] uppercase leading-tight tracking-[0.14em]">
{payload.place.englishName}
</span>
)}
</span>
</a>
<nav aria-label="주요 메뉴" className="hidden items-center gap-7 lg:flex">
{items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-muted py-1 text-[length:var(--fs-sm)] font-medium transition-colors hover:text-current"
>
{item.label}
</a>
))}
</nav>
<div className="flex shrink-0 items-center gap-1.5">
{/* 이 숙소의 노래. 곡이 없으면 아무것도 그리지 않는다(SongPlayer 머리주석). */}
<SongPlayer />
{payload.place.phone && (
<a
href={`tel:${payload.place.phone}`}
className="tap inline-flex items-center gap-1.5 rounded-lg px-3 text-[length:var(--fs-xs)] font-bold transition-opacity hover:opacity-100"
style={{backgroundColor: 'var(--color-brand)', color: 'var(--tpl-bg, #fff)'}}
>
<Phone className="size-4" />
<span className="hidden sm:inline">{payload.place.phone}</span>
<span className="sr-only sm:hidden"></span>
</a>
)}
{/* 좁은 화면의 전체 메뉴. 하단 탭은 네 자리뿐이라 소개·FAQ 로 갈 길이 여기밖에 없다. */}
<details className="relative lg:hidden">
<summary
className="tap flex cursor-pointer items-center justify-center rounded-lg marker:content-none [&::-webkit-details-marker]:hidden"
aria-label="메뉴 열기"
>
<Menu className="size-5" />
</summary>
<nav
aria-label="전체 메뉴"
className="border-line tpl-border absolute right-0 top-[calc(100%+0.5rem)] z-50 w-52 overflow-hidden rounded-xl border shadow-lg"
style={{backgroundColor: 'var(--tpl-surface, #fff)'}}
>
<ul className="divide-line divide-y">
{items.map((item) => (
<li key={item.label}>
<a
href={item.href}
className="tap flex items-center px-4 text-[length:var(--fs-sm)] font-medium"
>
{item.label}
</a>
</li>
))}
</ul>
</nav>
</details>
</div>
</div>
</header>
);
}