켈틱 크로스의 도전 — 복잡한 레이아웃을 CSS Grid로 풀기
10장의 카드를 전통 그대로 배치해야 한다
타로 리딩 모드 중 가장 복잡한 것이 켈틱 크로스다. 10장의 카드가 특정한 패턴으로 배치되며, 각 위치에 고유한 의미가 부여된다. 중앙에 십자형으로 6장, 오른쪽에 수직으로 4장. 이 배치가 수백 년간 이어져 온 전통이다.
문제는 이 배치가 일반적인 웹 레이아웃과 완전히 다르다는 것이다. 가로로 쭉 나열하거나, 격자형으로 정렬하거나, 스크롤로 이어지는 것이 아니라 특정 좌표에 카드를 "배치"해야 한다. 게다가 두 번째 카드는 첫 번째 카드 위에 가로로 겹쳐져야 한다. 이 레이아웃을 웹에서 어떻게 구현할 것인가.
Flexbox가 아닌 CSS Grid를 선택한 이유
처음에는 Flexbox로 시도했다. Flexbox는 일차원 레이아웃에 강력하고, 대부분의 카드 배치에 충분하다. 원카드와 쓰리카드 모드는 Flexbox로 간단하게 구현할 수 있었다. 카드를 가로로 나열하고 간격을 주면 끝이다.
하지만 켈틱 크로스의 십자형 배치를 Flexbox로 만들려면 중첩된 flex 컨테이너가 필요하다. 왼쪽 십자 영역과 오른쪽 기둥 영역을 나누고, 십자 영역 안에서 다시 행과 열을 나누고, 각 행 안에서 카드를 정렬하는 식이다. 결과적으로 div가 4~5단계로 중첩되고, 각 레벨마다 별도의 flex 설정이 필요하다.
CSS Grid는 이 문제를 근본적으로 다르게 접근한다. 이차원 격자를 먼저 정의하고, 각 아이템을 원하는 셀에 직접 배치한다. 4x4 그리드를 선언하고 "1번 카드는 2행 2열, 3번 카드는 1행 2열, 7번 카드는 1행 4열"처럼 지정하면 된다. 중간에 빈 셀이 있어도 상관없고, 중첩 구조도 필요 없다.
AI에게 두 접근 방식의 코드를 각각 요청해서 비교해본 결과, Grid 버전이 코드량도 적고 구조도 직관적이었다. 특히 "이 카드의 위치를 변경하고 싶다"는 요청에 대해 Grid는 grid-row와 grid-column 값만 바꾸면 되지만, Flexbox는 HTML 구조 자체를 변경해야 할 수도 있었다. 유지보수 측면에서 Grid가 압도적으로 유리했다.
4x4 그리드에 십자형 배치하기
최종 구현은 4열 4행의 그리드 위에 10장의 카드를 배치하는 방식이다. 타로 전통의 켈틱 크로스 배치를 웹 그리드에 매핑하는 과정은 퍼즐을 푸는 것 같았다.
십자형의 중앙 카드(1번, 현재 상황)는 2행 2열에 배치된다. 그 위에 겹쳐지는 2번 카드(도전)도 같은 위치다. 3번(무의식)은 3행 2열, 4번(과거)은 2행 1열, 5번(가능성)은 1행 2열, 6번(가까운 미래)은 2행 3열이다. 오른쪽 기둥의 7번부터 10번은 4열의 4행부터 1행까지 아래에서 위로 배치된다.
이 매핑을 정리하는 데 AI와의 대화가 매우 효율적이었다. "켈틱 크로스의 전통적 배치를 4x4 CSS Grid에 매핑해줘. 각 카드 번호와 grid-row, grid-column 값을 정리해줘"라고 요청하면 표 형태로 깔끔하게 정리된 결과가 돌아왔다. 물론 타로 전통에 대한 세부 사항은 직접 검증했지만, 기본 구조를 잡는 속도가 크게 빨라졌다.
두 번째 카드의 겹침 문제
켈틱 크로스에서 가장 특이한 부분은 2번 카드다. "도전"을 상징하는 이 카드는 1번 카드 위에 가로 방향으로 겹쳐진다. 두 카드가 십자(+) 모양을 이루는 것이 전통이다.
CSS Grid에서 두 요소를 같은 셀에 배치하면 자연스럽게 겹친다. 문제는 겹침의 방향과 스타일이다. 2번 카드는 단순히 1번 위에 놓이는 것이 아니라, 90도 회전된 상태로 놓여야 한다. CSS의 transform: rotate(90deg)를 적용하면 시각적으로는 원하는 결과가 나온다.
하지만 회전된 카드가 인접한 셀의 요소와 겹치는 문제가 발생했다. 카드를 90도 회전시키면 가로 세로가 바뀌면서 옆 셀 영역을 침범하는 것이다. 이 문제는 그리드 셀의 크기를 카드 대각선 길이 이상으로 여유를 주고, overflow: visible을 적용해서 해결했다.
z-index 관리도 필요했다. 2번 카드는 반드시 1번 위에 보여야 한다. 단순히 z-index를 높게 주면 되지만, 카드 뒤집기 애니메이션과 결합하면 상황이 복잡해진다. 뒤집기 도중에 z-index가 바뀌면 시각적으로 어색한 깜빡임이 생길 수 있다. 결국 뒤집기 애니메이션이 완료된 후에 z-index를 조정하는 방식으로 타이밍을 맞추었다.
모바일 반응형: 구조는 유지하되 크기만 조절
데스크톱에서 켈틱 크로스 레이아웃이 완성된 후 모바일 대응이 남았다. 10장의 카드를 십자형으로 배치하려면 상당한 화면 공간이 필요하다. 모바일의 좁은 화면에서 이 배치를 완전히 다른 형태로 바꿔야 할까.
여러 대안을 검토했다. 카드를 리스트 형태로 나열하는 방법, 스크롤 가능한 영역에 배치하는 방법, 탭으로 각 카드를 하나씩 보여주는 방법 등이 있었다. 하지만 결론은 "십자형 배치를 유지하되 크기만 줄이자"였다.
이유는 타로의 특성에 있다. 켈틱 크로스에서 카드의 "위치"는 단순한 배치가 아니라 의미를 담고 있다. 1번 카드 위에 2번이 교차하는 것은 "현재 상황에 놓인 도전"을 시각적으로 표현한다. 왼쪽의 과거, 오른쪽의 미래, 위의 의식, 아래의 무의식. 이 공간적 관계가 깨지면 해석의 맥락도 약해진다.
데스크톱에서 120px 너비의 카드를 모바일에서는 70px로 줄였다. 4x4 그리드의 gap도 줄이고, 폰트 크기도 비례적으로 조정했다. 70px는 카드 이미지를 알아볼 수 있는 최소 크기다. 이보다 작아지면 이미지가 뭉개져서 카드를 구분하기 어려워진다.
미디어 쿼리로 화면 너비에 따라 단계적으로 크기를 조절하는 대신, CSS의 clamp() 함수를 사용해서 부드럽게 크기가 변하도록 구현했다. 이렇게 하면 다양한 화면 크기에서 일일이 브레이크포인트를 정의하지 않아도 자연스러운 반응형이 된다. 이 접근은 AI가 제안한 것인데, 직접 시험해보니 미디어 쿼리 방식보다 훨씬 깔끔했다.
타로에서 카드 위치가 의미를 가지는 이유
이 파트를 작업하면서 타로의 깊이에 다시 한번 감탄했다. 켈틱 크로스에서 각 위치는 수백 년간 정교하게 다듬어진 상징 체계다. 1번 위치는 "질문자의 현재 상황", 2번은 "즉각적인 도전", 3번은 "무의식적 영향" 같은 식이다.
이것이 개발에 왜 중요한가. 카드의 위치에 따라 AI 해석의 맥락이 완전히 달라지기 때문이다. 같은 "The Tower" 카드가 1번 위치(현재)에 나오면 "지금 큰 변화를 겪고 있다"지만, 5번 위치(가능성)에 나오면 "앞으로 큰 변화가 올 수 있다"가 된다. 컴포넌트 설계에서 각 카드의 위치 정보를 반드시 포함시켜야 했던 이유다.
레이아웃은 단순한 시각적 배치가 아니라 데이터 구조와 직결된다. 이 사실을 체감한 것이 켈틱 크로스 구현의 가장 큰 수확이었다. CSS Grid의 좌표 시스템이 타로 카드의 의미 체계와 자연스럽게 매핑되는 것도 흥미로운 발견이었다. 기술적 도구가 도메인의 개념 구조와 잘 맞아떨어지면 구현이 놀라울 정도로 직관적이 된다.
CSS Grid가 열어준 가능성
켈틱 크로스를 CSS Grid로 구현하고 나서 느낀 것은 Grid의 유연성이 생각보다 훨씬 크다는 것이다. 전통적인 행렬 배치뿐 아니라 불규칙한 배치, 겹침, 빈 공간까지 자연스럽게 처리할 수 있다. 앞으로 새로운 리딩 모드를 추가하더라도 그리드 매핑만 새로 정의하면 된다.
이 작업을 통해 "레이아웃"이라는 개발 과제에 대한 시야가 넓어졌다. 대부분의 웹 페이지는 문서 흐름을 따라가는 단순한 레이아웃이다. 하지만 게임, 시각화, 이런 도메인 특화 앱에서는 CSS Grid의 좌표 기반 배치가 강력한 도구가 된다. 켈틱 크로스는 그 가능성을 직접 체험하게 해준 과제였다.
다음 편 예고
레이아웃까지 완성되면서 앱의 외형은 거의 갖춰졌다. 하지만 타로 앱의 핵심 기능이 아직 남아 있다. 바로 AI 해석이다. 다음 편에서는 선택된 카드와 위치 정보를 AI에게 전달하여 맞춤형 타로 해석을 생성하는 과정을 다룬다. Groq API로 시작해서 Cloudflare Workers AI로 전환한 이유, 그리고 AI에게 "좋은 타로 리더"가 되라고 지시하는 프롬프트 엔지니어링의 세계로 들어간다.
댓글
댓글 쓰기