Skip to content

Commit 83b6a2d

Browse files
committed
feat(sound): add bgm and se to diagnosis, adjust helpdesk volume
1 parent bce778f commit 83b6a2d

4 files changed

Lines changed: 99 additions & 31 deletions

File tree

frontend/src/features/diagnosis/components/BaselineSurvey.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useState, useCallback } from 'react';
3+
import { useState, useCallback, useEffect, useRef } from 'react';
44
import { useAtomValue } from 'jotai';
55
import { useRouter } from 'next/navigation';
66
import { mbtiAtom } from '@/stores/diagnosis';
@@ -22,6 +22,35 @@ export default function BaselineSurvey() {
2222
const mbti = useAtomValue(mbtiAtom);
2323
const game1Data = useAtomValue(game1DataAtom);
2424

25+
const bgmRef = useRef<HTMLAudioElement | null>(null);
26+
27+
const playSE = useCallback((path: string) => {
28+
const audio = new Audio(path);
29+
audio.volume = 0.5;
30+
audio.play().catch(() => { });
31+
}, []);
32+
33+
// BGMの初期化と再生管理
34+
useEffect(() => {
35+
const bgm = new Audio('/sounds/start-bgm.mp3');
36+
bgm.loop = true;
37+
bgm.volume = 0.4;
38+
bgmRef.current = bgm;
39+
40+
const playBGM = () => {
41+
bgm.play().catch(() => { });
42+
window.removeEventListener('click', playBGM);
43+
};
44+
45+
window.addEventListener('click', playBGM);
46+
playBGM();
47+
48+
return () => {
49+
bgm.pause();
50+
window.removeEventListener('click', playBGM);
51+
};
52+
}, []);
53+
2554
const [currentIndex, setCurrentIndex] = useState(0);
2655
const [answers, setAnswers] = useState<
2756
Partial<Record<QuestionKey, AnswerOption>>
@@ -66,6 +95,7 @@ export default function BaselineSurvey() {
6695
const handleAnswer = (value: AnswerOption) => {
6796
const newAnswers = { ...answers, [currentQuestion.key]: value };
6897
setAnswers(newAnswers);
98+
playSE('/sounds/general-button-se.mp3');
6999

70100
if (currentIndex < totalQuestions - 1) {
71101
setCurrentIndex((prev) => prev + 1);
@@ -75,6 +105,7 @@ export default function BaselineSurvey() {
75105
};
76106

77107
const handleRetry = () => {
108+
playSE('/sounds/general-button-se.mp3');
78109
submitToApi(answers as BaselineAnswers);
79110
};
80111

@@ -132,9 +163,8 @@ export default function BaselineSurvey() {
132163
{Array.from({ length: totalQuestions }).map((_, i) => (
133164
<div
134165
key={i}
135-
className={`h-6 w-8 rounded-lg transition-colors ${
136-
i <= currentIndex ? 'bg-rose-400' : 'bg-gray-200'
137-
}`}
166+
className={`h-6 w-8 rounded-lg transition-colors ${i <= currentIndex ? 'bg-rose-400' : 'bg-gray-200'
167+
}`}
138168
/>
139169
))}
140170
</div>

frontend/src/features/diagnosis/components/MbtiSelect.tsx

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import Image from 'next/image';
4-
import { useState, useCallback } from 'react';
4+
import { useState, useCallback, useEffect, useRef } from 'react';
55
import { flushSync } from 'react-dom';
66
import { motion, AnimatePresence } from 'framer-motion';
77
import { useSetAtom } from 'jotai';
@@ -39,6 +39,36 @@ export default function MbtiSelect() {
3939
const [transitionVia, setTransitionVia] = useState<
4040
'tab' | 'arrow-left' | 'arrow-right'
4141
>('tab');
42+
const bgmRef = useRef<HTMLAudioElement | null>(null);
43+
44+
const playSE = useCallback((path: string) => {
45+
const audio = new Audio(path);
46+
audio.volume = 0.5;
47+
audio.play().catch(() => { });
48+
}, []);
49+
50+
// BGMの初期化と再生管理
51+
useEffect(() => {
52+
// TopPageと同じ start-bgm を使用
53+
const bgm = new Audio('/sounds/start-bgm.mp3');
54+
bgm.loop = true;
55+
bgm.volume = 0.4;
56+
bgmRef.current = bgm;
57+
58+
const playBGM = () => {
59+
bgm.play().catch(() => { });
60+
window.removeEventListener('click', playBGM);
61+
};
62+
63+
window.addEventListener('click', playBGM);
64+
// すでに他のページでインタラクションがあれば即再生される
65+
playBGM();
66+
67+
return () => {
68+
bgm.pause();
69+
window.removeEventListener('click', playBGM);
70+
};
71+
}, []);
4272

4373
const currentGroup = MBTI_GROUPS[groupIndex];
4474
const currentTypes = getTypesByGroup(currentGroup);
@@ -54,33 +84,39 @@ export default function MbtiSelect() {
5484
flushSync(() => setTransitionVia('tab'));
5585
setGroupIndex(index);
5686
setSelected(null);
87+
playSE('/sounds/general-button-se.mp3');
5788
},
58-
[groupIndex]
89+
[groupIndex, playSE]
5990
);
6091

6192
const handleArrowPrev = useCallback(() => {
6293
setTransitionVia('arrow-right'); // コンテンツは右から入る
6394
setGroupIndex((i) => (i - 1 + MBTI_GROUPS.length) % MBTI_GROUPS.length);
6495
setSelected(null);
65-
}, []);
96+
playSE('/sounds/general-button-se.mp3');
97+
}, [playSE]);
6698

6799
const handleArrowNext = useCallback(() => {
68100
setTransitionVia('arrow-left'); // コンテンツは左から入る
69101
setGroupIndex((i) => (i + 1) % MBTI_GROUPS.length);
70102
setSelected(null);
71-
}, []);
103+
playSE('/sounds/general-button-se.mp3');
104+
}, [playSE]);
72105

73106
const handleConfirm = () => {
107+
playSE('/sounds/general-button-se.mp3');
74108
if (!selected) return;
75109
setMbti(selected);
76110
setStep('quiz');
77111
};
78112

79113
const handleReselect = () => {
114+
playSE('/sounds/general-button-se.mp3');
80115
setSelected(null);
81116
};
82117

83118
const handleSkip = () => {
119+
playSE('/sounds/general-button-se.mp3');
84120
setMbti(null);
85121
setStep('quiz');
86122
};
@@ -120,9 +156,8 @@ export default function MbtiSelect() {
120156
key={group}
121157
type="button"
122158
onClick={() => handleTabClick(index)}
123-
className={`relative z-10 cursor-pointer rounded-t-lg border-4 border-b-0 border-gray-800 px-4 py-2 text-base font-semibold text-gray-800 ${
124-
GROUP_COLORS[index]
125-
} ${groupIndex === index ? 'mb-[-4px]' : ''}`}
159+
className={`relative z-10 cursor-pointer rounded-t-lg border-4 border-b-0 border-gray-800 px-4 py-2 text-base font-semibold text-gray-800 ${GROUP_COLORS[index]
160+
} ${groupIndex === index ? 'mb-[-4px]' : ''}`}
126161
animate={{
127162
y: groupIndex === index ? 2 : 0,
128163
boxShadow:
@@ -184,7 +219,10 @@ export default function MbtiSelect() {
184219
<motion.button
185220
key={type.code}
186221
type="button"
187-
onClick={() => setSelected(type.code)}
222+
onClick={() => {
223+
setSelected(type.code);
224+
playSE('/sounds/general-button-se.mp3');
225+
}}
188226
className="relative z-0 flex flex-1 basis-0 flex-col cursor-pointer items-center justify-center overflow-hidden rounded-4xl border-2 border-gray-800 bg-white/80 outline-none ring-0 hover:z-50"
189227
whileHover={{
190228
scale: 1.2,
@@ -236,14 +274,13 @@ export default function MbtiSelect() {
236274
aria-labelledby="mbti-confirm-title"
237275
>
238276
<div
239-
className={`mx-4 w-full max-w-md rounded-2xl border-4 border-gray-800 p-6 shadow-xl ${
240-
GROUP_COLORS[
241-
Math.max(
242-
0,
243-
MBTI_GROUPS.findIndex((g) => g === selectedType.group)
244-
)
277+
className={`mx-4 w-full max-w-md rounded-2xl border-4 border-gray-800 p-6 shadow-xl ${GROUP_COLORS[
278+
Math.max(
279+
0,
280+
MBTI_GROUPS.findIndex((g) => g === selectedType.group)
281+
)
245282
]
246-
}`}
283+
}`}
247284
>
248285
<p
249286
id="mbti-confirm-title"

frontend/src/features/games/helpdesk/components/HelpdeskGameFlow.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@ export default function HelpdeskGameFlow() {
2222
const playSE = useCallback((path: string) => {
2323
const audio = new Audio(path);
2424
audio.volume = 0.5;
25-
audio.play().catch(() => {});
25+
audio.play().catch(() => { });
2626
}, []);
2727

2828
// BGMの初期化と再生管理
2929
useEffect(() => {
3030
const bgm = new Audio('/sounds/game2-bgm.mp3');
3131
bgm.loop = true;
32-
bgm.volume = 0.4;
32+
bgm.volume = 0.2;
3333
bgmRef.current = bgm;
3434

3535
const playBGM = () => {
36-
bgm.play().catch(() => {});
36+
bgm.play().catch(() => { });
3737
window.removeEventListener('click', playBGM);
3838
};
3939

frontend/src/features/games/helpdesk/hooks/useHelpdeskGame.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,12 @@ export function useHelpdeskGame(options: {
227227
const hasTextTurn = currentTurns.some((t) => t.inputMethod === 'text');
228228
const textInputMetrics: TextInputMetrics | null = hasTextTurn
229229
? {
230-
typingIntervalVariance:
231-
typingVariancesRef.current.length > 0
232-
? typingVariancesRef.current.reduce((a, b) => a + b, 0) /
233-
typingVariancesRef.current.length
234-
: 0,
235-
}
230+
typingIntervalVariance:
231+
typingVariancesRef.current.length > 0
232+
? typingVariancesRef.current.reduce((a, b) => a + b, 0) /
233+
typingVariancesRef.current.length
234+
: 0,
235+
}
236236
: null;
237237
const game2Data: Game2Data = {
238238
inputMethod: inputMethodRef.current,
@@ -245,7 +245,7 @@ export function useHelpdeskGame(options: {
245245

246246
// 電話終了音
247247
if (hangupAudioRef.current) {
248-
hangupAudioRef.current.play().catch(() => {});
248+
hangupAudioRef.current.play().catch(() => { });
249249
}
250250
}, [onComplete]);
251251

@@ -330,6 +330,7 @@ export function useHelpdeskGame(options: {
330330
const utterance = new SpeechSynthesisUtterance(supportText);
331331
utterance.lang = 'ja-JP';
332332
utterance.rate = 1.3;
333+
utterance.volume = 1.0;
333334
utterance.onend = transitionToInput;
334335
utterance.onerror = transitionToInput;
335336
window.speechSynthesis.speak(utterance);
@@ -556,7 +557,7 @@ export function useHelpdeskGame(options: {
556557
const silentAudio = new Audio();
557558
silentAudio.src =
558559
'data:audio/wav;base64,UklGRigAAABXQVZFRm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=';
559-
silentAudio.play().catch(() => {});
560+
silentAudio.play().catch(() => { });
560561

561562
// マイクの事前許可を求める
562563
try {
@@ -578,7 +579,7 @@ export function useHelpdeskGame(options: {
578579

579580
// 電話呼び出し音を開始
580581
if (callingAudioRef.current) {
581-
callingAudioRef.current.play().catch(() => {});
582+
callingAudioRef.current.play().catch(() => { });
582583
}
583584
}, [gamePhase]);
584585

0 commit comments

Comments
 (0)