[AI 레버리지 #3] 들어올린 무게를 돈으로 환전해주는 앱, 운동 동기부여

혼자하는 웨이트 트레이닝 특성상 지속적인 동기부여가 없으면 운동 강도 유지가 어렵다.

그래서 사용하던 게 번핏이란 운동 기록 앱. 하루의 운동 프로그램을 미리 설정하고, 투두리스트 지우듯 내가 운동한 세트를 체크하면서 오늘 운동이 뭐고 얼마나 했고 얼마나 남았는지 시각화하는 자체로 어느 정도 동기부여가 된다.

이것도 그냥 혼자 체크버튼 누르는 거라. 점차 중량과 횟수를 대충 기재하는 걸 자각. 뭔가 다른 방식의 동기부여가 필요하단 생각이 들었다.

그럼 이걸 돈과 연결해 보면 어떨까? 내가 들어올린 중량만큼 용돈을 쓸 수 있다는 컨셉. 예를 들어 100킬로 스쿼트 1회 할때마다 천원을 용돈으로 적립해 내 맘대로 쓰는 것.

하루 10톤을 들어올리면 10만원 용돈이 생기는 거다. 일주일에 5일 운동해 50톤을 들어올리면 50만원을 쓸 수 있는 셈.

아이디어는 자기계발서에 자주 나오는, 긍정적 행동에 스스로 상을 주라는 데서 착안.

예전에 역도 수업 가기가 너무 귀찮을때 든 생각이 있다. 역도 수업 가는 걸 돈으로 환산하면 얼마의 가치가 있을까. 한 5만원쯤이란 생각이 들었다.

역도 수업 가는데 택시를 타건 끝나고 비싼 점심을 먹건. 5만원을 상으로 써도 될 정도로 가치있는 일이란 생각.

이런 흐름에서 만든게 ‘쇠 지갑’ 앱. 표어는 ‘많이 들고 많이 쓰자’.

추가로, LLM을 붙여 그날, 그 주 , 그 달의 운동 프로그램을 분석하는 코칭 기능도 넣었다.

범용 앱은 운동 코칭에서 시작해 기능을 확장해 나가겠지만. 나는 철저히 동기부여에서 시작해 코칭으로 넘어갔다.

아래는 클로드가 정리한 기획안

# 쇠 지갑 (Iron Wallet) 프로젝트 기획안

## 1. 프로젝트 개요

쇠 지갑은 웨이트 트레이닝에서 들어올린 무게의 총량(볼륨)을 일정 환율로 돈으로 환산해, 그만큼을 본인의 용돈으로 쓸 수 있게 만든 1인용 PWA다. 운동을 하면 적립이 쌓이고 운동을 쉬면 적립이 멈춘다. 누적된 잔액에서 실제 지출을 직접 기록해 차감한다. 운동 일지는 사용자가 평소에 쓰는 앱(번핏)의 텍스트 공유 기능을 그대로 받아 자동 파싱한다. 누적된 운동 데이터는 Gemini 3.1 Pro에게 넘겨 일·주·월 단위 분석과 다음 트레이닝 처방을 받는 코치 기능까지 연결되어 있다.

핵심 한 줄: **"많이 들고 많이 쓰자"**

---

## 2. 시작 — 왜 만들었나

웨이트 트레이닝을 거의 매일 하지만, 어느 시점부터 동기 부여가 떨어지는 구간이 온다. 운동을 하면 분명히 좋은 일이지만, 그 좋음이 추상적이라 즉각적인 보상으로 와닿지 않는다. 운동을 안 한다고 해서 당장 손해를 보는 것도 아니다.

이 추상적인 보상을 **즉각적인 화폐 단위**로 바꾸면 어떨까? 1톤 들면 1만 원. 하루 10톤이면 10만 원. 운동 안 한 날은 0원. 충분히 운동한 날은 그날의 적립액을 양심의 가책 없이 쓸 수 있다. 운동을 안 했으면 쓸 돈도 없다.

이 구조를 사용자가 직접 시험해보고 싶었다. 본인의 운동량과 본인의 소비 패턴을 환율 하나로 묶어보고 싶다는 매우 개인적인 동기에서 출발했다.

---

## 3. 핵심 컨셉

> **운동 볼륨(kg) → 환율 적용 → 적립금 → 실제 지출**

이 흐름이 전부다. 게이미피케이션의 본질은 단순하고 정직한 변환 함수다.

- **볼륨**: 무게 × 횟수 × 세트의 누적 합. 맨몸 운동에는 사용자 체중을 합산한다(풀업 한 회는 자기 몸을 한 번 들어올린 것).
- **환율**: 1톤당 원. 기본 1만 원, 사용자가 조정 가능. 본인의 운동 강도와 일일 소비 적정선을 맞추는 다이얼.
- **적립**: 운동 기록 저장 시 환율 적용해 적립액 산출, 잔액에 가산.
- **지출**: 사용한 돈을 직접 입력해 잔액에서 차감.

---

## 4. 타깃 사용자와 시나리오

타깃은 **본인 한 명**이다. 비개발자가 학습 목적으로 만드는 사이드 프로젝트이며, 처음부터 멀티유저 SaaS를 지향하지 않는다. 다만 Supabase Auth와 RLS는 도입되어 있어 추후 1인 → 다인 확장이 가능한 구조로 만들어두었다.

### 일일 사용 시나리오

1. 헬스장에서 평소처럼 **번핏(Burnfit)** 앱에 운동을 기록한다.
2. 운동이 끝나면 번핏의 "텍스트 공유" 버튼을 눌러 안드로이드 공유 시트를 띄운다.
3. 공유 시트에 노출된 **"쇠 지갑"** 아이콘을 누른다.
4. 쇠 지갑이 열리면서 운동 일지 텍스트가 입력 폼에 자동으로 채워져 있다. 파싱 결과(운동별 볼륨, 총 톤수, 적립 예정액)가 미리보기로 즉시 보인다.
5. 저장 → 홈 잔액에 반영.
6. 점심시간에 카페에서 5천 원을 썼으면 지출 화면에서 빠른 추가 버튼으로 입력.
7. 일주일에 한 번쯤 AI 코치에서 "이번 주 분석"을 눌러 운동 균형/정체/처방을 받는다.



## 6. 데이터 설계 결정

### 6.1 테이블 구조

| 테이블 | 역할 |
|---|---|
| `profiles` | 체중, 환율, 일일 목표 등 1인당 설정 |
| `workouts` | 날짜 단위 운동 기록 (1일 1개, 유니크 제약) |
| `exercises` | 운동 종목 (workout 하위) |
| `sets` | 세트별 무게/횟수 (exercise 하위) |
| `expenses` | 지출 |
| `coach_logs` | AI 코치 응답과 토큰 사용량 |

`workouts → exercises → sets`의 3단 정규화는 운동을 종목별·세트별 단위로 쿼리할 수 있게 만든다. 정체 분석, PR 추적, 부위별 통계 같은 분석 기능을 미리 구조에 깔아둔 것이다.

### 6.2 스냅샷 패턴

`workouts` 테이블에는 의도적으로 `user_weight_kg`, `exchange_rate` 컬럼을 함께 저장한다. 저장 당시의 조건을 그대로 박제한다. 이유는 신뢰다. 사용자가 다이어트로 체중을 5kg 줄이면 풀업 볼륨이 줄어든다. 환율을 두 배로 올리면 모든 과거 적립액이 두 배가 된다. 둘 다 받아들일 수 없다. 과거의 기록은 과거 조건으로 동결되어야 한다. 이를 코드에서 다루는 대신 데이터 스키마에서 강제했다.

### 6.3 잔액은 저장하지 않음

`balance` 컬럼은 어디에도 없다. 매 조회마다 `SUM(workouts.earned_amount) - SUM(expenses.amount)`로 계산한다. 동기화 버그의 원천을 차단하고, 진실의 출처를 항상 거래 내역에 두기 위함이다. 1인 앱의 데이터 양에서는 성능도 충분하다.

### 6.4 운동 부위 태그 — 저장하지 않음

처음에는 운동마다 push/pull/leg/core 태그 컬럼을 둘까 고민했다. 결론은 "LLM에게 맡기자". 운동명을 보고 푸시인지 풀인지 분류하는 일은 정적 매핑 테이블보다 LLM이 훨씬 유연하고, 새 운동이 추가될 때마다 매핑을 업데이트할 필요가 없다. 인간이 관리해야 할 메타데이터를 LLM이 추론으로 대체할 수 있다면 굳이 만들지 않는다.

### 6.5 RLS

모든 테이블에 Row Level Security를 켜고 본인 행만 조회·수정할 수 있도록 정책을 걸었다. 1인 앱이지만 보안 디폴트는 미리 만들어두는 편이 안전하다.

---
## 9. 마주친 문제와 해결

### 9.1 번핏에 공식 API가 없다

대안을 검토했다. 데이터 export 기능, 스크린샷 OCR, 직접 입력. 번핏의 "텍스트 공유" 기능이 가장 깔끔한 해법이었고, 이걸 텍스트 파서로 받기로 했다. 추후 공유 시트 직접 진입(Web Share Target)으로 발전시켰다.


### 9.3 안드로이드 네이티브 캘린더는 손댈 수 없다

처음에는 HTML `<input type="date">`로 날짜를 받았다. 사용자 피드백: "운동한 날을 캘린더에서 한 번에 보고 싶다." 그런데 네이티브 캘린더는 마커 주입이 불가능하다. 자체 캘린더 컴포넌트를 직접 만들었다(월 그리드 + 운동일 점 표시 + 월 단위 네비게이션). 운동 날짜 리스트는 페이지 진입 시 한 번만 fetch하고, 월 이동은 클라이언트에서 처리해 추가 API 호출이 없다.


### 9.6 다크 배경에 다크 텍스트

홈 화면의 잔액 텍스트가 보이지 않았다. 원인은 `globals.css`에 남아있던 `body { color: var(--foreground) }` 설정이 라이트 모드 기본값(거의 검정)으로 적용되면서 Tailwind의 `text-zinc-100`을 덮어쓴 것. globals.css를 정리해 Tailwind 유틸리티만으로 색을 다루도록 했다.

### 9.7 "버튼이 안 눌렸나?" UX 이슈

서버 액션 호출에 0.3~0.5초 지연이 있는데, 사용자는 그게 로딩인 줄 모르고 버튼을 다시 누르게 된다. 두 단계의 시각 피드백을 추가했다.

1. 버튼 자체에 `useTransition` + `animate-pulse` + `cursor-wait` → 클릭 즉시 펄스 애니메이션
2. 목적지 페이지에 `loading.tsx` → 페이지 전환 중 회전 스피너 + 한국어 라벨

홈의 모든 네비게이션 Link를 `NavButton` 컴포넌트로 교체하고, 서버 fetch가 있는 모든 라우트에 loading.tsx를 추가했다.

### 9.8 아이콘 디자인 — 세 번 갈아엎음

처음 만든 아이콘은 바벨 라인 일러스트 + 한글 "쇠" 텍스트 조합이었다. 사용자 평가: "후졌다." 두 번째는 가운데 바에 양쪽 동전을 단 디자인으로 만들었다. "이건 덤벨이지 바벨 플레이트가 아니다." 세 번째에서 KRATOS 범퍼 플레이트의 정면 사진을 레퍼런스로 받아, 단일 플레이트 정면 + 가운데 바 통과 구멍 + 위/아래에 거꾸로 새긴 금색 ₩ 심볼로 정착했다. 디자인 의도는 명확하다 — 이 도형은 바벨 플레이트인 동시에 동전이다. 아이콘 하나에 게이미피케이션 컨셉을 응축했다.

---

## 10. 다음 단계

지금까지 만든 것은 사용자가 일상에서 즉시 쓸 수 있는 MVP에 가깝다. 이후 가능한 확장:

- **시각화**: 월간 볼륨 추이, 주간 부위별 분포 차트
- **PR(Personal Record) 자동 감지**: 세트당 무게 신기록 갱신 시 축하 + 잔액 보너스
- **자동 코치 푸시**: 매주 일요일 자동으로 주간 분석을 미리 생성, 알림으로 받기
- **데이터 export**: CSV/JSON으로 운동·지출 내역 내보내기
- **모델 셀렉터**: 설정 화면에서 Pro/Flash 선택 (비용 vs 품질 트레이드오프)
- **친구 비교**: 멀티 유저 모드에서 친구의 누적 적립과 비교 — 사회적 게이미피케이션

다만 우선순위는 본인이 며칠 사용해보면서 자연스럽게 드러나는 결핍을 따라가는 것이다. 미리 정한 로드맵보다 실제 사용 경험이 더 정확한 신호다.

---

## 11. 마무리


매일 들어올린 무게가 매일 쓸 수 있는 돈이 된다. 이 한 문장이 작동하는 작은 도구를 만든 기록이다.

같은 날, 다른 해

  1. 2025 1년 전

  2. 2015 11년 전

  3. 2008 18년 전

    • ‘부치지 않은 편지’ 의 두 가지 종류

      그대만큼 사랑스러운 사람을 본 일이 없다 그대만큼 나를 외롭게 한 이도 없었다 이 생각을 하면 내가 꼭 울게 된다 그대만큼 나를 정직하게 해 준 이가 없었다 내…

      1. 책과 콘텐츠

“[AI 레버리지 #3] 들어올린 무게를 돈으로 환전해주는 앱, 운동 동기부여”에 대한 1개의 생각

댓글 남기기