보고서 · 르포
디자인 규약 시트
텔레그램 한 줄에서 시작해 Cloudflare Pages 로 나가는 분석 보고서의 색 · 글자 · 여백 · 컴포넌트 · 도표 · 움직임을 한 장에 모았다. 값은 손으로 옮겨적지 않고 src/templates/report.css 에서 추출했다 — 이 문서와 코드는 원리적으로 어긋나지 않는다.
이 문서를 쓰는 법규약(regulation) · 예시(specimen) · 반례(anti-pattern) 세 층으로 읽는다
이 시트는 장식용 무드보드가 아니라 발행물을 실제로 구속하는 규약집이다. 한 문장이라도 여기와 코드가 어긋나면 코드가 아니라 이 문서를 먼저 고친다 — 그 뒤 어느 쪽이 정본인지 §B 의 SSOT 표로 판정한다.
세 가지 읽는 법
| 당신이 하려는 일 | 읽을 순서 | 결과물 |
|---|---|---|
| 새 컴포넌트를 하나 추가한다 | §03 타입 스케일 → §04 여백 리듬 → §05 가장 가까운 이웃 컴포넌트 → §10 확장 절차 | 기존 리듬에 얹히는 새 블록 |
| 새 테마를 하나 추가한다 | §02 토큰 사전 → §02 대비 검사 → §10 체크리스트 | 21종과 같은 계약을 지키는 22번째 팔레트 |
| 다른 프로젝트의 디자인 가이드를 새로 만든다 | §00 → §02 토큰 아키텍처 → §06 어휘 설계 → §10 | 이 시트의 구조(토큰 → 어휘 → 컴포넌트 → 반례)를 골격으로 재사용 |
일반 독자 우선. 이 보고서를 읽는 사람은 해당 분야 전문가가 아니다. 전문 용어를 평이한 말로 바꾸고, 못 바꾼 것만 문단 하단 주석으로 푼다. 이 원칙은 다른 모든 문체·디자인 규칙에 앞선다. 화면 위 모든 선택 — 글자 크기, 여백, 색, 차트 종류 — 은 결국 "처음 보는 사람이 한 번에 읽히는가" 로 판정된다.
이 문서의 팔레트·토큰은 빌드 시 src/templates/report.css 의 [data-theme=…] 블록을 파싱해 주입했다. var() 참조는 끝까지 따라가 해석하고, 선언이 없는 값은 :root 폴백으로 채운 뒤 상속 으로 표시한다. 문서가 코드를 베끼면 반드시 드리프트하기 때문이다.
두 장르같은 데이터, 완전히 다른 조판 — 분석 보고서와 르포는 템플릿부터 갈라진다
분석의 깊이(mode: fast · standard · deep)와 직교하는 축이 장르(report_format)다. 사용자 메시지에 "르포" 토큰이 있으면 르포, 없으면 일반 보고서. 두 장르는 가드를 덧댄 관계가 아니라 다른 템플릿 파일로 완전히 분기한다.
| 축 | 일반 보고서 (standard) | 르포 (reportage) |
|---|---|---|
| 템플릿 | archetypes/freeform_essay.html | archetypes/reportage.html |
| 테마 풀 | 13종 (라이트 1 + 다크 12) | 8종 (전부 다크, reportage_*) |
| 디스플레이 서체 | Newsreader (세리프) | G마켓 Sans (산세리프) |
| 본문 서체 | IBM Plex Sans KR | Noto Sans KR |
| 지오메트리 | 둥근 모서리 6 / 8 / 10px | 플랫 radius 0 · 그림자 0 |
| 본문 폭 | 780px | 820px (padding 26) |
| 헤드라인 | 34px / 900 | 44px / 700 |
| 본문 | 15.5px / 1.85 | 16px / 1.9 |
| 섹션 표기 | kicker + 세리프 제목 | 01 두 자리 번호 + 산세리프 제목 |
| 렌더하는 부가요소 | 목차 · lede · dropcap · fact_grid · pull_quote · analogy · 쟁점 · 감시신호 · 시간궤적 · 요약 · 신뢰도 | 용어 풀이 하나뿐. 나머지 전부 미렌더 |
| 맺음 | 감시 신호 epilogue + 신뢰도 | 에필로그 없음 — 가벼운 소설처럼 끝낸다 |
| 어투 | 차분한 설명체, 평어체 | 현재형 · 박진감. 사실/추론/가설 3등급 표기 강제 |
| 장르 표시 | — | [르포] 배지 3곳 (본문·제목엔 금지, UI 라벨로만) |
v8.5.6 부터 르포의 8개 팔레트를 일반 보고서도 쓴다. 두 장르는 같은 CSS 선언을 공유한다(복제가 아니라 선택자만 겹침 — 복제하면 한쪽만 손댔을 때 색이 갈라진다). 가져간 것은 색뿐이다. 플랫 지오메트리와 G마켓 Sans 는 [data-theme^="reportage_"] 접두 선택자에 걸려 있어 접두 없는 이름엔 붙지 않는다. 장르를 가르는 것은 색이 아니라 조판·지오메트리·템플릿이다.
나란히 보기
위 스위처의 장르 칩을 눌러 아래 미리보기를 통째로 전환할 수 있다. 색은 그대로 두고 조판만 바뀌는 것을 확인하는 것이 요점이다.
호르무즈 33일: 배가 멈춘 자리에 남은 것들
01첫 번째 배가 회항한 날
새벽 세 시, 관제 화면에서 점 하나가 방향을 틀었다. 유조선은 항로를 벗어나 남쪽으로 20해리를 물러섰고, 그 뒤로 여섯 척이 같은 궤적을 그렸다. 해협은 그때부터 닫히기 시작했다.
선사는 회항 사유를 밝히지 않았다. 다만 같은 날 보험료율이 두 배로 뛰었다는 사실이, 말하지 않은 이유를 대신 설명한다.
미리보기 · 르포 전용 컴포넌트(.rep-*). 장르 칩을 일반으로 바꾸면 서체가 G마켓 Sans → Newsreader 계열로, 모서리가 0 → 둥근 값으로 돌아간다.
색 · 토큰21개 팔레트가 하나의 계약을 공유한다 — 레이아웃은 같고 색만 다르다
테마는 스킨이 아니라 계약이다. 모든 테마가 같은 이름의 토큰 20여 개를 반드시 채운다. 그래서 컴포넌트 CSS 는 색을 한 번도 직접 쓰지 않고 var(--accent) 만 부르며, 테마를 늘려도 컴포넌트는 손대지 않는다. 보고서마다 random.choice 로 테마가 바뀌어도 레이아웃이 흔들리지 않는 이유다.
2.1 토큰 사전
토큰은 다섯 층이다. 위층이 아래층을 부르되 역방향은 없다.
| 층 | 토큰 | 뜻과 쓰임 |
|---|---|---|
| 표면 Surface | --bg | 페이지 바닥. 가장 넓은 면적이라 여기서 테마의 인상이 결정된다. |
| --card | 바닥에서 한 단계 떠 있는 면. 차트 카드·비유 박스·각주 박스. | |
| --card-hover | 카드 위 포인터 상태. bg 와 card 사이가 아니라 card 보다 한 단계 더 밝게/어둡게. | |
| --bg-3 | 가장 깊은 면. 맺음말 구역·푸터. 다크 테마에선 bg 보다 어둡다. | |
| --card-deep | 차트가 실제로 그려지는 무대(stage). 카드보다 한 단계 더 깊게 파야 도표가 뜬다. | |
| --accent-surface | 강조 블록의 배경. 대개 --card 와 같은 값을 준다. | |
| 선 Line | --border | 또렷한 경계. 카드 테두리·상단 규칙선. |
| --border-light | 약한 경계. 섹션 사이 구분선. | |
| --divider | 표 안 행 구분. 보통 --border 와 동일. | |
| --border-soft | 알파를 가진 유일한 선. 배경 위에 얹혀 어떤 표면에서도 톤이 맞는다. 다크 계열은 rgba(255,255,255,.09) 로 통일. | |
| 글자 Ink | --text / --fg-1 | 제목·본문 강조·큰 숫자. 대비 최대. |
| --fg-2 | 본문 기본. text 보다 한 단계 눅여 장문 피로를 줄인다. | |
| --muted / --fg-3 | 메타·캡션·각주·차트 주석. AA 4.5:1 을 반드시 넘겨야 하는 최소 대비 지점. | |
| --text-secondary… | 레거시 별칭(--gold/--red/--green/--blue 등). 멀티컬러 시절 이름을 모노 토큰으로 매핑해 옛 컴포넌트를 살려둔 것. 신규 코드에서 쓰지 말 것. | |
| 의미 Semantic | --accent | 테마당 단 하나의 강조색. 키커·드롭캡·인용 룰·차트 핵심 계열·링크. |
| --up | 상승·기회. 작은 변동 라벨에만. | |
| --down | 하락·위험·경보. 이벤트 수직선, 위험권 테두리. | |
| 지도 Map | --map-land / --map-water | 육지·바다. 바다는 "오래된 종이" 처럼 바닥보다 한 단계 깊게. |
| --map-boundary / --map-graticule | 국경선·경위선. 국경은 글자색 계열, 경위선은 선 계열. | |
| 형태 Geometry | --radius / --radius-lg / --shadow | 6px / 8px / none. 그림자는 전 테마에서 none — 깊이는 그림자가 아니라 표면 층(bg → card → card-deep)으로 만든다. |
- 새 색이 필요하면 먼저 기존 토큰 중 무엇의 역할인가를 묻는다.
- 깊이는 표면 층으로. bg → card → card-deep 3단이면 충분하다.
- 알파가 필요한 선은 --border-soft 하나로 끝낸다.
- 테마를 추가할 땐 20칸을 전부 채운다. 빈 칸은 :root(라이트)를 상속해 다크 테마에 크림색이 튄다.
- 컴포넌트 CSS 에 #hex 를 직접 쓰기 — 테마 하나만 맞고 나머지 20개에서 깨진다.
- 테마마다 액센트를 둘 이상 두기. 단일 액센트가 이 시스템의 뼈대다.
- 레거시 별칭(--gold)을 신규 컴포넌트에 쓰기.
- 그림자로 위계 만들기. --shadow: none 은 취향이 아니라 규약이다.
2.2 21종 팔레트
아래 카드는 실제 토큰 값으로 칠했다. AA/AAA 배지는 그 테마의 --muted(가장 약한 글자)와 --bg 의 대비를 브라우저에서 지금 계산한 것이다 — 명목이 아니라 실측이다.
르포 전용 이름 (색은 위 8종과 동일, 조판만 다름)
같은 색을 reportage_ 접두로 부르면 플랫 지오메트리 + G마켓 Sans 가 함께 붙는다. 접두가 없으면 붙지 않는다. 이 접두 하나가 두 장르의 경계다.
2.3 액센트 규율
| 역할 | 허용된 자리 |
|---|---|
| --accent | 키커 · 드롭캡 · 인용 좌우 룰 · 각주 라벨 · 링크 밑줄 · 차트에서 강조할 계열 하나 · 지도의 주요 경로 끝점 |
| --up | 상승·성공 변동 라벨(작은 글자)만 |
| --down | 하락·위험 변동 라벨, 이벤트 수직 점선, 위험 반경 테두리 |
지표의 큰 숫자(metric value)는 언제나 --text 다. 색은 옆에 붙는 작은 변동 라벨에만 쓴다. 큰 숫자를 물들이면 한 화면에 강조가 대여섯 개 생기고, 그 순간 강조는 아무것도 강조하지 못한다.
미리보기 · 큰 숫자는 전부 --fg-1. 상승·하락 여부는 숫자 자체가 말하므로 색을 더할 이유가 없다.
타이포그래피세리프가 말하고, 산세리프가 설명하고, 모노가 기록한다
서체는 셋이 아니라 세 개의 역할이다. 역할이 먼저 있고 서체는 거기 배정될 뿐이라, 장르가 바뀌면 서체가 통째로 갈려도 화면의 논리는 그대로다.
3.1 폰트 스택
| 역할 | 일반 보고서 | 르포 | 왜 |
|---|---|---|---|
| 디스플레이 headline · heading |
Newsreader, 'Noto Serif KR', serif | GmarketSans, 'Noto Sans KR' | 세리프는 신문 사설의 무게를, G마켓 Sans 는 현장 기사의 속도를 만든다. |
| 본문 prose · deck |
'IBM Plex Sans KR', sans-serif | 'Noto Sans KR', sans-serif | 장문 한글은 산세리프가 유리하다. 세리프 본문은 국문에서 획이 뭉친다. |
| 기록 date · version · 좌표 |
'IBM Plex Mono', monospace | 날짜·버전·마감시한처럼 자리수가 흔들리면 안 되는 값 전용. | |
| 도표 라벨 SVG 내부 |
'Noto Sans KR' (라벨) · 'Noto Serif KR' / Newsreader (값) | SVG 안에선 웹폰트 로딩 실패 위험을 줄이려 Noto 계열로 고정. 값 라벨만 세리프로 격을 준다. | |
Newsreader 는 optical size 축(opsz 6..72)을 가진 가변 폰트다. 큰 제목에서 자동으로 획 대비가 커지고 작은 캡션에서 완만해진다 — 제목과 캡션에 같은 패밀리를 써도 둘 다 제대로 읽히는 이유. 이탤릭(ital 1)은 lede · 캡션 · 맺음말 · 인용에만 쓴다.
3.2 타입 스케일
아래 각 행은 실제 선언값으로 렌더된다. 값은 freeform_essay.html / reportage.html 의 인라인 스타일이 정본.
−.5px
해협 통항량은 8월 첫 주에 41% 줄었다. 같은 기간 아시아향 원유 운임은 두 배 넘게 올랐고, 정유사들은 재고를 헐어 쓰기 시작했다.
uppercase
미리보기 · 모바일(≤640px)에서는 headline 34→26, heading 24→20, prose 15.5→15 / 1.78 로 내려간다. 본문 하한은 15px — 그 아래로는 어떤 이유로도 내리지 않는다.
3.3 한글 조판 — 반드시 세 줄이 함께
양끝 정렬은 한글에서 특히 위험하다. 아래 세 선언이 한 세트로 붙지 않으면 반드시 깨진다. 새 줄글 블록을 만들 때마다 셋을 함께 복사한다.
.any-prose{
text-align: justify; /* ① 양끝을 맞춰 오른쪽 끝단을 단정하게 */
text-justify: auto;
text-align-last: left; /* ② 마지막 줄은 늘리지 않는다 — justify 의 전형적 결함 차단 */
word-break: keep-all; /* ③ 한글은 어절 단위로만 줄바꿈 (단어 중간 절단 금지) */
overflow-wrap: break-word;/* ④ 긴 URL·영문 토큰이 컨테이너를 넘지 않게 */
}
- 오른쪽 끝단이 가지런하다.
- "호르무즈" 가 "호르 / 무즈" 로 잘리지 않는다.
- 문단 마지막 줄이 억지로 벌어지지 않는다.
- keep-all 누락 → 어절 중간에서 잘려 읽는 흐름이 끊긴다.
- text-align-last 누락 → 마지막 한 줄이 글자 사이가 벌어져 텅 빈다.
- overflow-wrap 누락 → 긴 URL 하나가 가로 스크롤을 만든다.
문단 — <br><br> 가 아니라 진짜 <p>
v8.5.7 이전에는 렌더러가 <br><br> 만 내보내서 CSS 의 문단 규칙과 드롭캡이 통째로 죽어 있었다. 지금은 세 단계로 문단을 보장한다.
| 단계 | 내용 |
|---|---|
| ① 작성 강령 | 작성 프롬프트가 \n\n 으로 문단을 나누도록 강제한다 (르포는 "한 문단 다섯 문장 이내 · 섹션당 문단 서넛 이상" 으로 더 강하다). |
| ② 결정적 변환 | 템플릿 필터가 실제 <p> 로 변환한다. 모델 출력에 의존하지 않는다. |
| ③ 결정적 분할 | 그래도 다섯 문장을 넘는 덩어리는 코드가 쪼갠다. 소수점(9,063.84)을 문장 끝으로 오인하지 않도록 보호한다. |
| 규칙 | 값 | 비고 |
|---|---|---|
| 문단 간격 | margin-bottom: 1em | 르포는 1.15em — 호흡이 더 길다 |
| 첫 줄 들여쓰기 | text-indent: 1em | 첫 문단 포함 모든 문단 (v8.5.9) |
| 유일한 예외 | has-dropcap p:first-child | 드롭캡은 float 이라 들여쓰면 밀려 깨진다 → text-indent: 0 |
| 블록 헤더 문단 | p > strong:first-child | inline-block + indent 0 — 소제목처럼 시작하는 문단은 들여쓰지 않는다 |
해협이 닫힌 첫 주, 시장은 이 사건을 일시적 소음으로 읽었다. 유가는 이틀 올랐다가 되돌아왔고 해운주는 하루 반짝했다. 그러나 보험 시장의 반응은 달랐다.
둘째 주에 접어들자 전쟁위험 보험료율이 열여섯 배로 뛰었다. 이 숫자는 되돌아오지 않았고, 그때부터 가격이 아니라 경로가 바뀌기 시작했다.
미리보기 · 드롭캡 + 들여쓰기 예외 + strong(fg-1) · em(accent, 기울이지 않음) 처리. 강조 어휘는 두 개뿐이다.
레이아웃 · 지오메트리한 열, 780px, 그리고 반복되는 수직 리듬
이 시스템에 다단 조판은 없다. 본문은 언제나 한 열이고, 차트·지도·사진도 같은 열 폭을 꽉 채운다. 본문만 좁히면 데스크탑에서 오른쪽 끝단이 시각물보다 안쪽에서 꺾여 화면이 어긋나 보이기 때문이다(v8.2.15 회귀).
4.1 폭과 여백
| 대상 | 값 | 비고 |
|---|---|---|
| 일반 보고서 본문 열 | max-width: 780px | hero · 섹션 · 맺음말 · 목차 전부 동일 |
| 르포 본문 열 | max-width: 820px / padding 0 26px | 본문 서체가 더 커서 열도 조금 넓다 |
| hero 상하 | 48px 0 32px | 사진이 있으면 아래를 14px 로 줄인다 (:has()) |
| 섹션 상하 | 32px 0 | 구분선 1px solid --border-soft, 마지막 섹션은 선 없음 |
| 맺음말 구역 | 32px 0 48px | 배경 --bg-3 — 페이지가 한 단계 가라앉으며 끝난다 |
| 블록 사이 | 18~28px | 차트·비유·인용 각각의 margin |
4.2 반응형 — 세 개의 분기점
모바일 우선이 아니라 데스크탑 기준 + 축소다. 보고서는 링크로 공유되어 절반 이상 모바일에서 열리지만, 도표의 정보 밀도는 넓은 화면을 기준으로 설계하고 좁은 화면에서 줄이는 편이 판독성이 낫다.
4.3 격자 — minmax(0, 1fr) 규칙
$4.99~5.50/hr 처럼 끊기지 않는 넓은 값이 한 칸에 들어오면 그 칸의 min-content 가 균등분할을 깨고 격자를 컨테이너 밖으로 밀어낸다 — 일부 칸만 가로로 길어지고 마지막 칸이 잘린다. 모든 트랙을 minmax(0, 1fr) 로 선언하면 칸이 내용보다 좁아지는 것을 허용해 항상 균등 폭이 된다.
| 항목 수 | 데스크탑 | ≤640px |
|---|---|---|
| 2 · 3 | 2 · 3열 | 그대로 |
| 4 | 4열 | 4열 (폰트·패딩 축소) |
| 5 · 6 | 3열 (6→3+3, 5→3+2) | 2열 |
| 7 이상 | 4열 자동 줄바꿈 | 2열 |
라벨 칸은 min-height: 2.6em 으로 두 줄 높이를 미리 예약한다. 한 줄 라벨과 두 줄 라벨이 섞여도 큰 숫자의 세로 시작선이 모든 칸에서 일치한다.
4.4 지오메트리와 깊이
| 토큰 | 일반 | 르포 | 쓰임 |
|---|---|---|---|
| --radius | 6px | 0 | 작은 칩·각주 박스·비유 박스 |
| --radius-lg | 8px | 0 | 카드·요약 박스 |
| 차트 카드 | 10px | 0 | 카드 8 + 무대(stage) 8 |
| --shadow | none | none | 전 테마 공통. 깊이는 표면 층으로만 |
비유 박스·각주 박스처럼 본문에서 살짝 떨어져 나온 블록은 좌측만 굵은 선을 두고 border-radius: 0 6px 6px 0 을 준다. 왼쪽이 각지고 오른쪽이 둥근 이 비대칭이 "본문에 붙어 있되 별개" 라는 관계를 만든다.
미리보기 · 비유 박스(좌측 4px accent)와 각주 박스(좌측 3px border-soft). 같은 형상, 다른 무게 — 굵기와 색만으로 위계를 만든다.
컴포넌트 카탈로그18종 · 각각 "언제 쓰는가" 보다 "얼마나 드물게 쓰는가" 가 규약이다
편집 컴포넌트는 많을수록 좋은 게 아니다. 한 보고서에서 인용 박스가 다섯 개면 어느 것도 인용으로 읽히지 않는다. 그래서 이 카탈로그의 핵심 열은 권장 빈도다.
| 컴포넌트 | 보고서당 빈도 | 역할 | 르포 |
|---|---|---|---|
| hero | 1 | eyebrow · headline · deck · meta 네 줄 | 대체 |
| toc | 0~1 | 섹션 5개 이상일 때만 | ✕ |
| origin | 0~1 | 후속 보고서의 "이 분석의 출발점" | 축약 |
| kicker | 0~2 섹션 | 한 줄 섹션 라벨 (도입부 위주) | ✕ |
| lede | 0~1 | 첫 섹션 도입 1~3 문장 | ✕ |
| dropcap | 0~1 | 첫 섹션 한정 | ✕ |
| prose | 제한 없음 | 본문. 이 시스템의 중심 | ◎ |
| pull_quote | 0~2 | 핵심 문장·수치 강조 | ✕ |
| analogy | 0~1 | 정말 어려운 개념 하나에만 | ✕ |
| fact_grid | 0~1 | 핵심 수치 3~6개 | ✕ |
| footnotes | 섹션당 0~3 | 평이화 못 한 용어만 | ◎ 유일한 부가요소 |
| figure | hero 0~1 + 본문 0~3 | 기사 og:image 만 | ◎ |
| chart-card | 모드별 3~8 | 제목·부제·단위줄·무대·출처·주석 | ◎ |
| contradiction | 0~1 섹션 | 봉합하지 않은 쟁점을 서술형으로 | ✕ |
| timeline-flow | 0~1 | 과거·현재·미래 수직 레일 | ✕ |
| watch epilogue | 0~1 | 감시 신호 + 마감시한 | ✕ |
| degraded banner | 사고 시에만 | 생성 중단·절단본 경고 | ◎ |
| rep-* 6종 | 르포 전용 | 배지·헤드라인·번호·소제목·용어풀이·푸터 | ◎ |
5.1 머리 — hero · 목차 · 출발점
호르무즈 봉쇄 33일, 해운 보험이 먼저 무너졌다
미리보기 · hero 는 네 줄 고정 — eyebrow(액센트) / headline(세리프 900) / deck(300 웨이트) / meta(가운뎃점 구분). 버전은 eyebrow 뒤에 모노로 따라붙는다.
5.2 본문 블록 — 인용과 사진
보험료가 먼저 움직였다
봉쇄 사흘째, 런던 보험시장은 호르무즈 통항 선박의 전쟁위험 요율을 선체가액의 0.05%에서 0.35%로 올렸다. 8월 14일에는 0.8%를 찍었다. 20만 톤급 유조선 한 척 기준으로 한 번 지날 때마다 6억 원 가까이가 더 붙는다는 뜻이다.
요율이 오르자 용선 계약이 먼저 깨졌다. 8월 첫째 주 아시아향 스팟 계약 중 21%가 취소되거나 목적지를 바꿨고, 그 배들은 희망봉으로 향했다.
미리보기 · 인용은 배경 없이 좌우 2px 룰만 쓴다. 배경 + 둥근 모서리 + 좌측 굵은 선의 "AI 인용 박스" 룩은 v5.8.1 에서 폐기했다. 캡션은 세리프 이탤릭, 크레딧만 산세리프로 톤다운.
5.3 도표 카드 — 여섯 개의 슬롯
차트 카드는 그림 한 장이 아니라 여섯 칸짜리 문서다. 순서가 고정이라 어느 보고서를 열어도 눈이 같은 자리를 찾는다.
| 슬롯 | 타이포 | 규칙 |
|---|---|---|
| title | Noto Serif KR 14 / 700 | 필수. 무엇을 보는지 |
| subtitle | 12.5 italic | 선택. 한 줄 해석 |
| unit_line | IBM Plex Sans 10.5 | 단위·기간. "억 원 · 2026년 1~8월" |
| stage | SVG, 배경 --card-deep | 도표 본체. 카드보다 한 단계 깊다 |
| source | 10.5 / 상단 점선 구분 | 출처. 점선이 데이터와 메타를 가른다 |
| note | 10.5 muted | 읽는 법·단서 |
미리보기 · 무대(stage) 안의 도표는 이 페이지에서 실제 렌더러 대신 같은 어휘로 손으로 그린 SVG 다. 어휘 자체는 §06 참조.
5.4 맺음 — 쟁점 · 시간 · 감시 신호
해운업계는 이 봉쇄를 분기 안에 풀릴 협상 카드로 본다. 우회 항로가 이미 가동 중이고, 실제 물동량 감소분은 한 자릿수라는 것이다. (선사 3곳 8월 IR 자료) 그러나 보험시장의 가격은 정반대를 가리킨다. 요율 곡선이 4분기까지 우상향으로 잡혀 있다면, 그 시장은 분기 내 해소를 믿지 않는다는 뜻이다.
이 글은 보험 쪽 셈법에 무게를 둔다. 물동량은 사후 지표이고 요율은 사전 지표이기 때문이다. 다만 해운업계의 판단이 맞을 경우 4분기 운임은 지금 수준에서 40% 이상 되돌아온다 — 그때 이 판단은 틀린 것이 된다.
- 7월 29일첫 나포 시도, 요율 0.05% 유지
- 8월 18일요율 0.8%, 통항 41% 감소
- 9월 중순정유사 재고 소진 예상 시점예상
런던 보험시장 주간 고시 기준. 1%는 사실상 통항 포기 가격대로 통한다. 봉쇄가 분기를 넘긴다는 신호. 이 글의 전제가 강화된다.
되돌림이 나오면 해운업계 판단이 맞았던 것이다. 이 글의 결론을 뒤집는 신호.
미리보기 · 쟁점은 박스가 아니라 문단으로 쓴다. 강조는 전환점(accent) · 근거(fg-3) · 판단(fg-1 굵게) 세 단뿐. 시간 레일은 미래 구간만 점선 + 빈 점 — 투사와 사실을 시각적으로 구분하기 위해서다.
5.5 상태 — 열화 배너
생성이 끝까지 완료되지 못한 보고서는 정상처럼 보이면 안 된다. 절단본을 무경고로 발행한 사고 이후, 열화 플래그가 서면 헤더 바로 아래 배너가 뜨고 텔레그램 알림에도 같은 경고가 붙는다.
미리보기 · 이 배너만은 테마 액센트를 쓰지 않고 고정 경보색(#c0392b)을 쓴다. 테마에 따라 경고가 눈에 안 띄는 일이 없어야 하기 때문이다.
데이터 시각화색이 아니라 패턴과 잉크 농도로 구분한다 — 21개 테마에서 동시에 성립해야 하므로
도표는 테마 하나에 맞춰 칠할 수 없다. 크림색 종이에서도, 자정 남색에서도, 진홍 마룬에서도 같은 그림이 읽혀야 한다. 그래서 이 시스템은 색상(hue)으로 카테고리를 나누지 않는다. 구분은 패턴이, 위계는 잉크 농도가 맡는다.
6.1 패턴 — 45° 한 방향만
| ID | 타일 | stroke | 색 | 쓰임 |
|---|---|---|---|---|
| hatch-tight | 2.4 × 2.4 | 0.85 | --text | 위험 high · 카테고리 1 |
| accent-hatch | 2.4 × 2.4 | 0.85 | --accent | 카테고리 2 |
| hatch-wide | 3.8 × 3.8 | 0.7 | --text | 위험 medium · 면적 채움 · 위험 반경 |
| dots | 2.4 × 2.4 | r = 0.22 | --text | 위험 low · 카테고리 4 |
| 액센트 솔리드 | — | — | --accent | 핵심 항목 하나에만 |
- 명목 카테고리 — 순서 없는 항목들 (국가, 업종, 진영)
- 넓은 면적 — 누적 면적 차트, 단계구분도, 지도 위험권
- 위험 3단계 — tight / wide / dots
- 교차 해칭 — 격자가 그리드선과 충돌해 카테고리가 사라진다
- 반대 방향 사선(45° + −45°) — 인접 막대에서 눈이 'X' 로 합쳐 읽는다
- 회전 패턴 안의 dash — 인접 타일의 dash 가 정렬돼 '+' 로 보인다
- 큰 도트(r ≥ 0.4) — 점박이 텍스처가 데이터 표면에 노이즈를 얹는다
- 같은 색조의 명암 차이로 카테고리 구분 — 모노에서 거의 안 보인다
6.2 잉크 농도 사다리 — 위계에는 패턴을 쓰지 않는다
순위(막대)·서수 강도(히트맵)·구성 위계(누적/도넛)처럼 한 변수의 크기 순서인 곳에 해칭을 쓰면 서로 다른 종류처럼 오독된다. 그런 자리는 단일 잉크(--text)의 농도 사다리 + 핵심 하나만 --accent 로 처리한다.
| 용도 | 사다리 | 비고 |
|---|---|---|
| 3단 (막대 · 도넛) | 0.42 · 0.24 · 0.13 | 1위는 --accent 솔리드, 2위부터 이 사다리 |
| 4단 (누적 · 히트맵) | 0.32 · 0.20 · 0.13 · 0.08 | 다섯 단 이상은 만들지 않는다 — 구분이 사라진다 |
| 보조 그리드선 | ≤ 0.06 | 0-기준선만 opacity 0.55 로 또렷하게 |
6.3 선 어휘 — 관계는 dash 로 말한다
| 관계 | dash | stroke-width | 쓰임 |
|---|---|---|---|
| 동맹 · 협력 | (실선) | 1.4 | 관계도 · 지도 주 경로 |
| 충돌 · 봉쇄 | 5,3 | 2.0 | 가장 굵다 — 대립이 가장 먼저 보여야 한다 |
| 영향 · 충격 | 2,3 | 1.4 | 단방향 전달 |
| 전략 · 보조 | 1,3 | 1.4 | 가장 약한 연결 |
값 라벨은 Newsreader 세리프로 대상 옆에 직접 붙인다(막대 끝 · 조각 중앙 · 기둥 위). 범례는 직접 라벨이 물리적으로 불가능할 때만 만든다 — 범례는 시선을 도표와 범례 사이로 왕복시키는 비용이 있다.
6.4 차트 27종
작성 모델이 데이터를 보고 직접 {type, title, data, note} 를 내보낸다. 별도 빌더가 없으므로 데이터가 비면 차트도 없다. 왼쪽 색 띠는 검증 등급이다 — ■ safe 는 기본 가드만, ■ guarded 는 도표 비평 + 시각 정합 게이트를 통과해야 발행된다. ■ 주입 은 작성 모델이 고를 수 없는 타입이다 — 데이터가 있을 때 코드가 결정적으로 꽂거나(브리핑 전용) 특정 장르에만 나온다.
새 차트 종류를 만들어 배선만 해두면 아무도 쓰지 않는다. 실제로 캔들 차트가 그렇게 몇 달을 놀았다. 그래서 다섯 층으로 사용을 보장한다 — ① 사용량 기록(굶주린 타입 자동 감지 → 다음 보고서 프롬프트에 우선 고려 힌트 주입) ② 작성 프롬프트의 결정 트리 ③ 분석 방법 × 필수 도표 매트릭스 ④ 다양성 쿼터(서사 차트 기준 standard ≥3종 · deep ≥4종) ⑤ 회귀 시나리오 29종.
6.5 지도
베이스맵은 d3-geo + world-atlas 110m TopoJSON 한 번 받아 캐시한다. 외부 타일 서비스와 글리프 PBF 호출은 금지 — 비트맵 타일은 모노 리스타일이 불가능하고 패턴 오버레이와 색이 충돌한다.
| 어휘 | 값 | 규칙 |
|---|---|---|
| projection | 평면 (기본) / globe | 경도 폭 100° 이상이면 결정적으로 지구본으로 승격. 평면 메르카토르에 대륙 간 스케일을 그리면 바다만 남고 구석이 왜곡된다 |
| arcs.kind | flow / alt / tension | 흐름(화살촉 + 굵기 1~3) · 우회(점선) · 긴장(하락색 ✕) |
| markers.kind | chokepoint ◆ / port ◎ / military ▲ | 글리프가 종류를 말하므로 색을 더하지 않는다 |
| regions | subject / ally / rival / contested | 국가 역할 색조. 원본 국가명 매칭 |
| rings | range / coverage | 사거리·작전반경 동심원. 최대 4개, 반경은 본문 근거 수치만 |
| sea_labels | 세리프 워터마크 | 바다 이름은 라벨이 아니라 배경 질감처럼 |
지도 데이터에 들어 있다는 이유로 관계없는 주석을 무조건 그리면 안 된다. 실제 사고: 모든 지도에 특정 미승인 국가 폴리곤과 범례가 따라붙었다. 지금은 현재 뷰포트와 실제로 교차할 때만 렌더한다.
모션움직임은 데이터가 들어오는 방식을 설명할 때만 허용된다
이 시스템의 애니메이션은 장식이 아니라 읽는 순서를 지시하는 문법이다. 그래서 규칙이 넷뿐이고, 넷 다 어기면 회귀로 잡는다.
| 규칙 | 값 | 이유 |
|---|---|---|
| 지속시간 | ≤ 900ms | 그 이상은 '기다림' 으로 인지된다 |
| 이징 | cubic-bezier(.22,1,.36,1) | 빠르게 나가고 부드럽게 안착 — 데이터가 '도착' 하는 느낌 |
| 진입 트리거 | IntersectionObserver | 화면에 들어올 때 한 번만. 관측 후 반드시 unobserve |
| 주변 모션 | RAF + 화면 밖 정지 | 보이지 않는 도표의 루프는 배터리만 태운다 |
| 접근성 | prefers-reduced-motion | 전부 정지. 최종 상태를 즉시 그린다 — 애니메이션이 없으면 안 보이는 요소를 만들지 않는다 |
르포 전용 주변 모션
배경 글리프는 --fg-1 + 저알파(0.08)로만. 액센트로 칠하면 본문의 유일한 강조색과 경쟁해 화면의 위계가 무너진다.
문체가 디자인에 거는 제약타이포그래피가 감당할 수 없는 것은 글이 하지 말아야 한다
아래는 문체 규약이지만 결과는 화면에 직접 나타난다. 조판이 아무리 좋아도 본문에 **굵게** 가 날것으로 박히면 그 페이지는 무너진다.
이 절의 기호 규칙은 발행되는 보고서 본문(headline · deck · prose · 요약 · 캡션)에만 걸린다. 결정적 후처리가 그 텍스트만 정화하기 때문이다. 지금 읽고 있는 이 시트 같은 개발 문서, 코드 주석, 커밋 메시지는 대상이 아니다. 규칙을 좁게 거는 것이 규칙을 지키게 만든다.
8.1 기호 금지 — 사용자 최우선 규칙
| 금지 | 대신 | 이유 |
|---|---|---|
| ** * ` (마크다운) | HTML strong / em | 날것으로 새면 AI 가 쓴 티가 난다. 결정적 후처리가 모든 노출 텍스트를 정화한다 |
| — – (em · en dash) | 쉼표 / ~ / 공백 | 삽입구는 쉼표, 숫자 범위는 물결, 단어 사이는 공백으로 자연 치환. URL·좌표는 보존 |
| TTS 발음 표기 | 원 표기 | "더블유티아이" 는 음성 내레이션 전용. 눈으로 읽는 글로 새면 안 된다 |
| 신뢰도 % | 서술 | "신뢰도 82%" 같은 기계 수치는 독자에게 아무 의미가 없다 |
8.2 평이화 어휘표 (발췌)
세 곳이 항상 정합해야 한다 — 문체 가이드의 어휘표, 작성 프롬프트의 평이화 예시, 발행본 핫픽스 도구의 치환 매핑. 새 매핑을 쓰면 세 곳에 함께 넣는다.
| 쓰지 않는다 | 대신 |
|---|---|
| 함의 / 귀결 / 부합 / 소지 | 뜻하는 바 / 결과 / 맞음 / 가능성 |
| 컨센서스 | 시장 전망 평균 |
| 내러티브 / 모멘텀 | 흐름 / 추진력 |
| 변곡점 | 흐름이 바뀌는 지점 |
| 익스포저 | 위험에 노출된 정도 |
| 가이던스 | 회사 자체 전망 |
| 헤지 | 위험을 줄이는 반대 거래 |
바꿀 수 없는 핵심 용어만 본문에 남기고, 그 섹션 하단 용어 풀이로 내린다. 이 두 단계가 §00 의 제1원칙을 화면에서 실행하는 방식이다.
8.3 시점 앵커링
| 상황 | 규칙 |
|---|---|
| 발행일 ≠ 사건일 | 첫 문단에 시간 거리를 명시한다. "같은 시각" 같은 표현으로 지속 상태를 사건일에 고정하지 않는다 |
| 미래 카운트다운 | D-N · "사흘 앞" 은 발행일 기준으로 직접 센다. 출처 문구를 베끼면 하루씩 어긋난다 |
| 시장 수치 | 최신 가용 봉을 쓴다. 옛 일자의 정확한 값을 무표기로 채택하는 것도 오류다 |
접근성 · 인쇄 · 성능테마가 21개면 접근성은 21번 검증돼야 한다
| 항목 | 기준 | 구현 |
|---|---|---|
| 본문 대비 | AA 4.5:1 | 가장 약한 --muted 가 기준점. 미달 팔레트는 색조를 유지한 채 명도만 올린다 (princess 3.96 · bridal 4.41 → 4.8+ 로 상향한 전례) |
| 본문 최소 크기 | 15px | 모바일에서도 하한 유지. 캡션·각주는 별도(13px) |
| 모션 | reduced-motion | 진입 애니메이션·주변 루프·스크롤 아크 전부 정지 |
| 포커스 | 키보드 도달 | 지도 확대 버튼 등 모든 컨트롤에 aria-label |
| 이미지 | alt | 캡션이 있으면 캡션을, 없으면 대체 텍스트를 |
| 인쇄 | 흑백 가정 | 패턴·dash 어휘가 그대로 살아남는다. 색에만 의존하는 인코딩이 없는 이유가 여기에도 있다 |
| 외부 자원 | 폰트 + TopoJSON | 도표 라이브러리는 d3 하나. 지도 타일·차트 CDN·추적 스크립트 없음 |
| 사진 | graceful degrade | 원격 이미지는 프리로드 성공 시에만 얹는다. 실패해도 레이아웃이 흔들리지 않는다 |
§02 팔레트의 AA/AAA 배지는 하드코딩이 아니라 WCAG 상대휘도 공식으로 브라우저가 실측한 값이다. 새 테마를 추가하면 이 페이지가 바로 판정한다.
확장 절차무엇을 건드리면 무엇이 함께 바뀌어야 하는가
이 시스템에서 가장 자주 깨지는 것은 코드가 아니라 정합이다. 한 곳만 고치고 짝을 안 고치면 몇 주 뒤 이유를 모르는 회귀로 돌아온다. 아래는 그 짝 목록이다.
10.1 새 테마 추가
- report.css 에 [data-theme="이름"] 블록 — 토큰 20칸 전부. 빈 칸은 라이트 테마를 상속해 다크에 크림색이 튄다.
- 지도 3색(--map-water / --map-land / --map-boundary)을 잊지 않는다. 이게 빠져서 다크 테마 지도가 크림색으로 나온 전례가 있다.
- 테마 풀 상수에 이름 추가 (일반 풀 / 르포 풀 중 어디인지 결정).
- --muted vs --bg 대비를 4.5 이상으로 확인. 미달이면 색조 유지 + 명도만 상향.
- 이 시트를 다시 생성해 팔레트 카드와 부록 A 를 갱신.
10.2 새 차트 종류 추가
- 렌더러 등록 (charts.js 의 RENDERERS)
- 작성 프롬프트에 데이터 스키마 섹션 추가
- 검증 가드 추가 (schemas.py 의 타입→가드 매핑) — 그리고 validate_chart_data 분기도 반드시. 분기 누락으로 새 타입이 100% 조용히 버려진 사고가 있었다
- 시각 역량 레지스트리 등록 (safe / guarded / experimental)
- 사용량 기록의 알려진 타입 목록에 추가
- 회귀 시나리오 추가 (타입 목록과 1:1)
- 회귀 테스트 · 갤러리 샘플 갱신
①~⑦ 을 다 해도 아무도 그 차트를 안 쓸 수 있다. §6.4 의 5층 사용 보장 중 최소 ②(결정 트리)와 ④(다양성 쿼터)까지 손대야 실제로 쓰인다.
10.3 새 컴포넌트 추가
- 가장 가까운 이웃 컴포넌트를 찾아 그 여백·서체·테두리 어휘를 물려받는다. 새 어휘를 만들지 않는다.
- 줄글이 들어가면 §3.3 의 네 줄 세트를 함께 붙인다.
- 색은 토큰으로만. 하드코딩된 hex 가 하나라도 있으면 20개 테마에서 깨진다.
- 권장 빈도를 정한다. 빈도 없는 컴포넌트는 반드시 남용된다.
- 두 장르 중 어디에 렌더할지 결정. 르포는 기본이 "미렌더" 다.
- 이 시트 §05 에 미리보기 + 빈도 행 추가.
10.4 발행 전 점검
- 21개 테마 중 라이트 1 + 다크 2 를 실제로 열어봤는가
- 모바일 폭(≤640px)에서 fact_grid 와 감시신호가 접히는가
- 인쇄 미리보기에서 도표 구분이 살아있는가
- reduced-motion 을 켜도 모든 요소가 보이는가
- 본문에 마크다운 기호·em dash 가 새지 않았는가
- 기본 테마 하나만 보고 발행 — 나머지 20개에서 대비가 무너진다
- 새 블록에 justify 만 넣고 keep-all 누락
- 차트 데이터가 비었는데 빈 카드 프레임만 렌더
- 도표 안에서 NaN 이 그대로 노출
- 큰 숫자에 액센트 색
부록 A · 토큰 전량표report.css 에서 추출한 값 그대로. 클릭하면 CSS 변수 블록이 복사된다
| theme | --bg | --card | --card-deep | --border | --text | --muted | --accent | --up | --down | 대비 |
|---|
부록 B · SSOT 파일 지도어떤 사실이 어느 파일에 한 번만 적혀 있는가
이 시스템의 문서 규율은 하나다 — 같은 사실을 두 곳에 적지 않는다. 한쪽은 반드시 링크가 된다. 아래 표에서 "정본" 열이 그 한 곳이다.
| 사실 | 정본 (SSOT) | 함께 갱신 |
|---|---|---|
| 테마 색 토큰 | src/templates/report.css | 테마 풀 상수 · 이 시트 · 팔레트 갤러리 |
| 테마 풀 목록 | src/lens_policy.py (ALL_THEMES / REPORTAGE_THEMES) | report.css 블록 · 이 시트 §02 |
| 일반 보고서 조판 | templates/archetypes/freeform_essay.html | 이 시트 §03~05 |
| 르포 조판 | templates/archetypes/reportage.html | 이 시트 §01 · §05 |
| 차트 카드 · 범례 프레임 | templates/static/charts.css | 이 시트 §05.3 |
| 차트 렌더러 · 패턴 정의 | templates/static/charts.js | 이 시트 §06 · 차트 갤러리 |
| 지도 렌더러 | templates/static/maps.js | 이 시트 §6.5 |
| 차트·패턴·색 원칙 | docs/MONO_THEME_GUIDE.md | 비교 샘플 · 이 시트 §06 |
| 본문 문체 | docs/REPORT_STYLE_GUIDE.md | 작성 프롬프트 2종 · 이 시트 §08 |
| 차트 회귀 반례 | docs/CHART_RENDERING_ANTIPATTERNS.md | 이 시트 §06 · 변경 이력 |
| 본문 회귀 반례 | docs/REPORT_WRITING_ANTIPATTERNS.md | 이 시트 §08 · 변경 이력 |
| 차트 타입 역량 등급 | docs/VISUAL_CAPABILITY_REGISTRY.yaml | 가드 · 사용량 기록 · 회귀 시나리오 |
| 버전 | src/orchestrator.py:VERSION | README · 변경 이력 · 커밋 메시지 접두 |
이 시트를 다시 만드는 법
팔레트와 부록 A 는 report.css 를 파싱해 채웠다. 추출기는 저장소에 들어 있으므로, 테마를 추가·수정했다면 다시 돌려 THEMES 상수만 갈아끼우면 된다. 손으로 hex 를 옮기는 순간 이 문서는 신뢰를 잃는다.
# 저장소 루트에서
python3 scripts/extract_theme_tokens.py > /tmp/themes.json
# 하는 일
# 1. report.css 에서 주석 제거 → [data-theme="X"] { … } 블록 수집
# (:root 는 editorial_cream 으로 취급, 셀렉터가 여러 테마를 겹쳐 잡으면 각각에 배분)
# 2. 각 블록의 --key: value 를 dict 로
# 3. var(--other) 참조는 같은 테마 안에서 끝까지 재귀 해석
# 4. 선언되지 않은 키는 :root 폴백으로 채우고 _inherited 에 이름을 남김
# 5. JSON 덤프 → 이 페이지의 THEMES 상수로 주입지금 이 문서의 THEMES 는 위 명령의 출력과 완전히 일치하는 상태로 생성됐다. 불일치가 생겼다면 둘 중 하나가 낡은 것이고, 정본은 언제나 report.css 다.