Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4218b7e1f5 | ||
|
|
228d8872fc | ||
|
|
28e00f2870 | ||
|
|
f61d181bb1 | ||
|
|
65fa928694 | ||
|
|
8a09386b11 | ||
|
|
2690a160e9 | ||
|
|
b6b7c656e3 | ||
|
|
6eae67628c |
No files matched your search
@@ -90,7 +90,7 @@ Collapsible의 trigger/content는 `asChild`로 자체 `button`/`div`를 써서 C
|
||||
| 연결선 | 3뎁스만, 1px, 세로선 x 68, 가로 12, 글자까지 8, 행 세로 중앙, 마지막 행 중앙에서 끝 | 행마다 background 2겹(세로 `1px × 100%`, 마지막 행 `1px × 50%`, 가로 `12 × 1` at `calc(50% - 0.5px)`). DOM·tab stop·aria 없음 | MATCHED / IMPLEMENTATION_CHANGE(명세의 "배경 레이어" 방식) |
|
||||
| 연결선 색 | 기본·hover `--hm-green-tint` 40%, 선택 `--hm-green-light-2` 40% | 같음. 선택 색은 선택된 행의 선에만 적용(나머지 행은 기본색) | MATCHED |
|
||||
| 접힘 | 하위 메뉴·연결선 함께 숨김 | Radix가 내용을 unmount → 선도 같이 사라짐 | MATCHED |
|
||||
| 중첩 그룹 | 배경·테두리·padding 중복, 폭 감소, 오른쪽 틈 금지 | 안쪽 그룹은 면·선·padding-bottom 없음, 폭 100%(바깥 그룹과 같은 252), 선택 항목 오른쪽 273(1px 겹침) | MATCHED |
|
||||
| 중첩 그룹 | 배경·테두리·padding 중복, 폭 감소, 오른쪽 틈 금지 | 안쪽 그룹은 면·선·padding-bottom 없음, 폭 100%(바깥 그룹과 같은 220), 선택 항목 오른쪽 241(1px 겹침). 2026-11-10 240px 열 실측(Third Level, 1440); 이전 252/273은 272px 열 기준 | MATCHED |
|
||||
| 안쪽 그룹 이름 위치 | 명세 수치 없음, 이미지상 2뎁스 글자와 같은 시작선 | trigger padding-left = 68 − 화살표 18 − gap 12 = 38 → 글자 x 68 | INTENTIONAL_CHANGE(이미지 기준) |
|
||||
|
||||
모바일(<1024): LNB ONLY는 Desktop 열을 숨기고 같은 메뉴를 독립 `LNBMobileHeader / LNBMobileMenu`로 표시한다(§3b). GNB ONLY 내부 Sidebar는 [GNB §15](gnb.md#15-gnb-only-sidebar-composition-2026-10-08)의 GNB 단일 Sheet를 사용하며 독립 LNB Mobile Header/Menu를 추가하지 않는다. DASHBOARD ONLY 운영 화면에는 Sidebar가 없다([Dashboard 현재 계약](dashboard-layout.md#1-현재-계약)).
|
||||
|
||||
@@ -557,7 +557,8 @@ Composed Controls의 기존 레이어·색상·상세보기 예제는 보존하
|
||||
| `--hm-sw-header-focus-color` | `#F0F7F5` | Focus |
|
||||
| `--hm-sw-header-arrow-color` | `#47AF91` | 파일명 화살표 |
|
||||
| `--hm-sw-header-hover` | `rgba(255,255,255,.08)` | 더보기 메뉴 항목 Hover 배경(`--hm-sw-header-more-item-hover-background` 기본값) |
|
||||
| `--hm-sw-header-more-menu-background` / `-more-menu-shadow` / `-more-menu-border-color` / `-more-item-hover-background` | `#0E3D2F` / `0 4px 12px rgba(0,0,0,.5)` / `var(--hm-sw-header-object-border-color)` / `var(--hm-sw-header-hover)` | 더보기(`...`) 메뉴 면·그림자·테두리·항목 Hover(2026-11-10 개별 토큰, 값 변경 없음). 뷰어 Header Dropdown·로고 메뉴도 사용 |
|
||||
| `--hm-sw-header-more-menu-background` / `-more-menu-shadow` / `-more-menu-border-color` / `-more-item-hover-background` | `#0E3D2F` / `0 4px 12px rgba(0,0,0,.5)` / `var(--hm-sw-header-object-border-color)` / `var(--hm-sw-header-hover)` | 더보기(`...`) 메뉴 면·그림자·테두리·항목 Hover(2026-11-10 개별 토큰, 값 변경 없음). 뷰어 Header Dropdown·로고 메뉴는 이 값을 기본값으로 하는 `--hm-sw-viewer-header-menu-*`를 읽음 |
|
||||
| `--hm-sw-header-k-guide-border-color` / `-k-guide-background` | `var(--hm-sw-property-border)`(`#A67D03`) / `#0E3C2E` | K 가이드 패널 테두리·면(2026-11-10 개별 토큰, 값 변경 없음). 속성창 테두리와 독립 |
|
||||
| `--hm-sw-header-hover-border-gradient` / `-hover-border-width` | Figma 홈 hover gradient / 1px | Hover 테두리(§11.1) |
|
||||
| `--hm-sw-header-home-on-line-color` / `-home-on-line-width` | `#5F4B0F` / 2px | Home On(로고 메뉴 열림, Figma 662:67267): Header 전폭 상·하단 선. 로고는 Hover 테두리 유지 |
|
||||
| `--hm-sw-header-document-menu-width` / `-document-menu-background` / `-document-menu-border-color` / `-document-menu-border-width` | 310px / `#103127` / `#041611` / 1px | 문서 Dropdown(§5.1) |
|
||||
@@ -589,7 +590,7 @@ Composed Controls의 기존 레이어·색상·상세보기 예제는 보존하
|
||||
| `--hm-sw-header-menu-list-max-height` / `-color-palette-max-height` | 240px / 118px | 긴 목록·사용자 색상 내부 스크롤 시작(구현값) |
|
||||
| `--hm-sw-header-color-mode-color` / `-color-mode-background` / `-color-mode-on-background` | `#D4CBBD` / `rgba(98,83,61,.3)` / `#62533D` | ByLayer / ByBlock |
|
||||
| `--hm-sw-header-color-pick-background` / `-color-chip-border-color` / `-color-input-border-color` | `#C5B19E → #5F554C` / `#C9C9C9` / `rgba(197,177,158,.2)` | 색상 추출, 22px 칩, 색상 입력 Hover·구분선 |
|
||||
| `--hm-sw-header-input-color` / `-input-hover-border-color` / `-input-editing-background` / `-input-editing-border-color` | `#E60403` / `rgba(212,203,189,.25)` / `#0F0D0A` / `rgba(212,203,189,.3)` | `SWHeaderInput`(§5.5), caret 색 포함 |
|
||||
| `--hm-sw-header-input-color` / `-input-hover-border-color` / `-input-editing-background` / `-input-editing-border-color` | `#E60403` / `rgba(212,203,189,.25)` / `#0F0D0A` / `rgba(212,203,189,.3)` | `SWHeaderInput`(§5.5), caret 색 포함. Popup(속성창·`SWPopupRow`·뷰어 패널) 값 글자색은 이 값을 기본값으로 하는 `--hm-sw-popup-value-color`(2026-11-10)를 읽음 |
|
||||
|
||||
## 15. Figma 비교 결과와 의도적 차이
|
||||
|
||||
|
||||
@@ -90,9 +90,17 @@ Strana의 2그룹(01 Modeling: Structure · Loads / 02 Analysis Result: Results)
|
||||
|
||||
전체 목록과 Strana 원본 키는 §7 표의 왼쪽 두 열을 봅니다.
|
||||
|
||||
**Step Header 전용 alias (2026-11-10).** Step Header 규칙이 칠하는 SWHeader 토큰 18개는 같은 이름의 `--hm-sw-process-*`(가장 바깥 SW 범위, 기본값 `var(--hm-sw-header-…)`)를 거쳐 읽습니다: `background`, `height`, `text-color`, `selected-color`, `hover-border-gradient`, `hover-border-width`, `document-background`, `document-border-gradient`, `document-border-color`, `object-background`, `object-border-gradient`, `object-radius`, `property-background`, `property-radius`, `property-shadow`, `window-icon-color`, `window-icon-hover-color`, `window-button-hover-background`. `--hm-sw-process-base-border-gradient`는 `--hm-sw-process-document-border-gradient`를 거쳐 같은 기본값을 씁니다.
|
||||
|
||||
- 기본(상속): `--hm-sw-process-*`가 없으면 가장 바깥 범위의 SWHeader 토큰 값이 그대로 적용됩니다. 계산값은 변경 전과 같습니다.
|
||||
- Step Header만 변경: 가장 바깥 SW 범위에 `--hm-sw-process-*`를 지정합니다(SWHeader 토큰보다 우선, SWHeader·Viewer 영향 없음).
|
||||
- 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 18개에 전달되지 않습니다.
|
||||
- 공유 유지: 바 높이·필드·로고 높이, 글자 크기, 글자 외곽선, 재사용 부품(홈·로고, 파일명 버튼, 창 이동, 도구 pill)의 `sw.css` 규칙.
|
||||
- `--hm-sw-process-text-color`는 Step Header 자체 글자 규칙(글자 로고, 레이어 필드)에만 적용됩니다. 루트의 기본 글자색은 SW 공통 규칙(`sw.css` `.hm-sw-header, .hm-sw-theme`)이 `--hm-sw-header-text-color`로 정하므로, 이를 상속하는 영역과 재사용 부품은 Header 토큰을 따릅니다.
|
||||
|
||||
## 7. Strana 테마 → 토큰 Override 대응표
|
||||
|
||||
Hanmac 열이 기본값입니다. 다른 테마는 Header의 `style`(가장 바깥 SW 범위)에 같은 토큰을 지정합니다. 예: `style={{ "--hm-sw-header-selected-color": "#FFC833" }}`. Strana ARGB `#AARRGGBB`는 CSS `rgba()`로 바꿉니다. 아래 표는 Strana `Styles/Theme/*/Colors.xaml`(HEAD `edda3cc`)에서 생성했습니다.
|
||||
Hanmac 열이 기본값입니다. 다른 테마는 Header의 `style`(가장 바깥 SW 범위)에 같은 토큰을 지정합니다. 예: `style={{ "--hm-sw-header-selected-color": "#FFC833" }}`. Strana ARGB `#AARRGGBB`는 CSS `rgba()`로 바꿉니다. 아래 표는 Strana `Styles/Theme/*/Colors.xaml`(HEAD `edda3cc`)에서 생성했습니다. 표의 `--hm-sw-header-*` 중 §6의 18개는 Step Header만 바꿀 때 같은 이름의 `--hm-sw-process-*`(예: `--hm-sw-process-selected-color`)에 지정해도 됩니다(2026-11-10).
|
||||
|
||||
<!-- theme-table:start -->
|
||||
| 토큰 | Strana `Color.Header.*` | Hanmac(기본값) | Dark | Light | HighContrast |
|
||||
|
||||
@@ -107,7 +107,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
|
||||
- SW 화면은 `SWTheme`(또는 SW 컴포넌트 루트)와 SW 토큰의 글꼴을 그대로 쓴다. Web UI도 Noto Sans KR이지만 Web 토큰(`--hm-font-family-base`)이나 Web `theme.css`의 글꼴 설정을 SW 화면에 복사하지 않고, SW 컴포넌트에 `font-family`를 개별 지정하지 않는다. 프로그램(사용자)이 다른 글꼴을 정한 경우에만 §3.1 방법으로 `--hm-sw-header-font-family`를 바꾸고 결과 보고에 "글꼴 변경"으로 적는다.
|
||||
- Weight 범위(2026-10-20 사용자 확정): SWHeader 안의 필드 값은 400, Composed Controls(SWHeader 밖에 단독 배치한 `SWHeaderLayerMenu` 등)의 필드 값은 700. 파일명·Footer 보조기능/명령어 제목·Footer 분류 탭·Viewer Header 파일명/버튼 라벨/Model·Layout·최근파일 이름/경로/미리보기 제목, Footer Model/Layout 탭 라벨(모든 상태, 2026-10-22 사용자 확정, 이름 편집 입력은 400)은 700, Footer `전체보기`·명령·보조기능 라벨은 400. 임의로 일괄 변경하지 않는다. Figma 고정 폭 칸(예: 가중치·스케일 38px)에 그보다 긴 값(예: `ByLayer`)이 들어가면 말줄임 + `title`이 된다. Figma 레이어 이름의 `Dotum/bold/14`를 보고 700으로 바꾸거나 겹침·외곽선을 따로 만들지 않는다.
|
||||
- CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다.
|
||||
- 스크롤바: SW 전용 스크롤바 디자인은 없다. 기존 SW 컴포넌트는 브라우저 기본 스크롤바를 쓰며(속성Bar만 `scrollbar-width: thin`), Popup 본문 스크롤은 `SWPopup`이 처리한다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다.
|
||||
- 스크롤바: 일반 스크롤바 디자인은 없다. 패키지가 정한 곳만 `scrollbar-width: thin`이다 — 속성Bar, Popup 본문(`SWPopup`·`SWViewerPanel`·속성창, `--hm-sw-popup-scrollbar-thumb`·`-track`), Footer 명령창 기록(`--hm-sw-footer-command-scrollbar-thumb`·`-track`, 2026-11-10). 그 밖의 SW 컴포넌트는 브라우저 기본 스크롤바를 쓴다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다.
|
||||
- TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다.
|
||||
|
||||
## 3. 스타일 규칙
|
||||
@@ -121,7 +121,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
|
||||
|
||||
### 3.1 테마(토큰) 설정 — 공식 방법 (2026-10-20)
|
||||
|
||||
SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-viewer-*`, `--hm-sw-footer-*`)의 기본값은 **가장 바깥 SW 범위**(`<SWTheme>`, 또는 단독으로 쓴 `SWHeader`·`SWViewerHeader`·`SWViewerPanel`·`SWViewerMenu`·`SWFooter`의 루트)에서 한 번만 선언됩니다. 그 안의 SW 요소 — Header의 Dropdown·More, `SWViewerMenu`, `SWViewerPanel`과 안쪽 컨트롤, `SWFooterCommandLine`, Tooltip·안내 — 는 자기 루트에 `hm-sw-theme`이 있어도 값을 다시 정하지 않고 **상속**합니다.
|
||||
SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-viewer-*`, `--hm-sw-footer-*`, `--hm-sw-process-*`, `--hm-sw-switch-*`, `--hm-sw-slider-*`, `--hm-sw-select-*`)의 기본값은 **가장 바깥 SW 범위**(`<SWTheme>`, 또는 단독으로 쓴 `SWHeader`·`SWProcessHeader`·`SWViewerHeader`·`SWViewerPanel`·`SWViewerMenu`·`SWFooter`의 루트)에서 한 번만 선언됩니다. 예외: 홈 메뉴 토큰(`--hm-sw-home-menu-*`, `--hm-sw-recent-files-*`)은 `SWHomeMenu` 루트(`.hm-sw-home-menu`)에 선언되어 `<SWTheme style>`로는 바뀌지 않습니다. `SWHomeMenu`의 `style`(SWHeader에서는 `homeMenu.style`)로 정합니다([DESIGN_RULES.md](./DESIGN_RULES.md) §12). 그 안의 SW 요소 — Header의 Dropdown·More, `SWViewerMenu`, `SWViewerPanel`과 안쪽 컨트롤, `SWFooterCommandLine`, Tooltip·안내 — 는 자기 루트에 `hm-sw-theme`이 있어도 값을 다시 정하지 않고 **상속**합니다.
|
||||
|
||||
1. 화면 전체(Header·Panel·Menu가 같은 값)를 바꾸려면 화면을 `<SWTheme style={theme}>` 하나로 감싸고 거기서만 토큰을 정합니다. 프로그램 class(`<SWTheme className="app-sw-theme">` + `.app-sw-theme { --hm-sw-header-font-family: … }`)도 됩니다.
|
||||
```tsx
|
||||
@@ -136,6 +136,14 @@ SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-
|
||||
4. 영역마다 다른 값이 필요하면 영역별 `<SWTheme>`을 둡니다. 옆 영역끼리 섞이지 않고, 안쪽 `<SWTheme style>`은 그 안에서만 적용됩니다.
|
||||
5. **하지 않음:** `hm-sw-*` class를 선택자로 쓴 CSS(`.hm-sw-header, .hm-sw-theme { --… }` 등), `!important`, 안쪽 컴포넌트마다 같은 값을 반복 지정, SW 범위가 아닌 조상(`body`, 앱 루트 `div`)에만 토큰 지정(SW 범위가 기본값을 다시 선언하므로 적용되지 않음).
|
||||
- 다른 토큰을 참조하는 토큰(예: `--hm-sw-viewer-panel-background` = `var(--hm-sw-property-surface)`)은 가장 바깥 범위에서 계산됩니다. 참조되는 토큰을 바꿀 때도 가장 바깥 범위에서 바꿉니다.
|
||||
- Step Header(`SWProcessHeader`, 2026-11-10): 칠하는 SWHeader 토큰 18개를 `--hm-sw-process-*` alias(기본값 `var(--hm-sw-header-…)`)로 읽습니다. 아무것도 정하지 않으면 SWHeader 테마를 따르고, `--hm-sw-process-selected-color` 등을 가장 바깥 범위에 정하면 Step Header만 바뀝니다. 위 규칙대로 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 alias에 전달되지 않습니다. 목록·공유 유지 토큰은 [DESIGN_RULES.md](./DESIGN_RULES.md) §14.
|
||||
- Viewer Header(`SWViewerHeader`, 2026-11-10): 파일명·객체 수정·속성 면의 SWHeader 토큰 10개를 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*` alias(기본값 `var(--hm-sw-header-…)`)로 읽습니다. 기본은 SWHeader 테마 공유 그대로이고, 가장 바깥 범위에 `--hm-sw-viewer-*`를 정하면 Viewer Header만 바뀝니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 alias에 전달되지 않습니다. 목록·적용 범위는 [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
|
||||
- 컴포넌트 전용 색 토큰(2026-11-10, 값 변경 없음): 아래 9개는 같은 방식(가장 바깥 범위에서 정함, 기본값은 기존 토큰 또는 기존 값)이며, 정하지 않으면 지금 모양 그대로이고 정하면 해당 부분만 바뀝니다.
|
||||
- Viewer Header 메뉴(로고 메뉴·Header 안 Dropdown·More): `--hm-sw-viewer-header-menu-background`·`-border-color`·`-shadow`·`-item-hover-background`(기본 SWHeader `--hm-sw-header-more-*`). SWHeader `...` 메뉴는 그대로입니다. [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
|
||||
- SWHeader K 가이드 패널: `--hm-sw-header-k-guide-border-color`(기본 `var(--hm-sw-property-border)`)·`--hm-sw-header-k-guide-background`(`#0e3c2e`). 속성창 테두리는 그대로입니다. §5.
|
||||
- Popup 값 글자색: `--hm-sw-popup-value-color`(기본 `var(--hm-sw-header-input-color)`). 속성Bar 값은 그대로입니다. §8.
|
||||
- Footer 명령창 기록 스크롤바: `--hm-sw-footer-command-scrollbar-thumb`(`#574b30`)·`-track`(`transparent`). §13.
|
||||
- 공유 유지(전용 토큰 없음): Focus 링(`--hm-sw-header-focus-color`, Footer 포함), 글자색·글자 외곽선, Viewer 메뉴 구분선.
|
||||
|
||||
## 4. 판단 기록 남기기
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
- 목록 최대 높이 240px(8행), 넘치면 내부 스크롤. 색상 팔레트 118px.
|
||||
- 속성창·K 가이드는 `position: fixed` Overlay(뷰포트 안 보정, Portal·Dim 없음).
|
||||
- 한 번에 하나의 Dropdown(SWHeader). 단독 사용 시 각 컴포넌트가 바깥 클릭으로 스스로 닫힙니다.
|
||||
- More(`...`) 메뉴 색(2026-11-10 개별 토큰, 값 변경 없음): 면 `--hm-sw-header-more-menu-background`(`#0E3D2F`), 그림자 `--hm-sw-header-more-menu-shadow`(`0 4px 12px rgba(0,0,0,.5)`), 테두리 `--hm-sw-header-more-menu-border-color`(기본 `var(--hm-sw-header-object-border-color)`), 항목 Hover `--hm-sw-header-more-item-hover-background`(기본 `var(--hm-sw-header-hover)`). 뷰어 Header의 Dropdown·로고 메뉴도 같은 토큰을 읽습니다([VIEWER.md](./VIEWER.md)).
|
||||
- More(`...`) 메뉴 색(2026-11-10 개별 토큰, 값 변경 없음): 면 `--hm-sw-header-more-menu-background`(`#0E3D2F`), 그림자 `--hm-sw-header-more-menu-shadow`(`0 4px 12px rgba(0,0,0,.5)`), 테두리 `--hm-sw-header-more-menu-border-color`(기본 `var(--hm-sw-header-object-border-color)`), 항목 Hover `--hm-sw-header-more-item-hover-background`(기본 `var(--hm-sw-header-hover)`). 뷰어 Header의 Dropdown·로고 메뉴는 기본값이 이 4개인 `--hm-sw-viewer-header-menu-*` alias를 읽습니다(§11, [VIEWER.md](./VIEWER.md)). 정하지 않으면 같은 색이고, alias를 정하면 뷰어 메뉴만 바뀝니다.
|
||||
- K 가이드 패널 색(2026-11-10 개별 토큰, 값 변경 없음): 테두리 `--hm-sw-header-k-guide-border-color`(기본 `var(--hm-sw-property-border)`, `#A67D03`), 면 `--hm-sw-header-k-guide-background`(`#0E3C2E`). 정하면 K 가이드만 바뀌고 속성창 테두리(`--hm-sw-property-border`)는 그대로입니다. 문구 글자·외곽선은 `--hm-sw-header-text-color`·`--hm-sw-header-label-outline` 그대로입니다.
|
||||
|
||||
## 6. Motion
|
||||
|
||||
@@ -111,7 +112,8 @@
|
||||
| 구분선 | 개수 목록 아래선 검정 50%, 위치 표 선 검정 40% | `--hm-sw-popup-divider-color`, `--hm-sw-popup-table-line-color` |
|
||||
| Switch 그림자 | Off·On 안쪽 그림자 + 테두리 링 + 1px `#FCF0D8` 하이라이트, Thumb `0 .5px 1px rgba(0,0,0,.6)` | `--hm-sw-switch-off-shadow`, `--hm-sw-switch-on-shadow`, `--hm-sw-switch-thumb-shadow` |
|
||||
| 속성 행(`SWPopupPropertyRow`) | 속성창 행 그대로(라벨 72px 700, 값 상자 28px, `picker` 필드). 데이터 = `SWHeaderProperty` + `menus` | `--hm-sw-property-*`, `--hm-sw-property-field-*` |
|
||||
| 행(`SWPopupRow`) | 최소 32px, 라벨 72px 700 검정, 값 글자 `#E60403` 500, 단위 12px | `--hm-sw-property-label`, `--hm-sw-header-input-color` |
|
||||
| 행(`SWPopupRow`) | 최소 32px, 라벨 72px 700 검정, 값 글자 `#E60403` 500, 단위 12px | `--hm-sw-property-label`, `--hm-sw-popup-value-color` |
|
||||
| 값 글자색(2026-11-10 개별 토큰, 값 변경 없음) | 편집 가능한 값 `#E60403`: 속성창·`SWPopupRow`의 값 Input(값·게이지 값·색상 값 칸), 값 `select`, 속성창 안 `SWSelect`(사용 가능), Dropdown Trigger, `SWViewerPanel`의 `SWViewerInput`(검색 제외). 읽기 전용 값은 라벨색 그대로. 기본 `var(--hm-sw-header-input-color)`라 Header 테마를 따르고, 정하면 Popup 값만 바뀜(SWHeader 속성Bar 값은 `--hm-sw-header-input-color`) | `--hm-sw-popup-value-color` |
|
||||
| 값 상자 | 28px, Hover 1px `rgba(33,29,19,.4)`, 숫자 편집 On `#0F0D0A`(단위 흰색), Dropdown On `#211D13` | `--hm-sw-property-field-*` |
|
||||
| 동작 | Non-modal(Dim·Portal·Focus trap 없음). Escape는 Popup 안이면 그 Popup, 밖이면 최근 사용 Popup 하나. `autoFocus`면 열릴 때 Focus, 안에서 닫히면 원래 위치로 | — |
|
||||
|
||||
@@ -160,6 +162,8 @@
|
||||
|
||||
- **Model / Layout 탭 토큰:** 위 `--hm-sw-viewer-tabs-*`·`--hm-sw-viewer-tab-*`은 탭 컨테이너·버튼에만 연결되며 다른 Header 부품·SWHeader·Footer·패널에 쓰이지 않습니다. 탭 색만 바꾸려면 가장 바깥 SW 범위의 `style`로 덮습니다: 화면을 `<SWTheme style={...}>`로 감싸거나 단독 `SWViewerHeader`의 루트에 지정합니다(토큰이 그 범위에 선언되므로 일반 `div` 래퍼의 값은 가려짐). 예: `style={{ "--hm-sw-viewer-tab-active-border": "linear-gradient(#e07b00, #e07b00)", "--hm-sw-viewer-tab-inactive-border": "linear-gradient(#3d6bb3, #3d6bb3)" }}`. `var(...)` 기본값(`active-text`·`hover-*`·`focus-color`)은 SW 범위에서 계산되므로 공통 토큰(`--hm-sw-viewer-accent` 등)을 바꾸면 같은 범위에서 함께 따라가고, 탭 토큰을 직접 지정하면 공통 토큰과 독립합니다. 글자 외곽선(`text-shadow`)은 탭에서도 기존 그대로(이름 `--hm-sw-header-text-outline` 1.3px, 개수 `--hm-sw-header-label-outline` 1px)이며 탭 토큰이 아닙니다. 탭 class로 색을 덮지 않습니다.
|
||||
- **공유 토큰(모드 혼합 아님):** 뷰어 Header의 파일명·객체 수정·속성 면은 `--hm-sw-header-document-*`, `--hm-sw-header-object-*`, `--hm-sw-header-property-*`를, 글자 외곽선은 `--hm-sw-header-text-outline`·`--hm-sw-header-label-outline`을 그대로 읽습니다(§2·§3). 패널 제목 막대 추가 버튼(`ghost`)의 Hover·Focus 색은 `--hm-sw-header-selected-color`입니다.
|
||||
- **Viewer Header 전용 덮어쓰기(2026-11-10, 공유 유지):** 위 면 토큰 10개는 같은 이름의 `--hm-sw-viewer-*` alias(가장 바깥 SW 범위, 기본값 `var(--hm-sw-header-…)`)를 거쳐 읽습니다 — `--hm-sw-viewer-document-background`·`-document-border-gradient`·`-document-border-color`, `--hm-sw-viewer-object-background`·`-object-border-gradient`·`-object-radius`, `--hm-sw-viewer-property-background`·`-property-border-color`·`-property-radius`·`-property-shadow`. 적용 범위: 파일명 면, 객체 수정 면, 속성 입력 그룹(`data-surface="field"`), 오른쪽 상태·버튼 띠와 그 시작 조각. 기본 동작은 그대로 공유입니다(정하지 않으면 SWHeader 테마를 따름). Viewer Header만 바꾸려면 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 `--hm-sw-viewer-*`를 지정합니다(SWHeader·Step Header 영향 없음). 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소에서만 바꾼 `--hm-sw-header-*`는 이 10개에 전달되지 않습니다. 파일명 버튼 등 재사용 SWHeader 부품의 `sw.css` 규칙(로고가 있을 때 로고 칸 바탕 — SWHeader 홈과 같은 공용 규칙 `.hm-sw-header__home::before, .hm-sw-viewer-header__brand[data-logo]::before` — 포함), 파일 목록, 패널 `selected-color`, 글자 외곽선은 계속 `--hm-sw-header-*`를 읽습니다. Header 안 Dropdown·More·로고 메뉴는 아래 메뉴 alias, 패널 값 글자색은 `--hm-sw-popup-value-color`(§8)를 읽습니다.
|
||||
- **Viewer Header 메뉴 전용 덮어쓰기(2026-11-10, 공유 유지):** 로고 메뉴와 Header 안 Dropdown(메뉴 버튼·More·하위 메뉴)은 `--hm-sw-viewer-header-menu-background`·`-border-color`·`-shadow`·`-item-hover-background`(가장 바깥 SW 범위, 기본값 각각 `var(--hm-sw-header-more-menu-background)`·`var(--hm-sw-header-more-menu-border-color)`·`var(--hm-sw-header-more-menu-shadow)`·`var(--hm-sw-header-more-item-hover-background)`)를 읽습니다. Hover 토큰은 Focus·서브메뉴 열림에도 같습니다. 정하지 않으면 SWHeader `...` 메뉴와 같은 색이고, 정하면 Viewer Header 메뉴만 바뀝니다(SWHeader More 영향 없음). 글자 `--hm-sw-header-text-color`(보조 글자 80%)·구분선 `--hm-sw-header-document-menu-divider-color`·Disabled 0.5는 공유 그대로이고, 단독 `SWViewerMenu`는 위 `--hm-sw-viewer-menu-*`·`--hm-sw-viewer-more-*`를 씁니다. 제약은 면 alias와 같습니다(안쪽 요소에서만 바꾼 `--hm-sw-header-more-*`는 전달되지 않음).
|
||||
|
||||
| 상태 | Header 항목(button·toggle·menu·tabs·switch) | 패널 콘텐츠 |
|
||||
|---|---|---|
|
||||
@@ -193,6 +197,7 @@
|
||||
|
||||
- Home On(Figma 662:67267, 2026-10-19 사용자 확정): 메뉴가 열려 있는 동안 SWHeader가 자동으로 로고에 노란 Hover 테두리를 유지하고, Header 전폭 상·하단에 `#5F4B0F` 2px 선(`--hm-sw-header-home-on-line-color` / `-width`)을 그립니다. 닫으면 기본 상태로 돌아갑니다. 크기·위치 변화가 없고 SW 일반 모드 전용입니다(뷰어 모드에는 없음). 소비 코드에서 별도 class나 상태를 추가하지 않습니다.
|
||||
- 새 색·크기는 위 토큰만 `style`로 덮어씁니다. `hm-sw-home-menu*` class를 소비 코드에 쓰지 않습니다.
|
||||
- 덮어쓰는 위치(2026-11-10 구현 확인): 홈 메뉴 토큰 39개(`--hm-sw-home-menu-*` 28, `--hm-sw-recent-files-*` 11)는 가장 바깥 SW 범위가 아니라 `SWHomeMenu` 루트(`.hm-sw-home-menu`, `sw-home-menu.css`)에 선언됩니다. 루트가 값을 다시 정하므로 `<SWTheme style>`이나 `SWHeader`의 `style`로 지정한 `--hm-sw-home-menu-*`·`--hm-sw-recent-files-*`는 적용되지 않습니다. `SWHomeMenu`의 `style`, 또는 `SWHeader`에서는 `homeMenu.style`(그대로 `SWHomeMenu`에 전달)로 지정합니다. 단, 홈 메뉴 토큰 중 `--hm-sw-header-layer-*`·`--hm-sw-header-text-color`를 참조하는 값(더보기 Dropdown·최근파일 면·행 Hover 등)은 홈 메뉴 요소에서 계산되므로 바깥 SW 범위에서 그 Header 토큰을 바꾸면 따라 바뀝니다. 선언 위치는 현재 구조를 유지합니다.
|
||||
|
||||
## 13. Footer (`SWFooter*`, Figma 26:10097) — SW 일반 모드 전용
|
||||
|
||||
@@ -204,7 +209,7 @@
|
||||
| 배치(1920, Figma) | 보조기능 버튼 0~74 → 2 → 보조기능 격자 76~392(위 2) → 4 → 명령 그룹 396~(위 1) → 명령어 전체보기(명령 칸 바로 뒤, 1552~1626) → 나머지 bg_vacant | — |
|
||||
| 측면 버튼 | 74 × 71, 자산 면(그라데이션 + 그라데이션 테두리), 그림자 8px 60% 검정(왼쪽 버튼 → 오른쪽 5px, 오른쪽 버튼 → 왼쪽 5px). 제목 14px 1.3px 외곽선, 부제 14px 1px 외곽선, 위 18 · 줄 간격 2. Hover·Pressed·Expanded = 제목·부제 `#FF5C00` + 1px 주황 그라데이션 테두리(`--hm-sw-footer-gra-border-orange`), Disabled 기본 모양 | `--hm-sw-footer-side-*` |
|
||||
| 보조기능 | 104 × 33, gap 2, radius 3, 면 `linear-gradient(0deg, #262218 8%, #4A3C2B 34%, #594C31 55%, #312A1B 90%)`, 아이콘 중심 (19.5, 16.5), 라벨 13px 왼쪽 36 폭 57 가운데. 테두리 1px 그라데이션(위 → 아래): Default `#886D35 0%, #423625 8.85%, #F3DBA8 26.17%, #0E0B06 83.79%, #574B30 100%`(Figma btn_* 26:9248 …) | `--hm-sw-footer-aux-*`, `--hm-sw-footer-aux-border` |
|
||||
| 보조기능 상태 | Hover(선택 아님) = 테두리만 노랑 `--hm-sw-header-hover-border-gradient`(`#A67D03 → #F2CC0F → #FADD4F → #F2CC0F → #926E03`, SW Header Figma Hover 재사용 — 버튼 세트에는 Hover 없음, 사용자 확정 2026-10-20). Selected(`aria-pressed="true"`, Figma Variant2 = Variant3) = 테두리 `#DA6813 8.52%, #FFBB89 37.93%, #E5690D 71.85%, #AE3607 92.77%`(Figma `gra-border-orange` = `--hm-sw-footer-gra-border-orange`) + 아이콘 채움·글자 `#FF5C00`, Hover보다 우선. 아이콘 외곽선 검정 90% 1px는 모든 상태 동일 | `--hm-sw-footer-aux-hover-border`, `--hm-sw-footer-aux-pressed-border`, `--hm-sw-footer-aux-icon-color`(`#F1EDE5`) |
|
||||
| 보조기능 상태 | Hover(선택 아님) = 테두리만 노랑 `--hm-sw-header-hover-border-gradient`(`#A67D03 → #F2CC0F → #FADD4F → #F2CC0F → #926E03`, SW Header Figma Hover 재사용 — 버튼 세트에는 Hover 없음, 사용자 확정 2026-10-20). Selected(`aria-pressed="true"`, Figma Variant2 = Variant3) = 테두리 `#DA6813 8.52%, #FFBB89 37.93%, #E5690D 71.85%, #AE3607 92.77%`(Figma `gra-border-orange` = `--hm-sw-footer-gra-border-orange`) + 아이콘 채움·글자 `#FF5C00`, Hover보다 우선. 선택 면 = `--hm-sw-footer-aux-pressed-background`(`linear-gradient(0deg, #594931 0%, #3B3021 23%, #1B170F 58%, #0D0B07 100%)`, Figma 없음 — 실제 프로그램 화면 샘플링 근사값, 사용자 확정 2026-11-09). 아이콘 외곽선 검정 90% 1px는 모든 상태 동일 | `--hm-sw-footer-aux-hover-border`, `--hm-sw-footer-aux-pressed-border`, `--hm-sw-footer-aux-pressed-background`, `--hm-sw-footer-aux-icon-color`(`#F1EDE5`) |
|
||||
| 명령 그룹 면 | 테두리 위·왼쪽·아래 1px `#886D35`, 왼쪽 위 radius 4, 면 `linear-gradient(0deg, #2F281A 0%, #312A1B 5%, #332B1C 36%, #463C29 58%, #38301F 79%, #19160E 98%)`. 마지막 명령 뒤(빈 칸·나머지)는 bg_vacant `linear-gradient(0deg, #18150E 28%, #262015 41%, #332B1C 58%, #38301F 81%, #18150E 100%)`, 명령 칸 높이까지만(2026-10-20: 분류 행 아래로 내려가지 않아 선택 탭 Pocket 면이 어느 탭에서나 같음) | `--hm-sw-footer-group-*`, `--hm-sw-footer-vacant-background` |
|
||||
| 명령 칸 | 68 × 47, 라벨 14px `#F6F4F2` 위 6(좌우 2, 넘치면 `…` 말줄임 — 칸·글자 크기 그대로, 2026-10-20), 아이콘 18 × 18 위 26 가운데. 칸 사이 구분선 1 × 37(위 5) `linear-gradient(0deg, #20190E, #8A7A5B 45%, #F3DBA8 70%, #423625)` + `-1px 0 1px #000` | `--hm-sw-footer-slot-*`, `--hm-sw-footer-divider*` |
|
||||
| 명령 상태 | Hover = 선택(Figma Variant2 = Variant3): 글자 `#FF5C00` + 주황 아이콘. 색·아이콘만 바뀌고 아이콘·라벨 위치·크기·간격과 칸 크기는 그대로. Disabled 40%(Figma 없음) | `--hm-sw-footer-accent` |
|
||||
@@ -214,7 +219,7 @@
|
||||
| Layout 탭 | 높이 22, 12px 자간 -5%(Model 포함, 2026-10-22; 이전 2%), 라벨 700(모든 상태, 2026-10-22; 이름 편집 입력은 400). Default 면 `#C3B79E` 글자 `#161410`. On: `linear-gradient(0deg, #7C6C4C, #7C6C4C 85%, #A6946F)` 흰 글자. 이름 편집: `#0F0D0A` 빨강 `#E60403` 주황 테두리. Hover·On·편집 글자 아래 70% 검정 1.3px 외곽선. 둥근 탭: 1px `rgba(0,0,0,.05)`(Hover·편집 `#DA6813`), 위 radius 4, 안쪽 그림자 `inset 0 -3px 4px rgba(56,48,31,.25)`, ×(16) Hover 노랑. 사선 탭(마지막): 97 × 22 자산(9-slice, 2026-10-22: 97px·라벨 57px가 최소, 라벨이 길면 사선 21px·모서리 4px는 고정하고 가운데만 늘어남, 라벨 최대 12em), On hover = 베이지 면 + 주황 글자, ×(28, 빨강 사선). Hover(선택 아님, 두 모양 모두, 2026-10-20) = Model과 같은 `gra-border-orange` 면(사선 탭은 자산 모양 안)만 바뀌고 글자는 Default 그대로(`#161410` 12px 700, 외곽선 없음, 위치·크기 불변) | `--hm-sw-footer-tab-*`, `--hm-sw-footer-gra-border-orange` |
|
||||
| `+` 버튼 | Layout 옆 `+`(24 × 22) Hover = `gra-border-orange` 면 + `+`(분류 행 `+` 자산). 분류 행 `+`(28 × 12) Hover = `+` 채움 `#FF5C00`(검정 외곽선 그대로) | `--hm-sw-footer-gra-border-orange`, `--hm-sw-footer-accent` |
|
||||
| Model 탭 | 73 × 22 자산(왼쪽 사선, 9-slice: 73px 최소, 긴 라벨이면 사선 21px 고정·가운데만 늘어남, 라벨 최대 12em, 2026-10-22), 글자 왼쪽 22. Hover·On = 자산 모양 안 `gra-border-orange` 면 + 검정 `#161410` 글자(12px, 외곽선 없음)(2026-10-20 사용자 확정, 이전 Figma On 갈색·흰 글자 대체). 라벨은 모든 상태 700(2026-10-22). 앞 사선 탭과 8px 겹침 | `--hm-sw-footer-gra-border-orange` |
|
||||
| 명령창 | 566 × 71 / 270, 위 radius 4, 위 71px `linear-gradient(180deg, #635537, #262218 5%, #281F16 10%, #282216)` + 아래 `#282216`. 12px 자간 10% 줄 14, 1px 외곽선. 입력 칸 23(좌우 4) `#16130D` + `0 -1px 2px rgba(255,255,255,.25)`, `inset 0 1px 4px #000`. 화살표 18 × 18 검정 radius 2(4, 4 오른쪽 위) | `--hm-sw-footer-command-*` |
|
||||
| 명령창 | 566 × 71 / 270, 위 radius 4, 위 71px `linear-gradient(180deg, #635537, #262218 5%, #281F16 10%, #282216)` + 아래 `#282216`. 12px 자간 10% 줄 14, 1px 외곽선. 입력 칸 23(좌우 4) `#16130D` + `0 -1px 2px rgba(255,255,255,.25)`, `inset 0 1px 4px #000`. 화살표 18 × 18 검정 radius 2(4, 4 오른쪽 위). 기록 스크롤바 `thin`, Thumb `#574B30` / Track `transparent`(2026-11-10 개별 토큰, 값 변경 없음) | `--hm-sw-footer-command-*`(스크롤바 `--hm-sw-footer-command-scrollbar-thumb`·`-track`) |
|
||||
| 바 안 명령창(실제 화면) | 보조기능 뒤 4px, 바 아래에 붙어 펼치면 위로 270. 남는 폭(바 − 보조기능 영역 − 명령어 전체보기)을 칸 영역 0.57 : 명령창 나머지(최소 280)로 나눔 — 칸 수는 바 폭으로만 계산, 분류의 명령 수와 무관(1920 → 12칸) | `--hm-sw-footer-command-min-width`, `--hm-sw-footer-slot-share` |
|
||||
| 좌표(실제 화면) | 바 위 왼쪽, 12px `#F6F4F2` 1px 외곽선, 아래 3px | — |
|
||||
| 좁은 폭(실제 화면) | 빈 칸이 먼저 줄고, 넘치는 명령은 마지막 칸 ▲(ico_arrow 18 × 18, 오른쪽 위 4) → 명령 칸 위 패널(같은 칸, 오른쪽 맞춤, 테두리 1px `#886D35`·아래 없음·위 radius 4, 명령 칸 면). 분류 탭 넘침 = 탭 영역 잘림 + ◀ ▶(13 × 16, 6 × 8 삼각형 `#F6F4F2`, Hover `#FF5C00`) + +. 바 1279px 이하 보조기능 아이콘만 45 × 33 | `--hm-sw-footer-aux-compact-width` |
|
||||
@@ -226,6 +231,13 @@
|
||||
|
||||
원본 `src/theme/sw-process-header.css`(2026-11-03). Strana Main Header(Hanmac 테마)의 값입니다. 새 토큰은 `--hm-sw-process-*`(`.hm-sw-header`, `.hm-sw-theme` 범위)이고, 값이 같은 곳은 SW Header 토큰을 그대로 읽습니다(파일명 면·테두리, 객체 면·테두리, 속성 필드 면·그림자, Hover gradient, 선택 `#FFC600`, 제목·글자, 창 이동 줄무늬, 창 버튼 글리프 `#ADB0AF`·Hover 배경). Light/Dark preset은 없으며 다른 Strana 테마는 가장 바깥 범위 `style`에 같은 토큰을 지정합니다([AI_USAGE.md](./AI_USAGE.md) §3.1).
|
||||
|
||||
Step Header 전용 토큰(2026-11-10): Step Header 규칙이 칠하는 SWHeader 토큰 18개는 같은 이름의 `--hm-sw-process-*`를 거쳐 읽습니다 — `background`·`height`·`text-color`·`selected-color`·`hover-border-gradient`·`hover-border-width`·`document-background`·`document-border-gradient`·`document-border-color`·`object-background`·`object-border-gradient`·`object-radius`·`property-background`·`property-radius`·`property-shadow`·`window-icon-color`·`window-icon-hover-color`·`window-button-hover-background`(예: `--hm-sw-process-selected-color`). 기본값은 각각 `var(--hm-sw-header-…)`라 계산값은 그대로입니다. `--hm-sw-process-base-border-gradient`도 `--hm-sw-process-document-border-gradient`를 거쳐 같은 기본값을 씁니다.
|
||||
- 기본 동작(상속): `--hm-sw-process-*`를 정하지 않으면 가장 바깥 SW 범위의 `--hm-sw-header-*` 값이 Step Header에도 적용됩니다.
|
||||
- Step Header만 바꾸기: 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWProcessHeader`의 `style`)에 `--hm-sw-process-*`를 지정합니다. SWHeader·Viewer에는 영향이 없습니다.
|
||||
- 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소(예: `<SWTheme>` 안 `SWProcessHeader`의 `style`)에서 `--hm-sw-header-*`만 바꾸면 이 18개에는 전달되지 않습니다. 이 경우 같은 위치에 `--hm-sw-process-*`를 지정하거나 바깥 범위에서 바꿉니다.
|
||||
- 공유 유지: 바 높이(`--hm-sw-header-bar-height`)·필드·로고 높이, 글자 크기, 글자 외곽선(`text-outline`·`label-outline`·`text-outline-room`)과 재사용 부품(홈·로고, 파일명 버튼, 창 이동, 도구 pill)의 `sw.css` 규칙은 SWHeader 토큰을 그대로 읽습니다.
|
||||
- `--hm-sw-process-text-color` 범위: Step Header 자체 글자 규칙(글자 로고, 레이어 필드)에만 적용됩니다. Step Header 루트의 기본 글자색은 SW 공통 규칙(`sw.css`의 `.hm-sw-header, .hm-sw-theme { color: var(--hm-sw-header-text-color) }`)이 정하므로, 그 색을 상속하는 영역과 재사용 부품의 글자는 `--hm-sw-header-text-color`를 따릅니다.
|
||||
|
||||
| 항목 | 값 | 토큰 |
|
||||
|---|---|---|
|
||||
| 배치 | 홈·파일명 글자 = SWHeader 그대로(두 타입 공통, 2026-11-03): 홈 12 + 로고(32 × 17 → 80) + 36, 파일명은 홈과 15 겹친 뒤 2 간격(x 65), padding 5 / 12, 14px 700·자간 0.06em·SW 외곽선, 화살표 없음(글자 로고 18px 700, 최소 58px 칸 → 홈 최소 106, 더 긴 글자는 글자 폭만큼 넓어짐) → 프로세스 → Layer 면(객체 면, 양옆 1) → 창 영역(파일명 면 + 테두리 1px) | SW 토큰 |
|
||||
|
||||
@@ -45,9 +45,10 @@
|
||||
|
||||
- **반응형(2026-10-21, 실제 가용 폭 기준 3단계):** 고정 Breakpoint를 쓰지 않고 Header 행의 실제 남는 폭(로고·파일명·메뉴·Gap·Padding·More 버튼 포함)을 측정합니다. 파일명 면은 글자 수가 아니라 Header 폭으로 정한 기본 폭으로 계산하므로, 같은 폭에서는 짧은 파일명과 긴 파일명의 단계·파일 열기 버튼·파일명 면 너비가 같고 긴 이름은 `…`입니다(2026-10-21). ① 모두 들어가면 기본 형태(아이콘+텍스트), ② 부족하면 `icon`과 `iconOnlyBelow`가 있는 항목을 모두 아이콘만(라벨은 Tooltip·접근성 이름), ③ 그래도 부족하면 `collapseBelow` 항목을 하나씩 More(⋯, 베이지 260px Dropdown)로 옮깁니다(값이 큰 것부터, 같으면 오른쪽 먼저). ③의 판단(2026-10-26)은 tabs를 최소 폭(`--hm-sw-viewer-tabs-min-width`)으로 세므로, tabs가 `…`로 좁아져서 들어가면 More로 옮기지 않습니다. 파일명 면 기본 폭은 `clamp(80px, min(45cqw - 256px, 100cqw - 780px), 360px)`(1280px 이상은 이전과 같음), Header 560px 미만에서는 56px입니다. 넓어지면 More → 아이콘 → 기본 순서로 복귀하고, 상단과 More에 같은 항목이 동시에 나오지 않습니다. `collapseBelow`·`iconOnlyBelow`의 숫자는 px Breakpoint가 아니며 순서만 정합니다(`iconOnlyBelow`는 지정 여부만, `collapseBelow`는 값이 큰 것부터; Props 형식 동일). 로고 옆 파일 열기 버튼(`onDocumentOpen`)은 ② 단계에서 아이콘만으로 줄고 More로 옮기지 않으며, 샘플은 `documents`. 레이어·설정·속성은 `collapseBelow` 없이 아이콘까지만. 1180px 미만 간격 2px, 560px 미만 상태 글자 숨김(CSS container)은 그대로이고, 560px 미만에서는 tabs 개수 숨김·switch `shortLabel` 캡션·switch 좌우 여백 1px도 적용됩니다(2026-10-26). 가로 스크롤은 만들지 않습니다.
|
||||
- More는 그룹/항목 단위 구획으로 자동 구성됩니다: menu·tabs는 자기 구획(`moreLabel`), button·toggle·switch는 그룹 구획(`group.moreLabel ?? group.label`). toggle·switch는 체크 항목, tabs는 라디오 항목이 됩니다.
|
||||
- **Dropdown 색(2026-10-21):** Header 안의 Dropdown — 메뉴 버튼(`type: "menu"`), More(⋯), 로고 메뉴 — 은 크기·간격·글자는 `SWViewerMenu` 그대로, 색은 일반 `SWHeader`의 `...` 메뉴와 같은 More 토큰을 읽습니다(2026-11-10: 면 `--hm-sw-header-more-menu-background` #0e3d2f, Border `--hm-sw-header-more-menu-border-color` = `var(--hm-sw-header-object-border-color)`, 그림자 `--hm-sw-header-more-menu-shadow` 0 4px 12px rgba(0,0,0,.5), 글자 `--hm-sw-header-text-color`, 보조 글자는 같은 색 80%, Hover `--hm-sw-header-more-item-hover-background` = `var(--hm-sw-header-hover)`, 구분선 `--hm-sw-header-document-menu-divider-color`). 파일명 목록(`documents`)은 `SWHeader` 파일명 목록 모양을 유지하고, 단독 `SWViewerMenu`(우클릭 메뉴 등)는 기존 색 그대로입니다.
|
||||
- **Dropdown 색(2026-10-21):** Header 안의 Dropdown — 메뉴 버튼(`type: "menu"`), More(⋯), 로고 메뉴 — 은 크기·간격·글자는 `SWViewerMenu` 그대로, 색은 기본적으로 일반 `SWHeader`의 `...` 메뉴와 같습니다. 2026-11-10부터 면·Border·그림자·Hover는 Viewer 전용 alias를 거쳐 읽습니다 — 면 `--hm-sw-viewer-header-menu-background`(기본 `var(--hm-sw-header-more-menu-background)`, #0e3d2f), Border `--hm-sw-viewer-header-menu-border-color`(기본 `var(--hm-sw-header-more-menu-border-color)` = `var(--hm-sw-header-object-border-color)`), 그림자 `--hm-sw-viewer-header-menu-shadow`(기본 `var(--hm-sw-header-more-menu-shadow)`, 0 4px 12px rgba(0,0,0,.5)), Hover·Focus·서브메뉴 열림 `--hm-sw-viewer-header-menu-item-hover-background`(기본 `var(--hm-sw-header-more-item-hover-background)` = `var(--hm-sw-header-hover)`). 이 4개를 가장 바깥 SW 범위에 정하면 Viewer Header 메뉴만 바뀌고 SWHeader `...` 메뉴는 그대로입니다. 글자 `--hm-sw-header-text-color`, 보조 글자는 같은 색 80%, 구분선 `--hm-sw-header-document-menu-divider-color`는 공유 그대로입니다. 파일명 목록(`documents`)은 `SWHeader` 파일명 목록 모양을 유지하고, 단독 `SWViewerMenu`(우클릭 메뉴 등)는 기존 색 그대로입니다.
|
||||
- **상태 표현:** Hover·Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(면·선택 박스 없음, Hover 전후 크기·위치 동일 — 2026-10-21 Hover 면 #266651 제거), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선.
|
||||
- **글자·아이콘 외곽선(SWHeader와 동일):** 14px 글자 `--hm-sw-header-text-outline`(검정 1.3px), 11px 캡션·개수·상태 `--hm-sw-header-label-outline`(검정 1px), 아이콘 `SWViewerIcon`(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke를 **SVG stroke**로 재현: 아이콘 뒤 복사본의 모든 도형을 검정, 원래 선 두께 + 화면 기준 양쪽 1px(`--hm-sw-viewer-icon-outline-width`)로 그림. 위 아이콘은 그대로라 색·모양·선 두께 유지. CSS `filter` 미사용, glow는 생략). Header 항목·`SWViewerHeaderButton`의 `icon`은 자동 적용, `custom` / `trailing`의 직접 그린 아이콘은 `<SWViewerIcon>`으로 감쌉니다. 색·크기·굵기·간격은 그대로이고 Hover/Active는 채움 색만 바뀝니다. Dropdown·패널 안에는 외곽선이 없습니다(SWHeader More 메뉴와 같음). 프로그램 아이콘은 `currentColor`로 그리면 상태 색과 외곽선이 자동 적용됩니다.
|
||||
- **면 색(SWHeader와 공유, 2026-11-10 Viewer 전용 덮어쓰기 추가):** 파일명·객체 수정·속성 입력 면과 오른쪽 상태·버튼 띠는 기본으로 SWHeader 토큰(`--hm-sw-header-document-*`·`-object-*`·`-property-*`)을 따릅니다. Viewer Header만 바꾸려면 같은 이름의 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*`(10개)를 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 지정합니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 값에 전달되지 않습니다. 목록은 [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
|
||||
|
||||
### 2.1 두 가지 구성 (2026-10-21)
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
--hm-sw-footer-aux-height: 33px;
|
||||
--hm-sw-footer-aux-gap: 2px;
|
||||
--hm-sw-footer-aux-background: linear-gradient(0deg, #262218 8%, #4a3c2b 34%, #594c31 55%, #312a1b 90%);
|
||||
/* Selected face (not in Figma: sampled from the reference program screen, user decision 2026-11-09; stops approximate). */
|
||||
--hm-sw-footer-aux-pressed-background: linear-gradient(0deg, #594931 0%, #3b3021 23%, #1b170f 58%, #0d0b07 100%);
|
||||
--hm-sw-footer-aux-radius: 3px;
|
||||
--hm-sw-footer-aux-font-size: 13px;
|
||||
/* 1px borders (Plugin API, btn_객체스냅 26:9248 … btn_선가중치 26:9412): Default = brown gradient, Variant2 / Variant3
|
||||
@@ -92,6 +94,9 @@
|
||||
--hm-sw-footer-command-input-shadow: 0 -1px 2px rgba(255, 255, 255, 0.25), inset 0 1px 4px #000;
|
||||
--hm-sw-footer-command-font-size: 12px;
|
||||
--hm-sw-footer-command-line-height: 14px;
|
||||
/* History scrollbar (thin; 2026-11-10 individual tokens, values as before: the former scrollbar-color literals). */
|
||||
--hm-sw-footer-command-scrollbar-thumb: #574b30;
|
||||
--hm-sw-footer-command-scrollbar-track: transparent;
|
||||
/* Not in Figma (reference screens 2026-10-20): the command line inside the bar keeps at least this width. */
|
||||
--hm-sw-footer-command-min-width: 280px;
|
||||
/* Not in Figma: with the command line inside the bar, the slot area takes this share of the free width (bar minus
|
||||
@@ -242,7 +247,7 @@
|
||||
/* Selected (Figma Variant2 / Variant3): orange border, icon and text together; it stays while hovered and after
|
||||
the pointer leaves. Icons follow through currentColor (swFooterAuxIcons, or any currentColor icon). */
|
||||
.hm-sw-footer-aux__button[aria-pressed="true"] {
|
||||
background: var(--hm-sw-footer-aux-background) padding-box, var(--hm-sw-footer-aux-pressed-border) border-box;
|
||||
background: var(--hm-sw-footer-aux-pressed-background) padding-box, var(--hm-sw-footer-aux-pressed-border) border-box;
|
||||
}
|
||||
.hm-sw-footer-aux__button[aria-pressed="true"] :is(.hm-sw-footer-aux__icon, .hm-sw-footer-aux__label) { color: var(--hm-sw-footer-accent); }
|
||||
|
||||
@@ -611,7 +616,7 @@
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #574b30 transparent;
|
||||
scrollbar-color: var(--hm-sw-footer-command-scrollbar-thumb) var(--hm-sw-footer-command-scrollbar-track);
|
||||
text-shadow: var(--hm-sw-header-label-outline);
|
||||
}
|
||||
.hm-sw-footer-command__history > li { white-space: pre; }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *) {
|
||||
/* Process area base (Header.Border.ProcessBase): ProcessBase gradient, DocBorder 1px, top corners 6. */
|
||||
--hm-sw-process-base-background: linear-gradient(180deg, #0c5941 0%, #19bf8d 10.1%, #109069 18.27%, #0c5941 100%);
|
||||
--hm-sw-process-base-border-gradient: var(--hm-sw-header-document-border-gradient);
|
||||
--hm-sw-process-base-border-gradient: var(--hm-sw-process-document-border-gradient);
|
||||
/* Cover over an unlit group, its arrow side and its outer rest (ProcessDim #E6004430). */
|
||||
--hm-sw-process-dim: rgba(0, 68, 48, 0.902);
|
||||
|
||||
@@ -93,6 +93,30 @@
|
||||
--hm-sw-process-multi-tab-disabled-border: #021912;
|
||||
--hm-sw-process-multi-tab-disabled-shadow: 1px 1px 1px 0 rgba(31, 148, 113, 0.7), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42);
|
||||
--hm-sw-process-multi-tab-disabled-text: #7d9f91;
|
||||
|
||||
/* Step Header's own copies of the SWHeader tokens it paints with (2026-11-10). Each defaults to the SWHeader token, so a
|
||||
theme that sets --hm-sw-header-* on the outermost SW scope still reaches the Step Header; set --hm-sw-process-* there to
|
||||
change the Step Header alone. Resolved on the outermost scope like every alias here: a --hm-sw-header-* value set on an
|
||||
inner element no longer reaches these. The reused SW parts (home / logo, file-name button, window move) keep reading
|
||||
--hm-sw-header-* in sw.css. */
|
||||
--hm-sw-process-background: var(--hm-sw-header-background);
|
||||
--hm-sw-process-height: var(--hm-sw-header-height);
|
||||
--hm-sw-process-text-color: var(--hm-sw-header-text-color);
|
||||
--hm-sw-process-selected-color: var(--hm-sw-header-selected-color);
|
||||
--hm-sw-process-hover-border-gradient: var(--hm-sw-header-hover-border-gradient);
|
||||
--hm-sw-process-hover-border-width: var(--hm-sw-header-hover-border-width);
|
||||
--hm-sw-process-document-background: var(--hm-sw-header-document-background);
|
||||
--hm-sw-process-document-border-gradient: var(--hm-sw-header-document-border-gradient);
|
||||
--hm-sw-process-document-border-color: var(--hm-sw-header-document-border-color);
|
||||
--hm-sw-process-object-background: var(--hm-sw-header-object-background);
|
||||
--hm-sw-process-object-border-gradient: var(--hm-sw-header-object-border-gradient);
|
||||
--hm-sw-process-object-radius: var(--hm-sw-header-object-radius);
|
||||
--hm-sw-process-property-background: var(--hm-sw-header-property-background);
|
||||
--hm-sw-process-property-radius: var(--hm-sw-header-property-radius);
|
||||
--hm-sw-process-property-shadow: var(--hm-sw-header-property-shadow);
|
||||
--hm-sw-process-window-icon-color: var(--hm-sw-header-window-icon-color);
|
||||
--hm-sw-process-window-icon-hover-color: var(--hm-sw-header-window-icon-hover-color);
|
||||
--hm-sw-process-window-button-hover-background: var(--hm-sw-header-window-button-hover-background);
|
||||
}
|
||||
|
||||
/* ---------- Header frame ---------- */
|
||||
@@ -102,8 +126,8 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: var(--hm-sw-header-height);
|
||||
background: var(--hm-sw-header-background);
|
||||
height: var(--hm-sw-process-height);
|
||||
background: var(--hm-sw-process-background);
|
||||
overflow: clip;
|
||||
}
|
||||
.hm-sw-process-header__bar { display: flex; align-items: stretch; height: var(--hm-sw-header-bar-height); min-width: 0; }
|
||||
@@ -120,7 +144,7 @@
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
min-width: 58px;
|
||||
color: var(--hm-sw-header-text-color);
|
||||
color: var(--hm-sw-process-text-color);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: var(--hm-sw-header-logo-height);
|
||||
@@ -157,8 +181,8 @@
|
||||
position: absolute;
|
||||
inset: 0 1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-header-object-radius);
|
||||
background: var(--hm-sw-header-object-background) padding-box, var(--hm-sw-header-object-border-gradient) border-box;
|
||||
border-radius: var(--hm-sw-process-object-radius);
|
||||
background: var(--hm-sw-process-object-background) padding-box, var(--hm-sw-process-object-border-gradient) border-box;
|
||||
}
|
||||
/* Layer field (Header.ToggleButton.LayerField): 38px, at most 234, property surface + layer border gradient.
|
||||
The bulb and the field are sibling buttons; the field button covers the whole field under the bulb. */
|
||||
@@ -174,18 +198,18 @@
|
||||
margin: 0 9px;
|
||||
padding: 0 10px 0 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-header-property-radius);
|
||||
background: var(--hm-sw-header-property-background) padding-box, var(--hm-sw-process-layer-border-gradient) border-box;
|
||||
box-shadow: var(--hm-sw-header-property-shadow);
|
||||
color: var(--hm-sw-header-text-color);
|
||||
border-radius: var(--hm-sw-process-property-radius);
|
||||
background: var(--hm-sw-process-property-background) padding-box, var(--hm-sw-process-layer-border-gradient) border-box;
|
||||
box-shadow: var(--hm-sw-process-property-shadow);
|
||||
color: var(--hm-sw-process-text-color);
|
||||
}
|
||||
.hm-sw-process-header__layer-field::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -1px;
|
||||
padding: var(--hm-sw-header-hover-border-width);
|
||||
padding: var(--hm-sw-process-hover-border-width);
|
||||
border-radius: inherit;
|
||||
background: var(--hm-sw-header-hover-border-gradient);
|
||||
background: var(--hm-sw-process-hover-border-gradient);
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||
@@ -195,7 +219,7 @@
|
||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover::after,
|
||||
.hm-sw-process-header__layer-field[data-open]::after { opacity: 1; }
|
||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover,
|
||||
.hm-sw-process-header__layer-field[data-open] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-header__layer-field[data-open] { color: var(--hm-sw-process-selected-color); }
|
||||
.hm-sw-process-header__layer-field[data-disabled] { opacity: 0.4; }
|
||||
.hm-sw-process-header .hm-sw-process-header__layer-field button:disabled { opacity: 1; }
|
||||
.hm-sw-process-header__layer-button { position: absolute; inset: -1px; z-index: 0; border-radius: inherit; }
|
||||
@@ -221,7 +245,7 @@
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid transparent;
|
||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-header-document-border-gradient) border-box;
|
||||
background: var(--hm-sw-process-document-background) padding-box, var(--hm-sw-process-document-border-gradient) border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-header__window > * { position: relative; }
|
||||
@@ -238,11 +262,11 @@
|
||||
}
|
||||
.hm-sw-process-header:not([data-appearance="multi"]) .hm-sw-process-header__tools .hm-sw-tool__pill-button {
|
||||
border-color: transparent;
|
||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-process-tool-border-gradient) border-box;
|
||||
background: var(--hm-sw-process-document-background) padding-box, var(--hm-sw-process-tool-border-gradient) border-box;
|
||||
}
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button { color: var(--hm-sw-process-tool-icon-color); }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button:hover:not(:disabled),
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"] { color: var(--hm-sw-process-selected-color); }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]::after { opacity: 1; }
|
||||
/* Pressed (not disabled) shows the hover picture too, like a hover that stays (SW pill hover icon = `hoverIcon`). */
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]:not(:disabled) > .hm-sw-header__icon { visibility: hidden; }
|
||||
@@ -251,9 +275,9 @@
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__icon > :is(svg, img) { width: 24px; height: 24px; max-width: none; max-height: none; }
|
||||
|
||||
/* Caption buttons (Header.WindowCaption, 32 x 48): glyph #ADB0AF; hover dark + white; close hover red + white. */
|
||||
.hm-sw-process-header__caption { display: block; flex: 0 0 32px; width: 32px; height: var(--hm-sw-header-bar-height); color: var(--hm-sw-header-window-icon-color); }
|
||||
.hm-sw-process-header__caption { display: block; flex: 0 0 32px; width: 32px; height: var(--hm-sw-header-bar-height); color: var(--hm-sw-process-window-icon-color); }
|
||||
.hm-sw-process-header__caption > svg { display: block; }
|
||||
.hm-sw-process-header__caption:hover:not(:disabled) { background: var(--hm-sw-header-window-button-hover-background); color: var(--hm-sw-header-window-icon-hover-color); }
|
||||
.hm-sw-process-header__caption:hover:not(:disabled) { background: var(--hm-sw-process-window-button-hover-background); color: var(--hm-sw-process-window-icon-hover-color); }
|
||||
.hm-sw-process-header__caption[data-kind="close"]:hover:not(:disabled) { background: var(--hm-sw-process-close-hover-background); color: var(--hm-sw-process-close-icon-hover); }
|
||||
|
||||
/* ---------- Process area (SWProcessBar) ---------- */
|
||||
@@ -374,8 +398,8 @@
|
||||
.hm-sw-process-bar__step-box > [data-layer="inset-left"] { background: linear-gradient(90deg, var(--hm-sw-process-tab-inset) 0, transparent 6px); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="ring"] { border: 2px solid var(--hm-sw-process-tab-ring); }
|
||||
|
||||
.hm-sw-process-bar__step:hover:not(:disabled) { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-bar__step:hover:not(:disabled) { color: var(--hm-sw-process-selected-color); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] { color: var(--hm-sw-process-selected-color); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] > .hm-sw-process-bar__step-box { opacity: 1; }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="depth"] { background: var(--hm-sw-process-tab-active-base); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="shade"] { background: var(--hm-sw-process-tab-active-shade); }
|
||||
@@ -490,11 +514,11 @@
|
||||
/* File name: 189 at most (Figma 873:16254), solid #0E4C3A border. */
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__bar > .hm-sw-header__document { flex: 189 1 0; max-width: 189px; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__bar > .hm-sw-header__document .hm-sw-header__document-button {
|
||||
background: var(--hm-sw-header-document-background) padding-box, linear-gradient(var(--hm-sw-header-document-border-color) 0 0) border-box;
|
||||
background: var(--hm-sw-process-document-background) padding-box, linear-gradient(var(--hm-sw-process-document-border-color) 0 0) border-box;
|
||||
}
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__center { flex: 1278 1 0; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__window::before {
|
||||
background: var(--hm-sw-header-document-background) padding-box, linear-gradient(var(--hm-sw-header-document-border-color) 0 0) border-box;
|
||||
background: var(--hm-sw-process-document-background) padding-box, linear-gradient(var(--hm-sw-process-document-border-color) 0 0) border-box;
|
||||
}
|
||||
/* SW pill at the Figma place: 41 high, 4 from the top, 5 from the window start, 2 before the window move. */
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__window .hm-sw-process-header__tools { height: 41px; margin: 4px 2px 0 5px; }
|
||||
@@ -609,7 +633,7 @@
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step:disabled { color: var(--hm-sw-process-multi-tab-disabled-text); }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group .hm-sw-process-bar__step:disabled > .hm-sw-process-bar__step-box {
|
||||
border-color: var(--hm-sw-process-multi-tab-disabled-border);
|
||||
background: var(--hm-sw-header-property-background);
|
||||
background: var(--hm-sw-process-property-background);
|
||||
box-shadow: var(--hm-sw-process-multi-tab-disabled-shadow);
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -627,8 +651,8 @@
|
||||
border-color: transparent;
|
||||
border-bottom-width: 0;
|
||||
border-radius: 2px 2px 0 0;
|
||||
background: var(--hm-sw-process-tool-open-background) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
color: var(--hm-sw-header-selected-color);
|
||||
background: var(--hm-sw-process-tool-open-background) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||
color: var(--hm-sw-process-selected-color);
|
||||
}
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-process-split[data-open] > .hm-sw-tool__pill-button::after { opacity: 0; }
|
||||
|
||||
@@ -644,7 +668,7 @@
|
||||
height: var(--hm-sw-process-split-tail, 3px);
|
||||
border: solid transparent;
|
||||
border-width: 0 1px;
|
||||
background: linear-gradient(var(--hm-sw-process-tool-open-edge), var(--hm-sw-process-tool-open-edge)) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
background: linear-gradient(var(--hm-sw-process-tool-open-edge), var(--hm-sw-process-tool-open-edge)) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||
}
|
||||
.hm-sw-process-split__panel {
|
||||
position: absolute;
|
||||
@@ -658,7 +682,7 @@
|
||||
border: 1px solid transparent;
|
||||
border-top-width: 0;
|
||||
border-radius: 0 2px 2px 2px;
|
||||
background: linear-gradient(var(--hm-sw-process-split-panel-background), var(--hm-sw-process-split-panel-background)) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
background: linear-gradient(var(--hm-sw-process-split-panel-background), var(--hm-sw-process-split-panel-background)) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||
box-shadow: var(--hm-sw-process-split-panel-shadow);
|
||||
}
|
||||
/* Gold line on the panel top, from the button right edge to the panel end (Header.Border.ToolMenuStep). */
|
||||
@@ -669,7 +693,7 @@
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-top-right-radius: 2px;
|
||||
background: var(--hm-sw-header-hover-border-gradient) 0 0 / 100% 36px no-repeat;
|
||||
background: var(--hm-sw-process-hover-border-gradient) 0 0 / 100% 36px no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-split__cell { display: grid; place-items: center; height: 34px; }
|
||||
@@ -685,4 +709,4 @@
|
||||
}
|
||||
.hm-sw-process-split__frame > svg { display: block; overflow: visible; }
|
||||
.hm-sw-process-split__pane { fill: var(--hm-sw-process-split-cell-pane); stroke: rgba(0, 0, 0, 0.3); stroke-width: 1; }
|
||||
.hm-sw-process-split__cell:is(:hover, [aria-checked="true"]) .hm-sw-process-split__pane { fill: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-split__cell:is(:hover, [aria-checked="true"]) .hm-sw-process-split__pane { fill: var(--hm-sw-process-selected-color); }
|
||||
@@ -111,6 +111,33 @@
|
||||
/* Title-bar icon buttons of viewer panels (collapse, `ghost` header actions; 2026-10-23). Popup-only: the header
|
||||
status text and switch captions keep --hm-sw-viewer-muted. SWPopup's own title icons are images. */
|
||||
--hm-sw-viewer-panel-icon-color: #c7bda9;
|
||||
|
||||
/* Viewer Header faces (2026-11-10): the file name, object and property surfaces, the trailing status / button strip and
|
||||
its start piece read these copies of the SWHeader tokens. Each defaults to the SWHeader token, so the shared look
|
||||
stays (a --hm-sw-header-* theme on the outermost SW scope still reaches the viewer); set --hm-sw-viewer-* there to
|
||||
change the Viewer Header alone. Resolved on the outermost scope like every alias here: a --hm-sw-header-* value set
|
||||
on an inner element no longer reaches these. Reused SWHeader parts keep reading --hm-sw-header-* in sw.css. */
|
||||
--hm-sw-viewer-document-background: var(--hm-sw-header-document-background);
|
||||
--hm-sw-viewer-document-border-gradient: var(--hm-sw-header-document-border-gradient);
|
||||
--hm-sw-viewer-document-border-color: var(--hm-sw-header-document-border-color);
|
||||
--hm-sw-viewer-object-background: var(--hm-sw-header-object-background);
|
||||
--hm-sw-viewer-object-border-gradient: var(--hm-sw-header-object-border-gradient);
|
||||
--hm-sw-viewer-object-radius: var(--hm-sw-header-object-radius);
|
||||
--hm-sw-viewer-property-background: var(--hm-sw-header-property-background);
|
||||
--hm-sw-viewer-property-border-color: var(--hm-sw-header-property-border-color);
|
||||
--hm-sw-viewer-property-radius: var(--hm-sw-header-property-radius);
|
||||
--hm-sw-viewer-property-shadow: var(--hm-sw-header-property-shadow);
|
||||
|
||||
/* Viewer Header menus (2026-11-10): the logo menu and the header dropdowns (menu buttons, More, their submenus) read
|
||||
these copies of the SWHeader `...` More tokens. Each defaults to the More token, so the shared look stays; set
|
||||
--hm-sw-viewer-header-menu-* on the outermost SW scope to change the Viewer Header menus alone (SWHeader More
|
||||
unchanged). Resolved on the outermost scope like the face aliases above. Text color and the divider stay shared
|
||||
(--hm-sw-header-text-color, --hm-sw-header-document-menu-divider-color); a standalone SWViewerMenu keeps
|
||||
--hm-sw-viewer-menu-* / -more-*. */
|
||||
--hm-sw-viewer-header-menu-background: var(--hm-sw-header-more-menu-background);
|
||||
--hm-sw-viewer-header-menu-border-color: var(--hm-sw-header-more-menu-border-color);
|
||||
--hm-sw-viewer-header-menu-shadow: var(--hm-sw-header-more-menu-shadow);
|
||||
--hm-sw-viewer-header-menu-item-hover-background: var(--hm-sw-header-more-item-hover-background);
|
||||
}
|
||||
|
||||
.hm-sw-viewer-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||
@@ -179,7 +206,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
/* SWHeader file-name surface: the Figma gradient stroke through the transparent top / right / bottom 1px. */
|
||||
border: 1px solid transparent;
|
||||
border-left: 0;
|
||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-header-document-border-gradient) border-box;
|
||||
background: var(--hm-sw-viewer-document-background) padding-box, var(--hm-sw-viewer-document-border-gradient) border-box;
|
||||
}
|
||||
.hm-sw-viewer-header__document-name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
|
||||
@@ -209,7 +236,10 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
min-width: 160px;
|
||||
padding: 4px;
|
||||
border-width: 1px;
|
||||
border-color: var(--hm-sw-viewer-header-menu-border-color);
|
||||
border-radius: var(--hm-sw-viewer-radius);
|
||||
background: var(--hm-sw-viewer-header-menu-background);
|
||||
box-shadow: var(--hm-sw-viewer-header-menu-shadow);
|
||||
color: var(--hm-sw-header-text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
@@ -221,7 +251,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
padding: 0 12px;
|
||||
color: inherit;
|
||||
}
|
||||
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:is(:hover, :focus):not(:disabled) { background: var(--hm-sw-header-more-item-hover-background); outline: none; }
|
||||
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:is(:hover, :focus):not(:disabled) { background: var(--hm-sw-viewer-header-menu-item-hover-background); outline: none; }
|
||||
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:disabled { opacity: 0.5; }
|
||||
/* Title = SWViewerMenu label, directly above its items (one group, no divider); icons = SWViewerMenu icon slot. */
|
||||
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-title { min-height: 0; padding: 6px 12px; font-size: 12px; font-weight: 500; line-height: 18px; }
|
||||
@@ -302,13 +332,13 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
unchanged on hover. Size, spacing and type stay SWViewerMenu. A standalone SWViewerMenu (context menu) and the
|
||||
document list keep their own colors. */
|
||||
.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface] {
|
||||
border-color: var(--hm-sw-header-more-menu-border-color);
|
||||
background: var(--hm-sw-header-more-menu-background);
|
||||
box-shadow: var(--hm-sw-header-more-menu-shadow);
|
||||
border-color: var(--hm-sw-viewer-header-menu-border-color);
|
||||
background: var(--hm-sw-viewer-header-menu-background);
|
||||
box-shadow: var(--hm-sw-viewer-header-menu-shadow);
|
||||
color: var(--hm-sw-header-text-color);
|
||||
}
|
||||
.hm-sw-viewer-header .hm-sw-viewer-menu .hm-sw-viewer-menu__item:is(:focus, :hover, [data-state="open"]):not([aria-disabled]) {
|
||||
background: var(--hm-sw-header-more-item-hover-background);
|
||||
background: var(--hm-sw-viewer-header-menu-item-hover-background);
|
||||
color: inherit;
|
||||
}
|
||||
.hm-sw-viewer-header .hm-sw-viewer-menu :is(.hm-sw-viewer-menu__label, .hm-sw-viewer-menu__shortcut, .hm-sw-viewer-menu__chevron) { color: inherit; opacity: 0.8; }
|
||||
@@ -339,8 +369,8 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
padding-inline: var(--hm-sw-viewer-gap);
|
||||
/* SWHeader object surface: the Figma gradient stroke through the transparent 1px border. */
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-header-object-radius);
|
||||
background: var(--hm-sw-header-object-background) padding-box, var(--hm-sw-header-object-border-gradient) border-box;
|
||||
border-radius: var(--hm-sw-viewer-object-radius);
|
||||
background: var(--hm-sw-viewer-object-background) padding-box, var(--hm-sw-viewer-object-border-gradient) border-box;
|
||||
}
|
||||
|
||||
.hm-sw-viewer-header__group {
|
||||
@@ -351,10 +381,10 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
padding-inline: var(--hm-sw-viewer-gap);
|
||||
}
|
||||
.hm-sw-viewer-header__group[data-surface="field"] {
|
||||
border: 1px solid var(--hm-sw-header-property-border-color);
|
||||
border-radius: var(--hm-sw-header-property-radius);
|
||||
background: var(--hm-sw-header-property-background);
|
||||
box-shadow: var(--hm-sw-header-property-shadow);
|
||||
border: 1px solid var(--hm-sw-viewer-property-border-color);
|
||||
border-radius: var(--hm-sw-viewer-property-radius);
|
||||
background: var(--hm-sw-viewer-property-background);
|
||||
box-shadow: var(--hm-sw-viewer-property-shadow);
|
||||
}
|
||||
.hm-sw-viewer-header__actions {
|
||||
display: flex;
|
||||
@@ -589,10 +619,10 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
align-self: stretch;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
background: var(--hm-sw-header-document-background);
|
||||
background: var(--hm-sw-viewer-document-background);
|
||||
/* Only the 1px line before a segment: top, bottom and right are the object surface border itself, so every segment
|
||||
(and the license button between them) has the same top and bottom edge (2026-10-21; no doubled lines). */
|
||||
box-shadow: inset 1px 0 0 var(--hm-sw-header-document-border-color);
|
||||
box-shadow: inset 1px 0 0 var(--hm-sw-viewer-document-border-color);
|
||||
position: relative;
|
||||
}
|
||||
/* Strip outline (2026-10-23): the file-name gradient stroke (--hm-sw-header-document-border-gradient) over the object
|
||||
@@ -606,7 +636,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
inset: 0;
|
||||
border: 0 solid transparent;
|
||||
border-block-width: 1px;
|
||||
border-image: var(--hm-sw-header-document-border-gradient) 1 / 1 / 1 0;
|
||||
border-image: var(--hm-sw-viewer-document-border-gradient) 1 / 1 / 1 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__actions > :is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons):last-child::after {
|
||||
@@ -632,7 +662,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
/* 2026-10-23: the first segment's left side is the strip's outer gradient line, painted inside it (a 1px layer in place
|
||||
of the flat divider, so its width is unchanged). */
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__actions > :is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons):not(:is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons) + *) {
|
||||
background: var(--hm-sw-header-document-border-gradient) left top / 1px 100% no-repeat border-box, var(--hm-sw-header-document-background);
|
||||
background: var(--hm-sw-viewer-document-border-gradient) left top / 1px 100% no-repeat border-box, var(--hm-sw-viewer-document-background);
|
||||
box-shadow: none;
|
||||
}
|
||||
/* The empty strip-start mark (SWViewerHeader, right before `trailing`) carries the surface end, so the first segment
|
||||
@@ -646,21 +676,21 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--hm-sw-header-object-border-width, 1px));
|
||||
bottom: calc(-1 * var(--hm-sw-header-object-border-width, 1px));
|
||||
left: calc(-1 * (var(--hm-sw-header-object-radius) + 2px));
|
||||
left: calc(-1 * (var(--hm-sw-viewer-object-radius) + 2px));
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-viewer-header__strip-start:has(+ :is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons))::before {
|
||||
width: calc(var(--hm-sw-header-object-radius) + 2px);
|
||||
width: calc(var(--hm-sw-viewer-object-radius) + 2px);
|
||||
background: var(--hm-sw-viewer-background);
|
||||
}
|
||||
.hm-sw-viewer-header__strip-start:has(+ :is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons))::after {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-sw-header-object-radius);
|
||||
width: var(--hm-sw-viewer-object-radius);
|
||||
/* The object surface end: its face and gradient stroke, spanning the same 1px-outset height so both line up. */
|
||||
border: 1px solid transparent;
|
||||
border-left: 0;
|
||||
border-radius: 0 var(--hm-sw-header-object-radius) var(--hm-sw-header-object-radius) 0;
|
||||
background: var(--hm-sw-header-object-background) padding-box, var(--hm-sw-header-object-border-gradient) border-box;
|
||||
border-radius: 0 var(--hm-sw-viewer-object-radius) var(--hm-sw-viewer-object-radius) 0;
|
||||
background: var(--hm-sw-viewer-object-background) padding-box, var(--hm-sw-viewer-object-border-gradient) border-box;
|
||||
}
|
||||
/* No line around the license / info button: the window buttons right after it drop their line before it. */
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__actions > .hm-sw-viewer-header__button + .hm-sw-header__window-buttons { box-shadow: none; }
|
||||
@@ -864,8 +894,8 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
border-color: transparent;
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
color: var(--hm-sw-header-input-color);
|
||||
caret-color: var(--hm-sw-header-input-color);
|
||||
color: var(--hm-sw-popup-value-color);
|
||||
caret-color: var(--hm-sw-popup-value-color);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
|
||||
@@ -112,12 +112,21 @@
|
||||
--hm-sw-popup-group-shadow: 0 2px 2px rgba(255, 255, 255, .6), inset 0 2px 4px #e1cfab, inset 0 -2px 2px rgba(104, 95, 76, .8);
|
||||
--hm-sw-popup-divider-color: rgba(0, 0, 0, .5);
|
||||
--hm-sw-popup-table-line-color: rgba(0, 0, 0, .4);
|
||||
/* Popup value (2026-11-10): the red editable value of the property window / SWPopupRow (input, select, SWSelect,
|
||||
dropdown trigger) and of SWViewerPanel inputs. Defaults to the header input color it used, so a header theme still
|
||||
reaches it; set it to change popup values alone (the SWHeader 속성Bar keeps --hm-sw-header-input-color). */
|
||||
--hm-sw-popup-value-color: var(--hm-sw-header-input-color);
|
||||
/* SWHeader `...` More menu (2026-11-10 individual tokens, values as before). The viewer header dropdowns and the
|
||||
viewer logo menu take the same face and shadow. Border and row hover default to the header tokens they used. */
|
||||
viewer logo menu default to the same face and shadow (--hm-sw-viewer-header-menu-*, sw-viewer.css). Border and row
|
||||
hover default to the header tokens they used. */
|
||||
--hm-sw-header-more-menu-background: #0e3d2f;
|
||||
--hm-sw-header-more-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
--hm-sw-header-more-menu-border-color: var(--hm-sw-header-object-border-color);
|
||||
--hm-sw-header-more-item-hover-background: var(--hm-sw-header-hover);
|
||||
/* SWHeader K guide preview panel (2026-11-10 individual tokens, values as before): its border defaulted to the
|
||||
property window border, its face was a literal. Text and outline stay the header tokens. */
|
||||
--hm-sw-header-k-guide-border-color: var(--hm-sw-property-border);
|
||||
--hm-sw-header-k-guide-background: #0e3c2e;
|
||||
/* Popup scrollbar (every SW popup body: SWPopup, SWViewerPanel, property window). Same as the DWG/DXF viewer
|
||||
panels (thin, HM warm-neutral-mid thumb on warm-neutral-light track). */
|
||||
--hm-sw-popup-scrollbar-thumb: #9a9284;
|
||||
@@ -1696,16 +1705,19 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
.hm-sw-property__row > label { flex: 0 0 72px; min-width: 0; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.hm-sw-property__value { display: flex; align-items: center; gap: 4px; flex: 1; min-width: 0; }
|
||||
.hm-sw-property__value > .hm-sw-header__input { width: auto; max-width: 100%; min-width: 0; flex: 1; text-shadow: none; letter-spacing: .02em; font-weight: 500; }
|
||||
.hm-sw-property__value > .hm-sw-header__input:focus { color: var(--hm-sw-header-input-color); }
|
||||
.hm-sw-property__value > select:not(.hm-sw-select) { flex: 1; min-width: 0; width: 0; height: 28px; padding: 0; appearance: none; border: 1px solid transparent; border-radius: 2px; background: transparent; color: var(--hm-sw-header-input-color); font: inherit; }
|
||||
.hm-sw-property__value > select:not(.hm-sw-select):disabled { opacity: 1; appearance: none; color: var(--hm-sw-header-input-color); }
|
||||
/* Popup value color (2026-11-10, --hm-sw-popup-value-color): the inputs of the value, gauge value and color value cells
|
||||
(before: the shared .hm-sw-header__input color); read-only values keep the label color (rule below, higher specificity). */
|
||||
:is(.hm-sw-property__value, .hm-sw-property__slider-value, .hm-sw-property__color-value) > .hm-sw-header__input,
|
||||
.hm-sw-property__value > .hm-sw-header__input:focus { color: var(--hm-sw-popup-value-color); caret-color: var(--hm-sw-popup-value-color); }
|
||||
.hm-sw-property__value > select:not(.hm-sw-select) { flex: 1; min-width: 0; width: 0; height: 28px; padding: 0; appearance: none; border: 1px solid transparent; border-radius: 2px; background: transparent; color: var(--hm-sw-popup-value-color); font: inherit; }
|
||||
.hm-sw-property__value > select:not(.hm-sw-select):disabled { opacity: 1; appearance: none; color: var(--hm-sw-popup-value-color); }
|
||||
.hm-sw-property__value > select:not(.hm-sw-select):focus { background: var(--hm-sw-property-content); border-color: var(--hm-sw-property-inner-border); }
|
||||
/* SWSelect: native select, panel field look; focus = the SW popup focus ring. */
|
||||
.hm-sw-select { flex: 0 0 auto; height: 28px; padding: 0 8px; border: 1px solid var(--hm-sw-select-border-color); border-radius: 4px; background: var(--hm-sw-select-background); color: var(--hm-sw-select-text); font: inherit; font-size: 12px; }
|
||||
.hm-sw-select:disabled { opacity: 0.4; cursor: default; }
|
||||
/* 2026-10-21: in an SW popup the chosen value of an enabled select is editable, so it is red like every editable value;
|
||||
a disabled select and the option list keep --hm-sw-select-text. The viewer panels keep their own text color (sw-viewer.css). */
|
||||
.hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-header-input-color); }
|
||||
.hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-popup-value-color); }
|
||||
.hm-sw-property .hm-sw-select option { color: var(--hm-sw-select-text); }
|
||||
.hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; }
|
||||
/* Color preview (26:12175): 70 x 16 black tile (1px padding, radius 2), 68 x 14 swatch with a white 1px border. */
|
||||
@@ -1915,7 +1927,7 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
align-self: stretch;
|
||||
min-width: 0;
|
||||
padding: 0 7px;
|
||||
color: var(--hm-sw-header-input-color);
|
||||
color: var(--hm-sw-popup-value-color);
|
||||
text-align: left;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@@ -1948,7 +1960,7 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
|
||||
|
||||
/* K guide 26:11698 / 26:11669: original image, fixed mask viewport and continuous pan/zoom. */
|
||||
.hm-sw-k-guide { position: relative; flex: 0 0 40px; height: 38px; }
|
||||
.hm-sw-k-guide__panel { z-index: 45; height: 149px; min-height: 149px; overflow: hidden; border: 1px solid var(--hm-sw-property-border); border-radius: 2px; background: #0e3c2e; animation: hm-sw-k-guide-enter 511ms ease-out; }
|
||||
.hm-sw-k-guide__panel { z-index: 45; height: 149px; min-height: 149px; overflow: hidden; border: 1px solid var(--hm-sw-header-k-guide-border-color); border-radius: 2px; background: var(--hm-sw-header-k-guide-background); animation: hm-sw-k-guide-enter 511ms ease-out; }
|
||||
.hm-sw-k-guide__viewport { position: absolute; top: 6px; left: 6px; width: 192px; height: 109px; mask-size: 192px 109px; mask-repeat: no-repeat; outline: 1px solid #000; }
|
||||
.hm-sw-k-guide__viewport > img { display: block; max-width: none; width: 456px; height: 256.5px; transform-origin: 0 0; transform: translate(0, 0) scale(1); transition-property: transform; transition-timing-function: ease-in-out; }
|
||||
.hm-sw-k-guide__panel[data-frame="2"] .hm-sw-k-guide__viewport > img { transform: translate(0, -40.738px) scale(1); }
|
||||
|
||||
@@ -196,10 +196,201 @@ async function dropdownColors(page, def, themed) {
|
||||
await page.close()
|
||||
}
|
||||
|
||||
// ---------- 1b. Step Header (2026-11-10): --hm-sw-process-* copies of the SWHeader tokens it paints with ----------
|
||||
// Without a Step Header value an SWHeader theme on the outermost SW scope still reaches the Step Header; a
|
||||
// --hm-sw-process-* value there changes the Step Header alone (it wins over the SWHeader token) and leaves SWHeader as is.
|
||||
{
|
||||
const page = await newPage()
|
||||
await open(page, 'sw-step-header--process-header')
|
||||
const outerOf = selector => page.locator(selector).first().evaluateHandle(element => { let outer = element; for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e; return outer })
|
||||
const scope = await outerOf('.hm-sw-process-header')
|
||||
const set = async (values) => scope.evaluate((outer, values) => Object.entries(values).forEach(([token, value]) => value === null ? outer.style.removeProperty(token) : outer.style.setProperty(token, value)), values)
|
||||
const read = () => page.evaluate(() => {
|
||||
const header = getComputedStyle(document.querySelector('.hm-sw-process-header'))
|
||||
const step = document.querySelector('.hm-sw-process-bar__step[aria-checked="true"]')
|
||||
return { background: `${header.backgroundColor} ${header.backgroundImage}`, selected: step ? getComputedStyle(step).color : null }
|
||||
})
|
||||
const base = await read()
|
||||
await set({ '--hm-sw-header-background': 'rgb(44, 55, 66)', '--hm-sw-header-selected-color': 'rgb(11, 22, 33)' })
|
||||
const inherited = await read()
|
||||
check('Step Header: 바깥 범위의 SWHeader 토큰(배경·선택 색)이 그대로 전달', base.selected !== null && inherited.background.includes('rgb(44, 55, 66)') && inherited.selected === 'rgb(11, 22, 33)', JSON.stringify({ base, inherited }))
|
||||
await set({ '--hm-sw-process-background': 'rgb(1, 2, 3)', '--hm-sw-process-selected-color': 'rgb(77, 88, 99)' })
|
||||
const own = await read()
|
||||
check('Step Header: --hm-sw-process-* 지정이 SWHeader 토큰보다 우선', own.background.includes('rgb(1, 2, 3)') && own.selected === 'rgb(77, 88, 99)', JSON.stringify(own))
|
||||
await set({ '--hm-sw-header-background': null, '--hm-sw-header-selected-color': null, '--hm-sw-process-background': null, '--hm-sw-process-selected-color': null })
|
||||
check('Step Header: 지정을 지우면 기본값으로 복귀', JSON.stringify(await read()) === JSON.stringify(base), JSON.stringify(base))
|
||||
await page.close()
|
||||
// The same Step Header tokens on an SWHeader page change nothing there.
|
||||
const header = await newPage()
|
||||
await open(header, 'sw-header--overview')
|
||||
const paint = () => header.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-header *')].map(element => { const s = getComputedStyle(element); return `${s.color}|${s.backgroundColor}|${s.backgroundImage}|${s.boxShadow}|${s.borderTopColor}` }).join('\n'))
|
||||
const before = await paint()
|
||||
await header.locator('.hm-sw-header').first().evaluate(element => { let outer = element; for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e; ['background', 'height', 'text-color', 'selected-color', 'hover-border-gradient', 'document-background', 'object-background', 'property-background', 'window-icon-color'].forEach(name => outer.style.setProperty(`--hm-sw-process-${name}`, 'rgb(1, 2, 3)')) })
|
||||
check('Step Header 토큰은 SWHeader 화면에 영향 없음', before === await paint(), `${before.split('\n').length} elements`)
|
||||
await header.close()
|
||||
}
|
||||
|
||||
// ---------- 1c. Viewer Header (2026-11-10): --hm-sw-viewer-document-* / -object-* / -property-* face aliases ----------
|
||||
// Shared by default (an SWHeader theme on the outermost SW scope reaches the viewer faces); a --hm-sw-viewer-* value there
|
||||
// changes the Viewer Header faces alone and leaves SWHeader and the Step Header as they are.
|
||||
{
|
||||
const setOn = (page, root, values) => page.locator(root).first().evaluate((element, values) => {
|
||||
let outer = element
|
||||
for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e
|
||||
Object.entries(values).forEach(([token, value]) => value === null ? outer.style.removeProperty(token) : outer.style.setProperty(token, value))
|
||||
}, values)
|
||||
const page = await newPage()
|
||||
await open(page, 'sw-viewer--viewer-header')
|
||||
const read = () => page.evaluate(() => {
|
||||
// Every match (the Story has more than one header; some file-name areas carry no face of their own).
|
||||
const paint = selector => { const all = [...document.querySelectorAll(selector)]; if (!all.length) return null; return all.map(e => { const s = getComputedStyle(e); return `${s.backgroundColor} ${s.backgroundImage}` }).join(' || ') }
|
||||
// The document face token paints the trailing status / button strip segments in this Story.
|
||||
return { document: paint('.hm-sw-viewer-header__actions > :is(.hm-sw-viewer-status, .hm-sw-viewer-header__button, .hm-sw-header__window-buttons)'), property: paint('.hm-sw-viewer-header__group[data-surface="field"]') }
|
||||
})
|
||||
const A = 'linear-gradient(rgb(44, 55, 66), rgb(44, 55, 66))', B = 'linear-gradient(rgb(1, 2, 3), rgb(1, 2, 3))'
|
||||
const base = await read()
|
||||
await setOn(page, '.hm-sw-viewer-header', { '--hm-sw-header-document-background': A, '--hm-sw-header-property-background': A })
|
||||
const inherited = await read()
|
||||
check('Viewer Header: 바깥 범위의 SWHeader 토큰(파일명·속성 면)이 그대로 전달', base.document !== null && base.property !== null && inherited.document.includes('rgb(44, 55, 66)') && inherited.property.includes('rgb(44, 55, 66)'), JSON.stringify({ base, inherited }))
|
||||
await setOn(page, '.hm-sw-viewer-header', { '--hm-sw-viewer-document-background': B, '--hm-sw-viewer-property-background': B })
|
||||
const own = await read()
|
||||
check('Viewer Header: --hm-sw-viewer-* 지정이 SWHeader 토큰보다 우선', own.document.includes('rgb(1, 2, 3)') && own.property.includes('rgb(1, 2, 3)'), JSON.stringify(own))
|
||||
await setOn(page, '.hm-sw-viewer-header', { '--hm-sw-header-document-background': null, '--hm-sw-header-property-background': null, '--hm-sw-viewer-document-background': null, '--hm-sw-viewer-property-background': null })
|
||||
check('Viewer Header: 지정을 지우면 기본값으로 복귀', JSON.stringify(await read()) === JSON.stringify(base), JSON.stringify(base))
|
||||
await page.close()
|
||||
// The Viewer Header face tokens on SWHeader and Step Header pages change nothing there.
|
||||
const names = ['document-background', 'document-border-gradient', 'document-border-color', 'object-background', 'object-border-gradient', 'object-radius', 'property-background', 'property-border-color', 'property-radius', 'property-shadow']
|
||||
const values = Object.fromEntries(names.map(name => [`--hm-sw-viewer-${name}`, name.endsWith('radius') ? '9px' : name.endsWith('shadow') ? '0 0 0 3px rgb(1, 2, 3)' : name.endsWith('color') ? 'rgb(1, 2, 3)' : B]))
|
||||
const untouched = []
|
||||
for (const [id, root] of [['sw-header--overview', '.hm-sw-header'], ['sw-step-header--process-header', '.hm-sw-process-header']]) {
|
||||
const other = await newPage()
|
||||
await open(other, id)
|
||||
const paint = () => other.evaluate(root => [...document.querySelectorAll(`${root}, ${root} *`)].map(element => { const s = getComputedStyle(element); return `${s.color}|${s.backgroundColor}|${s.backgroundImage}|${s.boxShadow}|${s.borderTopColor}|${s.borderTopLeftRadius}` }).join('\n'), root)
|
||||
const before = await paint()
|
||||
await setOn(other, root, values)
|
||||
untouched.push(`${id} ${before === await paint() ? 'same' : 'CHANGED'} ${before.split('\n').length}`)
|
||||
await other.close()
|
||||
}
|
||||
check('Viewer Header 토큰은 SWHeader·Step Header 화면에 영향 없음', untouched.every(item => item.includes(' same ')), untouched.join(', '))
|
||||
}
|
||||
|
||||
// ---------- 1d. Component tokens (2026-11-10): viewer header menus, K guide, popup value, footer command scrollbar ----------
|
||||
// Each defaults to the token / literal it replaced (same value), follows a shared token set on the outermost SW scope,
|
||||
// and when set itself changes its component alone.
|
||||
{
|
||||
const setOn = (page, root, values) => page.locator(root).first().evaluate((element, values) => {
|
||||
let outer = element
|
||||
for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e
|
||||
Object.entries(values).forEach(([token, value]) => value === null ? outer.style.removeProperty(token) : outer.style.setProperty(token, value))
|
||||
}, values)
|
||||
const C1 = 'rgb(44, 55, 66)', C2 = 'rgb(1, 2, 3)'
|
||||
const flow = async (label, page, root, read, shared, own, expect) => {
|
||||
const base = await read()
|
||||
await setOn(page, root, Object.fromEntries(shared.map(token => [token, C1])))
|
||||
const inherited = await read()
|
||||
await setOn(page, root, Object.fromEntries(own.map(token => [token, C2])))
|
||||
const mine = await read()
|
||||
await setOn(page, root, Object.fromEntries([...shared, ...own].map(token => [token, null])))
|
||||
const back = await read()
|
||||
check(`${label}: 기본값 = 기존 값`, JSON.stringify(base) === JSON.stringify(expect), JSON.stringify(base))
|
||||
check(`${label}: 바깥 범위의 공유 토큰을 따름`, Object.values(inherited).every(value => value.includes(C1)), JSON.stringify(inherited))
|
||||
check(`${label}: 전용 토큰 지정이 우선, 지우면 복귀`, Object.values(mine).every(value => value.includes(C2)) && JSON.stringify(back) === JSON.stringify(base), JSON.stringify(mine))
|
||||
}
|
||||
// Viewer Header menus: a header dropdown (도면) and the logo menu (no Story renders `logoMenu`: the same class
|
||||
// structure is appended to the header, so the real rules apply).
|
||||
const viewer = await newPage()
|
||||
await open(viewer, 'sw-viewer--dropdowns')
|
||||
await viewer.locator('.hm-sw-viewer-header').first().evaluate(header => {
|
||||
const wrap = document.createElement('div'); wrap.className = 'hm-sw-viewer-header__logo-menu hm-sw-header__more'
|
||||
wrap.innerHTML = '<div class="hm-sw-header__more-menu" data-probe=""><button type="button" class="hm-sw-header__more-item" data-visible="true">probe</button></div>'
|
||||
header.append(wrap)
|
||||
})
|
||||
await viewer.getByRole('button', { name: '도면' }).first().click()
|
||||
const menu = viewer.locator('.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface]').first()
|
||||
await menu.locator('.hm-sw-viewer-menu__item:not([aria-disabled])').first().hover()
|
||||
const readMenu = () => viewer.evaluate(() => {
|
||||
const m = getComputedStyle(document.querySelector('.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface]'))
|
||||
const hovered = [...document.querySelectorAll('.hm-sw-viewer-header .hm-sw-viewer-menu .hm-sw-viewer-menu__item')].find(item => item.matches(':hover, :focus'))
|
||||
const logo = getComputedStyle(document.querySelector('[data-probe]'))
|
||||
return { face: `${m.backgroundColor} | ${m.borderTopColor} | ${m.boxShadow}`, hover: hovered ? getComputedStyle(hovered).backgroundColor : 'none', logo: `${logo.backgroundColor} | ${logo.borderTopColor} | ${logo.boxShadow}` }
|
||||
})
|
||||
const header = await newPage()
|
||||
await open(header, 'sw-header--overview')
|
||||
// Expected defaults: the SWHeader More menu face (same tokens) — computed on a probe More menu inside SWHeader.
|
||||
const moreFace = await header.locator('.hm-sw-header').first().evaluate(root => {
|
||||
const wrap = document.createElement('div'); wrap.className = 'hm-sw-header__more'
|
||||
wrap.innerHTML = '<div class="hm-sw-header__more-menu"></div>'; root.append(wrap)
|
||||
const s = getComputedStyle(wrap.firstChild); const value = `${s.backgroundColor} | ${s.borderTopColor} | ${s.boxShadow}`; wrap.remove(); return value
|
||||
})
|
||||
const hoverDefault = 'rgba(255, 255, 255, 0.08)'
|
||||
await flow('Viewer Header 메뉴', viewer, '.hm-sw-viewer-header', readMenu,
|
||||
['--hm-sw-header-more-menu-background', '--hm-sw-header-more-menu-border-color', '--hm-sw-header-more-menu-shadow', '--hm-sw-header-more-item-hover-background'],
|
||||
['--hm-sw-viewer-header-menu-background', '--hm-sw-viewer-header-menu-border-color', '--hm-sw-viewer-header-menu-shadow', '--hm-sw-viewer-header-menu-item-hover-background'],
|
||||
{ face: moreFace, hover: hoverDefault, logo: moreFace })
|
||||
// The shadow token is a full shadow: the C1 / C2 color strings above are not valid shadows, so the shadow keeps its
|
||||
// value there; check it once with a real shadow.
|
||||
await setOn(viewer, '.hm-sw-viewer-header', { '--hm-sw-viewer-header-menu-shadow': `0 0 0 3px ${C2}` })
|
||||
const shadow = await readMenu()
|
||||
await setOn(viewer, '.hm-sw-viewer-header', { '--hm-sw-viewer-header-menu-shadow': null })
|
||||
check('Viewer Header 메뉴: 그림자 전용 토큰', shadow.face.endsWith(`${C2} 0px 0px 0px 3px`) && shadow.logo.endsWith(`${C2} 0px 0px 0px 3px`), JSON.stringify(shadow))
|
||||
await viewer.close()
|
||||
// K guide panel (hover opens it).
|
||||
const guide = await newPage()
|
||||
await open(guide, 'sw-header--k-guide')
|
||||
await guide.locator('.hm-sw-k-guide button').first().hover()
|
||||
await guide.locator('.hm-sw-k-guide__panel').first().waitFor()
|
||||
const readGuide = () => guide.evaluate(() => { const s = getComputedStyle(document.querySelector('.hm-sw-k-guide__panel')); return { border: s.borderTopColor, face: s.backgroundColor } })
|
||||
// Shared token: the border follows --hm-sw-property-border; the face was a literal (own token only).
|
||||
const guideBase = await readGuide()
|
||||
await setOn(guide, '.hm-sw-header', { '--hm-sw-property-border': C1 })
|
||||
const guideShared = await readGuide()
|
||||
await setOn(guide, '.hm-sw-header', { '--hm-sw-header-k-guide-border-color': C2, '--hm-sw-header-k-guide-background': C2 })
|
||||
const guideOwn = await readGuide()
|
||||
await setOn(guide, '.hm-sw-header', { '--hm-sw-property-border': null, '--hm-sw-header-k-guide-border-color': null, '--hm-sw-header-k-guide-background': null })
|
||||
check('K 가이드: 기본값 = 기존 값(#A67D03 / #0E3C2E)', guideBase.border === 'rgb(166, 125, 3)' && guideBase.face === 'rgb(14, 60, 46)', JSON.stringify(guideBase))
|
||||
check('K 가이드: 바깥 범위의 --hm-sw-property-border를 따름', guideShared.border === C1 && guideShared.face === guideBase.face, JSON.stringify(guideShared))
|
||||
check('K 가이드: 전용 토큰 지정이 우선, 지우면 복귀', guideOwn.border === C2 && guideOwn.face === C2 && JSON.stringify(await readGuide()) === JSON.stringify(guideBase), JSON.stringify(guideOwn))
|
||||
await guide.close()
|
||||
// Popup value color: property window inputs (value, gauge value), dropdown triggers, and the SWHeader 속성Bar untouched.
|
||||
const popup = await newPage()
|
||||
await open(popup, 'sw-header--property-panel-compare')
|
||||
const readValue = () => popup.evaluate(() => {
|
||||
const color = selector => [...new Set([...document.querySelectorAll(selector)].map(e => getComputedStyle(e).color))].join(',')
|
||||
return {
|
||||
input: color('.hm-sw-property__value:not([data-readonly]) > .hm-sw-header__input:not(:read-only)'),
|
||||
gauge: color('.hm-sw-property__slider-value:not([data-readonly]) > .hm-sw-header__input:not(:read-only)'),
|
||||
trigger: color('.hm-sw-property__value:not([data-readonly]) .hm-sw-property__trigger'),
|
||||
}
|
||||
})
|
||||
await flow('Popup 값 글자색', popup, '.hm-sw-property', readValue, ['--hm-sw-header-input-color'], ['--hm-sw-popup-value-color'],
|
||||
{ input: 'rgb(230, 4, 3)', gauge: 'rgb(230, 4, 3)', trigger: 'rgb(230, 4, 3)' })
|
||||
await popup.close()
|
||||
const bar = await newPage()
|
||||
await open(bar, 'sw-header--overview')
|
||||
const barColor = () => bar.evaluate(() => [...document.querySelectorAll('.hm-sw-property-bar .hm-sw-header__input, .hm-sw-property-bar__value')].map(e => getComputedStyle(e).color).join(','))
|
||||
const barBefore = await barColor()
|
||||
await setOn(bar, '.hm-sw-header', { '--hm-sw-popup-value-color': C2 })
|
||||
check('Popup 값 토큰은 SWHeader 속성Bar 값에 영향 없음', barBefore.length > 0 && barBefore === await barColor(), barBefore)
|
||||
await bar.close()
|
||||
// Footer command history scrollbar.
|
||||
const footer = await newPage()
|
||||
await open(footer, 'sw-footer--full-footer')
|
||||
const readBar = () => footer.evaluate(() => ({ scrollbar: getComputedStyle(document.querySelector('.hm-sw-footer-command__history')).scrollbarColor }))
|
||||
const footerBase = await readBar()
|
||||
await setOn(footer, '.hm-sw-footer-command', { '--hm-sw-footer-command-scrollbar-thumb': C2, '--hm-sw-footer-command-scrollbar-track': C1 })
|
||||
const footerOwn = await readBar()
|
||||
await setOn(footer, '.hm-sw-footer-command', { '--hm-sw-footer-command-scrollbar-thumb': null, '--hm-sw-footer-command-scrollbar-track': null })
|
||||
check('Footer 명령창 스크롤바: 기본값 = 기존 값(#574B30 / transparent)', footerBase.scrollbar === 'rgb(87, 75, 48) rgba(0, 0, 0, 0)', JSON.stringify(footerBase))
|
||||
check('Footer 명령창 스크롤바: 전용 토큰 지정이 우선, 지우면 복귀', footerOwn.scrollbar === `${C2} ${C1}` && JSON.stringify(await readBar()) === JSON.stringify(footerBase), JSON.stringify(footerOwn))
|
||||
await footer.close()
|
||||
await header.close()
|
||||
}
|
||||
|
||||
// ---------- 2. Regression: new cascade vs legacy cascade on every existing SW Story ----------
|
||||
const here = path.dirname(fileURLToPath(import.meta.url))
|
||||
const outermost = ':is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *)'
|
||||
const tokenBlocks = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
||||
// 2026-11-10: sw-process-header.css (Step Header aliases) and the Step Header Stories joined the comparison.
|
||||
const tokenBlocks = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css', 'sw-process-header.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
||||
.map(text => {
|
||||
const start = text.indexOf(`${outermost} {`)
|
||||
const end = text.indexOf('\n}', start)
|
||||
@@ -212,7 +403,7 @@ const legacy = tokenBlocks.map(block => `.hm-sw-header,\n.hm-sw-theme ${block}`)
|
||||
// command row) even though no value changes, so the legacy shot is also compared with this control shot.
|
||||
const control = tokenBlocks.map(block => `${outermost} ${block}`).join('\n')
|
||||
const index = await (await fetch(`${storybook}/index.json`)).json()
|
||||
const ids = Object.values(index.entries).filter(entry => entry.type === 'story' && /^sw-(header|popup|viewer|footer|home-menu)/.test(entry.id)).map(entry => entry.id)
|
||||
const ids = Object.values(index.entries).filter(entry => entry.type === 'story' && /^sw-(header|popup|viewer|footer|home-menu|step-header)/.test(entry.id)).map(entry => entry.id)
|
||||
const snapshot = page => page.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-theme')].flatMap(root => [root, ...root.querySelectorAll('*')]).map(element => {
|
||||
const style = getComputedStyle(element), rect = element.getBoundingClientRect()
|
||||
return `${style.fontFamily}|${style.fontSize}|${style.color}|${style.backgroundImage.slice(0, 80)}|${Math.round(rect.x * 10)},${Math.round(rect.y * 10)},${Math.round(rect.width * 10)},${Math.round(rect.height * 10)}`
|
||||
|
||||
@@ -30,7 +30,7 @@ Brown·Orange 계열 팔레트(`--hm-sem-palette-brown-*`, `--hm-sem-palette-ora
|
||||
| 페이지·콘텐츠 배경 | `--hm-sem-bg-content` | Beige |
|
||||
| 카드·팝업 표면 | `--hm-sem-bg-surface` | White |
|
||||
| 보조 면 | `--hm-sem-bg-subtle` | warm-neutral-muted |
|
||||
| 브랜드 배경(gradient) | `--hm-sem-bg-brand`, `--hm-sem-bg-brown`, `--hm-sem-bg-orange` | 세로 gradient(GNB·Brand 버튼 면) |
|
||||
| 브랜드 배경(gradient) | `--hm-sem-bg-brand`, `--hm-sem-bg-brown`, `--hm-sem-bg-orange` | 공통 세로 gradient 배경(소비 화면 배경용). 패키지 컴포넌트(GNB·Button·Dashboard)에는 자동 연결되지 않음 — GNB 면은 `--hm-gnb-background`(저장소 `docs/guidelines/runtime-theme-contract.md` "Brown·Orange 공통 Background") |
|
||||
| 기본 글자 | `--hm-sem-fg-primary` | green-dark-1 |
|
||||
| 보조 글자 | `--hm-sem-fg-secondary` | warm-neutral-dark |
|
||||
| placeholder | `--hm-sem-fg-placeholder` | gray-500 |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* LNB pattern (layout visual reference: D:\컴포넌트\ui-viz Patterns/LNB, implementation: HM Collapsible + plain elements).
|
||||
A 272px green column: horizontal 5-stop surface, brand on top, menu in the middle, text-only profile at the bottom. The current
|
||||
A 240px green column (--hm-lnb-width; 272px before 2026-11-09): horizontal 5-stop surface, brand on top, menu in the middle, text-only profile at the bottom. The current
|
||||
item is a Beige face (left corners rounded) that runs to the content edge and joins it through two concave corners; hover is
|
||||
Black 10% to the same edge; an open group is a Black 8% face with a 1px green line. Spacing values are pattern-local. */
|
||||
:root, [data-hm-theme] {
|
||||
|
||||
@@ -50,7 +50,9 @@ export const AUX_ITEMS: SWFooterAuxItem[] = [
|
||||
* Labels, categories and the extra command are program data; WIPEOUT and 객체스냅추적 have no Figma icon, so the
|
||||
* Story passes its own (lucide) icons the way a program would.
|
||||
*/
|
||||
const lucide = (node: ReactNode) => <span className="sw-footer-story-lucide">{node}</span>
|
||||
const lucide = (node: ReactNode, outlined = false) => (
|
||||
<span className={outlined ? "sw-footer-story-lucide sw-footer-story-lucide--outlined" : "sw-footer-story-lucide"}>{node}</span>
|
||||
)
|
||||
export const REFERENCE_TOOLS: SWFooterTool[] = [
|
||||
{ id: "line", label: "LINE", ...swFooterToolIcons.line },
|
||||
{ id: "pline", label: "PLINE", ariaLabel: "Polyline", ...swFooterToolIcons.pline },
|
||||
@@ -74,7 +76,7 @@ export const REFERENCE_AUX: SWFooterAuxItem[] = [
|
||||
{ id: "lineWeight", label: "선가중치", icon: swFooterAuxIcons.lineWeight, pressed: false },
|
||||
{ id: "gridBackground", label: "격자배경", icon: swFooterAuxIcons.gridBackground, pressed: true },
|
||||
{ id: "gridSnap", label: "격자스냅", icon: swFooterAuxIcons.gridSnap, pressed: false },
|
||||
{ id: "objectTrack", label: "객체스냅추적", icon: lucide(<PenLine size={14} />), pressed: true },
|
||||
{ id: "objectTrack", label: "객체스냅추적", icon: lucide(<PenLine size={14} />, true), pressed: true },
|
||||
]
|
||||
export const REFERENCE_HISTORY = [
|
||||
"Raster image support loaded.",
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
.sw-footer-story-guide-frame { display: block; width: 194px; height: 111px; background-repeat: no-repeat; background-size: 250px 1179px; }
|
||||
.sw-footer-story-guide-text { display: grid; place-items: center; height: 100%; padding: 8px; font-size: 12px; color: #d4cbbd; text-align: center; }
|
||||
.sw-footer-story-lucide { display: grid; place-items: center; }
|
||||
/* Aux icons carry a 1px OUTSIDE black 90% outline (AuxIcon); a stroke icon has none, so the same outline is drawn around its alpha. */
|
||||
.sw-footer-story-lucide--outlined svg {
|
||||
filter: drop-shadow(1px 0 0 rgba(0, 0, 0, .9)) drop-shadow(-1px 0 0 rgba(0, 0, 0, .9))
|
||||
drop-shadow(0 1px 0 rgba(0, 0, 0, .9)) drop-shadow(0 -1px 0 rgba(0, 0, 0, .9));
|
||||
}
|
||||
.sw-footer-story-status { padding: 0 12px; font-size: 12px; color: #d4cbbd; }
|
||||
.sw-footer-story-row { display: flex; align-items: flex-start; gap: 16px; }
|
||||
.sw-footer-story-row--tight { gap: 0; }
|
||||
|
||||
Reference in new issue
Block a user