# images.md — 사진을 어디서 구하고 어떻게 싣나 이미지는 대부분의 디자인에서 **가장 무거운 자산이고 가장 큰 인상**이다. 레이아웃이 좋아도 사진이 약하면 페이지가 약하다. 반대도 마찬가지다. 4단계(구현)에서 **레이아웃을 잡기 전에** 무엇을 실을지 정해라. 자리를 먼저 만들고 나중에 채우면 비율이 안 맞아 다시 짠다. --- ## 1. 조달 경로 — 위에서부터 확인한다 | 순위 | 경로 | 언제 | |---|---|---| | 1 | **사용자가 준 사진** | 있으면 무조건 이것. 실제 작업물을 이길 생성물은 없다 | | 2 | **생성**(codex `image_gen`) | 콘셉트 확인용, 톤이 정확히 지정된 추상·분위기 컷 | | 3 | **스톡** | 라이선스가 명확한 곳만. 출처를 `design.md` 에 적는다 | | 4 | 없이 간다 | 타이포·색·여백만으로 만든다. 약한 사진보다 낫다 | **0단계에서 물어라**: "쓸 수 있는 사진이 있나요?" 없다고 하면 2번으로 가되, **생성물이라는 사실을 `design.md` 에 적는다.** 나중에 실제 사진으로 바꿀 때 레이아웃을 다시 짜지 않도록 비율을 미리 고정해 둔다. --- ## 2. codex 가 있으면 생성한다 설치 여부를 먼저 확인한다. 없으면 조용히 3번으로 내려간다. ```bash codex --version # 있나 node -p "JSON.parse(require('fs').readFileSync(require('os').homedir()+'/.codex/auth.json','utf8')).auth_mode" # → "chatgpt" 여야 구독 경로다. "api_key" 면 사용자 과금이라 먼저 물어라. ``` ### 래퍼가 실패해도 이미지는 만들어져 있다 — 반드시 확인해라 `~/.codex/imagegen-headless/codex_imagegen.sh` 는 세션 rollout 에서 `image_generation_call` 의 **base64** 를 찾는다. **codex-cli 0.147.0 은 base64 를 돌려주지 않는다.** `exec` 도구를 거쳐 **파일로 저장**하고, 래퍼는 "image_gen 이 호출되지 않았다" 며 실패한다. 실측에서 이 메시지를 보고 두 번 다시 만들었는데, **두 번 다 파일은 멀쩡히 있었다.** ``` ~/.codex/generated_images//exec-.png ``` rollout jsonl 의 `custom_tool_call_output` 에 경로가 그대로 적혀 있다. ``` Generated images are saved to <디렉터리> as <디렉터리>\exec-.png by default. ``` **한 줄에 경로가 둘**이므로 마지막 것을 쓴다. 공백을 허용하는 정규식은 앞 디렉터리부터 `" as "` 까지 통째로 삼킨다. ```js // 세션 파일에서 실제 저장 경로를 읽는다 const all = line.match(/[A-Za-z]:\\[^\s"]*generated_images[^\s"]*\.png/g); if (all) hit = all[all.length - 1]; ``` 호출은 이렇게 한다. **`$imagegen` 이 프롬프트 맨 앞에 와야** 내장 도구를 쓴다 — 뒤에 붙이면 codex 가 스크립트를 짜려고 헤맨다. ```bash printf '%s' "\$imagegen $PROMPT" \ | codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox - ``` > 실패로 판정하기 전에 `~/.codex/generated_images/` 를 봐라. > "실패했다" 고 보고했는데 파일이 있으면 그건 도구가 아니라 판정이 틀린 것이다. ### 프롬프트 — 톤을 공통 문자열로 고정한다 여러 장을 만들 때 각 프롬프트에 톤을 다시 쓰면 장마다 색이 달라진다. **공통 접두사 하나**를 만들고 장면만 바꿔라. ``` [공통] Editorial magazine photograph. Warm off-white setting (#F8F6F0), soft natural window light, low saturation, muted restrained palette. Photographic realism, shallow depth of field, fine film grain. No text, no logo, no watermark. [개별] Overhead flat-lay on a worn wooden workbench: cut stems, florist shears, twine. ``` - **3단계에서 정한 배경색을 프롬프트에 그대로 넣어라.** 사진이 페이지 배경과 이어진다 - 크기는 양변 16의 배수: `1152x1536`(3:4) · `1536x1152`(4:3) · `2048x1152`(16:9) - 사람 얼굴은 피하는 쪽이 안전하다. 손·뒷모습·부분은 잘 나오고 얼굴은 어색해지기 쉽다 - `No text` 를 넣어라. 넣지 않으면 간판·라벨에 뭉개진 글자가 생긴다 ### 생성물은 반드시 눈으로 본다 `Read` 로 열어 확인한 뒤에만 쓴다. 특히 **공간 사진**(작업실·매장)이 어색해지기 쉽다. 주제가 안 맞으면 프롬프트를 **한 번에 한 가지만** 바꿔 다시 만든다. ### 여러 화면이면 이미지 바이블부터 만든다 히어로 한 장을 만든 뒤 카드·모바일·OG에 같은 파일을 억지로 자르지 않는다. 생성 전에 슬롯 계약을 쓴다. | 슬롯 | 역할 | 피사체 | 비율 | 안전영역 | 진실 라벨 | 폴백 | |---|---|---|---|---|---|---| | 데스크톱 히어로 | 첫 인상 | 주체+행동 | 16:9 | 텍스트 반대쪽 55% | 필요 시 `연출 이미지` | 단색+DOM 카피 | | 모바일 히어로 | 첫 화면 행동 | 핵심 주체 | 4:5 | 하단/중앙 | 동일 | 별도 정지컷 | | 증거 컷 | 재질·동선 | 부분·공간 | 3:2 | 중앙 | 합성/예시 여부 | DOM 도식 | **비율이 맞는 것과 의미가 맞는 것은 다르다.** 데스크톱 원본을 4:5로 잘랐을 때 얼굴만 남고 제품이 첫 화면 아래로 밀리면 모바일 전용 원본을 생성한다. 모바일 캡처 390×844에서 핵심 피사체가 실제로 보이는지 확인한다. 이미지는 재질·분위기·행동을 맡고, **가격·주소·지도·상태·AI 근거는 DOM이 맡는다.** 생성 이미지 안에 정보를 구우면 접근성·수정 가능성·사실 검증을 동시에 잃는다. 가상 매물·합성 매장처럼 실제 증거로 오인할 수 있는 사진은 파일 출처를 `design.md`에 적는 것만으로 부족하다. 사진과 같은 시각 그룹에 `연출 이미지 · 실제 사진 아님` 같은 라벨을 둔다. 상세 계약은 `trustworthy-showcases.md`를 따른다. --- ## 3. 싣기 전에 반드시 줄인다 생성물은 1~3MB PNG 로 나온다. 그대로 실으면 페이지가 10MB 가 된다. ```js const w = Math.min(meta.width, meta.width > meta.height ? 1600 : 1100); await sharp(src).resize({ width: w }).webp({ quality: 82 }).toFile(out); ``` 실측: **10.1MB → 603KB (94% 감소).** 화면에서 차이가 보이지 않는다. | 쓰임 | 최대 폭 | |---|---| | 전체폭 배경·히어로 | 1600px | | 본문 폭 이미지 | 1100px | | 카드·썸네일 | 640px | ## 4. `` 에 `width`/`height` 를 적었다면 `height: auto` 를 반드시 줘라 CLS 를 막으려고 속성을 적는 것은 맞다. 그런데 CSS 에서 높이를 정하지 않으면 **속성 height 가 그대로 계산된 높이가 되어 `aspect-ratio` 가 무시된다.** ```css img { max-width: 100%; height: auto; } /* 이 한 줄이 없으면 */ ``` 실측 사고: `.two-photo img { aspect-ratio: 3/4 }` 를 줬는데 computed 값은 `"3 / 4"` 로 멀쩡히 보이고 **실제 높이는 원본 1448px** 이었다. `aspect-ratio` 는 폭·높이 중 하나가 `auto` 일 때만 높이를 정한다. 증상이 조용하다 — 오류도 경고도 없고 **페이지만 길어진다.** 이 한 줄을 넣자 전체 높이가 10.9 화면에서 7 화면이 됐다. > 진단: 이미지 높이를 실제로 재라. 원본 픽셀 높이(예: 1448)가 그대로 나오면 이것이다. > ```js > [...document.querySelectorAll('img')] > .filter(i => i.getBoundingClientRect().height > 1000) > ``` ## 5. `alt` 는 장식이 아니면 반드시 쓴다 - 내용을 나르는 이미지 → **무엇이 보이는지** 적는다. "이미지", "사진" 은 alt 가 아니다 - 순수 장식(배경 텍스처·그레인) → `alt=""` + `aria-hidden="true"` - 캡션이 이미 설명하고 있으면 alt 는 짧게. 같은 문장을 두 번 읽히지 않는다