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
Binary file added frontend/public/sounds/check-box-se.mp3
Binary file not shown.
Binary file added frontend/public/sounds/game2-bgm.mp3
Binary file not shown.
Binary file added frontend/public/sounds/game3-bgm.mp3
Binary file not shown.
Binary file added frontend/public/sounds/general-button-se.mp3
Binary file not shown.
Binary file added frontend/public/sounds/result-bgm.mp3
Binary file not shown.
Binary file added frontend/public/sounds/start-bgm.mp3
Binary file not shown.
52 changes: 49 additions & 3 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import type { CSSProperties } from 'react';

//まる爆発アニメーションコンポーネント
// --- まる爆発アニメーションコンポーネント ---
const SparklesExplosion = () => {
const count = 20;
const colors = ['#e9eb7c', '#ee7ee6', '#6eb8ca', '#e17a78', '#91ec77'];
Expand Down Expand Up @@ -56,16 +56,51 @@ const SparklesExplosion = () => {
);
};

// --- メインのトップページコンポーネント ---
export default function TopPage() {
const router = useRouter();
const [showExplosion, setShowExplosion] = useState(false);

// BGMを保持するための Ref
const bgmRef = useRef<HTMLAudioElement | null>(null);

// BGMの初期化と再生管理
useEffect(() => {
// パスは public/sounds/start-bgm.mp3 を想定
const bgm = new Audio('/sounds/start-bgm.mp3');
bgm.loop = true;
bgm.volume = 0.4;
bgmRef.current = bgm;

const playBGM = () => {
bgm.play().catch(() => {
// 自動再生制限がかかった場合は何もしない
});
// 一度クリックされたらイベントリスナーを削除
window.removeEventListener('click', playBGM);
};

window.addEventListener('click', playBGM);

// クリーンアップ
return () => {
bgm.pause();
window.removeEventListener('click', playBGM);
};
}, []);

const handleStartClick = () => {
setShowExplosion(true);

// SEの再生(パスを修正)
const audio = new Audio('/sounds/start-se.mp3');
audio.play().catch(() => {});

// ボタン押下時にBGMを停止
if (bgmRef.current) {
bgmRef.current.pause();
}

setTimeout(() => {
router.push('/games/terms');
}, 800);
Expand All @@ -76,7 +111,18 @@ export default function TopPage() {
};

return (
<div className="flex h-dvh w-full flex-col items-center justify-center overflow-hidden bg-top-pattern">
<div
className="flex h-dvh w-full flex-col items-center justify-center overflow-hidden bg-top-pattern"
style={{
backgroundImage: `
radial-gradient(circle, rgba(255,255,255,0.8) 1.0px, transparent 4px),
url('/images/bg-pattern.svg')
`,
backgroundSize: '16px 16px, cover',
backgroundPosition: '0 0, center',
backgroundRepeat: 'repeat, no-repeat',
}}
>
<div className="flex flex-col items-center gap-[2vh] w-full">
<Image
src="/images/RealYouLogo.png"
Expand Down
33 changes: 32 additions & 1 deletion frontend/src/features/diagnosis/components/BaselineSurvey.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

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

const bgmRef = useRef<HTMLAudioElement | null>(null);

const playSE = useCallback((path: string) => {
const audio = new Audio(path);
audio.volume = 0.5;
audio.play().catch(() => {});
}, []);

// BGMの初期化と再生管理
useEffect(() => {
const bgm = new Audio('/sounds/start-bgm.mp3');
bgm.loop = true;
bgm.volume = 0.4;
bgmRef.current = bgm;

const playBGM = () => {
bgm.play().catch(() => {});
window.removeEventListener('click', playBGM);
};

window.addEventListener('click', playBGM);
playBGM();

return () => {
bgm.pause();
window.removeEventListener('click', playBGM);
};
}, []);

const [currentIndex, setCurrentIndex] = useState(0);
const [answers, setAnswers] = useState<
Partial<Record<QuestionKey, AnswerOption>>
Expand Down Expand Up @@ -66,6 +95,7 @@ export default function BaselineSurvey() {
const handleAnswer = (value: AnswerOption) => {
const newAnswers = { ...answers, [currentQuestion.key]: value };
setAnswers(newAnswers);
playSE('/sounds/general-button-se.mp3');

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

const handleRetry = () => {
playSE('/sounds/general-button-se.mp3');
submitToApi(answers as BaselineAnswers);
};

Expand Down
49 changes: 44 additions & 5 deletions frontend/src/features/diagnosis/components/MbtiSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import Image from 'next/image';
import { useState, useCallback } from 'react';
import { useState, useCallback, useEffect, useRef } from 'react';
import { flushSync } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { useSetAtom } from 'jotai';
Expand Down Expand Up @@ -39,6 +39,36 @@ export default function MbtiSelect() {
const [transitionVia, setTransitionVia] = useState<
'tab' | 'arrow-left' | 'arrow-right'
>('tab');
const bgmRef = useRef<HTMLAudioElement | null>(null);

const playSE = useCallback((path: string) => {
const audio = new Audio(path);
audio.volume = 0.5;
audio.play().catch(() => {});
}, []);

// BGMの初期化と再生管理
useEffect(() => {
// TopPageと同じ start-bgm を使用
const bgm = new Audio('/sounds/start-bgm.mp3');
bgm.loop = true;
bgm.volume = 0.4;
bgmRef.current = bgm;

const playBGM = () => {
bgm.play().catch(() => {});
window.removeEventListener('click', playBGM);
};

window.addEventListener('click', playBGM);
// すでに他のページでインタラクションがあれば即再生される
playBGM();

return () => {
bgm.pause();
window.removeEventListener('click', playBGM);
};
}, []);

const currentGroup = MBTI_GROUPS[groupIndex];
const currentTypes = getTypesByGroup(currentGroup);
Expand All @@ -54,33 +84,39 @@ export default function MbtiSelect() {
flushSync(() => setTransitionVia('tab'));
setGroupIndex(index);
setSelected(null);
playSE('/sounds/general-button-se.mp3');
},
[groupIndex]
[groupIndex, playSE]
);

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

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

const handleConfirm = () => {
playSE('/sounds/general-button-se.mp3');
if (!selected) return;
setMbti(selected);
setStep('quiz');
};

const handleReselect = () => {
playSE('/sounds/general-button-se.mp3');
setSelected(null);
};

const handleSkip = () => {
playSE('/sounds/general-button-se.mp3');
setMbti(null);
setStep('quiz');
};
Expand Down Expand Up @@ -184,7 +220,10 @@ export default function MbtiSelect() {
<motion.button
key={type.code}
type="button"
onClick={() => setSelected(type.code)}
onClick={() => {
setSelected(type.code);
playSE('/sounds/general-button-se.mp3');
}}
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"
whileHover={{
scale: 1.2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@ export default function GroupChatGameFlow() {
const chatEndRef = useRef<HTMLDivElement>(null);
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('loading');
const pendingDataRef = useRef<Game3Data | null>(null);
const bgmRef = useRef<HTMLAudioElement | null>(null);

const playSE = useCallback((path: string) => {
const audio = new Audio(path);
audio.volume = 0.5;
audio.play().catch(() => {});
}, []);

// BGMの初期化と再生管理
useEffect(() => {
// 指示はgame2でしたが、Game3の画面のためgame3-bgm.mp3を適用します
const bgm = new Audio('/sounds/game3-bgm.mp3');
bgm.loop = true;
bgm.volume = 0.3;
bgmRef.current = bgm;

const playBGM = () => {
bgm.play().catch(() => {});
window.removeEventListener('click', playBGM);
};

window.addEventListener('click', playBGM);
// 前の画面から継続している場合は即再生
playBGM();

return () => {
bgm.pause();
window.removeEventListener('click', playBGM);
};
}, []);

const submitGame3 = useCallback(async (data: Game3Data) => {
const userId = localStorage.getItem('user_id');
Expand All @@ -43,6 +73,7 @@ export default function GroupChatGameFlow() {
try {
await submitGame3(data);
setSubmitStatus('success');
bgmRef.current?.pause();
setTimeout(() => {
router.push('/result');
}, 2000);
Expand All @@ -54,19 +85,21 @@ export default function GroupChatGameFlow() {
);

const handleRetry = useCallback(async () => {
playSE('/sounds/general-button-se.mp3'); // リトライ音
const data = pendingDataRef.current;
if (!data) return;
setSubmitStatus('loading');
try {
await submitGame3(data);
setSubmitStatus('success');
bgmRef.current?.pause();
setTimeout(() => {
router.push('/result');
}, 2000);
} catch {
setSubmitStatus('error');
}
}, [router, submitGame3]);
}, [router, submitGame3, playSE]);

const {
gamePhase,
Expand All @@ -76,14 +109,28 @@ export default function GroupChatGameFlow() {
remainingTimeMs,
isTypingIndicatorVisible,
typingBotName,
startGame,
selectOption,
startGame: originalStartGame, // 名前を変更してラップ
selectOption: originalSelectOption, // 名前を変更してラップ
handleOptionHover,
stageTimeLimitMs,
groupName,
groupMemberCount,
} = useGroupChatGame({ onComplete: handleComplete });

// サウンドを鳴らすようにラップ
const startGame = useCallback(() => {
playSE('/sounds/general-button-se.mp3');
originalStartGame();
}, [originalStartGame, playSE]);

const selectOption = useCallback(
(optionId: number) => {
playSE('/sounds/general-button-se.mp3');
originalSelectOption(optionId);
},
[originalSelectOption, playSE]
);

useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatMessages, isTypingIndicatorVisible, gamePhase]);
Expand Down
Loading