# @fox — 포터블 디자인 시스템 프로젝트에 종속되지 않는 디자인 시스템 패키지. **이 폴더를 통째로 복사하고 설정 2줄을 추가하면** 다른 프로젝트에서 그대로 동작합니다. ``` @fox/ styles/ SCSS 토큰 · 함수 · 믹스인 index.scss 글로벌 진입점 (앱이 딱 한 번 import — CSS 출력) abstracts.scss 저작 진입점 (컴포넌트가 @use — CSS 출력 0) tokens/ 토큰 map = 단일 진실 공급원(SSOT) _root.scss map → --fox-* 커스텀 프로퍼티 생성 _reset.scss 리셋 & 베이스 _functions.scss fox.color() 등 토큰 접근 + 검증 _mixins.scss fox.typo() · fox.media() 등 조합 패턴 core/ components/ 공용 컴포넌트 (규약은 components/README.md) utils/ cx() 등 의존성 없는 유틸 ``` ## 다른 프로젝트로 이식하기 1. `@fox/` 폴더를 대상 프로젝트 루트에 복사합니다. 2. `sass`를 설치합니다. ```bash npm install --save-dev sass ``` 3. **TypeScript 경로 별칭** — `tsconfig.json`: ```json { "compilerOptions": { "paths": { "@fox/*": ["./@fox/*"] } } } ``` 4. **SCSS 로드 경로** — `next.config.ts`: ```ts sassOptions: { loadPaths: [path.join(process.cwd())] } ``` TypeScript의 `paths`는 번들러 전용이라 Sass의 `@use` 해석에는 관여하지 않습니다. 그래서 같은 경로를 양쪽에 각각 선언해야 TS와 SCSS의 import 표기가 일치합니다. 이게 없으면 컴포넌트마다 `../../../` 상대 경로를 써야 해서 폴더 복사가 불가능해집니다. 5. 앱의 글로벌 스타일에서 진입점을 한 번 import 합니다. ```scss /* app/globals.scss */ @use "@fox/styles"; ``` 6. (선택) 폰트를 주입합니다 — 아래 "호스트 앱과의 계약" 참고. Next.js가 아닌 번들러라면 4번만 그 도구의 Sass 옵션(`includePaths` / `loadPaths`)으로 바꿔주면 됩니다. ## 핵심 설계 **토큰의 SSOT는 `tokens/`의 SCSS map 하나뿐입니다.** 여기서 두 가지가 함께 파생됩니다 — `_root.scss`가 만드는 `--fox-*` CSS 커스텀 프로퍼티와, `_functions.scss`가 검증에 쓰는 유효 이름 목록. 값과 이름이 한 곳에만 있으므로 둘이 어긋날 수 없습니다. **규율을 컴파일러가 강제합니다.** 화면 코드는 `color: #4f46e5`를 쓸 수 없고 `fox.color(primary)`로 토큰을 지목해야 합니다. 없는 이름을 쓰면 빌드가 실패하며, 에러 메시지가 사용 가능한 토큰 목록을 함께 출력합니다. ``` Error: [@fox] 알 수 없는 color 토큰: `primry` — 사용 가능한 값: primary, primary-hover, ... ``` **1rem = 10px입니다.** `_reset.scss`의 `html { font-size: 62.5% }`가 근거입니다. px → rem 환산이 10으로 나누기가 되어 토큰 값이 읽기 쉽고, px 고정과 달리 사용자가 브라우저 글자 크기를 키우면 앱 전체가 비례해 확대됩니다. > ⚠️ **미디어 쿼리 안의 `rem`은 예외입니다.** 미디어 쿼리는 `html`의 `font-size` 영향을 > 받지 않고 항상 브라우저 기본 크기(보통 16px) 기준으로 계산합니다. 그래서 > `tokens/_layout.scss`의 브레이크포인트 `48rem`은 480px가 아니라 **768px**입니다. > 각 값에 px 환산을 주석으로 달아 두었습니다. **라이트/다크는 값이 한 곳에만 존재합니다.** `light-dark(라이트, 다크)`로 한 줄에 두 값을 함께 쓰고, 전환은 `color-scheme`이 담당합니다. 다크 전용 오버라이드 블록이 없으므로 한쪽만 고쳐서 생기는 테마 불일치가 구조적으로 불가능합니다. ``로 수동 선택을 덮어쓸 수 있고, 속성이 없으면 OS 설정을 따릅니다. > **브라우저 지원** — `light-dark()` 자체는 Chrome 123 / Safari 17.5 / Firefox 120 이상이 > 필요하지만, Next.js 16(Turbopack)의 Lightning CSS가 현재 설정에서 이를 > `var(--lightningcss-light, 라이트) var(--lightningcss-dark, 다크)` 조합으로 폴리필해 > 출력하므로 구형 브라우저에서도 동작합니다. browserslist 타깃을 좁히면 폴리필이 빠지고 > 원본 `light-dark()`가 그대로 나가므로, 타깃을 바꿀 때 이 전제를 다시 확인하세요. > Lightning CSS를 쓰지 않는 번들러로 옮길 때도 마찬가지입니다. ## 호스트 앱과의 계약 `@fox`가 소유하지 않고 앱에서 받는 값입니다. | CSS 변수 | 용도 | 미주입 시 | | --- | --- | --- | | `--app-font-sans` | 기본 서체 | `system-ui`로 폴백 | | `--app-font-mono` | 고정폭 서체 | `ui-monospace`로 폴백 | 폰트 파일은 앱이 소유합니다(Next.js에서는 `next/font`가 최적화·자체 호스팅을 담당). `@fox`는 변수를 참조만 하므로, 폴더를 복사해 간 프로젝트는 그 프로젝트의 폰트를 그대로 씁니다. ```tsx // app/layout.tsx const sans = localFont({ src: [...], variable: "--app-font-sans" }); ``` 테마 토글을 붙이려면 ``의 `data-theme` 속성을 `"light" | "dark"`로 설정하고, 전환 애니메이션이 필요하면 같은 tick에 `data-theme-transition` 속성을 부여한 뒤 200ms 후 제거합니다(`_root.scss`가 그 동안만 색상 트랜지션을 겁니다). ## 토큰 추가하기 1. `tokens/`의 해당 map에 항목을 추가합니다. 그것으로 끝입니다 — 커스텀 프로퍼티 생성과 함수 검증 목록이 자동으로 따라옵니다. 2. 값의 출처(Figma file/node 등)를 주석으로 남깁니다. 3. **새 토큰 그룹**(map 자체를 신설)이 필요하면 `tokens/_index.scss`에 `@forward`를 추가하고, `_functions.scss`에 접근 함수를, `_root.scss`에 `@each` 블록을 각각 추가합니다.