@infomind-ux/infoux-mcp 0.1.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.
Files changed (53) hide show
  1. package/README.md +81 -0
  2. package/bin/server.js +331 -0
  3. package/data/contract.md +215 -0
  4. package/data/manifest.json +286 -0
  5. package/data/references/accessibility.md +243 -0
  6. package/data/references/forbidden-patterns.md +462 -0
  7. package/data/references/guide-import.md +93 -0
  8. package/data/references/html-semantics.md +475 -0
  9. package/data/references/krds-components.md +1426 -0
  10. package/data/references/krds-source.md +1125 -0
  11. package/data/references/krds-tokens.md +201 -0
  12. package/data/references/project-profiles.md +99 -0
  13. package/data/references/snippet-template.md +185 -0
  14. package/data/references/tailwind-mapping.md +53 -0
  15. package/data/rules.json +780 -0
  16. package/data/snippets/accordion.md +49 -0
  17. package/data/snippets/alert.md +37 -0
  18. package/data/snippets/badge.md +29 -0
  19. package/data/snippets/boilerplate.md +125 -0
  20. package/data/snippets/breadcrumb.md +26 -0
  21. package/data/snippets/btn.md +63 -0
  22. package/data/snippets/calendar.md +64 -0
  23. package/data/snippets/card.md +50 -0
  24. package/data/snippets/carousel.md +43 -0
  25. package/data/snippets/check-radio.md +55 -0
  26. package/data/snippets/disclosure.md +44 -0
  27. package/data/snippets/file-upload.md +34 -0
  28. package/data/snippets/form.md +68 -0
  29. package/data/snippets/header.md +56 -0
  30. package/data/snippets/list.md +64 -0
  31. package/data/snippets/main-menu.md +49 -0
  32. package/data/snippets/modal.md +54 -0
  33. package/data/snippets/pagination.md +41 -0
  34. package/data/snippets/progress.md +30 -0
  35. package/data/snippets/select.md +34 -0
  36. package/data/snippets/side-panel.md +57 -0
  37. package/data/snippets/spinner.md +24 -0
  38. package/data/snippets/step-indicator.md +38 -0
  39. package/data/snippets/switch.md +32 -0
  40. package/data/snippets/tab.md +52 -0
  41. package/data/snippets/table.md +63 -0
  42. package/data/snippets/tag.md +42 -0
  43. package/data/snippets/toast.md +48 -0
  44. package/data/snippets/tooltip.md +38 -0
  45. package/data/tokens.css +390 -0
  46. package/data/workflows/change-token.md +20 -0
  47. package/data/workflows/create-component.md +71 -0
  48. package/data/workflows/design-form.md +20 -0
  49. package/data/workflows/design-page.md +22 -0
  50. package/data/workflows/design-widget.md +19 -0
  51. package/data/workflows/init-project.md +68 -0
  52. package/data/workflows/review-ui.md +14 -0
  53. package/package.json +34 -0
@@ -0,0 +1,780 @@
1
+ {
2
+ "meta": {
3
+ "version": "2.0",
4
+ "description": "인포마인드 UX팀 코딩 규칙 — 소스 진실. scripts/build-rules.js가 CLAUDE.md와 site/conventions/ 페이지를 여기서 생성한다. KRDS+Tailwind v4 기준 (2.0)."
5
+ },
6
+ "categories": [
7
+ {
8
+ "id": "css",
9
+ "title": "CSS 규칙",
10
+ "slug": "css-rules",
11
+ "order": 2,
12
+ "description": "CSS 파일 작성 시 적용되는 값·문법 규칙이다. Tailwind v4 + INFOUX 토큰 기반.",
13
+ "lang": "css"
14
+ },
15
+ {
16
+ "id": "bem",
17
+ "title": "BEM 네이밍",
18
+ "slug": "bem",
19
+ "order": 3,
20
+ "description": "CSS 클래스 명명 규칙이다. Stylelint와 check-violations.js로 자동 검증한다. 5-objects, 6-components 레이어에만 적용된다.",
21
+ "lang": "css"
22
+ },
23
+ {
24
+ "id": "html",
25
+ "title": "HTML/마크업 규칙",
26
+ "slug": "html-rules",
27
+ "order": 4,
28
+ "description": "HTML 마크업 작성 규칙이다.",
29
+ "lang": "html"
30
+ },
31
+ {
32
+ "id": "a11y",
33
+ "title": "접근성 규칙",
34
+ "slug": "a11y-rules",
35
+ "order": 5,
36
+ "description": "KWCAG/WCAG 2.1 AA 기반 접근성 필수 규칙이다.",
37
+ "lang": "css"
38
+ }
39
+ ],
40
+ "rules": [
41
+ {
42
+ "id": "R-01",
43
+ "category": "css",
44
+ "summary": "색상은 var(--token) 강제 — 간격/크기/타이포 스케일은 권장",
45
+ "severity": "error",
46
+ "enforcement": [
47
+ "check-violations.js"
48
+ ],
49
+ "rationale": "색상은 브랜드·상태·접근성·테마 변경의 영향이 크므로 반드시 토큰으로 관리한다. 반면 간격·크기·타이포 스케일은 KRDS 값을 참고하되 프로젝트 성격에 맞게 조정할 수 있다. 특히 CMS/관리자 화면은 정보 밀도와 반복 작업 효율이 중요하므로 KRDS 수치 체계를 그대로 강제하지 않는다.",
50
+ "bad": [
51
+ {
52
+ "code": "color: #256ef4;",
53
+ "note": "하드코딩 색상"
54
+ },
55
+ {
56
+ "code": "background: #f8f8f8;",
57
+ "note": "하드코딩 색상"
58
+ },
59
+ {
60
+ "code": "color: rgb(37, 110, 244);",
61
+ "note": "하드코딩 색상"
62
+ }
63
+ ],
64
+ "good": [
65
+ {
66
+ "code": "color: var(--color-primary);",
67
+ "note": "INFOUX 공개 토큰"
68
+ },
69
+ {
70
+ "code": "color: var(--color-primary);",
71
+ "note": "INFOMIND 시맨틱 별칭 (INFOUX 토큰을 가리킴)"
72
+ },
73
+ {
74
+ "code": "padding: 16px;",
75
+ "note": "프로젝트 맥락상 명확한 간격값은 허용"
76
+ },
77
+ {
78
+ "code": "gap: 1.6rem;",
79
+ "note": "반복 패턴에는 INFOMIND 간격 별칭 권장"
80
+ }
81
+ ],
82
+ "refs": [
83
+ "tokens/foundation.json",
84
+ "tokens/foundation.json",
85
+ "tokens/build/tokens.css"
86
+ ]
87
+ },
88
+ {
89
+ "id": "R-02",
90
+ "category": "css",
91
+ "summary": "!important 사용 금지 — 부득이한 경우 주석으로 사유 필수",
92
+ "severity": "warning",
93
+ "enforcement": [
94
+ "check-violations.js"
95
+ ],
96
+ "rationale": "캐스케이드 우선순위를 강제 무력화하여 이후 오버라이드를 불가능하게 만든다. 외부 라이브러리 인라인 스타일을 덮어야 하는 등 불가피한 경우에만 허용하며 반드시 사유를 주석으로 명시한다.",
97
+ "bad": [
98
+ {
99
+ "code": ".element { color: var(--color-primary) !important; }",
100
+ "note": "사유 없는 !important"
101
+ }
102
+ ],
103
+ "good": [
104
+ {
105
+ "code": ".pagefind-ui__input { font-size: 1.7rem !important; /* pagefind 인라인 스타일 오버라이드 */ }",
106
+ "note": "사유 주석 명시"
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ "id": "R-03",
112
+ "category": "css",
113
+ "summary": "SCSS 사용 금지 — 표준 CSS nesting + Tailwind v4 문법 허용",
114
+ "severity": "error",
115
+ "enforcement": [
116
+ "check-violations.js"
117
+ ],
118
+ "rationale": "프로젝트는 SCSS 빌드 파이프라인을 사용하지 않는다. .scss 확장자, @use/@forward, SCSS 변수($var), SCSS 전용 함수/믹스인 사용을 금지한다. 단, 표준 CSS nesting과 Tailwind v4의 @theme/@apply/@utility 등 CSS 기반 문법은 허용한다.",
119
+ "bad": [
120
+ {
121
+ "code": "/* style.scss */",
122
+ "note": ".scss 확장자 — 빌드 파이프라인 없음"
123
+ },
124
+ {
125
+ "code": "@use '../1-settings' as settings;",
126
+ "note": "Dart Sass @use"
127
+ },
128
+ {
129
+ "code": "@forward 'btn';",
130
+ "note": "Dart Sass @forward"
131
+ },
132
+ {
133
+ "code": "$primary: #256ef4;",
134
+ "note": "SCSS 변수"
135
+ }
136
+ ],
137
+ "good": [
138
+ {
139
+ "code": "/* style.css */",
140
+ "note": ".css 확장자만"
141
+ },
142
+ {
143
+ "code": ".card { &__title { color: var(--color-text); } }",
144
+ "note": "표준 CSS nesting"
145
+ },
146
+ {
147
+ "code": ".btn { @apply inline-flex items-center; }",
148
+ "note": "Tailwind @apply"
149
+ },
150
+ {
151
+ "code": "@import \"tailwindcss\";",
152
+ "note": "Tailwind v4 import"
153
+ }
154
+ ],
155
+ "refs": [
156
+ "src/styles/style.css",
157
+ "tokens/build/tokens.css"
158
+ ]
159
+ },
160
+ {
161
+ "id": "R-19",
162
+ "category": "css",
163
+ "summary": "스타일 CSS는 Tailwind v4 @apply 우선 — 토큰 값은 var(--token) 유지",
164
+ "severity": "error",
165
+ "enforcement": [
166
+ "check-violations.js"
167
+ ],
168
+ "rationale": "반복되는 레이아웃, 정렬, 표시 상태, 커서, overflow 같은 유틸리티 성격의 선언은 Tailwind v4 @apply로 작성해 CSS 구조를 간결하게 유지한다. 색상, 간격, 크기, radius, shadow, typography처럼 KRDS/INFOMIND 토큰 의미가 중요한 값은 Tailwind 기본 유틸로 억지 변환하지 않고 var(--token)을 유지한다.",
169
+ "bad": [
170
+ {
171
+ "code": ".btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}",
172
+ "note": "@apply로 표현 가능한 raw utility 선언"
173
+ }
174
+ ],
175
+ "good": [
176
+ {
177
+ "code": ".btn {\n @apply inline-flex items-center justify-center;\n color: var(--color-text);\n}",
178
+ "note": "유틸은 @apply, 토큰 값은 var() 유지"
179
+ },
180
+ {
181
+ "code": ".card {\n @apply relative overflow-hidden;\n border-radius: 0.6rem;\n}",
182
+ "note": "구조 유틸과 INFOUX 토큰 병행"
183
+ }
184
+ ],
185
+ "refs": [
186
+ "src/styles/**/*.css",
187
+ "starter/src/styles/**/*.css"
188
+ ]
189
+ },
190
+ {
191
+ "id": "R-20",
192
+ "category": "css",
193
+ "summary": "호환성 위험 CSS 선택자 금지 — 핵심 CSS에서 :has() 사용 금지",
194
+ "severity": "error",
195
+ "enforcement": [
196
+ "check-violations.js"
197
+ ],
198
+ "rationale": "공공서비스·공공기관·일반 납품물은 최신 브라우저 기준을 따르더라도 사용자의 실제 업데이트 상태와 임베디드/모바일 브라우저 차이를 고려해야 한다. `:has()`는 강력하지만 지원되지 않는 환경에서는 선택자 블록 전체가 무시된다. 핵심 상태 표현은 sibling selector, 의미적 상태 class, ARIA 속성 selector로 구현한다.",
199
+ "bad": [
200
+ {
201
+ "code": ".field:has(input:disabled) { opacity: .5; }",
202
+ "note": "부모 상태를 :has()에 의존"
203
+ },
204
+ {
205
+ "code": ".check:hover:not(:has(input:disabled)) .check__box { border-color: var(--color-border-dark); }",
206
+ "note": "비활성 상태 처리에 :has() 의존"
207
+ }
208
+ ],
209
+ "good": [
210
+ {
211
+ "code": ".field input:disabled ~ .field__label { color: var(--color-text-disabled); }",
212
+ "note": "기존 DOM 구조에서 sibling selector 사용"
213
+ },
214
+ {
215
+ "code": ".disclosure[aria-expanded=\"true\"] + .disclosure__panel { display: block; }",
216
+ "note": "ARIA 상태 속성으로 관계 표현"
217
+ },
218
+ {
219
+ "code": ".modal--open { display: flex; }",
220
+ "note": "BEM modifier로 시각 상태 표현"
221
+ }
222
+ ],
223
+ "refs": [
224
+ "site/testing/browser-testing.md",
225
+ "package.json#browserslist"
226
+ ]
227
+ },
228
+ {
229
+ "id": "R-04",
230
+ "category": "bem",
231
+ "summary": "BEM 사용 (5-objects, 6-components 레이어에만 적용)",
232
+ "severity": "info",
233
+ "enforcement": [
234
+ "stylelint"
235
+ ],
236
+ "rationale": "클래스명만으로 컴포넌트 역할과 관계를 파악할 수 있어 HTML과 CSS를 별도 탐색 없이 이해할 수 있다. 3-generic·4-elements·7-utilities 레이어는 BEM 미적용.",
237
+ "bad": [
238
+ {
239
+ "code": ".btn-primary { }",
240
+ "note": "modifier 구분자 누락"
241
+ },
242
+ {
243
+ "code": ".card-header { }",
244
+ "note": "element 구분자 누락"
245
+ },
246
+ {
247
+ "code": ".inputBox { }",
248
+ "note": "camelCase"
249
+ }
250
+ ],
251
+ "good": [
252
+ {
253
+ "code": ".btn--primary { }",
254
+ "note": "modifier: 이중 하이픈"
255
+ },
256
+ {
257
+ "code": ".card__header { }",
258
+ "note": "element: 이중 언더스코어"
259
+ },
260
+ {
261
+ "code": ".form__input { }",
262
+ "note": "소문자 kebab"
263
+ }
264
+ ]
265
+ },
266
+ {
267
+ "id": "R-05",
268
+ "category": "bem",
269
+ "summary": "element 2단계 중첩 금지 — 평탄화",
270
+ "severity": "error",
271
+ "enforcement": [
272
+ "check-violations.js",
273
+ "stylelint"
274
+ ],
275
+ "rationale": "HTML 구조가 바뀌면 클래스명이 함께 깨진다. Block 기준으로 element를 평탄화하면 HTML 리팩토링에 독립적이다.",
276
+ "bad": [
277
+ {
278
+ "code": ".card__header__title { }",
279
+ "note": "2단계 element"
280
+ },
281
+ {
282
+ "code": ".form__group__label { }",
283
+ "note": "2단계 element"
284
+ }
285
+ ],
286
+ "good": [
287
+ {
288
+ "code": ".card__title { }",
289
+ "note": "Block 기준 평탄화"
290
+ },
291
+ {
292
+ "code": ".form__label { }",
293
+ "note": "Block 기준 평탄화"
294
+ }
295
+ ]
296
+ },
297
+ {
298
+ "id": "R-06",
299
+ "category": "bem",
300
+ "summary": "시각적 modifier 금지 — 의미적 이름 사용",
301
+ "severity": "error",
302
+ "enforcement": [
303
+ "check-violations.js"
304
+ ],
305
+ "rationale": "브랜드 색상이 바뀌거나 디자인이 개편되면 시각적 이름(.btn--blue)은 클래스를 전부 바꿔야 한다. 의미적 이름(.btn--primary)은 디자인 변경에 독립적이다. KRDS 표준 modifier(primary/secondary/tertiary/text/xsmall~xlarge)만 사용한다.",
306
+ "bad": [
307
+ {
308
+ "code": ".btn--gray { }",
309
+ "note": "색상 이름 modifier"
310
+ },
311
+ {
312
+ "code": ".btn--blue { }",
313
+ "note": "색상 이름 modifier"
314
+ },
315
+ {
316
+ "code": ".text--red { }",
317
+ "note": "색상 이름 modifier"
318
+ },
319
+ {
320
+ "code": ".btn--ghost { }",
321
+ "note": "옛 variant — KRDS에서 폐기됨"
322
+ },
323
+ {
324
+ "code": ".btn--outline { }",
325
+ "note": "옛 variant — KRDS에서 폐기됨"
326
+ },
327
+ {
328
+ "code": ".btn--sm { }",
329
+ "note": "옛 size — KRDS small/xsmall 사용"
330
+ }
331
+ ],
332
+ "good": [
333
+ {
334
+ "code": ".btn--primary { }",
335
+ "note": "KRDS 정의 variant"
336
+ },
337
+ {
338
+ "code": ".btn--secondary { }",
339
+ "note": "KRDS 정의 variant"
340
+ },
341
+ {
342
+ "code": ".btn--tertiary { }",
343
+ "note": "KRDS 정의 variant"
344
+ },
345
+ {
346
+ "code": ".btn--small { }",
347
+ "note": "KRDS 정의 size"
348
+ },
349
+ {
350
+ "code": ".text--danger { }",
351
+ "note": "시맨틱 상태"
352
+ }
353
+ ]
354
+ },
355
+ {
356
+ "id": "R-07",
357
+ "category": "html",
358
+ "summary": "inline style 금지 — CSS 커스텀 프로퍼티 style=\"--var: val\"은 허용",
359
+ "severity": "warning",
360
+ "enforcement": [
361
+ "check-violations.js"
362
+ ],
363
+ "lang": "html",
364
+ "rationale": "스타일은 CSS 파일에서만 관리한다. 인라인 스타일은 특이도가 높아 오버라이드가 어렵고 토큰 시스템을 우회한다. CSS 커스텀 프로퍼티 주입(style=\"--width: 320px\")은 동적 값 전달 패턴이므로 허용한다.",
365
+ "bad": [
366
+ {
367
+ "code": "<div style=\"padding: 16px; color: #333\">",
368
+ "note": "일반 인라인 스타일"
369
+ }
370
+ ],
371
+ "good": [
372
+ {
373
+ "code": "<div style=\"--card-ratio: 16/9\">",
374
+ "note": "CSS 커스텀 프로퍼티만"
375
+ },
376
+ {
377
+ "code": "<div class=\"card card--featured\">",
378
+ "note": "클래스로 스타일 적용"
379
+ }
380
+ ]
381
+ },
382
+ {
383
+ "id": "R-08",
384
+ "category": "html",
385
+ "summary": "HTML 클래스에도 BEM 2단계 element 금지 (R-05 연동)",
386
+ "severity": "warning",
387
+ "enforcement": [
388
+ "check-violations.js"
389
+ ],
390
+ "lang": "html",
391
+ "rationale": "R-05와 동일 원칙. HTML 클래스가 CSS에서 정의된 것과 일치해야 한다.",
392
+ "bad": [
393
+ {
394
+ "code": "<div class=\"card__header__title\">",
395
+ "note": "2단계 element"
396
+ }
397
+ ],
398
+ "good": [
399
+ {
400
+ "code": "<div class=\"card__title\">",
401
+ "note": "평탄화"
402
+ }
403
+ ]
404
+ },
405
+ {
406
+ "id": "R-09",
407
+ "category": "html",
408
+ "summary": "img alt 속성 필수",
409
+ "severity": "error",
410
+ "enforcement": [
411
+ "check-violations.js"
412
+ ],
413
+ "lang": "html",
414
+ "rationale": "스크린 리더 사용자에게 이미지 정보를 전달한다. 장식용 이미지는 alt=\"\"로 빈 값을 명시해 스크린 리더가 건너뛰게 한다. WCAG 1.1.1 비텍스트 콘텐츠 (A등급).",
415
+ "bad": [
416
+ {
417
+ "code": "<img src=\"photo.jpg\">",
418
+ "note": "alt 없음"
419
+ }
420
+ ],
421
+ "good": [
422
+ {
423
+ "code": "<img src=\"photo.jpg\" alt=\"제품 상세 이미지\">",
424
+ "note": "의미 있는 alt"
425
+ },
426
+ {
427
+ "code": "<img src=\"deco.svg\" alt=\"\">",
428
+ "note": "장식용: 빈 alt 명시"
429
+ }
430
+ ]
431
+ },
432
+ {
433
+ "id": "R-10",
434
+ "category": "html",
435
+ "summary": "인터랙티브 요소는 시맨틱 HTML 사용 — div onclick 금지",
436
+ "severity": "error",
437
+ "enforcement": [
438
+ "check-violations.js"
439
+ ],
440
+ "lang": "html",
441
+ "rationale": "div에 onclick을 붙이면 키보드 포커스가 안 되고 스크린 리더가 역할을 인식하지 못한다. 시맨틱 요소는 키보드·스크린 리더 지원이 내장되어 있다.",
442
+ "bad": [
443
+ {
444
+ "code": "<div onclick=\"fn()\">클릭</div>",
445
+ "note": "키보드 접근 불가"
446
+ }
447
+ ],
448
+ "good": [
449
+ {
450
+ "code": "<button type=\"button\" onclick=\"fn()\">클릭</button>",
451
+ "note": "키보드·스크린 리더 지원"
452
+ }
453
+ ]
454
+ },
455
+ {
456
+ "id": "R-11",
457
+ "category": "a11y",
458
+ "summary": "포커스 스타일 필수 — :focus { outline: none } 금지",
459
+ "severity": "error",
460
+ "enforcement": [
461
+ "check-violations.js"
462
+ ],
463
+ "rationale": "키보드만 사용하는 사용자가 현재 포커스 위치를 파악할 수 없다. WCAG 2.4.7 Focus Visible (AA). KRDS는 4px 외곽선 + offset을 reset.css에서 전역 관리하므로 컴포넌트에서 제거 금지.",
464
+ "bad": [
465
+ {
466
+ "code": ":focus { outline: none; }",
467
+ "note": "포커스 제거 — 절대 금지"
468
+ },
469
+ {
470
+ "code": "*:focus { outline: 0; }",
471
+ "note": "전역 포커스 제거 — 절대 금지"
472
+ }
473
+ ],
474
+ "good": [
475
+ {
476
+ "code": ":focus-visible {\n outline: 4px solid var(--color-primary);\n outline-offset: 2px;\n}",
477
+ "note": "마우스 클릭은 포커스 표시 안 함, 키보드는 표시"
478
+ }
479
+ ]
480
+ },
481
+ {
482
+ "id": "R-12",
483
+ "category": "a11y",
484
+ "summary": "색상 대비 — 일반 텍스트 4.5:1 이상, 큰 텍스트 3:1 이상",
485
+ "severity": "error",
486
+ "enforcement": [
487
+ "pa11y-ci"
488
+ ],
489
+ "rationale": "시력이 약한 사용자와 밝은 환경에서도 콘텐츠를 인식할 수 있어야 한다. WCAG 1.4.3 Contrast Minimum (AA). 큰 텍스트 기준: 24px 이상 또는 18.67px bold 이상. INFOUX 토큰은 AA 통과 조합으로 설계되어 있으므로 임의 색상 대신 시맨틱 토큰을 사용한다.",
490
+ "bad": [
491
+ {
492
+ "code": "color: #b1b8be; /* 흰 배경 대비 2.7:1 */",
493
+ "note": "하드코딩 + 대비 부족"
494
+ }
495
+ ],
496
+ "good": [
497
+ {
498
+ "code": "color: var(--color-text-secondary); /* KRDS gray-700 — 흰 배경 대비 5.7:1 */",
499
+ "note": "AA 기준 통과"
500
+ }
501
+ ]
502
+ },
503
+ {
504
+ "id": "R-13",
505
+ "category": "a11y",
506
+ "summary": "터치/클릭 영역 최소 44×44px",
507
+ "severity": "error",
508
+ "enforcement": [
509
+ "manual"
510
+ ],
511
+ "rationale": "손가락으로 정확히 터치하기 어려운 작은 버튼은 오작동을 유발한다. 시각적 크기는 작더라도 padding으로 터치 영역을 확보한다. WCAG 2.5.5 Target Size (AAA) / iOS·Android HIG 권장. KRDS 모바일 권장 사이즈는 medium(48px) 이상이다.",
512
+ "bad": [
513
+ {
514
+ "code": ".icon-btn {\n width: 24px;\n height: 24px;\n}",
515
+ "note": "터치 영역 너무 작음"
516
+ }
517
+ ],
518
+ "good": [
519
+ {
520
+ "code": ".icon-btn {\n width: 24px;\n height: 24px;\n padding: 1.6rem; /* 터치 영역 ≥ 44px */\n}",
521
+ "note": "padding으로 터치 영역 확보"
522
+ }
523
+ ]
524
+ },
525
+ {
526
+ "id": "R-14",
527
+ "category": "a11y",
528
+ "summary": "건너뛰기 링크 필수 — .skip-to-content",
529
+ "severity": "error",
530
+ "enforcement": [
531
+ "check-violations.js"
532
+ ],
533
+ "lang": "html",
534
+ "rationale": "키보드 사용자가 반복 네비게이션을 건너뛰고 본문으로 바로 이동할 수 있어야 한다. WCAG 2.4.1 Bypass Blocks (A).",
535
+ "bad": [
536
+ {
537
+ "code": "<!-- 건너뛰기 링크 없음 -->",
538
+ "note": "키보드 사용자는 nav를 전부 탭해야 함"
539
+ }
540
+ ],
541
+ "good": [
542
+ {
543
+ "code": "<a href=\"#main\" class=\"skip-to-content\">본문 바로가기</a>",
544
+ "note": "body 최상단에 배치"
545
+ }
546
+ ]
547
+ },
548
+ {
549
+ "id": "R-15",
550
+ "category": "html",
551
+ "summary": "HTML 기본 구조는 기존 인포마인드 사이트 패턴을 우선 유지한다",
552
+ "severity": "error",
553
+ "enforcement": [
554
+ "check-html-structure.js"
555
+ ],
556
+ "lang": "html",
557
+ "rationale": "인포마인드가 기존에 구축·유지보수한 사이트들은 공통 HTML 골격, 영역 네이밍, 헤더/푸터/본문 구성 패턴을 갖고 있다. 신규 작업도 이 패턴을 우선 유지해 유지보수성과 팀 내 이해도를 확보한다. page shell은 header#header, main#main, footer#footer를 기본으로 하고, HTML 컴포넌트화는 main 안의 section 단위로 분리한다. 각 section은 .container와 접근 가능한 이름을 가진다. KRDS는 root 태그 강제가 아니라 시맨틱 HTML, 접근성 속성, 키보드 동작을 보강하는 기준으로 적용한다.",
558
+ "refs": [
559
+ "기존 인포마인드 구축 사이트 HTML",
560
+ "references/html-semantics.md",
561
+ "references/krds-components.md"
562
+ ],
563
+ "bad": [
564
+ {
565
+ "code": "<div onclick=\"goPage()\">이동</div>",
566
+ "note": "기존 골격 유지와 무관하게 인터랙션을 div에 직접 부여하는 것은 금지"
567
+ },
568
+ {
569
+ "code": "<div class=\"modal\">...</div>",
570
+ "note": "dialog 역할/상태 속성 없이 모달을 구성"
571
+ },
572
+ {
573
+ "code": "<div id=\"content\"><div class=\"header\">...</div><div class=\"footer\">...</div></div>",
574
+ "note": "기존 사이트 골격과 다른 임의 구조"
575
+ }
576
+ ],
577
+ "good": [
578
+ {
579
+ "code": "<a href=\"#main\" class=\"skip-to-content\">본문 바로가기</a>\n<header id=\"header\"><div class=\"container\">...</div></header>\n<main id=\"main\">\n <section class=\"section section--content\" aria-labelledby=\"section-title\">\n <div class=\"container\">\n <h1 id=\"section-title\">페이지 제목</h1>\n ...\n </div>\n </section>\n</main>\n<footer id=\"footer\"><div class=\"container\">...</div></footer>",
580
+ "note": "큰 영역은 단순하게 잡고 main 안은 section 단위, 각 section 안의 .container와 제목이 구조 담당"
581
+ },
582
+ {
583
+ "code": "<div class=\"modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"modal-title\">...</div>",
584
+ "note": "기존 마크업 패턴 위에 접근성 속성 보강"
585
+ },
586
+ {
587
+ "code": "<nav class=\"breadcrumb\" aria-label=\"페이지 경로\"><ol>...</ol></nav>",
588
+ "note": "기존 패턴이 없거나 접근성 개선이 필요한 경우 시맨틱 태그 사용"
589
+ }
590
+ ]
591
+ },
592
+ {
593
+ "id": "R-16",
594
+ "category": "a11y",
595
+ "summary": "인터랙티브 컴포넌트는 필수 ARIA 속성을 누락할 수 없다",
596
+ "severity": "error",
597
+ "enforcement": [
598
+ "check-html-structure.js"
599
+ ],
600
+ "lang": "html",
601
+ "rationale": "modal/tab/accordion/tooltip/disclosure/carousel/calendar 등 위젯 컴포넌트는 스크린리더가 의미를 파악할 수 있도록 ARIA로 상태·관계·역할을 명시해야 한다. 네이티브 시맨틱만으로 부족한 영역이며, KRDS와 WAI-ARIA 1.2 APG가 정의한 패턴을 따른다. KWCAG 1.3.1 / 4.1.2.",
602
+ "refs": [
603
+ "references/html-semantics.md",
604
+ "references/accessibility.md"
605
+ ],
606
+ "bad": [
607
+ {
608
+ "code": "<button class=\"disclosure\">자세히</button>\n<div class=\"disclosure__panel\">...</div>",
609
+ "note": "aria-expanded, aria-controls 누락"
610
+ },
611
+ {
612
+ "code": "<div role=\"dialog\" class=\"modal\">...</div>",
613
+ "note": "aria-modal과 aria-labelledby 누락"
614
+ },
615
+ {
616
+ "code": "<button class=\"tab__item\">탭1</button>",
617
+ "note": "role=\"tab\", aria-selected, aria-controls 누락"
618
+ }
619
+ ],
620
+ "good": [
621
+ {
622
+ "code": "<button class=\"disclosure\" aria-expanded=\"false\" aria-controls=\"more\">자세히</button>\n<div id=\"more\" class=\"disclosure__panel\" hidden>...</div>",
623
+ "note": "트리거-패널 ARIA 연결"
624
+ },
625
+ {
626
+ "code": "<div role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"modal-title\">...</div>",
627
+ "note": "다이얼로그 3종 세트"
628
+ },
629
+ {
630
+ "code": "<button role=\"tab\" id=\"tab-1\" aria-selected=\"true\" aria-controls=\"panel-1\">탭1</button>",
631
+ "note": "탭 ARIA 4종 세트"
632
+ }
633
+ ]
634
+ },
635
+ {
636
+ "id": "R-17",
637
+ "category": "bem",
638
+ "summary": "상태는 BEM modifier로만 표현 — .is-* / .has-* 비-BEM 상태 클래스 금지",
639
+ "severity": "error",
640
+ "enforcement": [
641
+ "check-html-structure.js"
642
+ ],
643
+ "lang": "html",
644
+ "rationale": "BEM의 modifier가 상태 표현의 표준이며, `.is-active` 같은 비-BEM 클래스는 어느 컴포넌트의 상태인지 모호하다. ARIA 속성(`aria-selected`, `aria-expanded`)이 의미 표현을 담당하고, 시각 변화는 BEM modifier(`.tab__item--selected`)가 담당한다.",
645
+ "refs": [
646
+ "references/html-semantics.md#62-상태-표현-r-17"
647
+ ],
648
+ "bad": [
649
+ {
650
+ "code": "<button class=\"btn is-disabled\">",
651
+ "note": ".is-disabled — 비-BEM 상태 클래스"
652
+ },
653
+ {
654
+ "code": "<div class=\"modal is-open\">",
655
+ "note": ".is-open — 비-BEM 상태 클래스"
656
+ },
657
+ {
658
+ "code": "<input class=\"input has-error\">",
659
+ "note": ".has-error — 비-BEM 상태 클래스"
660
+ }
661
+ ],
662
+ "good": [
663
+ {
664
+ "code": "<button class=\"btn\" disabled aria-disabled=\"true\">",
665
+ "note": "네이티브 속성 + ARIA"
666
+ },
667
+ {
668
+ "code": "<div class=\"modal modal--open\" aria-modal=\"true\">",
669
+ "note": "BEM modifier + ARIA"
670
+ },
671
+ {
672
+ "code": "<input class=\"input input--error\" aria-invalid=\"true\">",
673
+ "note": "BEM modifier + ARIA"
674
+ }
675
+ ]
676
+ },
677
+ {
678
+ "id": "R-18",
679
+ "category": "bem",
680
+ "summary": "modifier 이름은 의미적이어야 함 — 시각적 단어 금지",
681
+ "severity": "error",
682
+ "enforcement": [
683
+ "check-violations.js",
684
+ "check-html-structure.js"
685
+ ],
686
+ "lang": "html",
687
+ "rationale": "`.btn--blue`나 `.card--big` 같은 시각적 modifier는 의미가 시각 표현과 묶여 디자인 변경 시 클래스명도 같이 바꿔야 한다. 의미적 이름(`--primary`, `--large`)을 쓰면 색상·크기가 바뀌어도 클래스는 그대로 유지된다. KRDS 정의 variant/size 어휘만 허용. R-06의 자동화 버전.",
688
+ "refs": [
689
+ "references/html-semantics.md#63-modifier-의미성-r-06-r-18",
690
+ "references/krds-components.md"
691
+ ],
692
+ "bad": [
693
+ {
694
+ "code": "<button class=\"btn btn--blue\">",
695
+ "note": "색을 직접 명시한 modifier"
696
+ },
697
+ {
698
+ "code": "<div class=\"card card--big\">",
699
+ "note": "크기를 시각 단어로"
700
+ },
701
+ {
702
+ "code": "<span class=\"tag tag--rounded\">",
703
+ "note": "장식 속성을 modifier로"
704
+ }
705
+ ],
706
+ "good": [
707
+ {
708
+ "code": "<button class=\"btn btn--primary\">",
709
+ "note": "KRDS variant — 의미 기반"
710
+ },
711
+ {
712
+ "code": "<div class=\"card card--large\">",
713
+ "note": "KRDS 사이즈 스케일"
714
+ },
715
+ {
716
+ "code": "<span class=\"tag tag--info\">",
717
+ "note": "톤 — 의미 기반"
718
+ }
719
+ ]
720
+ },
721
+ {
722
+ "id": "R-21",
723
+ "category": "a11y",
724
+ "summary": "html lang 속성 필수 — 문서 기본 언어 명시",
725
+ "severity": "error",
726
+ "enforcement": [
727
+ "check-html-structure.js"
728
+ ],
729
+ "lang": "html",
730
+ "rationale": "스크린리더가 올바른 발음·언어 엔진을 선택하려면 <html>에 문서 기본 언어가 명시되어야 한다. WCAG 3.1.1 Language of Page (A) / KWCAG. 한국어 페이지는 lang=\"ko\"를 사용한다.",
731
+ "refs": [
732
+ "references/accessibility.md"
733
+ ],
734
+ "bad": [
735
+ {
736
+ "code": "<html>",
737
+ "note": "lang 속성 없음 — 문서 언어 미정"
738
+ },
739
+ {
740
+ "code": "<html class=\"no-js\">",
741
+ "note": "lang 누락"
742
+ }
743
+ ],
744
+ "good": [
745
+ {
746
+ "code": "<html lang=\"ko\">",
747
+ "note": "한국어 문서 언어 명시"
748
+ }
749
+ ]
750
+ },
751
+ {
752
+ "id": "R-22",
753
+ "category": "a11y",
754
+ "summary": "모션 최소화 선호 존중 — prefers-reduced-motion 가드",
755
+ "severity": "warning",
756
+ "enforcement": [
757
+ "check-violations.js",
758
+ "manual"
759
+ ],
760
+ "lang": "css",
761
+ "rationale": "전정기관 장애·멀미 민감 사용자를 위해 큰 이동·확대·회전·자동재생 애니메이션은 @media (prefers-reduced-motion: reduce)에서 축소·정지해야 한다. WCAG 2.3.3 Animation from Interactions (AAA) / 2.2.2 Pause, Stop, Hide (A). check-violations.js가 animation·@keyframes 사용 파일의 가드 누락을 경고로 자동 검출하며, 실제 모션 강도의 적절성은 수동 QA로 확인한다.",
762
+ "refs": [
763
+ "site/guides/css-animation.md",
764
+ "references/accessibility.md"
765
+ ],
766
+ "bad": [
767
+ {
768
+ "code": ".carousel__track { animation: slide 5s infinite; }",
769
+ "note": "reduced-motion 가드 없는 자동 애니메이션"
770
+ }
771
+ ],
772
+ "good": [
773
+ {
774
+ "code": "@media (prefers-reduced-motion: reduce) {\n .carousel__track { animation: none; }\n}",
775
+ "note": "모션 최소화 선호 시 정지"
776
+ }
777
+ ]
778
+ }
779
+ ]
780
+ }