Skip to content

Commit 7d91b97

Browse files
Merge pull request #44 from nka21/feature/37/result-ui
結果画面のUI作成
2 parents aa30ed5 + ce15d1d commit 7d91b97

3 files changed

Lines changed: 305 additions & 2 deletions

File tree

apps/web/src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import styles from './App.module.css';
44
// import { ShootingScreen } from './components/game/shooting-screen';
55
// import type { JudgeResult, Theme } from './components/game/types';
6-
import { PhotoScreen } from './components/photo/PhotoScreen';
6+
// import { HomeScreen } from './components/home/HomeScreen';
7+
import { ResultScreen } from './components/result/ResultScreen';
8+
// import { PhotoScreen } from './components/photo/PhotoScreen';
79
// import { HomeScreen } from './components/home/HomeScreen';
810
// import { PhotoPreview } from './components/photo/PhotoPreview';
911

@@ -34,6 +36,7 @@ function App() {
3436
return (
3537
<div className={styles['phone-container']}>
3638
{/* <HomeScreen /> */}
39+
<ResultScreen />
3740
{/* {showShooting ? (
3841
<ShootingScreen theme={testTheme} onComplete={handleComplete} />
3942
) : (
@@ -50,7 +53,7 @@ function App() {
5053
</button>
5154
</div>
5255
)} */}
53-
<PhotoScreen />
56+
{/* <PhotoScreen /> */}
5457
{/* <PhotoPreview /> */}
5558
</div>
5659
);
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
.screen {
2+
width: 100%;
3+
height: 100%;
4+
border-radius: 36px;
5+
overflow: hidden;
6+
position: relative;
7+
}
8+
9+
.result-screen {
10+
background: linear-gradient(
11+
180deg,
12+
var(--light-peach) 0%,
13+
var(--primary-bg) 100%
14+
);
15+
}
16+
17+
.content {
18+
height: 100%;
19+
position: relative;
20+
overflow-y: auto;
21+
}
22+
23+
.result-content {
24+
/* height: 100%; */
25+
display: flex;
26+
flex-direction: column;
27+
padding: 40px 20px;
28+
position: relative;
29+
text-align: center;
30+
}
31+
32+
.result-top-info {
33+
text-align: center;
34+
margin-bottom: 20px;
35+
}
36+
37+
.result-emoji {
38+
font-size: 40px;
39+
margin-bottom: 8px;
40+
}
41+
42+
.result-title {
43+
font-size: 24px;
44+
font-weight: 700;
45+
color: var(--dark-brown);
46+
margin-bottom: 20px;
47+
}
48+
49+
/* プログレスバースコア表示 */
50+
.score-progress-container {
51+
background: var(--primary-bg);
52+
padding: 20px;
53+
border-radius: 16px;
54+
border: 2px solid var(--warm-brown);
55+
margin-bottom: 20px;
56+
}
57+
58+
.score-header {
59+
display: flex;
60+
justify-content: space-between;
61+
align-items: center;
62+
margin-bottom: 12px;
63+
}
64+
65+
.score-label-progress {
66+
font-size: 14px;
67+
font-weight: 600;
68+
color: var(--text-primary);
69+
}
70+
71+
.score-value-progress {
72+
font-size: 20px;
73+
font-weight: 700;
74+
color: var(--warm-brown);
75+
}
76+
77+
.progress-bar-container {
78+
height: 12px;
79+
background: var(--accent-bg);
80+
border-radius: 10px;
81+
overflow: hidden;
82+
}
83+
84+
.progress-bar-fill {
85+
height: 100%;
86+
background: linear-gradient(90deg, var(--warm-brown), var(--medium-brown));
87+
border-radius: 10px;
88+
width: 0%;
89+
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
90+
}
91+
92+
.character-center {
93+
flex: 1;
94+
display: flex;
95+
flex-direction: column;
96+
justify-content: center;
97+
align-items: center;
98+
position: relative;
99+
margin: 20px 0;
100+
}
101+
102+
.character-main {
103+
width: 85%;
104+
height: 85%;
105+
display: flex;
106+
justify-content: center;
107+
align-items: center;
108+
position: relative;
109+
}
110+
111+
.character-image {
112+
width: 100%;
113+
height: 100%;
114+
object-fit: contain;
115+
object-position: center;
116+
}
117+
118+
.action-buttons-result {
119+
display: flex;
120+
flex-direction: column;
121+
gap: 12px;
122+
}
123+
124+
.action-button-result {
125+
padding: 16px 30px;
126+
border: none;
127+
border-radius: 25px;
128+
font-size: 16px;
129+
font-weight: 600;
130+
cursor: pointer;
131+
transition: all 0.3s ease;
132+
}
133+
134+
.btn-primary-result {
135+
background: var(--warm-brown);
136+
color: white;
137+
box-shadow: 0 6px 20px rgba(212, 165, 116, 0.3);
138+
}
139+
140+
.btn-primary-result:hover {
141+
background: var(--medium-brown);
142+
transform: translateY(-2px);
143+
}
144+
145+
.btn-secondary-result {
146+
background: var(--primary-bg);
147+
color: var(--text-primary);
148+
border: 2px solid var(--warm-brown);
149+
box-shadow: 0 4px 15px rgba(139, 69, 19, 0.1);
150+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
3+
import clsx from 'clsx';
4+
5+
import huntoru from '../../assets/huntoru.png';
6+
import styles from './ResultScreen.module.css';
7+
8+
export const ResultScreen = () => {
9+
const progressBarRef = useRef<HTMLDivElement>(null);
10+
const scoreValueRef = useRef<HTMLSpanElement>(null);
11+
const [score, setScore] = useState(0);
12+
const [progress, setProgress] = useState(0);
13+
14+
const animateScore = (
15+
element: HTMLElement,
16+
start: number,
17+
end: number,
18+
duration: number,
19+
) => {
20+
const startTime = performance.now();
21+
22+
const updateScore = (currentTime: number) => {
23+
const elapsed = currentTime - startTime;
24+
const progress = Math.min(elapsed / duration, 1);
25+
const easeProgress = 1 - Math.pow(1 - progress, 3);
26+
const currentValue = start + (end - start) * easeProgress;
27+
28+
// DOM更新のみ
29+
element.textContent = currentValue.toFixed(2);
30+
31+
if (progress < 1) {
32+
requestAnimationFrame(updateScore);
33+
} else {
34+
element.textContent = end.toFixed(2);
35+
}
36+
};
37+
38+
requestAnimationFrame(updateScore);
39+
};
40+
useEffect(() => {
41+
const progressBar = progressBarRef.current;
42+
const scoreValue = scoreValueRef.current;
43+
44+
if (!progressBar || !scoreValue) return;
45+
46+
const animateResult = () => {
47+
progressBar.style.width = '0%';
48+
setProgress(0);
49+
setScore(0);
50+
51+
return setTimeout(() => {
52+
progressBar.style.width = '85%';
53+
setProgress(85);
54+
animateScore(scoreValue, 0, 0.85, 1500);
55+
}, 500);
56+
};
57+
58+
const timeoutId = animateResult();
59+
return () => clearTimeout(timeoutId);
60+
}, []);
61+
62+
useEffect(() => {
63+
const progressBar = progressBarRef.current;
64+
const scoreValue = scoreValueRef.current;
65+
66+
if (!progressBar || !scoreValue) return;
67+
68+
const animateResult = () => {
69+
progressBar.style.width = '0%';
70+
setProgress(0);
71+
setScore(0);
72+
73+
return setTimeout(() => {
74+
progressBar.style.width = '85%';
75+
setProgress(85);
76+
animateScore(scoreValue, 0, 0.85, 1500);
77+
}, 500);
78+
};
79+
80+
const timeoutId = animateResult();
81+
return () => clearTimeout(timeoutId);
82+
}, []);
83+
84+
return (
85+
<div className={clsx(styles.screen, styles['result-screen'])}>
86+
<div className={styles.content}>
87+
<div className={styles['result-content']}>
88+
{/* 上部の情報 */}
89+
<div className={styles['result-top-info']}>
90+
<div className={styles['result-emoji']}>🎉</div>
91+
<h1 className={styles['result-title']}>成功!</h1>
92+
93+
{/* プログレスバースコア表示 */}
94+
<div className={styles['score-progress-container']}>
95+
<div className={styles['score-header']}>
96+
<div className={styles['score-label-progress']}>おいしさ度</div>
97+
<span
98+
className={styles['score-value-progress']}
99+
ref={scoreValueRef}
100+
>
101+
{score}
102+
</span>
103+
</div>
104+
<div className={styles['progress-bar-container']}>
105+
<div
106+
className={styles['progress-bar-fill']}
107+
style={{ width: `${progress}%` }}
108+
ref={progressBarRef}
109+
/>
110+
</div>
111+
</div>
112+
</div>
113+
114+
{/* キャラクター */}
115+
<div className={styles['character-center']}>
116+
<div className={styles['character-main']}>
117+
<img
118+
src={huntoru}
119+
alt="huntoru"
120+
className={styles['character-image']}
121+
/>
122+
</div>
123+
</div>
124+
125+
{/* ボタン */}
126+
<div className={styles['action-buttons-result']}>
127+
<button
128+
type="button"
129+
className={clsx(
130+
styles['action-button-result'],
131+
styles['btn-primary-result'],
132+
)}
133+
>
134+
もう一度とる!
135+
</button>
136+
<button
137+
type="button"
138+
className={clsx(
139+
styles['action-button-result'],
140+
styles['btn-secondary-result'],
141+
)}
142+
>
143+
ホームへ
144+
</button>
145+
</div>
146+
</div>
147+
</div>
148+
</div>
149+
);
150+
};

0 commit comments

Comments
 (0)