Files
sodalive-ios/docs/20260704_크리에이터_채널_후원_탭/plan-task.md

266 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 크리에이터 채널 후원 탭 구현 계획
## 기준 문서
- PRD: `docs/20260704_크리에이터_채널_후원_탭/prd.md`
- 기존 크리에이터 채널 홈 PRD: `docs/20260701_크리에이터_채널_홈/prd.md`
- 기존 크리에이터 채널 홈 구현 계획: `docs/20260701_크리에이터_채널_홈/plan-task.md`
- 기존 크리에이터 채널 오디오 탭 구현 계획: `docs/20260704_크리에이터_채널_오디오_탭/plan-task.md`
- 코드 스타일: `docs/agent-guides/code-style.md`
- 빌드/검증: `docs/agent-guides/build-test-verification.md`
- Figma 전체 화면: `290:9093`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9093&m=dev`
- Figma 후원 랭킹 섹션: `290:9097`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9097&m=dev`
- Figma 후원 랭킹 최신 섹션: `665:19056`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=665-19056&m=dev`
- Figma Empty: `290:9009`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9009&m=dev`
## 구현 원칙
- 기존 `CreatorChannelView`의 공통 shell, header, sticky tab-bar, title bar 동작은 유지한다.
- `CreatorChannelTab.donation` 콘텐츠만 신규 `CreatorChannelDonationTabView`로 교체한다.
- 후원 mutation은 기존 `LiveRoomDonationDialogView``ChannelDonationViewModel.postChannelDonation` 흐름을 재사용한다.
- 후원 성공 후 홈 데이터와 후원 탭 첫 페이지를 모두 다시 조회한다.
- 후원 랭킹은 최대 8명, 한 줄에 4명씩 표시하고 `전체보기`는 기존 `AppStep.userProfileDonationAll(userId:)`로 이동한다.
- sort-bar는 정렬 없이 `전체 {donationCount}`만 표시한다.
- `rankings.isEmpty && donationCount == 0`이면 empty view 내부 후원하기 버튼만 표시하고 하단 고정 후원하기 버튼은 숨긴다.
- 단, 내 채널인 경우 empty view 내부 후원하기 버튼과 우측하단 후원하기 버튼을 모두 표시하지 않는다.
- `donationCount != 0 || !rankings.isEmpty`이면 홈 탭 plus button 위치에 `ic_new_donation` icon-only 후원하기 버튼을 표시한다.
- `CreatorChannelDonationResponse`는 홈 탭 모델의 기존 타입을 재사용한다.
- 후원 item UI는 홈 탭과 후원 탭에서 같은 표시 규칙을 갖도록 공용화한다.
- 서버 응답에 없는 secret 여부는 클라이언트에서 추론하지 않는다.
## 주요 대상 파일
### 생성
- `SodaLive/Sources/V2/CreatorChannel/Donation/Models/CreatorChannelDonationTabResponse.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationApi.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationRepository.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationViewModel.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationTabView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationRankingSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationCountBar.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationFloatingButton.swift`
### 수정
- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- `SodaLive/Sources/I18n/I18n.swift`
- 필요 시 `SodaLive.xcodeproj/project.pbxproj`
## TASK 체크리스트
### Phase 1: 문서와 API 모델
- [x] **Task 1.1: PRD와 계획 문서 생성**
- 대상 파일:
- 생성: `docs/20260704_크리에이터_채널_후원_탭/prd.md`
- 생성: `docs/20260704_크리에이터_채널_후원_탭/plan-task.md`
- 검증 기준:
- 실행 명령: `rg "290:9093|290:9097|290:9009|CreatorChannelDonationTabResponse|MemberDonationRankingResponse|/api/v2/creator-channels/.*/donations|UserProfileDonationAllView" docs/20260704_크리에이터_채널_후원_탭`
- 기대 결과: Figma URL, 응답 모델, API path 요구사항이 문서에 기록된다.
- [x] **Task 1.2: 후원 탭 Response model 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Models/CreatorChannelDonationTabResponse.swift`
- 작업 내용:
- `CreatorChannelDonationTabResponse``MemberDonationRankingResponse``Decodable`로 생성한다.
- `CreatorChannelDonationTabResponse.donations`는 기존 `[CreatorChannelDonationResponse]`를 사용한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelDonationTabResponse|donationCount|rankings|donations|hasNext|struct MemberDonationRankingResponse|donationCan" SodaLive/Sources/V2/CreatorChannel/Donation/Models/CreatorChannelDonationTabResponse.swift`
### Phase 2: API, Repository, ViewModel
- [x] **Task 2.1: 후원 탭 API endpoint 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationApi.swift`
- 작업 내용:
- `CreatorChannelDonationApi.getDonations(creatorId:page:size:)`를 만든다.
- path는 `/api/v2/creator-channels/{creatorId}/donations`로 구성한다.
- query parameter는 `page`, `size`만 보낸다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationApi|getDonations|/api/v2/creator-channels/|/donations|page|size|URLEncoding.queryString" SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationApi.swift`
- [x] **Task 2.2: 후원 탭 Repository 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationRepository.swift`
- 작업 내용:
- `MoyaProvider<CreatorChannelDonationApi>`를 사용하는 `getDonations(...)` publisher를 제공한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationRepository|MoyaProvider<CreatorChannelDonationApi>|AnyPublisher<Response, MoyaError>|getDonations" SodaLive/Sources/V2/CreatorChannel/Donation/Repository/CreatorChannelDonationRepository.swift`
- [x] **Task 2.3: 후원 탭 ViewModel 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationViewModel.swift`
- 작업 내용:
- 상태는 loading, loadingNextPage, response, rankings, donations, page, size, hasNext, hasLoaded, error toast를 둔다.
- `fetchFirstPage(creatorId:)``page=0`, `size=20`으로 조회한다.
- `fetchNextPageIfNeeded(creatorId:currentItem:)`는 마지막 item 기준으로 다음 페이지를 조회한다.
- 첫 페이지는 목록 교체, 다음 페이지는 append한다.
- 최신 요청만 반영하도록 `latestRequestId` 패턴을 사용한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationViewModel|fetchFirstPage|fetchNextPageIfNeeded|isLoadingNextPage|latestRequestId|rankings|donations|hasNext" SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationViewModel.swift`
### Phase 3: Figma 섹션별 UI
- [x] **Task 3.1: 후원 count bar 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationCountBar.swift`
- 작업 내용:
- Figma sort-bar 기준으로 높이 52pt, 좌측 `전체 {donationCount}`만 표시한다.
- 정렬 버튼/아이콘/action은 포함하지 않는다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationCountBar|donationCount|전체|frame\\(height: 52\\)|ic_new_sort|selectedSort" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationCountBar.swift`
- 기대 결과: count bar 구현이 확인되고 정렬 관련 문자열은 없어야 한다.
- [x] **Task 3.2: 후원 랭킹 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationRankingSection.swift`
- 작업 내용:
- `rankings`가 비어 있으면 섹션을 숨긴다.
- `후원 랭킹` title, `Color.gray900` 카드, 75pt 원형 프로필, 닉네임, rank number overlay를 구현한다.
- 후원 랭킹 item은 최대 8명만 표시하고 한 줄에 4명씩 표시한다.
- Figma `전체보기` 버튼을 표시하고 tap action은 부모에서 주입한다.
- 부모 action은 기존 `AppStep.userProfileDonationAll(userId: creatorId)` 이동으로 연결한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationRankingSection|후원 랭킹|MemberDonationRankingResponse|rank \\+ 1|75|DownsampledKFImage|전체보기|onTapViewAll" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationRankingSection.swift`
- [x] **Task 3.3: 후원 item 공용화**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 생성 또는 이동: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationCard.swift`
- 작업 내용:
- 홈 탭 내부 private `CreatorChannelDonationCard`를 후원 탭에서도 사용할 수 있는 공용 컴포넌트로 분리한다.
- 기존 홈 탭 표시 규칙과 preview 동작을 유지한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelDonationCard|messageText|tierColor|relativeTimeText|CreatorChannelDonationSection" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift SodaLive/Sources/V2/CreatorChannel/Donation/Components`
- [x] **Task 3.4: Empty view 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- 작업 내용:
- Figma `290:9009` 기준으로 `아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요!` 문구를 표시한다.
- `ic_new_donation` 아이콘과 `후원하기` 텍스트가 있는 `Color.soda400` capsule button을 표시한다.
- tap action은 부모에서 주입한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationEmptyView|아직 후원이 없습니다|처음으로 크리에이터를 후원해 보세요|ic_new_donation|Color\\.soda400|후원하기|onTapDonate" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- [x] **Task 3.5: 후원 탭 본문 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationTabView.swift`
- 작업 내용:
- count bar, ranking section, donation list를 순서대로 배치한다.
- 첫 진입 시 `fetchFirstPage(creatorId:)`를 호출한다.
- 마지막 donation item 노출 시 `fetchNextPageIfNeeded`를 호출한다.
- `rankings.isEmpty && donationCount == 0`이면 `CreatorChannelDonationEmptyView`를 표시한다.
- 내 채널인 경우 empty view 내부 후원하기 버튼을 숨긴다.
- `CreatorChannelDonationRankingSection`의 전체보기 action은 부모로 전달한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationTabView|CreatorChannelDonationCountBar|CreatorChannelDonationRankingSection|CreatorChannelDonationEmptyView|CreatorChannelDonationCard|fetchFirstPage|fetchNextPageIfNeeded|onTapViewAll|onTapDonate" SodaLive/Sources/V2/CreatorChannel/Donation/CreatorChannelDonationTabView.swift`
- [x] **Task 3.6: 후원하기 floating button 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationFloatingButton.swift`
- 작업 내용:
- 홈 탭 plus button 위치에 표시할 후원하기 floating button을 구현한다.
- 버튼은 `Color.soda400` 배경과 `ic_new_donation` 아이콘만 표시하고 텍스트는 표시하지 않는다.
- tap action은 부모에서 주입한다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationFloatingButton|ic_new_donation|Color\\.soda400|Capsule|action" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationFloatingButton.swift`
- 실행 명령: `rg "Text\\(" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationFloatingButton.swift`
- 기대 결과: 첫 번째 검색으로 icon-only floating button 구현이 확인되고, 두 번째 검색은 결과가 없어야 한다.
### Phase 4: CreatorChannelView 연결과 후원 성공 갱신
- [x] **Task 4.1: 후원 탭 콘텐츠 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 작업 내용:
- `@StateObject`로 후원 탭 ViewModel을 보유한다.
- `selectedTabContent`에서 `.donation`일 때 `CreatorChannelDonationTabView`를 표시한다.
- 랭킹 `전체보기` action은 `AppState.shared.setAppStep(step: .userProfileDonationAll(userId: creatorId))`로 연결한다.
- 후원하기 floating button은 `selectedTab == .donation`이고 내 채널이 아니며 `donationCount != 0 || !rankings.isEmpty`일 때만 표시한다.
- `rankings.isEmpty && donationCount == 0`이면 empty view 내부 버튼만 사용하고 floating button은 숨긴다.
- 내 채널인 경우 empty view 내부 후원하기 버튼과 floating button을 모두 숨긴다.
- 검증 기준:
- 실행 명령: `rg "CreatorChannelDonationViewModel|CreatorChannelDonationTabView|viewModel\\.selectedTab == \\.donation|CreatorChannelDonationFloatingButton|showDonationDialog|userProfileDonationAll|donationCount|rankings\\.isEmpty" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- [x] **Task 4.2: 후원 성공 후 홈/후원 탭 갱신**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 작업 내용:
- `ChannelDonationViewModel.postChannelDonation` 성공 콜백에서 `viewModel.fetchHome(creatorId:)``donationViewModel.fetchFirstPage(creatorId:)`를 호출한다.
- 검증 기준:
- 실행 명령: `rg "postChannelDonation|fetchHome\\(creatorId: creatorId\\)|donationViewModel\\.fetchFirstPage\\(creatorId: creatorId\\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
### Phase 5: 프로젝트 등록과 검증
- [x] **Task 5.1: 프로젝트 파일 등록과 정적 검증**
- 대상 파일:
- 확인/수정: `SodaLive.xcodeproj/project.pbxproj`
- 검증 기준:
- 실행 명령: `git diff --check -- docs/20260704_크리에이터_채널_후원_탭 SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj`
- 기대 결과: 공백 오류 없이 성공한다.
- 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj`
- 기대 결과: `OK` 출력.
- [x] **Task 5.2: 빌드 검증**
- 대상 파일:
- 확인: `SodaLive.xcworkspace`
- 확인: `SodaLive.xcodeproj`
- 검증 기준:
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `BUILD SUCCEEDED`.
- workspace 인식 실패 등 환경 문제가 있으면 `xcodebuild -project "SodaLive.xcodeproj" -scheme "SodaLive-dev" -configuration Debug build`를 대체 검증으로 실행하고 실패 원인을 검증 기록에 남긴다.
## 검증 기록
- 2026-07-04: Figma `get_design_context``get_screenshot`으로 `290:9093`, `290:9097`을 확인했다.
- 2026-07-04: `rg``sed`로 기존 `CreatorChannelView`, `CreatorChannelDonationSection`, `CreatorChannelSortBar`, `ChannelDonationViewModel`, `ChannelDonationAllView` 관련 파일을 확인했다.
- 2026-07-04: 사용자 요청에 따라 랭킹 `전체보기`는 기존 `UserProfileDonationAllView`로 이동하도록 범위를 보완했다. `UserProfileDonationAllView``AppStep.userProfileDonationAll(userId:)` 기존 라우팅을 확인했다.
- 2026-07-04: Figma `get_design_context``get_screenshot`으로 empty view `290:9009`를 확인하고, `rankings.isEmpty && donationCount == 0` 조건과 empty 내부 후원하기 버튼 요구사항을 반영했다.
- 2026-07-04: 사용자 요청에 따라 `donationCount == 0` empty 상태에서는 하단 고정 후원하기 버튼을 숨기고, `donationCount != 0 || !rankings.isEmpty`일 때만 홈 탭 plus button 위치에 `ic_new_donation` icon-only 후원하기 버튼을 표시하도록 범위를 보완했다.
- 2026-07-04: Task 1.2~5.1 구현 완료. 신규 `Donation/**` Swift 파일과 공용 `CreatorChannelDonationCard`, `CreatorChannelView` 연결, `project.pbxproj` 등록을 수행했고 문서의 `rg`, `plutil`, `git diff --check` 검증을 실행했다.
- 2026-07-04: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`를 실행해 `BUILD SUCCEEDED`를 확인했다.
- 2026-07-04: 사용자 요청에 따라 내 채널인 경우 후원 탭 empty view 내부 후원하기 버튼과 우측하단 icon-only 후원하기 버튼을 모두 표시하지 않도록 범위를 보완했다.
- 2026-07-04: 사용자 요청에 따라 후원 랭킹은 최대 8명만, 한 줄에 4명씩 표시하도록 범위를 보완했다.
- 2026-07-08: 사용자 요청에 따라 공용 후원 item의 `can >= 501` 상단 영역에서 `nickname`은 white, 상대 시간은 `gray100`으로 표시하고, 그 외 후원 `can` 구간의 글자색은 기존 색상을 유지하도록 기록했다.
### Phase 6: 후원 empty state 다국어 보강
- [x] **Task 6.1: 후원 empty 문구 정의**
- 대상 파일:
- 수정: `SodaLive/Sources/I18n/I18n.swift`
- 작업 내용:
- Empty 문구를 한국어 `아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요!`, 영어 `No support yet.\nBe the first to support this creator!`, 일본어 `まだサポートがありません。\n最初のサポートをしてみましょう`로 정의한다.
- 지정된 `\n` 줄바꿈을 모든 언어에서 유지한다.
- 후원 버튼 문구용 신규 I18n 키는 추가하지 않는다.
- 검증 기준:
- 실행 명령: `rg -n "아직 후원이 없습니다|No support yet|まだサポートがありません|最初のサポートをしてみましょう" SodaLive/Sources/I18n/I18n.swift`
- 기대 결과: 후원 empty 문구의 ko/en/ja 값이 `pick(ko:en:ja:)`에서 확인된다.
- [x] **Task 6.2: 후원 empty View에 기존 I18n 적용**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- 작업 내용:
- 하드코딩된 empty 문구를 Task 6.1의 I18n 값으로 교체한다.
- 하드코딩된 `후원하기`를 기존 `I18n.ContentDetail.DonationDialog.title`로 교체한다.
- 기존 후원 action과 내 채널 버튼 숨김 규칙을 유지한다.
- 후원 버튼은 `Color.soda400` 배경과 white 아이콘·텍스트를 유지한다.
- 검증 기준:
- 실행 명령: `rg -n "CreatorChannelDonationEmptyView|I18n\.|ContentDetail\.DonationDialog\.title|showsDonateButton|onTapDonate|아직 후원이 없습니다|Text\(\"후원하기\"\)" SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- 기대 결과: empty 문구와 버튼은 I18n을 사용하고 기존 action·표시 조건을 유지하며 한국어 하드코딩이 없다.
- [x] **Task 6.3: 정적 검사와 Debug 빌드 검증**
- 대상 파일:
- 확인: `SodaLive/Sources/I18n/I18n.swift`
- 확인: `SodaLive/Sources/V2/CreatorChannel/Donation/Components/CreatorChannelDonationEmptyView.swift`
- 검증 기준:
- 실행 명령: `git diff --check`
- 기대 결과: 출력 없이 exit code 0이다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `** BUILD SUCCEEDED **`.
- 2026-08-14: 사용자 인터뷰로 후원 탭 empty 문구의 ko/en/ja 값을 확정하고 기존 후원 버튼 title 재사용을 Phase 6 미완료 후속 Task로 추가했다. 이번 작업은 문서만 보강했으며 Swift 구현과 빌드는 수행하지 않았다.
- 2026-08-14: Phase 6 구현을 완료했다. 후원 탭 empty 문구를 ko/en/ja I18n으로 교체하고 버튼은 기존 `I18n.ContentDetail.DonationDialog.title`을 재사용했으며 `Color.soda400` 배경과 white 아이콘·텍스트를 유지했다. 23개 정적 계약, `git diff --check`, iPhone 16 Pro 시뮬레이터 Debug 빌드가 통과했고, 세 언어 캡처와 두 독립 색상·CJK 리뷰에서 차단 항목 없이 PASS를 확인했다.