feat: initialize IoT Standards Lab website project with MAM skills & onboarding guide

This commit is contained in:
2026-07-29 23:43:59 +09:00
commit f3f53c1318
123 changed files with 19987 additions and 0 deletions
@@ -0,0 +1,120 @@
/**
* Abstract editorial hero — a custom illustration (inline SVG, no asset).
* • MCM → a constellation of interconnected nodes (vermillion).
* • QUIC → flowing, multiplexed streams (cobalt), animated dash flow.
* Animations are pure CSS (tailwind keyframes: node-pulse, stream-dash).
*
* CUSTOMIZATION HOOK — palette is inherited from CSS vars; geometry below.
*/
export default function HeroComposition({
className = "",
}: {
className?: string;
}) {
// MCM node coordinates (interconnected mesh, upper-left field).
const nodes = [
{ x: 70, y: 90 },
{ x: 160, y: 60 },
{ x: 130, y: 170 },
{ x: 230, y: 130 },
{ x: 60, y: 210 },
{ x: 210, y: 240 },
];
// Mesh edges between nodes (index pairs).
const edges: [number, number][] = [
[0, 1],
[0, 2],
[1, 3],
[2, 3],
[2, 4],
[3, 5],
[2, 5],
[4, 5],
];
return (
<svg
viewBox="0 0 380 380"
role="img"
aria-label="MCM 상호운용 노드와 QUIC 멀티스트림을 형상화한 추상 일러스트레이션"
className={className}
>
{/* Frame */}
<rect
x="6"
y="6"
width="368"
height="368"
fill="none"
stroke="#0A0A0A"
strokeWidth="1.5"
/>
{/* ---- QUIC: flowing multiplexed streams (cobalt) ---- */}
<g stroke="#1B3A8A" fill="none" strokeWidth="2.5" strokeLinecap="round">
{[0, 1, 2, 3].map((i) => {
const y = 250 + i * 30;
return (
<path
key={i}
d={`M 30 ${y} C 140 ${y - 40}, 240 ${y + 40}, 360 ${y - 20}`}
strokeDasharray="10 14"
className="animate-stream-dash"
style={{ animationDelay: `${i * 0.6}s`, opacity: 0.85 }}
/>
);
})}
</g>
{/* ---- MCM: interconnected node mesh (vermillion) ---- */}
<g stroke="#D9342B" strokeWidth="1.5">
{edges.map(([a, b], i) => (
<line
key={i}
x1={nodes[a].x}
y1={nodes[a].y}
x2={nodes[b].x}
y2={nodes[b].y}
opacity="0.55"
/>
))}
</g>
<g fill="#D9342B">
{nodes.map((n, i) => (
<circle
key={i}
cx={n.x}
cy={n.y}
r={i % 2 === 0 ? 9 : 6}
className="animate-node-pulse"
style={{
animationDelay: `${i * 0.5}s`,
transformBox: "fill-box",
transformOrigin: "center",
}}
/>
))}
</g>
{/* Annotations — placed in the top corners (y=24), clear of the
MCM node mesh (y=60..240) and the QUIC stream curves (y≥250 with
control points reaching y+40=380). See LAYOUT_AUDIT #3 and
REVIEW §3 "P1 #3" — keeping them at y=24 (mirror corners) preserves
a symmetric layout that the original LAYOUT_AUDIT mirror decided on. */}
<text x="20" y="24" fill="#0A0A0A" fontSize="11" fontWeight="600" letterSpacing="2">
MCM · MESH
</text>
<text
x="360"
y="24"
textAnchor="end"
fill="#0A0A0A"
fontSize="11"
fontWeight="600"
letterSpacing="2"
>
QUIC · STREAMS
</text>
</svg>
);
}
+292
View File
@@ -0,0 +1,292 @@
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:
"메타버스 상호운용성(MCM)과 QUIC 기반 멀티에이전트 오케스트레이션을 연구하는 경북대학교 사물인터넷 표준 연구실 소개.",
};
// CUSTOMIZATION HOOK: the two research thrusts (cover spread comparison).
const thrusts = [
{
no: "Thrust 01",
tag: "MCM",
accent: "vermillion" as const,
ko: "메타버스 상호운용성",
en: "Metaverse Interoperability",
desc: "이종(異種) 메타버스 플랫폼 간 객체·아바타·자산의 상호운용을 위한 공통 데이터 모델과 변환 계층을 설계합니다. oneM2M / W3C 표준을 기반으로 서로 다른 가상 환경이 동일한 의미(semantics)로 데이터를 교환합니다.",
points: [
"공통 정보 모델(Common Information Model) 정의",
"의미 보존 변환(semantic-preserving mapping) 계층",
"상호운용성 테스트베드 및 적합성 검증",
],
},
{
no: "Thrust 02",
tag: "QUIC",
accent: "cobalt" as const,
ko: "멀티에이전트 오케스트레이션",
en: "Multi-Agent Orchestration",
desc: "다수의 자율 에이전트가 저지연·다중스트림 환경에서 협력하도록 QUIC 전송 위에 오케스트레이션 아키텍처와 통신 인터페이스를 설계합니다. 연결 마이그레이션과 스트림 다중화로 메시지 라우팅을 최적화합니다.",
points: [
"QUIC 스트림 다중화 기반 에이전트 메시지 라우팅",
"오케스트레이터–에이전트 제어 인터페이스 설계",
"연결 마이그레이션 기반 모바일·엣지 지원",
],
},
];
// CUSTOMIZATION HOOK: headline figures (animated counters).
// `value: 2026` is marked static so it renders as a fixed year, not a
// 0→2026 count-up. The year is also rendered at the smaller `display-sm`
// scale (the other two figures use the full `display` scale), so the
// 4-digit "2026" does not push the cell padding. See LAYOUT_AUDIT #11 + #13.
const figures = [
{ value: 2, label: "연구 축 · Research Thrusts", suffix: "", size: "display" as const, static: false },
{ value: 4, label: "표준화 기구 · Standards Bodies", 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 = [
"INTEROPERABILITY",
"oneM2M",
"W3C WoT",
"QUIC TRANSPORT",
"MULTI-AGENT",
"STREAM MULTIPLEXING",
"CONNECTION MIGRATION",
"METAVERSE",
"SEMANTIC MAPPING",
"CONFORMANCE",
];
const focusAreas = [
{ ko: "사물인터넷 표준화", en: "IoT Standardization" },
{ ko: "메타버스 상호운용성", en: "Metaverse Interoperability" },
{ ko: "전송 프로토콜(QUIC)", en: "Transport Protocols" },
{ ko: "멀티에이전트 시스템", en: "Multi-Agent Systems" },
];
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-center gap-10 lg:grid-cols-12">
{/* Headline column */}
<div className="lg:col-span-7">
<Reveal delay={60}>
<p className="kicker"> · IoT Standards Lab</p>
</Reveal>
<Reveal delay={120}>
{/* Korean + English mixed, magazine-cover scale */}
<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>
Standards
<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">
(MCM) QUIC .
.
</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 MCM × QUIC
</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 */}
, ·
.
</p>
</Reveal>
</div>
</section>
{/* ============ TWO THRUSTS — SIDE BY SIDE WITH CONNECTING LINE ============ */}
<section className="border-b border-ink">
<div className="container-content py-16 sm:py-24">
<Reveal>
<SectionLabel index={3} label="Two Research Thrusts" />
</Reveal>
<div className="relative mt-12 grid gap-px lg:grid-cols-2">
{/* Connecting line + node between the two thrusts (desktop) */}
<div
aria-hidden
className="absolute left-1/2 top-1/2 hidden -translate-x-1/2 -translate-y-1/2 lg:block"
>
<div className="flex items-center">
<span className="h-2 w-2 rounded-full bg-vermillion" />
<span className="h-px w-16 bg-ink" />
<span className="flex h-9 w-9 items-center justify-center rounded-full border border-ink bg-ivory font-display text-xs">
×
</span>
<span className="h-px w-16 bg-ink" />
<span className="h-2 w-2 rounded-full bg-cobalt" />
</div>
</div>
{thrusts.map((t, i) => {
const isVerm = t.accent === "vermillion";
return (
<Reveal
key={t.tag}
variant={i === 0 ? "left" : "right"}
delay={i * 120}
>
<article
className={`group h-full 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 className="flex items-baseline justify-between">
<span className="kicker group-hover:text-ivory/70">{t.no}</span>
<span
className={`font-display text-5xl ${
isVerm ? "text-vermillion" : "text-cobalt"
} group-hover:text-ivory`}
>
{t.tag}
</span>
</div>
<h3 className="headline-ko mt-6 text-3xl leading-tight">
{t.ko}
</h3>
<p className="font-display mt-1 text-lg italic opacity-70">
{t.en}
</p>
<p className="mt-5 text-sm leading-relaxed opacity-90">
{t.desc}
</p>
<ul className="mt-6 space-y-3 text-sm">
{t.points.map((p) => (
<li key={p} 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>{p}</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>
</>
);
}