feat(skill): absorb external design skills, restore interview gate, add review route

- Restore the step-0 interview as a mechanical gate the skill explicitly
  depends on; add harness.md (per-harness question tools, limits,
  fallbacks) and brief-interview.md (slots, question cards, rounds).
- Add 10 reference docs absorbed from external design skills
  (accessibility, interaction-feel, elevation, color, icons, product-copy,
  component-systems, critique, change-review, print-email) and extend
  existing references.
- Add a review-only route and two hard-gate clauses (truncated content
  reachability, three-flashes limit).
- design-gate: split tap targets into WCAG 2.5.8 and 44px contract layers,
  run axe-core when available, and fix false positives found on a real
  site (decorative alt="", stacked wordmark line count, url-only pages).
- lint-skill: fail if the interview gate section or its links disappear.
- Ship agents/openai.yaml and THIRD_PARTY_NOTICES.md.
This commit is contained in:
Yun Chan 2026-09-24 13:26:03 +09:00
parent 79e79c120b
commit 6805fb2be7
37 changed files with 5688 additions and 128 deletions

View file

@ -0,0 +1,233 @@
# print-email — 인쇄와 이메일
브리프가 인쇄 가능한 문서(출력·PDF 저장·서명용 서류)나 이메일(뉴스레터·트랜잭션 메일·초대장)을 명시할 때만 연다. 둘 중 하나만 필요하면 해당 절만 읽는다.
이 문서는 `SKILL.md` 4-5(서류로서의 완성)의 인쇄 규칙을 일반화한다. 4-5는 프리셋이 서류·원장·콘솔 계열(장부, 시간표, 관리 화면)일 때만 도는 좁은 규칙이고, 여기서는 **그 밖의 어떤 화면이든 브리프가 인쇄를 요구하면** 적용할 수 있게 넓힌다. 4-5의 규칙(모달이 열려 있으면 그 서류만, `print-color-adjust: exact`)과 모순되지 않으며, 겹치는 부분은 새로 쓰지 않고 그대로 가져와 쓴다.
## 이 문서를 읽는 법
| 상황 | 읽을 곳 |
|---|---|
| 페이지를 인쇄·PDF 저장 가능하게 만든다 | §1 인쇄 |
| 인쇄 결과가 맞는지 확인한다 | §2 인쇄 검증 |
| 뉴스레터·트랜잭션 메일·초대장을 만든다 | §3 이메일 |
| 이메일 클라이언트에서 실제로 확인한다 | §4 이메일 검증 |
| 출시 전 한 번에 훑는다 | §5 체크리스트 |
---
## 0. 언제 여는가
- 브리프에 "인쇄", "PDF로 저장", "출력해서 제출", "서명 후 인쇄" 같은 말이 있으면 §1·§2.
- 브리프에 "뉴스레터", "이메일로 보낸다", "가입 확인 메일", "영수증 메일", "초대 메일" 같은 말이 있으면 §3·§4.
- 둘 다 아니면 이 문서는 읽지 않는다. 모든 웹 페이지를 기본적으로 인쇄·이메일 대응시키지 않는다 — 그것은 이 문서가 조건부로 존재하는 이유 자체를 없앤다.
---
## 1. 인쇄
언제: 브리프가 인쇄 가능한 문서를 요구할 때. 프리셋이 서류·원장·콘솔이면 `SKILL.md` 4-5를 먼저 따르고, 이 절은 그 규칙이 없는 일반 페이지(랜딩, 포트폴리오, 안내문 등)에 인쇄 대응을 추가할 때 쓴다.
### 1-1. `@media print` 뼈대
**프로젝트 계약**: 인쇄가 요구되면 `@media print` 블록으로 화면과 서류를 분리한다. 화면에서 필요한 앱 크롬(내비, 툴바, 플로팅 버튼, 사이드바)은 인쇄에서 의미가 없다 — 사용자는 종이 위에서 그것을 누를 수 없다.
```css
@media print {
header.app-nav,
[role="toolbar"],
.floating-action,
.cookie-banner,
button:not(.print-keep) {
display: none !important;
}
}
```
**프로젝트 계약**: 모달이 열린 채로 인쇄가 시작되면 배경의 앱 화면이 아니라 **그 모달 내용만** 서류가 되어야 한다. `SKILL.md` 4-5의 기존 규칙을 그대로 쓴다.
```css
@media print {
body:has(dialog[open]) > *:not(:has(dialog[open])) {
display: none !important;
}
}
```
**관찰 후보(시작값)**: `@page`의 여백은 브라우저 기본값에서 시작한다. 제본이나 클립이 필요한 서류만 여백을 늘린다. 정확히 몇 cm가 맞는지는 프로젝트마다 다르므로 숫자를 단정하지 않고, 실제 출력물로 확인한다(§2).
```css
@page {
margin: 2cm 1.5cm; /* 제본 쪽에 더 필요하면 margin-left 만 늘린다 — 실측 조정 */
}
```
### 1-2. 콘텐츠를 서류에 맞게 바꾼다
**프로젝트 계약**: 카드·표 행·영수증 구획처럼 한 덩어리로 읽혀야 하는 블록은 페이지 경계에서 잘리지 않게 한다.
```css
@media print {
.card,
tr,
.receipt-line,
figure {
break-inside: avoid;
}
h2, h3 {
break-after: avoid; /* 제목만 페이지 끝에 남고 본문이 다음 장으로 넘어가는 것을 막는다 */
}
}
```
**프로젝트 계약**: 상태색처럼 의미를 나르는 색(경고·성공·실패 배지 등)만 `print-color-adjust: exact`로 보존한다. 나머지는 브라우저의 기본 인쇄 설정(그레이스케일·절약 모드 포함)에 맡긴다. 전체를 강제 컬러로 찍으면 사용자의 잉크 절약 설정을 무시하게 된다.
```css
@media print {
.status-badge,
.status-dot {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
}
```
**하드 게이트**: 화면에서 잘라 보여주던 것(짧은 링크 텍스트, 접힌 섹션)은 종이 위에서 클릭이나 hover로 나머지에 닿을 수 없다. [typography.md](typography.md) §13의 "잘린 텍스트 — 전체 값에 도달하는 수단" 원칙과 같은 이유로, 인쇄에서는 그 값 자체를 펼쳐 보여줘야 정보 손실이 없다.
```css
@media print {
a[href^="http"]:not([href*="#"])::after {
content: " (" attr(href) ")";
word-break: break-all;
}
}
```
닫힌 `<details>`의 내용은 브라우저가 내부 슬롯으로 숨기므로, 자식 요소에 `display`를 주는 CSS로는 확실히 펼쳐지지 않는다. 인쇄 직전에 `open`을 켜고 끝나면 되돌린다.
```js
let reopened = [];
addEventListener('beforeprint', () => {
reopened = [...document.querySelectorAll('details:not([open])')];
reopened.forEach((d) => { d.open = true; });
});
addEventListener('afterprint', () => {
reopened.forEach((d) => { d.open = false; });
reopened = [];
});
```
### 1-3. 서류의 신원 — 머리말·바닥말·출력일
**관찰 후보**: 여러 장으로 인쇄될 문서(보고서, 명세서)는 페이지 번호와 문서 제목을 각 장에 남긴다. 독자가 순서를 잃어버리지 않게 하는 목적이므로, 한 장짜리 인쇄물(영수증, 초대장)에는 강제하지 않는다.
```css
@media print {
@page {
@bottom-right { content: counter(page) " / " counter(pages); }
}
.print-footer::after {
content: "출력일 " attr(data-print-date);
}
}
```
`@page` 안의 여백 상자(`@bottom-right` 등)는 브라우저 지원이 고르지 않으므로 점진적 향상으로 둔다 — 지원하지 않는 브라우저에서는 페이지 번호가 빠질 뿐이다. 페이지 번호가 계약상 반드시 필요하면 인쇄 미리보기에서 대상 브라우저로 확인하고, 안 되면 PDF 생성 도구 쪽에서 넣는다. `attr(data-print-date)`는 CSS만으로 오늘 날짜를 넣을 수 없으므로, 인쇄 전에 JS로 `document.querySelector('.print-footer').dataset.printDate = new Date().toLocaleDateString('ko-KR')` 같은 식으로 채워 둔다.
---
## 2. 인쇄 검증
인쇄는 화면 렌더링과 다른 엔진 경로를 타기 때문에, 화면에서 괜찮아 보인다고 인쇄도 괜찮다고 결론 내리지 않는다.
1. **인쇄 미리보기를 캡처한다.** 브라우저의 인쇄 미리보기(Ctrl/Cmd+P)를 열어 각 페이지를 실제로 넘겨 본다. 브라우저 자동화 도구가 있으면 인쇄 미디어를 에뮬레이트해 스크린샷으로 남긴다 — 쓸 수 있는 도구는 [harness.md](harness.md)를 따른다.
2. **PDF로 저장해 연다.** "PDF로 저장"으로 실제 파일을 만들고 별도 뷰어로 열어, 브라우저 미리보기와 최종 PDF가 다르게 나오는 경우(특히 폰트·배경색)가 없는지 확인한다.
3. **경계에서 확인할 것**: 카드·표·영수증 구획이 페이지 경계에서 잘리지 않는지, 앱 크롬이 실제로 사라졌는지, 모달만 인쇄될 상황에서 배경 화면이 섞여 나오지 않는지, 링크 URL과 펼친 `details`가 실제로 종이에 보이는지, 상태색이 흑백 인쇄에서도 배지 모양(테두리·아이콘)만으로 구분되는지(색만으로 구분되면 흑백에서 정보가 사라진다).
4. **확인하지 못했으면 "미검증"이라고 적는다.** 소스만 보고 통과로 추정하지 않는다 — `audit-gate.md`의 미검증 관례를 그대로 따른다.
---
## 3. 이메일
언제: 브리프가 뉴스레터, 트랜잭션 메일(가입 확인·영수증·비밀번호 재설정), 초대장처럼 **이메일 클라이언트 안에서 열리는 HTML**을 명시할 때. 웹사이트에 "문의하기 → 메일 발송" 버튼이 있는 정도는 해당하지 않는다.
이메일 클라이언트는 웹 브라우저가 아니다. **관찰 후보 — 이 문서에서 실측하지 않았다.** 일부 클라이언트는 데스크톱 문서 편집기의 렌더링 경로를 그대로 쓰거나, 외부 스타일시트·최신 레이아웃 CSS를 지원하지 않거나 부분적으로만 지원한다는 관찰이 있다. 정확히 어떤 클라이언트가 무엇을 지원하는지는 이 문서에서 확인하지 않았으므로 클라이언트별 지원 수치를 단정하지 않는다. 아래는 그런 제약을 가정했을 때 안전한 기본값이며, 실제 확인은 §4를 따른다.
### 3-1. 레이아웃
**프로젝트 계약**: 폭은 600px 안팎의 단일 컬럼을 기본값으로 한다. 화면이 넓어도 이메일 본문 폭을 늘리지 않는다 — 클라이언트의 미리보기 창, 모바일 메일 앱 폭이 그보다 좁은 경우가 흔하다[SKILL-IMPECCABLE].
**프로젝트 계약**: 레이아웃은 표(`<table>`) 기반으로 짜고, 스타일은 `<style>` 블록이 아니라 각 태그의 `style` 속성에 인라인으로 넣는다. `display: flex`나 `grid` 같은 최신 레이아웃 CSS, 외부 스타일시트 링크는 이메일에서 기대한 대로 렌더링되지 않을 수 있다는 것이 근거다[SKILL-IMPECCABLE].
```html
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="margin:0 auto; max-width:600px;">
<tr>
<td style="padding:24px; font-family:Arial, sans-serif; font-size:16px; line-height:1.5; color:#1a1a1a;">
본문 내용
</td>
</tr>
</table>
```
### 3-2. 인터랙션
**프로젝트 계약**: 행동 유도는 텍스트 링크가 아니라 버튼처럼 보이는 요소로 만든다. 배경색을 채운 `<a>`를 표 셀 안에 넣는 방식이 흔한 구현이다[SKILL-IMPECCABLE](`<button>` 태그 자체는 이메일 클라이언트에서 일관되게 렌더링되지 않는 경우가 있어 피한다 — **관찰 후보, 이 문서에서 실측하지 않았다**).
```html
<a href="https://example.com/confirm"
style="display:inline-block; padding:12px 24px; background:#1a56db; color:#ffffff;
text-decoration:none; border-radius:6px; font-weight:600;">
가입 확인하기
</a>
```
**하드 게이트**: 대부분의 이메일 클라이언트는 hover 상태 자체가 없다(모바일 메일 앱, 미리보기 창). 핵심 동작(다음 단계로 가는 CTA, 정보 확인)이 hover에만 반응하도록 만들면 애초에 작동하지 않는다. 추가 정보나 보조 동작이 hover에 의존해서도 안 된다 — 항상 보이는 상태로 둔다[SKILL-IMPECCABLE].
**프로젝트 계약**: 폼 제출, 다단계 흐름, 필터처럼 복잡한 상호작용은 이메일 안에서 구현하지 않는다. 버튼으로 웹의 해당 화면에 딥링크한다[SKILL-IMPECCABLE].
### 3-3. 이미지가 막혀도, 어두운 테마에서도 읽힌다
**프로젝트 계약**: 많은 이메일 클라이언트가 기본적으로 이미지를 차단한 채 미리보기를 띄운다. 로고·CTA·핵심 문구를 이미지에만 담으면 이미지가 막힌 순간 그 내용이 통째로 사라진다. 핵심 텍스트(제목, 본문, 버튼 라벨)는 실제 HTML 텍스트로 쓰고, 이미지에는 내용을 설명하는 `alt`를 붙이며, 이미지가 있어야 할 자리에는 배경색을 지정해 이미지가 빠져도 빈 흰 칸이 아니라 브랜드 색 영역으로 보이게 한다.
```html
<img src="logo.png" alt="회사 로고" width="120"
style="display:block; background-color:#f3f4f6;">
```
**관찰 후보**: 일부 클라이언트는 사용자가 어두운 테마를 켰을 때 메일 배경·글자색을 자동으로 반전하거나 재조정한다. 이 동작은 클라이언트마다 다르고 이 문서에서 실측하지 않았으므로 어떤 클라이언트가 어떻게 반전하는지는 단정하지 않는다. 실무적으로 안전한 방향은 배경색을 투명에 맡기지 않고 컨테이너마다 명시적으로 지정하는 것, 로고처럼 투명 배경을 가정한 자산을 흰 배경 전용으로 만들지 않는 것이다. 실제 확인은 §4를 따른다.
---
## 4. 이메일 검증
브라우저 렌더링과 실제 이메일 클라이언트 렌더링은 별개다. 로컬 브라우저에서 HTML 파일을 열어 봤다고 이메일에서도 같다고 보고하지 않는다.
1. **실제 클라이언트로 확인한 경우만 "검증됨"으로 보고한다.** 예를 들어 실제 계정으로 지메일·아웃룩·애플 메일 등에 발송해 열어 보고, 폭·표 레이아웃·버튼·이미지 차단 상태·어두운 테마에서 각각 확인한다.
2. **확인하지 못했으면 "미검증"이라고 명시한다.** 코드가 규칙(§3)을 따랐다는 것과 실제 클라이언트에서 그대로 보인다는 것은 다른 주장이다. "표 레이아웃과 인라인 CSS를 썼으니 호환될 것이다"는 추정이지 검증이 아니다.
3. **클라이언트별 지원 수치·점유율을 지어내지 않는다.** 이 스킬은 "아웃룩이 몇 퍼센트를 지원한다" 같은 구체 수치를 확인하지 않았다. 필요하면 사용자에게 발송 대상 클라이언트 범위를 물어 §3의 어떤 항목을 더 엄격하게 지킬지 좁힌다.
---
## 5. 체크리스트
**인쇄**
- [ ] `@media print`로 앱 크롬(내비·툴바·플로팅 버튼)을 숨겼다
- [ ] 모달이 열린 채 인쇄되면 그 모달만 서류가 된다
- [ ] 카드·표 행이 페이지 경계에서 잘리지 않는다(`break-inside: avoid`)
- [ ] 상태색은 `print-color-adjust: exact`로 보존하되, 색만으로 상태를 구분하지 않는다(흑백에서도 구분 가능)
- [ ] 짧은 링크 텍스트가 전체 URL로 펼쳐진다
- [ ] 접힌 섹션(`details` 등)이 인쇄에서는 펼쳐진다
- [ ] 여러 장 문서면 페이지 번호·문서 제목이 있다
- [ ] 인쇄 미리보기와 PDF 출력을 실제로 열어 확인했다(§2). 못 했으면 미검증이라고 적었다
**이메일**
- [ ] 폭 600px 안팎, 단일 컬럼
- [ ] 표 기반 레이아웃, 인라인 CSS
- [ ] CTA가 버튼처럼 보이는 요소다(텍스트 링크가 아니다)
- [ ] 핵심 동작·정보가 hover에 의존하지 않는다
- [ ] 복잡한 상호작용은 웹으로 딥링크했다
- [ ] 이미지가 막혀도 핵심 텍스트가 읽히고, `alt`와 배경색을 지정했다
- [ ] 컨테이너 배경색을 명시해 다크모드 반전에 대비했다
- [ ] 실제 클라이언트에서 확인했다(§4). 못 했으면 미검증이라고 적었다