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:
2026-08-21 10:51:26 +09:00
parent a838af4b94
commit 1470806e06
10 changed files with 424 additions and 8 deletions
+11
View File
@@ -7,3 +7,14 @@
# Binary zip archives
*.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 <<<
+133
View File
@@ -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
+4
View File
@@ -68,3 +68,7 @@ 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.
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 -->
+84
View File
@@ -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

+13
View File
@@ -172,6 +172,14 @@ body {
.draw-underline:hover {
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-"] {
animation: none !important;
}
/* 스마트 스티키: 모션 줄임 사용자에게는 항상 펼친 상태 유지 */
header[data-smart-sticky] {
transform: none !important;
transition: none !important;
}
html {
scroll-behavior: auto;
}
+17
View File
@@ -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

+1 -1
View File
@@ -34,7 +34,7 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="ko">
<html lang="ko" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
{/* No-JS fallback for the .reveal scroll-reveal: without this, every
+92 -5
View File
@@ -2,9 +2,14 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
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" },
@@ -16,8 +21,84 @@ const navItems = [
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 === "/"
@@ -26,7 +107,13 @@ export default function Header() {
: false;
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. */}
<div className="hidden border-b border-line desktop:block">
<div className="container-content flex items-center justify-between py-1.5">
@@ -39,7 +126,7 @@ export default function Header() {
</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 */}
<Link
href="/"
@@ -49,7 +136,7 @@ export default function Header() {
<span className="headline-ko text-lg text-ink sm:text-xl">
AI
</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)
</span>
</Link>
@@ -112,7 +199,7 @@ export default function Header() {
{open && (
<nav
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="모바일 메뉴"
>
<ul className="container-content flex flex-col py-2">
+67
View File
@@ -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')."