# Semantic Colors

oc-ui 색상 foundation은 `Global primitive → Theme semantic → Component contract`의 3단계로 사용합니다. Global primitive는 색상 램프와 opacity 숫자를 제공하고, Theme semantic은 light/dark 맥락에서 의미가 유지되는 역할 이름을 제공합니다. 컴포넌트에서는 primitive를 직접 고르기보다 semantic token을 우선하고, 컴포넌트별 상태·variant가 필요할 때 `--oc-{component}-*` contract로 한 번 더 연결합니다.

**역할 이름은 계약이고 값은 customizable 기본값입니다.** 아래 표는 패키지가 제공하는 기본 light/dark theme를 설명합니다. 제품 theme는 같은 역할 이름을 유지한 채 CSS variable 또는 SCSS theme config로 값을 바꿀 수 있습니다.

## 역할 가이드

### Static

`static-white`, `static-black`, `static-transparent`는 theme 전환과 무관하게 같은 물리 색을 유지해야 하는 경우에만 사용합니다. 로고 원색, 이미지 위의 고정 contrast icon처럼 의도가 명확한 표면이 대상입니다. 일반 본문이나 surface에 static 색을 쓰면 dark theme에서 역할이 뒤집히지 않으므로 피합니다.

### Primary

`primary-normal`은 브랜드의 주 행동, 선택 상태, 핵심 강조에 사용합니다. 페이지 전체의 장식색으로 넓게 칠하기보다 사용자가 다음 행동이나 현재 선택을 식별해야 하는 지점에 제한합니다.

### Focus ring

`focus-ring`은 `primary-normal`을 기본 alias로 삼지만 별도 리테마 지점입니다. 키보드 포커스 대비가 브랜드 primary로 충분하지 않은 제품은 이 token만 override할 수 있습니다. 컴포넌트와 커스텀 focusable 요소에서는 직접 outline을 조립하기보다 `@include oc-focus-ring`을 사용합니다.

### Label

label 단계는 콘텐츠 중요도와 상호작용 가능성을 나타냅니다. `strong`은 가장 강한 제목·핵심 값, `normal`은 기본 본문, `neutral`은 한 단계 낮은 본문, `alternative`는 부가 설명, `assistive`는 placeholder·hint, `disable`은 비활성 텍스트에 사용합니다. 단순히 더 연한 색이 필요하다는 이유로 단계를 건너뛰지 말고 정보 위계와 상태를 먼저 정합니다.

### Background

`background-normal-*`은 페이지 흐름의 기본 surface, `background-elevated-*`는 modal처럼 떠 있는 surface의 배경 짝입니다. 층위 구분의 기본 신호는 그림자와 dimmer이며 이는 light/dark 공통입니다 — elevated 색은 이를 대체하는 것이 아니라 **dark theme에서 추가되는 분리 신호**입니다. 어두운 배경에서는 그림자가 잘 드러나지 않으므로 떠 있는 표면을 base보다 밝은 단계로 올려 층위를 확실히 하고(iOS system background의 base/elevated 개념과 같은 접근), light 기본 theme에서는 base와 같은 값을 유지합니다(기본값 표 참조). 현행 컴포넌트의 대표 사용처는 Modal surface입니다. `normal`과 `alternative`는 인접 영역을 분리할 때 짝으로 사용합니다.

### Interaction

`interaction-inactive`는 아직 활성화되지 않은 control 면이나 indicator에, `interaction-disable`은 조작할 수 없는 control 면에 사용합니다. 비활성 control의 글자는 `label-disable`, 면은 `interaction-disable`로 구분합니다. 두 역할을 하나의 token으로 대체하면 theme별 대비가 쉽게 무너집니다.

### Line

`line-normal-*`은 alpha가 포함된 선으로 단일 surface 위의 구분선과 stroke에 사용합니다. 반투명 선을 중첩하면 예상보다 진해지므로 같은 위치에 여러 번 겹치지 않습니다. 중첩 가능성이 있거나 정확한 단색 경계가 필요한 layout에서는 대응하는 `line-solid-*`을 사용합니다. `strong → normal → neutral → alternative` 순서로 시각적 강도가 낮아집니다.

### Fill

`fill-normal`은 중립 control·선택 전 surface, `fill-strong`은 더 뚜렷한 중립 강조, `fill-alternative`는 가장 약한 보조 면에 사용합니다. 텍스트 역할을 fill로 대체하거나, primary 행동을 중립 fill만으로 표현하지 않습니다.

### Accent background

`accent-background-*`은 badge, status dot, chart mark처럼 색 자체가 면을 이루고 **텍스트 명도대비를 확보할 필요가 없는** 곳에 사용합니다. 같은 hue라도 명도대비가 필요한 텍스트·icon에는 `accent-foreground-*`을 사용합니다. 색상만으로 의미를 전달하지 말고 label, icon shape 또는 접근 가능한 이름을 함께 제공합니다.

### Accent foreground

`accent-foreground-*`은 **텍스트 명도대비를 준수하도록 조정된** 전경색입니다. 색상 텍스트·아이콘처럼 명도 확보가 필요한 곳에 사용하고, 명도대비가 필요 없는 단순 면에는 `accent-background-*`을 사용합니다. background token을 텍스트에 쓰면 대비 준수가 보장되지 않으므로 피합니다.

### Status

`status-positive`, `status-cautionary`, `status-negative`는 성공·주의·오류의 의미 역할이며 기본값은 대응 accent background의 alias입니다. 제품이 status palette를 별도로 운영할 때 alias 대상만 바꿀 수 있습니다. 단순 장식색에는 status token 대신 accent token을 사용합니다.

### Inverse

inverse trio는 tooltip처럼 기본 페이지와 명암이 반전된 surface를 하나의 묶음으로 구성합니다. `inverse-background` 위에 `inverse-label`을 놓고, 행동 강조가 필요하면 `inverse-primary`를 사용합니다. 일반 dark theme surface를 inverse로 대신 만들지 않습니다.

### Material

`material-dimmer`는 modal scrim처럼 아래 맥락을 낮추는 overlay material입니다. 콘텐츠 배경이나 disabled 면에 재사용하지 않습니다.

## 기본 사용 규칙

페이지의 기본 상속값은 `@include typo(body1)`, `--oc-color-theme-label-normal`, `--oc-color-theme-background-normal-normal`의 trio로 시작합니다. 하위 요소는 역할이 달라질 때만 delta를 선언합니다.

```scss
@use '@orioncactuscorp/ui/scss/mixins/typo' as *;

.page {
  @include typo(body1);

  background: var(--oc-color-theme-background-normal-normal);
  color: var(--oc-color-theme-label-normal);
}
```

## 기본값 표

<!-- prettier-ignore-start -->
<!-- generated:colors:start -->
> 아래 표는 기본 theme CSS를 기준으로 생성됩니다. 값은 고정 스펙이 아니라 기본값이며, consumer override 이후 실제 렌더 값은 달라질 수 있습니다.

### Static

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-static-white` | `white` | `white` | `var(--oc-color-global-common-100)` |
| `--oc-color-theme-static-black` | `black` | `black` | `var(--oc-color-global-common-0)` |
| `--oc-color-theme-static-transparent` | `transparent` | `transparent` | `var(--oc-color-global-common-transparent)` |

### Primary

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-primary-normal` | `#ff5e00` | `#ff7b2e` | `light: var(--oc-color-global-redOrange-50)`<br>`dark: var(--oc-color-global-redOrange-60)` |

### Focus ring

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-focus-ring` | `#ff5e00` | `#ff7b2e` | `var(--oc-color-theme-primary-normal)` |

### Label

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-label-strong` | `black` | `white` | `light: var(--oc-color-global-common-0)`<br>`dark: var(--oc-color-global-common-100)` |
| `--oc-color-theme-label-normal` | `#171719` | `#f7f7f8` | `light: var(--oc-color-global-coolNeutral-10)`<br>`dark: var(--oc-color-global-coolNeutral-99)` |
| `--oc-color-theme-label-neutral` | `rgb(46 47 51 / 88%)` | `rgb(194 196 200 / 88%)` | `light: oc-alpha(coolNeutral-22, 88)`<br>`dark: oc-alpha(coolNeutral-90, 88)` |
| `--oc-color-theme-label-alternative` | `rgb(55 56 60 / 61%)` | `rgb(174 176 182 / 61%)` | `light: oc-alpha(coolNeutral-25, 61)`<br>`dark: oc-alpha(coolNeutral-80, 61)` |
| `--oc-color-theme-label-assistive` | `rgb(55 56 60 / 28%)` | `rgb(174 176 182 / 28%)` | `light: oc-alpha(coolNeutral-25, 28)`<br>`dark: oc-alpha(coolNeutral-80, 28)` |
| `--oc-color-theme-label-disable` | `rgb(55 56 60 / 16%)` | `rgb(152 155 162 / 16%)` | `light: oc-alpha(coolNeutral-25, 16)`<br>`dark: oc-alpha(coolNeutral-70, 16)` |

### Background

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-background-normal-normal` | `white` | `#1b1c1e` | `light: var(--oc-color-global-common-100)`<br>`dark: var(--oc-color-global-coolNeutral-15)` |
| `--oc-color-theme-background-normal-alternative` | `#f7f7f8` | `#0f0f10` | `light: var(--oc-color-global-coolNeutral-99)`<br>`dark: var(--oc-color-global-coolNeutral-5)` |
| `--oc-color-theme-background-elevated-normal` | `white` | `#212225` | `light: var(--oc-color-global-common-100)`<br>`dark: var(--oc-color-global-coolNeutral-17)` |
| `--oc-color-theme-background-elevated-alternative` | `#f7f7f8` | `#141415` | `light: var(--oc-color-global-coolNeutral-99)`<br>`dark: var(--oc-color-global-coolNeutral-7)` |

### Interaction

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-interaction-inactive` | `#989ba2` | `#5a5c63` | `light: var(--oc-color-global-coolNeutral-70)`<br>`dark: var(--oc-color-global-coolNeutral-40)` |
| `--oc-color-theme-interaction-disable` | `#f4f4f5` | `#2e2f33` | `light: var(--oc-color-global-coolNeutral-98)`<br>`dark: var(--oc-color-global-coolNeutral-22)` |

### Line

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-line-normal-strong` | `rgb(112 115 124 / 52%)` | `rgb(194 196 200 / 52%)` | `light: oc-alpha(coolNeutral-50, 52)`<br>`dark: oc-alpha(coolNeutral-90, 52)` |
| `--oc-color-theme-line-normal-normal` | `rgb(112 115 124 / 22%)` | `rgb(112 115 124 / 32%)` | `light: oc-alpha(coolNeutral-50, 22)`<br>`dark: oc-alpha(coolNeutral-50, 0.32)` |
| `--oc-color-theme-line-normal-neutral` | `rgb(112 115 124 / 16%)` | `rgb(112 115 124 / 28%)` | `light: oc-alpha(coolNeutral-50, 16)`<br>`dark: oc-alpha(coolNeutral-50, 28)` |
| `--oc-color-theme-line-normal-alternative` | `rgb(112 115 124 / 8%)` | `rgb(112 115 124 / 22%)` | `light: oc-alpha(coolNeutral-50, 8)`<br>`dark: oc-alpha(coolNeutral-50, 22)` |
| `--oc-color-theme-line-solid-strong` | `#aeb0b6` | `#70737c` | `light: var(--oc-color-global-coolNeutral-80)`<br>`dark: var(--oc-color-global-coolNeutral-50)` |
| `--oc-color-theme-line-solid-normal` | `#e1e2e4` | `#37383c` | `light: var(--oc-color-global-coolNeutral-96)`<br>`dark: var(--oc-color-global-coolNeutral-25)` |
| `--oc-color-theme-line-solid-neutral` | `#eaebec` | `#333438` | `light: var(--oc-color-global-coolNeutral-97)`<br>`dark: var(--oc-color-global-coolNeutral-23)` |
| `--oc-color-theme-line-solid-alternative` | `#f4f4f5` | `#2e2f33` | `light: var(--oc-color-global-coolNeutral-98)`<br>`dark: var(--oc-color-global-coolNeutral-22)` |

### Fill

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-fill-normal` | `rgb(112 115 124 / 8%)` | `rgb(112 115 124 / 22%)` | `light: oc-alpha(coolNeutral-50, 8)`<br>`dark: oc-alpha(coolNeutral-50, 22)` |
| `--oc-color-theme-fill-strong` | `rgb(112 115 124 / 16%)` | `rgb(112 115 124 / 28%)` | `light: oc-alpha(coolNeutral-50, 16)`<br>`dark: oc-alpha(coolNeutral-50, 28)` |
| `--oc-color-theme-fill-alternative` | `rgb(112 115 124 / 5%)` | `rgb(112 115 124 / 12%)` | `light: oc-alpha(coolNeutral-50, 5)`<br>`dark: oc-alpha(coolNeutral-50, 12)` |

### Accent background

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-accent-background-red` | `#ff4242` | `#ff6363` | `light: var(--oc-color-global-red-50)`<br>`dark: var(--oc-color-global-red-60)` |
| `--oc-color-theme-accent-background-orange` | `#ff9200` | `#ffa938` | `light: var(--oc-color-global-orange-50)`<br>`dark: var(--oc-color-global-orange-60)` |
| `--oc-color-theme-accent-background-lime` | `#58cf04` | `#6be016` | `light: var(--oc-color-global-lime-50)`<br>`dark: var(--oc-color-global-lime-60)` |
| `--oc-color-theme-accent-background-green` | `#00bf40` | `#1ed45a` | `light: var(--oc-color-global-green-50)`<br>`dark: var(--oc-color-global-green-60)` |
| `--oc-color-theme-accent-background-cyan` | `#00bdde` | `#28d0ed` | `light: var(--oc-color-global-cyan-50)`<br>`dark: var(--oc-color-global-cyan-60)` |
| `--oc-color-theme-accent-background-lightBlue` | `#00aeff` | `#3dc2ff` | `light: var(--oc-color-global-lightBlue-50)`<br>`dark: var(--oc-color-global-lightBlue-60)` |
| `--oc-color-theme-accent-background-blue` | `#0066ff` | `#3385ff` | `light: var(--oc-color-global-blue-50)`<br>`dark: var(--oc-color-global-blue-60)` |
| `--oc-color-theme-accent-background-violet` | `#6541f2` | `#7d5ef7` | `light: var(--oc-color-global-violet-50)`<br>`dark: var(--oc-color-global-violet-60)` |
| `--oc-color-theme-accent-background-purple` | `#cb59ff` | `#d478ff` | `light: var(--oc-color-global-purple-50)`<br>`dark: var(--oc-color-global-purple-60)` |
| `--oc-color-theme-accent-background-pink` | `#f553da` | `#fa73e3` | `light: var(--oc-color-global-pink-50)`<br>`dark: var(--oc-color-global-pink-60)` |
| `--oc-color-theme-accent-background-redOrange` | `#ff5e00` | `#ff7b2e` | `light: var(--oc-color-global-redOrange-50)`<br>`dark: var(--oc-color-global-redOrange-60)` |

### Accent foreground

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-accent-foreground-red` | `#e52222` | `#ff6363` | `light: var(--oc-color-global-red-40)`<br>`dark: var(--oc-color-global-red-60)` |
| `--oc-color-theme-accent-foreground-redOrange` | `#f55a00` | `#ff7b2e` | `light: var(--oc-color-global-redOrange-48)`<br>`dark: var(--oc-color-global-redOrange-60)` |
| `--oc-color-theme-accent-foreground-orange` | `#d17600` | `#ff9200` | `light: var(--oc-color-global-orange-39)`<br>`dark: var(--oc-color-global-orange-50)` |
| `--oc-color-theme-accent-foreground-lime` | `#429e00` | `#58cf04` | `light: var(--oc-color-global-lime-37)`<br>`dark: var(--oc-color-global-lime-50)` |
| `--oc-color-theme-accent-foreground-green` | `#009632` | `#1ed45a` | `light: var(--oc-color-global-green-40)`<br>`dark: var(--oc-color-global-green-60)` |
| `--oc-color-theme-accent-foreground-cyan` | `#0098b2` | `#00bdde` | `light: var(--oc-color-global-cyan-40)`<br>`dark: var(--oc-color-global-cyan-50)` |
| `--oc-color-theme-accent-foreground-lightBlue` | `#008dcf` | `#00aeff` | `light: var(--oc-color-global-lightBlue-40)`<br>`dark: var(--oc-color-global-lightBlue-50)` |
| `--oc-color-theme-accent-foreground-blue` | `#005eeb` | `#4f95ff` | `light: var(--oc-color-global-blue-45)`<br>`dark: var(--oc-color-global-blue-65)` |
| `--oc-color-theme-accent-foreground-violet` | `#5b37ed` | `#9e86fc` | `light: var(--oc-color-global-violet-45)`<br>`dark: var(--oc-color-global-violet-70)` |
| `--oc-color-theme-accent-foreground-purple` | `#ad36e3` | `#d478ff` | `light: var(--oc-color-global-purple-40)`<br>`dark: var(--oc-color-global-purple-60)` |
| `--oc-color-theme-accent-foreground-pink` | `#e846cd` | `#fa73e3` | `light: var(--oc-color-global-pink-46)`<br>`dark: var(--oc-color-global-pink-60)` |

### Status

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-status-positive` | `#00bf40` | `#1ed45a` | `var(--oc-color-theme-accent-background-green)` |
| `--oc-color-theme-status-cautionary` | `#ff9200` | `#ffa938` | `var(--oc-color-theme-accent-background-orange)` |
| `--oc-color-theme-status-negative` | `#ff4242` | `#ff6363` | `var(--oc-color-theme-accent-background-red)` |

### Inverse

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-inverse-primary` | `#ff7b2e` | `#ff5e00` | `light: var(--oc-color-global-redOrange-60)`<br>`dark: var(--oc-color-global-redOrange-50)` |
| `--oc-color-theme-inverse-background` | `#1b1c1e` | `white` | `light: var(--oc-color-global-coolNeutral-15)`<br>`dark: var(--oc-color-global-common-100)` |
| `--oc-color-theme-inverse-label` | `#f7f7f8` | `#171719` | `light: var(--oc-color-global-coolNeutral-99)`<br>`dark: var(--oc-color-global-coolNeutral-10)` |

### Material

| Token | Light 기본값 | Dark 기본값 | Primitive 유도식 |
| --- | --- | --- | --- |
| `--oc-color-theme-material-dimmer` | `rgb(23 23 25 / 52%)` | `rgb(23 23 25 / 74%)` | `light: oc-alpha(coolNeutral-10, 52)`<br>`dark: oc-alpha(coolNeutral-10, 74)` |
<!-- generated:colors:end -->
<!-- prettier-ignore-end -->

## Customize

### Runtime CSS variable

theme selector와 같은 scope에서 semantic token을 override합니다. Global primitive를 바꾸는 대신 역할 token을 바꾸면 컴포넌트 의미가 유지됩니다.

```css
:root,
[data-theme='light'] {
  --oc-color-theme-primary-normal: #0066ff;
  --oc-color-theme-focus-ring: #0054d1;
}

[data-theme='dark'] {
  --oc-color-theme-primary-normal: #69a5ff;
  --oc-color-theme-focus-ring: #9ec5ff;
}
```

Storybook의 semantic swatch는 `var(--oc-color-theme-*)`를 직접 읽으므로 consumer foundation profile이나 runtime override를 적용한 환경에서는 현재 실값으로 렌더됩니다.

### SCSS theme config

필요한 theme만 출력하거나 OS theme preference 연동을 끄려면 foundation을 직접 compile하면서 `color.config`를 먼저 설정합니다.

```scss
@use '@orioncactuscorp/ui/scss/foundations/color.config' with (
  $oc-color-themes: (
    light,
    dark,
  ),
  $oc-color-system-preference: false
);
```

전체 import 순서와 theme scope 예시는 [README Theming](../README.md#theming), responsive compile 설정은 [Responsive Foundation Profile](./responsive-foundation-profile.md)을 참조하세요.
