diff --git a/frontend/public/bg-pattern.svg b/frontend/public/images/bg-pattern.svg similarity index 100% rename from frontend/public/bg-pattern.svg rename to frontend/public/images/bg-pattern.svg diff --git a/frontend/public/images/mbti/ENFJ.png b/frontend/public/images/mbti/ENFJ.png new file mode 100644 index 0000000..bf1a22e Binary files /dev/null and b/frontend/public/images/mbti/ENFJ.png differ diff --git a/frontend/public/images/mbti/ENFP.png b/frontend/public/images/mbti/ENFP.png new file mode 100644 index 0000000..d87732e Binary files /dev/null and b/frontend/public/images/mbti/ENFP.png differ diff --git a/frontend/public/images/mbti/ENTJ.png b/frontend/public/images/mbti/ENTJ.png new file mode 100644 index 0000000..9d26337 Binary files /dev/null and b/frontend/public/images/mbti/ENTJ.png differ diff --git a/frontend/public/images/mbti/ENTP.png b/frontend/public/images/mbti/ENTP.png new file mode 100644 index 0000000..d4c8f91 Binary files /dev/null and b/frontend/public/images/mbti/ENTP.png differ diff --git a/frontend/public/images/mbti/ESFJ.png b/frontend/public/images/mbti/ESFJ.png new file mode 100644 index 0000000..17c7fbe Binary files /dev/null and b/frontend/public/images/mbti/ESFJ.png differ diff --git a/frontend/public/images/mbti/ESFP.png b/frontend/public/images/mbti/ESFP.png new file mode 100644 index 0000000..cd1bdff Binary files /dev/null and b/frontend/public/images/mbti/ESFP.png differ diff --git a/frontend/public/images/mbti/ESTJ.png b/frontend/public/images/mbti/ESTJ.png new file mode 100644 index 0000000..9888ad8 Binary files /dev/null and b/frontend/public/images/mbti/ESTJ.png differ diff --git a/frontend/public/images/mbti/ESTP.png b/frontend/public/images/mbti/ESTP.png new file mode 100644 index 0000000..a8bb173 Binary files /dev/null and b/frontend/public/images/mbti/ESTP.png differ diff --git a/frontend/public/images/mbti/INFJ.png b/frontend/public/images/mbti/INFJ.png new file mode 100644 index 0000000..69c58ea Binary files /dev/null and b/frontend/public/images/mbti/INFJ.png differ diff --git a/frontend/public/images/mbti/INFP.png b/frontend/public/images/mbti/INFP.png new file mode 100644 index 0000000..dc1641c Binary files /dev/null and b/frontend/public/images/mbti/INFP.png differ diff --git a/frontend/public/images/mbti/INTJ.png b/frontend/public/images/mbti/INTJ.png new file mode 100644 index 0000000..80a86e6 Binary files /dev/null and b/frontend/public/images/mbti/INTJ.png differ diff --git a/frontend/public/images/mbti/INTP.png b/frontend/public/images/mbti/INTP.png new file mode 100644 index 0000000..78c62a5 Binary files /dev/null and b/frontend/public/images/mbti/INTP.png differ diff --git a/frontend/public/images/mbti/ISFJ.png b/frontend/public/images/mbti/ISFJ.png new file mode 100644 index 0000000..20b3654 Binary files /dev/null and b/frontend/public/images/mbti/ISFJ.png differ diff --git a/frontend/public/images/mbti/ISFP.png b/frontend/public/images/mbti/ISFP.png new file mode 100644 index 0000000..5de1a15 Binary files /dev/null and b/frontend/public/images/mbti/ISFP.png differ diff --git a/frontend/public/images/mbti/ISTJ.png b/frontend/public/images/mbti/ISTJ.png new file mode 100644 index 0000000..73da310 Binary files /dev/null and b/frontend/public/images/mbti/ISTJ.png differ diff --git a/frontend/public/images/mbti/ISTP.png b/frontend/public/images/mbti/ISTP.png new file mode 100644 index 0000000..070f846 Binary files /dev/null and b/frontend/public/images/mbti/ISTP.png differ diff --git a/frontend/src/app/diagnosis/page.tsx b/frontend/src/app/diagnosis/page.tsx index c60027a..c78449c 100644 --- a/frontend/src/app/diagnosis/page.tsx +++ b/frontend/src/app/diagnosis/page.tsx @@ -1,9 +1,20 @@ import DiagnosisFlow from '@/features/diagnosis/components/DiagnosisFlow'; +const diagnosisBackgroundStyle = { + backgroundColor: '#f0f380', + backgroundImage: 'radial-gradient(circle, #fff 2px, transparent 2px)', + backgroundSize: '20px 20px', +}; + export default function DiagnosisPage() { return ( -
- +
+
+ +
); } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 1069212..ae6061e 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,5 +1,26 @@ @import 'tailwindcss'; +/* 共通ページ背景: 黄色ベース + 白の水玉模様 */ +@utility bg-page-pattern { + background-color: #f0f380; + background-image: radial-gradient(circle, #fff 2px, transparent 2px); + background-size: 20px 20px; +} + +/* トップページ用: SVG背景 + 白の水玉オーバーレイ */ +@utility bg-top-pattern { + background-image: + radial-gradient(circle, rgba(255, 255, 255, 0.8) 1px, transparent 4px), + url('/images/bg-pattern.svg'); + background-size: + 16px 16px, + cover; + background-position: + 0 0, + center; + background-repeat: repeat, no-repeat; +} + @keyframes fadeInUp { from { opacity: 0; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 7650a2b..daba27e 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -76,18 +76,7 @@ export default function TopPage() { }; return ( -
+
-
-

簡易性格診断

- - {currentIndex + 1} / {totalQuestions} - +
+ {/* ヘッダー: タイトル + プログレスバー */} +
+ {/* タイトルエリア - メインカードに少し重なる */} +
+
+

質問コーナー

+
+
+ + {/* プログレスバー: 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) => ( - +

+ あなたのMBTIを選んでね!! +

+ + {/* キャラ表示エリア + 左右矢印 - グリッドでボタンとカードを分離 */} +
+ - ))} +
+ {currentTypes.map((type) => ( + setSelected(type.code)} + 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, + y: -16, + transition: { duration: 0.2 }, + }} + whileTap={{ scale: 1.02 }} + style={{ boxShadow: 'none' }} + > +
+ {type.name} +
+

+ {type.code} / {type.name} +

+
+
+
+ ))} +
+ +
+ +
- ))} +
-
- - -
+
g === selectedType.group) + ) + ] + }`} + > +

+ あなたのMBTIは +

+
+ {selectedType.name} +
+

+ {selectedType.code} / {selectedType.name} +

+
+ + +
+
+
+ )}
); } 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',