diff --git a/CHANGELOG.json b/CHANGELOG.json
index 96a3962..63f570f 100644
--- a/CHANGELOG.json
+++ b/CHANGELOG.json
@@ -1,11 +1,32 @@
{
"metadata": {
- "lastUpdated": "2026-08-08T13:27:17Z",
- "currentVersion": "1.4.16",
+ "lastUpdated": "2026-08-08T13:56:13Z",
+ "currentVersion": "1.4.17",
"projectType": "project_type: react # spring, flutter, react, react-native, react-native-expo, node, basic",
- "totalReleases": 31
+ "totalReleases": 32
},
"releases": [
+ {
+ "version": "1.4.17",
+ "project_type": "project_type: react # spring, flutter, react, react-native, react-native-expo, node, basic",
+ "date": "2026-08-08",
+ "pr_number": 120,
+ "raw_summary": "Summary by CodeRabbit\n릴리스 노트\n\n\n버그 수정\n\n휴대폰에서 인트로 화면이 일부 잘려 보이던 문제 해결\n\n\n\n개선\n\n화면 구성 코드를 정리해 유지보수성 향상",
+ "parsed_changes": {
+ "버그_수정": {
+ "title": "버그 수정",
+ "items": [
+ "휴대폰에서 인트로 화면이 일부 잘려 보이던 문제 해결"
+ ]
+ },
+ "개선": {
+ "title": "개선",
+ "items": [
+ "화면 구성 코드를 정리해 유지보수성 향상"
+ ]
+ }
+ }
+ },
{
"version": "1.4.16",
"project_type": "project_type: react # spring, flutter, react, react-native, react-native-expo, node, basic",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ada628..af19957 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,19 @@
# Changelog
-**현재 버전:** 1.4.16
-**마지막 업데이트:** 2026-08-08T13:27:17Z
+**현재 버전:** 1.4.17
+**마지막 업데이트:** 2026-08-08T13:56:13Z
+
+---
+
+## [1.4.17] - 2026-08-08
+
+**PR:** #120
+
+**버그 수정**
+- 휴대폰에서 인트로 화면이 일부 잘려 보이던 문제 해결
+
+**개선**
+- 화면 구성 코드를 정리해 유지보수성 향상
---
diff --git a/README.md b/README.md
index dcdfce6..be29389 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-## 최신 버전 : v1.4.15 (2026-08-08)
+## 최신 버전 : v1.4.16 (2026-08-08)
[전체 업데이트 내역 보기](CHANGELOG.md)
diff --git a/docs/DESIGN-SYSTEM.md b/docs/DESIGN-SYSTEM.md
index 73d0a72..e3e6b7d 100644
--- a/docs/DESIGN-SYSTEM.md
+++ b/docs/DESIGN-SYSTEM.md
@@ -147,6 +147,39 @@ border-radius: 20px
`animations` 객체에 Framer Motion용 variant가 정의되어 있다 — `fadeIn`, `slideUp`,
`scaleIn`, `hover`, `tap`.
+## 3해상도 점검 결과
+
+| 해상도 | 결과 |
+| ----------------- | --------------------- |
+| 모바일 390×844 | ✅ 뷰포트 초과 요소 0 |
+| 태블릿 768×1024 | ✅ 뷰포트 초과 요소 0 |
+| 데스크톱 1440×900 | ✅ 뷰포트 초과 요소 0 |
+
+### 점검 방법
+
+캡처만으로는 "조금 잘렸는지"를 눈으로 판별하기 어렵다. 그래서 **측정**했다.
+
+```js
+[...document.querySelectorAll("*")].filter(
+ (e) => e.getBoundingClientRect().right > window.innerWidth + 1
+).length;
+```
+
+가로 스크롤 발생 여부(`scrollWidth > innerWidth`)와 함께 확인한다. `overflow: hidden`이
+걸려 있으면 스크롤은 안 생기지만 요소는 여전히 화면 밖에 있으므로 둘 다 봐야 한다.
+
+### 찾아서 고친 결함
+
+**인트로 화면이 모바일에서 뷰포트를 벗어났다.**
+
+`DialogueSystem`의 `CharacterImage`가 모바일에서 `width: 500px` 고정이었고,
+부모 `CharacterContainer`에 폭 제한이 없어 자식만큼 늘어났다. 390px 화면에서
+컨테이너가 500px가 되어 레이아웃이 밀렸다.
+
+부모에 `width: 100%`, 자식에 `max-width: 100%`를 넣어 해소했다.
+`background-size: contain`이라 이미지 자체는 잘리지 않았기 때문에 **눈으로는 알아채기
+어려운 결함**이었다.
+
## Phase 3 계획
### 1. 전수 점검 (선행)
diff --git a/package.json b/package.json
index 39d7472..980784b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "re-wave",
- "version": "1.4.16",
+ "version": "1.4.17",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
diff --git a/src/components/DialogueSystem.jsx b/src/components/DialogueSystem.jsx
index 1998988..8255559 100644
--- a/src/components/DialogueSystem.jsx
+++ b/src/components/DialogueSystem.jsx
@@ -40,6 +40,9 @@ const CharacterContainer = styled.div`
align-items: center;
gap: 0;
margin-bottom: 1rem;
+ /* 폭 제한이 없으면 자식(CharacterImage)의 고정 폭만큼 늘어나
+ 좁은 화면에서 컨테이너가 뷰포트를 벗어난다 */
+ width: 100%;
${media.mobile} {
gap: 0;
@@ -49,6 +52,9 @@ const CharacterContainer = styled.div`
const CharacterImage = styled.div`
width: 600px; /* 1200px의 절반 */
+ /* 고정 폭이 뷰포트를 넘으면 요소가 화면 밖으로 밀린다.
+ background-size: contain이라 이미지 자체는 잘리지 않지만 레이아웃이 어긋난다. */
+ max-width: 100%;
height: 400px; /* 800px의 절반 */
background-image: url(${(props) => props.src});
background-size: contain;
diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx
index 75d5102..822d340 100644
--- a/src/pages/Profile.jsx
+++ b/src/pages/Profile.jsx
@@ -10,6 +10,25 @@ import StatsContent from "../components/StatsContent";
import axios from "../utils/axios";
import { logger } from "../utils/logger";
+// 스켈레톤 로딩 UI — 원래 인라인 style로 4번 반복되던 것
+const SkeletonList = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+`;
+
+const SkeletonRow = styled(motion.div)`
+ width: 100%;
+ height: 48px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.2rem;
+`;
+
const ErrorPanel = styled.div`
display: flex;
flex-direction: column;
@@ -175,6 +194,9 @@ const SkeletonCircle = styled(SkeletonElement)`
const SkeletonText = styled(SkeletonElement)`
height: 20px;
border-radius: 4px;
+ /* 자리표시자 너비는 행마다 달라서 prop으로 받는다.
+ transient prop($ 접두사)이라 DOM으로 전달되지 않는다 */
+ width: ${(props) => props.$width || "100%"};
`;
const SkeletonTitle = styled(SkeletonElement)`
@@ -314,26 +336,14 @@ function Profile() {
>