feat(header): add smart sticky scroll animation and styling refinements
- Implement RAF-throttled smart sticky scroll interaction on app header - Add flow-root BFC isolation, vertical margin, and mobile menu scroll container - Update brand logo kicker hover color to primary accent (cobalt-dark) - Fix theme script hydration warning with suppressHydrationWarning - Add favicon.ico and icon.svg to resolve root /favicon.ico 404 - Add MAM orchestration guidelines, env generation script, and templates
This commit is contained in:
+12
-1
@@ -6,4 +6,15 @@
|
|||||||
.agents/
|
.agents/
|
||||||
|
|
||||||
# Binary zip archives
|
# Binary zip archives
|
||||||
*.zip
|
*.zip
|
||||||
|
# >>> MAM managed block (managed by install.sh — do not edit) >>>
|
||||||
|
/.venv/
|
||||||
|
/.mam/
|
||||||
|
/.mam_deploy/
|
||||||
|
/.mam.env
|
||||||
|
/.mam.env.*
|
||||||
|
!/.mam.env.example
|
||||||
|
/.cache/multi-agent-mux-monitor/
|
||||||
|
/.mam-skill-backup.*/
|
||||||
|
CURRENT_JOB.md
|
||||||
|
# <<< MAM managed block <<<
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# .mam.env.example — committable template for the multi-agent-mux-* skills
|
||||||
|
#
|
||||||
|
# This file is tracked in git and contains NO secrets. To get a working local
|
||||||
|
# config, copy it to `.mam.env` (which is git-ignored) and edit as needed:
|
||||||
|
#
|
||||||
|
# scripts/generate-env.sh # creates .mam.env from this template if absent
|
||||||
|
# # or manually: cp .mam.env.example .mam.env
|
||||||
|
#
|
||||||
|
# Every variable below is OPTIONAL. The skills already resolve sane defaults
|
||||||
|
# (shown after each `#default:` line), so an unset/commented variable just keeps
|
||||||
|
# the built-in behaviour. Uncomment + edit only the ones you want to override.
|
||||||
|
#
|
||||||
|
# SECURITY: never put real secrets in this template. Secret-bearing vars use a
|
||||||
|
# `replace_me` placeholder — fill them in only in your local `.mam.env`.
|
||||||
|
# SECURITY NOTE:
|
||||||
|
# The default MQTT broker (broker.hivemq.com) is public and unencrypted. Job event
|
||||||
|
# payloads (including task details) sent to the default broker can be read by anyone.
|
||||||
|
# For production or private workloads, configure a private broker with TLS and Auth below.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Workspace / runtime paths
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
# Single source of truth for the agent session registry YAML.
|
||||||
|
#default: <workspace>/.mam/agent-sessions.yaml
|
||||||
|
# AGENT_SESSIONS_YAML=/path/to/workspace/.mam/agent-sessions.yaml
|
||||||
|
|
||||||
|
# Root directory that holds Claude Code per-project conversation logs (*.jsonl).
|
||||||
|
#default: $HOME/.claude/projects
|
||||||
|
# CLAUDE_PROJECT_DIR=$HOME/.claude/projects
|
||||||
|
|
||||||
|
# Directory scanned for per-session launcher wrappers (~/.local/bin/<session>).
|
||||||
|
#default: $HOME/.local/bin
|
||||||
|
# LOCAL_BIN=$HOME/.local/bin
|
||||||
|
|
||||||
|
# Isolated Herdr session socket name (`herdr --session <name>`).
|
||||||
|
#default: mam-<workspace-slug>
|
||||||
|
# HERDR_SESSION_NAME=mam-multi-agent-mux
|
||||||
|
|
||||||
|
# Legacy Herdr server identifier (DEPRECATED: use HERDR_SESSION_NAME instead).
|
||||||
|
# Written into .mam.env by install.sh as an active default.
|
||||||
|
#default: multi-agent-mux
|
||||||
|
# HERDR_SERVER_NAME=multi-agent-mux
|
||||||
|
|
||||||
|
# Path to the Herdr Unix domain socket.
|
||||||
|
#default: $HOME/.config/herdr/herdr.sock
|
||||||
|
# HERDR_SOCKET_PATH=$HOME/.config/herdr/herdr.sock
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# delegate-job / MQTT broker
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
# MQTT broker host the delegate-job publisher/subscriber connects to.
|
||||||
|
#default: broker.hivemq.com
|
||||||
|
# MQTT_BROKER=broker.hivemq.com
|
||||||
|
|
||||||
|
# MQTT broker port. Written into .mam.env by install.sh as an active default.
|
||||||
|
#default: 1883
|
||||||
|
# MQTT_PORT=1883
|
||||||
|
|
||||||
|
# MQTT reconnect retry interval (seconds).
|
||||||
|
#default: 2
|
||||||
|
# MQTT_RETRY_INTERVAL=2
|
||||||
|
|
||||||
|
# MQTT maximum reconnect retries.
|
||||||
|
#default: 5
|
||||||
|
# MQTT_MAX_RETRIES=5
|
||||||
|
|
||||||
|
# Broker auth username. Leave unset for anonymous brokers.
|
||||||
|
#default: (unset → anonymous)
|
||||||
|
# MQTT_USERNAME=replace_me
|
||||||
|
|
||||||
|
# Broker auth password. SECRET — fill in only in your local .mam.env, never commit.
|
||||||
|
#default: (unset → anonymous)
|
||||||
|
# MQTT_PASSWORD=replace_me
|
||||||
|
|
||||||
|
# Prefix for generated MQTT client ids (publisher/subscriber/monitor).
|
||||||
|
#default: hermes
|
||||||
|
# MQTT_CLIENT_ID_PREFIX=hermes
|
||||||
|
|
||||||
|
# Log level for MAM runtime components (DEBUG, INFO, WARN, ERROR).
|
||||||
|
#default: INFO
|
||||||
|
# MAM_LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# Retention period (days) for delegate-job event logs.
|
||||||
|
#default: 7
|
||||||
|
# MAM_EVENT_RETENTION_DAYS=7
|
||||||
|
|
||||||
|
# Garbage collection threshold (minutes) for old message buffer logs.
|
||||||
|
#default: 60
|
||||||
|
# MAM_BUFFER_GC_MINUTES=60
|
||||||
|
|
||||||
|
# Marker string used by loop delegation guard (O-3).
|
||||||
|
#default: (built-in guard marker)
|
||||||
|
# MAM_LOOP_GUARD_MARKER=mam_loop_active
|
||||||
|
|
||||||
|
# Path to a CA bundle for TLS broker verification (set MQTT_TLS=1 to use TLS).
|
||||||
|
#default: (unset → no custom CA)
|
||||||
|
# MQTT_CA_CERTS=/path/to/ca.crt
|
||||||
|
|
||||||
|
# Client certificate for mutual-TLS brokers.
|
||||||
|
#default: (unset → no client cert)
|
||||||
|
# MQTT_CERTFILE=/path/to/client.crt
|
||||||
|
|
||||||
|
# Client private key for mutual-TLS brokers. SECRET — keep the key file private.
|
||||||
|
#default: (unset → no client key)
|
||||||
|
# MQTT_KEYFILE=/path/to/client.key
|
||||||
|
|
||||||
|
# Directory for delegate-job audit logs (sits beside .mam/jobs/).
|
||||||
|
#default: <cwd>/.mam/delegate_job_logs
|
||||||
|
# DELEGATE_JOB_LOGS_DIR=/path/to/workspace/.mam/delegate_job_logs
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# deploy / distribution source (for forks/mirrors)
|
||||||
|
# ==============================================================================
|
||||||
|
# Note: These variables are read from the execution environment by deployment scripts.
|
||||||
|
# Since deploy/install.sh runs before .mam.env exists, you must pass them via export
|
||||||
|
# or prepended variables (e.g. MAM_REPO_URL=... bash deploy/install.sh).
|
||||||
|
# If you run a private mirror, we strongly recommend configuring all three variables.
|
||||||
|
|
||||||
|
# Distribution repository URL (cloned during recovery steps).
|
||||||
|
#default: https://git.godopu.com/tmpl/multi-agent-mux.git
|
||||||
|
# MAM_REPO_URL=https://git.godopu.com/tmpl/multi-agent-mux.git
|
||||||
|
|
||||||
|
# Distribution archive download URL (used for bootstrap extraction).
|
||||||
|
#default: https://git.godopu.com/tmpl/multi-agent-mux/archive/main.tar.gz
|
||||||
|
# MAM_ARCHIVE_URL=https://git.godopu.com/tmpl/multi-agent-mux/archive/main.tar.gz
|
||||||
|
|
||||||
|
# Distribution update/installer script URL.
|
||||||
|
#default: https://git.godopu.com/tmpl/multi-agent-mux/raw/branch/main/deploy/install.sh
|
||||||
|
# MAM_INSTALLER_URL=https://git.godopu.com/tmpl/multi-agent-mux/raw/branch/main/deploy/install.sh
|
||||||
@@ -67,4 +67,8 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
|
|||||||
|
|
||||||
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||||
|
|
||||||
Read [MULTI_AGENT_RULES.md](.agents/MULTI_AGENT_RULES.md) (or [Korean version](.agents/MULTI_AGENT_RULES.ko.md)) and [multi-agent-mux-loop/SKILL.md](.agents/skills/multi-agent-mux-loop/SKILL.md) first before working and follow the instructions for orchestration and collaboration.
|
Read [MULTI_AGENT_RULES.md](.agents/MULTI_AGENT_RULES.md) (or [Korean version](.agents/MULTI_AGENT_RULES.ko.md)) and [multi-agent-mux-loop/SKILL.md](.agents/skills/multi-agent-mux-loop/SKILL.md) first before working and follow the instructions for orchestration and collaboration.
|
||||||
|
<!-- BEGIN MAM ORCHESTRATION -->
|
||||||
|
# 🤖 Multi-Agent Orchestration Guidelines
|
||||||
|
Please refer to [.agents/MULTI_AGENT_RULES.md](file://./.agents/MULTI_AGENT_RULES.md) for detailed collaborative rules and state flow constraints.
|
||||||
|
<!-- END MAM ORCHESTRATION -->
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
- `<p class="text-ink-mute text-base sm:text-lg leading-relaxed max-w-2xl">AI 에이전트 네트워크 연구실(ANL)은 멀티 AI 에이전트 오케스트레이션을 위해 서로 소통하고 협력하는 에이전트 네트워킹 기술과 표준 프레임워크 기술을 연구합니다.</p>`
|
||||||
|
|
||||||
|
- 문구 영어로 변경
|
||||||
|
|
||||||
|
- `<div class="pt-6 border-t border-line grid grid-cols-3 gap-4"><div><div class="font-serif text-3xl text-cobalt-dark font-normal">4</div><div class="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">Research Areas</div></div><div><div class="font-serif text-3xl text-cobalt-dark font-normal">220<!-- -->+</div><div class="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">Publications</div></div><div><div class="font-serif text-3xl text-ink font-normal">2026</div><div class="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">Issue Static</div></div></div>`
|
||||||
|
|
||||||
|
- 문구 수정
|
||||||
|
- 4 RESEARCH AREAS -> {Number of International Journals & Conference} International Journals & Conference
|
||||||
|
- 220+ PUBLICATIONS -> {Number of Patent} Patent
|
||||||
|
- 2026 ISSUE STATIC -> {Number of Standardization} Standardization Contributions
|
||||||
|
|
||||||
|
- `body > main > div > section.space-y-6 > div.flex.flex-col.sm\:flex-row.sm\:items-end.justify-between.gap-4.border-b.border-line.pb-4 > div > h2`
|
||||||
|
- 주요 연구 프로젝트 (Research Projects) -> Research Projects
|
||||||
|
|
||||||
|
- `"project-iot-standards`
|
||||||
|
- IoT Standards -> AIoT & AI-RAN
|
||||||
|
- `#project-iot-standards > div.space-y-4 > div.flex.items-start.justify-between.gap-3.border-b.border-line\/60.pb-3 > span` : 2026 ~ Present
|
||||||
|
- AIoT 관련 특히 Agent based IoT 관련 내용으로 수정
|
||||||
|
- `#project-iot-standards > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.flex.items-baseline.justify-between.gap-2 > span.text-cobalt-dark.font-bold`
|
||||||
|
- ISO/IEC JTC1/SC41 -> 한국연구재단 (NRF) 수정
|
||||||
|
- `#project-iot-standards > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.space-y-1\.5.pt-1` 부분 삭제
|
||||||
|
|
||||||
|
|
||||||
|
- `#project-ccis`
|
||||||
|
- CCIS -> MCM으로 수정
|
||||||
|
- `#project-ccis > div.space-y-4 > div.flex.items-start.justify-between.gap-3.border-b.border-line\/60.pb-3 > span` : 2026 ~ Present
|
||||||
|
- `#project-ccis > div.space-y-4 > p` : MCM 내용으로 수정
|
||||||
|
- `#project-ccis > div.space-y-4 > div.flex.flex-wrap.gap-1\.5.pt-1` : MCM 관련 키워드로 수정
|
||||||
|
- `#project-ccis > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.flex.items-baseline.justify-between.gap-2 > span.text-cobalt-dark.font-bold`
|
||||||
|
- "IEC/TC 100/TA 17" -> "IEC/TC 100/WG 12 supported by KEIT"
|
||||||
|
- `#project-ccis > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.space-y-1\.5.pt-1` 삭제
|
||||||
|
|
||||||
|
- `#project-vlc-iot`
|
||||||
|
- VLC-IoT -> MVQM으로 수정
|
||||||
|
- `#project-vlc-iot > div.space-y-4 > p` : MVQM 내용으로 수정
|
||||||
|
- `#project-vlc-iot > div.space-y-4 > div.flex.flex-wrap.gap-1\.5.pt-1` : MVQM 관련 키워드로 수정
|
||||||
|
- `#project-vlc-iot > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.flex.items-baseline.justify-between.gap-2 > span.text-cobalt-dark.font-bold`
|
||||||
|
- "ITU-T SG20" -> "IEC/TC 100/WG 12 supported by KEIT"
|
||||||
|
- `#project-vlc-iot > div.space-y-3.pt-3.border-t.border-line\/60.text-xs.font-mono > div.space-y-1\.5.pt-1` 삭제
|
||||||
|
|
||||||
|
- `body > main > div > section.space-y-8.pt-20.md\:pt-28.border-t.border-line > div.flex.justify-between.items-end > div:nth-child(1)`
|
||||||
|
- 영문화 작업을 위해 "RESEARCH AREAS" 로 수정
|
||||||
|
- `body > main > div > section.space-y-8.pt-20.md\:pt-28.border-t.border-line > div.flex.justify-between.items-end > div:nth-child(1) > div`
|
||||||
|
- 삭제
|
||||||
|
|
||||||
|
- `body > main > div > section.space-y-8.pt-20.md\:pt-28.border-t.border-line > div.flex.justify-between.items-end > div.font-mono.text-xs.text-ink-mute`
|
||||||
|
- 삭제
|
||||||
|
|
||||||
|
- `body > main > div > section.space-y-8.pt-20.md\:pt-28.border-t.border-line > div.grid.grid-cols-1.md\:grid-cols-2.lg\:grid-cols-2.gap-6`
|
||||||
|
- 삭제
|
||||||
|
|
||||||
|
- 다음 키워드들을 각각 카드 형태로 표현
|
||||||
|
- Quick UDP Internet Conenction (QUIC)
|
||||||
|
- QUIC-based Mobility and Multi-Streaming Support
|
||||||
|
- Services and Protocols for Internet-of-Things (IoT)
|
||||||
|
- Constrained Application Protocol (CoAP)
|
||||||
|
- In-Vehicle Infotainment (IVI)
|
||||||
|
- Optical Wireless or Visible Light Communications (OWC/VLC)
|
||||||
|
- Lighting Control Networks (PLASA E1.45) for LED-based VLC
|
||||||
|
- Mobility Management: Distributed Mobility Control
|
||||||
|
- Multicast Routing Protocols and Reliable Multicasting
|
||||||
|
- mobile SCTP (mSCTP): Soft Handover in Transport Layer
|
||||||
|
- Stream Control Transmission Protocol (SCTP)
|
||||||
|
- Telecommunication Optimization: Network Planning and Management
|
||||||
|
- TAC/TAL Configuration for Paging and Location Management
|
||||||
|
- Tabu Search and Genetic Algorithm
|
||||||
|
|
||||||
|
- 새로운 섹션 추가:
|
||||||
|
위치 : `body > main > div > section.space-y-8.pt-20.md\:pt-28.border-t.border-line > div.flex.justify-between.items-end > div:nth-child(1)` 아래
|
||||||
|
섹션명: International Standardizations
|
||||||
|
내용:
|
||||||
|
|
||||||
|
```
|
||||||
|
Along with the research activities, we are also in pursuit of the International Standardization,
|
||||||
|
from the market deployment perspective, in the following SDOs (Standard-Defining Organizations):
|
||||||
|
|
||||||
|
- IEC TC100: Multimedia Systems and Equipment
|
||||||
|
- ISO/IEC JTC1/SC41: Internet-of-Things (IoT) Applications
|
||||||
|
- ITU-T SG20: IoT Services
|
||||||
|
- ITU-T SG13: Mobility Management
|
||||||
|
- ISO/IEC JTC1/SC6: Reliable Multicasting
|
||||||
|
|
||||||
|
Please refer to [here](link-to-standardization-page) for details of our works on International Standardization so far.
|
||||||
|
```
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -172,6 +172,14 @@ body {
|
|||||||
.draw-underline:hover {
|
.draw-underline:hover {
|
||||||
background-size: 100% 2px;
|
background-size: 100% 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile menu scroll container for low-height / landscape viewports (F4) */
|
||||||
|
.mobile-menu-panel {
|
||||||
|
max-height: calc(100vh - 6rem); /* Fallback: browsers without dvh support */
|
||||||
|
max-height: calc(100dvh - 6rem); /* Modern: dynamic viewport height */
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain; /* Prevent chaining scroll to main page */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================================================================
|
/* ==================================================================
|
||||||
@@ -217,6 +225,11 @@ body {
|
|||||||
[class*="animate-"] {
|
[class*="animate-"] {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
}
|
}
|
||||||
|
/* 스마트 스티키: 모션 줄임 사용자에게는 항상 펼친 상태 유지 */
|
||||||
|
header[data-smart-sticky] {
|
||||||
|
transform: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
scroll-behavior: auto;
|
scroll-behavior: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="cobaltGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#4E77FF"/>
|
||||||
|
<stop offset="100%" stop-color="#2B4CDE"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="32" height="32" rx="7" fill="url(#cobaltGrad)"/>
|
||||||
|
<!-- Network Agent Nodes & Graph -->
|
||||||
|
<circle cx="16" cy="9" r="2.5" fill="#FFFFFF"/>
|
||||||
|
<circle cx="9" cy="22" r="2.5" fill="#FFFFFF"/>
|
||||||
|
<circle cx="23" cy="22" r="2.5" fill="#FFFFFF"/>
|
||||||
|
<circle cx="16" cy="17" r="2" fill="#FFD666"/>
|
||||||
|
|
||||||
|
<path d="M16 9 L9 22 M16 9 L23 22 M9 22 L23 22 M16 9 L16 17 M9 22 L16 17 M23 22 L16 17"
|
||||||
|
stroke="#FFFFFF" stroke-width="1.2" stroke-opacity="0.8" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 787 B |
@@ -34,7 +34,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="ko">
|
<html lang="ko" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||||
{/* No-JS fallback for the .reveal scroll-reveal: without this, every
|
{/* No-JS fallback for the .reveal scroll-reveal: without this, every
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import ThemeToggle from "./ThemeToggle";
|
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.
|
// CUSTOMIZATION HOOK: edit nav labels / routes here.
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ href: "/", ko: "홈", en: "Home", no: "01" },
|
{ href: "/", ko: "홈", en: "Home", no: "01" },
|
||||||
@@ -16,8 +21,84 @@ const navItems = [
|
|||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [hidden, setHidden] = useState(false);
|
||||||
const pathname = usePathname();
|
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) =>
|
const isActive = (href: string) =>
|
||||||
pathname
|
pathname
|
||||||
? href === "/"
|
? href === "/"
|
||||||
@@ -26,7 +107,13 @@ export default function Header() {
|
|||||||
: false;
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur">
|
<header
|
||||||
|
data-smart-sticky
|
||||||
|
onFocusCapture={() => 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. */}
|
{/* Journal issue strip — magazine masthead feel. */}
|
||||||
<div className="hidden border-b border-line desktop:block">
|
<div className="hidden border-b border-line desktop:block">
|
||||||
<div className="container-content flex items-center justify-between py-1.5">
|
<div className="container-content flex items-center justify-between py-1.5">
|
||||||
@@ -39,7 +126,7 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container-content flex h-[4.25rem] items-center justify-between gap-4">
|
<div className="container-content flex h-[4.25rem] items-center justify-between gap-4 my-2 sm:my-3">
|
||||||
{/* Brand logo */}
|
{/* Brand logo */}
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
@@ -49,7 +136,7 @@ export default function Header() {
|
|||||||
<span className="headline-ko text-lg text-ink sm:text-xl">
|
<span className="headline-ko text-lg text-ink sm:text-xl">
|
||||||
AI 에이전트 네트워크 연구실
|
AI 에이전트 네트워크 연구실
|
||||||
</span>
|
</span>
|
||||||
<span className="kicker mt-1 group-hover:text-vermillion">
|
<span className="kicker mt-1 transition-colors group-hover:text-cobalt-dark">
|
||||||
AI Agent Networking Lab (ANL)
|
AI Agent Networking Lab (ANL)
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -112,7 +199,7 @@ export default function Header() {
|
|||||||
{open && (
|
{open && (
|
||||||
<nav
|
<nav
|
||||||
id="mobile-menu"
|
id="mobile-menu"
|
||||||
className="border-t border-ink bg-ivory desktop:hidden"
|
className="mobile-menu-panel border-t border-ink bg-ivory desktop:hidden"
|
||||||
aria-label="모바일 메뉴"
|
aria-label="모바일 메뉴"
|
||||||
>
|
>
|
||||||
<ul className="container-content flex flex-col py-2">
|
<ul className="container-content flex flex-col py-2">
|
||||||
|
|||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# generate-env.sh — create a local .mam.env from the committed .mam.env.example template.
|
||||||
|
#
|
||||||
|
# Behaviour:
|
||||||
|
# - .mam.env absent → copy .mam.env.example to .mam.env, print the path.
|
||||||
|
# - .mam.env present → no-op (leaves your edits intact), exit 0.
|
||||||
|
# - .mam.env present --force → overwrite .mam.env from .mam.env.example (backs up to .mam.env.bak).
|
||||||
|
# - --migrate-legacy → explicitly rename existing .env to .mam.env if absent.
|
||||||
|
#
|
||||||
|
# Paths are resolved relative to this script (repo root = parent of scripts/),
|
||||||
|
# so it works regardless of the caller's cwd.
|
||||||
|
#
|
||||||
|
# Usage: scripts/generate-env.sh [--force] [--migrate-legacy] [-h|--help]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
SRC="$REPO_ROOT/.mam.env.example"
|
||||||
|
DST="$REPO_ROOT/.mam.env"
|
||||||
|
LEGACY_ENV="$REPO_ROOT/.env"
|
||||||
|
|
||||||
|
FORCE=0
|
||||||
|
MIGRATE=0
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--force) FORCE=1; shift ;;
|
||||||
|
--migrate-legacy) MIGRATE=1; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [--force] [--migrate-legacy]"
|
||||||
|
echo " Create .mam.env from .mam.env.example."
|
||||||
|
echo " --force overwrites an existing .mam.env (backs up to .mam.env.bak)."
|
||||||
|
echo " --migrate-legacy explicitly renames an existing legacy .env to .mam.env."
|
||||||
|
exit 0 ;;
|
||||||
|
*) echo "ERROR: unknown arg: $1" >&2; echo "Usage: $0 [--force] [--migrate-legacy]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$MIGRATE" = "1" ]; then
|
||||||
|
if [ -f "$DST" ]; then
|
||||||
|
echo "ERROR: cannot migrate: $DST already exists." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$LEGACY_ENV" ]; then
|
||||||
|
echo "ERROR: cannot migrate: legacy file $LEGACY_ENV not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mv -f "$LEGACY_ENV" "$DST"
|
||||||
|
chmod 0600 "$DST" 2>/dev/null || true
|
||||||
|
echo "migrated: $LEGACY_ENV -> $DST"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f "$SRC" ] || { echo "ERROR: template not found: $SRC" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [ -f "$DST" ] && [ "$FORCE" != "1" ]; then
|
||||||
|
echo "no-op: $DST already exists (use --force to overwrite)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$DST" ] && [ "$FORCE" = "1" ]; then
|
||||||
|
cp -p "$DST" "$DST.bak"
|
||||||
|
echo "backed up existing .mam.env -> $DST.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$SRC" "$DST"
|
||||||
|
chmod 0600 "$DST" 2>/dev/null || true
|
||||||
|
echo "created: $DST"
|
||||||
|
echo "Next: edit $DST and fill in any secrets (look for 'replace_me')."
|
||||||
Reference in New Issue
Block a user