{\rtf1\ansi\ansicpg949\cocoartf2706 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} \paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0 \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 \f0\fs24 \cf0 import React, \{ useState, useEffect, useCallback \} from 'react';\ import \{ Play, RotateCcw, Pause, CheckCircle2, FlaskConical, Mountain, Settings2, Edit3, Shuffle, ListOrdered, CheckSquare, ArrowRight, AlertCircle, X, Check, XCircle, RefreshCw, Hand, Map, Activity, Split, MousePointerClick, Home, ArrowLeft, Atom \} from 'lucide-react';\ \ // =================================================================================================\ // [APP 1] \uc0\u50896 \u49548 \u44592 \u54840 \u47560 \u49828 \u53552 (ElementFlashcards)\ // =================================================================================================\ const ElementFlashcards = (\{ onGoHome \}) => \{\ const elements1to20 = [\ \{ num: 1, sym: 'H', name: '\uc0\u49688 \u49548 ', p: 1, g: 1 \}, \{ num: 2, sym: 'He', name: '\u54764 \u47464 ', p: 1, g: 18 \},\ \{ num: 3, sym: 'Li', name: '\uc0\u47532 \u53932 ', p: 2, g: 1 \}, \{ num: 4, sym: 'Be', name: '\u48288 \u47540 \u47464 ', p: 2, g: 2 \},\ \{ num: 5, sym: 'B', name: '\uc0\u48533 \u49548 ', p: 2, g: 13 \}, \{ num: 6, sym: 'C', name: '\u53444 \u49548 ', p: 2, g: 14 \},\ \{ num: 7, sym: 'N', name: '\uc0\u51656 \u49548 ', p: 2, g: 15 \}, \{ num: 8, sym: 'O', name: '\u49328 \u49548 ', p: 2, g: 16 \},\ \{ num: 9, sym: 'F', name: '\uc0\u54540 \u47336 \u50724 \u47536 ', p: 2, g: 17 \}, \{ num: 10, sym: 'Ne', name: '\u45348 \u50728 ', p: 2, g: 18 \},\ \{ num: 11, sym: 'Na', name: '\uc0\u45208 \u53944 \u47464 ', p: 3, g: 1 \}, \{ num: 12, sym: 'Mg', name: '\u47560 \u44536 \u45348 \u49816 ', p: 3, g: 2 \},\ \{ num: 13, sym: 'Al', name: '\uc0\u50508 \u47336 \u48120 \u45700 ', p: 3, g: 13 \}, \{ num: 14, sym: 'Si', name: '\u44508 \u49548 ', p: 3, g: 14 \},\ \{ num: 15, sym: 'P', name: '\uc0\u51064 ', p: 3, g: 15 \}, \{ num: 16, sym: 'S', name: '\u54889 ', p: 3, g: 16 \},\ \{ num: 17, sym: 'Cl', name: '\uc0\u50684 \u49548 ', p: 3, g: 17 \}, \{ num: 18, sym: 'Ar', name: '\u50500 \u47476 \u44260 ', p: 3, g: 18 \},\ \{ num: 19, sym: 'K', name: '\uc0\u52860 \u47464 ', p: 4, g: 1 \}, \{ num: 20, sym: 'Ca', name: '\u52860 \u49816 ', p: 4, g: 2 \},\ ];\ const extraElements = [\ \{ sym: 'Mn', name: '\uc0\u47581 \u44032 \u45768 \u51592 ' \}, \{ sym: 'Pb', name: '\u45225 ' \}, \{ sym: 'Ag', name: '\u51008 ' \},\ \{ sym: 'Au', name: '\uc0\u44552 ' \}, \{ sym: 'Pt', name: '\u48177 \u44552 ' \}, \{ sym: 'Cu', name: '\u44396 \u47532 ' \},\ \{ sym: 'Fe', name: '\uc0\u52384 ' \}, \{ sym: 'Zn', name: '\u50500 \u50672 ' \}, \{ sym: 'Hg', name: '\u49688 \u51008 ' \},\ \{ sym: 'Ba', name: '\uc0\u48148 \u47464 ' \}, \{ sym: 'Sr', name: '\u49828 \u53944 \u47200 \u53932 ' \}, \{ sym: 'Br', name: '\u48652 \u47196 \u48124 ' \},\ \{ sym: 'I', name: '\uc0\u50500 \u51060 \u50724 \u46360 ' \},\ ];\ const allElements = [...elements1to20, ...extraElements];\ \ const [gameState, setGameState] = useState('start');\ const [cards, setCards] = useState([]);\ const [currentIndex, setCurrentIndex] = useState(0);\ const [isFlipped, setIsFlipped] = useState(false);\ const [isPaused, setIsPaused] = useState(false);\ const [isTransitioning, setIsTransitioning] = useState(false);\ const [targetCount, setTargetCount] = useState(20);\ const [isCustomMode, setIsCustomMode] = useState(false);\ const [customInput, setCustomInput] = useState('');\ const [orderMode, setOrderMode] = useState('mix');\ const [selectedCategories, setSelectedCategories] = useState(['A', 'B', 'C']);\ const [isReviewMode, setIsReviewMode] = useState(false);\ const [wrongCards, setWrongCards] = useState([]);\ const [currentMarkedWrong, setCurrentMarkedWrong] = useState(false);\ \ // Helper\ const generatePoolByType = useCallback((type) => \{\ let pool = [];\ const id = () => Math.random().toString(36).substr(2, 9);\ switch (type) \{\ case 1: elements1to20.forEach(e => pool.push(\{ id: id(), type: 1, q: `$\{e.num\}\uc0\u48264 `, a: `$\{e.sym\}($\{e.name\})`, color: 'bg-blue-100 text-blue-800' \})); break;\ case 2: allElements.forEach(e => pool.push(\{ id: id(), type: 2, q: `$\{e.sym\} \uc0\u51060 \u47492 \u51008 ?`, a: e.name, color: 'bg-green-100 text-green-800' \})); break;\ case 3: allElements.forEach(e => pool.push(\{ id: id(), type: 3, q: `$\{e.name\} \uc0\u44592 \u54840 \u45716 ?`, a: e.sym, color: 'bg-yellow-100 text-yellow-800' \})); break;\ case 4: elements1to20.forEach(e => pool.push(\{ id: id(), type: 4, q: `$\{e.p\}\uc0\u51452 \u44592 $\{e.g\}\u51313 `, a: `$\{e.sym\}($\{e.name\})`, color: 'bg-purple-100 text-purple-800' \})); break;\ case 5: elements1to20.forEach(e => pool.push(\{ id: id(), type: 5, q: `$\{e.sym\} \uc0\u48264 \u54840 \u45716 ?`, a: `$\{e.num\}\u48264 `, color: 'bg-red-100 text-red-800' \})); break;\ case 6: elements1to20.forEach(e => pool.push(\{ id: id(), type: 6, q: `$\{e.sym\} \uc0\u51452 \u44592 /\u51313 `, a: `$\{e.p\}\u51452 \u44592 $\{e.g\}\u51313 `, color: 'bg-indigo-100 text-indigo-800' \})); break;\ default: break;\ \}\ return pool;\ \}, []);\ \ const generateQuestions = useCallback((count, categories, mode) => \{\ const categoryMap = \{ 'A': [1, 5], 'B': [2, 3], 'C': [4, 6] \};\ const numCats = categories.length;\ if (numCats === 0) return [];\ const baseCount = Math.floor(count / numCats);\ let remainder = count % numCats;\ let final = [];\ \ categories.forEach((catCode) => \{\ let catCount = baseCount + (remainder > 0 ? 1 : 0);\ if (remainder > 0) remainder--;\ if (catCount === 0) return;\ \ const types = categoryMap[catCode];\ let catQuestions = [];\ const typeBaseCount = Math.floor(catCount / types.length);\ let typeRemainder = catCount % types.length;\ \ types.forEach(typeId => \{\ let typeCount = typeBaseCount + (typeRemainder > 0 ? 1 : 0);\ if (typeRemainder > 0) typeRemainder--;\ const pool = generatePoolByType(typeId).sort(() => 0.5 - Math.random());\ let selected = [];\ while (selected.length < typeCount) selected = selected.concat(pool);\ catQuestions = catQuestions.concat(selected.slice(0, typeCount));\ \});\ catQuestions.sort(() => 0.5 - Math.random());\ final = final.concat(catQuestions);\ \});\ \ if (mode === 'mix') final.sort(() => 0.5 - Math.random());\ return final;\ \}, [generatePoolByType]);\ \ const handleStart = () => \{\ if (selectedCategories.length === 0) return alert('\uc0\u50976 \u54805 \u51012 \u49440 \u53469 \u54644 \u51452 \u49464 \u50836 .');\ let cnt = targetCount;\ if (isCustomMode) \{\ cnt = parseInt(customInput, 10);\ if (isNaN(cnt) || cnt < 1 || cnt > 100) return alert('1~100 \uc0\u49324 \u51060 \u49707 \u51088 \u51077 \u47141 ');\ \}\ const newCards = generateQuestions(cnt, selectedCategories, orderMode);\ setCards(newCards);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setCurrentMarkedWrong(false);\ setGameState('playing');\ \};\ \ const handleRetry = () => \{\ const pool = [...wrongCards];\ const target = Math.ceil(pool.length * 1.5);\ let retry = [...pool];\ while (retry.length < target) retry.push(\{ ...pool[Math.floor(Math.random() * pool.length)], id: Math.random().toString() \});\ retry.sort(() => 0.5 - Math.random());\ setCards(retry);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setCurrentMarkedWrong(false);\ setGameState('playing');\ \};\ \ const nextCard = () => \{\ if (currentIndex < cards.length - 1) \{\ setIsTransitioning(true);\ setIsFlipped(false);\ setTimeout(() => \{\ setCurrentIndex(p => p + 1);\ setCurrentMarkedWrong(false);\ setIsTransitioning(false);\ \}, 500);\ \} else \{\ setGameState('finished');\ \}\ \};\ \ const handleCardClick = () => \{\ if (isPaused || isTransitioning) return;\ if (!isFlipped) setIsFlipped(true);\ else nextCard();\ \};\ \ const toggleWrong = (e) => \{\ e.stopPropagation();\ const card = cards[currentIndex];\ if (currentMarkedWrong) \{\ setWrongCards(p => p.filter(c => c.id !== card.id));\ setCurrentMarkedWrong(false);\ \} else \{\ setWrongCards(p => [...p, card]);\ setCurrentMarkedWrong(true);\ \}\ \};\ \ if (gameState === 'start') \{\ const presetCounts = [15, 20, 25, 30, 40, 50];\ const cats = [\ \{ id: 'A', label: '\uc0\u48264 \u54840 \u54984 \u47144 ', sub: '\u50896 \u51088 \u48264 \u54840 \u8596 \u50896 \u49548 ', color: 'bg-blue-50 border-blue-200 text-blue-700' \},\ \{ id: 'B', label: '\uc0\u44592 \u54840 \u54984 \u47144 ', sub: '\u50896 \u49548 \u47749 \u8596 \u44592 \u54840 ', color: 'bg-green-50 border-green-200 text-green-700' \},\ \{ id: 'C', label: '\uc0\u51452 \u44592 /\u51313 \u54984 \u47144 ', sub: '\u51452 \u44592 /\u51313 \u8596 \u50896 \u49548 ', color: 'bg-purple-50 border-purple-200 text-purple-700' \},\ ];\ return (\
\
\
\ \
\
\
\

\uc0\u54868 \u54617 \u50896 \u49548 \u47560 \u49828 \u53552

\ \
\
setIsReviewMode(!isReviewMode)\}>\
\uc0\u50724 \u45813 \u45432 \u53944 \u47784 \u46300
\
\
\
\
\uc0\u47928 \u54637 \u49688
\
\ \{presetCounts.map(c => )\}\
\ \ \{isCustomMode && setCustomInput(e.target.value)\} className="mt-2 w-full px-3 py-2 text-sm rounded-lg border-2 border-blue-200 text-center font-bold" placeholder="1~100"/>\}\
\
\
\uc0\u49692 \u49436
\
\ \ \
\
\
\
\uc0\u50976 \u54805
\
\ \{cats.map(c => (\ \ ))\}\
\
\
\ \
\
\ );\ \}\ \ if (gameState === 'finished') \{\ const wrongCnt = wrongCards.length;\ const accuracy = Math.round(((cards.length - wrongCnt) / cards.length) * 100);\ return (\
\
\
\ \{isReviewMode ?
= 80 ? 'text-green-500' : 'text-red-500'\}`\}>\{accuracy\}%
: \}\
\

\uc0\u54617 \u49845 \u50756 \u47308 !

\ \{isReviewMode && wrongCnt > 0 && (\
\
\uc0\u53952 \u47536 \u47928 \u51228 (\{wrongCnt\})
\
\{wrongCards.map((c, i) =>
Q. \{c.q\}
A. \{c.a\}
)\}
\
\ )\}\
\ \{isReviewMode && wrongCnt > 0 && \}\ \ \
\
\
\ );\ \}\ \ const currentCard = cards[currentIndex];\ return (\
\
\
setGameState('start')\}>\uc0\u50896 \u49548 \u47560 \u49828 \u53552
\
\ \{isReviewMode &&
\uc0\u50724 \u45813 \u45432 \u53944
\}\ \{currentIndex + 1\}/\{cards.length\}\ \ \
\
\
\ \
\ \{isPaused &&

\uc0\u51068 \u49884 \u51221 \u51648

\}\ \
\
\ \{/* Front */\}\
\
Question
\

\uc0\u52852 \u46300 \u47484 \u53552 \u52824 \u54616 \u50668 \u51221 \u45813 \u54869 \u51064

\

\{currentCard.q\}

\
\ \{/* Back */\}\
\
Answer
\

\{currentCard.a\}

\
\uc0\u53552 \u52824 \u54616 \u50668 \u45796 \u51020 \u47928 \u51228
\ \{isReviewMode && (\
\ \
\ )\}\
\
\
\
\ \
\ );\ \};\ \ // =================================================================================================\ // [APP 2] \uc0\u54032 \u51032 \u44221 \u44228 \u47560 \u49828 \u53552 (PlateBoundaryFlashcards)\ // =================================================================================================\ const PlateBoundaryFlashcards = (\{ onGoHome \}) => \{\ const boundaryOptions = ['\uc0\u45824 \u47449 -\u45824 \u47449 \u49688 \u47156 ', '\u45824 \u47449 -\u54644 \u50577 \u49688 \u47156 ', '\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 ', '\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 ', '\u45824 \u47449 -\u45824 \u47449 \u48156 \u49328 ', '\u48372 \u51316 \u54805 \u44221 \u44228 '];\ const boundaryData = [\ \{ q: '\uc0\u55176 \u47568 \u46972 \u50556 \u49328 \u47589 ', a: '\u45824 \u47449 -\u45824 \u47449 \u49688 \u47156 \u54805 (\u52649 \u46028 \u45824 )', correct: ['\u45824 \u47449 -\u45824 \u47449 \u49688 \u47156 '] \},\ \{ q: '\uc0\u50504 \u45936 \u49828 \u49328 \u47589 ', a: '\u45824 \u47449 -\u54644 \u50577 \u49688 \u47156 \u54805 (\u49453 \u51077 \u45824 )', correct: ['\u45824 \u47449 -\u54644 \u50577 \u49688 \u47156 '] \},\ \{ q: '\uc0\u47560 \u47532 \u50500 \u45208 \u54644 \u44396 ', a: '\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 \u54805 (\u49453 \u51077 \u45824 )', correct: ['\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 '] \},\ \{ q: '\uc0\u51068 \u48376 \u54644 \u44396 ', a: '\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 \u54805 (\u49453 \u51077 \u45824 )', correct: ['\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 '] \},\ \{ q: '\uc0\u54168 \u47336 -\u52832 \u47112 \u54644 \u44396 ', a: '\u45824 \u47449 -\u54644 \u50577 \u49688 \u47156 \u54805 (\u49453 \u51077 \u45824 )', correct: ['\u45824 \u47449 -\u54644 \u50577 \u49688 \u47156 '] \},\ \{ q: '\uc0\u49328 \u50504 \u46300 \u47112 \u50500 \u49828 \u45800 \u52789 ', a: '\u48372 \u51316 \u54805 \u44221 \u44228 (\u48320 \u54872 \u45800 \u52789 )', correct: ['\u48372 \u51316 \u54805 \u44221 \u44228 '] \},\ \{ q: '\uc0\u45824 \u49436 \u50577 \u51473 \u50521 \u54644 \u47161 ', a: '\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 \u54805 ', correct: ['\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 '] \},\ \{ q: '\uc0\u46041 \u53468 \u54217 \u50577 \u54644 \u47161 ', a: '\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 \u54805 ', correct: ['\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 '] \},\ \{ q: '\uc0\u46041 \u50500 \u54532 \u47532 \u52852 \u50676 \u44257 \u45824 ', a: '\u45824 \u47449 -\u45824 \u47449 \u48156 \u49328 \u54805 ', correct: ['\u45824 \u47449 -\u45824 \u47449 \u48156 \u49328 '] \},\ \{ q: '\uc0\u50500 \u51060 \u49836 \u46976 \u46300 \u50676 \u44257 \u45824 ', a: '\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 \u54805 (\u50977 \u51648 \u51032 \u54644 \u47161 )', correct: ['\u54644 \u50577 -\u54644 \u50577 \u48156 \u49328 '] \},\ \{ q: '\uc0\u50508 \u47448 \u49328 \u50676 \u46020 ', a: '\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 \u54805 (\u49453 \u51077 \u45824 )', correct: ['\u54644 \u50577 -\u54644 \u50577 \u49688 \u47156 '] \},\ ];\ const commonLandformOptions = ['\uc0\u54644 \u47161 ', '\u50676 \u44257 ', '\u50676 \u44257 \u45824 ', '\u54644 \u44396 ', '\u54840 \u49345 \u50676 \u46020 ', '\u49845 \u44257 \u49328 \u47589 ', '\u48320 \u54872 \u45800 \u52789 '];\ const landformData = [\ \{ q: '\uc0\u54644 \u50577 \u54032 -\u54644 \u50577 \u54032 \u48156 \u49328 \u54805 \u44221 \u44228 ', a: '\u54644 \u47161 , \u50676 \u44257 ', correct: ['\u54644 \u47161 ', '\u50676 \u44257 '] \},\ \{ q: '\uc0\u45824 \u47449 \u54032 -\u45824 \u47449 \u54032 \u48156 \u49328 \u54805 \u44221 \u44228 ', a: '\u50676 \u44257 \u45824 ', correct: ['\u50676 \u44257 \u45824 '] \},\ \{ q: '\uc0\u54644 \u50577 \u54032 -\u45824 \u47449 \u54032 \u49688 \u47156 \u54805 \u44221 \u44228 ', a: '\u54644 \u44396 , \u49845 \u44257 \u49328 \u47589 , \u54840 \u49345 \u50676 \u46020 (\u9651 \u54617 \u44368 \u49688 \u50629 \u52280 \u44256 )', correct: ['\u54644 \u44396 ', '\u49845 \u44257 \u49328 \u47589 ', '\u54840 \u49345 \u50676 \u46020 '] \},\ \{ q: '\uc0\u54644 \u50577 \u54032 -\u54644 \u50577 \u54032 \u49688 \u47156 \u54805 \u44221 \u44228 ', a: '\u54644 \u44396 , \u54840 \u49345 \u50676 \u46020 ', correct: ['\u54644 \u44396 ', '\u54840 \u49345 \u50676 \u46020 '] \},\ \{ q: '\uc0\u45824 \u47449 \u54032 -\u45824 \u47449 \u54032 \u49688 \u47156 \u54805 \u44221 \u44228 ', a: '\u49845 \u44257 \u49328 \u47589 ', correct: ['\u49845 \u44257 \u49328 \u47589 '] \},\ \{ q: '\uc0\u48372 \u51316 \u54805 \u44221 \u44228 (\u54032 \u51032 \u50612 \u44555 \u45224 )', a: '\u48320 \u54872 \u45800 \u52789 ', correct: ['\u48320 \u54872 \u45800 \u52789 '] \},\ ];\ const nameData = [\ \{ q: '\uc0\u46041 \u50500 \u54532 \u47532 \u52852 ', a: '\u50676 \u44257 \u45824 ', correct: ['\u50676 \u44257 \u45824 '] \}, \{ q: '\u47560 \u47532 \u50500 \u45208 ', a: '\u54644 \u44396 ', correct: ['\u54644 \u44396 '] \},\ \{ q: '\uc0\u46041 \u53468 \u54217 \u50577 ', a: '\u54644 \u47161 ', correct: ['\u54644 \u47161 '] \}, \{ q: '\u45824 \u49436 \u50577 \u51473 \u50521 ', a: '\u54644 \u47161 ', correct: ['\u54644 \u47161 '] \},\ \{ q: '\uc0\u49328 \u50504 \u46300 \u47112 \u50500 \u49828 ', a: '\u48320 \u54872 \u45800 \u52789 ', correct: ['\u48320 \u54872 \u45800 \u52789 '] \}, \{ q: '\u55176 \u47568 \u46972 \u50556 ', a: '\u49845 \u44257 \u49328 \u47589 ', correct: ['\u49845 \u44257 \u49328 \u47589 '] \},\ \{ q: '\uc0\u50504 \u45936 \u49828 ', a: '\u49845 \u44257 \u49328 \u47589 ', correct: ['\u49845 \u44257 \u49328 \u47589 '] \}, \{ q: '\u51068 \u48376 ', a: '\u54644 \u44396 ', correct: ['\u54644 \u44396 '] \},\ ];\ const featureOptionsOX = ['O', 'X'];\ const featureOptionsDepth = ['\uc0\u52380 \u48156 ', '\u52380 \u48156 ~\u51473 \u48156 ', '\u52380 \u48156 ~\u49900 \u48156 '];\ const featureData = [\ \{ q: '\uc0\u54644 \u47161 \u50640 \u49436 \u51032 \u54868 \u49328 \u54876 \u46041 \u50976 \u47924 ?', a: 'O (\u51080 \u51020 )', correct: ['O'], options: featureOptionsOX \},\ \{ q: '\uc0\u48320 \u54872 \u45800 \u52789 \u50640 \u49436 \u51032 \u54868 \u49328 \u54876 \u46041 \u50976 \u47924 ?', a: 'X (\u50630 \u51020 )', correct: ['X'], options: featureOptionsOX \},\ \{ q: '\uc0\u55176 \u47568 \u46972 \u50556 \u49328 \u47589 (\u52649 \u46028 \u45824 )\u50640 \u49436 \u51032 \u54868 \u49328 \u54876 \u46041 \u50976 \u47924 ?', a: 'X (\u50630 \u51020 )', correct: ['X'], options: featureOptionsOX \},\ \{ q: '\uc0\u50504 \u45936 \u49828 \u49328 \u47589 (\u49453 \u51077 \u45824 )\u50640 \u49436 \u51032 \u54868 \u49328 \u54876 \u46041 \u50976 \u47924 ?', a: 'O (\u51080 \u51020 )', correct: ['O'], options: featureOptionsOX \},\ \{ q: '\uc0\u54644 \u47161 \u50640 \u49436 \u48156 \u49373 \u54616 \u45716 \u51648 \u51652 \u51032 \u44618 \u51060 \u45716 ?', a: '\u52380 \u48156 \u51648 \u51652 ', correct: ['\u52380 \u48156 '], options: featureOptionsDepth \},\ \{ q: '\uc0\u48320 \u54872 \u45800 \u52789 \u50640 \u49436 \u48156 \u49373 \u54616 \u45716 \u51648 \u51652 \u51032 \u44618 \u51060 \u45716 ?', a: '\u52380 \u48156 \u51648 \u51652 ', correct: ['\u52380 \u48156 '], options: featureOptionsDepth \},\ \{ q: '\uc0\u54644 \u44396 (\u49453 \u51077 \u45824 )\u50640 \u49436 \u48156 \u49373 \u54616 \u45716 \u51648 \u51652 \u51032 \u44618 \u51060 \u45716 ?', a: '\u52380 \u48156 ~\u49900 \u48156 \u51648 \u51652 (\u47784 \u46160 \u48156 \u49373 )', correct: ['\u52380 \u48156 ~\u49900 \u48156 '], options: featureOptionsDepth \},\ \{ q: '\uc0\u55176 \u47568 \u46972 \u50556 \u49328 \u47589 (\u52649 \u46028 \u45824 )\u50640 \u49436 \u48156 \u49373 \u54616 \u45716 \u51648 \u51652 \u51032 \u44618 \u51060 \u45716 ?', a: '\u52380 \u48156 ~\u51473 \u48156 \u51648 \u51652 ', correct: ['\u52380 \u48156 ~\u51473 \u48156 '], options: featureOptionsDepth \},\ ];\ const convergenceOptions = ['\uc0\u49453 \u51077 \u54805 ', '\u52649 \u46028 \u54805 '];\ const convergenceData = [\ \{ q: '\uc0\u55176 \u47568 \u46972 \u50556 \u49328 \u47589 \u51008 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u52649 \u46028 \u54805 (\u45824 \u47449 -\u45824 \u47449 )', correct: ['\u52649 \u46028 \u54805 '] \},\ \{ q: '\uc0\u50504 \u45936 \u49828 \u49328 \u47589 \u51008 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u49453 \u51077 \u54805 (\u45824 \u47449 -\u54644 \u50577 )', correct: ['\u49453 \u51077 \u54805 '] \},\ \{ q: '\uc0\u51068 \u48376 \u54644 \u44396 \u45716 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u49453 \u51077 \u54805 (\u54644 \u50577 -\u54644 \u50577 )', correct: ['\u49453 \u51077 \u54805 '] \},\ \{ q: '\uc0\u47560 \u47532 \u50500 \u45208 \u54644 \u44396 \u45716 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u49453 \u51077 \u54805 (\u54644 \u50577 -\u54644 \u50577 )', correct: ['\u49453 \u51077 \u54805 '] \},\ \{ q: '\uc0\u50508 \u54532 \u49828 \u49328 \u47589 \u51008 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u52649 \u46028 \u54805 (\u45824 \u47449 -\u45824 \u47449 )', correct: ['\u52649 \u46028 \u54805 '] \},\ \{ q: '\uc0\u54168 \u47336 -\u52832 \u47112 \u54644 \u44396 \u45716 \u49453 \u51077 \u54805 \u51064 \u44032 \u52649 \u46028 \u54805 \u51064 \u44032 ?', a: '\u49453 \u51077 \u54805 (\u45824 \u47449 -\u54644 \u50577 )', correct: ['\u49453 \u51077 \u54805 '] \},\ ];\ \ const getRandomOptions = (correctAnswers, allOptions, maxCount = 4) => \{\ let options = [...correctAnswers];\ const distractors = allOptions.filter(opt => !correctAnswers.includes(opt));\ distractors.sort(() => 0.5 - Math.random());\ const needed = maxCount - options.length;\ if (needed > 0) options = options.concat(distractors.slice(0, needed));\ return options.sort(() => 0.5 - Math.random());\ \};\ \ const [gameState, setGameState] = useState('start');\ const [cards, setCards] = useState([]);\ const [currentIndex, setCurrentIndex] = useState(0);\ const [isFlipped, setIsFlipped] = useState(false);\ const [isPaused, setIsPaused] = useState(false);\ const [isTransitioning, setIsTransitioning] = useState(false);\ const [selectedOptions, setSelectedOptions] = useState([]);\ const [quizResult, setQuizResult] = useState(null);\ const [targetCount, setTargetCount] = useState(20);\ const [isCustomMode, setIsCustomMode] = useState(false);\ const [customInput, setCustomInput] = useState('');\ const [selectedCategories, setSelectedCategories] = useState(['A', 'B', 'C', 'D', 'E']);\ const [isReviewMode, setIsReviewMode] = useState(false);\ const [wrongCards, setWrongCards] = useState([]);\ \ const generateQuestions = useCallback((count, categories) => \{\ let pool = [];\ const id = () => Math.random().toString(36).substr(2, 9);\ if (categories.includes('A')) boundaryData.forEach(i => pool.push(\{ id: id(), type: '\uc0\u44221 \u44228 \u52286 \u44592 ', q: i.q, a: i.a, correct: i.correct, options: getRandomOptions(i.correct, boundaryOptions, 4), color: 'bg-indigo-100 text-indigo-800', icon: \}));\ if (categories.includes('B')) landformData.forEach(i => pool.push(\{ id: id(), type: '\uc0\u51648 \u54805 \u52286 \u44592 ', q: i.q, a: i.a, correct: i.correct, options: getRandomOptions(i.correct, commonLandformOptions, 4), color: 'bg-emerald-100 text-emerald-800', icon: \}));\ if (categories.includes('C')) nameData.forEach(i => pool.push(\{ id: id(), type: '\uc0\u51060 \u47492 \u50756 \u49457 ', q: i.q, a: i.a, correct: i.correct, options: getRandomOptions(i.correct, commonLandformOptions, 3), color: 'bg-amber-100 text-amber-800', icon: \}));\ if (categories.includes('D')) featureData.forEach(i => pool.push(\{ id: id(), type: '\uc0\u51648 \u44033 \u48320 \u46041 ', q: i.q, a: i.a, correct: i.correct, options: i.options, color: 'bg-rose-100 text-rose-800', icon: \}));\ if (categories.includes('E')) convergenceData.forEach(i => pool.push(\{ id: id(), type: '\uc0\u49453 \u51077 vs \u52649 \u46028 ', q: i.q, a: i.a, correct: i.correct, options: convergenceOptions, color: 'bg-cyan-100 text-cyan-800', icon: \}));\ if (pool.length === 0) return [];\ const shuffled = pool.sort(() => 0.5 - Math.random());\ let selected = [];\ while (selected.length < count) \{\ if (shuffled.length >= count) \{ selected = shuffled.slice(0, count); break; \}\ selected = selected.concat(shuffled);\ \}\ return selected.slice(0, count);\ \}, []);\ \ const handleStart = () => \{\ if (selectedCategories.length === 0) return alert('\uc0\u50976 \u54805 \u49440 \u53469 \u54596 \u50836 ');\ let cnt = targetCount;\ if (isCustomMode) cnt = parseInt(customInput, 10);\ const newCards = generateQuestions(cnt, selectedCategories);\ setCards(newCards);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setSelectedOptions([]);\ setQuizResult(null);\ setGameState('playing');\ \};\ \ const handleRetry = () => \{\ const pool = [...wrongCards];\ const target = Math.ceil(pool.length * 1.5);\ let retry = [...pool];\ while (retry.length < target) retry.push(\{ ...pool[Math.floor(Math.random() * pool.length)], id: Math.random().toString() \});\ retry.sort(() => 0.5 - Math.random());\ setCards(retry);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setSelectedOptions([]);\ setQuizResult(null);\ setGameState('playing');\ \};\ \ const nextCard = () => \{\ if (currentIndex < cards.length - 1) \{\ setIsTransitioning(true);\ setIsFlipped(false);\ setTimeout(() => \{\ setCurrentIndex(p => p + 1);\ setSelectedOptions([]);\ setQuizResult(null);\ setIsTransitioning(false);\ \}, 300);\ \} else \{\ setGameState('finished');\ \}\ \};\ \ const toggleOption = (opt) => \{\ if (isFlipped) return;\ setSelectedOptions(prev => \{\ const card = cards[currentIndex];\ if (card.type === '\uc0\u51648 \u54805 \u52286 \u44592 ') return prev.includes(opt) ? prev.filter(o => o !== opt) : [...prev, opt];\ else return prev.includes(opt) ? [] : [opt];\ \});\ \};\ \ const checkAnswer = () => \{\ const card = cards[currentIndex];\ const isCorrect = selectedOptions.length === card.correct.length && selectedOptions.every(o => card.correct.includes(o));\ setQuizResult(isCorrect ? 'correct' : 'wrong');\ setIsFlipped(true);\ if (isReviewMode && !isCorrect) setWrongCards(p => \{ if (p.some(c => c.id === card.id)) return p; return [...p, card]; \});\ \};\ \ const formatAnswer = (text) => \{\ const parts = text.split('(');\ if (parts.length > 1) \{\ return <>\{parts[0].trim()\}(\{parts.slice(1).join('(')\};\ \}\ return text;\ \};\ \ if (gameState === 'start') \{\ return (\
\
\
\ \
\
\
\

\uc0\u54032 \u51032 \u44221 \u44228 \u47560 \u49828 \u53552

\
\
setIsReviewMode(!isReviewMode)\}>\
\uc0\u50724 \u45813 \u45432 \u53944 \u47784 \u46300
\
\
\
\
\uc0\u47928 \u54637 \u49688
\
\ \{[10, 15, 20, 25, 30, 40].map(c => )\}\
\ \ \{isCustomMode && setCustomInput(e.target.value)\} className="mt-2 w-full px-3 py-2 text-sm rounded-lg border-2 border-teal-200 text-center font-bold" placeholder="1~100"/>\}\
\
\
\uc0\u50976 \u54805 (\u47004 \u45924 )
\
\ \{[\{ id: 'A', label: '\uc0\u44221 \u44228 \u52286 \u44592 ', icon: \}, \{ id: 'B', label: '\u51648 \u54805 \u52286 \u44592 ', icon: \}, \{ id: 'C', label: '\u51060 \u47492 \u50756 \u49457 ', icon: \}, \{ id: 'D', label: '\u51648 \u44033 \u48320 \u46041 ', icon: \}, \{ id: 'E', label: '\u49453 \u51077 vs \u52649 \u46028 ', icon: \}].map(c => (\ \ ))\}\
\
\
\ \
\
\ );\ \}\ \ if (gameState === 'finished') \{\ const wrongCnt = wrongCards.length;\ const accuracy = Math.round(((cards.length - wrongCnt) / cards.length) * 100);\ const retryCnt = Math.ceil(wrongCnt * 1.5);\ return (\
\
\
\ \{isReviewMode ?
= 80 ? 'text-teal-500' : 'text-red-500'\}`\}>\{accuracy\}%
: \}\
\

\uc0\u54617 \u49845 \u50756 \u47308 !

\ \{isReviewMode && wrongCnt > 0 && (\
\
\uc0\u53952 \u47536 \u47928 \u51228 (\{wrongCnt\})
\
\{Array.from(new Set(wrongCards.map(c => c.q))).map((q, i) => \{ const card = wrongCards.find(c => c.q === q); return
Q. \{card.q\}
A. \{card.a\}
; \})\}
\
\ )\}\
\ \{isReviewMode && wrongCnt > 0 && \}\ \ \
\
\
\ );\ \}\ \ const currentCard = cards[currentIndex];\ const isQuiz = true;\ \ return (\
\
\
setGameState('start')\}>\uc0\u54032 \u51032 \u44221 \u44228 \u47560 \u49828 \u53552
\
\ \{isReviewMode &&
\uc0\u50724 \u45813 \u45432 \u53944
\}\ \{currentIndex + 1\}/\{cards.length\}\ \ \
\
\
\ \
\ \{isPaused &&

\uc0\u51068 \u49884 \u51221 \u51648

\}\ \
\
\ \{/* Front */\}\
\
\{currentCard.icon\}\{currentCard.type\}
\
\

\ \{currentCard.type === '\uc0\u51060 \u47492 \u50756 \u49457 ' ? \{currentCard.q\} : currentCard.q\}\

\
\
\

\{currentCard.type === '\uc0\u51648 \u54805 \u52286 \u44592 ' ? "\u54644 \u45817 \u46104 \u45716 \u51648 \u54805 \u51012 \u47784 \u46160 \u44256 \u47476 \u49464 \u50836 " : "\u50508 \u47582 \u51008 \u45813 \u51012 \u49440 \u53469 \u54616 \u49464 \u50836 "\}

\
\ \{currentCard.options.map(opt => (\ \ ))\}\
\ \
\
\ \{/* Back */\}\
\
\
Answer
\ \
\
\ \{quizResult && (\
\
\{quizResult === 'correct' ? : \}
\ \{quizResult === 'correct' ? '\uc0\u51221 \u45813 \u51077 \u45768 \u45796 !' : '\u50500 \u49789 \u45348 \u50836 !'\}\
\ )\}\

\{formatAnswer(currentCard.a)\}

\
\
\{ e.stopPropagation(); nextCard(); \}\} className="mt-4 flex items-center gap-2 bg-white/10 px-6 py-3 rounded-full cursor-pointer hover:bg-white/20">\uc0\u53552 \u52824 \u54616 \u50668 \u45796 \u51020 \u47928 \u51228 \u47196
\
\
\
\
\ \
\ );\ \};\ \ // =================================================================================================\ // [APP 3] \uc0\u47932 \u51656 \u51032 \u48516 \u47448 \u47560 \u49828 \u53552 (SubstanceFlashcards)\ // =================================================================================================\ const SubstanceFlashcards = (\{ onGoHome \}) => \{\ // Elements: \uc0\u49688 \u49548 , \u54764 \u47464 , \u44396 \u47532 , \u44552 , \u52384 , \u45796 \u51060 \u50500 \u47788 \u46300 , \u50724 \u51316 , \u51656 \u49548 \ // Compounds: \uc0\u47932 , \u51060 \u49328 \u54868 \u53444 \u49548 , \u47700 \u53580 \u51064 , \u50516 \u47784 \u45768 \u50500 , \u50684 \u54868 \u45208 \u53944 \u47464 , \u54252 \u46020 \u45817 , \u49328 \u54868 \u52384 , \u50640 \u53444 \u50732 \ const data = [\ \{ q: '\uc0\u49688 \u49548 (H\u8322 )', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (H)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u54764 \u47464 (He)', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (He)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u44396 \u47532 (Cu)', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (Cu)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u44552 (Au)', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (Au)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u52384 (Fe)', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (Fe)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u45796 \u51060 \u50500 \u47788 \u46300 (C)', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (C)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u50724 \u51316 (O\u8323 )', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (O)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u51656 \u49548 (N\u8322 )', a: '\u50896 \u49548 ', desc: '\u54620 \u44032 \u51648 \u50896 \u49548 (N)\u47196 \u47564 \u51060 \u47336 \u50612 \u51652 \u47932 \u51656 ' \},\ \{ q: '\uc0\u47932 (H\u8322 O)', a: '\u54868 \u54633 \u47932 ', desc: '\u49688 \u49548 (H)\u50752 \u49328 \u49548 (O) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u51060 \u49328 \u54868 \u53444 \u49548 (CO\u8322 )', a: '\u54868 \u54633 \u47932 ', desc: '\u53444 \u49548 (C)\u50752 \u49328 \u49548 (O) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u47700 \u53580 \u51064 (CH\u8324 )', a: '\u54868 \u54633 \u47932 ', desc: '\u53444 \u49548 (C)\u50752 \u49688 \u49548 (H) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u50516 \u47784 \u45768 \u50500 (NH\u8323 )', a: '\u54868 \u54633 \u47932 ', desc: '\u51656 \u49548 (N)\u50752 \u49688 \u49548 (H) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u50684 \u54868 \u45208 \u53944 \u47464 (NaCl)', a: '\u54868 \u54633 \u47932 ', desc: '\u45208 \u53944 \u47464 (Na)\u44284 \u50684 \u49548 (Cl) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u54252 \u46020 \u45817 (C\u8326 H\u8321 \u8322 O\u8326 )', a: '\u54868 \u54633 \u47932 ', desc: '\u53444 \u49548 (C), \u49688 \u49548 (H), \u49328 \u49548 (O) \u49464 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u49328 \u54868 \u52384 (Fe\u8322 O\u8323 )', a: '\u54868 \u54633 \u47932 ', desc: '\u52384 (Fe)\u44284 \u49328 \u49548 (O) \u46160 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ \{ q: '\uc0\u50640 \u53444 \u50732 (C\u8322 H\u8325 OH)', a: '\u54868 \u54633 \u47932 ', desc: '\u53444 \u49548 (C), \u49688 \u49548 (H), \u49328 \u49548 (O) \u49464 \u44032 \u51648 \u50896 \u49548 \u47196 \u51060 \u47336 \u50612 \u51664 ' \},\ ];\ \ const [gameState, setGameState] = useState('start');\ const [cards, setCards] = useState([]);\ const [currentIndex, setCurrentIndex] = useState(0);\ const [isFlipped, setIsFlipped] = useState(false);\ const [isPaused, setIsPaused] = useState(false);\ const [isTransitioning, setIsTransitioning] = useState(false);\ const [selectedOption, setSelectedOption] = useState(null);\ const [quizResult, setQuizResult] = useState(null);\ const [targetCount, setTargetCount] = useState(10);\ const [isCustomMode, setIsCustomMode] = useState(false);\ const [customInput, setCustomInput] = useState('');\ const [isReviewMode, setIsReviewMode] = useState(false);\ const [wrongCards, setWrongCards] = useState([]);\ \ const generateQuestions = (count) => \{\ let pool = data.map(item => (\{ ...item, id: Math.random().toString(36).substr(2, 9) \}));\ const shuffled = pool.sort(() => 0.5 - Math.random());\ let selected = [];\ while (selected.length < count) \{\ if (shuffled.length >= count) \{ selected = shuffled.slice(0, count); break; \}\ selected = selected.concat(shuffled);\ \}\ return selected.slice(0, count);\ \};\ \ const handleStart = () => \{\ let cnt = targetCount;\ if (isCustomMode) cnt = parseInt(customInput, 10);\ const newCards = generateQuestions(cnt);\ setCards(newCards);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setSelectedOption(null);\ setQuizResult(null);\ setGameState('playing');\ \};\ \ const handleRetry = () => \{\ const pool = [...wrongCards];\ const target = Math.ceil(pool.length * 1.5);\ let retry = [...pool];\ while (retry.length < target) retry.push(\{ ...pool[Math.floor(Math.random() * pool.length)], id: Math.random().toString() \});\ retry.sort(() => 0.5 - Math.random());\ setCards(retry);\ setWrongCards([]);\ setCurrentIndex(0);\ setIsFlipped(false);\ setIsPaused(false);\ setSelectedOption(null);\ setQuizResult(null);\ setGameState('playing');\ \};\ \ const nextCard = () => \{\ if (currentIndex < cards.length - 1) \{\ setIsTransitioning(true);\ setIsFlipped(false);\ setTimeout(() => \{\ setCurrentIndex(p => p + 1);\ setSelectedOption(null);\ setQuizResult(null);\ setIsTransitioning(false);\ \}, 300);\ \} else \{\ setGameState('finished');\ \}\ \};\ \ const checkAnswer = (answer) => \{\ if (isFlipped) return;\ setSelectedOption(answer);\ const card = cards[currentIndex];\ const isCorrect = card.a === answer;\ setQuizResult(isCorrect ? 'correct' : 'wrong');\ setIsFlipped(true);\ if (isReviewMode && !isCorrect) setWrongCards(p => \{ if (p.some(c => c.id === card.id)) return p; return [...p, card]; \});\ \};\ \ if (gameState === 'start') \{\ return (\
\
\
\ \
\
\
\

\uc0\u47932 \u51656 \u51032 \u48516 \u47448 \u47560 \u49828 \u53552

\
\
setIsReviewMode(!isReviewMode)\}>\
\uc0\u50724 \u45813 \u45432 \u53944 \u47784 \u46300
\
\
\
\
\uc0\u47928 \u54637 \u49688
\
\ \{[10, 15, 20, 25, 30, 40].map(c => )\}\
\ \ \{isCustomMode && setCustomInput(e.target.value)\} className="mt-2 w-full px-3 py-2 text-sm rounded-lg border-2 border-amber-200 text-center font-bold" placeholder="1~100"/>\}\
\
\ \
\
\ );\ \}\ \ if (gameState === 'finished') \{\ const wrongCnt = wrongCards.length;\ const accuracy = Math.round(((cards.length - wrongCnt) / cards.length) * 100);\ return (\
\
\
\ \{isReviewMode ?
= 80 ? 'text-green-500' : 'text-red-500'\}`\}>\{accuracy\}%
: \}\
\

\uc0\u54617 \u49845 \u50756 \u47308 !

\ \{isReviewMode && wrongCnt > 0 && (\
\
\uc0\u53952 \u47536 \u47928 \u51228 (\{wrongCnt\})
\
\{Array.from(new Set(wrongCards.map(c => c.q))).map((q, i) => \{ const card = wrongCards.find(c => c.q === q); return
Q. \{card.q\}
A. \{card.a\}
; \})\}
\
\ )\}\
\ \{isReviewMode && wrongCnt > 0 && \}\ \ \
\
\
\ );\ \}\ \ const currentCard = cards[currentIndex];\ return (\
\
\
setGameState('start')\}>\uc0\u47932 \u51656 \u48516 \u47448
\
\ \{isReviewMode &&
\uc0\u50724 \u45813 \u45432 \u53944
\}\ \{currentIndex + 1\}/\{cards.length\}\ \ \
\
\
\ \
\ \{isPaused &&

\uc0\u51068 \u49884 \u51221 \u51648

\}\ \
\
\ \{/* Front */\}\
\
Question
\

\{currentCard.q\}

\
\ \ \
\
\ \{/* Back */\}\
\
\
Answer
\ \
\
\ \{quizResult && (\
\
\{quizResult === 'correct' ? : \}
\ \{quizResult === 'correct' ? '\uc0\u51221 \u45813 \u51077 \u45768 \u45796 !' : '\u50500 \u49789 \u45348 \u50836 !'\}\
\ )\}\

\{currentCard.a\}

\

\{currentCard.desc\}

\
\
\{ e.stopPropagation(); nextCard(); \}\} className="mt-4 flex items-center gap-2 bg-white/10 px-6 py-3 rounded-full cursor-pointer hover:bg-white/20">\uc0\u53552 \u52824 \u54616 \u50668 \u45796 \u51020 \u47928 \u51228 \u47196
\
\
\
\
\ \
\ );\ \};\ \ // =================================================================================================\ // [MAIN HUB] \uc0\u53685 \u54633 \u44284 \u54617 \u54617 \u49845 \u54728 \u48652 \ // =================================================================================================\ const ScienceHub = () => \{\ const [app, setApp] = useState('hub'); // 'hub', 'element', 'plate', 'substance'\ \ if (app === 'element') return setApp('hub')\} />;\ if (app === 'plate') return setApp('hub')\} />;\ if (app === 'substance') return setApp('hub')\} />;\ \ return (\
\
\

\uc0\u44284 \u54617 \u47560 \u49828 \u53552 \u53364 \u47000 \u49828

\

\uc0\u50896 \u54616 \u45716 \u54617 \u49845 \u51452 \u51228 \u47484 \u49440 \u53469 \u54616 \u49464 \u50836

\ \
\ \{/* Card 1: Elements */\}\ \ \ \{/* Card 2: Plates */\}\ \ \ \{/* Card 3: Substances */\}\ \
\
\
\ );\ \};\ \ export default ScienceHub;}