From cb41efeb1a1d2ec99dd4b9b7bc9b5bd737b50afc Mon Sep 17 00:00:00 2001 From: ru-se Date: Sun, 22 Feb 2026 11:24:21 +0900 Subject: [PATCH 1/7] feat: implement interactive MBTI loading screen with sequential animations --- .../src/components/common/LoadingScreen.tsx | 180 ++++++++++++++++++ .../diagnosis/components/BaselineSurvey.tsx | 28 +-- .../features/result/components/ResultPage.tsx | 5 +- 3 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 frontend/src/components/common/LoadingScreen.tsx diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx new file mode 100644 index 0000000..f7150e7 --- /dev/null +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -0,0 +1,180 @@ +'use client'; + +import React, { useEffect, useState, useMemo } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import Image from 'next/image'; + +interface LoadingScreenProps { + message?: string; +} + +const MBTI_GROUPS = [ + { + name: 'Analysts', + color: '#E0D7FF', // Purple-ish + textColor: '#5D2FB7', + characters: ['INTJ', 'INTP', 'ENTJ', 'ENTP'], + }, + { + name: 'Diplomats', + color: '#D7FFD7', // Green-ish + textColor: '#2D812D', + characters: ['INFJ', 'INFP', 'ENFJ', 'ENFP'], + }, + { + name: 'Sentinels', + color: '#D7F3FF', // Blue-ish + textColor: '#2B6DA1', + characters: ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'], + }, + { + name: 'Explorers', + color: '#FFF7D7', // Yellow-ish + textColor: '#A17D1F', + characters: ['ISTP', 'ISFP', 'ESTP', 'ESFP'], + }, +]; + +export default function LoadingScreen({ + message = 'Loading...', +}: LoadingScreenProps) { + const [activeStep, setActiveStep] = useState(0); + + // Pick one random character from each group + const selectedCharacters = useMemo(() => { + return MBTI_GROUPS.map((group) => { + const randomIndex = Math.floor(Math.random() * group.characters.length); + return { + id: group.characters[randomIndex], + groupColor: group.color, + textColor: group.textColor, + }; + }); + }, []); + + useEffect(() => { + const timer = setInterval(() => { + setActiveStep((prev) => (prev + 1) % selectedCharacters.length); + }, 1500); // Wait for jump animation to mostly complete + + return () => clearInterval(timer); + }, [selectedCharacters.length]); + + return ( + + {/* Retro-pop dot pattern background */} +
+ +
+ {/* Characters Row */} +
+ {selectedCharacters.map((char, index) => { + const isActive = index === activeStep; + + return ( +
+ + {char.id} + + + {/* Visual indicator / shadow under active char */} + +
+ ); + })} +
+ + {/* Loading Text */} +
+ + {message} + + + {/* Pulsing dots indicator */} +
+ {[0, 1, 2].map((i) => ( + + ))} +
+
+
+ + {/* Group Name display (Subtle) */} +
+ + {MBTI_GROUPS[activeStep].name} + +
+ + ); +} diff --git a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx index 0e8a1a4..fbac092 100644 --- a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx +++ b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx @@ -11,7 +11,8 @@ import { type BaselineAnswers, type AnswerOption, } from '@/features/diagnosis/types'; -import Spinner from '@/components/ui/Spinner'; +import Spinner from '@/components/common/Spinner'; +import LoadingScreen from '@/components/common/LoadingScreen'; import { postRegister, submitGame } from '@/lib/api'; type Status = 'answering' | 'loading' | 'error' | 'success'; @@ -79,11 +80,7 @@ export default function BaselineSurvey() { }; if (status === 'loading') { - return ( -
- -
- ); + return ; } if (status === 'error') { @@ -101,19 +98,7 @@ export default function BaselineSurvey() { } if (status === 'success') { - return ( -
-

診断完了!

-

- これからゲームが始まります。 -
- ゲームでのあなたの行動から、本当の性格を分析します。 -

-

- まもなくゲーム画面に移動します... -

-
- ); + return ; } return ( @@ -132,9 +117,8 @@ export default function BaselineSurvey() { {Array.from({ length: totalQuestions }).map((_, i) => (
))}
diff --git a/frontend/src/features/result/components/ResultPage.tsx b/frontend/src/features/result/components/ResultPage.tsx index 96c708a..2179b0b 100644 --- a/frontend/src/features/result/components/ResultPage.tsx +++ b/frontend/src/features/result/components/ResultPage.tsx @@ -5,13 +5,14 @@ import { resultAtom } from '@/stores/result'; import { useResult } from '../hooks/useResult'; import AnalyzingView from './AnalyzingView'; import ResultReport from './ResultReport'; +import LoadingScreen from '@/components/common/LoadingScreen'; export default function ResultPage() { const { status, errorMessage, retry } = useResult(); const result = useAtomValue(resultAtom); if (status === 'loading') { - return ; + return ; } if (status === 'error') { @@ -28,5 +29,5 @@ export default function ResultPage() { return ; } - return ; + return ; } From 048164cac2cac2f8a928771659bb8d52b22e2411 Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 11:56:27 +0900 Subject: [PATCH 2/7] =?UTF-8?q?style:=20=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/LoadingScreen.tsx | 323 +++++++++--------- .../diagnosis/components/BaselineSurvey.tsx | 5 +- 2 files changed, 171 insertions(+), 157 deletions(-) diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx index f7150e7..ec184e2 100644 --- a/frontend/src/components/common/LoadingScreen.tsx +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/purity */ 'use client'; import React, { useEffect, useState, useMemo } from 'react'; @@ -5,176 +6,188 @@ import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; interface LoadingScreenProps { - message?: string; + message?: string; } const MBTI_GROUPS = [ - { - name: 'Analysts', - color: '#E0D7FF', // Purple-ish - textColor: '#5D2FB7', - characters: ['INTJ', 'INTP', 'ENTJ', 'ENTP'], - }, - { - name: 'Diplomats', - color: '#D7FFD7', // Green-ish - textColor: '#2D812D', - characters: ['INFJ', 'INFP', 'ENFJ', 'ENFP'], - }, - { - name: 'Sentinels', - color: '#D7F3FF', // Blue-ish - textColor: '#2B6DA1', - characters: ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'], - }, - { - name: 'Explorers', - color: '#FFF7D7', // Yellow-ish - textColor: '#A17D1F', - characters: ['ISTP', 'ISFP', 'ESTP', 'ESFP'], - }, + { + name: 'Analysts', + color: '#E0D7FF', // Purple-ish + textColor: '#5D2FB7', + characters: ['INTJ', 'INTP', 'ENTJ', 'ENTP'], + }, + { + name: 'Diplomats', + color: '#D7FFD7', // Green-ish + textColor: '#2D812D', + characters: ['INFJ', 'INFP', 'ENFJ', 'ENFP'], + }, + { + name: 'Sentinels', + color: '#D7F3FF', // Blue-ish + textColor: '#2B6DA1', + characters: ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'], + }, + { + name: 'Explorers', + color: '#FFF7D7', // Yellow-ish + textColor: '#A17D1F', + characters: ['ISTP', 'ISFP', 'ESTP', 'ESFP'], + }, ]; export default function LoadingScreen({ - message = 'Loading...', + message = 'Loading...', }: LoadingScreenProps) { - const [activeStep, setActiveStep] = useState(0); + const [activeStep, setActiveStep] = useState(0); - // Pick one random character from each group - const selectedCharacters = useMemo(() => { - return MBTI_GROUPS.map((group) => { - const randomIndex = Math.floor(Math.random() * group.characters.length); - return { - id: group.characters[randomIndex], - groupColor: group.color, - textColor: group.textColor, - }; - }); - }, []); + // Pick one random character from each group + const selectedCharacters = useMemo(() => { + return MBTI_GROUPS.map((group) => { + const randomIndex = Math.floor(Math.random() * group.characters.length); + return { + id: group.characters[randomIndex], + groupColor: group.color, + textColor: group.textColor, + }; + }); + }, []); - useEffect(() => { - const timer = setInterval(() => { - setActiveStep((prev) => (prev + 1) % selectedCharacters.length); - }, 1500); // Wait for jump animation to mostly complete + useEffect(() => { + const timer = setInterval(() => { + setActiveStep((prev) => (prev + 1) % selectedCharacters.length); + }, 1500); // Wait for jump animation to mostly complete - return () => clearInterval(timer); - }, [selectedCharacters.length]); + return () => clearInterval(timer); + }, [selectedCharacters.length]); - return ( - - {/* Retro-pop dot pattern background */} -
+ return ( + + {/* Retro-pop dot pattern background */} +
-
- {/* Characters Row */} -
- {selectedCharacters.map((char, index) => { - const isActive = index === activeStep; +
+ {/* Characters Row */} +
+ {selectedCharacters.map((char, index) => { + const isActive = index === activeStep; - return ( -
- - {char.id} - + return ( +
+ + {char.id} + - {/* Visual indicator / shadow under active char */} - -
- ); - })} -
+ {/* Visual indicator / shadow under active char */} + +
+ ); + })} +
- {/* Loading Text */} -
- - {message} - + {/* Loading Text */} +
+ - {[0, 1, 2].map((i) => ( - - ))} -
-
-
+ // --- ここからが丸っこく太くするための魔法の調整です --- + // 縁取りを文字の「外側」に広げます(中の文字が潰れません) + paintOrder: 'stroke fill', + // 白い縁取りを思い切って太くします(丸みがグッと増します) + WebkitTextStroke: '8px white', + // 縁取りが太くなった分、影も少し大きく・濃くしてポップな立体感を出します + textShadow: '5px 5px 0px rgba(0,0,0,0.25)', + }} + > + {message} + - {/* Group Name display (Subtle) */} -
- - {MBTI_GROUPS[activeStep].name} - -
- - ); + {/* Pulsing dots indicator */} +
+ {[0, 1, 2].map((i) => ( + + ))} +
+
+
+ + {/* Group Name display (Subtle) */} +
+ + {MBTI_GROUPS[activeStep].name} + +
+
+ ); } diff --git a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx index fbac092..8797633 100644 --- a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx +++ b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx @@ -117,8 +117,9 @@ export default function BaselineSurvey() { {Array.from({ length: totalQuestions }).map((_, i) => (
))}
From b12dcc99b6e739a282d8edcef5622546657df521 Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 11:57:13 +0900 Subject: [PATCH 3/7] =?UTF-8?q?style:=20=E6=96=87=E5=AD=97=E9=96=93?= =?UTF-8?q?=E9=9A=94=E3=82=92=E8=AA=BF=E6=95=B4=E3=81=97=E3=80=81=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=B3=E3=83=88=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/LoadingScreen.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx index ec184e2..0faa97a 100644 --- a/frontend/src/components/common/LoadingScreen.tsx +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -139,11 +139,9 @@ export default function LoadingScreen({ // 文字間隔(tracking)を wider から wide や normal に少し狭めると、文字同士がくっついてより「丸っこく」見えます className="text-4xl font-black tracking-wide sm:text-5xl" style={{ - fontFamily: - '"Noto Sans JP", "Hiragino Kaku Gothic ProN", "Meiryo", sans-serif', + fontFamily: '"Noto Sans JP", "Hiragino Kaku Gothic ProN", "Meiryo", sans-serif', color: '#222222', - // --- ここからが丸っこく太くするための魔法の調整です --- // 縁取りを文字の「外側」に広げます(中の文字が潰れません) paintOrder: 'stroke fill', // 白い縁取りを思い切って太くします(丸みがグッと増します) From d2acd0a722c1c6346fbc4b89c7eaef1586aa4315 Mon Sep 17 00:00:00 2001 From: tamtya Date: Sun, 22 Feb 2026 11:58:00 +0900 Subject: [PATCH 4/7] =?UTF-8?q?style:=20=E3=83=95=E3=82=A9=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=83=95=E3=82=A1=E3=83=9F=E3=83=AA=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E3=82=A4=E3=83=AB=E3=82=92=E6=95=B4=E5=BD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/LoadingScreen.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx index 0faa97a..67d7930 100644 --- a/frontend/src/components/common/LoadingScreen.tsx +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -139,7 +139,8 @@ export default function LoadingScreen({ // 文字間隔(tracking)を wider から wide や normal に少し狭めると、文字同士がくっついてより「丸っこく」見えます className="text-4xl font-black tracking-wide sm:text-5xl" style={{ - fontFamily: '"Noto Sans JP", "Hiragino Kaku Gothic ProN", "Meiryo", sans-serif', + fontFamily: + '"Noto Sans JP", "Hiragino Kaku Gothic ProN", "Meiryo", sans-serif', color: '#222222', // 縁取りを文字の「外側」に広げます(中の文字が潰れません) From 935375d6fc389653f6d0728352905c5ff93ff8cb Mon Sep 17 00:00:00 2001 From: Koseeee-27 Date: Sun, 22 Feb 2026 12:09:51 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E3=83=93=E3=83=AB=E3=83=89=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/diagnosis/components/BaselineSurvey.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx index 8797633..12e7825 100644 --- a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx +++ b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx @@ -11,7 +11,6 @@ import { type BaselineAnswers, type AnswerOption, } from '@/features/diagnosis/types'; -import Spinner from '@/components/common/Spinner'; import LoadingScreen from '@/components/common/LoadingScreen'; import { postRegister, submitGame } from '@/lib/api'; From 7e62f3319da6378616be985a7ce1cadb12cc1e9a Mon Sep 17 00:00:00 2001 From: Koseeee-27 Date: Sun, 22 Feb 2026 12:17:01 +0900 Subject: [PATCH 6/7] feat: replace completion message with loading screen for game transition --- .../games/terms/components/TermsGameFlow.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/games/terms/components/TermsGameFlow.tsx b/frontend/src/features/games/terms/components/TermsGameFlow.tsx index 4a029bc..b163dab 100644 --- a/frontend/src/features/games/terms/components/TermsGameFlow.tsx +++ b/frontend/src/features/games/terms/components/TermsGameFlow.tsx @@ -7,6 +7,7 @@ import type { Game1Data, ScrollEvent } from '@/features/games/types'; import { game1DataAtom } from '@/stores/games'; import PopupAd from './PopupAd'; import PopupTerms from './PopupTerms'; +import LoadingScreen from '@/components/common/LoadingScreen'; // TODO: バックエンド接続時にコメントアウトを解除する // import { submitGame } from '@/lib/api'; @@ -183,16 +184,7 @@ export default function TermsGameFlow() { ); if (isCompleted) { - return ( -
-
-

完了しました

-

- 次のゲームに移動します... -

-
-
- ); + return ; } return ( From e1c1561a0e98dbe30f631d828455268cf34cab5c Mon Sep 17 00:00:00 2001 From: Koseeee-27 Date: Sun, 22 Feb 2026 12:18:55 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=E3=83=AA=E3=83=B3=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/LoadingScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx index 67d7930..86fe71c 100644 --- a/frontend/src/components/common/LoadingScreen.tsx +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -2,7 +2,7 @@ 'use client'; import React, { useEffect, useState, useMemo } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; +import { motion } from 'framer-motion'; import Image from 'next/image'; interface LoadingScreenProps {