Files

333 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 디자인 시스템 — "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) | `#4E77FF` | `--cobalt` | **MCP / A2A Protocol 강조(Cool)** · 짝수 항목 |
| `cobalt-dark` | `#345AD2` | — | 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** (영문 세리프, 300600 + 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 |
### 원칙
1. **스크롤 등장, 단 한 번.** `.reveal` 이 뷰포트 진입 시 fade + translate 후 유지된다(`Reveal` 컴포넌트, IntersectionObserver, 라이브러리 없음).
2. **단일 이징.** 전부 `editorial-out` 으로 ~820ms — 페이지 전체가 한 호흡.
3. **방향이 레이아웃을 인코딩.** 좌측 컬럼은 왼쪽에서, 우측은 오른쪽에서, 히어로 아트는 scale 로 들어온다.
4. **마퀴는 앰비언트.** 3260s 연속 루프는 "관찰 대상"이 아니라 마스트헤드 배경.
### 사용처 가이드
| 모션을 **쓸 때** | 모션을 **안 쓸 때** |
| --- | --- |
| 섹션/카드가 처음 뷰포트에 들어올 때 `Reveal` | 동일 뷰 안에서 반복 토글되는 상태 |
| 통계 수치 등장(`Counter`) | 본문 가독성에 영향을 주는 텍스트 |
| 키워드/서지 마스트헤드(`Marquee`, 페이지당 1개) | 정보 전달을 모션에 의존(접근성 위배) |
| 호버 색 전환(`duration-500`) | `prefers-reduced-motion: reduce` 사용자(자동 비활성화) |
---
## 6. 공유 컴포넌트 카탈로그
`components/` 의 신규 컴포넌트. 각 파일에 `CUSTOMIZATION HOOK` 주석이 있습니다.
### `Reveal` — 스크롤 등장 모션 래퍼
스크롤 진입 시 자식을 fade + translate. 모든 모션의 기본 프리미티브.
```tsx
Reveal(props: {
children: ReactNode;
variant?: "up" | "left" | "right" | "scale"; // 진입 방향, 기본 "up"
delay?: number; // 스태거(ms), transition-delay 로 적용
as?: ElementType; // 렌더 태그, 기본 "div"
className?: string;
})
```
```tsx
<Reveal variant="left" delay={120}>
<article></article>
</Reveal>
```
### `SectionLabel` — 매거진 섹션 넘버링
페이지 *내부* 섹션을 `01 / 05` 넘버링 + 아이브로우로 표시.
```tsx
SectionLabel(props: {
index: number;
total?: number; // 기본 5
label: string; // 영문 아이브로우
className?: string;
})
```
```tsx
<Reveal>
<SectionLabel index={2} label="Researchers" />
</Reveal>
```
### `Counter` — 0 → 값 카운트업 숫자
스크롤 진입 시 0에서 `value` 까지 easeOutExpo 로 틱업(rAF, 라이브러리 없음).
`prefers-reduced-motion` 이면 즉시 최종값.
```tsx
Counter(props: {
value: number;
duration?: number; // 기본 1600ms
prefix?: string;
suffix?: string;
className?: string;
})
```
```tsx
<Counter value={5} suffix="+" className="display block text-ink" />
```
### `Marquee` — 키워드 러닝 티커
키워드/서지/기구명을 연속 스크롤하는 마스트헤드 배너. 트랙을 2배로 렌더해
`-50%` 이동이 매끈하게 루프. `aria-hidden`(장식).
```tsx
Marquee(props: {
items: string[];
reverse?: boolean; // 진행 방향 반전
className?: string;
})
```
```tsx
<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 변수 상속, 기하만 내부 정의.
```tsx
HeroComposition(props: { className?: string })
```
```tsx
<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. 새 페이지 추가 체크리스트
1. **내비게이션 배지 & 마스트헤드:** `Header.tsx` 활성 배지(`bg-vermillion text-white dark:text-ivory`, `aria-current="page"`)와 페이지 타이틀 섹션으로 시작.
2. **콘텐츠 분리:** 편집 데이터 전부를 파일 상단 `// CUSTOMIZATION HOOK` 배열에 둔다.
3. **레이아웃:** 헤어라인 그리드 관용구(`gap-px bg-line` + 자식 `bg-ivory`)로 구성, 섹션은 `border-b border-ink` 로 구획.
4. **모션:** 등장 가치가 있는 블록을 `Reveal` 로 감싸고, §2 페어링 규칙대로 vermillion/cobalt 를 교차 배정.
5. **토큰만 사용:** 디자인 토큰(`ink`/`ivory`/`paper`/`line`/`vermillion`/`cobalt`)과 컴포넌트 타입 클래스만 사용 — 임의 회색·팔레트 밖 hex 금지.
---
## 9. 접근성 (a11y) 고려사항
- **대비비 (Contrast).** 본문은 `ink`(#0A0A0A) on `ivory`(#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`
등 정식 토큰을 쓰세요.