Figma textarea(135:1655)의 6상태를 옮긴다. 시안에 크기 축이 없어 상자는
하나뿐이고 높이는 rows가 정한다(기본 5줄, 시안과 같음).
상태 판정은 FoxInput과 같은 두 갈래다 — 기본은 브라우저가 판단하고(포커스·
값·네이티브 속성·aria-invalid), data-state를 주면 그 모양으로 고정된다.
상태 매트릭스도 FoxInput과 동일해 규칙을 그대로 옮겼다.
하단은 왼쪽 헬퍼와 오른쪽 글자수 카운터로 나뉜다. 카운터는 현재 길이만 굵고
색이 다르며, 오류일 때 빨개진다(시안의 error 변형이 100/100을 빨갛게 둔다).
maxLength가 없으면 개수만 적는다. 비제어 입력의 길이는 직접 따라간다.
리사이즈는 시안에 신호가 없어 폭이 흔들리지 않게 세로만 허용한다.
Co-Authored-By: Claude Opus 5
Figma input(135:621)의 2타입 × 3크기 × 6상태를 옮긴다. 네이티브 을
감싸므로 시안에 없는 password·email·number 등 모든 타입이 그대로 통한다.
상태는 두 갈래다. 기본은 브라우저가 판단한다 — 포커스는 :focus-within,
비활성·읽기전용은 네이티브 속성, 오류는 aria-invalid. 시안의 completed는
default와 글자색만 다른데 그건 플레이스홀더와 입력값의 네이티브 차이라 규칙이
필요 없다. 모양을 못박아야 하면 루트에 data-state를 준다.
date는 브라우저가 그리는 달력 아이콘과 피커를 그대로 쓴다(사용자 승인).
지우기 버튼은 포커스 중이고 값이 있을 때만 뜨고 누르면 값을 비운다 —
비제어 입력은 DOM을 직접 비우고 제어 입력은 onChange()로 알린다.
헬퍼 메시지 색은 상태를 따라간다(기본 회색 · 포커스 파랑 · 완료 초록 ·
오류 빨강). 아이콘은 @fox에 아이콘 세트가 없어 슬롯으로 두었다.
Co-Authored-By: Claude Opus 5
Figma btn-page(404:5121)의 네 변형을 옮긴다. 자식은 버튼 그룹이고 여럿이면
양끝, 하나면 가운데다.
자식 수를 React에서 세지 않고 CSS `:has(> :only-child)`로 판단한다. 프래그먼트나
조건부 렌더로 자식이 갈려도 실제 DOM이 기준이라 어긋나지 않고, React를 안 쓰는
소비자도 표시할 클래스가 없다.
패널이 자식에게 강제하는 두 가지(사용자 승인):
- 패널 안 그룹 간격은 gap-3(8px). 단독으로 쓰는 그룹의 gap-2(4px)는 그대로다.
- 모바일에서는 패널도 그룹도 세로로 쌓이고 버튼이 폭을 채운다.
시안이 패널 안 버튼에만 주는 min-width 160px은 변수 바인딩이 없고 토큰 스케일에도
160이 없어 리터럴로 둔다(사용자 승인). 상단 80px 여백은 spacing-top-2xlg이며
offset={false}로 끌 수 있다.
Co-Authored-By: Claude Opus 5
새 Figma 파일(KE9UILWhB8qTBe14uXUn4F)의 다섯 시안을 이전 파일과 대조했다.
노드 ID·변형 목록·토큰 값·구조가 전부 같아 스타일 변경분은 없다. 컴파일된
CSS는 바이트 단위로 이전과 동일하다.
btn 컴포넌트 세트 48변형 전체 대조 — 일치
btn-txt 생성 코드가 노드 ID까지 이전 파일과 동일
btn-lnk 생성 코드 바이트 비교 — 동일(에셋 URL만 재발급)
btn-ico 변수 세트·메타데이터 + ghost/overlay/disabled 대표 변형 — 일치
toggle default·ghost·overlay의 active 3종 + 아이콘 fill — 일치
btn-txt의 아이콘 크기는 컴포넌트 세트가 여전히 리터럴이다. 다만 전시 섹션의
인스턴스가 icon/4·3·2를 쓰고 있어 우리 매핑과 의도가 같음을 확인했고 그
근거를 주석에 남긴다.
Co-Authored-By: Claude Opus 5
$FOX_TOKENS_SRC를 새 export로 바꿔 변환기를 재실행했다. 토큰의 개수와
이름은 그대로이고 값 세 자리만 바뀐다.
spacing-bottom-sm 1.2rem → 1rem (PC·모바일 공통)
grid-wrap-default 158.4rem → 155.2rem (PC만, 모바일 34.8rem 유지)
변환기가 함께 생성하는 _root/_functions/_mixins/abstracts/index는 손으로
줄여 둔 주석이 되살아나 되돌렸다 — 이 파일들은 토큰 맵을 순회할 뿐이라
값 변화의 영향을 받지 않는다.
원본 JSON 전량 대조: responsive 150 · size 55 · number 23 · primitive 색 69 ·
theme 33 · light/dark 271쌍 · chart-alt 29 모두 일치.
Co-Authored-By: Claude Opus 5
Figma btn-group(398:4854)에서 가져온 건 간격뿐이다 — 크기·방향과 무관하게
gap-2(4px) 하나여서 그룹에 크기별 스타일이 없다. 배치 규칙(방향·정렬·줄바꿈)은
지시받은 사양이다.
크기는 그룹이 단일 진실 공급원이다. 자식을 cloneElement로 덮어써서 자식이
저마다 다른 size를 줘도 그룹 값으로 통일된다. 세로일 때는 fullWidth까지 함께
내려주고 CSS도 stretch라, React를 안 쓰는 소비자도 같은 결과를 얻는다.
Children.map이 프래그먼트 안으로 들어가지 않아 직접 내려간다. 그러지 않으면
<>…로 감싼 버튼이 덮어쓰기를 피해 가고 Fragment에 size가 붙어 경고가 난다.
호스트 요소(div·span)는 DOM에 없는 속성이 넘어가지 않도록 건드리지 않는다.
Co-Authored-By: Claude Opus 5
시안이 지정한 text-underline-position/text-decoration-thickness의
`from-font`를 옮기지 않아 브라우저 기본값 `auto`로 그려졌다. auto는 밑줄을
베이스라인 바로 아래에 붙여 g·y·다 같은 내림획을 관통하고, skip-ink가 그
자리를 파내 밑줄이 끊겨 보인다. lg에서 글자가 커질수록 두드러진다.
단축 `text-decoration`은 thickness를 auto로 되돌리므로 계열·상태 규칙은
`text-decoration-line`만 쓴다.
Co-Authored-By: Claude Opus 5
디자인 레이어가 React + CSS Modules 전용이던 문제를 푼다. 기존 클래스명은
`.button`·`.label`·`.icon`처럼 일반명사여서, CSS Modules 스코프 밖으로 나가면 소비
프로젝트의 동명 클래스와 전부 충돌했다. SCSS만 쓰는 프로젝트(Vue·Svelte·서버 템플릿)는
버튼을 다시 만들 수밖에 없었다.
`fox-` 접두사 + BEM(fox-button__label · fox-button--primary)으로 바꿔 이름을 전역
유일하게 만들고 CSS Modules를 걷어냈다. 충돌 방지는 도구가 아니라 접두사 규율이 담당한다.
React 컴포넌트는 이 클래스를 조립하는 얇은 래퍼가 되고, 래퍼 없이도 같은 디자인을 얻는다.
진입점을 둘 다 제공한다 — `@fox/styles/components`(전부)와 개별 파티셜
`@fox/styles/fox-button`(안 쓰는 CSS를 번들에서 뺀다). 둘을 같이 써도 Sass가 모듈을
한 번만 로드해 중복되지 않는다.
마크업 계약을 스타일 파일 상단 주석과 @fox/README.md에 적었다 — React 밖 소비자는
그것만 보고 DOM을 짤 수 있어야 한다.
앱 화면 스타일은 `.module.scss` 그대로다(앱 전용이라 스코프가 이득). 규약 문서도 그
구분이 드러나게 고쳤다.
Co-Authored-By: Claude Opus 5
Figma 시안(관리자페이지 node 390:439)을 토큰만으로 옮겼다. 컴파일된 CSS의 토큰 참조
50개, 원시 hex/px/rem 리터럴 0개다.
시안에서 확인한 것(추측하지 않음): gap은 lg·md·sm이 gap-2이고 xsm만 gap-1이다.
primary에는 테두리가 없고 secondary·default만 border-1을 갖는다. disabled는 계열과
무관하게 테두리가 사라진다(시안에서 disabled-border 미사용). 높이만 고정이고 좌우
padding만 지정돼 너비는 가변이다.
토큰이 아닌 코드 세 가지를 사용자 승인 후 넣었다.
- line-height: 1 / letter-spacing: -0.025em — 시안 텍스트 스타일 label/*-strong의 값이나
Figma에서 변수로 export되지 않아 토큰이 없다. 네 크기가 같은 값이라 규칙 하나로 끝난다.
- box-sizing: border-box — 높이 고정 + secondary·default의 1px 테두리 때문에 필수다.
없으면 계열별 바깥 높이가 2px 어긋난다.
- cursor·white-space·flex-shrink — 구조·상호작용 규칙. 버튼 스코프에만 적용한다.
모양 규칙은 @fox/styles/_fox-button.scss가 소유하고 module이 @use로 끌어올린다 —
클래스명이 모듈 스코프로 감싸져 전역 충돌이 없고, index.scss는 이 파티셜을 포함하지
않아 CSS가 중복 출력되지 않는다.
아이콘은 계열별 색을 토큰으로 입히기 위해 ReactNode로 받는다(currentColor 전제).
크기는 슬롯이 form-icon-* 토큰으로 정한다.
error 계열은 시안 미정이라 클래스를 비워 뒀다.
Co-Authored-By: Claude Opus 5
값의 출처가 컴파일된 CSS로 옮겨간 뒤 매니페스트가 제공하던 것은 Figma 변수 경로와
"원본에선 참조였으나 컬렉션이 없어 값으로 굳은 것"(chart-alt 18개) 표시 둘뿐이었다.
둘 다 쓰지 않기로 해 생성물·화면·변환기에서 모두 걷어낸다.
변환기는 이제 SCSS만 생성한다. 화면은 스타일시트 외에 아무것도 참조하지 않는다.
Co-Authored-By: Claude Opus 5
SCSS를 고치면 화면에 즉시 반영되게 하려는 것이다. 매니페스트를 값의 출처로 두면
생성물과 화면이 어긋날 수 있는데, 스타일시트를 직접 읽으면 그 여지가 없다.
핵심은 계산값이 아니라 **선언 원문**을 읽는 것이다. 계산값은 참조가 이미 풀려 있고
현재 테마·뷰포트 한쪽만 보이지만, 원문에는 참조(var(--fox-…))와 light/dark 양쪽,
PC/모바일 양쪽이 모두 남아 있다. 덕분에 다크 테마에서도 Light 섹션이, 넓은 창에서도
Mobile 섹션이 정확한 값을 보여준다.
light-dark()는 Lightning CSS가 폴리필한 형태로 나오므로 그 형태를 먼저 파싱하고,
폴리필이 없는 설정을 대비해 원형도 함께 처리한다. 괄호 짝을 세어 자르는 이유는
값 안에 var()가 중첩돼 정규식으로는 안전하게 나눌 수 없기 때문이다.
매니페스트는 CSS가 담지 못하는 두 가지만 제공한다 — Figma 변수 경로, 그리고
"원본에선 참조였으나 컬렉션이 없어 값으로 굳은 것"(chart-alt 18개) 표시.
Co-Authored-By: Claude Opus 5
디자인 시안이 나오면 다시 추가한다. Tailwind 제거 이후 퇴장 애니메이션(animate-fade-out)
클래스가 죽어 onAnimationEnd가 영영 발화하지 않았고, 그 결과 markSplashShown()도
setIsVisible(false)도 호출되지 않아 모든 화면 상단에 스플래시가 영구히 남아 있었다.
삭제: app/_components/splash-gate.tsx · components/ui/splash-screen.tsx ·
lib/constants/splash.ts. app/layout.tsx의 inline style/script와 도
함께 걷어냈다.
lib/constants/theme.ts의 주석이 삭제될 splash 상수 파일을 가리키고 있어 그 상호참조만
제거했다. public/images/logo-foxpay-white.svg는 재추가 시 다시 쓰므로 남겨둔다.
(public)/layout.tsx는 AuthCard를 더 이상 감싸지 않으므로 그 설명 주석을 제거한다.
Co-Authored-By: Claude Opus 5
5px에서는 밝은 회색 계열(#FFFFFF · #F4F5F6 · #E6E8EA)이 서로 구분되지 않았고
반투명 토큰의 알파 여부도 보이지 않았다.
16px는 icon-3(1.6rem)과 같은 값이라 리터럴 대신 토큰을 쓴다 — 5px일 때는 스케일에
맞는 값이 없어 리터럴이 불가피했던 예외가 사라졌다.
Co-Authored-By: Claude Opus 5
Figma 변수 페이지와 1:1로 대조하기 위한 감사 화면으로 바꾼다. 분류를 우리 CSS 그룹이
아니라 원본 컬렉션/모드로 두고 — Mode(Light/Dark) · Responsive(PC/Mobile) · Size ·
Theme · Primitive — 파일에 값이 적혀 있으면 그 값을, 다른 토큰을 가리키면 참조 토큰을
표시한다. 토큰 이름 아래에 Figma 변수 경로를 함께 적는다.
CSSOM 읽기로는 이 요구를 만족할 수 없어 걷어냈다. CSS는 alias를 var()로 흡수하고
light/dark를 light-dark() 한 줄로 합치므로 "원본이 값을 적었는지 토큰을 가리켰는지"를
되살릴 수 없다. 대신 변환기가 원본 구조 그대로를 담은 매니페스트를 함께 생성한다 —
여전히 자동 생성이라 목록을 손으로 관리하지 않는다.
미리보기도 CSS 변수가 아니라 매니페스트의 해석값을 쓴다. 변수를 쓰면 PC 섹션을 좁은
창에서 볼 때 모바일 값이, Light 섹션을 다크 테마에서 볼 때 다크 색이 그려진다.
⚠️ 매니페스트를 만들며 잡은 것: 이 export에는 `theme`이라는 이름의 컬렉션이 둘이고
(제공된 파랑 계열, 미제공 청록 계열) 변수 경로까지 같아, 이름만으로 매칭하면 chart-alt
18개가 엉뚱하게 해결된 것처럼 보인다. 값 대조를 넣어 "미제공 컬렉션"으로 표시한다.
SCSS 생성 쪽은 원래부터 값 대조를 하고 있어 영향이 없었다.
Co-Authored-By: Claude Opus 5
토큰 표기를 `fox.color(background-default)`에서 `color-background-default`로 바꾼다.
`--fox-` 접두사만 뗀 실제 CSS 변수 이름이라 devtools에서 보는 것과 1:1로 대응하고,
함수 표기처럼 그룹명이 괄호 밖으로 빠지지 않아 목록을 훑을 때 이름이 바로 읽힌다.
표기를 만드는 함수가 하나뿐이라 스와치·서체 목록·값 표가 갈릴 일이 없다.
그 결과 쓰이지 않게 된 fn prop을 제거한다.
사이드바의 "추가 방법"은 컴포넌트 등록 여부와 무관하게 목록 맨 아래에 고정한다 —
이전에는 컴포넌트가 하나라도 생기면 사라져 추가 절차를 찾을 수 없었다. 컴포넌트가
아니라 안내이므로 점선 테두리와 흐린 글자로 등록된 탭과 층위를 구분한다.
Co-Authored-By: Claude Opus 5