Skip to content

Commit 68f5c1b

Browse files
authored
Merge pull request #38 from kc3hack/feature/game3-ui
Feature/game3 UI
2 parents 1c6ca9f + 500bf98 commit 68f5c1b

2 files changed

Lines changed: 161 additions & 135 deletions

File tree

frontend/src/features/games/group-chat/components/GroupChatGameFlow.tsx

Lines changed: 142 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export default function GroupChatGameFlow() {
8080
selectOption,
8181
handleOptionHover,
8282
stageTimeLimitMs,
83-
totalStages,
8483
groupName,
8584
groupMemberCount,
8685
} = useGroupChatGame({ onComplete: handleComplete });
@@ -89,51 +88,29 @@ export default function GroupChatGameFlow() {
8988
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
9089
}, [chatMessages, isTypingIndicatorVisible, gamePhase]);
9190

92-
if (gamePhase === 'completed') {
93-
if (submitStatus === 'loading') {
94-
return (
95-
<div className="flex min-h-screen items-center justify-center bg-gray-100">
96-
<Spinner message="送信中..." />
97-
</div>
98-
);
99-
}
100-
if (submitStatus === 'error') {
101-
return (
102-
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-gray-100">
103-
<p className="text-lg font-semibold text-red-600">
104-
通信に失敗しました
105-
</p>
106-
<button
107-
onClick={handleRetry}
108-
className="rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white transition hover:bg-blue-700"
109-
>
110-
リトライ
111-
</button>
112-
</div>
113-
);
114-
}
115-
return (
116-
<div className="flex min-h-screen items-center justify-center bg-gray-100">
117-
<div className="text-center">
118-
<p className="text-lg font-bold">完了しました</p>
119-
<p className="mt-2 text-sm text-gray-500">
120-
Loading画面へ移動します...
121-
</p>
122-
</div>
123-
</div>
124-
);
125-
}
91+
const isOverlayActive = ['tutorial', 'stage-cutin', 'completed'].includes(
92+
gamePhase
93+
);
12694

12795
const timerRatio = remainingTimeMs / stageTimeLimitMs;
128-
const timerColorClass =
129-
timerRatio > 0.5
130-
? 'bg-green-500'
131-
: timerRatio > 0.2
132-
? 'bg-amber-500'
133-
: 'bg-red-500';
13496

13597
return (
136-
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-100 p-4">
98+
<div
99+
className="fixed inset-0 flex flex-col items-center justify-center overflow-hidden p-4"
100+
style={{ backgroundColor: '#F0D44A', height: '100dvh' }}
101+
>
102+
{/* 背景のドット模様(CSSで描画) - オーバーレイ非表示時のみ */}
103+
{!isOverlayActive && (
104+
<div
105+
className="absolute inset-0 z-0 opacity-40"
106+
style={{
107+
backgroundImage:
108+
'radial-gradient(circle, rgba(255,255,255,0.8) 1.0px, transparent 4px)',
109+
backgroundSize: '16px 16px, cover',
110+
}}
111+
/>
112+
)}
113+
137114
{/* --- チュートリアルオーバーレイ --- */}
138115
{gamePhase === 'tutorial' && (
139116
<div
@@ -143,16 +120,16 @@ export default function GroupChatGameFlow() {
143120
onKeyDown={(e) => {
144121
if (e.key === 'Enter' || e.key === ' ') startGame();
145122
}}
146-
className="fixed inset-0 z-50 flex cursor-pointer flex-col items-center justify-center bg-black/30"
123+
className="fixed inset-0 z-50 flex cursor-pointer flex-col items-center justify-center bg-black/60"
147124
>
148-
<div className="animate-[fadeInUp_0.4s_ease-out] px-6 text-center">
149-
<p className="text-lg font-black tracking-widest text-white drop-shadow-lg">
150-
指示‼️
125+
<div className="z-10 animate-[fadeInUp_0.4s_ease-out] px-6 text-center">
126+
<p className="text-3xl font-black tracking-widest text-white drop-shadow-md">
127+
適切に応答せよ!
151128
</p>
152-
<p className="mt-6 whitespace-pre-line text-xl font-bold leading-relaxed text-white drop-shadow-lg">
129+
<p className="mt-6 whitespace-pre-line text-lg font-bold leading-relaxed text-white drop-shadow-md">
153130
{TUTORIAL_TEXT}
154131
</p>
155-
<p className="mt-8 animate-pulse text-sm text-white/70">
132+
<p className="mt-8 animate-pulse text-sm font-bold text-white/80">
156133
タップして開始
157134
</p>
158135
</div>
@@ -161,7 +138,7 @@ export default function GroupChatGameFlow() {
161138

162139
{/* --- カットイン演出 --- */}
163140
{gamePhase === 'stage-cutin' && currentStage && (
164-
<div className="fixed inset-0 z-40 flex items-center justify-center bg-black/60">
141+
<div className="fixed inset-0 z-40 flex flex-col items-center justify-center bg-black/60">
165142
<div className="animate-[fadeInUp_0.3s_ease-out] text-center">
166143
<p className="text-4xl font-black text-white drop-shadow-lg">
167144
場面{currentStageIndex + 1}
@@ -176,74 +153,102 @@ export default function GroupChatGameFlow() {
176153
</div>
177154
)}
178155

156+
{/* --- 終了(完了)オーバーレイ --- */}
157+
{gamePhase === 'completed' && (
158+
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-black/60">
159+
<div className="z-10 animate-[fadeInUp_0.4s_ease-out] px-6 text-center">
160+
{submitStatus === 'error' ? (
161+
<>
162+
<p className="text-4xl font-black tracking-widest text-[#e03131] drop-shadow-md bg-white px-6 py-2 rounded-xl border-[4px] border-black">
163+
通信エラー!
164+
</p>
165+
<button
166+
onClick={handleRetry}
167+
className="mt-6 flex items-center justify-center rounded-xl border-[4px] border-black bg-white px-8 py-3 text-xl font-black text-black shadow-[4px_4px_0_0_#000] transition-transform hover:-translate-y-1 hover:shadow-[6px_6px_0_0_#000] mx-auto"
168+
>
169+
リトライする
170+
</button>
171+
</>
172+
) : (
173+
<>
174+
<p className="text-6xl font-black tracking-widest text-white drop-shadow-lg">
175+
終了!
176+
</p>
177+
{submitStatus === 'loading' && (
178+
<div className="mt-8 flex justify-center text-white">
179+
<Spinner message="送信中..." />
180+
</div>
181+
)}
182+
{submitStatus === 'success' && (
183+
<p className="mt-6 text-lg font-bold text-white/80">
184+
Loading画面へ移動します...
185+
</p>
186+
)}
187+
</>
188+
)}
189+
</div>
190+
</div>
191+
)}
192+
179193
{/* --- スマホフレーム --- */}
180194
<div
181-
className="flex w-full max-w-sm flex-col overflow-hidden rounded-2xl border-2 border-gray-800 bg-white shadow-2xl"
195+
className="relative z-10 flex w-full max-w-sm flex-col overflow-hidden rounded-[24px] border-[6px] border-black bg-white shadow-[8px_8px_0px_0px_rgba(0,0,0,0.3)] transition-all"
182196
style={{ height: 'min(90vh, 700px)' }}
183197
>
184198
{/* ヘッダー: LINE風 */}
185-
<header className="flex items-center justify-between bg-blue-500 px-4 py-3 text-white">
199+
<header className="flex items-center justify-between border-b-[6px] border-black bg-[#2d5be3] px-4 py-3 text-white">
186200
<div className="flex items-center gap-2">
187-
<span className="inline-block h-2 w-2 rounded-full bg-green-300" />
188-
<h1 className="text-sm font-bold">
201+
<span className="inline-block h-3 w-3 rounded-full border-[2px] border-black bg-[#57d071]" />
202+
<h1 className="text-lg font-black tracking-widest">
189203
{groupName}({groupMemberCount})
190204
</h1>
191205
</div>
192-
{currentStage && (
193-
<span className="rounded-full bg-white/20 px-2 py-0.5 text-[10px] font-medium">
194-
{currentStageIndex + 1}/{totalStages}
195-
</span>
196-
)}
206+
<button
207+
onClick={() => window.location.reload()}
208+
className="flex items-center justify-center rounded-full border-[3px] border-black bg-[#e03131] px-4 py-1 text-xs font-black shadow-[2px_2px_0_0_#000] transition-all hover:translate-y-0.5 hover:shadow-[0_0_0_0_#000]"
209+
>
210+
RESET
211+
</button>
197212
</header>
198213

199214
{/* チャットエリア */}
200-
<div className="flex-1 overflow-y-auto bg-sky-100 px-3 py-3">
201-
<div className="space-y-3">
202-
{/* DAYラベル */}
203-
{currentStage && (
204-
<>
205-
<p className="text-center text-[10px] text-gray-400">TODAY</p>
206-
<p className="text-center">
207-
<span className="inline-block rounded-full bg-gray-300/60 px-3 py-0.5 text-[10px] text-gray-500">
208-
{currentStage.dayLabel}
209-
</span>
210-
</p>
211-
</>
212-
)}
213-
215+
<div className="flex-1 overflow-y-auto bg-[#dae5f3] px-3 py-4">
216+
<div className="space-y-4">
214217
{chatMessages.map((msg, i) =>
215-
msg.type === 'bot' ? (
216-
<div
217-
key={`s${currentStageIndex}-${i}-${msg.botId}`}
218-
className="flex items-start gap-2"
219-
>
218+
msg.type === 'separator' ? (
219+
<div key={`msg-${i}`} className="my-2 flex justify-center">
220+
<span className="rounded-full border-[2px] border-gray-300 bg-white/50 px-4 py-1 text-[10px] font-bold text-gray-500">
221+
{msg.label}
222+
</span>
223+
</div>
224+
) : msg.type === 'bot' ? (
225+
<div key={`msg-${i}`} className="flex items-start gap-2">
220226
{(() => {
221227
const bot = getBotByBotId(msg.botId);
222228
return (
223-
<div className="flex flex-col items-center gap-0.5">
229+
<div className="mt-1 flex flex-col items-center gap-1">
224230
<div
225-
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-xs font-bold ${
231+
className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-full border-[3px] border-black text-sm font-black shadow-[2px_2px_0_0_#000] ${
226232
bot?.color ?? 'bg-gray-400 text-white'
227233
}`}
228234
>
229235
{bot?.avatarLabel ?? '?'}
230236
</div>
231-
<span className="text-[9px] text-gray-500">
232-
{bot?.name}
233-
</span>
234237
</div>
235238
);
236239
})()}
237-
<div className="max-w-[70%] rounded-lg rounded-tl-none bg-white px-3 py-2 text-sm shadow-sm">
238-
{msg.text}
240+
<div className="flex flex-col">
241+
<span className="mb-1 ml-1 text-[10px] font-bold text-gray-600">
242+
{getBotByBotId(msg.botId)?.name}
243+
</span>
244+
<div className="max-w-[80%] rounded-2xl rounded-tl-none border-[3px] border-black bg-white px-4 py-3 text-sm font-bold text-black shadow-[2px_2px_0_0_#000]">
245+
{msg.text}
246+
</div>
239247
</div>
240248
</div>
241249
) : (
242-
<div
243-
key={`s${currentStageIndex}-${i}-user`}
244-
className="flex justify-end"
245-
>
246-
<div className="max-w-[70%] rounded-lg rounded-tr-none bg-green-400 px-3 py-2 text-sm text-white shadow-sm">
250+
<div key={`msg-${i}`} className="flex justify-end">
251+
<div className="max-w-[80%] rounded-2xl rounded-tr-none border-[3px] border-black bg-[#57d071] px-4 py-3 text-sm font-bold text-white shadow-[2px_2px_0_0_#000]">
247252
{msg.text}
248253
</div>
249254
</div>
@@ -252,19 +257,19 @@ export default function GroupChatGameFlow() {
252257

253258
{/* 入力中インジケータ */}
254259
{isTypingIndicatorVisible && typingBotName && (
255-
<div className="flex items-center justify-center gap-2 rounded-full bg-white/80 px-4 py-1.5 shadow-sm mx-auto w-fit">
256-
<span className="inline-flex gap-0.5">
257-
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-500" />
260+
<div className="mx-auto flex w-fit items-center justify-center gap-2 rounded-full border-[3px] border-black bg-white px-4 py-2 shadow-[2px_2px_0_0_#000]">
261+
<span className="inline-flex gap-1">
262+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-black" />
258263
<span
259-
className="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-500"
264+
className="h-1.5 w-1.5 animate-bounce rounded-full bg-black"
260265
style={{ animationDelay: '0.15s' }}
261266
/>
262267
<span
263-
className="h-1.5 w-1.5 animate-bounce rounded-full bg-gray-500"
268+
className="h-1.5 w-1.5 animate-bounce rounded-full bg-black"
264269
style={{ animationDelay: '0.3s' }}
265270
/>
266271
</span>
267-
<span className="text-sm font-medium text-gray-600">
272+
<span className="text-sm font-bold text-black">
268273
{typingBotName}が返信中
269274
</span>
270275
</div>
@@ -274,53 +279,57 @@ export default function GroupChatGameFlow() {
274279
</div>
275280

276281
{/* 下部: タイマー + 選択肢 */}
277-
<div className="border-t border-gray-800 bg-amber-50">
282+
<div className="border-t-[6px] border-black bg-[#f1cf44] pb-6">
278283
{gamePhase === 'waiting-input' && currentStage && (
279-
<div className="px-3 pb-3 pt-2">
284+
<>
280285
{/* タイマーゲージ */}
281-
<div className="mb-2 h-1.5 overflow-hidden rounded-full bg-gray-200">
286+
<div className="h-2 w-full bg-black">
282287
<div
283-
className={`h-full transition-all duration-100 ${timerColorClass}`}
288+
className="h-full bg-[#e03131] transition-all duration-100"
284289
style={{ width: `${timerRatio * 100}%` }}
285290
/>
286291
</div>
287292
{/* 選択肢 */}
288-
{currentStage.options.some((o) => o.label) ? (
289-
<div className="flex flex-col gap-1.5">
290-
{currentStage.options.map((opt, idx) => (
291-
<button
292-
key={idx}
293-
type="button"
294-
onMouseEnter={() => handleOptionHover(idx + 1)}
295-
onFocus={() => handleOptionHover(idx + 1)}
296-
onClick={() => selectOption(idx + 1)}
297-
className="flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-left text-sm transition-colors hover:bg-gray-50 active:bg-gray-100"
298-
>
299-
<span className="text-base">{opt.emoji}</span>
300-
<span>{opt.label}</span>
301-
</button>
302-
))}
303-
</div>
304-
) : (
305-
<div className="grid grid-cols-4 gap-2">
306-
{currentStage.options.map((opt, idx) => (
307-
<button
308-
key={idx}
309-
type="button"
310-
onMouseEnter={() => handleOptionHover(idx + 1)}
311-
onFocus={() => handleOptionHover(idx + 1)}
312-
onClick={() => selectOption(idx + 1)}
313-
className="flex items-center justify-center rounded-lg border border-gray-300 bg-white py-3 text-3xl transition-colors hover:bg-gray-50 active:bg-gray-100"
314-
>
315-
{opt.emoji}
316-
</button>
317-
))}
318-
</div>
319-
)}
320-
</div>
293+
<div className="px-5 pt-5 pb-2">
294+
{currentStage.options.some((o) => o.label) ? (
295+
<div className="flex flex-col gap-3">
296+
{currentStage.options.map((opt, idx) => (
297+
<button
298+
key={idx}
299+
type="button"
300+
onMouseEnter={() => handleOptionHover(idx + 1)}
301+
onFocus={() => handleOptionHover(idx + 1)}
302+
onClick={() => selectOption(idx + 1)}
303+
className="flex items-center gap-3 rounded-xl border-[3px] border-black bg-white px-5 py-3.5 text-left font-bold transition-transform hover:-translate-y-1 hover:shadow-[4px_4px_0_0_#000]"
304+
>
305+
<span className="text-lg">{opt.emoji}</span>
306+
<span className="text-[13px] text-black">
307+
{opt.label}
308+
</span>
309+
</button>
310+
))}
311+
</div>
312+
) : (
313+
<div className="grid grid-cols-4 gap-3">
314+
{currentStage.options.map((opt, idx) => (
315+
<button
316+
key={idx}
317+
type="button"
318+
onMouseEnter={() => handleOptionHover(idx + 1)}
319+
onFocus={() => handleOptionHover(idx + 1)}
320+
onClick={() => selectOption(idx + 1)}
321+
className="flex items-center justify-center rounded-xl border-[3px] border-black bg-white py-4 text-3xl font-bold transition-transform hover:-translate-y-1 hover:shadow-[4px_4px_0_0_#000]"
322+
>
323+
{opt.emoji}
324+
</button>
325+
))}
326+
</div>
327+
)}
328+
</div>
329+
</>
321330
)}
322331
{(gamePhase === 'chat-playing' || gamePhase === 'stage-cutin') && (
323-
<p className="py-3 text-center text-xs text-gray-400">
332+
<p className="py-6 text-center text-sm font-bold text-black/60">
324333
メッセージを表示しています...
325334
</p>
326335
)}

0 commit comments

Comments
 (0)