feat: complete ANL lab web application with lossless data migration and SSG routes

This commit is contained in:
2026-07-30 10:34:22 +09:00
parent 293431c1e8
commit ea26ef503e
23 changed files with 5326 additions and 882 deletions
@@ -0,0 +1,87 @@
import React from "react";
export function DirectionsBlock() {
return (
<section className="mt-20 pt-16 border-t border-line" id="directions">
<div className="flex items-center gap-3 mb-6">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Location & Contact
</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Left info column */}
<div className="lg:col-span-7 space-y-6">
<h2 className="font-serif text-3xl font-normal text-ink">
&
</h2>
<p className="text-ink-mute text-base leading-relaxed">
AI (ANL) IT대학 5 , .
</p>
<address className="not-italic space-y-4 pt-2 text-sm font-sans text-ink">
<div className="flex items-start gap-3">
<span className="font-mono text-xs uppercase tracking-wider text-ink-mute w-20 pt-0.5">
</span>
<div>
<p className="font-medium">
IT대학 5(415) 527
</p>
<p className="text-xs text-ink-mute mt-0.5">
School of Computer Science and Engineering, Kyungpook National University
</p>
</div>
</div>
<div className="flex items-center gap-3">
<span className="font-mono text-xs uppercase tracking-wider text-ink-mute w-20">
</span>
<span>
(<a href="mailto:sjkoh@knu.ac.kr" className="text-vermillion hover:underline">sjkoh@knu.ac.kr</a>)
</span>
</div>
<div className="flex items-start gap-3">
<span className="font-mono text-xs uppercase tracking-wider text-ink-mute w-20 pt-0.5">
</span>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs font-mono text-cobalt">
<a href="http://www.knu.ac.kr/" target="_blank" rel="noreferrer" className="hover:underline">
(KNU)
</a>
<a href="http://cse.knu.ac.kr/" target="_blank" rel="noreferrer" className="hover:underline">
</a>
<a href="http://it.knu.ac.kr/" target="_blank" rel="noreferrer" className="hover:underline">
IT대학
</a>
</div>
</div>
</address>
</div>
{/* Right card column */}
<div className="lg:col-span-5 bg-paper p-6 rounded-lg border border-line flex flex-col justify-between">
<div className="space-y-4">
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider">
Research Environment
</div>
<h3 className="font-serif text-xl font-normal text-ink">
AI Agent Networking Lab
</h3>
<p className="text-xs text-ink-mute leading-relaxed">
QUIC/gRPC .
</p>
</div>
<div className="mt-6 pt-4 border-t border-line flex justify-between items-center text-xs font-mono text-ink-mute">
<span>IT-5 Building #527</span>
<span className="text-vermillion font-bold">ANL Laboratory</span>
</div>
</div>
</div>
</section>
);
}
-313
View File
@@ -1,313 +0,0 @@
import type { Metadata } from "next";
import Link from "next/link";
import Reveal from "@/components/Reveal";
import Marquee from "@/components/Marquee";
import Counter from "@/components/Counter";
import SectionLabel from "@/components/SectionLabel";
import HeroComposition from "./_components/HeroComposition";
export const metadata: Metadata = {
title: "연구실 소개 (Intro)",
description:
"QUIC, gRPC, A2A 기반 에이전트 통신 및 멀티에이전트 오케스트레이션을 연구하는 경북대학교 AI 에이전트 네트워크 연구실 (ANL) 소개.",
};
// CUSTOMIZATION HOOK: the five research pillars (5 Pillars, Option A).
const pillars = [
{
no: "Pillar 01",
tag: "Agent Eng",
accent: "vermillion" as const,
ko: "AI 에이전트 3대 핵심 엔지니어링",
en: "Agent Core Engineering",
desc: "프롬프트, 컨텍스트, 하네스 엔지니어링을 결합하여 대화 상태 보존, 외부 기억 바인딩, 도구 연동 인터페이스를 설계하고 자율 추론 경로를 최적화합니다.",
points: [
"역할 페르소나 및 Few-shot 최적화",
"동적 컨텍스트 윈도우 & Memory 바인딩",
"물리적 도구(Tool Use) 하네스 연결",
],
},
{
no: "Pillar 02",
tag: "MCP & SKILL",
accent: "cobalt" as const,
ko: "AI Agent 기능 확장 프로토콜",
en: "Extensibility & Protocol",
desc: "Anthropic MCP 통일 호환 규격 및 코드+스키마+Few-shot이 패키징된 SKILL 모듈을 통해 에이전트의 로컬/원격 역량을 자율적으로 동적 확장합니다.",
points: [
"MCP (Model Context Protocol) 통일 규격",
"자율 확장 SKILL 모듈 패키징",
"CLI 기반 자율 코딩 에이전트 제어",
],
},
{
no: "Pillar 03",
tag: "Orchestration",
accent: "vermillion" as const,
ko: "멀티 에이전트 협업 및 오케스트레이션",
en: "Multi-Agent Systems",
desc: "단일 에이전트의 환각과 컨텍스트 인지 한계를 극복하기 위해 역할 세분화(Role Specialization) 및 이종 모델 교차 검증과 오케스트레이션 인프라를 구축합니다.",
points: [
"HERDR 백그라운드 프로세스 컨테이너 격리",
"crewAI 역할/목표 기반 크루 프로세스",
"LangGraph 상태 보존형 순환 그래프 제어",
],
},
{
no: "Pillar 04",
tag: "A2A & ACP",
accent: "cobalt" as const,
ko: "에이전트 상호운용성 개방형 표준",
en: "Interoperability Standards",
desc: "Linux Foundation A2A 및 IBM ACP 오픈 표준 기반으로 이종 에이전트 간 서비스 발견, 태스크 위임, Agent Card 프로필 명세를 구현합니다.",
points: [
"A2A (Agent-to-Agent) 통신 오픈 표준",
"ACP 서비스 발견 & 태스크 위임",
"Agent Card (/.well-known/agent-card.json)",
],
},
{
no: "Pillar 05",
tag: "AIoT & gRPC",
accent: "vermillion" as const,
ko: "AIoT & 고성능 통신 백본",
en: "High-Performance Backbone",
desc: "스마트 팩토리/팜 엣지 자원 제약 환경에서 경량 프로토콜(MQTT/CoAP)과 HTTP/2·HTTP/3 gRPC/QUIC 양방향 스트리밍을 결합한 하이브리드 백본을 설계합니다.",
points: [
"gRPC & QUIC 양방향 스트리밍 메시징",
"Protobuf 바이너리 직렬화 최적화",
"MQTT-gRPC 하이브리드 엣지 메쉬",
],
},
];
// CUSTOMIZATION HOOK: headline figures (animated counters).
// `value: 2026` is marked static so it renders as a fixed year, not a
// 0→2026 count-up, at the smaller `display-sm` scale so the 4-digit year
// does not push the cell padding. See LAYOUT_AUDIT #11 + #13.
const figures = [
{ value: pillars.length, label: "연구 기둥 · Research Pillars", suffix: "", size: "display" as const, static: false },
{ value: pillars.reduce((n, p) => n + p.points.length, 0), label: "세부 연구 주제 · Research Topics", suffix: "", size: "display" as const, static: false },
{ value: 2026, label: "Issue · 발행", suffix: "", size: "display-sm" as const, static: true },
];
// CUSTOMIZATION HOOK: research keyword ticker.
const keywords = [
"A2A PROTOCOL",
"MCP & SKILL",
"HERDR PROCESS CONTAINER",
"LANGGRAPH CYCLIC GRAPH",
"QUIC & GRPC STREAMING",
"MULTI-AGENT ORCHESTRATION",
"AIOT EDGE CONTROL",
"AGENT CARD SPEC",
];
const focusAreas = [
{ ko: "에이전트 통신 프로토콜 (A2A)", en: "A2A & ACP Protocols" },
{ ko: "멀티에이전트 오케스트레이션", en: "Multi-Agent Orchestration" },
{ ko: "MCP & SKILL 기능 확장", en: "MCP & SKILL Engineering" },
{ ko: "AIoT & gRPC/QUIC 백본", en: "AIoT & gRPC/QUIC Backbone" },
];
export default function IntroPage() {
return (
<>
{/* ============ COVER SPREAD ============ */}
<section className="relative overflow-hidden border-b border-ink">
<div className="container-content pb-10 pt-12 sm:pt-16">
<Reveal>
<SectionLabel index={1} label="The Cover" />
</Reveal>
<div className="mt-8 grid items-start gap-10 lg:grid-cols-12">
{/* Title & Masthead column */}
<div className="lg:col-span-7">
<Reveal delay={100}>
<p className="kicker"> · AI Agent Networking Lab (ANL)</p>
<h1 className="headline-ko mt-4 text-[clamp(2.5rem,8vw,5.5rem)] leading-[0.98] text-ink">
<br />
<span className="text-vermillion"></span>
</h1>
<p className="display mt-3 leading-[0.9]" aria-hidden>
Agents
<span className="block italic text-cobalt">in&nbsp;Motion</span>
</p>
</Reveal>
<Reveal delay={200}>
<p className="mt-8 max-w-prose text-base leading-relaxed text-ink-soft">
A2A·ACP HERDR·LangGraph
.
.
</p>
</Reveal>
<Reveal delay={260}>
<div className="mt-8 flex flex-wrap gap-3">
<Link
href="/publications"
className="bg-ink px-6 py-3 text-sm font-semibold uppercase tracking-[0.16em] text-ivory transition hover:bg-vermillion"
>
</Link>
<Link
href="/members"
className="border border-ink px-6 py-3 text-sm font-semibold uppercase tracking-[0.16em] text-ink transition hover:bg-ink hover:text-ivory"
>
</Link>
</div>
</Reveal>
</div>
{/* Abstract illustration column */}
<Reveal variant="scale" delay={200} className="lg:col-span-5">
<HeroComposition className="w-full" />
<p className="mt-3 text-right text-xs italic text-ink-mute">
Fig. 01 A2A × QUIC/gRPC
</p>
</Reveal>
</div>
</div>
{/* Keyword marquee band */}
<Reveal>
<Marquee
items={keywords}
className="border-y border-ink bg-ink py-3 font-display text-xl tracking-wide text-ivory"
/>
</Reveal>
</section>
{/* ============ FIGURES / COUNTERS ============ */}
<section className="border-b border-ink">
<div className="container-content grid gap-px bg-line sm:grid-cols-3">
{figures.map((f, i) => (
<Reveal key={f.label} delay={i * 100} className="bg-ivory">
<div className="px-6 py-10">
<Counter
value={f.value}
suffix={f.suffix}
static={f.static}
className={`${f.size} block text-ink`}
/>
<p className="kicker mt-3">{f.label}</p>
</div>
</Reveal>
))}
</div>
</section>
{/* ============ MISSION / PULL QUOTE ============ */}
<section className="border-b border-ink">
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={2} label="Mission" />
</Reveal>
<Reveal delay={100}>
<blockquote className="pull-quote mt-10 max-w-4xl text-ink">
, {" "}
<span className="text-vermillion"> </span>{" "}
<span className="text-cobalt"> </span> .
</blockquote>
</Reveal>
<Reveal delay={160}>
<p className="mt-6 max-w-prose text-sm leading-relaxed text-ink-mute">
{/* CUSTOMIZATION HOOK: marginalia / footnote */}
(A2A/ACP) (HERDR/LangGraph) , ·
.
</p>
</Reveal>
</div>
</section>
{/* ============ FIVE RESEARCH PILLARS ============ */}
<section className="border-b border-ink">
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={3} label="Five Research Pillars" />
</Reveal>
<div className="mt-12 grid gap-px bg-line sm:grid-cols-2 lg:grid-cols-6">
{pillars.map((p, i) => {
const isVerm = p.accent === "vermillion";
const span = `${i < 3 ? "lg:col-span-2" : "lg:col-span-3"}${i === 4 ? " sm:col-span-2" : ""}`;
return (
<Reveal
key={p.tag}
variant={i % 2 === 0 ? "left" : "right"}
delay={i * 80}
className={`bg-ivory ${span}`}
>
<article
className={`group flex h-full flex-col justify-between border border-ink p-8 transition-colors duration-500 sm:p-10 ${
isVerm
? "hover:bg-vermillion hover:text-ivory"
: "hover:bg-cobalt hover:text-ivory"
}`}
>
<div>
<div className="flex items-baseline justify-between">
<span className="kicker group-hover:text-ivory/70">{p.no}</span>
<span
className={`font-display text-2xl ${
isVerm ? "text-vermillion" : "text-cobalt"
} group-hover:text-ivory`}
>
{p.tag}
</span>
</div>
<h3 className="headline-ko mt-6 text-2xl leading-tight">
{p.ko}
</h3>
<p className="font-display mt-1 text-base italic opacity-70">
{p.en}
</p>
<p className="mt-5 text-sm leading-relaxed opacity-90">
{p.desc}
</p>
</div>
<ul className="mt-6 space-y-3 text-sm">
{p.points.map((pt) => (
<li key={pt} className="flex gap-3 border-t border-ink/20 pt-3 group-hover:border-ivory/20">
<span className="font-display text-xs opacity-60"></span>
<span>{pt}</span>
</li>
))}
</ul>
</article>
</Reveal>
);
})}
</div>
</div>
</section>
{/* ============ FOCUS AREAS ============ */}
<section>
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={4} label="Focus Areas" />
</Reveal>
<div className="mt-10 grid gap-px bg-line sm:grid-cols-2 lg:grid-cols-4">
{focusAreas.map((f, i) => (
<Reveal key={f.en} delay={i * 80} className="bg-ivory">
<div className="group flex h-full flex-col justify-between gap-8 p-7 transition-colors duration-500 hover:bg-ink hover:text-ivory">
<span className="font-display text-2xl text-ink-mute group-hover:text-vermillion">
{String(i + 1).padStart(2, "0")}
</span>
<div>
<p className="headline-ko text-lg">{f.ko}</p>
<p className="mt-1 text-xs uppercase tracking-[0.16em] text-ink-mute group-hover:text-ivory/60">
{f.en}
</p>
</div>
</div>
</Reveal>
))}
</div>
</div>
</section>
</>
);
}
+120 -93
View File
@@ -1,102 +1,129 @@
import type { Metadata } from "next";
import PageHeader from "@/components/PageHeader";
import Reveal from "@/components/Reveal";
import SectionLabel from "@/components/SectionLabel";
export const metadata: Metadata = {
title: "강의 (Lectures)",
description: "AI 에이전트 네트워크 연구실 (ANL) 지도교수가 담당하는 강의 목록 (placeholder).",
};
// CUSTOMIZATION HOOK: replace placeholder lectures below.
const lectures = [
{
code: "CSE3xx",
ko: "사물인터넷 개론",
en: "Introduction to IoT",
level: "학부 · Undergraduate",
accent: "vermillion" as const,
desc: "IoT 아키텍처, 센서·액추에이터, 통신 프로토콜(MQTT/CoAP)과 oneM2M 표준의 기본 개념을 다룹니다. 간단한 디바이스–서버 연동 실습을 포함합니다.",
},
{
code: "CSE4xx",
ko: "네트워크 프로토콜과 전송 기술",
en: "Network Protocols & Transport",
level: "학부/대학원 · Undergraduate / Graduate",
accent: "cobalt" as const,
desc: "TCP/UDP에서 QUIC까지 전송 계층의 발전과 설계 원리를 학습합니다. 스트림 다중화, 연결 마이그레이션, 혼잡 제어를 실험을 통해 분석합니다.",
},
{
code: "CSE6xx",
ko: "메타버스 상호운용성과 표준",
en: "Metaverse Interoperability & Standards",
level: "대학원 · Graduate",
accent: "ink" as const,
desc: "이종 메타버스 플랫폼 간 데이터 모델, 의미 보존 변환, W3C/oneM2M 표준을 다루는 세미나형 대학원 강의입니다. 최신 논문 리뷰와 프로젝트를 병행합니다.",
},
];
const accent: Record<string, { hover: string; text: string }> = {
vermillion: { hover: "hover:bg-vermillion hover:text-ivory", text: "text-vermillion" },
cobalt: { hover: "hover:bg-cobalt hover:text-ivory", text: "text-cobalt" },
ink: { hover: "hover:bg-ink hover:text-ivory", text: "text-ink" },
};
import React from "react";
import { semesters } from "../../content/lectures";
export default function LecturesPage() {
const recentSemesters = semesters.slice(0, 3);
const olderSemesters = semesters.slice(3);
return (
<>
<PageHeader
ko="강의"
en="Lectures"
index={4}
description="연구실에서 담당하는 학부·대학원 강의입니다. 아래 강의 정보는 예시(placeholder)이며 실제 개설 과목으로 교체할 수 있습니다."
/>
<div className="space-y-16 py-6">
{/* Header */}
<section className="space-y-3">
<div className="flex items-center gap-3">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Academic Courses
</span>
</div>
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
</h1>
<p className="text-ink-mute text-base max-w-2xl">
2016 2026 20 / .
</p>
</section>
<section>
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={1} label="Courses" />
</Reveal>
<div className="mt-12 grid gap-px bg-line lg:grid-cols-3">
{lectures.map((l, i) => {
const a = accent[l.accent];
return (
<Reveal
key={l.code}
variant={i === 0 ? "left" : i === 2 ? "right" : "up"}
delay={i * 100}
className="bg-ivory"
>
<article
className={`group flex h-full flex-col border border-ink p-8 transition-colors duration-500 sm:p-10 ${a.hover}`}
>
<div className="flex items-baseline justify-between">
<span className={`font-display text-xl ${a.text} group-hover:text-ivory`}>
{l.code}
</span>
<span className="font-display text-4xl text-ink-mute group-hover:text-ivory/70">
{String(i + 1).padStart(2, "0")}
</span>
</div>
<h3 className="headline-ko mt-6 text-2xl leading-tight">{l.ko}</h3>
<p className="font-display mt-1 text-lg italic opacity-70">{l.en}</p>
<p className={`mt-4 inline-flex w-fit border border-ink/30 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] group-hover:border-ivory/40`}>
{l.level}
</p>
<p className="mt-6 border-t border-ink/20 pt-6 text-sm leading-relaxed opacity-90 group-hover:border-ivory/20">
{l.desc}
</p>
</article>
</Reveal>
);
})}
{/* Recent 3 Semesters */}
<section className="space-y-6 pt-6 border-t border-line">
<div className="flex justify-between items-end">
<div>
<div className="font-mono text-xs text-vermillion uppercase tracking-widest font-bold">
Recent Courses ({recentSemesters.length} Semesters)
</div>
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
</h2>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{recentSemesters.map((sem, idx) => (
<div
key={idx}
className="bg-paper p-6 rounded-lg border border-line space-y-4 flex flex-col justify-between"
>
<div className="space-y-3">
<div className="flex justify-between items-center border-b border-line pb-2">
<span className="font-serif text-xl font-normal text-ink">
{sem.term} {sem.year}
</span>
<span className="font-mono text-xs text-vermillion font-bold">
Latest
</span>
</div>
<ul className="space-y-2 pt-1">
{sem.courses.map((c, cIdx) => (
<li key={cIdx} className="space-y-0.5">
<div className="font-serif text-base text-ink">
{c.en}
</div>
<div className="font-mono text-xs text-ink-mute">
Code: {c.code} {c.note && `(${c.note})`}
</div>
</li>
))}
</ul>
</div>
<div className="pt-3 border-t border-line/60 font-mono text-xs text-ink-mute">
Course Count: {sem.courses.length}
</div>
</div>
))}
</div>
</section>
</>
{/* Older Semesters (Native <details>/<summary> progressive disclosure) */}
<section className="space-y-6 pt-6 border-t border-line">
<details className="group border border-line rounded-lg bg-paper p-6 transition-all duration-200">
<summary className="flex justify-between items-center cursor-pointer list-none select-none">
<div className="space-y-1">
<span className="font-serif text-xl font-normal text-ink">
({olderSemesters.length} )
</span>
<p className="text-xs text-ink-mute font-mono">
2016 Fall ~ 2024 Spring
</p>
</div>
<span className="font-mono text-xs text-vermillion font-bold border border-vermillion px-3 py-1.5 rounded group-open:bg-vermillion group-open:text-ivory transition-colors">
<span className="group-open:hidden"> </span>
<span className="hidden group-open:inline"> </span>
</span>
</summary>
<div className="mt-6 pt-6 border-t border-line grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{olderSemesters.map((sem, idx) => (
<div
key={idx}
className="bg-ivory p-5 rounded border border-line space-y-3"
>
<div className="font-serif text-lg font-normal text-ink border-b border-line pb-1.5 flex justify-between">
<span>
{sem.term} {sem.year}
</span>
<span className="font-mono text-xs text-ink-mute">
{sem.courses.length} Courses
</span>
</div>
<ul className="space-y-2">
{sem.courses.map((c, cIdx) => (
<li key={cIdx} className="space-y-0.5">
<div className="font-serif text-sm text-ink">
{c.en}
</div>
<div className="font-mono text-[0.7rem] text-ink-mute">
Code: {c.code} {c.note && `(${c.note})`}
</div>
</li>
))}
</ul>
</div>
))}
</div>
</details>
</section>
</div>
);
}
+194 -155
View File
@@ -1,174 +1,213 @@
import type { Metadata } from "next";
import PageHeader from "@/components/PageHeader";
import Reveal from "@/components/Reveal";
import SectionLabel from "@/components/SectionLabel";
import Counter from "@/components/Counter";
export const metadata: Metadata = {
title: "구성원 (Members)",
description: "AI 에이전트 네트워크 연구실 (ANL) 지도교수 및 대학원·학부 연구원 소개 (placeholder).",
};
// CUSTOMIZATION HOOK: replace placeholder member data below.
const advisor = {
ko: "고석주 교수",
en: "Prof. Seok-Joo Koh",
role: "지도교수 · Principal Investigator",
desc: "QUIC/gRPC/A2A 에이전트 통신, 멀티에이전트 오케스트레이션, MCP & SKILL 확장, 네트워크 프로토콜. (sjkoh@knu.ac.kr)",
fields: ["Agent Communication (A2A)", "Multi-Agent Orchestration", "QUIC & gRPC Transport"],
};
const groups = [
{
ko: "박사과정 연구원",
en: "Ph.D. Students",
accent: "vermillion" as const,
members: [
{ name: "연구원 A (placeholder)", topic: "QUIC 기반 멀티에이전트 오케스트레이션" },
{ name: "연구원 B (placeholder)", topic: "메타버스 공통 정보 모델 (MCM)" },
],
},
{
ko: "석사과정 연구원",
en: "M.S. Students",
accent: "cobalt" as const,
members: [
{ name: "연구원 C (placeholder)", topic: "oneM2M 적합성 검증" },
{ name: "연구원 D (placeholder)", topic: "QUIC 스트림 다중화 성능 분석" },
{ name: "연구원 E (placeholder)", topic: "W3C WoT Thing Description 매핑" },
],
},
{
ko: "학부 연구원",
en: "Undergraduate Researchers",
accent: "ink" as const,
members: [
{ name: "연구원 F (placeholder)", topic: "테스트베드 개발" },
{ name: "연구원 G (placeholder)", topic: "데이터 시각화" },
],
},
];
// CUSTOMIZATION HOOK: headcount figures (animated counters).
const figures = [
{ value: 1, label: "지도교수 · Principal Investigator" },
{ value: 5, label: "대학원 연구원 · Graduate Members" },
{ value: 2, label: "학부 연구원 · Undergraduates" },
];
const accentText: Record<string, string> = {
vermillion: "text-vermillion",
cobalt: "text-cobalt",
ink: "text-ink",
};
import React from "react";
import { activeMembers, alumni } from "../../content/members";
export default function MembersPage() {
return (
<>
<PageHeader
ko="구성원"
en="Members"
index={2}
description="연구실 지도교수와 박사·석사·학부 연구원을 소개합니다. 아래 정보는 예시(placeholder)이며 실제 구성원 정보로 교체할 수 있습니다."
/>
const professor = activeMembers.find((m) => m.degree === "Professor");
const phdStudents = activeMembers.filter(
(m) => m.degree === "PhD" || m.degree === "PhDCandidate" || m.degree === "PhDStudent"
);
const msStudents = activeMembers.filter(
(m) => m.degree === "MSCandidate" || m.degree === "MSStudent"
);
{/* ============ HEADCOUNT FIGURES ============ */}
<section className="border-b border-ink">
<div className="container-content grid gap-px bg-line sm:grid-cols-3">
{figures.map((f, i) => (
<Reveal key={f.label} delay={i * 100} className="bg-ivory">
<div className="px-6 py-10">
<Counter value={f.value} className="display block text-ink" />
<p className="kicker mt-3">{f.label}</p>
</div>
</Reveal>
))}
return (
<div className="space-y-16 py-6">
{/* Header */}
<section className="space-y-3">
<div className="flex items-center gap-3">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
People & Researchers
</span>
</div>
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
</h1>
<p className="text-ink-mute text-base max-w-2xl">
16, IT 60 .
</p>
</section>
{/* ============ PRINCIPAL INVESTIGATOR ============ */}
<section className="border-b border-ink">
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={1} label="Principal Investigator" />
</Reveal>
{/* Professor Section */}
<section className="space-y-6 pt-6 border-t border-line">
<div className="font-mono text-xs text-vermillion uppercase tracking-widest font-bold">
Faculty
</div>
<div className="mt-10 grid gap-px lg:grid-cols-12">
<Reveal variant="left" className="lg:col-span-7">
<article className="group h-full border border-ink p-8 transition-colors duration-500 hover:bg-ink hover:text-ivory sm:p-10">
<span className="kicker group-hover:text-ivory/70">{advisor.role}</span>
<h2 className="headline-ko mt-5 text-4xl leading-tight">{advisor.ko}</h2>
<p className="font-display mt-1 text-2xl italic text-vermillion">
{advisor.en}
</p>
<p className="mt-6 max-w-prose text-sm leading-relaxed opacity-90">
{advisor.desc}
</p>
</article>
</Reveal>
<Reveal variant="right" delay={120} className="lg:col-span-5">
<div className="flex h-full flex-col gap-px bg-line">
{advisor.fields.map((field, i) => (
<div
key={field}
className="group flex items-center gap-5 bg-ivory px-7 py-6 transition-colors duration-500 hover:bg-paper"
>
<span className="font-display text-3xl text-ink-mute group-hover:text-vermillion">
{String(i + 1).padStart(2, "0")}
</span>
<span className="text-sm uppercase tracking-[0.16em] text-ink-soft">
{field}
</span>
</div>
))}
</div>
</Reveal>
<div className="bg-paper p-8 rounded-lg border border-line grid grid-cols-1 lg:grid-cols-12 gap-8 items-center">
<div className="lg:col-span-8 space-y-4">
<div className="flex items-baseline gap-3">
<h2 className="font-serif text-3xl font-normal text-ink">
{professor?.ko || "고석주"}
</h2>
<span className="font-mono text-sm text-ink-mute">
{professor?.en || "Seok-Joo Koh"}, Professor
</span>
</div>
<p className="text-sm text-ink-mute leading-relaxed">
· AI (ANL) .
QUIC/gRPC , A2A/ACP , MCP &amp; SKILL .
</p>
<div className="pt-4 border-t border-line/60 flex flex-wrap gap-4 text-xs font-mono">
<a
href="mailto:sjkoh@knu.ac.kr"
className="text-vermillion hover:underline"
>
sjkoh@knu.ac.kr
</a>
<span className="text-ink-mute"></span>
<a
href="https://github.com/sjkoh12"
target="_blank"
rel="noreferrer"
className="text-cobalt hover:underline"
>
GitHub @sjkoh12
</a>
</div>
</div>
<div className="lg:col-span-4 bg-ivory p-6 rounded border border-line text-xs space-y-2 font-mono">
<div className="text-ink font-bold border-b border-line pb-1">
Affiliation & Office
</div>
<p className="text-ink-mute">Kyungpook National University</p>
<p className="text-ink-mute">IT Building #5 Room 527</p>
<p className="text-ink-mute">Daegu, Republic of Korea</p>
</div>
</div>
</section>
{/* ============ RESEARCH GROUPS ============ */}
<section>
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={2} label="Researchers" />
</Reveal>
{/* Active Researchers Section */}
<section className="space-y-8 pt-6 border-t border-line">
<div className="flex justify-between items-end">
<div>
<div className="font-mono text-xs text-cobalt uppercase tracking-widest font-bold">
Active Researchers ({activeMembers.length} Members)
</div>
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
</h2>
</div>
</div>
{groups.map((g, gi) => (
<div key={g.en} className="mt-14 first:mt-12">
<Reveal>
<div className="flex items-baseline justify-between border-b border-ink pb-4">
<h3 className="headline-ko text-2xl text-ink sm:text-3xl">{g.ko}</h3>
<span
className={`font-display text-lg italic ${accentText[g.accent]}`}
>
{g.en}
{/* Ph.D. Candidates / Students */}
<div className="space-y-4">
<h3 className="font-mono text-xs uppercase tracking-wider text-ink-mute border-b border-line pb-2">
Ph.D. Course ({phdStudents.length})
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{phdStudents.map((mem, idx) => (
<div
key={idx}
className="bg-paper p-5 rounded border border-line space-y-2 hover:border-cobalt transition-colors"
>
<div className="flex justify-between items-baseline">
<span className="font-serif text-lg font-normal text-ink">
{mem.ko}
</span>
<span className="font-mono text-xs text-cobalt">
{mem.degree}
</span>
</div>
</Reveal>
<div className="mt-px grid gap-px bg-line sm:grid-cols-2">
{g.members.map((m, mi) => (
<Reveal key={m.name} delay={mi * 80} className="bg-ivory">
<article className="group flex h-full flex-col justify-between gap-8 p-7 transition-colors duration-500 hover:bg-ink hover:text-ivory">
<span className="font-display text-2xl text-ink-mute group-hover:text-vermillion">
{String(gi + 1).padStart(2, "0")}.{String(mi + 1).padStart(2, "0")}
</span>
<div>
<h4 className="headline-ko text-lg">{m.name}</h4>
<p className="mt-2 text-xs leading-relaxed text-ink-mute group-hover:text-ivory/60">
{m.topic}
</p>
</div>
</article>
</Reveal>
))}
<div className="font-mono text-xs text-ink-mute">{mem.en}</div>
{mem.affiliation && (
<div className="text-xs text-vermillion pt-1 border-t border-line/40">
with {mem.affiliation}
</div>
)}
</div>
</div>
))}
))}
</div>
</div>
{/* M.S. Candidates / Students */}
<div className="space-y-4 pt-4">
<h3 className="font-mono text-xs uppercase tracking-wider text-ink-mute border-b border-line pb-2">
M.S. Course ({msStudents.length})
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{msStudents.map((mem, idx) => (
<div
key={idx}
className="bg-paper p-5 rounded border border-line space-y-2 hover:border-cobalt transition-colors"
>
<div className="flex justify-between items-baseline">
<span className="font-serif text-lg font-normal text-ink">
{mem.ko}
</span>
<span className="font-mono text-xs text-cobalt">
{mem.degree}
</span>
</div>
<div className="font-mono text-xs text-ink-mute">{mem.en}</div>
{mem.affiliation && (
<div className="text-xs text-vermillion pt-1 border-t border-line/40">
with {mem.affiliation}
</div>
)}
</div>
))}
</div>
</div>
</section>
</>
{/* Alumni Section (Native <details>/<summary> progressive disclosure) */}
<section className="space-y-6 pt-6 border-t border-line">
<div className="flex justify-between items-end">
<div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-widest font-bold">
Alumni Inventory ({alumni.length} Graduates)
</div>
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
</h2>
</div>
</div>
<details className="group border border-line rounded-lg bg-paper p-6 transition-all duration-200">
<summary className="flex justify-between items-center cursor-pointer list-none select-none">
<div className="space-y-1">
<span className="font-serif text-xl font-normal text-ink">
{alumni.length}
</span>
<p className="text-xs text-ink-mute font-mono">
2007 2026 (Ph.D.) (M.S.)
</p>
</div>
<span className="font-mono text-xs text-vermillion font-bold border border-vermillion px-3 py-1.5 rounded group-open:bg-vermillion group-open:text-ivory transition-colors">
<span className="group-open:hidden"> </span>
<span className="hidden group-open:inline"> </span>
</span>
</summary>
<div className="mt-6 pt-6 border-t border-line grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{alumni.map((alum, idx) => (
<div
key={idx}
className="bg-ivory p-4 rounded border border-line space-y-1.5"
>
<div className="flex justify-between items-baseline">
<span className="font-serif text-base font-normal text-ink">
{alum.ko} ({alum.en})
</span>
<span className="font-mono text-xs text-ink-mute">
{alum.degree}
</span>
</div>
<div className="font-mono text-xs text-ink-mute flex justify-between">
<span>{alum.graduatedAt}</span>
{alum.major && <span>{alum.major}</span>}
</div>
{alum.currentPosition && (
<div className="text-xs text-cobalt pt-1 border-t border-line/40">
{alum.currentPosition}
</div>
)}
</div>
))}
</div>
</details>
</section>
</div>
);
}
+215
View File
@@ -0,0 +1,215 @@
import React from "react";
import HeroComposition from "./_components/HeroComposition";
import { DirectionsBlock } from "./_components/DirectionsBlock";
import { publications } from "../content/publications";
const PILLARS = [
{
no: "01",
en: "AI Agent Engineering",
ko: "AI 에이전트 3대 핵심 엔지니어링",
theme: "vermillion" as const,
topics: [
"프롬프트 엔지니어링 (System Prompt Optimization)",
"맥락 엔지니어링 (Dynamic Context & Memory Packaging)",
"하네스 엔지니어링 (Agent Control Loop & Sandbox Isolation)",
],
desc: "AI 에이전트의 구동 성능과 안전성을 좌우하는 프롬프트·맥락·하네스 3대 핵심 엔지니어링 기술을 연구합니다.",
},
{
no: "02",
en: "MCP & SKILL Architecture",
ko: "AI Agent 기능 확장 프로토콜",
theme: "cobalt" as const,
topics: [
"Model Context Protocol (MCP) 서버/클라이언트 아키텍처",
"SKILL 도구 캡슐화 및 재사용 패키징",
"CLI 에이전트 인터페이스 제어 및 웹 가동 제어",
],
desc: "에이전트가 외부 도구 및 시스템과 표준화된 방식으로 연동할 수 있도록 MCP와 SKILL 패키징 기술을 개발합니다.",
},
{
no: "03",
en: "Multi-Agent Orchestration",
ko: "멀티 에이전트 협업 및 오케스트레이션",
theme: "vermillion" as const,
topics: [
"HERDR 백그라운드 프로세스 컨테이너 격리 제어",
"crewAI 기반 역할 분담 및 태스크 위임 체계",
"LangGraph 순환 상태 그래프 기반 워크플로우 제어",
],
desc: "다중 에이전트 환경에서 독립적인 역할 분담과 선순환 오케스트레이션을 보장하는 자율 협업 플랫폼을 연구합니다.",
},
{
no: "04",
en: "A2A & ACP Open Protocols",
ko: "에이전트 상호운용성 개방형 표준",
theme: "cobalt" as const,
topics: [
"Agent-to-Agent (A2A) 프로토콜 규격 정의 및 검증",
"Agent Communication Protocol (ACP) 메시지 교환 규격",
"Agent Card (/.well-known/agent-card.json) 메타데이터 검증",
],
desc: "서로 다른 프레임워크 기반의 에이전트 간의 상호운용성을 보장하는 국제 표준 개방형 통신 프로토콜을 구현합니다.",
},
{
no: "05",
en: "High-Performance Networking Backbone",
ko: "AIoT & 고성능 통신 백본",
theme: "vermillion" as const,
topics: [
"gRPC / QUIC 양방향 스트리밍 에이전트 백본 통신",
"Protocol Buffers (Protobuf) 직렬화 통신 최적화",
"MQTT / CoAP 엣지 메시 통신망 통합",
],
desc: "초저지연 양방향 스트리밍 통신 인프라를 바탕으로 고성능 에이전트 백본 네트워크를 설계하고 실증합니다.",
},
];
export default function HomePage() {
const pubCount = publications.length;
return (
<div className="space-y-16 py-6">
{/* Hero Section */}
<section className="relative">
<div className="flex items-center gap-3 mb-4">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Kyungpook National University · AI Agent Networking Lab (ANL)
</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
<div className="lg:col-span-7 space-y-6">
<h1 className="font-serif text-4xl sm:text-5xl lg:text-6xl font-normal text-ink leading-tight">
<br />
<span className="italic font-serif text-vermillion"> </span>
</h1>
<p className="text-ink-mute text-base sm:text-lg leading-relaxed max-w-2xl">
AI (ANL) , MCP/SKILL ,
, A2A/ACP , gRPC/QUIC
5 .
</p>
{/* Metrics Row (Derived from data array, AC-13) */}
<div className="pt-6 border-t border-line grid grid-cols-3 gap-4">
<div>
<div className="font-serif text-3xl text-vermillion font-normal">
{PILLARS.length}
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">
Core Pillars
</div>
</div>
<div>
<div className="font-serif text-3xl text-cobalt font-normal">
{pubCount}
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">
Publications
</div>
</div>
<div>
<div className="font-serif text-3xl text-ink font-normal">
2026
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">
Issue Static
</div>
</div>
</div>
</div>
<div className="lg:col-span-5 border border-line bg-paper p-4 rounded-lg">
<HeroComposition />
</div>
</div>
</section>
{/* 5 Pillars Magazine Spread Grid */}
<section className="space-y-8 pt-8 border-t border-line">
<div className="flex justify-between items-end">
<div>
<div className="font-mono text-xs text-vermillion uppercase tracking-widest font-bold">
Research Pillars
</div>
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
5
</h2>
</div>
<div className="font-mono text-xs text-ink-mute">
Magazine Layout 0105
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6">
{PILLARS.map((pillar, idx) => {
const isVermillion = pillar.theme === "vermillion";
const colSpan = idx < 3 ? "lg:col-span-2" : "lg:col-span-3";
return (
<div
key={pillar.no}
className={`${colSpan} bg-paper p-6 rounded-lg border border-line flex flex-col justify-between hover:border-ink transition-colors duration-200`}
>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span
className={`font-mono text-sm font-bold ${
isVermillion ? "text-vermillion" : "text-cobalt"
}`}
>
[{pillar.no}]
</span>
<span className="font-mono text-xs text-ink-mute uppercase">
{pillar.en}
</span>
</div>
<h3 className="font-serif text-xl font-normal text-ink">
{pillar.ko}
</h3>
<p className="text-xs text-ink-mute leading-relaxed">
{pillar.desc}
</p>
<ul className="space-y-2 pt-2">
{pillar.topics.map((topic, tIdx) => (
<li
key={tIdx}
className="text-xs text-ink flex items-start gap-2"
>
<span
className={`inline-block w-1.5 h-1.5 rounded-full mt-1 ${
isVermillion ? "bg-vermillion" : "bg-cobalt"
}`}
></span>
<span>{topic}</span>
</li>
))}
</ul>
</div>
<div className="mt-6 pt-4 border-t border-line/60 flex justify-between items-center text-xs font-mono text-ink-mute">
<span>ANL Core Spec</span>
<span
className={
isVermillion ? "text-vermillion" : "text-cobalt"
}
>
Pillar #{pillar.no}
</span>
</div>
</div>
);
})}
</div>
</section>
{/* Directions Block */}
<DirectionsBlock />
</div>
);
}
@@ -0,0 +1,146 @@
import React from "react";
import { notFound } from "next/navigation";
import { CategoryNav } from "../_components/CategoryNav";
import { publications, patents } from "../../../content/publications";
import { PUB_CATEGORIES } from "../../../content/categories";
import { PubCategory } from "../../../content/types";
interface PageProps {
params: {
category: string;
};
}
// SSG static pre-rendering (● SSG) for Next.js 14
export function generateStaticParams() {
return PUB_CATEGORIES.map((cat) => ({
category: cat.slug,
}));
}
export default function PublicationCategoryPage({ params }: PageProps) {
const { category } = params;
const currentCat = PUB_CATEGORIES.find((c) => c.slug === category);
if (!currentCat) {
notFound();
}
const slug = category as PubCategory;
let records: any[] = [];
if (slug === "patent") {
records = patents.map((p) => ({
title: p.title,
inventors: p.inventors,
appNo: p.applicationNo,
appDate: p.applicationAt,
regNo: p.registrationNo,
regDate: p.registrationAt,
country: p.country,
publishedAt: p.publishedAt,
isPatent: true,
}));
} else {
records = publications
.filter((p) => p.category === slug)
.map((p) => ({
title: p.title,
venue: p.venue,
volume: p.volume,
publishedAt: p.publishedAt,
kci: p.kci,
isPatent: false,
}));
}
return (
<div className="space-y-16 py-6">
{/* Header */}
<section className="space-y-3">
<div className="flex items-center gap-3">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Publications Category
</span>
</div>
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
{currentCat.label}
</h1>
<p className="text-ink-mute text-base max-w-2xl">
{currentCat.desc} {records.length} .
</p>
</section>
{/* Category Nav */}
<CategoryNav currentCategory={slug} />
{/* Record List Section */}
<section className="space-y-6">
<div className="flex justify-between items-center border-b border-line pb-3">
<span className="font-mono text-xs text-ink-mute uppercase">
Showing {records.length} Pre-rendered Records
</span>
<span className="font-mono text-xs text-vermillion font-bold">
SSG Static HTML
</span>
</div>
{records.length === 0 ? (
<div className="bg-paper p-8 rounded-lg border border-line text-center space-y-3">
<p className="font-serif text-lg text-ink">
0.
</p>
<p className="text-xs text-ink-mute font-mono">
(D-01 )
</p>
</div>
) : (
<div className="space-y-4">
{records.map((rec, idx) => (
<div
key={idx}
className="bg-paper p-6 rounded-lg border border-line space-y-3 hover:border-vermillion transition-colors"
>
<div className="flex justify-between items-start gap-4">
<h3 className="font-serif text-xl font-normal text-ink leading-snug">
{rec.title}
</h3>
<span className="font-mono text-xs text-vermillion bg-ivory border border-line px-2.5 py-1 rounded shrink-0 font-bold">
{rec.publishedAt}
</span>
</div>
{rec.isPatent ? (
<div className="space-y-1 text-xs text-ink-mute font-mono">
<p className="text-ink">: {rec.inventors}</p>
<p>
: {rec.appNo} ({rec.country}, {rec.appDate})
</p>
{rec.regNo && (
<p className="text-cobalt font-bold">
: {rec.regNo} ({rec.regDate})
</p>
)}
</div>
) : (
<div className="space-y-1 text-xs text-ink-mute font-mono">
<p className="text-ink font-serif italic">{rec.venue}</p>
<div className="flex items-center gap-3">
<span>{rec.volume}</span>
{rec.kci && (
<span className="bg-cobalt text-ivory text-[0.65rem] px-1.5 py-0.5 rounded font-bold">
KCI
</span>
)}
</div>
</div>
)}
</div>
))}
</div>
)}
</section>
</div>
);
}
@@ -0,0 +1,45 @@
import React from "react";
import Link from "next/link";
import { PUB_CATEGORIES } from "../../../content/categories";
import { PubCategory } from "../../../content/types";
interface CategoryNavProps {
currentCategory?: PubCategory;
totalCount?: number;
}
export function CategoryNav({ currentCategory, totalCount }: CategoryNavProps) {
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-2 border-b border-line pb-4">
<Link
href="/publications"
className={`px-4 py-2 rounded text-xs font-mono tracking-wider transition-colors ${
!currentCategory
? "bg-ink text-ivory font-bold"
: "bg-paper text-ink border border-line hover:border-ink"
}`}
>
All Overview {!currentCategory && totalCount ? `(${totalCount})` : ""}
</Link>
{PUB_CATEGORIES.map((cat) => {
const isActive = currentCategory === cat.slug;
return (
<Link
key={cat.slug}
href={`/publications/${cat.slug}`}
className={`px-4 py-2 rounded text-xs font-mono tracking-wider transition-colors ${
isActive
? "bg-vermillion text-ivory font-bold"
: "bg-paper text-ink border border-line hover:border-vermillion"
}`}
>
{cat.label}
</Link>
);
})}
</div>
</div>
);
}
+124 -159
View File
@@ -1,173 +1,138 @@
import type { Metadata } from "next";
import PageHeader from "@/components/PageHeader";
import Reveal from "@/components/Reveal";
import SectionLabel from "@/components/SectionLabel";
import Counter from "@/components/Counter";
import Marquee from "@/components/Marquee";
import React from "react";
import Link from "next/link";
import { CategoryNav } from "./_components/CategoryNav";
import { publications, patents } from "../../content/publications";
import { PUB_CATEGORIES } from "../../content/categories";
export const metadata: Metadata = {
title: "논문 (Publications)",
description: "AI 에이전트 네트워크 연구실 (ANL)의 대표 논문 목록 (placeholder).",
};
export default function PublicationsIndexPage() {
const allRecords = [
...publications.map((p) => ({ ...p, type: "paper" as const })),
...patents.map((p) => ({
category: "patent" as const,
title: p.title,
venue: `발명자: ${p.inventors} (출원번호: ${p.applicationNo})`,
volume: p.registrationNo ? `등록번호: ${p.registrationNo}` : `출원국가: ${p.country}`,
publishedAt: p.publishedAt,
type: "patent" as const,
})),
];
// CUSTOMIZATION HOOK: replace placeholder publications below.
const publications = [
{
year: "2025",
type: "Journal",
title:
"메타버스 상호운용을 위한 공통 정보 모델 설계 및 oneM2M 매핑 (Design of a Common Information Model for Metaverse Interoperability over oneM2M)",
authors: "Hong G., Researcher A., et al.",
venue: "한국통신학회논문지 (J-KICS), Vol. 50, No. 3",
},
{
year: "2025",
type: "Conference",
title:
"QUIC-based Orchestration Architecture for Low-Latency Multi-Agent Communication",
authors: "Researcher B., Hong G.",
venue: "IEEE International Conference on Communications (ICC)",
},
{
year: "2024",
type: "Journal",
title:
"W3C WoT Thing Description를 활용한 이종 메타버스 자산의 의미 보존 변환 (Semantic-Preserving Mapping of Cross-Platform Metaverse Assets Using W3C WoT)",
authors: "Researcher C., Hong G., et al.",
venue: "정보과학회논문지 (KIISE Transactions), Vol. 51, No. 11",
},
{
year: "2024",
type: "Conference",
title:
"Stream Multiplexing Strategies for Agent Message Routing over QUIC",
authors: "Researcher D., Researcher B., Hong G.",
venue: "ACM/IEEE Symposium on Edge Computing (SEC)",
},
{
year: "2023",
type: "Conference",
title:
"oneM2M 기반 IoT 디바이스 상호운용성 적합성 검증 프레임워크 (A Conformance Testing Framework for oneM2M-based IoT Interoperability)",
authors: "Researcher E., Hong G.",
venue: "한국정보과학회 학술발표회 (KSC)",
},
];
// Sort by publishedAt descending
const sortedRecords = [...allRecords].sort(
(a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
);
// CUSTOMIZATION HOOK: venue ticker.
const venueTicker = [
"J-KICS",
"IEEE ICC",
"KIISE TRANSACTIONS",
"ACM/IEEE SEC",
"KSC",
"oneM2M",
"W3C WoT",
"IETF QUIC",
];
const recentRecords = sortedRecords.slice(0, 10);
// Accent per publication type, drawn from the editorial palette.
const typeAccent: Record<string, { text: string; rule: string }> = {
Journal: { text: "text-vermillion", rule: "bg-vermillion" },
Conference: { text: "text-cobalt", rule: "bg-cobalt" },
};
const getCategoryCount = (slug: string) => {
if (slug === "patent") return patents.length;
return publications.filter((p) => p.category === slug).length;
};
// Derived figures.
const journalCount = publications.filter((p) => p.type === "Journal").length;
const confCount = publications.filter((p) => p.type === "Conference").length;
const figures = [
{ value: publications.length, label: "전체 논문 · Total Publications" },
{ value: journalCount, label: "저널 · Journal" },
{ value: confCount, label: "학회 · Conference" },
];
export default function PublicationsPage() {
return (
<>
<PageHeader
ko="논문"
en="Publications"
index={3}
description="메타버스 상호운용성(MCM)과 QUIC 기반 멀티에이전트 통신 분야의 대표 논문입니다. 아래 목록은 예시(placeholder)입니다."
/>
<div className="space-y-16 py-6">
{/* Header */}
<section className="space-y-3">
<div className="flex items-center gap-3">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Research Output &amp; Patents
</span>
</div>
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
</h1>
<p className="text-ink-mute text-base max-w-2xl">
· 148 75 {allRecords.length} .
</p>
</section>
{/* Venue marquee band */}
<Reveal>
<Marquee
items={venueTicker}
reverse
className="border-b border-ink bg-ink py-3 font-display text-xl tracking-wide text-ivory"
/>
</Reveal>
{/* Category Nav */}
<CategoryNav totalCount={allRecords.length} />
{/* ============ FIGURES ============ */}
<section className="border-b border-ink">
<div className="container-content grid gap-px bg-line sm:grid-cols-3">
{figures.map((f, i) => (
<Reveal key={f.label} delay={i * 100} className="bg-ivory">
<div className="px-6 py-10">
<Counter value={f.value} className="display block text-ink" />
<p className="kicker mt-3">{f.label}</p>
{/* Category Cards Overview */}
<section className="space-y-6">
<h2 className="font-serif text-2xl font-normal text-ink">
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{PUB_CATEGORIES.map((cat) => {
const count = getCategoryCount(cat.slug);
return (
<div
key={cat.slug}
className="bg-paper p-6 rounded-lg border border-line flex flex-col justify-between hover:border-vermillion transition-colors"
>
<div className="space-y-3">
<div className="flex justify-between items-baseline">
<span className="font-mono text-xs text-vermillion font-bold uppercase">
Category
</span>
<span className="font-serif text-2xl font-normal text-ink">
{count}
</span>
</div>
<h3 className="font-serif text-xl font-normal text-ink">
{cat.label}
</h3>
<p className="text-xs text-ink-mute leading-relaxed">
{cat.desc}
</p>
</div>
<div className="mt-6 pt-4 border-t border-line flex justify-between items-center">
<span className="font-mono text-xs text-ink-mute">
SSG Pre-rendered
</span>
<Link
href={`/publications/${cat.slug}`}
className="font-mono text-xs text-vermillion font-bold hover:underline"
>
({count})
</Link>
</div>
</div>
</Reveal>
);
})}
</div>
</section>
{/* Recent 10 Highlight Records */}
<section className="space-y-6 pt-6 border-t border-line">
<div className="flex justify-between items-end">
<h2 className="font-serif text-2xl font-normal text-ink">
(Top 10)
</h2>
<span className="font-mono text-xs text-ink-mute">
Total {allRecords.length} Records
</span>
</div>
<div className="space-y-4">
{recentRecords.map((rec, idx) => (
<div
key={idx}
className="bg-paper p-5 rounded border border-line space-y-2"
>
<div className="flex justify-between items-start gap-4">
<h3 className="font-serif text-lg font-normal text-ink leading-snug">
{rec.title}
</h3>
<span className="font-mono text-xs text-vermillion bg-ivory border border-line px-2 py-0.5 rounded shrink-0">
{rec.publishedAt}
</span>
</div>
<div className="text-xs text-ink-mute font-mono">
{rec.venue}
</div>
{rec.volume && (
<div className="text-xs text-ink-mute font-mono">
{rec.volume}
</div>
)}
</div>
))}
</div>
</section>
{/* ============ PUBLICATION LIST ============ */}
<section>
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={1} label="Selected Works" />
</Reveal>
<ol className="mt-12 border-t border-ink">
{publications.map((p, i) => {
const accent = typeAccent[p.type] ?? {
text: "text-ink",
rule: "bg-ink",
};
// Reveal `as="li"` keeps the <ol> direct-child contract (HTML spec:
// <ol> may only contain <li>/<script>/<template>). See REVIEW.md §3 #1.
return (
<Reveal
as="li"
key={p.title}
delay={(i % 3) * 80}
className="group grid gap-6 border-b border-ink py-8 transition-colors duration-500 hover:bg-paper sm:grid-cols-12 sm:py-10"
>
{/* Index + year rail */}
<div className="flex items-baseline gap-4 sm:col-span-3 sm:flex-col sm:gap-3">
<span className="font-display text-5xl leading-none text-ink-mute">
{String(i + 1).padStart(2, "0")}
</span>
<span className="font-display text-2xl italic text-ink">
{p.year}
</span>
<span
className={`inline-flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em] ${accent.text}`}
>
<span className={`h-1.5 w-1.5 rounded-full ${accent.rule}`} />
{p.type}
</span>
</div>
{/* Title + meta */}
<div className="sm:col-span-9">
<h3 className="headline-ko text-xl leading-snug text-ink sm:text-2xl">
{p.title}
</h3>
<p className="mt-3 text-sm text-ink-soft">{p.authors}</p>
<p className="font-display text-sm italic text-ink-mute">
{p.venue}
</p>
</div>
</Reveal>
);
})}
</ol>
</div>
</section>
</>
</div>
);
}
+11
View File
@@ -0,0 +1,11 @@
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: "https://anl.knu.ac.kr/sitemap.xml",
};
}
+48
View File
@@ -0,0 +1,48 @@
import { MetadataRoute } from "next";
import { PUB_CATEGORIES } from "../content/categories";
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = "https://anl.knu.ac.kr";
const staticRoutes: MetadataRoute.Sitemap = [
{
url: `${baseUrl}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1.0,
},
{
url: `${baseUrl}/members`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${baseUrl}/publications`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.9,
},
{
url: `${baseUrl}/lectures`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.7,
},
{
url: `${baseUrl}/standardization`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.8,
},
];
const categoryRoutes: MetadataRoute.Sitemap = PUB_CATEGORIES.map((cat) => ({
url: `${baseUrl}/publications/${cat.slug}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.8,
}));
return [...staticRoutes, ...categoryRoutes];
}
+103 -159
View File
@@ -1,173 +1,117 @@
import type { Metadata } from "next";
import PageHeader from "@/components/PageHeader";
import Reveal from "@/components/Reveal";
import SectionLabel from "@/components/SectionLabel";
import Counter from "@/components/Counter";
import Marquee from "@/components/Marquee";
export const metadata: Metadata = {
title: "표준화 활동 (Standardization)",
description:
"oneM2M, W3C WoT, IETF QUIC, OMA LwM2M 등 국제 표준화 기구에서의 연구실 기여 활동 (placeholder).",
};
// CUSTOMIZATION HOOK: replace placeholder standardization activities below.
const bodies = [
{
org: "oneM2M",
full: "oneM2M Partnership Project",
area: "IoT 서비스 계층 표준",
accent: "vermillion" as const,
contributions: [
"메타버스 상호운용을 위한 공통 정보 모델(CIM) 기고 (placeholder)",
"디바이스 상호운용성 적합성 테스트 케이스 제안",
],
},
{
org: "W3C WoT",
full: "W3C Web of Things",
area: "Thing Description / 시맨틱",
accent: "cobalt" as const,
contributions: [
"메타버스 자산의 Thing Description 매핑 프로파일 제안 (placeholder)",
"의미 보존 변환 가이드라인 논의 참여",
],
},
{
org: "IETF QUIC",
full: "IETF QUIC Working Group",
area: "전송 프로토콜",
accent: "vermillion" as const,
contributions: [
"멀티에이전트 환경을 위한 스트림 우선순위 확장 검토 (placeholder)",
"연결 마이그레이션 활용 사례 기여",
],
},
{
org: "OMA LwM2M",
full: "OMA SpecWorks Lightweight M2M",
area: "경량 디바이스 관리",
accent: "cobalt" as const,
contributions: [
"엣지 에이전트 디바이스 관리 객체 정의 제안 (placeholder)",
"리소스 모델 상호운용성 검토",
],
},
];
// CUSTOMIZATION HOOK: standards-body ticker.
const orgTicker = [
"oneM2M",
"W3C WoT",
"IETF QUIC",
"OMA LwM2M",
"CONFORMANCE",
"INTEROPERABILITY",
"SEMANTIC MAPPING",
];
const totalContributions = bodies.reduce(
(n, b) => n + b.contributions.length,
0
);
const figures = [
{ value: bodies.length, label: "표준화 기구 · Standards Bodies" },
{ value: totalContributions, label: "기여 항목 · Contributions" },
{ value: 4, label: "기술 영역 · Technical Areas" },
];
const accentText: Record<string, string> = {
vermillion: "text-vermillion",
cobalt: "text-cobalt",
};
const accentDot: Record<string, string> = {
vermillion: "bg-vermillion",
cobalt: "bg-cobalt",
};
import React from "react";
import { standardsBodies } from "../../content/standards";
export default function StandardizationPage() {
const totalDocs = standardsBodies.reduce(
(acc, body) =>
acc + body.projects.reduce((pAcc, proj) => pAcc + proj.documents.length, 0),
0
);
return (
<>
<PageHeader
ko="표준화 활동"
en="Standardization"
index={5}
description="국제 표준화 기구와 협력하여 연구 결과를 표준에 반영합니다. 아래 기여 내역은 예시(placeholder)이며 실제 활동으로 교체할 수 있습니다."
/>
{/* Standards-body marquee band */}
<Reveal>
<Marquee
items={orgTicker}
className="border-b border-ink bg-ink py-3 font-display text-xl tracking-wide text-ivory"
/>
</Reveal>
{/* ============ FIGURES ============ */}
<section className="border-b border-ink">
<div className="container-content grid gap-px bg-line sm:grid-cols-3">
{figures.map((f, i) => (
<Reveal key={f.label} delay={i * 100} className="bg-ivory">
<div className="px-6 py-10">
<Counter value={f.value} className="display block text-ink" />
<p className="kicker mt-3">{f.label}</p>
</div>
</Reveal>
))}
<div className="space-y-16 py-6">
{/* Header */}
<section className="space-y-3">
<div className="flex items-center gap-3">
<span className="h-px w-8 bg-vermillion"></span>
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
Open Standards &amp; Contributions
</span>
</div>
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
</h1>
<p className="text-ink-mute text-base max-w-2xl">
IEC TC100, ISO/IEC JTC1, ITU-T, TTA / {standardsBodies.length} {totalDocs} .
</p>
</section>
{/* ============ CONTRIBUTIONS BY BODY ============ */}
<section>
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={1} label="Contributions" />
</Reveal>
{/* Standards Bodies Section */}
<section className="space-y-12 pt-6 border-t border-line">
{standardsBodies.map((body, idx) => (
<div key={idx} className="bg-paper p-8 rounded-lg border border-line space-y-6">
<div className="flex flex-wrap justify-between items-start gap-4 border-b border-line pb-4">
<div>
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-vermillion font-bold uppercase">
[{body.scope}]
</span>
{body.role && (
<span className="bg-vermillion text-ivory font-mono text-[0.65rem] px-2 py-0.5 rounded font-bold">
{body.role}
</span>
)}
</div>
<h2 className="font-serif text-2xl font-normal text-ink mt-1">
{body.org}
</h2>
<p className="text-xs text-ink-mute font-mono mt-0.5">
{body.full} ({body.period})
</p>
</div>
<div className="mt-12 grid gap-px bg-line sm:grid-cols-2">
{bodies.map((b, i) => (
<Reveal
key={b.org}
variant={i % 2 === 0 ? "left" : "right"}
delay={(i % 2) * 100}
className="bg-ivory"
>
<article className="group flex h-full flex-col border border-ink p-8 transition-colors duration-500 hover:bg-ink hover:text-ivory sm:p-10">
<div className="flex items-baseline justify-between gap-3">
<h3 className="headline-ko text-3xl">{b.org}</h3>
<span className="font-display text-4xl text-ink-mute group-hover:text-ivory/40">
{String(i + 1).padStart(2, "0")}
<div className="font-mono text-xs text-ink-mute bg-ivory border border-line px-3 py-1.5 rounded">
Projects: {body.projects.length}
</div>
</div>
{/* 3-Tier Hierarchy: Projects & Documents */}
<div className="space-y-6">
{body.projects.map((proj, pIdx) => (
<div key={pIdx} className="bg-ivory p-6 rounded border border-line space-y-3">
<div className="font-serif text-lg font-normal text-ink border-b border-line/60 pb-2 flex justify-between items-center">
<div>
<span className="font-mono text-xs text-cobalt font-bold mr-2">
{proj.wg}
</span>
<span>{proj.name}</span>
</div>
<span className="font-mono text-xs text-ink-mute">
{proj.documents.length} Docs
</span>
</div>
<p className="font-display mt-1 text-sm italic opacity-70">{b.full}</p>
<p
className={`mt-4 inline-flex w-fit border px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] ${
b.accent === "vermillion"
? "border-vermillion/40"
: "border-cobalt/40"
} ${accentText[b.accent]} group-hover:border-ivory/40 group-hover:text-ivory`}
>
{b.area}
</p>
<ul className="mt-6 space-y-4 border-t border-ink/20 pt-6 text-sm group-hover:border-ivory/20">
{b.contributions.map((c) => (
<li key={c} className="flex gap-3">
<span
aria-hidden
className={`mt-1.5 h-1.5 w-1.5 flex-none rounded-full ${accentDot[b.accent]} group-hover:bg-ivory`}
/>
<span className="leading-relaxed opacity-90">{c}</span>
</li>
))}
</ul>
</article>
</Reveal>
))}
{proj.documents.length > 0 ? (
<div className="space-y-3 pt-1">
{proj.documents.map((doc, dIdx) => (
<div
key={dIdx}
className="bg-paper p-4 rounded border border-line/60 space-y-1.5"
>
<div className="flex justify-between items-start gap-4">
<h3 className="font-serif text-base font-normal text-ink leading-snug">
&ldquo;{doc.title}&rdquo;
</h3>
<span
className={`font-mono text-[0.65rem] px-2 py-0.5 rounded font-bold shrink-0 ${
doc.status === "InProgress"
? "bg-vermillion text-ivory"
: "bg-cobalt text-ivory"
}`}
>
{doc.status}
</span>
</div>
<div className="font-mono text-xs text-ink-mute flex justify-between">
<span>{doc.ref}</span>
{doc.publishedAt && (
<span className="text-ink">{doc.publishedAt}</span>
)}
</div>
</div>
))}
</div>
) : (
<div className="text-xs text-ink-mute font-mono italic">
</div>
)}
</div>
))}
</div>
</div>
</div>
))}
</section>
</>
</div>
);
}