@infomind-ux/infoux-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/server.js +192 -4
- package/data/icons.json +1291 -0
- package/data/manifest.json +43 -13
- package/data/profiles.json +1 -1
- package/data/references/accessibility.md +22 -1
- package/data/references/art-direction.md +1 -1
- package/data/references/html-semantics.md +56 -12
- package/data/references/krds-components.md +651 -18
- package/data/references/krds-tokens.md +12 -12
- package/data/references/project-profiles.md +1 -1
- package/data/references/release-checklist.md +2 -0
- package/data/references/tailwind-mapping.md +1 -1
- package/data/references/trade-off-rules.md +5 -0
- package/data/rules.json +62 -3
- package/data/snippets/error-page.md +107 -0
- package/data/snippets/footer.md +150 -0
- package/data/snippets/header.md +6 -3
- package/data/snippets/icon.md +102 -0
- package/data/snippets/main-menu.md +78 -11
- package/data/snippets/mobile-menu.md +99 -0
- package/data/snippets/notice-bar.md +85 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 자동 생성됨. 직접 수정 금지.
|
|
4
4
|
> 출처: `tokens/foundation.json` + `tokens/brand.json` (합성 결과)
|
|
5
|
-
> 빌드:
|
|
5
|
+
> 빌드: 113663a
|
|
6
6
|
|
|
7
7
|
색상, 기본 폰트, 브레이크포인트는 이 문서의 토큰을 사용한다. 임의 hex/rgb/hsl 색상 작성은 금지한다. 간격·크기·타이포 스케일·모션·z-index는 토큰 카탈로그 대상이 아니며 CSS/Tailwind 직접값으로 작성한다.
|
|
8
8
|
|
|
@@ -98,17 +98,17 @@
|
|
|
98
98
|
|
|
99
99
|
| 단계 | 토큰 | hex |
|
|
100
100
|
|------|------|-----|
|
|
101
|
-
| 5 | `--color-
|
|
102
|
-
| 10 | `--color-
|
|
103
|
-
| 20 | `--color-
|
|
104
|
-
| 30 | `--color-
|
|
105
|
-
| 40 | `--color-
|
|
106
|
-
| 50 | `--color-
|
|
107
|
-
| 60 | `--color-
|
|
108
|
-
| 70 | `--color-
|
|
109
|
-
| 80 | `--color-
|
|
110
|
-
| 90 | `--color-
|
|
111
|
-
| 95 | `--color-
|
|
101
|
+
| 5 | `--color-information-5` | `#e7f4fe` |
|
|
102
|
+
| 10 | `--color-information-10` | `#d3ebfd` |
|
|
103
|
+
| 20 | `--color-information-20` | `#9ed2fa` |
|
|
104
|
+
| 30 | `--color-information-30` | `#5fb5f7` |
|
|
105
|
+
| 40 | `--color-information-40` | `#2098f3` |
|
|
106
|
+
| 50 | `--color-information-50` | `#0b78cb` |
|
|
107
|
+
| 60 | `--color-information-60` | `#096ab3` |
|
|
108
|
+
| 70 | `--color-information-70` | `#085691` |
|
|
109
|
+
| 80 | `--color-information-80` | `#053961` |
|
|
110
|
+
| 90 | `--color-information-90` | `#03253f` |
|
|
111
|
+
| 95 | `--color-information-95` | `#021a2c` |
|
|
112
112
|
|
|
113
113
|
### Point (강조 — 빨강)
|
|
114
114
|
|
|
@@ -33,7 +33,7 @@ KRDS 체크리스트는 모든 프로젝트에 같은 방식으로 적용하지
|
|
|
33
33
|
| 밀도 | section 패딩 (PC / 모바일) | 폼 행 간격 | 표 셀 패딩 | 기준 |
|
|
34
34
|
|------|---------------------------|------------|------------|------|
|
|
35
35
|
| 여유 | 8rem / 4rem | 2.4rem | 1.6rem | 읽기 중심 화면. 한 화면에 담는 정보보다 가독성을 우선한다. |
|
|
36
|
-
| 고밀도 | 4rem / 2.4rem | 1.6rem | 1rem | 반복 작업 화면. 스크롤과 클릭 수를 줄이는 쪽을 우선한다. 터치
|
|
36
|
+
| 고밀도 | 4rem / 2.4rem | 1.6rem | 1rem | 반복 작업 화면. 스크롤과 클릭 수를 줄이는 쪽을 우선한다. 터치 입력 기기에서는 44px 하한을 그대로 지키고(R-13), 마우스 기반 데스크탑 화면에는 강제하지 않는다. |
|
|
37
37
|
|
|
38
38
|
## 표현 등급 기준
|
|
39
39
|
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
- [ ] 발주처 개발자가 6개월 뒤 이 코드를 고칠 수 있는 구조인가
|
|
65
65
|
- [ ] 토큰·BEM·카탈로그를 벗어난 부분이 있다면 이유가 주석이나 문서에 남아 있는가
|
|
66
66
|
- [ ] `--deliver`로 만든 납품본이 `npm install` → `npm run build` → `npm run check`로 자립 통과하는가
|
|
67
|
+
- [ ] 외부 origin 리소스(스크립트·스타일·폰트·위젯)가 없는가 — 불가피한 CDN은 버전 고정 + SRI + `crossorigin`인가 (`trade-off-rules.md` §4)
|
|
68
|
+
- [ ] 배포 환경 제약을 확인했는가 — 폐쇄망·CSP 환경이라면 외부 CDN·인라인 스크립트·런타임 컴파일이 그 환경에서 실제로 동작하는가
|
|
67
69
|
|
|
68
70
|
---
|
|
69
71
|
|
|
@@ -33,7 +33,7 @@ INFOUX는 Tailwind v4 `@theme`에 색상과 폰트만 공개한다.
|
|
|
33
33
|
| `bg-danger-{5..95}` | `--color-danger-*` |
|
|
34
34
|
| `bg-warning-{5..95}` | `--color-warning-*` |
|
|
35
35
|
| `bg-success-{5..95}` | `--color-success-*` |
|
|
36
|
-
| `bg-
|
|
36
|
+
| `bg-information-{5..95}` | `--color-information-*` |
|
|
37
37
|
|
|
38
38
|
## 폰트 유틸리티
|
|
39
39
|
|
|
@@ -38,6 +38,11 @@ WCAG 2.1 AA는 우리 프로젝트에서 **협상 대상이 아니다.** 공공
|
|
|
38
38
|
|
|
39
39
|
카탈로그에 있는 컴포넌트로 풀 수 있으면 그것을 쓴다. 카탈로그 밖 컴포넌트가 정말 필요하다고 판단되면 **임의로 만들지 말고 UX팀 결정으로 확장한다.** 용어도 마찬가지다 — 같은 것을 페이지마다 다르게 부르지 않는다.
|
|
40
40
|
|
|
41
|
+
**외부 라이브러리·스크립트 도입도 같은 원칙이다.** 새 의존을 붙이기 전에 기존 스택(토큰·카탈로그·표준 CSS·이미 쓰는 라이브러리)으로 풀리는지 먼저 검토하고, 도입은 UX팀 결정을 거친다. 도입이 확정되면:
|
|
42
|
+
|
|
43
|
+
- **셀프호스트를 우선한다** — 납품물은 self-contained가 원칙이고, 폐쇄망·내부망 배포에서 외부 CDN은 그대로 장애다.
|
|
44
|
+
- 불가피하게 CDN을 쓰면 **버전 고정 + SRI(`integrity`) + `crossorigin`** 세 가지를 함께 건다. 버전 고정만으로는 공급망 변조를 막지 못한다.
|
|
45
|
+
|
|
41
46
|
## 5. 유지보수 가능성 > 기교
|
|
42
47
|
|
|
43
48
|
우리가 만든 것을 **발주처가 이어받는다.** 영리하지만 우리만 이해하는 해법은 인수인계 시점에 부채가 된다.
|
package/data/rules.json
CHANGED
|
@@ -503,12 +503,12 @@
|
|
|
503
503
|
{
|
|
504
504
|
"id": "R-13",
|
|
505
505
|
"category": "a11y",
|
|
506
|
-
"summary": "
|
|
506
|
+
"summary": "터치 영역 최소 44×44px — 터치 입력 기기 기준",
|
|
507
507
|
"severity": "error",
|
|
508
508
|
"enforcement": [
|
|
509
509
|
"manual"
|
|
510
510
|
],
|
|
511
|
-
"rationale": "손가락으로 정확히 터치하기 어려운 작은 버튼은 오작동을 유발한다. 시각적 크기는 작더라도 padding으로 터치 영역을 확보한다. WCAG 2.5.5 Target Size (AAA)
|
|
511
|
+
"rationale": "손가락으로 정확히 터치하기 어려운 작은 버튼은 오작동을 유발한다. 시각적 크기는 작더라도 padding으로 터치 영역을 확보한다. 적용 범위는 터치 입력(모바일·태블릿)이며, 마우스처럼 정밀한 포인터를 쓰는 데스크탑 dense UI에는 강제하지 않는다 — btn xsmall(32px)·small(40px)이 데스크탑에서 허용되는 근거다. 근거: Apple HIG 44pt / Android 48dp, WCAG 2.5.5 Target Size (AAA). AA 의무선은 WCAG 2.2의 2.5.8 Target Size (Minimum) 24×24이고 간격 대체가 허용되므로, 44px은 팀이 터치 환경에 한정해 상향한 기준이다. KRDS 모바일 권장 사이즈는 medium(48px) 이상이다.",
|
|
512
512
|
"bad": [
|
|
513
513
|
{
|
|
514
514
|
"code": ".icon-btn {\n width: 24px;\n height: 24px;\n}",
|
|
@@ -598,7 +598,7 @@
|
|
|
598
598
|
"check-html-structure.js"
|
|
599
599
|
],
|
|
600
600
|
"lang": "html",
|
|
601
|
-
"rationale": "modal/tab/accordion/tooltip/disclosure/carousel/calendar 등 위젯 컴포넌트는 스크린리더가 의미를 파악할 수 있도록 ARIA로 상태·관계·역할을 명시해야 한다. 네이티브 시맨틱만으로 부족한 영역이며, KRDS와 WAI-ARIA 1.2 APG가 정의한 패턴을 따른다.
|
|
601
|
+
"rationale": "modal/tab/accordion/tooltip/disclosure/carousel/calendar 등 위젯 컴포넌트는 스크린리더가 의미를 파악할 수 있도록 ARIA로 상태·관계·역할을 명시해야 한다. 네이티브 시맨틱만으로 부족한 영역이며, KRDS와 WAI-ARIA 1.2 APG가 정의한 패턴을 따른다. WCAG 1.3.1 Info and Relationships (A) / 4.1.2 Name, Role, Value (A) — KWCAG 대응 항목은 5.3.2 콘텐츠의 선형구조 / 8.2.1 웹 애플리케이션 접근성 준수다.",
|
|
602
602
|
"refs": [
|
|
603
603
|
"references/html-semantics.md",
|
|
604
604
|
"references/accessibility.md"
|
|
@@ -909,6 +909,65 @@
|
|
|
909
909
|
"tokens/brand.json",
|
|
910
910
|
"contracts/art-direction.json"
|
|
911
911
|
]
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
"id": "R-27",
|
|
915
|
+
"category": "html",
|
|
916
|
+
"summary": "아이콘은 카탈로그에서 가져온다 — 임의 SVG 삽입 금지",
|
|
917
|
+
"severity": "error",
|
|
918
|
+
"enforcement": [
|
|
919
|
+
"check-icons.js",
|
|
920
|
+
"manual"
|
|
921
|
+
],
|
|
922
|
+
"rationale": "아이콘을 그때그때 붙여 넣으면 프로젝트마다 출처·굵기·그리드가 섞이고 라이선스 근거가 남지 않는다. 색상을 토큰으로 강제하는 R-01과 같은 이유다. 쓸 아이콘은 contracts/icon-codepoints.json에 등재된 것뿐이며, 없으면 지어내지 말고 UX팀에 요청한다 — AI 도구는 MCP list_icons·get_icon으로 실재하는 이름만 받는다. 기본 표기는 `<span class=\"icon-font icon-font--{name}\">`이고, 아이콘에는 항상 aria-hidden을 붙인다(스크린리더가 PUA 코드포인트를 오독한다). 뜻은 아이콘 밖에서 전한다 — 옆의 텍스트나 버튼의 aria-label이다. SVG 태그는 폰트를 못 쓰는 곳이나 개별 색이 필요할 때만 쓴다. 표정은 슬림·레귤러·볼드·필 네 가지이고 이름과 코드포인트는 그대로 둔 채 글리프만 바뀐다 — 폰트는 `icon-font--bold` 같은 클래스가, SVG는 스프라이트 파일(`sprite-bold.svg`)이 표정을 정한다. SVG 쪽에 `icon--bold` 같은 클래스를 붙이지 않는다(아무 일도 하지 않는다). 한 화면에서 표정을 섞지 않으며, 모든 아이콘에 필이 있는 것은 아니다 — 없는 표정을 부르면 빈 네모가 나온다.",
|
|
923
|
+
"bad": [
|
|
924
|
+
{
|
|
925
|
+
"code": "<svg viewBox=\"0 0 20 20\"><path fill=\"#333\" d=\"...\"/></svg>",
|
|
926
|
+
"note": "카탈로그 밖 임의 SVG — 뷰박스·색이 세트와 어긋난다"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
"code": "<span class=\"icon-font icon-font--search\"></span>",
|
|
930
|
+
"note": "aria-hidden이 없다 — 스크린리더가 코드포인트를 읽는다"
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
"code": "<button class=\"btn btn--text\"><span class=\"icon-font icon-font--close\" aria-hidden=\"true\"></span></button>",
|
|
934
|
+
"note": "아이콘만 있는데 버튼에 이름이 없다 — 무슨 버튼인지 알 수 없다"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
"code": "<span class=\"icon-font icon-font--magnifier\" aria-hidden=\"true\"></span>",
|
|
938
|
+
"note": "대장에 없는 이름 — 아무것도 안 나온다"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
"code": "<svg class=\"icon icon--bold\" aria-hidden=\"true\"><use href=\"/assets/icons/sprite.svg#search\"></use></svg>",
|
|
942
|
+
"note": "SVG에서 표정은 클래스가 아니라 스프라이트 파일이 정한다 — 이 클래스는 아무 일도 안 하고 기본 굵기가 나온다"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"code": "<span class=\"icon-font icon-font--fill icon-font--search\" aria-hidden=\"true\"></span>",
|
|
946
|
+
"note": "돋보기는 채울 면이 없어 필이 없다 — 빈 네모가 나온다. 그 아이콘이 가진 표정만 쓴다"
|
|
947
|
+
}
|
|
948
|
+
],
|
|
949
|
+
"good": [
|
|
950
|
+
{
|
|
951
|
+
"code": "<button class=\"btn\"><span class=\"icon-font icon-font--search\" aria-hidden=\"true\"></span>검색</button>",
|
|
952
|
+
"note": "옆의 텍스트가 뜻을 전한다"
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
"code": "<button class=\"btn btn--text\" aria-label=\"닫기\"><span class=\"icon-font icon-font--close\" aria-hidden=\"true\"></span></button>",
|
|
956
|
+
"note": "아이콘만 있을 때는 버튼에 이름을 준다"
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
"code": "<svg class=\"icon\" aria-hidden=\"true\"><use href=\"/assets/icons/sprite.svg#search\"></use></svg>",
|
|
960
|
+
"note": "폰트를 못 쓰는 곳이나 개별 색이 필요할 때"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
"code": "<span class=\"icon-font icon-font--bold icon-font--search\" aria-hidden=\"true\"></span>",
|
|
964
|
+
"note": "폰트는 클래스가 표정을 바꾼다 — 표정 클래스를 이름 클래스 앞에 둔다"
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
"code": "<svg class=\"icon\" aria-hidden=\"true\"><use href=\"/assets/icons/sprite-bold.svg#search\"></use></svg>",
|
|
968
|
+
"note": "SVG는 스프라이트 파일이 곧 표정이다"
|
|
969
|
+
}
|
|
970
|
+
]
|
|
912
971
|
}
|
|
913
972
|
]
|
|
914
973
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 오류 페이지 (Error Page) — infoUX
|
|
2
|
+
|
|
3
|
+
요청한 페이지를 보여줄 수 없을 때(404 · 403 · 500 · 점검) 한 화면으로 보여주는 패턴. 사용자는 이 화면을 구경하러 온 것이 아니라 **빠져나가러** 왔다. 장식보다 탈출 경로가 먼저다.
|
|
4
|
+
|
|
5
|
+
## 기본 마크업
|
|
6
|
+
|
|
7
|
+
페이지 shell(skip link · `header#header` · `main#main` · `footer#footer`)은 그대로 두고 `main` 안에 한 섹션으로 넣는다.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<section class="section section--content" aria-labelledby="error-title">
|
|
11
|
+
<div class="container">
|
|
12
|
+
<div class="error-page">
|
|
13
|
+
<h1 class="error-page__title" id="error-title">요청하신 페이지를 찾을 수 없습니다</h1>
|
|
14
|
+
<p class="error-page__desc">주소가 바뀌었거나 삭제된 페이지일 수 있습니다. 아래 방법으로 원하시는 내용을 찾아보세요.</p>
|
|
15
|
+
|
|
16
|
+
<div class="error-page__actions">
|
|
17
|
+
<a class="btn btn--primary" href="/">홈으로 이동</a>
|
|
18
|
+
<a class="btn btn--tertiary" href="/sitemap">사이트맵 보기</a>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<form class="error-page__search" role="search" action="/search" method="get">
|
|
22
|
+
<label class="sr-only" for="error-search">검색어</label>
|
|
23
|
+
<input class="input" type="search" id="error-search" name="q" autocomplete="off">
|
|
24
|
+
<button type="submit" class="btn btn--secondary">검색</button>
|
|
25
|
+
</form>
|
|
26
|
+
|
|
27
|
+
<nav class="error-page__help" aria-labelledby="error-help-title">
|
|
28
|
+
<h2 class="error-page__help-title" id="error-help-title">많이 찾는 페이지</h2>
|
|
29
|
+
<ul class="error-page__help-list">
|
|
30
|
+
<li><a class="error-page__help-link" href="/notice">공지사항</a></li>
|
|
31
|
+
<li><a class="error-page__help-link" href="/apply">신청하기</a></li>
|
|
32
|
+
<li><a class="error-page__help-link" href="/lookup">처리 현황 조회</a></li>
|
|
33
|
+
<li><a class="error-page__help-link" href="/faq">자주 묻는 질문</a></li>
|
|
34
|
+
</ul>
|
|
35
|
+
</nav>
|
|
36
|
+
|
|
37
|
+
<p class="error-page__contact">계속 같은 화면이 나오면 대표전화 064-123-4567(평일 09:00~18:00)로 알려 주세요.</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</section>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 시맨틱 구조
|
|
44
|
+
|
|
45
|
+
- **Root 태그**: `<div class="error-page">` — `main > section > .container` 안의 컴포넌트 루트
|
|
46
|
+
- **자식**: `h1.__title` → `p.__desc` → `div.__actions` → (선택) `form.__search` → `nav.__help` → (선택) `p.__contact`
|
|
47
|
+
- **제목**: 오류 화면의 `h1`은 하나다. 섹션 이름은 `aria-labelledby`로 이 `h1`에 연결한다
|
|
48
|
+
- **검색 폼**: `role="search"` + `<label>`. 시각적으로 레이블을 숨기려면 `.sr-only`를 쓴다 (placeholder만으로 레이블을 대신하지 않는다)
|
|
49
|
+
- **바로가기**: `nav` + `aria-labelledby`로 이름을 받는다. 페이지에 nav가 여럿이므로 이름이 서로 달라야 한다
|
|
50
|
+
|
|
51
|
+
> 상세: `references/html-semantics.md#error-page`
|
|
52
|
+
|
|
53
|
+
## 오류 유형별 문안
|
|
54
|
+
|
|
55
|
+
[마이크로카피 3-Part 공식](/design/microcopy/) — **무엇이 잘못됐는지 + (왜) + 어떻게 해결하는지**. 사용자를 탓하지 않고, 내부 오류 코드를 문장에 노출하지 않는다.
|
|
56
|
+
|
|
57
|
+
| 유형 | 제목 예 | 안내 예 | 주 행동 |
|
|
58
|
+
|------|---------|---------|---------|
|
|
59
|
+
| 404 없는 페이지 | 요청하신 페이지를 찾을 수 없습니다 | 주소가 바뀌었거나 삭제된 페이지일 수 있습니다. | 홈 · 검색 · 바로가기 |
|
|
60
|
+
| 403 접근 권한 없음 | 이 페이지를 볼 수 있는 권한이 없습니다 | 로그인이 필요하거나 접근이 제한된 페이지입니다. | 로그인 · 권한 신청 |
|
|
61
|
+
| 500 서버 오류 | 일시적으로 페이지를 보여드릴 수 없습니다 | 잠시 뒤에 다시 시도해 주세요. 문제가 계속되면 문의해 주세요. | 새로고침 · 문의 |
|
|
62
|
+
| 503 점검 | 서비스 점검 중입니다 | 점검 시간 안내(확인된 일시만) | 점검 종료 후 이동 · 공지 |
|
|
63
|
+
|
|
64
|
+
- 점검 문안의 **일시·기간은 확인된 값만** 쓴다. 지어내거나 "곧 완료됩니다" 같은 추측을 쓰지 않는다 (R-23)
|
|
65
|
+
- 제목에 "에러", "Error 404", 기술 용어를 쓰지 않는다. 상태 코드가 필요하면 `__code`를 작게 따로 둔다
|
|
66
|
+
|
|
67
|
+
## 사이트 유형별 수위
|
|
68
|
+
|
|
69
|
+
오류 화면은 사이트의 표현 등급과 무관하게 **utility~restrained**다 (art-direction §1 원칙 3 — 페이지 단위 강등).
|
|
70
|
+
|
|
71
|
+
| 유형 | 수위 | 허용 | 쓰지 않는 것 |
|
|
72
|
+
|------|------|------|--------------|
|
|
73
|
+
| 공공서비스 | restrained | 제목 · 안내 · 홈/검색/바로가기 · 문의 | 일러스트 · 큰 상태 코드 |
|
|
74
|
+
| 공공기관 | restrained | 위와 동일 + 기관 대표 연락처 | 일러스트 · 유머 문구 |
|
|
75
|
+
| CMS·관리자 | utility | 제목 · 안내 · 이전 화면/대시보드로 이동 | 일러스트 · 장식 전부 |
|
|
76
|
+
| 일반사이트 | restrained | 위 + **장식 일러스트 한 점**(`__visual`, `alt=""`) · 상태 코드 | 게임 · 글리치 · 3D · 움직이는 장식 |
|
|
77
|
+
| 커머스·예약 | restrained | 위 + 상품 추천 링크 | 프로모션 배너 · 쿠폰 팝업 |
|
|
78
|
+
|
|
79
|
+
404 화면을 눈에 띄는 연출로 만드는 갤러리 사례가 많지만, 공공 사이트에서는 **재미보다 탈출 경로**가 먼저다. 연출이 필요하면 일반사이트에서 일러스트 한 점으로 끝낸다.
|
|
80
|
+
|
|
81
|
+
## 서버 · 문서 측 요건
|
|
82
|
+
|
|
83
|
+
HTML만으로 끝나지 않는다. 아래는 구현팀과 함께 확인한다.
|
|
84
|
+
|
|
85
|
+
- **HTTP 상태 코드를 실제 값으로 응답한다** (404는 404). 화면만 오류 문구이고 상태가 200이면 검색 엔진·보조기기가 오류를 알 수 없다(soft 404)
|
|
86
|
+
- 없는 주소를 **홈으로 자동 이동(redirect)시키지 않는다.** 사용자가 어디서 길을 잃었는지 알 수 없게 된다
|
|
87
|
+
- `<title>`에 오류를 밝힌다: `페이지를 찾을 수 없습니다 | 사이트명`
|
|
88
|
+
- 검색 엔진에 색인되지 않도록 `<meta name="robots" content="noindex">`를 둔다
|
|
89
|
+
- 오류 화면에서도 전체 페이지 shell(헤더 · 메뉴 · 푸터)을 유지한다 — 일반 내비게이션이 길 찾기의 첫 수단이다
|
|
90
|
+
|
|
91
|
+
## 접근성
|
|
92
|
+
|
|
93
|
+
- 페이지 `h1`은 오류 제목 하나다. 바로가기 제목은 `h2`
|
|
94
|
+
- 제목·안내는 `role="alert"`로 낭독시키지 않는다. 사용자가 직접 이 주소로 들어온 화면이고, 페이지 제목(`<title>`)과 `h1`이 상황을 전한다
|
|
95
|
+
- 검색 입력에는 `<label>`이 필요하다. `autocomplete="off"`는 검색어 재입력 보호용이며 선택이다
|
|
96
|
+
- 링크는 밑줄로 구분하고 본문 링크 대비 4.5:1 이상(`--color-primary-pressed`)
|
|
97
|
+
- 터치 영역 44×44px 이상 (R-13): 바로가기 링크 `min-h-[4.4rem]`, 버튼은 `btn` 기본 크기(48px)
|
|
98
|
+
- 상태 코드(`__code`)는 장식이 아니라 정보다. 크게 쓰더라도 `aria-hidden`을 주지 않는다
|
|
99
|
+
- 움직이는 요소를 두지 않는다. 장식 일러스트는 정지 이미지이며 `alt=""`
|
|
100
|
+
|
|
101
|
+
## 출처
|
|
102
|
+
|
|
103
|
+
- 색상은 `--color-primary` · `--color-primary-pressed` · `--color-text` · `--color-text-subtle` · `--color-border-light`
|
|
104
|
+
- 간격·타이포는 프로젝트 밀도에 맞는 CSS/Tailwind 직접값 사용
|
|
105
|
+
- CSS: `src/styles/6-components/error-page.css`
|
|
106
|
+
- 문안 기준: `site/design/microcopy.md` (에러 메시지 3-Part 공식)
|
|
107
|
+
- 수위 기준: `references/art-direction.md` §1 (페이지 단위 강등)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# 푸터 (Site Footer) — infoUX
|
|
2
|
+
|
|
3
|
+
페이지 하단 공통 영역. 페이지 shell의 `footer#footer` 랜드마크 안에 쓴다. 사이트 유형에 따라 구성 요소를 덜어 쓰는 패턴이라, 모든 요소를 채우는 것이 목표가 아니다.
|
|
4
|
+
|
|
5
|
+
## 기본 마크업
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<footer id="footer" class="site-footer">
|
|
9
|
+
<div class="container">
|
|
10
|
+
<div class="site-footer__top">
|
|
11
|
+
<a class="site-footer__brand" href="/">
|
|
12
|
+
<img class="site-footer__logo" src="/images/logo.svg" alt="기관명">
|
|
13
|
+
</a>
|
|
14
|
+
<nav class="site-footer__nav" aria-label="푸터 메뉴">
|
|
15
|
+
<div class="site-footer__group">
|
|
16
|
+
<h2 class="site-footer__heading" id="footer-group-about">재단 소개</h2>
|
|
17
|
+
<ul class="site-footer__list" aria-labelledby="footer-group-about">
|
|
18
|
+
<li><a class="site-footer__link" href="/about/greeting">인사말</a></li>
|
|
19
|
+
<li><a class="site-footer__link" href="/about/history">연혁</a></li>
|
|
20
|
+
<li><a class="site-footer__link" href="/about/location">오시는 길</a></li>
|
|
21
|
+
</ul>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="site-footer__group">
|
|
24
|
+
<h2 class="site-footer__heading" id="footer-group-program">프로그램</h2>
|
|
25
|
+
<ul class="site-footer__list" aria-labelledby="footer-group-program">
|
|
26
|
+
<li><a class="site-footer__link" href="/program/exhibition">전시</a></li>
|
|
27
|
+
<li><a class="site-footer__link" href="/program/performance">공연</a></li>
|
|
28
|
+
<li><a class="site-footer__link" href="/program/education">교육</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="site-footer__group">
|
|
32
|
+
<h2 class="site-footer__heading" id="footer-group-news">알림마당</h2>
|
|
33
|
+
<ul class="site-footer__list" aria-labelledby="footer-group-news">
|
|
34
|
+
<li><a class="site-footer__link" href="/news/notice">공지사항</a></li>
|
|
35
|
+
<li><a class="site-footer__link" href="/news/press">보도자료</a></li>
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="site-footer__group">
|
|
39
|
+
<h2 class="site-footer__heading" id="footer-group-help">이용 안내</h2>
|
|
40
|
+
<ul class="site-footer__list" aria-labelledby="footer-group-help">
|
|
41
|
+
<li><a class="site-footer__link" href="/help/faq">자주 묻는 질문</a></li>
|
|
42
|
+
<li><a class="site-footer__link" href="/help/sitemap">사이트맵</a></li>
|
|
43
|
+
</ul>
|
|
44
|
+
</div>
|
|
45
|
+
</nav>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="site-footer__bottom">
|
|
49
|
+
<div class="site-footer__info">
|
|
50
|
+
<nav class="site-footer__legal" aria-label="약관 및 정책">
|
|
51
|
+
<ul class="site-footer__legal-list">
|
|
52
|
+
<li><a class="site-footer__link site-footer__link--important" href="/privacy">개인정보처리방침</a></li>
|
|
53
|
+
<li><a class="site-footer__link" href="/terms">이용약관</a></li>
|
|
54
|
+
<li><a class="site-footer__link" href="/copyright">저작권 정책</a></li>
|
|
55
|
+
</ul>
|
|
56
|
+
</nav>
|
|
57
|
+
<address class="site-footer__address">
|
|
58
|
+
제주특별자치도 제주시 한라로 100<br>
|
|
59
|
+
대표전화 064-123-4567 · 이메일 contact@example.org
|
|
60
|
+
</address>
|
|
61
|
+
<small class="site-footer__copy">© 2026 기관명. All rights reserved.</small>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<details class="site-footer__family">
|
|
65
|
+
<summary class="site-footer__family-summary">
|
|
66
|
+
패밀리 사이트
|
|
67
|
+
<svg class="site-footer__family-icon icon icon--xsmall" aria-hidden="true"><use href="/assets/icons/sprite.svg#chevron-down"></use></svg>
|
|
68
|
+
</summary>
|
|
69
|
+
<ul class="site-footer__family-list">
|
|
70
|
+
<li><a class="site-footer__family-link" href="https://example.org/museum">미술관</a></li>
|
|
71
|
+
<li><a class="site-footer__family-link" href="https://example.org/library">도서관</a></li>
|
|
72
|
+
</ul>
|
|
73
|
+
</details>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</footer>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 시맨틱 구조
|
|
80
|
+
|
|
81
|
+
- **Root 태그**: `<footer id="footer" class="site-footer">` — 페이지에 하나, `main` 바깥 (R-15 page shell)
|
|
82
|
+
- **자식**: `.container` → `__top`(브랜드 + 푸터 메뉴) → `__bottom`(정책 링크·연락처·저작권·패밀리 사이트)
|
|
83
|
+
- **내비게이션**: 푸터 안 `nav`가 둘 이상이면 각각 `aria-label`로 구분한다 (`푸터 메뉴`, `약관 및 정책`)
|
|
84
|
+
- **연락처**: `<address>`는 이 사이트의 연락처에만 쓴다. 임의의 주소 문장에 쓰지 않는다
|
|
85
|
+
- **저작권**: `<small>` — 부가 고지임을 나타낸다
|
|
86
|
+
|
|
87
|
+
> 상세: `references/html-semantics.md#site-footer`
|
|
88
|
+
|
|
89
|
+
## Variant
|
|
90
|
+
|
|
91
|
+
| Variant | 클래스 | 용도 |
|
|
92
|
+
|---------|--------|------|
|
|
93
|
+
| 기본 | `.site-footer` | 푸터 메뉴 + 하단 고지. 일반사이트·공공기관 |
|
|
94
|
+
| 소형 | `.site-footer--compact` | `__top` 없이 `__bottom`만. 공공서비스·관리자 화면 |
|
|
95
|
+
| 반전 | `.site-footer--inverse` | 어두운 바탕. 일반사이트·커머스 표현형 한정 |
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<!-- 소형: 상단 메뉴 없이 정책 링크와 저작권만 -->
|
|
99
|
+
<footer id="footer" class="site-footer site-footer--compact">
|
|
100
|
+
<div class="container">
|
|
101
|
+
<div class="site-footer__bottom">
|
|
102
|
+
<div class="site-footer__info">
|
|
103
|
+
<nav class="site-footer__legal" aria-label="약관 및 정책">
|
|
104
|
+
<ul class="site-footer__legal-list">
|
|
105
|
+
<li><a class="site-footer__link site-footer__link--important" href="/privacy">개인정보처리방침</a></li>
|
|
106
|
+
<li><a class="site-footer__link" href="/terms">이용약관</a></li>
|
|
107
|
+
</ul>
|
|
108
|
+
</nav>
|
|
109
|
+
<small class="site-footer__copy">© 2026 기관명</small>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</footer>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 사이트 유형별 구성
|
|
117
|
+
|
|
118
|
+
| 유형 | 변형 | 구성 | 비고 |
|
|
119
|
+
|------|------|------|------|
|
|
120
|
+
| 일반사이트 | 기본 / 반전 | 푸터 메뉴 + 정책 링크 + 연락처 + 저작권 (+ 패밀리 사이트) | 표현형이라 브랜드 톤을 푸터에서 가장 크게 연다 |
|
|
121
|
+
| 공공서비스 | 소형 | 정책 링크 + 저작권 | 과업 화면을 방해하지 않는다. 기관 식별자는 발주처 지침이 확인된 경우에만 |
|
|
122
|
+
| 공공기관 | 기본 | 푸터 메뉴 + 정책 링크 + 연락처 + 저작권 + 패밀리 사이트 | 공공 푸터 필수 링크는 과업지시서·기관 정책 확인 후 추가 |
|
|
123
|
+
| CMS·관리자 | 소형 | 저작권·버전 표기 정도 | utility 등급. 장식 없음 |
|
|
124
|
+
| 커머스·예약 | 기본 / 반전 | 약관·환불·개인정보 링크 + 사업자 정보 | 법정 표기 항목은 발주처·법무 확인 후 채운다 |
|
|
125
|
+
|
|
126
|
+
## 사용 조건
|
|
127
|
+
|
|
128
|
+
- 푸터 메뉴는 사이트맵을 복제하는 곳이 아니다 — 자주 찾는 경로만 3~5개 그룹으로 추린다
|
|
129
|
+
- 한 그룹의 링크는 5개 안팎으로 둔다. 길어지면 그룹을 나눈다
|
|
130
|
+
- 정책 링크 중 가장 중요한 하나(개인정보처리방침 등)에만 `--important`를 준다
|
|
131
|
+
- 패밀리 사이트는 **링크 클릭으로만** 이동한다. `<select>` 선택만으로 페이지가 바뀌지 않게 한다 (WCAG 3.2.2)
|
|
132
|
+
- 사업자 정보·법정 고지 문구는 지어내지 않는다 — 발주처가 확인해 준 값만 쓴다 (R-23)
|
|
133
|
+
|
|
134
|
+
## 접근성
|
|
135
|
+
|
|
136
|
+
- 랜드마크: `<footer>`는 `contentinfo`로 노출된다. 페이지에 하나만 둔다
|
|
137
|
+
- 푸터 `nav`가 여럿이면 `aria-label`이 서로 달라야 한다
|
|
138
|
+
- 그룹 제목은 `h2` + 목록 `aria-labelledby`로 연결해 스크린리더가 그룹 이름을 읽게 한다
|
|
139
|
+
- 링크 터치 영역 44×44px 이상 (R-13) — `__link`는 `min-h-[4.4rem]`를 포함한다
|
|
140
|
+
- 패밀리 사이트 `details`는 키보드 `Enter`/`Space`로 열고 닫는다. JS가 필요 없다
|
|
141
|
+
- 반전 변형: 일반 텍스트 4.5:1 이상을 유지한다 (`--color-gray-20` on `--color-bg-inverse`)
|
|
142
|
+
- 외부 사이트 링크는 새 창을 열지 않는다. 새 창이 필요하면 링크 텍스트나 `aria-label`에 "새 창"을 밝힌다
|
|
143
|
+
- 초점 외곽선: 칸을 꽉 채우는 링크(`__family-link`)는 `outline-offset`을 음수로 두어 안쪽에 그린다
|
|
144
|
+
|
|
145
|
+
## 출처
|
|
146
|
+
|
|
147
|
+
- 색상은 `--color-bg-subtler` · `--color-text` · `--color-text-subtle` · 반전은 `--color-bg-inverse` · `--color-text-inverse` · `--color-gray-20`
|
|
148
|
+
- 간격·타이포는 프로젝트 밀도에 맞는 CSS/Tailwind 직접값 사용
|
|
149
|
+
- CSS: `src/styles/6-components/footer.css`
|
|
150
|
+
- 아이콘: `chevron-down` 스프라이트 — `/assets/icons/sprite.svg` (아이콘 카탈로그, R-27)
|
package/data/snippets/header.md
CHANGED
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
|
|
23
23
|
<div class="site-header__actions">
|
|
24
24
|
<button type="button" class="btn btn--text btn--small">로그인</button>
|
|
25
|
-
<button type="button" class="site-header__toggle" aria-label="메뉴
|
|
25
|
+
<button type="button" class="site-header__toggle" aria-label="전체 메뉴" aria-expanded="false" aria-controls="mobile-menu" data-mobile-menu-open="mobile-menu">
|
|
26
|
+
<svg class="icon" aria-hidden="true"><use href="/assets/icons/sprite.svg#menu"></use></svg>
|
|
27
|
+
</button>
|
|
26
28
|
</div>
|
|
27
29
|
</div>
|
|
28
30
|
</header>
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
|
|
31
33
|
## 동작
|
|
32
34
|
|
|
33
|
-
- 모바일/태블릿 (< 1280px): 주 메뉴 숨김 + 햄버거 토글
|
|
35
|
+
- 모바일/태블릿 (< 1280px): 주 메뉴 숨김 + 햄버거 토글 노출. 토글은 [모바일 메뉴](mobile-menu.md)를 연다
|
|
34
36
|
- PC (≥ 1280px): 주 메뉴 노출 + 햄버거 숨김
|
|
35
37
|
- `position: sticky` 적용 (스크롤 시에도 상단 유지)
|
|
36
38
|
|
|
@@ -39,7 +41,8 @@
|
|
|
39
41
|
- `<header id="header">` 시맨틱 태그 사용 (페이지당 하나)
|
|
40
42
|
- 주 메뉴는 `<nav aria-label="주 메뉴">` (페이지에 nav가 여러 개면 label 필수)
|
|
41
43
|
- 현재 페이지 메뉴: `aria-current="page"`
|
|
42
|
-
- 모바일 토글: `aria-label
|
|
44
|
+
- 모바일 토글: `aria-label`은 상태와 무관하게 고정(`전체 메뉴`)하고, 열림 여부는 `aria-expanded`가 전한다. `aria-controls`로 [모바일 메뉴](mobile-menu.md) 패널 id를 연결한다
|
|
45
|
+
- 토글 아이콘은 아이콘 카탈로그(`menu` 스프라이트)를 쓴다 — 햄버거 모양의 텍스트 기호를 아이콘 대신 쓰지 않는다 (R-27)
|
|
43
46
|
- 로고 `<img>` `alt` 텍스트 필수 (KRDS R-09)
|
|
44
47
|
|
|
45
48
|
## 조건부 공공/정부 요소
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# 아이콘 (Icon) — infoUX
|
|
2
|
+
|
|
3
|
+
카탈로그에 등재된 아이콘만 쓴다. 목록은 `npm run icons:sheet`로 보거나
|
|
4
|
+
AI 도구에서는 MCP `list_icons`로 받는다. 없는 아이콘은 지어내지 말고 UX팀에 요청한다 (R-27).
|
|
5
|
+
|
|
6
|
+
**한국어로 찾는다.** `list_icons("달력")`처럼 평소 쓰는 말로 물으면 걸린다 —
|
|
7
|
+
이름이 영어라 「없다」고 판단하고 지어내는 일을 막는 다리다
|
|
8
|
+
(사전: `contracts/icon-keywords.json`).
|
|
9
|
+
|
|
10
|
+
## 기본 마크업
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<!-- 장식용 — 옆에 텍스트가 있어 아이콘이 의미를 더하지 않을 때 -->
|
|
14
|
+
<button class="btn">
|
|
15
|
+
<span class="icon-font icon-font--search" aria-hidden="true"></span>
|
|
16
|
+
검색
|
|
17
|
+
</button>
|
|
18
|
+
|
|
19
|
+
<!-- 의미를 담을 때 — 아이콘만으로 기능을 나타낸다 -->
|
|
20
|
+
<button class="btn btn--text" aria-label="닫기">
|
|
21
|
+
<span class="icon-font icon-font--close" aria-hidden="true"></span>
|
|
22
|
+
</button>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`aria-hidden`은 장식·의미 어느 쪽이든 붙인다. 폰트 아이콘은 스크린리더가 PUA 코드포인트를
|
|
26
|
+
엉뚱하게 읽으므로 아이콘 자체를 숨기고, **뜻은 옆의 텍스트나 버튼의 `aria-label`이 전한다.**
|
|
27
|
+
|
|
28
|
+
### SVG 태그로 넣을 때
|
|
29
|
+
|
|
30
|
+
폰트를 못 쓰는 곳(일부 메일 템플릿·외부 CMS)이나 아이콘 하나만 색을 달리해야 할 때 쓴다.
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<svg class="icon" aria-hidden="true"><use href="/assets/icons/sprite.svg#search"></use></svg>
|
|
34
|
+
<svg class="icon" role="img" aria-label="검색"><use href="/assets/icons/sprite.svg#search"></use></svg>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
낱개 SVG 파일이 필요하면 스튜디오 상세 화면에서 「SVG 파일 받기」로 내려받는다.
|
|
38
|
+
|
|
39
|
+
## 표정 — 슬림 · 레귤러 · 볼드 · 필
|
|
40
|
+
|
|
41
|
+
한 아이콘이 네 얼굴을 가진다. **이름과 코드포인트는 그대로이고 글리프만 바뀐다.**
|
|
42
|
+
|
|
43
|
+
| 표정 | 획 굵기 | 언제 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 슬림 | 1.0 | 32px 이상 큰 자리, 옅은 보조 정보 |
|
|
46
|
+
| 레귤러 | 1.5 | 기본. 본문 옆에서 글자와 무게가 맞는다 |
|
|
47
|
+
| 볼드 | 2.0 | 버튼·헤더처럼 눈이 먼저 닿아야 하는 자리 |
|
|
48
|
+
| 필 | — | 선택·활성 상태 (탭 현재 항목, 즐겨찾기 켬) |
|
|
49
|
+
|
|
50
|
+
한 화면에서 표정을 섞지 않는다. 같은 층위의 아이콘은 같은 표정으로 둔다 —
|
|
51
|
+
굵기가 뒤섞이면 중요도가 다른 것처럼 읽힌다.
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<!-- 폰트: 클래스가 표정을 바꾼다 -->
|
|
55
|
+
<span class="icon-font icon-font--bold icon-font--search" aria-hidden="true"></span>
|
|
56
|
+
|
|
57
|
+
<!-- SVG: 스프라이트 파일이 곧 표정이다 — 클래스를 더 붙이지 않는다 -->
|
|
58
|
+
<svg class="icon" aria-hidden="true"><use href="/assets/icons/sprite-bold.svg#search"></use></svg>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**모든 아이콘에 네 표정이 다 있는 것은 아니다.** 채울 면이 없는 형태(돋보기·화살표 등)에는
|
|
62
|
+
필을 만들지 않는다 — 72종 중 40종에만 있다. 없는 표정을 부르면 빈 네모가 나오므로,
|
|
63
|
+
스튜디오 상세 화면에서 그 아이콘이 가진 표정만 골라 쓴다.
|
|
64
|
+
|
|
65
|
+
프로젝트가 기본만 쓰면 `infoux-icons.woff2` 하나만 넣으면 된다. 표정은 파일이 따로라
|
|
66
|
+
쓰는 것만 가져가면 된다.
|
|
67
|
+
|
|
68
|
+
## 크기
|
|
69
|
+
|
|
70
|
+
폰트는 `font-size`를 따라간다. 기본 24px이고, 클래스로 바꾼다.
|
|
71
|
+
|
|
72
|
+
- `.icon-font` (기본 24px) · `--xsmall` 16 · `--small` 20 · `--large` 32 · `--xlarge` 40
|
|
73
|
+
- 본문과 섞일 때는 크기를 지정하지 않는다 — 글자 크기를 그대로 따른다
|
|
74
|
+
|
|
75
|
+
SVG 방식은 `.icon` + 같은 어휘(`.icon--small` 등)를 쓴다. 시각적 이름(`--big`)은
|
|
76
|
+
쓰지 않는다 (R-06·R-18).
|
|
77
|
+
|
|
78
|
+
## 색
|
|
79
|
+
|
|
80
|
+
색을 아이콘에 넣지 않는다. `fill: currentColor`라 부모의 `color`를 따라간다 (R-01의 아이콘판).
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<span style="--tone: var(--color-danger)" class="delete-action">
|
|
84
|
+
<svg class="icon" aria-hidden="true"><use href="/assets/icons/sprite.svg#delete"></use></svg>
|
|
85
|
+
삭제
|
|
86
|
+
</span>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 접근성
|
|
90
|
+
|
|
91
|
+
- 아이콘에는 항상 `aria-hidden="true"`를 붙인다. 폰트는 스크린리더가 오독하고, SVG도
|
|
92
|
+
장식일 때가 대부분이다
|
|
93
|
+
- **뜻은 아이콘 밖에서 전한다** — 옆의 텍스트, 또는 버튼의 `aria-label`
|
|
94
|
+
- 아이콘만 있는 버튼은 버튼에 `aria-label`이 없으면 무슨 버튼인지 알 수 없다
|
|
95
|
+
- 클릭 영역은 아이콘 크기가 아니라 44×44px 이상 (R-13)
|
|
96
|
+
- SVG를 의미 전달에 직접 쓸 때만 `role="img"` + `aria-label`을 쓴다
|
|
97
|
+
|
|
98
|
+
## 출처
|
|
99
|
+
|
|
100
|
+
- CSS: `assets/icons/icons.css` (생성물 — 직접 고치지 않는다)
|
|
101
|
+
- 원본: `assets/icons/svg/` · 대장: `contracts/icon-codepoints.json`
|
|
102
|
+
- 규격: `contracts/icon-contract.json` · 다시 만들기: `npm run icons:build`
|