// ───────────────────────────────────────────────────────────────────────────── // 토큰 CSS 출력 — `tokens/` 의 SCSS map을 `--fox-*` 커스텀 프로퍼티로 선언한다. // // **이 파일은 앱 전체에서 딱 한 번만 로드되어야 한다** (`index.scss` 경유). // 컴포넌트의 `.module.scss`에서는 절대 `@use` 하지 않는다 — CSS Module은 파일마다 // 별도 컴파일 단위여서, 컴포넌트마다 `:root` 블록 전체가 복제된다. // // map을 순회해 생성하므로 토큰을 추가할 때 이 파일은 손대지 않는다. 값의 이름·개수는 // 언제나 `tokens/` 쪽 map 하나에만 존재한다. // ───────────────────────────────────────────────────────────────────────────── @use "sass:map"; @use "tokens"; :root { // 기본은 OS 설정 추종. 아래 `[data-theme]` 규칙이 이 값만 덮어써 수동 선택을 // 처리하므로, 색상 값 자체는 `tokens/_color.scss` 한 곳에만 존재한다. color-scheme: light dark; @each $name, $value in tokens.$color { --fox-color-#{$name}: #{$value}; } @each $name, $value in tokens.$space { --fox-space-#{$name}: #{$value}; } @each $name, $value in tokens.$radius { --fox-radius-#{$name}: #{$value}; } @each $name, $value in tokens.$shadow { --fox-shadow-#{$name}: #{$value}; } @each $name, $value in tokens.$duration { --fox-duration-#{$name}: #{$value}; } @each $name, $value in tokens.$easing { --fox-easing-#{$name}: #{$value}; } @each $name, $value in tokens.$font-family { --fox-font-#{$name}: #{$value}; } @each $name, $value in tokens.$font-weight { --fox-weight-#{$name}: #{$value}; } @each $name, $value in tokens.$z { --fox-z-#{$name}: #{$value}; } // 브레이크포인트는 **미디어 쿼리에서 쓸 수 없다** — 조건부는 `var()`를 해석하지 // 않는다. 그럼에도 내보내는 이유는 컨테이너 `max-width`처럼 선언부에서 같은 값을 // 참조할 일이 있고, 카탈로그가 토큰 목록을 CSSOM에서 읽기 때문이다. // 미디어 쿼리는 언제나 `fox.media()` 믹스인을 쓴다. @each $name, $value in tokens.$breakpoint { --fox-breakpoint-#{$name}: #{$value}; } // 타이포 프리셋은 한 이름당 4개의 프로퍼티로 펼쳐진다 — `fox.typo()` 믹스인이 // 이 네 개를 함께 참조한다. @each $name, $preset in tokens.$typo { --fox-typo-#{$name}-size: #{map.get($preset, size)}; --fox-typo-#{$name}-weight: #{map.get($preset, weight)}; --fox-typo-#{$name}-line-height: #{map.get($preset, line-height)}; --fox-typo-#{$name}-letter-spacing: #{map.get($preset, letter-spacing)}; } } // 수동 테마 선택 — `color-scheme`만 고정하면 `light-dark()`가 해당 분기로 확정된다. // 색상 값을 여기서 재정의하지 않으므로 라이트/다크 값이 어긋날 수 없다. // 속성이 없거나 `"system"`이면 위 `:root`의 `light dark`가 유지되어 OS를 따른다. :root[data-theme="light"] { color-scheme: light; } :root[data-theme="dark"] { color-scheme: dark; } // 테마 전환 애니메이션 — 호스트 앱이 토글 직전 ``에 `[data-theme-transition]`을 // 부여하고 ~200ms 후 제거하는 **시간 한정 opt-in**이다. 상시 트랜지션이 아니므로 // 다른 hover 트랜지션과 경쟁하지 않는다. // `prefers-reduced-motion` 게이팅으로 감소 모션 환경에서는 즉시 반영된다. // `!important`는 이 200ms 동안만 존재하는 규칙이 컴포넌트의 transition 선언에 // 밀리지 않게 하기 위한 것이다. @media (prefers-reduced-motion: no-preference) { html[data-theme-transition], html[data-theme-transition] *, html[data-theme-transition] *::before, html[data-theme-transition] *::after { transition: background-color var(--fox-duration-base) var(--fox-easing-out), border-color var(--fox-duration-base) var(--fox-easing-out), color var(--fox-duration-base) var(--fox-easing-out), fill var(--fox-duration-base) var(--fox-easing-out), stroke var(--fox-duration-base) var(--fox-easing-out), outline-color var(--fox-duration-base) var(--fox-easing-out) !important; } }