Communitygithub.com

OpenLabs-so/oa-design

Open Analytics의 디자인 언어를 단일 파일로 제공합니다.

oa-design란 무엇인가요?

이 스킬은 Open Analytics 제품의 디자인 언어를 단일 파일로 압축하여 AI 에이전트와 이를 사용하는 개발자에게 제공합니다. 모든 색상 코드(hex), 스프링 상수, 모서리 반경, easing 값이 제품에 실제로 적용된 그대로 포함되어 있어, 에이전트 컨텍스트에 이 파일을 붙여넣거나 저장소에 규칙 파일로 추가한 후 'OA 스타일로' 화면을 요청하면 즉시 사용할 수 있습니다. 핵심 규칙은 단일 잉크(ink)에서 모든 중립색을 파생하고, 흰색 표면과 연속 곡률 모서리(squircle)를 사용하며, 단일 스프링 패밀리로 모든 움직임을 제어하는 것입니다. 가벼운 테마와 어두운 테마 모두에 대한 CSS 토큰, 타이포그래피(Inter Tight, Geist Mono), 레이아웃 규칙(plates, 그리드, 반응형), 버튼, 드롭다운, 모달, 탭 바, 스켈레톤, 알림, 토스트 등의 컴포넌트 설계가 상세히 문서화되어 있습니다. 또한 PANEL, LAYOUT, POP 등 7가지 명명된 스프링과 마이크로 페이드, 서명 움직임(measured-height choreography, traveling highlight) 등 모션 시스템을 정의하며, 애니메이션 존재(AnimatePresence)와 'prefers-reduced-motion' 준수를 강조합니다. 이 스킬은 Claude Code, Cursor, Codex 등 AI 에이전트 플랫폼에서 사용할 수 있으며, 저장소에는 컴포넌트별 레시피와 타입 검증 소스가 포함된 'skills/oa-design/' 폴더와 CLI('npx getopen-design add <component>')도 함께 제공됩니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OpenLabs-so/oa-design/tree/HEAD

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

DESIGN-SKILL.md

Open Analytics의 디자인 언어를 단일 파일로 제공합니다. AI 에이전트와 이를 안내하는 인간을 위해 작성되었습니다. 여기의 모든 값은 제품에 실제로 적용됩니다: 16진수 코드, 스프링 상수, 반경, easing 값입니다. 이 파일을 에이전트 컨텍스트에 붙여넣거나(또는 저장소에 규칙 파일로 추가) "OA 스타일로" 화면을 요청하세요.

이것은 붙여넣기용으로 제작된 컴파일된 단일 파일 에디션입니다. 동일한 언어가 이 저장소에 적절한 에이전트 스킬(skills/oa-design/: 전체 타입 검증 소스가 포함된 컴포넌트별 레시피)과 CLI(npx getopen-design add <component>)로도 제공됩니다. Claude Code 및 기타 스킬 인식 에이전트에는 해당 버전을 선호하세요.

한 문장으로 표현된 디자인: 연속 곡률 모서리를 가진 흰색 표면이 조용한 회색 무대 위에 놓여 있고, 단일 잉크로 그려지며, 단일 스프링으로 움직입니다.


1. 열 가지 규칙

  1. 하나의 잉크, 모든 것이 파생됨. 전체 중립 시스템은 하나의 색상을 고정된 비율로 투명도에 혼합한 것입니다. 두 번째 회색을 절대 선택하지 마십시오.
  2. 두 개의 레이어, 그리고 그 사이의 간격이 페이지입니다. 표면은 움푹 들어간 회색 인세트를 담는 흰색 프레임입니다. 섹션은 플레이트이며, 그 사이의 페이지 배경이 유일한 구분선입니다. 수평선은 사용하지 마십시오.
  3. 표면은 스커클(squircle), 동작은 알약(pill). 카드는 연속 곡률 모서리를 가집니다. 카드가 아닌 클릭 가능한 모든 것은 알약입니다.
  4. 하나의 스프링 패밀리. 7개의 명명된 스프링이 전체 제품을 포괄합니다. 여덟 번째를 발명하지 마십시오.
  5. 크롬은 절대 기다리지 않습니다. 레이아웃과 제목은 즉시 렌더링됩니다. 픽셀 일치 스켈레톤에서 데이터만 교체되며, 팝(pop)이 아닌 블러(blur)로 도착합니다.
  6. 가중치는 500에서 멈춥니다. 어디에도 볼드는 없습니다. 계층 구조는 크기, 색상 및 간격에서 비롯됩니다.
  7. 하나의 악센트, 한 곳에 사용됩니다. 기본 동작과 기본 차트 라인에 단일 파란색을 사용합니다. 의미론적 색상은 텍스트 색조이며 채움이 아닙니다.
  8. 상태에는 알약, 이벤트는 스스로 종료됩니다. 지속적인 조건은 해제 불가능한 스트립으로 렌더링되며, 상태가 유지되는 동안 정확히 지속됩니다. 일회성 결과는 스스로 사라지는 토스트입니다.
  9. 카피는 디자인의 일부입니다. 문장형 케이스; 버튼은 발생하는 일을 알립니다. 오류는 원인과 해결 방법을 비난 없이 명시합니다.
  10. 항상 품질 기준을 유지합니다. 포커스 링, 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 스프링에서 측정된 레이블을 따릅니다. 전체 불투명도가 유지됩니다. 마스크가 작업을 수행합니다.

스켈레톤

  1. 크롬은 절대 기다리지 않습니다. 데이터 영역만 스켈레톤입니다.
  2. 픽셀 일치: 막대는 나타내는 텍스트와 동일한 줄 높이 슬롯 안에 위치합니다(flex h-6 items-center가 h-3 막대를 감쌈). 교체 시 아무것도 움직이지 않습니다.
  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 → 0 px (0.28초).

반복 시 다시 시작되도록 홀수/짝수 키프레임 복사본을 번갈아 사용합니다.


5. 모션

이름값용도
PANEL550 / 38드롭다운, 메뉴, 보드, 토글
LAYOUT550 / 40측정된 높이/너비, 슬라이딩 알약
POP400 / 26모달 입장
POP_EXIT380 / 28모달 퇴장
BANNER400 / 30떠 있는 알약, 페이지 배너
FLICK900 / 50아이콘 마이크로 움직임
CHART300 / 25-30차트 툴팁, 크로스헤어

마이크로 페이드: 0.1초에서 0.18초 easeOut. 앱 크롬에서 0.2초보다 긴 트윈은 없습니다. 느리게 느껴지면, 지속 시간을 늘리지 말고 댐핑을 낮추십시오.

시그니처 움직임: 측정된 높이 안무(하나의 일정한 패널, 단계가 슬라이드), 이동하는 강조 표시(공유 활성 요소 하나, 항목별 다시 칠하지 않음), 입장/퇴장 비대칭(퇴장이 항상 더 빠름), 호버는 색상만(최대 2px 화살표 슬라이드 추가 가능, 카드는 절대 들리거나 크기 조절되지 않음), 화면당 최대 하나의 작은 분위기 루프.

규칙: 마운트 해제되는 모든 항목에 AnimatePresence 사용; prefers-reduced-motion 존중(공개는 축소하여 표시, 루프 중지); 측정되지 않은 레이아웃에 절대 애니메이션 적용하지 않음; 렌더링 중에 시계를 읽지 않음(분 버킷의 틱 저장소에 구독).


6. 랜딩 페이지

동일한 토큰, 동일한 구성 요소, 동일한 모션. 랜딩 페이지는 마케팅 너비와 더 큰 타이포그래피를 사용합니다.


이 파일은 OA 디자인 시스템의 완전히 컴파일된 버전입니다. 자세한 내용은 이 저장소의 skills/oa-design/ 디렉토리 또는 npx getopen-design add 를 참조하십시오.

관련 스킬