designpaca/packages/skill/references/print-email.md
Yun Chan 88c11682b1 feat(skill): route fixes, delivery report, composition diversity, platform refresh
- route table: extension route for products with an existing visual
  language, feedback row, derived state tokens stay local
- completion: blocking 0 plus reported unverified; hard-gate surface is
  the touched scope; delivery report starts with what and how to open
- design.md created at step 0 with a skeleton, fingerprint line and
  change log; product-surface rubric reading; direction delegation
  states the chosen card and reason
- layout: hero families, nav forms, footer forms; art-direction page
  convergence 6 axes; genre sections; image treatments; placeholder
  conventions; multi-screen order; Tailwind theme mapping
- platform: scroll-driven examples, anchor positioning, canvas OT,
  three/R3F pins, shadcn style prefixes, Safari 26 notes, subgrid
- design-gate: four result states, exit codes, cwd module resolution,
  --url/--out-dir/--strict, oklch contrast, dead selector word match
- Korean font budget split from the Latin baseline
2026-10-06 16:55:38 +09:00

20 KiB

print-email — 인쇄와 이메일

브리프가 인쇄 가능한 문서(출력·PDF 저장·서명용 서류)나 이메일(뉴스레터·트랜잭션 메일·초대장)을 명시할 때만 연다. 둘 중 하나만 필요하면 해당 절만 읽는다.

이 문서는 SKILL.md 4-5(서류로서의 완성)의 인쇄 규칙을 일반화한다. 4-5는 프리셋이 서류·원장·콘솔 계열(장부, 시간표, 관리 화면)일 때만 도는 좁은 규칙이고, 여기서는 그 밖의 어떤 화면이든 브리프가 인쇄를 요구하면 적용할 수 있게 넓힌다. 4-5의 규칙(모달이 열려 있으면 그 서류만, print-color-adjust: exact)과 모순되지 않으며, 겹치는 부분은 새로 쓰지 않고 그대로 가져와 쓴다.

이 문서를 읽는 법

상황 읽을 곳
페이지를 인쇄·PDF 저장 가능하게 만든다 §1 인쇄
인쇄 결과가 맞는지 확인한다 §2 인쇄 검증
뉴스레터·트랜잭션 메일·초대장을 만든다 §3 이메일 (스타일 두 층은 §3-1)
받은편지함 표시·프리헤더·크기 한도·텍스트 대체본 §3-2-b
광고성(마케팅) 메일의 "(광고)"·수신거부 표기 §3-2-c — 한국 수신자면 하드 게이트
이메일 클라이언트에서 실제로 확인한다 §4 이메일 검증
출시 전 한 번에 훑는다 §5 자기 점검. 납품 판정은 preflight.md X11·X12

0. 언제 여는가

  • 브리프에 "인쇄", "PDF로 저장", "출력해서 제출", "서명 후 인쇄" 같은 말이 있으면 §1·§2.
  • 브리프에 "뉴스레터", "이메일로 보낸다", "가입 확인 메일", "영수증 메일", "초대 메일" 같은 말이 있으면 §3·§4.
  • 둘 다 아니면 이 문서는 읽지 않는다. 모든 웹 페이지를 기본적으로 인쇄·이메일 대응시키지 않는다 — 그것은 이 문서가 조건부로 존재하는 이유 자체를 없앤다.

1. 인쇄

언제: 브리프가 인쇄 가능한 문서를 요구할 때. 프리셋이 서류·원장·콘솔이면 SKILL.md 4-5를 먼저 따르고, 이 절은 그 규칙이 없는 일반 페이지(랜딩, 포트폴리오, 안내문 등)에 인쇄 대응을 추가할 때 쓴다.

1-1. @media print 뼈대

프로젝트 계약: 인쇄가 요구되면 @media print 블록으로 화면과 서류를 분리한다. 화면에서 필요한 앱 크롬(내비, 툴바, 플로팅 버튼, 사이드바)은 인쇄에서 의미가 없다 — 사용자는 종이 위에서 그것을 누를 수 없다.

@media print {
  header.app-nav,
  [role="toolbar"],
  .floating-action,
  .cookie-banner,
  button:not(.print-keep) {
    display: none !important;
  }
}

프로젝트 계약: 모달이 열린 채로 인쇄가 시작되면 배경의 앱 화면이 아니라 그 모달 내용만 서류가 되어야 한다. SKILL.md 4-5의 기존 규칙을 그대로 쓴다.

@media print {
  body:has(dialog[open]) > *:not(:has(dialog[open])) {
    display: none !important;
  }
}

관찰 후보(시작값): @page의 여백은 브라우저 기본값에서 시작한다. 제본이나 클립이 필요한 서류만 여백을 늘린다. 정확히 몇 cm가 맞는지는 프로젝트마다 다르므로 숫자를 단정하지 않고, 실제 출력물로 확인한다(§2).

@page {
  margin: 2cm 1.5cm; /* 제본 쪽에 더 필요하면 margin-left 만 늘린다 — 실측 조정 */
}

1-2. 콘텐츠를 서류에 맞게 바꾼다

프로젝트 계약: 카드·표 행·영수증 구획처럼 한 덩어리로 읽혀야 하는 블록은 페이지 경계에서 잘리지 않게 한다.

@media print {
  .card,
  tr,
  .receipt-line,
  figure {
    break-inside: avoid;
  }
  h2, h3 {
    break-after: avoid; /* 제목만 페이지 끝에 남고 본문이 다음 장으로 넘어가는 것을 막는다 */
  }
}

프로젝트 계약: 상태색처럼 의미를 나르는 색(경고·성공·실패 배지 등)만 print-color-adjust: exact로 보존한다. 나머지는 브라우저의 기본 인쇄 설정(그레이스케일·절약 모드 포함)에 맡긴다. 전체를 강제 컬러로 찍으면 사용자의 잉크 절약 설정을 무시하게 된다.

@media print {
  .status-badge,
  .status-dot {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}

하드 게이트: 화면에서 잘라 보여주던 것(짧은 링크 텍스트, 접힌 섹션)은 종이 위에서 클릭이나 hover로 나머지에 닿을 수 없다. typography.md §13의 "잘린 텍스트 — 전체 값에 도달하는 수단" 원칙과 같은 이유로, 인쇄에서는 그 값 자체를 펼쳐 보여줘야 정보 손실이 없다.

@media print {
  a[href^="http"]:not([href*="#"])::after {
    content: " (" attr(href) ")";
    word-break: break-all;
  }
}

닫힌 <details>의 내용은 브라우저가 내부 슬롯으로 숨기므로, 자식 요소에 display를 주는 CSS로는 확실히 펼쳐지지 않는다. 인쇄 직전에 open을 켜고 끝나면 되돌린다.

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. 서류의 신원 — 머리말·바닥말·출력일

관찰 후보: 여러 장으로 인쇄될 문서(보고서, 명세서)는 페이지 번호와 문서 제목을 각 장에 남긴다. 독자가 순서를 잃어버리지 않게 하는 목적이므로, 한 장짜리 인쇄물(영수증, 초대장)에는 강제하지 않는다.

@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를 따른다.
  2. PDF로 저장해 연다. "PDF로 저장"으로 실제 파일을 만들고 별도 뷰어로 열어, 브라우저 미리보기와 최종 PDF가 다르게 나오는 경우(특히 폰트·배경색)가 없는지 확인한다.
  3. 경계에서 확인할 것: 카드·표·영수증 구획이 페이지 경계에서 잘리지 않는지, 앱 크롬이 실제로 사라졌는지, 모달만 인쇄될 상황에서 배경 화면이 섞여 나오지 않는지, 링크 URL과 펼친 details가 실제로 종이에 보이는지, 상태색이 흑백 인쇄에서도 배지 모양(테두리·아이콘)만으로 구분되는지(색만으로 구분되면 흑백에서 정보가 사라진다).
  4. 확인하지 못했으면 "미검증"이라고 적는다. 소스만 보고 통과로 추정하지 않는다 — audit-gate.md의 미검증 관례를 그대로 따른다.

3. 이메일

언제: 브리프가 뉴스레터, 트랜잭션 메일(가입 확인·영수증·비밀번호 재설정), 초대장처럼 이메일 클라이언트 안에서 열리는 HTML을 명시할 때. 웹사이트에 "문의하기 → 메일 발송" 버튼이 있는 정도는 해당하지 않는다.

이메일 클라이언트는 웹 브라우저가 아니다. 관찰 후보 — 이 문서에서 실측하지 않았다. 일부 클라이언트는 데스크톱 문서 편집기의 렌더링 경로를 그대로 쓰거나, 외부 스타일시트·최신 레이아웃 CSS를 지원하지 않거나 부분적으로만 지원한다는 관찰이 있다. 정확히 어떤 클라이언트가 무엇을 지원하는지는 이 문서에서 확인하지 않았으므로 클라이언트별 지원 수치를 단정하지 않는다. 아래는 그런 제약을 가정했을 때 안전한 기본값이며, 실제 확인은 §4를 따른다.

3-1. 레이아웃

프로젝트 계약: 폭은 600px 안팎의 단일 컬럼을 기본값으로 한다. 화면이 넓어도 이메일 본문 폭을 늘리지 않는다 — 클라이언트의 미리보기 창, 모바일 메일 앱 폭이 그보다 좁은 경우가 흔하다[SKILL-IMPECCABLE].

프로젝트 계약: 레이아웃은 표(<table role="presentation">) 기반으로 짠다. display: flex·grid 같은 최신 레이아웃 CSS와 외부 스타일시트 링크는 이메일에서 기대한 대로 렌더링되지 않을 수 있다[SKILL-IMPECCABLE].

프로젝트 계약 — 스타일은 두 층으로 둔다.

  • 핵심 스타일은 인라인: 폭·여백·글꼴·색·버튼처럼 빠지면 메일이 깨지는 값은 각 태그의 style 속성에 둔다. <style>을 지우거나 무시하는 클라이언트·전달(forward) 경로가 있기 때문이다.
  • 반응형·다크모드는 <head>의 <style>: 모바일 폭 조정 미디어쿼리(@media (max-width: 600px)), 다크모드 대응(@media (prefers-color-scheme: dark))은 인라인으로 표현할 수 없으므로 head의 <style> 블록에 둔다. Gmail도 head의 <style>과 폭 기준 미디어쿼리를 지원한다(Google 개발자 문서 "CSS support"). 다만 <body> 안의 <style>은 무시하는 클라이언트가 있으니 head에만 둔다.
  • 손으로 두 벌을 맞추지 말고 인라이너(빌드 단계에서 <style> 규칙을 style 속성으로 병합하는 도구)로 핵심 스타일을 인라인화하고, 미디어쿼리는 head에 남긴다.
  • 클라이언트별 지원 범위는 바뀐다. 쓰려는 속성은 채택 시점에 지원표(caniemail 등)로 확인하고 §4에서 실제로 연다.
<!doctype html>
<html lang="ko" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  <title>주문이 접수되었습니다</title>
  <style>
    @media (max-width: 600px) { .container { width: 100% !important; } .px { padding-left: 16px !important; padding-right: 16px !important; } }
    @media (prefers-color-scheme: dark) { .surface { background-color: #1c1c1e !important; } .ink { color: #f2f2f2 !important; } }
  </style>
</head>
<table role="presentation" class="container surface" width="600" cellpadding="0" cellspacing="0" style="margin:0 auto; max-width:600px; background-color:#ffffff;">
  <tr>
    <td class="px ink" 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> 태그 자체는 이메일 클라이언트에서 일관되게 렌더링되지 않는 경우가 있어 피한다 — 관찰 후보, 이 문서에서 실측하지 않았다).

<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-2-b. 받은편지함에서 보이는 것 — 제목·프리헤더·크기·텍스트 대체본

  • 프리헤더: 받은편지함 목록에서 제목 옆에 보이는 미리보기 문장을 본문 첫머리에 숨긴 텍스트로 직접 쓴다. 쓰지 않으면 "이메일이 보이지 않으면 여기를 누르세요" 같은 첫 줄이 미리보기로 나간다.
  • 크기: Gmail은 HTML이 약 102KB를 넘으면 본문을 잘라 "메시지 잘림" 링크로 숨긴다. 법정 표기·수신거부 링크가 보통 맨 아래에 있으므로 잘리면 그것부터 사라진다. 인라인화한 뒤의 최종 HTML 크기를 잰다.
  • plain-text 대체본: multipart/alternative로 텍스트 버전을 함께 보낸다. 텍스트만 보는 클라이언트와 일부 스팸 필터가 이것을 읽는다.
  • <html lang="ko">(외국어 메일이면 그 언어)와 dir을 둔다. 스크린리더 발음과 줄바꿈 규칙이 lang을 따른다.

3-2-c. 광고성 메일의 법정 표기 — 한국 수신자 대상

하드 게이트(영리 목적의 광고성 메일에 한함). 정보통신망법 제50조와 시행령은 영리 목적의 광고성 정보를 전자우편으로 보낼 때 표기 의무를 둔다. 디자인이 이 자리를 처음부터 확보하지 않으면 나중에 끼워 넣다 레이아웃이 깨진다.

  • 제목이 시작되는 부분에 (광고) 를 붙인다. 프리헤더나 본문에만 두면 안 된다.
  • 본문에 전송자의 명칭과 연락처(전자우편 주소·전화번호·주소 등)를 둔다.
  • 수신거부 방법을 본문에 분명히 둔다. 클릭 한 번으로 처리되는 수신거부 링크를 기본으로 하고, 링크 라벨은 한글로 쓰고 필요하면 Unsubscribe를 병기한다. 회색 6px 글씨처럼 찾기 어렵게 만들지 않는다 — 본문 대비 기준(color.md)을 그대로 적용한다.
  • 가입 확인·영수증·비밀번호 재설정 같은 거래 메일에는 광고를 섞지 않는다. 섞으면 그 메일이 광고성 정보가 된다.
  • 수신 동의 획득, 야간 전송 제한, 정기적인 수신 동의 확인 같은 발송 운영 의무는 디자인 범위 밖이지만, 브리프에 마케팅 메일이 있으면 운영 측에 확인할 항목으로 design.md에 적는다.
  • 세부 문구·예외는 법령과 KISA 안내서로 확인하고, 확정 문구는 사용자(법무)에게 받는다. 다른 나라 수신자가 섞이면 그 나라 규정(예: 미국 CAN-SPAM, EU 개인정보 규정)도 브리프에서 확인한다.

3-3. 이미지가 막혀도, 어두운 테마에서도 읽힌다

프로젝트 계약: 많은 이메일 클라이언트가 기본적으로 이미지를 차단한 채 미리보기를 띄운다. 로고·CTA·핵심 문구를 이미지에만 담으면 이미지가 막힌 순간 그 내용이 통째로 사라진다. 핵심 텍스트(제목, 본문, 버튼 라벨)는 실제 HTML 텍스트로 쓰고, 이미지에는 내용을 설명하는 alt를 붙이며, 이미지가 있어야 할 자리에는 배경색을 지정해 이미지가 빠져도 빈 흰 칸이 아니라 브랜드 색 영역으로 보이게 한다.

<img src="logo.png" alt="회사 로고" width="120"
     style="display:block; background-color:#f3f4f6;">

관찰 후보: 일부 클라이언트는 사용자가 어두운 테마를 켰을 때 메일 배경·글자색을 자동으로 반전하거나 재조정한다. 이 동작은 클라이언트마다 다르고 이 문서에서 실측하지 않았으므로 어떤 클라이언트가 어떻게 반전하는지는 단정하지 않는다. 실무적으로 안전한 방향은 배경색을 투명에 맡기지 않고 컨테이너마다 명시적으로 지정하는 것, 로고처럼 투명 배경을 가정한 자산을 흰 배경 전용으로 만들지 않는 것이다. 실제 확인은 §4를 따른다.


4. 이메일 검증

브라우저 렌더링과 실제 이메일 클라이언트 렌더링은 별개다. 로컬 브라우저에서 HTML 파일을 열어 봤다고 이메일에서도 같다고 보고하지 않는다.

  1. 실제 클라이언트로 확인한 경우만 "검증됨"으로 보고한다. 예를 들어 실제 계정으로 지메일·아웃룩·애플 메일 등에 발송해 열어 보고, 폭·표 레이아웃·버튼·이미지 차단 상태·어두운 테마에서 각각 확인한다.
  2. 확인하지 못했으면 "미검증"이라고 명시한다. 코드가 규칙(§3)을 따랐다는 것과 실제 클라이언트에서 그대로 보인다는 것은 다른 주장이다. "표 레이아웃과 인라인 CSS를 썼으니 호환될 것이다"는 추정이지 검증이 아니다. 모바일 폭(미디어쿼리 적용 여부)과 다크모드는 따로 확인한다.
  3. 클라이언트별 지원 수치·점유율을 지어내지 않는다. 이 스킬은 "아웃룩이 몇 퍼센트를 지원한다" 같은 구체 수치를 확인하지 않았다. 필요하면 사용자에게 발송 대상 클라이언트 범위를 물어 §3의 어떤 항목을 더 엄격하게 지킬지 좁힌다.

5. 체크리스트 (구현 중 자기 점검 — 납품 판정은 preflight.md X11·X12)

납품 판정은 preflight.md X11(인쇄)·X12(이메일)가 한다.

인쇄

  • @media print로 앱 크롬(내비·툴바·플로팅 버튼)을 숨겼다
  • 모달이 열린 채 인쇄되면 그 모달만 서류가 된다
  • 카드·표 행이 페이지 경계에서 잘리지 않는다(break-inside: avoid)
  • 상태색은 print-color-adjust: exact로 보존하되, 색만으로 상태를 구분하지 않는다(흑백에서도 구분 가능)
  • 짧은 링크 텍스트가 전체 URL로 펼쳐진다
  • 접힌 섹션(details 등)이 인쇄에서는 펼쳐진다
  • 여러 장 문서면 페이지 번호·문서 제목이 있다
  • 인쇄 미리보기와 PDF 출력을 실제로 열어 확인했다(§2). 못 했으면 미검증이라고 적었다

이메일

  • 폭 600px 안팎, 단일 컬럼
  • 표 기반 레이아웃, 핵심 스타일은 인라인, 반응형·다크모드 미디어쿼리는 head의 <style>(§3-1)
  • <html lang>, color-scheme·supported-color-schemes 메타, 프리헤더, plain-text 대체본이 있다(§3-2-b)
  • 최종 HTML이 약 102KB 미만이라 Gmail에서 잘리지 않는다(§3-2-b)
  • 광고성 메일이면 제목 첫머리 (광고), 본문의 전송자 명칭·연락처, 찾기 쉬운 수신거부 링크가 있다. 거래 메일에 광고를 섞지 않았다(§3-2-c, 하드 게이트)
  • CTA가 버튼처럼 보이는 요소다(텍스트 링크가 아니다)
  • 핵심 동작·정보가 hover에 의존하지 않는다
  • 복잡한 상호작용은 웹으로 딥링크했다
  • 이미지가 막혀도 핵심 텍스트가 읽히고, alt와 배경색을 지정했다
  • 컨테이너 배경색을 명시해 다크모드 반전에 대비했다
  • 실제 클라이언트에서 확인했다(§4). 못 했으면 미검증이라고 적었다