18 KiB
디자인 시스템 — "Issue 01"
AI 에이전트 네트워크 연구실 (ANL) 랜딩 페이지의 정본(canonical) 디자인 레퍼런스입니다. 코드베이스를 다시 읽지 않고도 디자인 시스템을 이해할 수 있도록 작성했습니다. 토큰의 실제 정의는
tailwind.config.ts와app/globals.css(:root)에, 폰트 로딩은app/layout.tsx에 있습니다.
1. 한 줄 요약 (One-liner)
연구실 웹사이트를 "한 권의 인쇄 잡지(Issue 01)"로 다룬다. 따뜻한 종이(ivory) 위에 가까운-검정 잉크(ink), 두 개의 편집 강조색 (vermillion = Agent Engineering / Orchestration, cobalt = MCP / A2A Protocol), 큰 세리프 디스플레이 타이포, 그리고 절제된 스크롤 등장 모션으로 구성된 고대비 매거진 스프레드 시스템입니다. 소수의 토큰에서 사이트 전체가 파생되며, 토큰 하나를 바꾸면 전 페이지에 전파됩니다.
2. 디자인 원칙 (Principles)
| 원칙 | 설명 |
|---|---|
| 편집 레이아웃 (Editorial layout) | 모든 면을 잡지 스프레드처럼 다룬다. 섹션은 01 / 05 넘버링과 헤어라인 괘선으로 구획하고, 큰 디스플레이 숫자가 카드를 앵커한다. |
| 큰 고대비 타이포 (Big high-contrast type) | ink(#0A0A0A) on ivory(#F5F1E8)의 강한 대비. 히어로는 clamp() 기반 거대 세리프(Fraunces)로 잡지 표지 스케일을 만든다. |
| 의도적 여백 (Intentional whitespace) | 종이처럼 숨 쉬게 둔다. 섹션 패딩은 py-16 sm:py-24, 그리드는 gap-px bg-line 헤어라인으로 분리해 장식 테두리를 최소화한다. |
| 움직임은 절제 (Restrained motion) | 콘텐츠는 한 번 부드럽게 자리잡고 멈춘다. 튀거나 반복되지 않으며, 단일 이징으로 페이지 전체가 일관된 호흡을 갖는다. |
| 콘텐츠 우선 (Content first) | 모든 편집 데이터는 각 페이지 상단 // CUSTOMIZATION HOOK 배열에 모은다. 모션·장식은 정보를 전달하는 수단이지 그 자체가 목적이 아니다. |
| 한/영 혼식 (KO/EN bilingual setting) | 한국어 세리프(Noto Serif KR) 헤드라인과 영어 디스플레이 세리프(Fraunces)를 짝지어, 국문 제목 + 영문 디스플레이 단어의 매거진 바이링구얼 조판을 만든다. |
| 의미 있는 색 (Semantic accent pairing) | 두 강조색은 장식이 아니라 연구 영역을 인코딩한다 — vermillion = Agent Eng / Orchestration (홀수), cobalt = MCP / A2A Protocol (짝수). |
3. 컬러 토큰 (Color Tokens)
tailwind.config.ts 의 theme.extend.colors 에 정의되고, 동일 값이
app/globals.css 의 :root CSS 변수로 미러링됩니다. 두 곳을 함께 바꾸세요
(순수 CSS 그레인·그라디언트가 CSS 변수를 재사용하기 때문).
| Tailwind 토큰 | Hex | CSS 변수 | 역할 / 사용 규칙 |
|---|---|---|---|
ink (DEFAULT) |
#0A0A0A |
--ink |
본문 텍스트, 괘선·테두리, 다크 면(填) |
ink-soft |
#262420 |
— | 2차 본문 텍스트 |
ink-mute |
#6B655B |
— | 3차 텍스트, 숫자, 캡션 |
ivory |
#F5F1E8 |
--ivory |
페이지 기본 배경(따뜻한 종이) |
paper |
#ECE6D6 |
--paper |
인셋/호버용 어두운 종이 |
line |
#D8D1C0 |
--line |
헤어라인 그리드 간격(gap-px bg-line) |
vermillion (DEFAULT) |
#D9342B |
--vermillion |
Agent Eng / Orchestration 강조(Hot) · 홀수 항목 |
vermillion-dark |
#A8231C |
— | vermillion 의 호버/심도(dim) 변형 |
cobalt (DEFAULT) |
#1B3A8A |
--cobalt |
MCP / A2A Protocol 강조(Cool) · 짝수 항목 |
cobalt-dark |
#122863 |
— | cobalt 의 호버/심도(dim) 변형 |
brand, accent |
(별칭) | — | 레거시 호환 전용. 신규 코드에서 사용 금지 |
개념적 역할 매핑 (Naming note)
설계 논의에서 쓰던 개념 명칭과 실제 코드 토큰의 매핑입니다. 코드에서는 항상 실제 토큰명을 사용하세요.
개념 명칭 → 실제 코드 토큰
─────────────────────────────────────────
accent.ink → ink (주 잉크/텍스트)
종이(paper) 계열 → ivory, paper, line
accent.ko (국문) → headline-ko 클래스가 font-serif-ko 로 처리(색은 ink)
accent.ko-warm(강조) → vermillion (Hot, Agent Eng / Orchestration)
accent.glow / dim → vermillion-dark / cobalt-dark (각 강조색의 dark 변형)
Protocol/A2A 강조 → cobalt (Cool, MCP / A2A Protocol)
별도의
accent.glow(발광)나 중립 회색 팔레트는 현재 정의돼 있지 않습니다. 강조가 필요하면 vermillion/cobalt 를, 심도가 필요하면 그-dark변형을 쓰세요.
헤어라인 그리드 관용구
gap-px bg-line → 부모 그리드의 1px 간격이 line 색으로 드러남
└ 각 자식에 bg-ivory → 인쇄 괘선 효과(추가 border 불필요)
4. 타이포 시스템 (Typography)
app/layout.tsx 에서 next/font/google 로 로드해 CSS 변수로 노출하고,
tailwind.config.ts 의 fontFamily 에서 패밀리로 묶습니다.
| 패밀리 클래스 | CSS 변수 | 서체 | 용도 |
|---|---|---|---|
font-display |
--font-display |
Fraunces (영문 세리프, 300–600 + italic) | 페이지 히어로/매거진 제목, 이탤릭 강조어, 숫자 |
font-serif-ko |
--font-serif-ko |
Noto Serif KR (400/600/700) | 한국어 디스플레이/헤드라인 |
font-sans |
--font-sans |
Pretendard(선호) → Inter(웹 폴백) |
본문, 키커, UI |
| (mono) | — | 미설정 | 코드/수치/라벨용 모노스페이스는 현재 미연동 — §10 FAQ 참고 |
본문 sans 스택은
tailwind.config.ts에서Pretendard → var(--font-sans)(Inter) → ui-sans-serif → system-ui → Apple SD Gothic Neo → Malgun Gothic순입니다. Pretendard 는 Google Fonts 로 제공되지 않아 Inter 가 웹 폴백으로 로드됩니다.
컴포넌트 타입 클래스 (app/globals.css, @layer components)
ad-hoc 유틸리티 스택보다 아래 시맨틱 클래스를 우선 사용하세요.
| 클래스 | 결과 |
|---|---|
.display |
매거진 표지급 영문 세리프, text-display, 가는 굵기, optical sizing |
.headline-ko |
한국어 세리프 헤드라인, semibold, 좁은 자간 |
.kicker |
소문자 대문자화 아이브로우, tracking-caps(0.28em), 뮤트 |
.corner-label |
tabular-nums 스프레드 넘버링("01 / 05") |
.pull-quote |
큰 이탤릭 세리프 인용구 |
.container-content |
max-w-content(78rem) 중앙 정렬 + 반응형 패딩 |
.rule / .spread-card / .draw-underline |
헤어라인 괘선 / 인셋 카드 / 호버 밑줄 드로우 |
스케일 (clamp 기반)
뷰포트에 따라 브레이크포인트 없이 유체적으로 커집니다.
| 단계 | 토큰/클래스 | 값 (clamp) | 용도 |
|---|---|---|---|
| 1 — Hero | text-display (.display) |
clamp(3rem, 10vw, 9rem) / lh 0.92 |
커버 디스플레이 |
| 2 — Sub-hero | text-display-sm |
clamp(2.25rem, 6vw, 4.5rem) / lh 0.98 |
보조 디스플레이 |
| 3 — Page H1 | (PageHeader 내) | clamp 없이 text-4xl → lg:text-6xl |
페이지 마스트헤드 한글 제목 |
| 4 — Section H | text-2xl ~ text-3xl |
고정 | 섹션/카드 헤드라인 |
| 5 — Lead | text-base ~ text-lg |
고정 | 리드 문단 |
| 6 — Body | text-sm |
고정 | 본문 |
| 7 — Caption | text-xs |
고정 | 캡션/메타 |
| 8 — Kicker | .kicker (text-[0.7rem]) |
고정 | 아이브로우/라벨 |
인트로 히어로는 인라인으로
text-[clamp(2.5rem,8vw,5.5rem)]를 쓰기도 합니다. 새 거대 제목은 가급적text-display/text-display-sm토큰을 재사용하세요.
5. 모션 원칙 (Motion)
모션은 조용하고 편집적입니다. 콘텐츠가 자리잡을 뿐, 튀지 않습니다. 타이밍 토큰은
app/globals.css(--reveal-*)와 tailwind.config.ts(animation)에 있습니다.
토큰
| 종류 | 토큰 | 값 | 비고 |
|---|---|---|---|
| Duration · reveal | --reveal-duration |
820ms |
스크롤 등장 기본 |
| Duration · medium | (Tailwind duration-500) |
500ms |
호버 색 전환 |
| Duration · marquee | animate-marquee 외 |
32s / 45s(reverse) / 60s(slow) |
러닝 티커 |
| Easing · editorial-out | --reveal-ease |
cubic-bezier(0.16, 1, 0.3, 1) |
부드러운 감속(전역 단일 이징) |
| Keyframes | stream-dash / node-pulse |
6s / 4s |
HeroComposition SVG |
원칙
- 스크롤 등장, 단 한 번.
.reveal이 뷰포트 진입 시 fade + translate 후 유지된다(Reveal컴포넌트, IntersectionObserver, 라이브러리 없음). - 단일 이징. 전부
editorial-out으로 ~820ms — 페이지 전체가 한 호흡. - 방향이 레이아웃을 인코딩. 좌측 컬럼은 왼쪽에서, 우측은 오른쪽에서, 히어로 아트는 scale 로 들어온다.
- 마퀴는 앰비언트. 32–60s 연속 루프는 "관찰 대상"이 아니라 마스트헤드 배경.
사용처 가이드
| 모션을 쓸 때 | 모션을 안 쓸 때 |
|---|---|
섹션/카드가 처음 뷰포트에 들어올 때 Reveal |
동일 뷰 안에서 반복 토글되는 상태 |
통계 수치 등장(Counter) |
본문 가독성에 영향을 주는 텍스트 |
키워드/서지 마스트헤드(Marquee, 페이지당 1개) |
정보 전달을 모션에 의존(접근성 위배) |
호버 색 전환(duration-500) |
prefers-reduced-motion: reduce 사용자(자동 비활성화) |
6. 공유 컴포넌트 카탈로그
components/ 의 신규 컴포넌트. 각 파일에 CUSTOMIZATION HOOK 주석이 있습니다.
Reveal — 스크롤 등장 모션 래퍼
스크롤 진입 시 자식을 fade + translate. 모든 모션의 기본 프리미티브.
Reveal(props: {
children: ReactNode;
variant?: "up" | "left" | "right" | "scale"; // 진입 방향, 기본 "up"
delay?: number; // 스태거(ms), transition-delay 로 적용
as?: ElementType; // 렌더 태그, 기본 "div"
className?: string;
})
<Reveal variant="left" delay={120}>
<article>…</article>
</Reveal>
SectionLabel — 매거진 섹션 넘버링
페이지 내부 섹션을 01 / 05 넘버링 + 아이브로우로 표시.
SectionLabel(props: {
index: number;
total?: number; // 기본 5
label: string; // 영문 아이브로우
className?: string;
})
<Reveal>
<SectionLabel index={2} label="Researchers" />
</Reveal>
Counter — 0 → 값 카운트업 숫자
스크롤 진입 시 0에서 value 까지 easeOutExpo 로 틱업(rAF, 라이브러리 없음).
prefers-reduced-motion 이면 즉시 최종값.
Counter(props: {
value: number;
duration?: number; // 기본 1600ms
prefix?: string;
suffix?: string;
className?: string;
})
<Counter value={5} suffix="+" className="display block text-ink" />
Marquee — 키워드 러닝 티커
키워드/서지/기구명을 연속 스크롤하는 마스트헤드 배너. 트랙을 2배로 렌더해
-50% 이동이 매끈하게 루프. aria-hidden(장식).
Marquee(props: {
items: string[];
reverse?: boolean; // 진행 방향 반전
className?: string;
})
<Reveal>
<Marquee items={keywords} reverse
className="border-y border-ink bg-ink py-3 font-display text-xl text-ivory" />
</Reveal>
HeroComposition — 추상 편집 히어로 (인트로 전용)
인라인 SVG 일러스트(에셋 없음). A2A = 에이전트 메시 노드 성좌(vermillion, node-pulse),
QUIC = 다중화 스트림 흐름(cobalt, stream-dash). 색은 CSS 변수 상속, 기하만 내부 정의.
HeroComposition(props: { className?: string })
<Reveal variant="scale" delay={200} className="lg:col-span-5">
<HeroComposition className="w-full" />
</Reveal>
함께 쓰는 관용구: 모든 페이지는 페이지 섹션 헤더 → (선택)
Marquee밴드 1개 → 통계 밴드(Counter×3,gap-px bg-line sm:grid-cols-3) →Reveal+SectionLabel로 여는 콘텐츠 섹션. 행 내 스태거는delay={i * 80}~120.
7. 페이지별 디자인 차이 (잡지 비유)
| 라우트 | index | 잡지에서의 역할 | 특징 |
|---|---|---|---|
/ |
1 | 커버 스프레드 | 거대 한/영 히어로 + HeroComposition 아트, Research Project PageView(3대 과제), 4대 핵심 분야 조판 그리드 |
/members |
2 | 기고자 페이지(Contributors) | 인원 통계 카운터, PI 피처 스프레드, 박/석/학부 그룹을 헤어라인 그리드 + 교차 강조색으로 |
/publications |
3 | 목차/서지(Index & Bibliography) | 서지 Marquee, 파생 통계, 번호 매긴 편집 목록(저널=vermillion, 학회=cobalt) |
/lectures |
4 | 강의 카탈로그(Course catalog) | 45개 학기 학부·대학원 개설 교과목 이력 |
/standardization |
5 | 표준화 바이라인(Standards byline) | 기구 Marquee, 통계, 기관별 2열 기여 그리드, 기관마다 강조색 배정 |
8. 새 페이지 추가 체크리스트
- 내비게이션 배지 & 마스트헤드:
Header.tsx활성 배지(bg-vermillion text-white dark:text-ivory,aria-current="page")와 페이지 타이틀 섹션으로 시작. - 콘텐츠 분리: 편집 데이터 전부를 파일 상단
// CUSTOMIZATION HOOK배열에 둔다. - 레이아웃: 헤어라인 그리드 관용구(
gap-px bg-line+ 자식bg-ivory)로 구성, 섹션은border-b border-ink로 구획. - 모션: 등장 가치가 있는 블록을
Reveal로 감싸고, §2 페어링 규칙대로 vermillion/cobalt 를 교차 배정. - 토큰만 사용: 디자인 토큰(
ink/ivory/paper/line/vermillion/cobalt)과 컴포넌트 타입 클래스만 사용 — 임의 회색·팔레트 밖 hex 금지.
9. 접근성 (a11y) 고려사항
- 대비비 (Contrast). 본문은
ink(#0A0A0A) onivory(#F5F1E8)로 WCAG AAA 수준의 강한 대비. 보조 텍스트는ink-soft/ink-mute까지만 낮추고, 작은 본문에ink-mute단독 사용은 지양. 강조색 위 텍스트는 항상ivory(흰 종이색)로 뒤집어 대비 확보. - 모션 줄임 (Reduced motion).
@media (prefers-reduced-motion: reduce)에서.reveal·마퀴·[class*="animate-"]·smooth scroll 을 전부 비활성화.Counter도 즉시 최종값으로 표기. 모션에 정보를 싣지 말 것(원칙 §5). - 키보드 포커스 (Keyboard focus). 링크/버튼은 시맨틱 요소(
<Link>,<button>)를 사용해 기본 포커스 링을 유지. 호버 전용 정보(예: 색 전환)에 의미를 담지 말고, 콘텐츠는 호버 없이도 읽히게 둔다. - 장식 요소.
Marquee·HeroComposition의 텍스트 주석 등 장식은aria-hidden또는role="img"+aria-label로 스크린리더 노이즈를 차단. - 언어 표시. 루트
<html lang="ko">. 한/영 혼식이지만 문서 기본 언어는 한국어.
10. 자주 묻는 질문 (FAQ)
Q. 컬러를 시즌별(Issue 02 등)로 바꾸고 싶어요.
A. 강조색 2개(vermillion, cobalt)와 그 -dark 변형만 교체하면 됩니다. 단,
tailwind.config.ts 의 theme.extend.colors 와 app/globals.css 의 :root
CSS 변수(--vermillion, --cobalt)를 함께 바꾸세요(그레인/그라디언트/SVG가 변수를
참조). 종이 톤을 바꾸려면 ivory/paper/line 을 같은 방식으로 조정합니다. 의미
페어링(vermillion=Agent Eng / Orchestration, cobalt=MCP / A2A Protocol)을 유지하면 페이지 전반의 강조색 배정 로직을
건드리지 않아도 됩니다.
Q. 모션 줄이기(prefers-reduced-motion) 대응은 되어 있나요?
A. 네. app/globals.css 의 미디어 쿼리에서 reveal/마퀴/keyframe/smooth scroll 을
모두 끄고, Counter 는 즉시 최종값을 표시합니다. 새 모션을 추가할 때도 이 쿼리에
비활성화 규칙을 넣고, 정보를 모션에만 의존시키지 마세요.
Q. 폰트를 한국어 웹폰트로 교체할 수 있나요?
A. 본문 sans 는 이미 Pretendard 를 1순위로 두고 Inter 를 웹 폴백으로 로드합니다
(tailwind.config.ts sans 스택, app/layout.tsx). Pretendard 를 self-host 하거나
next/font/local 로 추가하면 폴백 없이 적용됩니다. 한국어 헤드라인 세리프(Noto
Serif KR)나 영문 디스플레이(Fraunces)를 바꾸려면 app/layout.tsx 의 next/font
로더를 교체하고 --font-serif-ko / --font-display 변수명을 유지하면 됩니다.
Q. 코드/수치용 모노스페이스 폰트는 어디 있나요?
A. 현재 시스템에는 모노 패밀리가 연동돼 있지 않습니다. 수치는 font-display
(Fraunces) + tabular-nums, 라벨은 .kicker/.corner-label 로 처리합니다. 모노가
필요하면 app/layout.tsx 에 IBM_Plex_Mono(또는 Geist_Mono)를 next/font 로
추가해 --font-mono 변수로 노출하고, tailwind.config.ts 의 fontFamily.mono 에
등록하세요. (소스 변경이 필요하므로 본 문서는 현재 상태만 기록합니다.)
Q. brand / accent 토큰은 뭔가요? 써도 되나요?
A. 이전(비-편집) 디자인의 레거시 호환 별칭입니다. 남은 stray 클래스가 깨지지
않게 두었을 뿐, 신규 코드에서는 사용하지 마세요. 항상 vermillion/cobalt/ink
등 정식 토큰을 쓰세요.