DESIGN-SKILL.md
Open Analytics의 디자인 언어를 단일 파일로 제공합니다. AI 에이전트와 이를 안내하는 인간을 위해 작성되었습니다. 여기의 모든 값은 제품에 실제로 적용됩니다: 16진수 코드, 스프링 상수, 반경, easing 값입니다. 이 파일을 에이전트 컨텍스트에 붙여넣거나(또는 저장소에 규칙 파일로 추가) "OA 스타일로" 화면을 요청하세요.
이것은 붙여넣기용으로 제작된 컴파일된 단일 파일 에디션입니다. 동일한 언어가 이 저장소에 적절한 에이전트 스킬(skills/oa-design/: 전체 타입 검증 소스가 포함된 컴포넌트별 레시피)과 CLI(npx getopen-design add <component>)로도 제공됩니다. Claude Code 및 기타 스킬 인식 에이전트에는 해당 버전을 선호하세요.
한 문장으로 표현된 디자인: 연속 곡률 모서리를 가진 흰색 표면이 조용한 회색 무대 위에 놓여 있고, 단일 잉크로 그려지며, 단일 스프링으로 움직입니다.
1. 열 가지 규칙
- 하나의 잉크, 모든 것이 파생됨. 전체 중립 시스템은 하나의 색상을 고정된 비율로 투명도에 혼합한 것입니다. 두 번째 회색을 절대 선택하지 마십시오.
- 두 개의 레이어, 그리고 그 사이의 간격이 페이지입니다. 표면은 움푹 들어간 회색 인세트를 담는 흰색 프레임입니다. 섹션은 플레이트이며, 그 사이의 페이지 배경이 유일한 구분선입니다. 수평선은 사용하지 마십시오.
- 표면은 스커클(squircle), 동작은 알약(pill). 카드는 연속 곡률 모서리를 가집니다. 카드가 아닌 클릭 가능한 모든 것은 알약입니다.
- 하나의 스프링 패밀리. 7개의 명명된 스프링이 전체 제품을 포괄합니다. 여덟 번째를 발명하지 마십시오.
- 크롬은 절대 기다리지 않습니다. 레이아웃과 제목은 즉시 렌더링됩니다. 픽셀 일치 스켈레톤에서 데이터만 교체되며, 팝(pop)이 아닌 블러(blur)로 도착합니다.
- 가중치는 500에서 멈춥니다. 어디에도 볼드는 없습니다. 계층 구조는 크기, 색상 및 간격에서 비롯됩니다.
- 하나의 악센트, 한 곳에 사용됩니다. 기본 동작과 기본 차트 라인에 단일 파란색을 사용합니다. 의미론적 색상은 텍스트 색조이며 채움이 아닙니다.
- 상태에는 알약, 이벤트는 스스로 종료됩니다. 지속적인 조건은 해제 불가능한 스트립으로 렌더링되며, 상태가 유지되는 동안 정확히 지속됩니다. 일회성 결과는 스스로 사라지는 토스트입니다.
- 카피는 디자인의 일부입니다. 문장형 케이스; 버튼은 발생하는 일을 알립니다. 오류는 원인과 해결 방법을 비난 없이 명시합니다.
- 항상 품질 기준을 유지합니다. 포커스 링,
role="status",aria-hidden장식,prefers-reduced-motion, 그리고 수평 페이지 스크롤 없음.
2. 토큰
라이트
:root {
--ink: #292929;
--background: #f6f6f6; /* 플레이트 사이의 회색 무대 */
--card: #ffffff;
--popover: #ffffff;
/* 잉크 기반 중립색: 백분율이 시스템입니다 */
--border: color-mix(in srgb, var(--ink) 12%, transparent);
--input: color-mix(in srgb, var(--ink) 14%, transparent);
--accent: color-mix(in srgb, var(--ink) 5%, transparent); /* 호버 워시 */
--muted: color-mix(in srgb, var(--ink) 5%, transparent);
--muted-foreground: #6d6d6d;
--foreground: var(--ink);
--primary: #305dde; /* 하나의 악센트 */
--primary-foreground: #ffffff;
--ring: #3ba6f1;
--secondary: #e9e9e9; /* 평평한 회색 컨트롤, 테두리 없음 */
--secondary-foreground: var(--ink);
/* 의미론적, 라이트에서 TEXT 색조로 (Tailwind 스케일, 700 행) */
--destructive: var(--color-red-500);
--destructive-foreground: var(--color-red-700);
--success-foreground: var(--color-emerald-700);
--warning-foreground: var(--color-amber-700);
--info-foreground: var(--color-blue-700);
--chart-1: #296FF0; /* 기본 차트 라인 */
--radius: 0.625rem;
}
더 강한 구분선이 필요하신가요? 잉크 백분율을 높이십시오(제품은 4, 5, 10, 12, 14만 사용합니다). 의미론적 색상은 단어와 8px 점에 사용되며, 패널 채움에는 절대 사용되지 않습니다.
다크
동일한 레시피, 잉크만 변경되었으며 두 가지 체계적인 변경 사항이 있습니다: 헤어라인 백분율이 2포인트 상승하고, 의미론적 텍스트가 700 행에서 400 행으로 이동합니다.
.dark {
--ink: #ededed;
--background: #191919;
--card: #212121;
--popover: #242424;
--border: color-mix(in srgb, var(--ink) 14%, transparent);
--input: color-mix(in srgb, var(--ink) 16%, transparent);
--muted-foreground: #969696;
--secondary: #2e2e2e;
--primary: #296FF0;
--destructive-foreground: var(--color-red-400);
--success-foreground: var(--color-emerald-400);
}
반경
하나의 기본값을 곱합니다. 맞춤 반경은 사용하지 않습니다:
--radius: 0.625rem; /* sm ×0.6, md ×0.8, lg ×1, xl ×1.4,
2xl ×1.8, 3xl ×2.2, 4xl ×2.6 */
rounded-full은 알약을 위해 예약되었습니다. 스커클 표면은 자체 더 큰 반경을 사용합니다(섹션 4).
타이포그래피
- Inter Tight, 가변, 가중치 300에서 500, 자체 호스팅(글꼴 CDN은 빌드를 실패시킵니다. woff2를 저장소에 보관하십시오).
- Geist Mono는
code, kbd, samp, pre, 키 및 스니펫에 사용됩니다. - 가중치 상한선은 정체성입니다: 500보다 무거운 것은 렌더링되지 않습니다. 계층 구조는 크기,
text-foreground/80대text-muted-foreground, 그리고 간격에서 비롯됩니다. - 제목:
font-medium tracking-tight; 마케팅 헤드라인은text-wrap: balance를 추가합니다. - 데이터: 열에 있거나 제자리에서 업데이트되는 숫자에는
tabular-nums를 사용합니다. - 기본 UI 음성은 14px입니다. 보조 줄은 12px이며
leading-5/leading-6를 사용합니다.
그림자
높낮이는 이진적입니다:
/* 일반 카드 */ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
/* 떠 있는 크롬 */ box-shadow: 0 1px 2px rgba(0,0,0,0.08),
0 8px 24px rgba(0,0,0,0.08);
배경 규율
body는 흰색입니다(오버스크롤 시 본문이 보입니다. 흰 화면 너머로 회색이 보이면 다른 페이지처럼 읽힙니다). 회색 무대를 원하는 화면은 자체 배경을 칠합니다. 루트는 overflow-x: clip을 얻습니다.
3. 레이아웃
플레이트. 섹션은 독립된 패널입니다. 그 사이에 보이는 페이지 배경이 유일한 경계입니다. 수직 간격은 넉넉하게(gap-12 sm:gap-20 마케팅, gap-6 앱 내부), 측면 패딩은 더 타이트하게(px-4 sm:px-6) 설정합니다. 형제 요소는 부모의 gap으로 간격을 두며, 요소별 마진은 절대 사용하지 않습니다. <hr>나 섹션 사이의 border-b는 없습니다.
범위별 너비: 마케팅 76rem (6.5xl), 앱 사이트 페이지 max-w-6xl, 목록/선택기 화면 max-w-5xl, 읽기 표면 ~65ch. 중앙 정렬, mx-auto.
앱 페이지 해부학:
고정 헤더 (h-14) → 컨테이너 pt-14
선택적 떠 있는 알약 (고정 top-16, 중앙 정렬)
<main class="px-4 pb-36 pt-8 sm:px-6 bg-white">
제목 행: h1 text-xl font-medium tracking-tight + 오른쪽에 하나의 컨트롤
선택적 알림 스트립
플레이트: flex flex-col gap-6
제목 행에는 정확히 하나의 컨트롤만 있습니다. pb-36은 콘텐츠가 뷰포트 가장자리에 닿지 않도록 합니다.
그리드: grid gap-3, 반응형 열(grid-cols-2 sm:grid-cols-4 통계용). 카드는 고정된 콘텐츠 높이를 공유하고 내부적으로 스크롤됩니다. 그리드의 기준선은 절대 깨지지 않습니다.
반응형: 390px 및 1280px에서 디자인합니다. 수평 쌍은 sm: 아래에서 쌓이며 동작이 마지막에 옵니다. 넓은 콘텐츠는 자체 overflow-x-auto 컨테이너에서 스크롤되며, 페이지는 가로로 스크롤되지 않습니다.
설정: 좁은 탭 레일 + 섹션 패널의 콘텐츠 열; 각 섹션은 제목 블록(제목 + 하나의 회색 문장, 오른쪽에 선택적 단일 동작)으로 시작합니다. ?tab=<id>를 통해 탭에 딥 링크합니다.
4. 컴포넌트
스커클 표면
두 변수로 매개변수화된 CSS shape() 클립-패스를 통한 연속 곡률 모서리, 지원되는 경우 corner-shape: squircle 사용:
.squircle {
border-radius: 26px;
--card-clip-radius: 14px;
--card-clip-handle: 2.25px;
clip-path: shape(
from var(--card-clip-radius) 0px,
line to calc(100% - var(--card-clip-radius)) 0px,
curve to 100% var(--card-clip-radius)
with calc(100% - var(--card-clip-handle)) 0px / 100% var(--card-clip-handle),
line to 100% calc(100% - var(--card-clip-radius)),
curve to calc(100% - var(--card-clip-radius)) 100%
with 100% calc(100% - var(--card-clip-handle)) / calc(100% - var(--card-clip-handle)) 100%,
line to var(--card-clip-radius) 100%,
curve to 0px calc(100% - var(--card-clip-radius))
with var(--card-clip-handle) 100% / 0px calc(100% - var(--card-clip-handle)),
line to 0px var(--card-clip-radius),
curve to var(--card-clip-radius) 0px
with 0px var(--card-clip-handle) / var(--card-clip-handle) 0px,
close
);
corner-shape: squircle;
}
@media (min-width: 640px) {
.squircle { border-radius: 50px; --card-clip-radius: 20px; --card-clip-handle: 3px; }
}
미니 카드
외부 스커클: bg-card, border, p-1, 일반 그림자
├── 헤더 스트립 (pb-2 pl-3.5 pr-2 pt-1.5):
│ 16px 음소거 아이콘 + text-sm font-medium text-foreground/80 제목
│ 제목 중앙선 위의 선택적 칩
│ "모두 보기 ›" (text-xs 음소거; 호버: 악센트 워시, 화살표가 2px 슬라이드)
└── 내부 스커클: bg #f6f6f6, border, rounded-[22px] sm:rounded-[44px],
고정 높이 (h-44 = 5개의 32px 행), overflow hidden
카드는 데이터와 함께 절대 커지지 않습니다. 더 긴 목록은 내부에서 스크롤됩니다. 오버플로는 오른쪽 아래에 24px 쉐브론 칩을 보여주며, y:[0,2.5,0] (1.4초 easeInOut 루프)로 움직이고 스크롤 끝에서 페이드 아웃(0.18s, scale 0.7)됩니다. 행: 32px, 전체 너비 악센트 워시 호버, tabular-nums 값.
버튼
알약, font-medium, 물리적으로 눌림: active:translate-y-px active:scale-[0.98].
- 기본: 악센트 파란색, 베벨 포함(배경/테두리 색상이 진한 인디고 방향으로 혼합, 상단에
inset 0 1px 0 rgba(255,255,255,0.22), 하단에 어두운 인세트). 호버 시 순수한 기본색으로 밝아집니다. - 보조: 평평한
--secondary, 투명 테두리, 호버 시 5% 잉크 혼합. 실제 작업을 수행합니다. - 고스트: 음소거 텍스트만. 파괴적: 빨간색 채움, 확인 단계에서만 사용합니다.
- 크기 h-9 / h-8 / h-7; 앱 크롬은 주로 작은 크기로 실행됩니다.
- 로딩 중에는 레이블을 유지하고 14px 링 스피너를 추가합니다(
border-2 border-current border-t-transparent). 너비는 절대 변하지 않습니다. focus-visible:ring-[3px]를--ring/50으로 설정합니다. 절대 제거하지 마십시오.
드롭다운 패널 (메뉴, 선택, 스위처)
트리거 알약 → 떠 있는 패널(떠 있는 그림자, 테두리, 흰색, rounded-2xl+), PANEL (550/38)로 스프링 인, 작은 y/오프셋 포함, 더 빠르게 종료합니다. 32px rounded-lg 항목, 악센트 워시 호버; 활성 강조 표시는 이동하는 하나의 공유 요소(layoutId 또는 너비/높이 스프링 사용)이며, 항목별로 다시 칠하지 않습니다. 외부 포인터 다운 및 Escape 시 닫힘.
모달
배경은 ~0.15초 동안 페이드; 패널은 opacity 0, scale .96에서 POP (400/26)으로 스프링 인, 380/28에서 종료됩니다. 다중 단계 대화상자는 하나의 패널을 유지하며, 각 단계의 측정된 크기(LAYOUT 550/40 + ResizeObserver)로 높이가 애니메이션되는 동안 단계가 슬라이드(x: ±12px, popLayout, 0.16초 easeOut 페이드)됩니다. 하단: h-12 스트립, 뒤로(보조 xs) + 기본 xs, 오른쪽 정렬.
탭 바
활성 알약은 고정된 창입니다. 레이블이 그 뒤로 슬라이드됩니다. 들어오는 레이블은 자체 너비의 110%에서 이동 방향으로 들어옵니다. 나가는 레이블은 반대 방향으로 고정된 130px로 나갑니다(알약이 더 긴 단어를 향해 커질 때 백분율은 부족합니다). 알약 너비/높이는 LAYOUT 스프링에서 측정된 레이블을 따릅니다. 전체 불투명도가 유지됩니다. 마스크가 작업을 수행합니다.
스켈레톤
- 크롬은 절대 기다리지 않습니다. 데이터 영역만 스켈레톤입니다.
- 픽셀 일치: 막대는 나타내는 텍스트와 동일한 줄 높이 슬롯 안에 위치합니다(
flex h-6 items-center가h-3막대를 감쌈). 교체 시 아무것도 움직이지 않습니다. - 데이터는 포커스로 도착합니다:
opacity ~0.4 + blur(4px)에서 선명해지는 원샷 애니메이션. 전환이 아닌 애니메이션(콘텐츠는 이미 최종 상태로 마운트되며, 전환은 이전 값이 없음). 이후filter: blur(0)를 유지합니다.
막대: rounded bg-muted-foreground/15, 느린 background-position 스윕(2초 선형 무한, -1s 시작 오프셋으로 막대가 위상이 다름).
알림, 빈 상태, 떠 있는 알약
알림 스트립이 설명하는 콘텐츠 위에:
<div class="flex flex-col gap-2 rounded-2xl border border-border bg-card
px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
role="status">
<p class="text-sm leading-6 text-muted-foreground">
<span class="font-medium text-foreground">아직 이벤트가 없습니다.</span>
스니펫이 활성화되어 있으면 사이트를 탐색하십시오. 그러면 몇 초 안에 첫 번째 페이지뷰를 감지합니다.
</p>
<button class="…보조 알약…">설치 확인</button>
</div>
하나의 굵은 주장, 하나의 회색 문장, 하나의 보조 동작; sm: 아래에서 쌓임; 상태가 지속되는 동안 정확히 지속되며 해제할 수 없음.
떠 있는 알약은 앱 전체의 지속적인 조건에 사용됨: 고정 top-16, 중앙 정렬, rounded-full bg-card py-1.5 pl-3 pr-4 text-xs font-medium, 떠 있는 그림자, 8px 상태 점(긴급 시 깜빡임), BANNER (400/30)에서 y:-8로 스프링 인, 0.15초에 종료. 알약 전체가 상태가 해결되는 위치로 연결됨. 상태 알약은 절대 스스로 해제되지 않음. 이벤트 알약은 ~6초 후에 사라짐.
토스트
0.35초 미만, easing cubic-bezier(0.5, 1, 0.89, 1):
- 성공: 펄스
scale 1 → 1.025 → 0.99 → 1(0.32초). - 오류: 흔들림
x 0 → -3 → 3 → -3 → 0px (0.28초).
반복 시 다시 시작되도록 홀수/짝수 키프레임 복사본을 번갈아 사용합니다.
5. 모션
| 이름 | 값 | 용도 |
|---|---|---|
| PANEL | 550 / 38 | 드롭다운, 메뉴, 보드, 토글 |
| LAYOUT | 550 / 40 | 측정된 높이/너비, 슬라이딩 알약 |
| POP | 400 / 26 | 모달 입장 |
| POP_EXIT | 380 / 28 | 모달 퇴장 |
| BANNER | 400 / 30 | 떠 있는 알약, 페이지 배너 |
| FLICK | 900 / 50 | 아이콘 마이크로 움직임 |
| CHART | 300 / 25-30 | 차트 툴팁, 크로스헤어 |
마이크로 페이드: 0.1초에서 0.18초 easeOut. 앱 크롬에서 0.2초보다 긴 트윈은 없습니다. 느리게 느껴지면, 지속 시간을 늘리지 말고 댐핑을 낮추십시오.
시그니처 움직임: 측정된 높이 안무(하나의 일정한 패널, 단계가 슬라이드), 이동하는 강조 표시(공유 활성 요소 하나, 항목별 다시 칠하지 않음), 입장/퇴장 비대칭(퇴장이 항상 더 빠름), 호버는 색상만(최대 2px 화살표 슬라이드 추가 가능, 카드는 절대 들리거나 크기 조절되지 않음), 화면당 최대 하나의 작은 분위기 루프.
규칙: 마운트 해제되는 모든 항목에 AnimatePresence 사용; prefers-reduced-motion 존중(공개는 축소하여 표시, 루프 중지); 측정되지 않은 레이아웃에 절대 애니메이션 적용하지 않음; 렌더링 중에 시계를 읽지 않음(분 버킷의 틱 저장소에 구독).
6. 랜딩 페이지
동일한 토큰, 동일한 구성 요소, 동일한 모션. 랜딩 페이지는 마케팅 너비와 더 큰 타이포그래피를 사용합니다.
이 파일은 OA 디자인 시스템의 완전히 컴파일된 버전입니다. 자세한 내용은 이 저장소의 skills/oa-design/ 디렉토리 또는 npx getopen-design add 를 참조하십시오.