unwrap.fm design system guide. Reference for colors, typography, layout, and component patterns. Use when making design decisions or creating UI components.
가독성 우선의 모듈형 UI. Oxidized Cyan을 기반으로 밝고 세련된 느낌에 은은한 레트로 무드를 담은 Solid Surface 시각 언어.
readability, solid surfaces, oxidized cyan, blue-leaning, modern retro, "bright but calm"
unwrap.fm은 사용자의 음악 스트리밍 기록을 분석하는 서비스입니다 (stats.fm과 유사).
핵심 화면:
디자인 요구사항:
구조와 스타일을 분리한다
고밀도 영역은 깨끗하게 유지한다
브랜드 무드는 핵심 20%에만
강조는 소재, 두께, 톤으로 (빛이 아닌)
모션은 의미 있을 때만, 대부분 원샷
| Name | Hex | Role | 의미 |
|---|---|---|---|
| Oxidized Cyan | #0C8A99 | Primary (palette 600) | 인터랙션, 주의 집중 |
| Fog | #C9D4D6 | Secondary (palette 300) | 표면, 구조 |
이 토큰은 의미를 잠그기 위해 존재합니다. "모든 곳을 cyan으로 칠하기" 위한 것이 아닙니다.
Primary (Teal/Cyan) = 인터랙션/주의
Secondary (Fog) = 표면/구조
텍스트는 중립 fog 계열
각 팔레트는 50-950 11단계 스케일로 구성됩니다.
| Stop | Hex | 용도 |
|---|---|---|
| 50 | #EDF7F9 | 매우 밝은 틴트 |
| 100 | #D5EEF3 | 밝은 배경, selection (light) |
| 200 | #ABDCE7 | 선택 배경 (light mode) |
| 300 | #7AC7D8 | 링크 hover (dark mode) |
| 400 | #45AFC6 | 링크, 포커스 링 (dark mode) |
| 500 | #2196B2 | 포커스 링 중간 단계, hover |
| 600 | #0C8A99 | Core - 버튼, 액션 |
| 700 | #0A6F7B | 링크 (light mode), active |
| 800 | #085661 | 버튼 active (light mode) |
| 900 | #063D46 | 매우 어두운 cyan |
| 950 | #04252A | 최심부 |
| Stop | Hex | 용도 |
|---|---|---|
| 50 | #F8FAFB | 페이지 배경 (light mode) |
| 100 | #F1F4F5 | hover 배경, sunken (light) |
| 200 | #E3E8EA | 함몰 영역, 미세 보더 |
| 300 | #C9D4D6 | Core - 기본 보더 (light) |
| 400 | #9FACB0 | 칩 배경, 구분, disabled |
| 500 | #758588 | 강조 보더 (light), tertiary text |
| 600 | #5A686B | tertiary 텍스트 (light) |
| 700 | #434E50 | secondary 텍스트 (light) |
| 800 | #2C3436 | border default (dark) |
| 900 | #191F20 | 다크 모드 sunken |
| 950 | #0E1213 | 페이지 배경 (dark mode) |
브랜드 의미를 깨끗하게 유지하기 위해 상태 컬러는 별도 시스템으로 운영합니다.
| Palette | Core (500) | Dark Mode 사용 | Light Mode 사용 |
|---|---|---|---|
| Success (Green) | #22C55E | 400 (#4ADE80) | 600 (#16A34A) |
| Warning (Orange) | #F59E0B | 400 (#FBBF24) | 600 (#D97706) |
| Danger (Red) | #EF4444 | 400 (#F87171) | 600 (#DC2626) |
| Info | teal-400 | teal-400 | teal-600 |
Primary를 "성공/위험"으로 사용하지 마세요 (의미 충돌).
| Level | 이름 | Dark Mode | Light Mode | 용도 |
|---|---|---|---|---|
| 1 | Base (Page) | fog-950 #0E1213 | fog-50 #F8FAFB | 페이지 최외곽 |
| 2 | Surface (Card/Panel) | #171E20 | #FFFFFF | flat fill + thin border |
| 3 | Raised (Modal/Drawer) | #212B2D | #FFFFFF + border/shadow | 포커스/중요도 전용 |
blur/glow를 기본 계층 도구로 사용하지 않는다.
엔터테인먼트 중심: 전문적인 분석 서비스지만, 재미있고 흥미로운 경험 제공. 완벽한 정돈보다는 visual interest와 discovery를 우선.
┌─────────────────────────────────────────────────────────┐
│ Logo [Search] [Profile] │ Top Nav
├──────┬──────────────────────────────────────────────────┤
│ │ ┌─────────────────────────────────────────────┐│
│ │ │ ☀️ Morning → 🌤️ Afternoon → 🌙 Evening ││ Mood Timeline
│ │ │ Your listening mood this week ││
│ │ └─────────────────────────────────────────────┘│
│ │ ┌─────────────────────────────────────────────┐│
│ │ │ 🎵 Your Week in Music ││
│ │ │ ┌─────────────────────────────────────────┐││
│ │ │ │ [Album Art Mosaic / Gradient] │││ Visual Hero
│ │ │ │ "Chill Vibes Only" │││
│ │ │ │ Top genre: Indie Electronic │││
│ │ │ └─────────────────────────────────────────┘││
│ │ └─────────────────────────────────────────────┘│
│ Nav │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ Home │ │ 2,847 │ │ 432 │ │ 89 │ │ 15 │ │ Big Stats
│ Top │ │ minutes│ │ songs │ │ artists│ │ genres │ │
│ Rec │ └────────┘ └────────┘ └────────┘ └────────┘ │
│ Disc │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │
│ │ │ ┌────┐ │ │ ┌────┐ │ │
│ │ │ │ 🖼 │ 1. Artist │ │ │ 🖼 │ 1. Track │ │ Lists with Art
│ │ │ └────┘ ♪ 247 │ │ └────┘ ♪ 89 │ │
│ │ │ ┌────┐ │ │ ┌────┐ │ │
│ │ │ │ 🖼 │ 2. ... │ │ │ 🖼 │ 2. ... │ │
│ │ └──────────────────┘ └──────────────────┘ │
└──────┴──────────────────────────────────────────────────┘
200px flexible
| 영역 | 높이/너비 | 특징 |
|---|---|---|
| Top Nav | 56px | Logo, 검색, 프로필. 미니멀. |
| Sidebar | 200px | 네비게이션만. Collapsible (60px) |
| Mood Timeline | 80px | 시간대별 리스닝 무드 시각화 |
| Visual Hero | 200-280px | 앨범 아트 모자이크/그라디언트 + highlight |
| Big Stats | 120px | 임팩트 있는 숫자 + playful labels |
| Content Lists | flexible | 앨범 아트 포함 랭킹 리스트 |
허용되는 visual elements:
제한:
사용자의 리스닝 패턴을 시각화:
☀️ Morning 🌤️ Afternoon 🌙 Evening 🌃 Late Night
Chill Energetic Melancholy Ambient
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
주간/월간 하이라이트를 visual하게 표현:
| Breakpoint | 너비 | 레이아웃 |
|---|---|---|
| Mobile | < 640px | Sidebar hidden, single column, Hero 축소 |
| Tablet | 640-1023px | Collapsed sidebar, 2 columns |
| Desktop | ≥ 1024px | Full sidebar, full Hero |
— 01 —, SECTION 02 등 일관된 섹션 구분자4px(0.25rem) 단위의 일관된 간격 체계.
| Token | Value | 사용 예 |
|---|---|---|
space[0] | 0 | 간격 제거 |
space[1] | 0.25rem (4px) | 아이콘과 텍스트 사이 |
space[2] | 0.5rem (8px) | 인라인 요소 간 간격 |
space[3] | 0.75rem (12px) | 리스트 아이템 패딩 |
space[4] | 1rem (16px) | 기본 컴포넌트 패딩 |
space[5] | 1.25rem (20px) | 섹션 내부 간격 |
space[6] | 1.5rem (24px) | 카드 패딩 |
space[8] | 2rem (32px) | 섹션 간 간격 |
space[10] | 2.5rem (40px) | 큰 섹션 간격 |
space[12] | 3rem (48px) | 헤더/풋터 패딩 |
space[16] | 4rem (64px) | 페이지 섹션 |
space[20] | 5rem (80px) | 히어로 섹션 |
space[24] | 6rem (96px) | 대형 여백 |
| Token | Value | 용도 |
|---|---|---|
fontSize.xs | 0.75rem (12px) | 캡션, 배지, label bar |
fontSize.sm | 0.875rem (14px) | 보조 텍스트, 레이블 |
fontSize.base | 1rem (16px) | 본문 기본 |
fontSize.lg | 1.125rem (18px) | 강조 본문 |
fontSize.xl | 1.25rem (20px) | 소제목, 컴포넌트 타이틀 |
fontSize.2xl | 1.5rem (24px) | 섹션 제목 |
fontSize.3xl | 1.875rem (30px) | 페이지 제목 |
fontSize.4xl | 2.25rem (36px) | 대형 제목 |
fontSize.5xl | 3rem (48px) | 히어로 제목 |
Typography 규칙:
| Token | Value | 용도 |
|---|---|---|
fontWeight.normal | 400 | 본문 |
fontWeight.medium | 500 | 강조 본문, 네비게이션 |
fontWeight.semibold | 600 | 소제목, 버튼 |
fontWeight.bold | 700 | 제목, 데이터 수치 |
| Token | Value | 용도 |
|---|---|---|
lineHeight.tight | 1.25 | 제목, 데이터 수치, label bar |
lineHeight.normal | 1.5 | 본문 기본 |
lineHeight.relaxed | 1.75 | 긴 텍스트, 설명문 |
| Token | Value | 용도 |
|---|---|---|
borderRadius.sm | 0.25rem (4px) | 배지, 태그, 칩 |
borderRadius.md | 0.375rem (6px) | 입력 필드, 작은 버튼 |
borderRadius.lg | 0.5rem (8px) | 카드, 버튼 |
borderRadius.xl | 0.75rem (12px) | 대형 카드, 모달 |
borderRadius.full | 9999px | 원형 요소, pill 버튼 |
Solid Surface 컨셉에 맞게 shadow는 최소한으로 사용합니다. 계층 표현은 border weight와 tone shift를 우선합니다.
| Token | 용도 |
|---|---|
shadow.sm | 미세한 부유감 (드롭다운 항목) |
shadow.md | Raised 패널 (모달/드로어) |
shadow.lg | 최대 elevation (오버레이) |
Shadow는 3단계만 제공. "xl" 없음. glow 효과의 shadow 사용 금지.
| Token | Duration | 용도 |
|---|---|---|
transition.colors | 150ms | 색상 변화 (hover, active, focus) |
transition.transform | 200ms | 위치/크기 변화 (slide, 축소/확대) |
transition.opacity | 150ms | 투명도 변화 (fade in/out) |
150-250ms 범위. 루핑 없음. 고밀도 영역에서는 강도를 줄인다.
| Token | Value | 용도 |
|---|---|---|
zIndex[10] | 10 | Sticky header |
zIndex[20] | 20 | 드롭다운 메뉴 |
zIndex[30] | 30 | 팝오버, 툴팁 |
zIndex[40] | 40 | 모달 배경 (scrim) |
zIndex[50] | 50 | 모달, 사이드바 |
import { vars } from "@/styles/theme.css";
import { sprinkles } from "@/styles/sprinkles.css";
import { breakpoints } from "@/styles/breakpoints";
Sprinkles는 responsive 스타일을 위한 utility 함수입니다.
// 기본 사용법
<div className={sprinkles({
display: "flex",
padding: "4",
gap: "3"
})} />
// 반응형 사용법
<div className={sprinkles({
display: { base: "block", md: "flex" },
padding: { base: "4", md: "8" },
flexDirection: "column"
})} />
// 색상 사용
<div className={sprinkles({
background: "surface",
color: "primary"
})} />
// Semantic colors (테마에 따라 자동 전환)
vars.color.bg.canvas // 페이지 배경
vars.color.bg.surface // 카드/패널 배경
vars.color.text.primary // 본문 텍스트
vars.color.text.link // 링크
vars.color.border.default // 기본 보더
vars.color.action.primary.bg // CTA 버튼 배경
vars.color.status.success // 성공 상태
// Raw palette (테마 불문 고정값, 차트/시각화 등)
vars.palette.teal[600] // Primary core (#0C8A99)
vars.palette.fog[300] // Secondary core (#C9D4D6)
vars.palette.danger[400] // 차트 위험 표시
// Design tokens
vars.space[4] // 1rem 간격
vars.fontSize.lg // 18px
vars.fontWeight.bold // 700
vars.lineHeight.normal // 1.5
vars.borderRadius.lg // 8px
vars.shadow.md // 기본 shadow
vars.transition.colors // 150ms 색상 전환
vars.zIndex[50] // 모달 레이어
// Breakpoints (JS/CSS 공용)
breakpoints.sm // "640px"
breakpoints.md // "768px"
breakpoints.lg // "1024px"
UI 요소에는 항상 semantic token 사용: vars.color.bg.surface, vars.color.text.primary 등. Light/dark 테마 전환이 자동 적용됩니다.
Raw palette는 예외적 상황에서만:
하드코딩된 색상값 금지: #0C8A99 같은 hex 값을 직접 사용하지 않습니다.
Primary는 인터랙션 전용: 본문 텍스트, 배경 fill, 장식에 사용하지 않습니다.
Ghost 버튼 텍스트는 neutral: brand cyan이 아닌 fog 팔레트의 중립 톤 사용.
기본값은 dark mode. Light mode는 HTML root에 data-theme="light" 추가.
<!-- Dark mode (default) -->
<html lang="en">
<!-- Light mode -->
<html lang="en" data-theme="light">
import { style } from "@vanilla-extract/css";
import { vars } from "@/styles/theme.css";
// Solid Surface 카드: border로 구분, 최소 shadow
export const card = style({
backgroundColor: vars.color.bg.surface,
border: `1px solid ${vars.color.border.default}`,
borderRadius: vars.borderRadius.lg,
padding: vars.space[6],
transition: vars.transition.colors,
});
// Label bar (retrofuturism kit)
export const labelBar = style({
backgroundColor: vars.color.bg.surfaceElevated,
borderBottom: `1px solid ${vars.color.border.subtle}`,
padding: `${vars.space[2]} ${vars.space[4]}`,
fontSize: vars.fontSize.xs,
fontWeight: vars.fontWeight.semibold,
color: vars.color.text.tertiary,
letterSpacing: "0.05em",
textTransform: "uppercase",
});
화면을 아래 유형으로 분류하고 "스타일 예산"을 배분합니다.
예시: 테이블, 로그, 코드, 긴 폼, 관리 목록
예시: 요약 + 상세, 리소스 상세 페이지, 섹션화된 설정
예시: 온보딩, 빈 상태, 접근/오류 페이지, 인트로 화면
모든 텍스트/배경 조합은 WCAG AA (4.5:1) 이상을 충족합니다.
Interactive UI 컴포넌트용 border.strong은 3:1 이상을 충족.
border.strong 사용#117D7F (palette 500 직접 사용 금지 — 대비 부족)#0C8A99): 산화된 느낌의 cyan. blue 계열을 높여 밝고 세련되면서도 차분함. 기존 Oxidized Teal보다 밝고 blue 높임으로써 old하지 않고 modern retro 느낌. 흰색 텍스트 대비 4.5:1 이상으로 접근성 기준 충족.#C9D4D6): 깨끗하고 미세한 cool tint가 있는 neutral. Primary cyan과 조화로우면서도 중립적."Fog 팔레트의 neutral 톤을 텍스트에 사용" 원칙에 따라, Fog 팔레트의 어두운 끝(700-950)을 텍스트에 사용합니다. 이들은 미세한 cool tint를 가진 neutral로, 순수 gray보다 시스템 내 일관성이 높습니다.
Ghost 버튼은 Primary 컬러 텍스트를 사용하지 않고 neutral 텍스트(fog-200 dark / fog-700 light)를 사용합니다. "Primary는 1-2개 핵심 액션에만" 원칙을 따릅니다.
Solid Surface 컨셉에서 계층 표현의 우선순위는 "spacing → border weight → tone shift → minimal shadow"입니다. Shadow는 최후 수단이므로 단계를 줄이고 opacity를 낮췄습니다.
모든 모션은 짧고 의미 있게. 150-250ms 범위를 유지합니다.
음악 스트리밍 분석 서비스의 특성상 dark mode가 기본입니다: