Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
- API health checks and command-line diagnostics may support investigation, but they do not replace simulator verification and must not be reported as completed app testing.
- Use a web target only for explicit web build or export compatibility work. Even then, do not use it for product acceptance testing unless the user's latest message explicitly overrides this rule.

## Text Color

- 기본 사용자 노출 텍스트와 버튼, 탭, 칩의 인터랙션 라벨은 흰색 또는 흰색 투명도 계열을 사용한다. 상태를 구분하는 의미 색상과 브랜드 강조 색상은 예외로 둔다.
- `text-black`, 검정색, 진한 보라색 텍스트 값을 새로 추가하지 않는다.
- 기본, 보조, 비활성 텍스트의 명도 차이는 검정색이나 회색 고정값이 아니라 `#FFFFFF` 또는 `rgba(255,255,255,alpha)`로 표현한다.
- 밝은 강조 배경에서 검정색 텍스트로 대비를 해결하지 않는다. 흰색 텍스트가 읽히도록 배경과 테두리 대비를 조정한다.
- `colors.text.inverse`와 `text-soundlog-inverse`는 흰색 의미를 유지한다.
- 사용자에게 보이는 문자열은 React Native의 `Text`를 직접 사용하지 않고 기본 글자색이 흰색인 `AppText`를 사용한다.
- `text-white/52`처럼 기본 Tailwind에 없는 투명도를 사용하면 `tailwind.config.js`의 opacity에도 같은 값을 선언해 검정색 기본값으로 떨어지지 않게 한다.
- UI 변경 후 `text-black`, `color: 'black'`, `#000000`, `#4A1D96`가 텍스트 색상으로 추가되지 않았는지 검색한다. 그림자, 배경, 아이콘, 지도 스타일은 이 검사 대상이 아니다.

## Recap And Log Domain

- Before changing Recap, Log, camera capture, travel mode, route tracking, map pins, visibility, or related API behavior, read `docs/product/RECAP_LOG_DOMAIN_MODEL.md`.
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@ EXPO_PUBLIC_MOCK_API_FAIL_ENDPOINTS=playlist.detail npm run web
실제 서버와 연동할 때는 Expo 환경변수로 API base URL을 지정합니다. 이 값이 없으면 mock으로 돌아가지 않고 API URL 누락 오류를 표시합니다.

```bash
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=https://api.soundlog.shop npm run ios
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=https://api.soundlog.p-e.kr npm run ios
```

Soundlog는 웹 서비스를 배포하지 않습니다. 운영 배포 대상은 Expo/EAS로 빌드한 iOS·Android 네이티브 앱이며, 앱은 Vercel 프록시 없이 GCP의 `https://api.soundlog.shop`을 직접 호출합니다. 저장소의 web·Vercel 관련 설정은 로컬 호환성 확인과 과거 실험용 자료일 뿐 운영 배포 경로에 포함하지 않습니다.
Soundlog는 웹 서비스를 배포하지 않습니다. 운영 배포 대상은 Expo/EAS로 빌드한 iOS·Android 네이티브 앱이며, 앱은 Vercel 프록시 없이 API와 ML이 함께 배포된 `https://api.soundlog.p-e.kr`을 직접 호출합니다. 저장소의 web·Vercel 관련 설정은 로컬 호환성 확인과 과거 실험용 자료일 뿐 운영 배포 경로에 포함하지 않습니다.

네이티브 실배포 빌드는 `api.soundlog.shop` 서브도메인을 직접 호출합니다(GCP VM 위 Caddy가 HTTPS를 종료). 로그인은 Soundlog 자체 이메일/비밀번호 계정으로 처리합니다.
네이티브 실배포 빌드는 `api.soundlog.p-e.kr` 서브도메인을 직접 호출합니다. 로그인은 Soundlog 자체 이메일/비밀번호 계정으로 처리합니다.

## 테스트 설치 빌드

`development`, `preview` EAS profile은 `api.soundlog.shop`을 직접 바라보도록 설정되어 있습니다.
`development`, `preview` EAS profile은 `api.soundlog.p-e.kr`을 직접 바라보도록 설정되어 있습니다.

본인 iPhone을 USB로 연결해 최신 코드를 직접 테스트할 때는 [iPhone 실기기 테스트 가이드](docs/deployment/IOS_PHYSICAL_DEVICE_TESTING.md)를 먼저 확인합니다. 실기기에서 `127.0.0.1`은 Mac이 아니라 iPhone 자신이므로, 로컬 서버를 사용할 때는 Mac의 LAN IP를 앱 빌드 환경변수로 지정해야 합니다.

- API: `https://api.soundlog.shop`
- 개인정보 처리방침: `https://api.soundlog.shop/legal/privacy`
- 서비스 이용약관: `https://api.soundlog.shop/legal/terms`
- 고객지원: `https://api.soundlog.shop/support`
- API 및 ML: `https://api.soundlog.p-e.kr`
- 개인정보 처리방침: `https://api.soundlog.p-e.kr/legal/privacy`
- 서비스 이용약관: `https://api.soundlog.p-e.kr/legal/terms`
- 고객지원: 실제 수신과 답장이 확인된 메일을 production EAS 환경에 설정
- API source: `server`
- auth: Soundlog 자체 이메일/비밀번호 로그인
- iOS/Android: HTTPS API만 사용
Expand All @@ -67,20 +67,20 @@ Mock API로 되돌리는 런타임 경로는 제거했습니다. 화면 상태
API origin이 최신 서버인지 확인하려면 아래처럼 실행합니다.

```bash
SOUNDLOG_API_ORIGIN=https://api.soundlog.shop npm run check:api-origin
SOUNDLOG_API_ORIGIN=https://api.soundlog.p-e.kr npm run check:api-origin
```

이 검사는 로그인 필수 API도 함께 확인하므로 `SOUNDLOG_CHECK_EMAIL`,
`SOUNDLOG_CHECK_PASSWORD`를 지정하면 해당 smoke 계정으로 로그인합니다. 값을
지정하지 않으면 `@soundlog.test` 임시 계정을 생성해 검증합니다.
지정하지 않으면 `@soundlog.test` 임시 계정을 생성해 API와 ML 추천을 검증한 뒤 즉시 삭제합니다.

Android 지인 테스트용 내부 배포 빌드는 아래 명령으로 생성합니다.

```bash
npx eas build --profile preview --platform android
```

iOS는 TestFlight 또는 ad hoc 기기 등록이 필요합니다. App Store/TestFlight에 올릴 production profile도 현재는 `https://api.soundlog.shop`을 사용합니다.
iOS는 TestFlight 또는 ad hoc 기기 등록이 필요합니다. App Store/TestFlight에 올릴 production profile은 API와 개인정보 처리방침과 이용약관에 `https://api.soundlog.p-e.kr`을 사용합니다. 새 서버에 공개 문서 경로를 배포하고 실제 수신 가능한 고객지원 메일을 설정한 뒤 릴리스 검사를 통과해야 합니다.

## 문서

Expand Down
10 changes: 7 additions & 3 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,13 @@ const baseConfig = {
},
extra: {
legal: {
privacyPolicyUrl: process.env.EXPO_PUBLIC_SOUNDLOG_PRIVACY_URL,
supportEmail: process.env.EXPO_PUBLIC_SOUNDLOG_SUPPORT_EMAIL ?? 'support@soundlog.shop',
termsUrl: process.env.EXPO_PUBLIC_SOUNDLOG_TERMS_URL,
privacyPolicyUrl:
process.env.EXPO_PUBLIC_SOUNDLOG_PRIVACY_URL ??
'https://api.soundlog.p-e.kr/legal/privacy',
supportEmail: process.env.EXPO_PUBLIC_SOUNDLOG_SUPPORT_EMAIL ?? 'hcy032020@gmail.com',
termsUrl:
process.env.EXPO_PUBLIC_SOUNDLOG_TERMS_URL ??
'https://api.soundlog.p-e.kr/legal/terms',
},
eas: {
projectId: '4b07627b-36bf-463d-a15e-b4839022ecbb',
Expand Down
21 changes: 16 additions & 5 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Redirect, router } from "expo-router";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

Expand Down Expand Up @@ -77,14 +77,24 @@ export default function MapHomeScreen() {
{ enabled: status === "authenticated" && session.status === "active" },
);
const nearestTourPlace = currentLocation
? nearbyPlacesQuery.data?.find(
(place) => place.source === "tour-api" && Boolean(place.location),
)
? nearbyPlacesQuery.data?.find((place) => Boolean(place.location))
: undefined;
const cachedTourPlace =
currentPlace?.source === "tour-api" && currentPlace.location
currentPlace?.location &&
(currentPlace.source === "tour-api" || currentPlace.source === "seed")
? currentPlace
: undefined;
const nearbyTourPlaces = useMemo(() => {
const places = (nearbyPlacesQuery.data ?? []).filter((place) =>
Boolean(place.location),
);

if (places.length > 0) {
return places;
}

return cachedTourPlace ? [cachedTourPlace] : [];
}, [cachedTourPlace, nearbyPlacesQuery.data]);
const activeCurrentPlace = currentLocation
? nearestTourPlace
: cachedTourPlace;
Expand Down Expand Up @@ -366,6 +376,7 @@ export default function MapHomeScreen() {
overlayBottomInset={mapOverlayBottomInset}
overlayTopInset={mapOverlayTopInset}
sessionStatus={session.status}
tourPlaces={nearbyTourPlaces}
tourPlaceStatus={tourPlaceStatus}
variant="page"
/>
Expand Down
2 changes: 1 addition & 1 deletion app/auth/callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function AuthCallbackScreen() {

<Pressable
accessibilityRole="button"
className="mt-auto h-14 items-center justify-center rounded-xl bg-soundlog-lime"
className="mt-auto h-14 items-center justify-center rounded-xl border border-soundlog-lime/45 bg-soundlog-action"
onPress={() => router.replace('/auth/login' as never)}
>
<AppText className="text-sm font-semibold text-soundlog-inverse">
Expand Down
2 changes: 1 addition & 1 deletion app/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export default function LoginScreen() {

<Pressable
accessibilityRole="button"
className="mt-2 min-h-[56px] items-center justify-center rounded-xl bg-soundlog-lime px-5"
className="mt-2 min-h-[56px] items-center justify-center rounded-xl border border-soundlog-lime/45 bg-soundlog-action px-5"
disabled={isPending}
onPress={() => {
void handleSubmit();
Expand Down
26 changes: 15 additions & 11 deletions docs/deployment/IOS_PHYSICAL_DEVICE_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ xcrun devicectl list devices

### `127.0.0.1`을 사용하면 안 되는 이유

iPhone에서 `127.0.0.1`은 Mac이 아니라 **iPhone 자신**입니다. `.env.local`의 아래 값은 시뮬레이터용이며 실기기에서는 서버에 도달하지 못합니다.
iPhone에서 `127.0.0.1`은 Mac이 아니라 **iPhone 자신**입니다. 로컬 서버를 직접 테스트하려고 아래 값을 임시로 사용하면 실기기에서는 서버에 도달하지 못합니다.

```env
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=http://127.0.0.1:4000
Expand All @@ -85,6 +85,8 @@ ipconfig getifaddr en0
http://172.30.1.99:4000
```

Expo SDK 56 개발 실행에서는 `.env.local`이 셸에 입력한 같은 이름의 환경변수보다 우선합니다. 로컬 서버를 테스트할 때는 `.env.local`의 API와 업로드 주소를 Mac의 LAN 주소로 임시 변경해야 합니다. 테스트가 끝나면 두 값을 `https://api.soundlog.p-e.kr`로 복구합니다.

### 서버 실행 및 확인

DB와 서버 환경변수를 준비한 뒤 API를 실행합니다.
Expand All @@ -109,13 +111,17 @@ iPhone Safari에서도 같은 헬스 체크 URL을 열 수 있어야 합니다.

## 5. iPhone에 앱 설치

앱 디렉터리에서 Mac의 LAN IP를 주입해 개발 빌드를 설치합니다.
앱 디렉터리의 `.env.local`에 Mac의 LAN IP를 임시로 설정합니다.

```dotenv
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=http://<MAC_LAN_IP>:4000
EXPO_PUBLIC_SOUNDLOG_UPLOAD_ORIGIN=http://<MAC_LAN_IP>:4000
```

그다음 개발 빌드를 설치합니다.

```bash
cd /Users/manwook-han/Desktop/hmw/code/apps/soundlog/soundlog

EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server \
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=http://<MAC_LAN_IP>:4000 \
npx expo run:ios --device
```

Expand All @@ -124,8 +130,6 @@ npx expo run:ios --device
앱이 한 번 설치된 뒤 JavaScript 코드만 수정할 때는 네이티브 재빌드 없이 Metro를 다시 연결할 수 있습니다.

```bash
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server \
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=http://<MAC_LAN_IP>:4000 \
npx expo start --dev-client --lan
```

Expand All @@ -136,16 +140,16 @@ LAN 연결이 불안정하면 Metro에만 `--tunnel`을 사용할 수 있습니
배포 API가 정상일 때는 다음 주소를 사용합니다.

```text
https://api.soundlog.shop
https://api.soundlog.p-e.kr
```

앱 실행 전에 먼저 상태를 확인합니다.

```bash
curl https://api.soundlog.shop/v1/health
curl https://api.soundlog.p-e.kr/v1/health
```

DNS 오류나 타임아웃이 발생하면 배포 API 테스트를 중단하고 로컬 LAN API를 사용하거나 도메인과 GCP API 상태를 먼저 복구합니다. 도메인 장애 중에 GCP IP를 앱에 임시 하드코딩하지 않습니다.
DNS 오류나 타임아웃이 발생하면 배포 API 테스트를 중단하고 로컬 LAN API를 사용하거나 운영 API 상태를 먼저 복구합니다. 도메인 장애 중에 서버 IP를 앱에 임시 하드코딩하지 않습니다.

## 7. EAS 내부 배포

Expand All @@ -158,7 +162,7 @@ npx eas-cli device:create
npm run build:dev:ios
```

빌드가 완료되면 표시되는 링크나 QR 코드로 iPhone에 설치합니다. 현재 `development` profile은 `https://api.soundlog.shop`을 직접 사용하므로 도메인 상태가 정상이어야 전체 기능을 테스트할 수 있습니다.
빌드가 완료되면 표시되는 링크나 QR 코드로 iPhone에 설치합니다. 현재 `development` profile은 `https://api.soundlog.p-e.kr`을 직접 사용하므로 도메인 상태가 정상이어야 전체 기능을 테스트할 수 있습니다.

설치 후 로컬 코드를 연결하려면 다음을 실행합니다.

Expand Down
72 changes: 25 additions & 47 deletions docs/deployment/SOUNDLOG_SHOP_DOMAIN.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,44 @@
# Soundlog app deployment domain
# Soundlog 앱 배포 도메인

Soundlog는 웹 서비스를 운영하거나 배포하지 않습니다. 출시 대상은 Expo/EAS로 빌드한 iOS·Android 네이티브 앱이며, 앱은 GCP API를 직접 호출합니다. Vercel은 운영 배포 경로에 포함하지 않습니다.
Soundlog는 웹 서비스를 운영하지 않습니다. 출시 대상은 Expo와 EAS로 빌드한 iOSAndroid 네이티브 앱입니다. Vercel은 운영 앱의 네트워크 경로에 포함하지 않습니다.

## Production architecture
## 운영 주소

| Host | Target | Purpose |
| 주소 | 용도 | 현재 상태 |
| --- | --- | --- |
| `api.soundlog.shop` | GCP Compute Engine | 앱 API, 업로드, 개인정보 처리방침, 이용약관, 고객지원 |
| `https://api.soundlog.p-e.kr` | 앱 API, 파일 업로드, ML 음악 추천 | 운영 앱이 직접 호출 |
| `https://api.soundlog.p-e.kr/legal/privacy` | 개인정보 처리방침 | 최신 서버 배포 후 사용하는 공개 문서 |
| `https://api.soundlog.p-e.kr/legal/terms` | 서비스 이용약관 | 최신 서버 배포 후 사용하는 공개 문서 |
| production EAS 지원 메일 | 고객지원 | 실제 수신과 답장이 확인된 주소를 설정 |

앱의 모든 네트워크 요청은 `https://api.soundlog.shop`으로 전송됩니다. GCP VM의 Caddy가 HTTPS를 종료하고 Express API로 reverse proxy합니다.
새 API 도메인의 `/v1/health`와 `/openapi.yaml`은 정상 응답합니다. `/v1/recommendations/playlists`는 인증된 요청에 ML 추천을 반환합니다. 새 API 도메인의 `/legal/privacy`, `/legal/terms`, `/support`는 최신 서버 배포 후 사용합니다. 심사 전에는 세 경로의 200 응답과 고객지원 메일 수신을 확인해야 합니다.

## GCP Cloud DNS
## EAS 앱 환경변수

GCP project `nomi-app-deploy-2026`의 `soundlog-shop` public zone을 사용합니다. 도메인 등록기관에서 `soundlog.shop`의 네임서버를 아래 값으로 위임합니다.

- `ns-cloud-d1.googledomains.com`
- `ns-cloud-d2.googledomains.com`
- `ns-cloud-d3.googledomains.com`
- `ns-cloud-d4.googledomains.com`

Cloud DNS에는 아래 운영 레코드가 등록되어 있습니다.

| Type | Host | TTL | Value |
| --- | --- | --- | --- |
| `A` | `api` | `300` | `34.64.116.40` |

기존 AWS Route 53 또는 Vercel 네임서버는 사용하지 않습니다. 등록기관의 네임서버 위임이 Cloud DNS로 바뀐 뒤 Caddy가 `api.soundlog.shop`의 인증서를 자동 발급합니다.

## EAS app env

`development`, `preview`, `production` profile은 모두 GCP API를 직접 호출합니다.
`development`, `preview`, `production` profile은 모두 새 API와 ML 서버를 직접 호출합니다.

```dotenv
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=https://api.soundlog.shop
EXPO_PUBLIC_SOUNDLOG_UPLOAD_ORIGIN=https://api.soundlog.shop
EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=https://api.soundlog.p-e.kr
EXPO_PUBLIC_SOUNDLOG_UPLOAD_ORIGIN=https://api.soundlog.p-e.kr
```

Production profile은 App Store와 앱 설정에 사용할 공개 법적 문서 URL을 같은 GCP 서버로 지정합니다.
Production profile은 공개 법적 문서와 고객지원 정보를 기존 주소로 유지합니다.

```dotenv
EXPO_PUBLIC_SOUNDLOG_PRIVACY_URL=https://api.soundlog.shop/legal/privacy
EXPO_PUBLIC_SOUNDLOG_TERMS_URL=https://api.soundlog.shop/legal/terms
EXPO_PUBLIC_SOUNDLOG_SUPPORT_EMAIL=support@soundlog.shop
EXPO_PUBLIC_SOUNDLOG_PRIVACY_URL=https://api.soundlog.p-e.kr/legal/privacy
EXPO_PUBLIC_SOUNDLOG_TERMS_URL=https://api.soundlog.p-e.kr/legal/terms
EXPO_PUBLIC_SOUNDLOG_SUPPORT_EMAIL=실제_수신_가능한_메일
```

## Public pages

- 개인정보 처리방침: `https://api.soundlog.shop/legal/privacy`
- 서비스 이용약관: `https://api.soundlog.shop/legal/terms`
- 고객지원: `https://api.soundlog.shop/support`

세 페이지는 로그인 없이 접근할 수 있어야 하며 SoundLogServer가 직접 HTML로 제공합니다.

## Verification
## 검증

```bash
dig +short soundlog.shop NS
dig +short api.soundlog.shop A
curl https://api.soundlog.shop/v1/health
curl -I https://api.soundlog.shop/legal/privacy
curl -I https://api.soundlog.shop/legal/terms
curl -I https://api.soundlog.shop/support
SOUNDLOG_API_ORIGIN=https://api.soundlog.shop npm run check:api-origin
curl https://api.soundlog.p-e.kr/v1/health
curl -I https://api.soundlog.p-e.kr/openapi.yaml
SOUNDLOG_API_ORIGIN=https://api.soundlog.p-e.kr npm run check:api-origin
curl -I https://api.soundlog.p-e.kr/legal/privacy
curl -I https://api.soundlog.p-e.kr/legal/terms
```

`check:api-origin`은 임시 계정을 생성해 인증 API와 ML 음악 추천을 확인한 뒤 계정을 즉시 삭제합니다.
8 changes: 6 additions & 2 deletions docs/design-system/SOUNDLOG_RN_DESIGN_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ import {
| Accent purple | `#7A2CFF` | `text-soundlog-purple` |
| Accent gold | `#B1913A` | `text-soundlog-gold` |
| Warning action | `#FF8A3D` | `bg-soundlog-warning` |
| Inverse text | `#090515` | `text-soundlog-inverse` |
| Primary action | `#4A1D96` | `bg-soundlog-action` |
| Danger action | `#8E310D` | `bg-soundlog-danger` |
| Inverse text | `#FFFFFF` | `text-soundlog-inverse` |

## Typography

Expand Down Expand Up @@ -300,9 +302,11 @@ import { AppText, soundlogRecipes } from '@/design-system';
홈의 `HomeHeader` 패턴을 재사용합니다.

- 바깥 트랙: `rounded-full border border-white/10 bg-white/[0.06] p-1`
- 선택 탭: 라임 또는 흰색 배경
- 선택 탭: 흰색 글자가 읽히는 어두운 강조 배경
- 선택 탭 text: `text-soundlog-inverse`

기본 사용자 노출 텍스트와 인터랙션 라벨은 `AppText`를 사용하고 흰색 또는 흰색 투명도 계열을 적용합니다. 상태를 구분하는 의미 색상과 브랜드 강조 색상은 유지할 수 있습니다. 선택 상태와 CTA는 검정색이나 진한 보라색 글자를 사용하지 않고 `bg-soundlog-selected`, `bg-soundlog-action`, `bg-soundlog-danger`처럼 흰색이 읽히는 배경과 테두리로 대비를 조정합니다.

### Filter Bar

`HomeTopFilterBar`처럼 pill 컨테이너 안에 `Chip`을 가로 스크롤로 배치합니다.
Expand Down
2 changes: 1 addition & 1 deletion docs/frontend/MAIN_PAGE_RN_BUILD_DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ RN에서는 다음 방식 중 하나를 사용한다.
| `background.chip` | `#0E1E3A` | 필터 칩 |
| `border.chip` | `#364283` | 칩 테두리 |
| `text.primary` | `#FFFFFF` | 주요 텍스트 |
| `text.secondary` | `#ACACAC` | 보조 텍스트 |
| `text.secondary` | `rgba(255,255,255,0.68)` | 보조 텍스트 |
| `accent.purple` | `#7A2CFF` | 진행바/선택 포인트 |
| `accent.gold` | `#B1913A` | 추천 카드 |
| `player.background` | `#45343D` | 미니 플레이어 |
Expand Down
Loading
Loading