333 lines
18 KiB
Markdown
333 lines
18 KiB
Markdown
# 디자인 시스템 — "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 |
|
||
|
||
### 원칙
|
||
|
||
1. **스크롤 등장, 단 한 번.** `.reveal` 이 뷰포트 진입 시 fade + translate 후 유지된다(`Reveal` 컴포넌트, IntersectionObserver, 라이브러리 없음).
|
||
2. **단일 이징.** 전부 `editorial-out` 으로 ~820ms — 페이지 전체가 한 호흡.
|
||
3. **방향이 레이아웃을 인코딩.** 좌측 컬럼은 왼쪽에서, 우측은 오른쪽에서, 히어로 아트는 scale 로 들어온다.
|
||
4. **마퀴는 앰비언트.** 32–60s 연속 루프는 "관찰 대상"이 아니라 마스트헤드 배경.
|
||
|
||
### 사용처 가이드
|
||
|
||
| 모션을 **쓸 때** | 모션을 **안 쓸 때** |
|
||
| --- | --- |
|
||
| 섹션/카드가 처음 뷰포트에 들어올 때 `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>
|
||
```
|
||
|
||
> **함께 쓰는 관용구:** 모든 페이지는 `PageHeader`(`components/PageHeader.tsx`)로 시작
|
||
> → (선택) `Marquee` 밴드 1개 → 통계 밴드(`Counter` ×3, `gap-px bg-line sm:grid-cols-3`)
|
||
> → `Reveal`+`SectionLabel` 로 여는 콘텐츠 섹션. 행 내 스태거는 `delay={i * 80}`~`120`.
|
||
|
||
---
|
||
|
||
## 7. 페이지별 디자인 차이 (잡지 비유)
|
||
|
||
| 라우트 | index | 잡지에서의 역할 | 특징 |
|
||
| --- | --- | --- | --- |
|
||
| `/intro` | 1 | **커버 스프레드** | 거대 한/영 히어로 + `HeroComposition` 아트, 키워드 `Marquee`, 통계, 풀쿼트, 5대 연구 기둥 3+2 매거진 조판 그리드 |
|
||
| `/members` | 2 | **기고자 페이지(Contributors)** | 인원 통계 카운터, PI 피처 스프레드, 박/석/학부 그룹을 헤어라인 그리드 + 교차 강조색으로 |
|
||
| `/publications` | 3 | **목차/서지(Index & Bibliography)** | 서지 `Marquee`, 파생 통계, 번호 매긴 편집 목록(저널=vermillion, 학회=cobalt) |
|
||
| `/lectures` | 4 | **강의 카탈로그(Course catalog)** | 3개 강의 카드(테두리·강조색 호버 면), 코드·KO/EN 제목·레벨 칩 |
|
||
| `/standardization` | 5 | **표준화 바이라인(Standards byline)** | 기구 `Marquee`, 통계, 기관별 2열 기여 그리드, 기관마다 강조색 배정 |
|
||
|
||
---
|
||
|
||
## 8. 새 페이지 추가 체크리스트
|
||
|
||
1. **마스트헤드:** `PageHeader` 로 시작 — `ko`, `en`, 라우트 `index`(intro 1 … standardization 5) 부여.
|
||
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`
|
||
등 정식 토큰을 쓰세요.
|