+ {/* ヘッダー: タイトル + プログレスバー */}
+
+ {/* タイトルエリア - メインカードに少し重なる */}
+
+
+ {/* プログレスバー: 5セグメント */}
+
+ {Array.from({ length: totalQuestions }).map((_, i) => (
+
+ ))}
+
-
-
-
-
+ {/* メインカード: 質問 + 4択 - タイトルと重なる */}
+
+
Q{currentIndex + 1}. {currentQuestion.label}
-
-
- {currentQuestion.options.map((option) => (
-
- ))}
+ {/* 2x2グリッドの選択肢 */}
+
+ {currentQuestion.options.map((option) => (
+
+ ))}
+
);
diff --git a/frontend/src/features/diagnosis/components/MbtiSelect.tsx b/frontend/src/features/diagnosis/components/MbtiSelect.tsx
index 9a479a4..ea3b42e 100644
--- a/frontend/src/features/diagnosis/components/MbtiSelect.tsx
+++ b/frontend/src/features/diagnosis/components/MbtiSelect.tsx
@@ -1,73 +1,287 @@
'use client';
-import { useState } from 'react';
+import Image from 'next/image';
+import { useState, useCallback } from 'react';
+import { flushSync } from 'react-dom';
+import { motion, AnimatePresence } from 'framer-motion';
import { useSetAtom } from 'jotai';
import { mbtiAtom, diagnosisStepAtom } from '@/stores/diagnosis';
-import { MBTI_TYPES, MBTI_GROUPS } from '@/constants/mbti';
+import { MBTI_TYPES, MBTI_GROUPS, type MbtiType } from '@/constants/mbti';
+
+// タブとカードで同じ色を使用(ジャンルごと)
+const GROUP_COLORS = [
+ 'bg-[#E5A1F4]', // 分析家 - light purple
+ 'bg-[#89F1C8]', // 外交官 - teal
+ 'bg-[#8EE3FA]', // 番人 - light blue
+ 'bg-[#FFD77B]', // 探検家 - light orange
+] as const;
+
+// 水玉模様用(ジャンルごとの色・HEX)
+const GROUP_COLOR_HEX = ['#E5A1F4', '#89F1C8', '#8EE3FA', '#FFD77B'] as const;
+
+// オーバーレイ用(ジャンルごとの少し濃い色)
+const GROUP_OVERLAY_COLORS = [
+ 'bg-[#c77dd9]', // 分析家 - darker magenta
+ 'bg-[#52c9a0]', // 外交官 - darker mint
+ 'bg-[#55c9e8]', // 番人 - darker cyan
+ 'bg-[#e6b84d]', // 探検家 - darker gold
+] as const;
+
+function getTypesByGroup(group: string): MbtiType[] {
+ return MBTI_TYPES.filter((t) => t.group === group);
+}
-// ここのUIは仮です。実際のUIはデザイナーさんと相談して作成してください。
export default function MbtiSelect() {
const setMbti = useSetAtom(mbtiAtom);
const setStep = useSetAtom(diagnosisStepAtom);
+ const [groupIndex, setGroupIndex] = useState(0);
const [selected, setSelected] = useState
(null);
+ const [transitionVia, setTransitionVia] = useState<
+ 'tab' | 'arrow-left' | 'arrow-right'
+ >('tab');
+
+ const currentGroup = MBTI_GROUPS[groupIndex];
+ const currentTypes = getTypesByGroup(currentGroup);
+ const selectedType = selected
+ ? MBTI_TYPES.find((t) => t.code === selected)
+ : null;
- const handleSubmit = () => {
+ const handleTabClick = useCallback(
+ (index: number) => {
+ if (index === groupIndex) return;
+ // タブクリック時は transitionVia を先に 'tab' に反映してから groupIndex を更新
+ // (矢印遷移後の初回タブクリックで正しいアニメーションが使われるようにする)
+ flushSync(() => setTransitionVia('tab'));
+ setGroupIndex(index);
+ setSelected(null);
+ },
+ [groupIndex]
+ );
+
+ const handleArrowPrev = useCallback(() => {
+ setTransitionVia('arrow-right'); // コンテンツは右から入る
+ setGroupIndex((i) => (i - 1 + MBTI_GROUPS.length) % MBTI_GROUPS.length);
+ setSelected(null);
+ }, []);
+
+ const handleArrowNext = useCallback(() => {
+ setTransitionVia('arrow-left'); // コンテンツは左から入る
+ setGroupIndex((i) => (i + 1) % MBTI_GROUPS.length);
+ setSelected(null);
+ }, []);
+
+ const handleConfirm = () => {
if (!selected) return;
setMbti(selected);
setStep('quiz');
};
+ const handleReselect = () => {
+ setSelected(null);
+ };
+
const handleSkip = () => {
setMbti(null);
setStep('quiz');
};
+ const getContentVariants = () => {
+ if (transitionVia === 'tab') {
+ return {
+ enter: { opacity: 0 },
+ center: { opacity: 1, x: 0 },
+ exit: { opacity: 0 },
+ };
+ }
+ if (transitionVia === 'arrow-left') {
+ return {
+ enter: { opacity: 0, x: 80 },
+ center: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: -80 },
+ };
+ }
+ return {
+ enter: { opacity: 0, x: -80 },
+ center: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: 80 },
+ };
+ };
+
+ const contentVariants = getContentVariants();
+
return (
-
-
あなたのMBTIタイプは?
-
わからない場合はスキップできます
-
-
- {MBTI_GROUPS.map((group) => (
-
-
{group}
-
- {MBTI_TYPES.filter((t) => t.group === group).map((type) => (
-
);
}
diff --git a/frontend/src/features/diagnosis/types/index.ts b/frontend/src/features/diagnosis/types/index.ts
index ee8a4ce..2e5341a 100644
--- a/frontend/src/features/diagnosis/types/index.ts
+++ b/frontend/src/features/diagnosis/types/index.ts
@@ -9,37 +9,66 @@ export type QuestionKey =
export type BaselineAnswers = Record
;
+export type QuestionOption = {
+ value: AnswerOption;
+ label: string;
+};
+
export type Question = {
key: QuestionKey;
label: string;
- options: AnswerOption[];
+ options: QuestionOption[];
};
-// TODO: 質問内容と選択肢が確定したら更新する
export const QUESTIONS: Question[] = [
{
key: 'q1_caution',
- label: '(質問内容未定:慎重さに関する質問)',
- options: ['A', 'B', 'C', 'D'],
+ label: '初めての街でランチ。お店選びは?',
+ options: [
+ { value: 'A', label: '口コミを熟読して予約する' },
+ { value: 'B', label: '歩きながらスマホで比較する' },
+ { value: 'C', label: '外観の雰囲気で決める' },
+ { value: 'D', label: '直感でパッと飛び込む' },
+ ],
},
{
key: 'q2_calmness',
- label: '(質問内容未定:冷静さに関する質問)',
- options: ['A', 'B', 'C', 'D'],
+ label: '感動的な映画を見終わった直後は?',
+ options: [
+ { value: 'A', label: 'ストーリーの構成を分析する' },
+ { value: 'B', label: '心の中で静かに余韻に浸る' },
+ { value: 'C', label: '「最高だった!」と熱く語る' },
+ { value: 'D', label: '感情移入して思い切り泣く' },
+ ],
},
{
key: 'q3_logic',
- label: '(質問内容未定:論理性に関する質問)',
- options: ['A', 'B', 'C', 'D'],
+ label: '新しい服を買うときの決め手は?',
+ options: [
+ { value: 'A', label: '着回しやすさや素材の良さ' },
+ { value: 'B', label: '今の流行や使い勝手' },
+ { value: 'C', label: 'デザインの第一印象' },
+ { value: 'D', label: '一目惚れで即決' },
+ ],
},
{
key: 'q4_cooperativeness',
- label: '(質問内容未定:協調性に関する質問)',
- options: ['A', 'B', 'C', 'D'],
+ label: '大人数での食事。自分の注文は?',
+ options: [
+ { value: 'A', label: '全体のバランスを見て合わせる' },
+ { value: 'B', label: '浮かない範囲で好きなものを頼む' },
+ { value: 'C', label: '周りを気にせず食べたいものを頼む' },
+ { value: 'D', label: '自分のイチオシをみんなにも勧める' },
+ ],
},
{
key: 'q5_positivity',
- label: '(質問内容未定:積極性に関する質問)',
- options: ['A', 'B', 'C', 'D'],
+ label: '初対面の人が多いパーティーでは?',
+ options: [
+ { value: 'A', label: '自分からどんどん話しかける' },
+ { value: 'B', label: '目が合った人に挨拶してみる' },
+ { value: 'C', label: '話しかけられるのを笑顔で待つ' },
+ { value: 'D', label: '聞き役に徹して相槌を打つ' },
+ ],
},
];
diff --git a/frontend/src/features/result/components/ResultReport.tsx b/frontend/src/features/result/components/ResultReport.tsx
index 46b83e8..0d925fe 100644
--- a/frontend/src/features/result/components/ResultReport.tsx
+++ b/frontend/src/features/result/components/ResultReport.tsx
@@ -53,7 +53,7 @@ export default function ResultReport({ data }: ResultReportProps) {
style={{
backgroundImage: `
radial-gradient(circle, rgba(255,255,255,0.8) 1.5px, transparent 4px),
- url('/bg-pattern.svg')
+ url('/images/bg-pattern.svg')
`,
backgroundSize: '16px 16px, cover',
backgroundPosition: '0 0, center',