"예쁘게"를 코드로 옮기는 법

운영 중인 서비스의 인스타그램에 올릴 카드뉴스, 매번 손으로 만드는 게 귀찮았아서 AI로 자동화를 하고 싶었어요.

사실 저는 클로드의 역량을 아주 진보적으로 평가하고 있어서, "이 정도는 금방 되겠지" 하고 주말에 가볍게 붙잡았는데, 결국 몇 주가 걸렸고요.

그리고 마지막엔 "AI한테 일을 시킨다는 게 대체 뭘까"를 다시 생각해볼 수 있었어요. 그 이야기를 해볼게요.

카드뉴스, 생각보다 까다로워요

만들어 보기 전엔 저도 "이미지 몇 장 뽑으면 되겠지" 했어요. 그런데 착각이었어요.

카드뉴스는 한 장이 아니라 시리즈예요. 표지 한 장, 본문 몇 장, 마지막 CTA 한 장. 이게 전부 "같은 브랜드"처럼 보여야 해요. 슬라이드마다 강조 색이 미묘하게 다르거나 로고 위치가 3px씩만 어긋나도, 사람 눈은 그 미세한 어긋남을 기가 막히게 잡아내요. 그리고 "대충 만들었네" 하고 넘어가죠.

그러니까 진짜 목표는 예쁜 이미지 한 장이 아니었어요. 끝까지 일관된 시리즈였어요. 이걸 한참 뒤에야 깨달았습니다.

1차 시도: 이미지를 통째로 뽑기

처음엔 제일 단순한 길로 갔어요. 이미지 생성 모델한테 프롬프트를 던지고, 완성된 PNG를 그대로 받는다. 2026년인데 이 정도는 당연히 되겠거니 했죠.

그런데 제가 AI 이미지 생성 경험은 많지 않았던터라, 예상했던 것에 훨씬 못미치는 디자인이 나왔어요.

정확히는, 텍스트가 망했어요. 카드뉴스는 결국 타이포그래피인데, 카피 한 줄이 딱 박혀야 할 자리에 철자가 뭉개진 글자가 나왔어요. 한글은 더 심했어요. "지원하세요"가 "지윈허세오" 비슷한 뭔가가 됐거든요. 짧은 단어는 그럭저럭 버티는데, 문장이 되는 순간 무너졌어요.

처음엔 제 프롬프트가 문제인 줄 알고 문구를 고쳐보기도 했었는데, 소용없었어요. 나중에 찾아보니, 이건 제 탓이 아니라 모델 구조에서 오는 한계더라고요.

Diffusion 계열 이미지 모델이 "읽을 수 있는 글자"를 잘 못 그린다는 건, 학계에서도 이미 알려진 한계예요. 이 모델의 텍스트 인코더는 글의 "의미"를 해석하도록 학습됐지, 획 하나하나의 정밀함을 담도록 만들어지지 않았거든요. 그래서 글자 수가 늘수록 정확도가 뚝뚝 떨어져요. 대략 20자를 넘기면 못 믿는다는 연구도 있고요(Visual Text Processing 리뷰, arXiv 2025). 자음·모음을 조합하는 한글은 사정이 더 나쁘죠.

정리하면 이래요. 원래 글자를 못 그리는 도구한테, 글자가 전부인 작업을 시킨 거예요. 프롬프트를 백 번 고쳐서 될 일이 아니었어요.

방향 전환: 그림 말고, 문서

그래서 관점을 하나 바꿨어요.

카드뉴스를 "그림"이라고 보면 이미지 생성이 맞아요. 그런데 뜯어보면, 카드뉴스는 사실 잘 배치된 문서에 가까워요. 제목, 본문, 강조 박스, 로고, 배경. 전부 브라우저가 매일 픽셀 단위로 정확하게 그려내는 것들이죠.

그래서 AI한테 그림을 그리게 하는 대신, HTML/CSS를 짜게 하고 그걸 헤드리스 브라우저로 PNG로 구워내는 아이디어를 떠올렸어요. Puppeteer로 .slide 요소만 2배 크기로 스크린샷을 떠서, 2160×2160 레티나 PNG로 뽑는 구조예요.

여기서 결정적인 차이가 하나 있어요. 이미지 생성은 확률적이라 같은 프롬프트도 매번 다른 걸 뱉어요. 반면 HTML 렌더링은 결정론적이에요. 같은 코드는 언제나 같은 픽셀을 내죠. 글자는 폰트가 그리니까 뭉개질 일이 없고, 브랜드 색도 #F05A2A라고 쓰면 딱 그 색이 나오고, 로고는 원본 SVG를 그대로 박으면 끝이에요.

글자 문제는 이걸로 완전히 사라졌어요.

그런데... 안 예뻤어요

그렇게 첫 시리즈의 빌드 통과, 렌더 통과, PNG 7장이 깔끔하게 떨어졌어요. 설레는 마음으로 열어봤어요.

7장이 다 똑같았어요. 다크 배경 왼쪽에 글자, 다음 장도 다크 배경 왼쪽에 글자, 그다음도요. 오른쪽은 매번 휑하게 비어 있었고요. 아무리 넘겨도 뭐가 달라졌는지 눈에 안 들어오지도 않고 인스타에 올릴 카드뉴스가 아니라, 그냥 다크 테마 터미널 출력 같았어요.

이게 글자 깨짐보다 더 골치 아팠어요. 글자 문제는 도구를 바꾸니까 풀렸는데, 이건 아무리 생각해봐도 도구 문제가 아닌 것 같았거든요.

그래서 규칙을 써주기 시작했어요. 폰트는 최소 몇 px, 강조는 굵게, 여백은 화면의 몇 %, 슬라이드마다 레이아웃 좀 바꿔라. 디자인 원칙을 프롬프트에 문단째로 박아넣었죠.

그래도 지루했어요. "예쁘게 만들어"가 코드로 도무지 번역이 안 됐어요. 규칙을 촘촘히 쓸수록, AI는 그 규칙을 딱 만족시키는 선에서만 움직였지 보기 좋게 만들진 않더라고요. 여기서 며칠을 날렸어요. 그런데 그 며칠 덕분에, 이 프로젝트에서 유일하게 건진 걸 배웠어요.

AI는 디자인을 알아요. 우리 브랜드를 모를 뿐이었어요

지루한 카드가 나온 건 모델이 멍청해서가 아니었어요. Opus든 뭐든, 요즘 모델은 좋은 카드와 나쁜 카드를 충분히 구별해요. 문제는 "우리 브랜드에서 예쁘다는 게 뭔지"가 컨텍스트에 없었다는 거예요. 그건 오직 제 머릿속에만 있었거든요.

여기까진 사실 요즘 다들 하는 얘기죠. 모델보다 컨텍스트가 병목이라는 말. 제가 진짜로 막힌 건 그다음이었어요.

그 컨텍스트를, 저조차 넣기 전엔 몰랐어요.

이론상으론 간단해요. 제 머릿속 판단 기준을 문서로 옮겨 적으면 되니까요. 그런데 막상 앉아서 "우리 브랜드 디자인 규칙"을 적으려니까 손이 안 나갔어요. 색은 오렌지, 배경은 다크. 여기까진 술술 나와요. 그런데 "그래서 강조는 어떻게 하는데?" 하고 스스로 물으면, 대답을 못 했어요. 저는 강조하는 법을 분명히 알고 있었는데, 미리 말로는 못 꺼냈던 거예요.

이게 언제 튀어나왔냐면, 지루한 카드를 눈으로 보고 나서였어요. "아, 강조는 검정 박스에 흰 글씨지. 오렌지로 강조하면 배경이랑 싸워서 안 되잖아." 이 규칙은 원래 제 안에 있었어요. 그런데 망한 결과물을 마주치기 전엔 그런 게 있는 줄도 몰랐죠. 넣어야 할 맥락은 미리 정리된 목록으로 있는 게 아니었어요. 뭔가 잘못된 걸 봤을 때, 그제야 하나씩 튀어나왔어요.

"예쁘게"를 파일로 굳히기

그래서 온보딩을 "결과물 보고 반응하는" 사이클로 다시 짰어요. 카드를 뽑고 → 제가 어디가 별로인지 반응하고 → 그 반응을 규칙으로 받아 적고. 이 규칙들이 브랜드별 폴더에 파일로 차곡차곡 쌓여요.

그 핵심이 idioms.json이에요. "이 브랜드는 강조를, 리스트를, 큰 숫자를 구체적으로 이렇게 표현한다"를 기계가 읽을 수 있게 적어둔 파일이죠. 실제로 들어간 강조 규칙은 이렇게 생겼어요.

"emphasis": {
  "type": "inline-highlight",
  "css": ".emph { background:#000; color:#fff; padding:4px 14px; font-weight:800; }",
  "rationale": "검정 박스 + 흰 텍스트. 사자 로고의 검정 눈/코와 시각적으로 호응.",
  "enforcement": "default",
  "usageBudget": { "perSlide": 1, "perSeries": 3 },
  "contextFit": "키워드 1-2개 강조가 필요한 slide에만. 큰 숫자가 주인공인 slide에선 생략."
}

여기서 rationale가 진짜 중요해요. "검정 박스"라는 결과만 적는 게 아니라, "왜 검정인지"(로고의 눈·코와 호응)까지 같이 적거든요. 그래야 AI가 새 슬라이드에서 규칙을 기계적으로 복붙하지 않고, 맥락에 맞게 응용해요.

그리고 usageBudget이랑 contextFit. 이게 바로 아까 "7장이 다 똑같은" 문제의 진짜 해법이었어요. 규칙만 던져주면 AI는 그 규칙을 모든 슬라이드에 발라버려요. "폴더 카드 컨테이너 예쁘다"고 하면 일곱 장을 전부 폴더 카드로 만들어버리죠. 그럼 그게 벽지가 돼요. 그래서 표현마다 "시리즈당 최대 3번", "팀·파트 분류 같은 categorical 콘텐츠에만, 단일 인용구엔 금지" 같은 예산과 조건을 같이 박았어요. 규칙이랑 규율을 한 세트로 준 거예요.

재밌는 흔적도 하나 있어요. 8개 표현 중에 "성공·축하 느낌"을 담당하는 idiom은 처음에 일부러 null로 비워뒀거든요. 로그엔 이렇게 적혀 있어요. success-feel: TBD, 첫 실제 시리즈에서 정한다. 축하 표현이 언젠가 필요할 건 아는데, 진짜로 축하할 슬라이드가 오기 전엔 그게 어떻게 생겨야 할지 도저히 못 정하겠더라고요. 이 한 줄이 프로젝트 전체를 요약해요. 맥락은 필요해질 때 채워지지, 미리 다 못 채워요.

이렇게 쌓인 규칙으로 다시 뽑은 detail 슬라이드는 이런 모습이에요. 흰 탭 + 오렌지 바디의 폴더 카드 안에, 검정 라벨 박스로 항목을 나누고, 우상단엔 시그니처 클립을 꽂았어요.

똑같이 "왼쪽 텍스트 + 오른쪽 여백"에서 출발했는데요. 이번엔 오른쪽 여백이 시그니처 오브젝트로 채워지고, 밋밋하던 강조가 로고와 호응하는 검정 박스로 바뀌었어요. 프롬프트를 더 잘 써서가 아니에요. 참조할 파일이 생겨서예요.

그래서, 생성이 아니라 축적이에요

이 파일들은 대화를 거듭할수록 두꺼워져요. taste-profile.json엔 값마다 confidence 점수가 붙어 있는데요. 처음엔 색·여백이 0.6~0.7이었다가, 시리즈를 몇 번 뽑고 나면 0.9로 올라가요. 축적이라는 게 그럴듯한 비유로 끝나는 게 아니라, 문자 그대로 이 숫자가 오르는 일인 거죠. 피드백("여백 더", "이 색 좋다")이 일정 이상 쌓이면 자동 적용되는 규칙으로 승격되고, 두 번 반박당하면 다시 강등돼요.

그래서 열 번째 카드뉴스는 첫 번째랑 알아볼 수 없게 달라져요. 모델이 좋아져서가 아니에요. 모델이 참조하는 맥락이 그만큼 두꺼워진 거예요. 그리고 이 맥락 파일은 도구 안이 아니라 사용자 폴더에 남아요. 색이나 폰트 같은 표면 값이야 스크린샷 한 장이면 5분 만에 베끼지만, 수십 번의 반응 사이클로 쌓인 idioms.json은 그렇게 못 훔쳐요.

아직 못 푼 것들

물론 자랑만 할 순 없어요. 지금도 confidence가 낮은 쪽에선 AI가 엉뚱하게 응용해요. 규율(budget/contextFit)을 너무 빡세게 걸면 이번엔 매번 똑같아지고, 느슨하게 풀면 다시 벽지가 되고요. 이 사이의 균형은 아직도 감으로 잡고 있어요.

AI한테 일을 시킬 때, 제 몫의 절반은 이거였어요. "머릿속엔 있는데 아직 말로 못 꺼낸 판단 기준"을, 망한 결과물을 미끼 삼아 계속 끄집어내서 파일로 굳히는 일. (나중에 알고 보니 "우리는 말할 수 있는 것보다 더 많이 안다"는 이 상태를 부르는 말이 따로 있더라고요. 암묵지라고 불리는...)

모델은 이미 충분히 똑똑해요. 병목은, 대개 인간의 머릿속에 있다고 생각해요.


전체 코드는 github.com/xhae123/card-designer에 있어요.