라벨이 타로인 게시물 표시

회고 — AI와 함께한 사이드 프로젝트, 그리고 그 이후

회고 — AI와 함께한 사이드 프로젝트, 그리고 그 이후 20편의 여정을 돌아보며 "타로카드를 보는 웹페이지를 만드려고 해." 이 한 문장에서 시작된 프로젝트가 여기까지 왔다. 78장의 카드 데이터를 설계하고, 남색 배경에 금빛 텍스트로 분위기를 잡고, 카드가 숨 쉬듯 떠다니는 애니메이션을 만들고, 모바일에서의 수십 가지 문제를 해결하고. 20편에 걸쳐 그 과정을 기록했다. 이 마지막 편에서는 프로젝트 전체를 거시적으로 돌아보려 한다. AI가 정말로 도움이 됐던 순간과 그렇지 않았던 순간. 이 프로젝트를 통해 정립한 AI 협업 방법론. 그리고 사이드 프로젝트에서 AI 협업이 가지는 본질적 가치에 대해. 전체 프로젝트 타임라인 타로 마스터의 개발 과정을 시간순으로 돌아보자. 아이디어 구체화와 기술 스택 결정에 하루. 78장 카드 데이터와 해석 텍스트 작성에 이틀. 기본 리딩 기능과 UI 구현에 사흘. 애니메이션과 마이크로 인터랙션에 이틀. AI 실시간 해석과 서버리스 API 구축에 이틀. PWA 전환과 사용자 기능 추가에 사흘. 모바일 최적화와 UI 개편에 나흘. 전부 합치면 약 2주 반이다. 풀타임으로 집중한 게 아니라 퇴근 후와 주말을 활용한 시간이니, 실제 투입 시간은 이보다 적다. AI 없이 같은 결과물을 만들었다면 최소 두 달은 걸렸을 것이다. 이 시간 차이가 사이드 프로젝트의 생사를 결정한다. 두 달짜리 사이드 프로젝트는 완주 확률이 극히 낮지만, 2주 반이면 에너지가 식기 전에 끝낼 수 있다. AI가 잘한 것 다섯 가지 도메인 지식 전달. 타로에 대해 거의 몰랐던 내가 78장 카드의 의미, 다양한 스프레드 방식, 정방향과 역방향 해석의 차이를 빠르게 파악할 수 있었던 건 AI 덕분이다. 새로운 분야에 진입하는 초기 학습 곡선을 극적으로 낮춰줬다. 대량 콘텐츠 생성. 78장 카드 각각에 대한 정방향/역방향 해석 텍스트, 총 156개의 해석을 작성하는 건 사람이 하면 일주일은 걸린다. AI와 함께 이틀 만에 완성했다...

바이브 코딩의 함정과 의도 기반 협업

바이브 코딩의 함정과 의도 기반 협업 "AI에게 다 시키면 되지"의 달콤한 유혹 "GPT한테 시키면 5분이면 만들어주던데?" 이 말을 들을 때마다 복잡한 감정이 든다. 틀린 말은 아니다. AI에게 "타로 앱 만들어줘"라고 하면 정말로 동작하는 코드가 나온다. 화면에 카드가 보이고, 클릭하면 뒤집어지고, 해석 텍스트가 나타난다. 5분은 과장이지만, 30분이면 충분하다. 문제는 그 다음이다. "카드 애니메이션을 바꾸고 싶어." "리딩 히스토리를 추가하고 싶어." "모바일에서 레이아웃이 깨지는데." 이런 요구가 오는 순간, AI가 5분 만에 만들어준 코드 위에서 개발자가 할 수 있는 일이 거의 없다. 코드를 이해하지 못하니까. 이것이 바이브 코딩의 함정이다. 그리고 이 글은 타로 마스터 프로젝트 전체를 관통하는 핵심 주제, "바이브 코딩과 의도 기반 협업의 차이"에 대한 이야기다. 바이브 코딩이란 무엇인가 바이브 코딩은 간단히 말해 "AI에게 코드를 쓰게 하고, 분위기(vibe)만 확인하면서 개발하는 것"이다. 코드의 내부 구조를 이해하지 않고, 결과물이 원하는 대로 동작하면 넘어가는 방식이다. 빠르고, 편하고, 즉각적인 만족감을 준다. 이 방식이 유효한 경우도 분명 있다. 일회성 스크립트, 빠른 프로토타입, 학습 목적의 코드. 한 번 쓰고 버릴 코드라면 내부 구조를 깊이 이해할 필요가 없다. 문제는 이 방식을 "지속되는 프로젝트"에 적용할 때 발생한다. 타로 마스터는 한 달이 넘는 기간 동안 점진적으로 기능이 추가되고, 디자인이 바뀌고, 버그가 수정되는 프로젝트였다. 초기에 만들어진 코드 위에 계속 새로운 코드가 쌓인다. 기초가 이해되지 않은 상태에서 쌓아올린 코드는 모래 위의 성이다. 결정적 차이: "만들어줘" vs "이렇게 설계하고" 바이브 코딩과 ...

모바일 최적화 — 생각보다 까다로운 모바일 UX

모바일 최적화 — 생각보다 까다로운 모바일 UX 데스크톱에서 완벽했던 앱이 모바일에서 무너지다 "데스크톱에서 잘 되는데?" 이 말은 프론트엔드 개발에서 가장 위험한 문장 중 하나다. 타로 마스터를 처음으로 내 스마트폰에서 열었을 때, 화면 하단이 잘려 있었다. 카드 뒤집기 애니메이션이 버벅거렸다. 텍스트가 너무 작아서 읽을 수 없었다. 데스크톱에서의 그 우아한 경험은 어디로 간 걸까. 웹 트래픽의 절반 이상이 모바일에서 발생한다. 타로 마스터 같은 가벼운 엔터테인먼트 앱은 그 비율이 더 높을 수밖에 없다. 출퇴근길에, 침대에서, 친구와 커피를 마시다가 "타로 한번 볼까?"라고 꺼내는 게 일상적 사용 시나리오다. 모바일 경험이 나쁘면 사용자의 대부분을 잃는다는 뜻이다. 하단 탭 바: 네이티브 앱의 감각 빌리기 모바일 앱에서 가장 자연스러운 네비게이션은 하단 탭 바다. 인스타그램, 카카오톡, 거의 모든 네이티브 앱이 하단 탭을 사용한다. 엄지손가락이 자연스럽게 닿는 위치에 주요 메뉴를 배치하는 것이 모바일 UX의 기본 중의 기본이다. 타로 마스터에도 하단 탭 바를 도입했다. 홈, 리딩, 히스토리, 설정. 네 개의 탭으로 구성했다. 웹앱에서 하단 탭 바를 구현하는 건 기술적으로 어렵지 않다. CSS의 position: fixed와 bottom: 0을 쓰면 된다. 하지만 여기서부터 진짜 문제가 시작된다. 모바일 브라우저에는 자체 UI가 있다. 주소창, 하단 도구 모음. 이것들이 화면 공간을 차지하면서 우리가 만든 하단 탭 바와 충돌한다. 특히 스크롤할 때 브라우저 UI가 나타났다 사라졌다 하면서 레이아웃이 춤을 추는 현상이 발생한다. CSS의 환경 변수 env(safe-area-inset-bottom)을 사용해서 이 문제를 완화했다. 이 값은 노치나 홈 인디케이터 같은 시스템 UI 영역의 크기를 알려준다. 하단 탭 바의 패딩에 이 값을 더하면 시스템 UI와 겹치지 않는 안전한 영역에 탭이 위치한다. 하지만 이건 시작에...

사용자 기능 확장 — AI와 빠르게 기능 추가하기

사용자 기능 확장 — AI와 빠르게 기능 추가하기 "이거 하루면 되겠는데"가 진짜 하루에 되는 경험 기능 하나를 추가하는 데 보통 얼마나 걸릴까. 기획하고, 설계하고, 구현하고, 테스트하고. 회사 프로젝트라면 일주일은 잡아야 하는 일이다. 그런데 AI와 함께하면 "이거 하루면 되겠는데"라는 직감이 진짜로 하루 안에 현실이 된다. 이번 편은 타로 마스터에 추가한 여섯 가지 기능의 이야기다. 리딩 히스토리, 스트릭, SNS 공유, PDF 리포트, Instagram 스토리 카드, 자유 선택 모드. 하나하나가 독립적인 기능이지만, 모두 "사용자가 더 오래, 더 자주 돌아오게 만드는" 공통 목적을 가지고 있다. 리딩 히스토리: localStorage로 만드는 "내 타로 일지" 타로 리딩을 하고 나서 결과를 다시 보고 싶을 때가 있다. "지난주에 받은 리딩이 뭐였더라?" 하는 순간. 이 니즈를 충족시키는 가장 간단한 방법이 리딩 히스토리 기능이다. 데이터베이스 없이 localStorage만으로 구현했다. 사이드 프로젝트에서 데이터베이스는 유지 비용과 복잡성을 급격히 높이는 선택이다. 서버를 운영해야 하고, 사용자 인증이 필요해지고, 개인정보 보호 문제까지 따라온다. localStorage는 브라우저에 데이터를 저장하는 가장 단순한 방법이다. 서버 없이, 인증 없이, 사용자의 기기에만 데이터가 남는다. 리딩 결과를 JSON으로 직렬화해서 저장하고, 히스토리 페이지에서 날짜순으로 보여주는 구조다. Claude에게 "localStorage를 활용한 리딩 히스토리 기능을 만들고 싶다. 날짜, 리딩 타입, 뽑은 카드, 해석 텍스트를 저장해야 한다"고 설명하자, 데이터 구조 설계부터 저장/조회 로직까지 빠르게 나왔다. 한계는 분명하다. 브라우저 캐시를 지우면 데이터가 사라진다. 기기 간 동기화도 안 된다. 하지만 사이드 프로젝트에서 이 정도면 충분하다. ...

PWA로 만들기 — 웹앱을 '앱처럼' 느끼게 하는 기술

PWA로 만들기 — 웹앱을 "앱처럼" 느끼게 하는 기술 앱스토어 없이 앱을 배포할 수 있다면 "이거 앱스토어에 올려야 하는 거 아니에요?" 타로 마스터를 보여줄 때마다 받는 질문이었다. 웹 브라우저에서 동작하는 걸 보여주면 다들 고개를 갸웃거렸다. 앱처럼 생겼는데 앱이 아니라니. 바로 그 "앱처럼"을 만들어주는 기술이 PWA다. Progressive Web App, 줄여서 PWA. 이름은 거창하지만 핵심은 간단하다. 웹사이트에 몇 가지 설정을 추가해서, 사용자의 홈 화면에 설치되고, 오프라인에서도 동작하며, 푸시 알림도 보낼 수 있게 만드는 기술이다. 앱스토어 심사도 없고, 개발자 등록비도 없다. 사이드 프로젝트에는 이보다 좋은 선택이 없다. 사이드 프로젝트에 PWA가 적합한 현실적 이유 네이티브 앱을 만드는 것은 사이드 프로젝트에서 거의 불가능에 가까운 선택이다. iOS 앱을 만들려면 Swift를 배워야 하고, 연간 99달러의 개발자 등록비를 내야 하며, 앱스토어 심사를 통과해야 한다. 안드로이드까지 지원하려면 Kotlin도 필요하다. React Native나 Flutter로 크로스 플랫폼을 노릴 수도 있지만, 새로운 프레임워크를 배우는 시간이 추가된다. PWA는 이미 만들어놓은 웹앱에 설정 파일 몇 개를 추가하는 것만으로 된다. 이미 React와 Vite로 만들어둔 타로 마스터에 PWA를 입히는 건, 완전히 새로운 기술을 도입하는 게 아니라 기존 기술 위에 레이어를 하나 얹는 것에 가깝다. 비용 측면에서도 비교가 안 된다. 앱스토어 등록 없이 URL 하나로 배포할 수 있고, 업데이트도 서버에 파일만 올리면 끝이다. 심사 대기 시간이 없으니 버그 수정도 즉시 반영된다. 혼자서 만드는 사이드 프로젝트에 이보다 현실적인 선택은 없다. vite-plugin-pwa: 설정 한 줄로 시작하기 Vite 기반 프로젝트에서 PWA를 구현하는 건 놀라울 정도로 간단했다. vite-plugin-pwa라는 플...

배포의 세계 — GitHub Pages부터 Cloudflare까지

배포의 세계 — GitHub Pages부터 Cloudflare까지 코드를 로컬에서 돌리는 것과 세상에 공개하는 것 사이에는 생각보다 넓은 간극이 있다. 배포는 단순히 파일을 서버에 올리는 게 아니라, 인프라 아키텍처를 결정하는 일이다. 사이드 프로젝트의 배포 옵션들 프론트엔드 프로젝트를 배포할 수 있는 선택지는 여러 가지다. Vercel, Netlify, GitHub Pages, Cloudflare Pages 등. 각각 장단점이 뚜렷하다. Vercel은 Next.js 팀이 만든 플랫폼답게 React 프로젝트 배포에 최적화되어 있다. 서버리스 함수도 제공하고, 프리뷰 배포도 자동으로 해준다. 하지만 무료 티어에 상업적 사용 제한이 있고, 대역폭 한도도 있다. Netlify도 비슷한 포지션이다. 빌드 자동화와 서버리스 함수, 폼 처리 등 편의 기능이 많다. 무료 티어가 넉넉한 편이지만, 빌드 시간에 월별 한도가 있다. GitHub Pages는 가장 단순하다. GitHub 저장소에 정적 파일을 올리면 끝이다. 서버리스 함수가 없고, 빌드 자동화도 GitHub Actions를 직접 설정해야 한다. 하지만 완전 무료이고, 제한이 거의 없다. GitHub Pages를 선택한 이유 결론부터 말하면 GitHub Pages를 선택했다. 이유는 세 가지다. 완전 무료다. 대역폭 한도가 월 100GB로 넉넉하고, 사이트 크기 제한도 1GB까지다. 사이드 프로젝트로서는 도달하기 어려운 수치다. 단순하다. 빌드된 정적 파일을 특정 브랜치에 푸시하면 배포가 완료된다. 별도의 계정 설정이나 플랫폼 학습이 필요 없다. 이미 GitHub을 쓰고 있으니 추가 도구가 하나도 없다. React SPA에 충분하다. 타로 마스터는 정적 사이트다. 서버 사이드 로직이 없고, API 호출은 Cloudflare Workers가 담당한다. 정적 파일 호스팅만 하면 되는 상황에서 Vercel이나 Netlify의 고급 기능은 필요 없었다. GitHub Pages의 단점도 있다. SPA 라...

콘텐츠로 승부하기 — 가이드 페이지와 칼럼으로 SEO 영토 확장

콘텐츠로 승부하기 — 가이드 페이지와 칼럼으로 SEO 영토 확장 SEO 기술적 최적화를 마쳤는데도 검색 유입은 미미했다. 기능 페이지만으로는 사람들이 검색하는 키워드를 커버할 수 없었기 때문이다. 앱이 아니라 콘텐츠가 필요했다. 기능 페이지만으로는 안 된다 타로 마스터의 핵심 기능 페이지는 몇 개 되지 않는다. 메인 페이지, 리딩 모드 선택, 카드 선택, 결과 페이지. 이 페이지들의 메타 태그를 아무리 잘 최적화해도, 사람들이 검색하는 키워드와 매칭되지 않는다. 사람들은 "무료 타로 리딩"이라고도 검색하지만, "타로 카드 의미", "메이저 아르카나 해석", "켈틱 크로스 배열법", "타로 카드 역방향 뜻" 같은 정보성 키워드를 훨씬 더 많이 검색한다. 이런 키워드에 대응하는 콘텐츠가 없으면, 아무리 좋은 앱을 만들어도 검색으로는 찾아오지 않는다. 검색 유입의 현실은 냉정하다. 앱의 기능이 아무리 훌륭해도, 검색 엔진은 텍스트 콘텐츠를 읽는다. 콘텐츠가 없으면 검색에 노출되지 않고, 노출되지 않으면 존재하지 않는 것과 같다. "기능 앱"에서 "콘텐츠 허브"로 전략을 바꾸기로 했다. 타로 마스터를 단순한 리딩 앱이 아니라, 타로에 관한 모든 정보를 제공하는 콘텐츠 허브로 만드는 것이다. 리딩 기능은 그 허브의 핵심 기능이지만, 사용자를 끌어오는 것은 콘텐츠의 역할이다. 이 전략은 간단한 퍼널 구조를 따른다. 정보성 검색("타로 카드 의미")으로 가이드 페이지에 유입된 사용자가 "직접 리딩을 해보고 싶다"는 생각으로 리딩 기능을 사용한다. 콘텐츠가 유입 경로가 되고, 기능이 체류 이유가 되는 구조다. 문제는 콘텐츠의 양이다. 타로 관련 정보성 키워드를 충분히 커버하려면 상당한 양의 콘텐츠가 필요하다. 혼자서 수십 편의 가이드를 작성하는 건 현실적으로 불가능하다. 여기서 AI의 역할이 다시 등...

SEO를 처음부터 생각했어야 했다 — 뒤늦은 최적화 분투기

SEO를 처음부터 생각했어야 했다 — 뒤늦은 최적화 분투기 사이트를 공개한 지 2주가 지났는데 구글 검색 유입이 0이었다. 알고 보니 구글 봇 눈에는 내 사이트가 텅 빈 HTML 파일이었다. React SPA의 근본적 한계를 그때서야 깨달았다. 검색 유입 제로의 충격 프로젝트를 GitHub Pages에 올리고 나서, 매일 구글 서치 콘솔을 확인했다. 노출 0, 클릭 0. 일주일이 지나도 변함없었다. "아직 인덱싱이 안 됐나 보다"라고 자위했지만, 2주째에 현실을 직시했다. URL 검사 도구로 확인해보니, 구글이 크롤링한 페이지의 렌더링 결과가 거의 비어 있었다. 타이틀과 간단한 로딩 스피너만 보이고, 실제 콘텐츠는 없었다. 이유는 단순했다. React SPA는 JavaScript가 실행되어야 콘텐츠가 렌더링되는데, 구글 봇이 항상 JavaScript를 완벽하게 실행하는 건 아니기 때문이다. SPA의 근본적 SEO 문제 전통적인 웹사이트는 서버에서 HTML을 완성해서 보내준다. 구글 봇이 페이지를 요청하면 콘텐츠가 담긴 HTML을 받는다. 하지만 React SPA는 다르다. 서버가 보내는 HTML은 빈 껍데기이고, 브라우저에서 JavaScript가 실행되면서 비로소 콘텐츠가 채워진다. 구글은 JavaScript를 렌더링할 수 있다고 공식적으로 밝히고 있지만, 실제로는 두 단계 인덱싱 과정을 거친다. 첫 번째 단계에서 HTML만 보고, 두 번째 단계에서 JavaScript를 렌더링한다. 두 번째 단계까지 가는 데 시간이 걸리고, 모든 페이지가 완벽하게 렌더링되는 것도 아니다. Next.js나 Remix 같은 프레임워크를 썼다면 서버 사이드 렌더링(SSR)으로 이 문제를 자연스럽게 해결할 수 있었다. 하지만 이 프로젝트는 Vite로 시작했고, 지금 와서 프레임워크를 바꾸는 건 사실상 처음부터 다시 만드는 것과 같았다. 프리렌더링이라는 절충안 SSR을 도입할 수 없다면, 대안은 프리렌더링이다. 빌드 시점에 각 페이지의 HTML을 미...

AI 해석 기능 붙이기 — 서버리스 API 아키텍처

AI 해석 기능 붙이기 — 서버리스 API 아키텍처 타로 카드를 뒤집었을 때 매번 똑같은 해석이 나온다면, 두 번째 리딩부터는 흥미가 사라진다. AI 실시간 해석은 선택이 아니라 필수였다. 문제는 비용이었다. 처음에는 고정 텍스트였다 프로젝트 초기에는 78장 각각에 대해 미리 작성된 해석 텍스트를 보여줬다. 정방향과 역방향까지 합하면 156개의 해석문이 필요했고, 이걸 JSON 파일에 담아서 카드가 뒤집어질 때 매칭해서 보여주는 방식이었다. 이 방식의 장점은 명확하다. API 호출이 없으니 비용이 제로이고, 지연 시간도 없다. 카드를 뒤집는 순간 해석이 바로 나타난다. 하지만 치명적인 단점이 있었다. 한 번 본 해석은 다음에도 똑같다. 타로 리딩의 핵심은 "같은 카드라도 맥락에 따라 다른 메시지를 전달한다"는 것인데, 이걸 구현할 수 없었다. AI 해석으로 확장하기로 한 순간 결정적 계기는 쓰리카드 리딩이었다. 과거-현재-미래 세 장의 카드가 나왔을 때, 각각의 고정 해석을 나열하는 건 의미가 있지만, 세 장의 관계를 엮어서 해석하는 건 불가능했다. "과거에 이런 카드가 나왔기 때문에 현재의 이 카드는 이런 의미를 가진다"는 맥락적 해석은 AI만이 할 수 있었다. 문제는 비용이었다. OpenAI GPT-4 기준으로 한 번 해석에 수백 토큰이 들고, 하루 수백 건의 리딩이 발생하면 비용이 급격히 올라간다. 사이드 프로젝트에 월 수십 달러를 쓸 수는 없었다. Groq API라는 발견 Groq는 자체 LPU 칩을 사용해서 LLM 추론 속도가 극도로 빠른 서비스다. 중요한 건 무료 티어가 있다는 점이었다. 하루 14,400건의 요청이 가능하고, LLaMA 3.3 70B 모델을 사용할 수 있다. LLaMA 3.3 70B는 오픈소스 모델치고 성능이 상당히 좋다. 타로 해석 같은 창작적 텍스트 생성에서 GPT-4에 비해 크게 뒤지지 않았다. 무엇보다 Groq의 추론 속도가 압도적이었다. 일반적인 LLM API가 응답에 ...

마이크로 인터랙션의 힘 — 작은 애니메이션이 체감 품질을 바꾼다

마이크로 인터랙션의 힘 — 작은 애니메이션이 체감 품질을 바꾼다 기능은 완성됐는데 뭔가 허전했다. 카드를 클릭하면 결과가 나오긴 하는데, '타로 리딩을 받고 있다'는 느낌이 전혀 들지 않았다. 그 차이를 만든 건 기능이 아니라 애니메이션이었다. 움직이지 않는 카드는 죽은 카드다 타로 리딩의 핵심 경험은 "선택"이다. 78장의 카드 중 어떤 카드를 고를 것인가. 실제 타로에서는 카드를 펼쳐놓고 손을 가져다 대면 어떤 카드가 끌리는 느낌이 있다. 화면 속 카드에도 그런 생동감이 필요했다. 처음에는 카드가 그냥 정적인 이미지로 나열되어 있었다. 클릭하면 뒤집어지긴 하지만, 화면에 놓인 카드들이 그저 버튼처럼 느껴졌다. 타로 카드가 아니라 쇼핑몰의 상품 목록 같았다. 첫 번째 시도: floating 애니메이션 아직 선택하지 않은 카드에 미세한 떠다니는 움직임을 넣었다. 위아래로 아주 살짝, 2~3픽셀 정도만 움직이는 부드러운 반복 애니메이션이다. 카드마다 시작 타이밍을 조금씩 다르게 줘서, 전체가 숨을 쉬는 것처럼 보이게 했다. 이 작은 변화만으로 화면의 느낌이 완전히 달라졌다. 카드들이 살아있다는 인상을 준다. "나를 골라달라"고 말하는 것 같은 시각적 힌트가 생긴다. 사소하지만, 사용자가 카드 앞에서 머무는 시간이 체감상 눈에 띄게 늘었다. hover와 click: 실체감을 만드는 두 가지 트릭 카드 위에 마우스를 올리면 1.05배 확대된다. 겨우 5퍼센트인데, 이게 "이 카드를 선택하려는 중"이라는 피드백을 확실하게 준다. 클릭하는 순간에는 0.95배로 살짝 줄어든다. 실제로 카드를 손가락으로 누르는 것 같은 눌림 효과다. 이 두 가지를 조합하면 hover(확대) 후 click(축소) 후 뒤집기라는 자연스러운 흐름이 만들어진다. 물리적 카드를 다루는 느낌에 한 걸음 더 가까워진다. 재미있는 건, 이 효과를 빼면 사용자들이 "뭔가 반응이 느린 것 같다"고 느낀다...

켈틱 크로스의 도전 — 복잡한 레이아웃을 CSS Grid로 풀기

켈틱 크로스의 도전 — 복잡한 레이아웃을 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-...

React 컴포넌트 설계 — AI와 함께 잡는 컴포넌트 구조

React 컴포넌트 설계 — AI와 함께 잡는 컴포넌트 구조 화면이 예뻐도 구조가 엉키면 끝이다 색상 팔레트는 완성했고, 별 배경은 흐르고, 카드 뒤집기 애니메이션은 매끄럽다. 78장의 이미지도 확보했다. 이제 이 모든 요소를 하나의 앱으로 조립해야 한다. 그런데 무작정 코드를 치기 시작하면 나중에 반드시 후회한다. 프론트엔드 개발에서 컴포넌트 구조는 건물의 골조와 같다. 외관이 아무리 멋져도 골조가 부실하면 새로운 기능을 추가하거나 버그를 수정할 때마다 벽이 무너진다. 특히 타로 앱처럼 여러 리딩 모드와 복잡한 상태 전이가 있는 프로젝트에서는 초기 구조 설계가 이후 개발 속도를 결정한다. 화면 흐름부터 정리하기 컴포넌트를 나누기 전에 사용자의 화면 흐름을 먼저 정리했다. 시작 화면에서 리딩 모드를 선택하고, 카드를 뽑고, 결과를 확인하는 세 단계다. 각 단계를 더 세분화하면 이렇다. 시작 화면에서 사용자는 네 가지 리딩 모드 중 하나를 선택한다. 원카드, 쓰리카드, 켈틱크로스, 자유선택. 모드를 선택하면 카드 선택 화면으로 이동한다. 여기서 뒤집힌 카드들 중에서 필요한 수만큼 카드를 고른다. 카드를 모두 고르면 뒤집기 애니메이션이 재생되고, 선택된 카드의 의미와 AI 해석이 표시된다. 이 흐름을 AI에게 설명할 때 중요한 것은 "화면"과 "데이터"를 분리해서 전달하는 것이었다. "시작 화면 → 카드 선택 → 결과 표시"라는 화면 흐름과 함께, "선택된 모드 → 뽑힌 카드 배열 → 각 카드의 위치와 방향 → AI 해석 텍스트"라는 데이터 흐름을 별도로 설명했다. AI에게 컴포넌트 구조를 요청하는 팁 AI에게 "타로 앱의 컴포넌트 구조를 설계해줘"라고 바로 요청하면 너무 일반적인 결과가 나온다. App, Header, Footer, Card, Reading 같은 누구나 떠올릴 수 있는 구조가 제시된다. 이걸 쓸 수 있긴 하지만, 프로젝트의 구체적인 요구사항을...

무료 이미지를 찾아서 — 저작권, 깨진 URL, 호스팅 불안정과의 싸움

무료 이미지를 찾아서 — 저작권, 깨진 URL, 호스팅 불안정과의 싸움 78장의 이미지가 필요하다 화면 디자인은 완성되었다. 별이 흐르는 배경 위에서 카드가 아름답게 뒤집힌다. 문제는 뒤집힌 카드 앞면에 보여줄 이미지가 없다는 것이었다. 타로 덱은 메이저 아르카나 22장과 마이너 아르카나 56장, 총 78장으로 구성된다. 78장의 이미지를 모두 확보해야 한다. 상업용 타로 덱의 이미지를 사용하려면 라이선스 비용이 발생한다. 사이드 프로젝트에 수십만 원을 투자하고 싶지는 않았다. 그렇다면 무료 이미지를 찾아야 한다. 여기서부터 예상치 못한 긴 여정이 시작되었다. 퍼블릭 도메인의 함정: 1909 vs 1971 타로 이미지 하면 가장 먼저 떠오르는 것이 라이더-웨이트-스미스(RWS) 덱이다. 1909년에 아서 에드워드 웨이트가 기획하고 파멜라 콜먼 스미스가 그린 이 덱은 현대 타로의 표준이다. 대부분의 타로 앱이 이 이미지를 사용한다. "1909년이면 저작권이 만료되었을 테니 자유롭게 쓸 수 있겠다"고 생각했다. 반은 맞고 반은 틀렸다. 파멜라 콜먼 스미스의 원본 흑백 선화는 1909년 작품으로, 대부분의 국가에서 저작권이 만료되어 퍼블릭 도메인이다. 하지만 우리가 흔히 보는 컬러 버전은 다른 이야기다. 1971년에 US Games Systems가 새로운 색채로 칠한 버전을 출시했다. 이 컬러링에 대한 저작권은 여전히 유효할 수 있다. 즉, 같은 그림이라도 "어느 버전의 색칠인가"에 따라 저작권 상태가 달라진다. AI에게 이 부분을 물어봤을 때 처음에는 "RWS는 퍼블릭 도메인이므로 자유롭게 사용할 수 있다"는 답을 받았다. 틀린 답은 아니지만, 컬러 버전에 대한 중요한 뉘앙스가 빠져 있었다. 추가로 질문을 던져서야 1971년 컬러 버전의 저작권 이슈에 대한 설명을 얻을 수 있었다. 이 경험은 중요한 교훈을 남겼다. AI의 첫 번째 답변을 그대로 믿지 말고, 특히 법적 문제에서는 반드시 후속 질문으...

CSS만으로 마법 만들기 — 별이 흐르는 배경과 3D 카드 뒤집기

CSS만으로 마법 만들기 — 별이 흐르는 배경과 3D 카드 뒤집기 애니메이션 없는 타로 앱은 카드를 뒤집지 않는 것과 같다 타로 리딩에서 카드를 뒤집는 순간은 가장 극적인 순간이다. 손으로 카드를 천천히 뒤집을 때의 긴장감, 그 앞면이 드러나는 순간의 설렘. 이걸 웹 앱에서 "클릭하면 이미지가 바뀌는 것"으로 구현하면 그 모든 감정이 사라진다. 그래서 애니메이션에 진지하게 투자하기로 했다. 배경부터 카드 인터랙션까지, CSS와 Framer Motion만으로 어디까지 표현할 수 있는지 실험하는 과정이었다. 결론부터 말하면, 생각보다 훨씬 많은 것이 가능했다. 별이 흐르는 배경: radial-gradient 3중 레이어 남색 배경(#0a0a1a)은 충분히 아름답지만, 그것만으로는 밋밋하다. 밤하늘에 별이 없으면 그냥 어두운 천장을 보는 것과 같다. 별 배경을 만드는 방법은 여러 가지가 있다. Canvas API, SVG 애니메이션, 심지어 별 이미지를 깔 수도 있다. 하지만 가장 가볍고 우아한 방법은 CSS radial-gradient를 중첩하는 것이었다. 핵심 아이디어는 간단하다. 아주 작은 원형 그라데이션을 무수히 많이 반복하면 별처럼 보인다. 이걸 크기가 다른 세 개 레이어로 나누면 깊이감이 생긴다. 가장 작은 별은 빠르게, 중간 별은 보통 속도로, 큰 별은 느리게 움직이면 패럴랙스 효과까지 얻을 수 있다. 첫 번째 레이어는 1px 크기의 아주 작은 별들이다. 화면 전체에 촘촘히 흩뿌려진 이 별들은 마치 먼 은하수처럼 보인다. 두 번째 레이어는 2px짜리 별로, 첫 번째보다 좀 더 밝고 덜 촘촘하다. 세 번째 레이어는 3px로, 가장 밝고 드문드문 배치된다. 이 세 레이어를 background 속성에 쉼표로 연결하면 하나의 요소에 모두 적용할 수 있다. 여기에 CSS animation으로 각 레이어의 background-position을 서로 다른 속도로 움직이면 별이 천천히 흐르는 효과가 완성된다. 가장 작은 별은 1분에 한...

분위기를 코드로 번역하기 — UI/UX 설계에서 AI의 역할

"분위기"를 코드로 번역하기 — UI/UX 설계에서 AI의 역할 효율적인 앱과 몰입하는 앱은 다르다 "사용하기 편한 앱"을 만드는 것과 "들어가면 빠져드는 앱"을 만드는 것은 완전히 다른 문제다. 타로 앱을 만들면서 가장 먼저 부딪힌 질문이 바로 이것이었다. 일반적인 SaaS나 쇼핑몰이라면 버튼 색상, 여백, 폰트 크기 같은 표준적인 UI 가이드라인을 따르면 된다. 하지만 타로 앱은 처음부터 다른 접근이 필요했다. 사용자가 카드를 뒤집는 순간, 그 결과에 진지하게 집중하려면 화면 자체가 하나의 "공간"이 되어야 한다. 마치 어두운 방에서 촛불 하나를 켜고 카드를 펼치는 것처럼. 이 분위기를 코드로 어떻게 만들 수 있을까. 문제는 "분위기"라는 단어가 너무 추상적이라는 점이었다. "신비로운 느낌", "밤하늘 같은 깊이감", "고급스러우면서도 접근하기 쉬운". 이런 요구사항은 디자이너에게 전달해도 해석이 갈리는데, 과연 AI에게 전달하면 어떤 결과가 나올까. 추상적인 감각을 구체적 코드로 변환하기 AI에게 처음 던진 질문은 단순했다. "타로 카드 리딩 앱에 어울리는 색상 팔레트를 추천해줘." 돌아온 답변은 예상대로 일반적이었다. 보라색, 검은색, 금색 같은 뻔한 조합이었다. 이건 누구나 떠올릴 수 있는 수준이다. 접근 방식을 바꿨다. "밤하늘을 올려다볼 때 느껴지는 깊이감을 배경색으로 표현하고 싶다. 순수한 검은색은 너무 평면적이고, 남색 계열이 좋겠다. 별빛처럼 작은 포인트가 있으면서도 텍스트 가독성이 유지되어야 한다." 이렇게 구체적인 감각을 설명하자 결과가 달라졌다. AI가 제안한 팔레트 중에서 남색 배경 #0a0a1a가 눈에 들어왔다. 순수 검정(#000000)보다 미세하게 푸른 기운이 도는 이 색은 화면에서 놀라운 차이를 만들어냈다. 검정 배경은 화면이 꺼진 느낌을...

AI로 156개의 해석 텍스트 만들기 — 대량 콘텐츠 생성의 전략

AI로 156개의 해석 텍스트 만들기 — 대량 콘텐츠 생성의 전략 78장의 카드, 각각 정방향과 역방향. 총 156개의 해석 텍스트가 필요하다. 각 해석은 자연스러운 한국어 3~4문장, 점술 특유의 톤을 유지해야 한다. 개발자 혼자 이걸 직접 쓰는 건 비현실적이다. 하지만 AI에게 "156개 만들어줘"라고 던지면 될까? 그것도 아니다. 이 글은 AI를 활용한 대량 콘텐츠 생성에서 "프레임워크 먼저, 생성 나중에"라는 원칙이 왜 중요한지, 그리고 생성된 콘텐츠를 어떻게 검증했는지에 대한 이야기다. "프레임워크 먼저, 생성 나중에" AI에게 대량의 텍스트를 생성 요청할 때 가장 흔한 실수가 있다. 바로 "그냥 만들어줘"다. "78장의 타로카드 해석을 만들어줘"라고 요청하면, AI는 만들어준다. 하지만 결과물에 일관성이 없다. 앞쪽 카드와 뒤쪽 카드의 톤이 다르고, 비슷한 의미를 가진 카드끼리 표현이 겹치고, 어떤 해석은 길고 어떤 해석은 짧다. 해결책은 생성 전에 프레임워크를 먼저 정의하는 것이다. "무엇을, 어떤 톤으로, 어떤 구조로, 얼마만큼의 길이로" 쓸 것인지를 먼저 정하고, 그 프레임워크 안에서 생성을 요청한다. 요리로 치면 레시피를 먼저 쓰고 나서 요리하는 것이다. 타로 마스터의 해석 텍스트 프레임워크는 크게 네 가지 축으로 구성됐다. 톤 가이드라인, 길이 기준, 슈트별 주제 프레임워크, 역방향 해석 철학이다. 한국어 해석의 톤 잡기 가장 먼저 정해야 할 축은 톤이다. 타로 해석은 일반적인 정보 전달이 아니다. 점술 특유의 분위기가 있어야 한다. 영어권 타로 해석은 "You are entering a period of transformation"처럼 2인칭 직접 화법이 일반적이다. 이걸 그대로 한국어로 번역하면 "당신은 변화의 시기에 접어들고 있습니다"가 된다. 문법적으로 틀리지 않지만, 한국적 ...

코드보다 먼저, 데이터 — 콘텐츠 중심 프로젝트의 설계 원칙

코드보다 먼저, 데이터 — 콘텐츠 중심 프로젝트의 설계 원칙 스펙이 정해지면 대부분의 개발자가 하고 싶어하는 일이 있다. 코드를 쓰는 것이다. 터미널을 열고, 프로젝트를 초기화하고, 첫 번째 컴포넌트를 만드는 그 쾌감. 나도 그 유혹에 빠질 뻔했다. 하지만 이 프로젝트에서 가장 먼저 해야 할 일은 코드가 아니라 데이터였다. 이 글은 78장의 타로카드 데이터를 어떤 구조로 설계했는지, 그리고 왜 코드보다 데이터가 먼저여야 하는지에 대한 이야기다. 왜 데이터가 먼저인가 타로 마스터에서 사용자가 실제로 "경험"하는 것은 무엇인가. 화려한 UI가 아니다. 카드 해석 텍스트다. 사용자가 카드를 뒤집고 가장 먼저 읽는 것, 가장 오래 머무는 것, 가장 기억에 남는 것이 해석 텍스트다. UI가 아무리 화려해도 해석이 빈약하면 앱의 가치는 없다. 이것은 타로 앱만의 문제가 아니다. 콘텐츠 중심 프로젝트에서 데이터는 곧 제품이다. 요리 레시피 앱이라면 레시피 데이터가 핵심이고, 사전 앱이라면 단어 데이터가 핵심이다. 이런 프로젝트에서 데이터 설계를 소홀히 하고 UI부터 만들면, 나중에 데이터 구조가 바뀔 때 UI 전체를 뒤엎어야 하는 상황이 온다. 데이터 스키마가 바뀌면 영향 범위가 프로젝트 전체에 미친다. 데이터를 표시하는 UI 컴포넌트가 바뀌고, 데이터를 처리하는 로직이 바뀌고, 데이터를 불러오는 방식이 바뀐다. 반대로 데이터 스키마가 확정되면, 나머지는 그 스키마에 맞춰 자연스럽게 따라온다. 그래서 원칙을 세웠다. 코드를 한 줄도 쓰기 전에 데이터 구조부터 확정한다. 78장, 단순 계산의 함정 먼저 필요한 데이터의 양을 계산해봤다. 78장의 카드 각각에 정방향과 역방향 해석이 필요하다. 단순 계산으로 156개의 해석 텍스트다. 여기에 각 카드의 기본 정보(이름, 타입, 번호, 이미지 경로)와 키워드가 추가된다. 156개의 해석 텍스트. 이 숫자가 처음에는 그냥 "많다" 정도로 느껴졌다. 하지만 각 해석이 3~4문장의 ...

10분 만에 스펙 정하기 — AI와의 의사결정 프레임워크

10분 만에 스펙 정하기 — AI와의 의사결정 프레임워크 프로젝트 기획에서 가장 고통스러운 순간은 "선택"이다. 기술 스택을 뭘로 할지, 어떤 기능을 넣을지, 범위를 어디까지 잡을지. 선택지가 동시에 밀려오면 하나도 결정하지 못하는 상태에 빠지기 쉽다. 의사결정 마비다. 이 글은 AI가 던진 세 가지 질문으로 프로젝트의 전체 스펙이 10분 만에 확정된 이야기다. 핵심은 AI가 "대신 결정해준 것"이 아니라, 적절한 질문으로 내 안의 답을 끄집어낸 것이다. 모호함이라는 적 1편에서 Claude에게 "타로카드를 보는 웹페이지를 만드려고 해"라고 던졌고, 2편에서 타로 덱의 기본 구조와 저작권 이슈를 파악했다. 도메인 지식은 어느 정도 갖춰졌다. 이제 남은 것은 "구체적으로 뭘 만들 것인가"를 정하는 일이다. 문제는 이 단계에서 선택지가 갑자기 폭발한다는 것이다. 리딩 모드는 뭘 넣을까? 해석은 어떤 방식으로 제공할까? 기술 스택은? 배포는? 데이터베이스가 필요한가? PWA로 만들까? 이 질문들이 한꺼번에 머릿속에 쏟아지면, 아무것도 결정하지 못하고 "좀 더 생각해보자"는 결론에 도달한다. 사이드 프로젝트가 죽는 두 번째 시점이다. AI와의 대화에서 인상적이었던 것은, 이 모든 질문을 한꺼번에 던지지 않았다는 것이다. Claude는 세 가지 핵심 축을 순서대로 제시했다. 리딩 방식이라는 출발점 처음 돌아온 질문은 리딩 방식이었다. 선택지는 네 가지였다. 원카드(오늘의 운세), 쓰리카드(과거/현재/미래), 켈틱 크로스(10장, 종합 분석), 자유 선택(사용자가 장수 지정). 이 질문이 가장 먼저 온 것은 논리적으로 맞다. 리딩 방식이 정해져야 UI 설계가 가능하고, 필요한 데이터의 형태가 결정되기 때문이다. 하지만 나 혼자 기획했다면 이 순서를 잡지 못했을 가능성이 높다. 아마 기술 스택부터 고민하거나, 전체 기능 목록부터 나열했을 것이다. 내 선택은 원카드, ...

모르는 분야에 뛰어들기 — AI로 도메인 지식 빠르게 습득하기

모르는 분야에 뛰어들기 — AI로 도메인 지식 빠르게 습득하기 당신이 전혀 모르는 분야의 프로젝트를 맡게 됐다고 상상해보자. 의료, 법률, 물류, 혹은 타로. 본능적으로 드는 생각은 아마 "일단 공부해야 한다"일 것이다. 그리고 그 공부의 첫 단계는 보통 구글링이다. 하지만 구글링에는 구조적인 한계가 있다. 이 글은 타로에 대해 거의 아무것도 몰랐던 개발자가, AI와의 대화만으로 프로젝트에 필요한 수준의 도메인 지식을 빠르게 습득한 이야기다. 타로에 대해 내가 알고 있던 것 솔직하게 고백하면, 프로젝트를 시작할 때 내가 타로에 대해 아는 건 이 정도였다. 카드를 뒤집어서 점을 보는 것. 카드마다 그림이 다르고 의미가 있는 것. 드라마에서 본 "죽음" 카드가 꼭 나쁜 의미는 아니라는 것. 이게 전부였다. 카드가 총 몇 장인지도 몰랐다. 어떤 종류가 있는지도 몰랐다. 리딩을 어떻게 하는지도 몰랐다. 그리고 가장 중요한 것, 카드 이미지를 어디서 구해야 하는지도 몰랐다. 전통적인 방법이었다면 "타로카드 기본 구조"를 검색하고, 위키피디아를 읽고, 타로 관련 사이트를 돌아다니며 정보를 모았을 것이다. 경험상 이런 식의 리서치는 최소 반나절이 걸리고, 정리가 안 된 정보 더미를 남긴다. 구글링의 구조적 한계 잠깐 구글링이라는 학습 방법의 한계를 짚어보자. 내가 경력 28년 동안 수없이 겪은 것이다. 검색은 내가 "무엇을 모르는지 아는 것"을 전제한다. "타로카드 구조"를 검색하려면, 적어도 "타로카드에 구조라는 게 있다"는 사실을 알아야 한다. 모르는 분야에서는 무엇을 검색해야 할지 자체를 모른다. 이것을 "질문을 모르는 상태"라고 부른다. 또한 정보의 깊이를 조절할 수 없다. "타로카드"를 검색하면 입문자용 글부터 전문가 수준의 해석론까지 뒤섞여 나온다. 내 프로젝트에 필요한 수준의 정보만 골라내는 것도 일이...

시작의 충동 — '타로 웹앱을 만들어볼까?'

시작의 충동 — "타로 웹앱을 만들어볼까?" 사이드 프로젝트는 대부분 기획서 작성 단계에서 죽는다. 당신도 경험이 있을 것이다. 번뜩이는 아이디어가 떠올라 노션을 열었다가, 기획을 정리하다 에너지를 다 써버리고, 결국 "나중에 해야지" 폴더에 묻히는 그 패턴 말이다. 이 글은 그 패턴을 깬 이야기다. AI에게 한 문장을 던지고, 10분 만에 프로젝트가 구체화된 경험에 대한 이야기다. 한 문장에서 시작된 프로젝트 "타로카드를 보는 웹페이지를 만드려고 해." 프로젝트의 시작은 이 한 문장이었다. 거창한 기획서도, PRD도, 경쟁사 분석도 없었다. 평소 타로에 관심이 있었고, 웹으로 만들면 재밌겠다는 생각이 전부였다. 구체적인 기능 목록도 없었고, 어떤 기술 스택을 쓸지도 정하지 않았다. 보통이라면 여기서 두 가지 중 하나가 벌어진다. 구글링을 시작해서 "타로 웹앱 만들기"를 검색하거나, 노션에 기획 문서를 만들고 목차부터 쓰기 시작하는 것이다. 두 방법 모두 나쁘지 않지만, 공통적인 위험이 있다. 실행에 들어가기 전에 에너지가 소진된다는 것이다. 이번에는 다르게 접근했다. Claude에게 바로 물었다. 이 선택 하나가 프로젝트 전체의 성격을 결정지었다. 왜 기획 단계에서 프로젝트가 죽는가 잠깐 이야기를 돌려서, 사이드 프로젝트가 왜 기획 단계에서 사라지는지 생각해보자. 내 경험상 이유는 세 가지다. 완벽주의의 함정이 있다. "제대로 시작하려면 제대로 기획해야지"라는 생각이 들면, 기획서의 목차를 쓰고, 벤치마킹할 서비스를 찾고, 기능 목록을 정리하기 시작한다. 그러다 보면 만들어야 할 것이 점점 커지고, "이건 주말에 할 수 있는 규모가 아닌데"라는 결론에 도달한다. 도메인 지식의 벽도 크다. 타로 웹앱을 만들려면 타로에 대해 알아야 한다. 카드가 몇 장인지, 어떤 종류가 있는지, 리딩은 어떻게 하는지. 이걸 알아보려고 검색을 시작하면 탭...