"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useState, useRef, useEffect } from "react"; import ThemeToggle from "./ThemeToggle"; // CUSTOMIZATION HOOK — MOTION: 스마트 스티키 튜닝 const SCROLL_DELTA = 6; // px — 방향 전환 인식 최소 이동량(지터 방지) const HIDE_AFTER = 140; // px — 이 지점 이전에는 숨기지 않음(실측 데스크톱 헤더 높이 122.8px + 여유) const NAV_SETTLE_MS = 300; // ms — 라우트 전환 직후 방향 판정 유예 구간 // CUSTOMIZATION HOOK: edit nav labels / routes here. const navItems = [ { href: "/", ko: "홈", en: "Home", no: "01" }, { href: "/members", ko: "구성원", en: "Members", no: "02" }, { href: "/publications", ko: "논문", en: "Publications", no: "03" }, { href: "/lectures", ko: "강의", en: "Lectures", no: "04" }, { href: "/standardization", ko: "표준화", en: "Standardization", no: "05" }, ]; export default function Header() { const [open, setOpen] = useState(false); const [hidden, setHidden] = useState(false); const pathname = usePathname(); const lastY = useRef(0); const ticking = useRef(false); const navSettling = useRef(false); const openRef = useRef(open); openRef.current = open; useEffect(() => { // (A) 모션 줄임 사용자는 자동 숨김 자체를 비활성화 — DESIGN.md 접근성 원칙 if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; lastY.current = window.scrollY; const update = () => { ticking.current = false; const y = window.scrollY; // (B) 최상단 + iOS 러버밴드(음수) 구간 → 항상 표시 if (y <= 0) { setHidden(false); lastY.current = 0; return; } // (F-2) 라우트 전환 정착 구간: 전환이 유발한 스크롤 점프는 방향 판정에서 제외. // 기준점만 따라가고 hidden은 건드리지 않는다. if (navSettling.current) { lastY.current = y; return; } // (C) 모바일 메뉴가 열려 있으면 숨기지 않되, lastY는 계속 추적 // (추적하지 않으면 메뉴를 닫는 순간 헤더가 튀며 사라짐) if (openRef.current) { setHidden(false); lastY.current = y; return; } const delta = y - lastY.current; // (D) 미세 이동 무시. 이때 lastY를 갱신하지 않고 보존 if (Math.abs(delta) < SCROLL_DELTA) return; if (delta > 0 && y > HIDE_AFTER) { setHidden(true); // 아래로 → 숨김 } else if (delta < 0) { setHidden(false); // 위로 → 복귀 } lastY.current = y; }; const onScroll = () => { if (ticking.current) return; ticking.current = true; requestAnimationFrame(update); // 프레임당 1회로 제한 }; window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); // (F) 라우트 전환 대응 — persistent layout이라 hidden이 페이지를 넘어 생존한다. // 스크롤 이벤트 발화 여부와 무관하게 표시 상태를 보장한다. useEffect(() => { setHidden(false); lastY.current = window.scrollY; // 현재 실제 위치로 동기화 setOpen(false); // 뒤로/앞으로 이동 시 모바일 메뉴 잔존 방지 navSettling.current = true; const id = window.setTimeout(() => { navSettling.current = false; }, NAV_SETTLE_MS); return () => window.clearTimeout(id); }, [pathname]); const isActive = (href: string) => pathname ? href === "/" ? pathname === "/" : pathname === href || pathname.startsWith(`${href}/`) : false; return (
setHidden(false)} className={`flow-root sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur transition-transform duration-300 ease-out ${ hidden ? "-translate-y-full" : "translate-y-0" }`} > {/* Journal issue strip — magazine masthead feel. */}
KNU/CSE/ANL {/* CUSTOMIZATION HOOK: masthead tagline */} Kyungpook National University · School of Computer Science & Engineering Daegu / South Korea
{/* Brand logo */} setOpen(false)} >
ANL AI Agent Networking LAB
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
{/* Mobile controls (visible under 1240px) */}
{/* Mobile menu panel */} {open && ( )}
); }