씬에서 색을 꺼내는 4단계 — 일러스트 기반 화면의 디자인 토큰 절차

진행 막대만 초록이던 화면
지방 전문대 한 곳의 성인 학습자용 교육 앱을 만들고 있었습니다. 학습자·강사·관리자 세 역할이 쓰고, 학습자 화면은 학습 단계마다 캠핑 장면 일러스트가 깔린 뒤 진도가 오르면 장면이 자라는 구조입니다. 단계 히어로 이미지만 단계당 5장이고 페르소나와 배지를 합치면 서른 장 남짓이 화면에 붙습니다.
1단계 화면을 열었을 때 크림색 텐트가 놓인 캠핑 장면 위에 형광 초록 진행 막대가 얹혀 있었습니다. 그 아래 탭은 자홍색이었습니다. 요소를 하나씩 떼어 보면 어디에도 문제가 없는데, 한 화면에 모이니 서로 남처럼 보였습니다.
색이 틀린 것이 아니라 색을 정한 곳이 두 군데였습니다. 컨셉이 한 번 바뀌는 동안 이전 컨셉의 CSS 변수가 코드에 그대로 남았고, 일러스트는 그와 무관하게 나중에 그려졌습니다. 개발은 변수에서 색을 꺼내 쓰고 그림은 캔버스에서 색을 골랐습니다.
일러스트가 화면의 절반을 덮는 제품에서 색의 source of truth는 코드가 아니라 그림입니다. 저희는 이 순서를 뒤집는 데 작업의 대부분을 썼습니다.
왜 코드 리뷰에서 걸리지 않는가
이 어긋남은 색 목록만 놓고 보면 전부 통과합니다. 변수 이름도 값도 정상이고 컴포넌트 단위 스크린샷도 멀쩡합니다. 그림 위에 얹은 뒤에야 보이기 때문에 발견이 늦고, 발견한 시점에는 이미 화면 수십 개가 그 값을 참조하고 있습니다.
그래서 저희에게 남은 산출물은 색표가 아니라 순서였습니다. 아래 네 단계는 이후 배지 여섯 종과 페르소나 씬을 추가할 때 그대로 다시 돌린 절차입니다.
1. 씬에서 색과 기준선 꺼내기
완성된 장면 이미지에서 하늘·잔디·비탈·흙길 색을 픽셀 단위로 뽑아 원시 토큰으로 등록했습니다. 화면에 쓰는 색은 이 토큰만 참조하게 하고, 기존의 화면별 별칭은 값을 직접 갖지 않도록 바꿨습니다.
/* 씬에서 실측한 원시 토큰 */
--camp-sky: #d4ecfb;
--camp-grass: #99c681;
--camp-slope: #42a27d;
--camp-ink: #26454e;
/* 화면 별칭은 값을 갖지 않고 위를 참조합니다 */
--journey-sky: var(--camp-sky);부수 효과가 하나 따라왔습니다. 이미지 최하단 픽셀 줄이 단색이면 그 값을 CSS로 이어 칠할 수 있습니다. 그림이 끝나는 자리에서 화면이 잔디를 이어받아 카드 뒤까지 연장되고, 이미지와 화면의 경계선이 사라집니다. 아트를 3D 클레이 렌더에서 플랫 벡터로 전량 교체한 이유의 절반이 여기 있습니다. 클레이는 원형 받침 위 디오라마 문법이라 하단이 단색이 되지 않아 이 연결 자체가 불가능했습니다.
경계면을 규격으로 만들 때 색만으로는 부족했습니다. 이어 칠하기가 성립하려면 그림마다 지평선이 같은 높이에 있어야 하는데, 세 장이 각각 611행·593행·618행에 구워져 있었습니다. object-cover라 그 차이가 그대로 화면 단차가 되고, 슬라이드를 넘길 때 390px 폭에서 약 7px씩 잔디 선이 튀었습니다. 그래서 하단 색과 함께 캔버스 크기와 지평선 행을 에셋 규격으로 고정했습니다.
씬 에셋 규격 — 새 장면을 만들 때 지킵니다
캔버스 1024 × 1024
지평선 618행 (가장 낮은 장면에 맞춤)
최하단 줄 단일 hex #99c681
기존 장면 셋은 잔디 띠만 하늘색으로 다시 칠해 618행에 맞췄습니다. 그림 전체를 세로로 미는 방법은 쓰지 않았습니다. 텐트와 모닥불은 지면에 박힌 오브젝트라 같이 밀면 발이 뜹니다. 첫 시도에서 색 문턱을 RGB 거리 60으로 잡았다가 기둥의 어두운 면(잔디와의 거리 59)이 배경으로 오인돼 2단계 골조의 다리가 하늘에서 끊겼고, 거리 8 이내의 순수 잔디만 지우는 것이 안전선이었습니다.
2. 채도로 주인공 가르기
배경과 지형은 채도를 낮춘 밝은 파스텔로 깔고 주인공 오브젝트만 고채도로 씁니다. 명도가 아니라 채도로 띄우는 방식입니다. 처음 만든 무대는 하늘이 #6cb0e2였는데 채도가 어중간하게 높아 무대가 주인공과 경쟁했고, 저희가 "촌스럽다"고 느낀 원인이 여기였습니다.
단계 색은 새로 고르지 않습니다. 그 단계 씬의 주인공 색을 그대로 씁니다.
- 1단계 = 오렌지색 A형 텐트
#f5822b - 2단계 = 금색 돔 텐트
#f5a800 - 3단계 = 빨간 캠핑카
#e4574e

진행 막대와 배지와 버튼이 이 값을 따라가므로, 학습자는 자기 위치를 숫자가 아니라 색으로 기억합니다.
3. 대비 실측 후 잉크 분리
단계 색을 정한 직후 비비드 면 위 글자 대비를 전부 실측했습니다. 금색 면 위 흰 글자가 2.00:1로, 본문 기준 4.5:1에 한참 못 미쳤습니다. 네 색 모두 흰 글자로는 기준을 넘지 못했습니다. 그래서 흰 글자를 포기하고 밝은 면 전용 잉크 토큰(--ink-on-bright)을 따로 뒀습니다. 값은 씬 속 오브젝트의 딥 틸이 아니라 거의 검정에 가까운 네이비 #0a0a1d입니다. 이 잉크로 바꾸면 네 색 모두 4.90:1에서 9.76:1 사이로 올라갑니다.

글자 크기도 같은 자리에서 결정했습니다. 주 사용자가 고령 학습자라 기본 크기를 키워야 했는데, 처음 세운 계획은 화면 곳곳에 흩어진 px 값 554곳을 rem으로 바꾸는 전면 스윕이었습니다. 크기를 램프 토큰 7개로 먼저 수렴시키고 나니 rem이 필요한 곳은 그 7개뿐이었고, 여백과 아이콘은 px로 남겨도 기준이 성립했습니다. 스윕은 실행하지 않고 폐기했습니다.
토큰이 있으면 접근성 개선 비용이 화면 수만큼 곱해지지 않고 토큰 수만큼만 더해집니다.
검증 방법도 한 번 틀렸습니다. 브라우저 200% 확대로 스모크 테스트를 했는데, 확대는 px도 같이 키우기 때문에 px를 하나도 고치지 않아도 통과합니다. 실제 검증은 root font-size를 바꿔 보는 것이었습니다.
4. 이름을 의미에 맞추기
정리 중에 단계 색 변수가 코드 마흔 곳 남짓에서 단계와 무관하게 쓰이는 것을 발견했습니다. 1단계 색이 "성공" 초록 자리에, 3단계 색이 "정보" 파랑 자리에 들어가 있었습니다. 이름은 단계를 가리키는데 실제 의미는 상태였습니다.
값을 그대로 둔 채 상태색에 제 이름을 새로 붙이고, 옛 변수는 그 이름을 가리키는 별칭으로 남겼습니다. 화면은 바뀌지 않고 새로 쓰는 코드만 새 이름을 씁니다.
--success-strong: #2a7300;
--info: #0a66aa;
/* 레거시 별칭 — 신규 코드에서 사용 금지 */
--step1-500: var(--success-strong);
--step3-300: var(--info);이름을 바로잡는 작업이 색을 고르는 작업보다 오래 걸렸습니다.
함정 둘
생성 이미지의 배경색 흔들림
현상 같은 하늘색을 지정해 만든 씬인데 장마다 값이 미세하게 달라, 이미지와 CSS 배경 사이에 눈에 보이는 단차가 생겼습니다.
원인 이미지 생성 모델은 프롬프트에 적은 hex를 정확히 재현하지 않습니다.
해결 받은 이미지의 배경을 단일 값으로 다시 칠하는 후처리를 고정 절차로 넣었습니다. 플랫 벡터는 면이 단색이라 근사색 치환과 가장자리 픽셀 복제가 이음매 없이 됩니다.
스코프 밖으로 새는 학습자 전용 토큰
현상 학습자 화면에서만 커야 할 글자와 학습자 전용 액센트가 관리자 화면의 토스트에 나타났습니다.
원인 역할 경계를 라우트 그룹 프레임의 data 속성 하나로 나눴는데, 토스트와 축하 오버레이는 루트에 장착되어 그 프레임 바깥에 있습니다. 후손 선택자는 이 경계를 넘어갑니다.
해결 경계 밖 컴포넌트를 목록으로 뽑아 개별 처리했습니다. 기본을 크게 두고 콘솔에서 되돌리는 반대 극성은 같은 이유로 더 크게 샙니다.
적용 조건과 한계
이 절차는 일러스트가 화면 면적의 상당 부분을 차지하고 그 일러스트가 여러 장으로 늘어나는 제품에 맞습니다. 그림이 아이콘 수준이면 순서를 뒤집을 이유가 없고, 브랜드 팔레트가 이미 확정된 조직이라면 오히려 반대 방향으로 토큰을 기준 삼아 그림을 발주하는 편이 맞습니다.
한계는 둘입니다. 씬을 다시 만들면 원시 토큰 실측과 파생 대비 재측정을 처음부터 다시 돌려야 하는데 이 부분을 자동화하지 못해 지금도 수작업입니다. 그리고 레거시 별칭을 남기는 방식은 화면을 안전하게 지키는 대신 잘못된 이름을 코드에 존속시킵니다. 신규 코드 금지 규칙을 주석으로 박아 뒀지만 강제되지는 않습니다.
다시 한다면 순서 하나를 바꾸겠습니다. 토큰 이름을 단계 번호가 아니라 의미로 먼저 붙였을 것입니다. 색은 나중에 바꿔도 화면이 버티지만, 이름은 한 번 굳으면 마흔 곳을 다시 읽어야 합니다.

