メインコンテンツへスキップ
見出し画像

小学6年生がAIとバイブコーティングで勉強アプリを作った結果

    僕はお父さんに勧められて、バイブコーティングを始めてこのアプリを作りました。
    最初はお父さんが隣にいたけど、あともう僕1人でやって、それでこの作品、が完成しました。気に入っていただけたならフォローとスキお願いします。下のコードをコピーしてコードペンのHTMLに貼ってみてください。

    <!DOCTYPE html>
    <html lang="ja">
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>楽しく勉強ができるクイズ(全学年対応)</title>
       <link href="https://fonts.googleapis.com/css2?family=M+PLUS+1p:wght@400;700;900&family=Orbitron:wght@500;700;900&display=swap" rel="stylesheet">
       <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" async></script>
       <script>
         window.MathJax = { tex: { inlineMath: [['$', '$'], ['\\(', '\\)']] }, chtml: { displayAlign: 'center', matchFontHeight: false } };
       </script>
       <style>
           body { font-family: 'M PLUS 1p', sans-serif; margin: 0; padding: 15px; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; box-sizing: border-box; overflow-x: hidden; transition: background 0.8s cubic-bezier(0.16, 1, 0.3, 1), filter 0.5s ease; }
           @keyframes flowWood { 0% { background-position: 0px 0px; } 100% { background-position: 160px 60px; } }
           @keyframes shiftMarble { 0% { background-size: 100% 100%; } 50% { background-size: 120% 120%; } 100% { background-size: 100% 100%; } }
           @keyframes driftConcrete { 0% { filter: contrast(1); } 50% { filter: contrast(1.1) brightness(0.95); } 100% { filter: contrast(1); } }
           @keyframes wavePaper { 0% { background-position: 0 0; } 100% { background-position: 0 48px; } }
           @keyframes pulseDark { 0% { background-color: #0f172a; } 50% { background-color: #1e1b4b; } 100% { background-color: #0f172a; } }
           @keyframes driftCyber { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
           @keyframes digitalFall { 0% { background-position: 0px 0px; } 100% { background-position: 0px 160px; } }
           @keyframes bossPulse { 0% { background-color: #450a0a; box-shadow: inset 0 0 50px #7f1d1d; } 50% { background-color: #220505; box-shadow: inset 0 0 100px #b91c1c; } 100% { background-color: #450a0a; box-shadow: inset 0 0 50px #7f1d1d; } }
           @keyframes warningFlash { 0% { opacity: 0; transform: scale(0.8); } 20% { opacity: 1; transform: scale(1.1); } 80% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(1.2); } }

           body.bg-wood { background-color: #8b5a2b; background-image: linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px), linear-gradient(rgba(0,0,0,0.06) 1px, transparent 1px); background-size: 80px 100%; animation: flowWood 20s linear infinite; }
           body.bg-marble { background-color: #0f172a; background-image: radial-gradient(circle at center, #312e81 0%, #020617 100%); animation: shiftMarble 15s ease-in-out infinite; }
           body.bg-concrete { background-color: #94a3b8; background-image: linear-gradient(135deg, #cbd5e1 0%, #64748b 100%); animation: driftConcrete 12s ease-in-out infinite; }
           body.bg-paper { background-color: #fef08a; background-image: linear-gradient(#fef9c3 2px, transparent 2px); background-size: 100% 24px; animation: wavePaper 25s linear infinite; }
           body.bg-dark { animation: pulseDark 8s ease-in-out infinite; }
           body.bg-cyber { background: linear-gradient(120deg, #0f172a, #7c3aed, #db2777, #0f172a); background-size: 300% 300%; animation: driftCyber 8s ease infinite; }
           body.bg-matrix { background-color: #000000; background-image: linear-gradient(rgba(0, 255, 65, 0.15) 50%, transparent 50%); background-size: 100% 10px; animation: digitalFall 4s linear infinite; color: #00ff41; }
           body.bg-boss { animation: bossPulse 2s infinite; background-image: repeating-linear-gradient(45deg, rgba(0,0,0,0.2) 0px, rgba(0,0,0,0.2) 2px, transparent 2px, transparent 4px); }

           .theme-selector-panel { background-color: rgba(255, 255, 255, 0.96); padding: 10px 16px; border-radius: 25px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); margin-bottom: 20px; display: flex; gap: 10px; align-items: center; font-size: 13px; font-weight: bold; color: #1e293b; max-width: 600px; width: 100%; overflow-x: auto; white-space: nowrap; box-sizing: border-box; z-index: 10; position: relative; }
           .theme-tab { border: 2px solid #e2e8f0; padding: 6px 14px; font-size: 12px; border-radius: 18px; background-color: white; cursor: pointer; transition: all 0.3s ease; color: #475569; font-weight: bold; font-family: inherit; }
           .theme-tab.active-theme { background-color: #1e293b; color: white; border-color: #1e293b; box-shadow: 0 4px 10px rgba(0,0,0,0.25); }

           .container { background-color: rgba(255, 255, 255, 0.98); border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); padding: 32px 24px; width: 100%; max-width: 600px; text-align: center; box-sizing: border-box; position: relative; z-index: 10; overflow: hidden; transition: all 0.3s; }
           h1 { font-family: 'M PLUS 1p', sans-serif; color: #1e293b; font-size: 21px; margin-top: 0; margin-bottom: 18px; border-bottom: 3px solid #f1f5f9; padding-bottom: 12px; font-weight: 900; }
           
           .progress { font-family: 'Orbitron', 'M PLUS 1p', sans-serif; font-size: 14px; color: #1e293b; margin-bottom: 18px; font-weight: 700; background: #f1f5f9; padding: 6px 16px; border-radius: 20px; display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; border: 2px solid transparent; transition: 0.3s; }
           .progress.boss-ui { border-color: #ef4444; background: #fef2f2; color: #991b1b; box-shadow: 0 0 10px rgba(239,68,68,0.5); }
           .streak-badge { background: linear-gradient(135deg, #f59e0b, #ef4444); color: white; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 900; box-shadow: 0 0 8px rgba(239,68,68,0.5); animation: pulse 1s infinite alternate; }
           .level-badge { background: linear-gradient(135deg, #7c3aed, #3b82f6); color: white; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 900; box-shadow: 0 0 8px rgba(124,58,237,0.5); }
           @keyframes pulse { 0% { transform: scale(1); } 100% { transform: scale(1.05); } }

           .question { text-align: left; line-height: 1.7; color: #1e293b; font-size: 16px; margin-bottom: 22px; font-weight: 700; }
           .question.boss-text { color: #991b1b; font-size: 17px; font-weight: 900; text-shadow: 0 0 5px rgba(239,68,68,0.2); }
           
           .brainwave-box { width: 100%; height: 40px; background: #0f172a; border-radius: 8px; margin-bottom: 15px; overflow: hidden; position: relative; box-shadow: inset 0 2px 6px rgba(0,0,0,0.5); }
           .brainwave-canvas { width: 100%; height: 100%; display: block; }
           .brainwave-text { font-family: 'Orbitron', monospace; position: absolute; left: 10px; top: 12px; font-size: 11px; color: #38bdf8; font-weight: 900; z-index: 2; text-shadow: 0 1px 2px rgba(0,0,0,0.8); }

           .timer-setting-box { background-color: #f8fafc; border: 2px solid #e2e8f0; border-radius: 12px; padding: 12px; margin-top: 15px; margin-bottom: 15px; font-size: 14px; color: #334155; display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; font-weight: bold; }
           .timer-input { font-family: 'Orbitron', sans-serif; width: 70px; padding: 6px; font-size: 14px; border: 2px solid #cbd5e1; border-radius: 6px; text-align: center; font-weight: bold; color: #1e293b; outline: none; }
           
           .subject-menu { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; width: 100%; }
           .subject-menu .full-width { grid-column: span 2; }
           .options-list { display: grid; grid-template-columns: 1fr; gap: 12px; margin-bottom: 12px; width: 100%; }

           .btn { font-family: inherit; color: white; border: none; padding: 14px 6px; font-size: 14px; border-radius: 10px; cursor: pointer; transition: all 0.15s ease; width: 100%; box-sizing: border-box; font-weight: 700; word-break: keep-all; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
           .option-btn { background: linear-gradient(135deg, #3b82f6, #1d4ed8); }
           .option-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(37,99,235,0.4); }
           .option-btn:active { transform: scale(0.96); }
           
           .subject-btn { padding: 20px 2px; font-size: 15px; text-shadow: 0 2px 4px rgba(0,0,0,0.15); font-weight: 900; }
           .btn-math { background: linear-gradient(135deg, #f59e0b, #d97706); }
           .btn-jp { background: linear-gradient(135deg, #ec4899, #db2777); }
           .btn-sci { background: linear-gradient(135deg, #0d9488, #0f766e); }
           .btn-soc { background: linear-gradient(135deg, #d97706, #b45309); }
           .btn-info { background: linear-gradient(135deg, #475569, #1e293b); }
           .btn-myst { background: linear-gradient(135deg, #8b5cf6, #5b21b6); }
           .btn-share { background: linear-gradient(135deg, #14b8a6, #0d9488); margin-top: 10px;}
           
           .result { font-family: 'Orbitron', 'M PLUS 1p', sans-serif; font-size: 18px; font-weight: 900; margin-top: 8px; min-height: 35px; transition: all 0.3s; }
           .correct { color: #10b981; text-shadow: 0 0 10px rgba(16,185,129,0.5); transform: scale(1.05); }
           .wrong { color: #ef4444; text-shadow: 0 0 10px rgba(239,68,68,0.5); }
           
           .action-container { margin-top: 20px; min-height: 50px; display: flex; gap: 10px; width: 100%; }
           .hint-btn { background: linear-gradient(135deg, #64748b, #475569); flex: 1.2; }
           .skip-btn { font-family: 'Orbitron', 'M PLUS 1p', sans-serif; background: linear-gradient(135deg, #f59e0b, #d97706); visibility: hidden; flex: 1.2; }
           .terminate-btn { background: linear-gradient(135deg, #ef4444, #b91c1c); visibility: hidden; flex: 1; transition: background 0.3s; }
           
           .hint-text-box, .explanation-text-box { font-size: 14px; margin-top: 18px; padding: 14px; border-radius: 6px; text-align: left; line-height: 1.6; display: none; }
           .hint-text-box { color: #334155; box-shadow: inset 0 2px 4px rgba(0,0,0,0.02); }
           .explanation-text-box { color: #0f172a; background-color: #f0fdf4; border-left: 6px solid #10b981; }

           .radar-chart-container { display: flex; flex-direction: column; align-items: center; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 15px; margin: 20px 0; width: 100%; }
           .radar-svg { max-width: 280px; width: 100%; height: 280px; }
           .radar-axis { stroke: #cbd5e1; stroke-dasharray: 2,2; }
           .radar-grid { fill: none; stroke: #e2e8f0; }
           .radar-polygon { fill: rgba(59, 130, 246, 0.25); stroke: #3b82f6; stroke-width: 2.5; stroke-linejoin: round; }
           .radar-label { font-family: 'M PLUS 1p', sans-serif; font-size: 11px; font-weight: bold; fill: #475569; text-anchor: middle; }

           .review-section { margin-top: 30px; text-align: left; border-top: 3px solid #e2e8f0; padding-top: 25px; max-height: 380px; overflow-y: auto; }
           .review-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 16px; margin-bottom: 14px; }
           .review-q-text { font-size: 14px; font-weight: bold; margin-bottom: 10px; }
           .review-correct-ans { font-size: 13px; font-weight: bold; color: #10b981; }

           .screen { display: none; }
           .active-screen { display: block; }
           .score-text { font-family: 'Orbitron', 'M PLUS 1p', sans-serif; font-size: 20px; margin-bottom: 12px; font-weight: 900; color: #1e3a8a; }
           .rank-box { background-color: #fef9c3; border: 2px dashed #f59e0b; border-radius: 10px; padding: 18px; margin: 22px 0; font-size: 19px; font-weight: 900; color: #b45309; text-align: center; }
           
           #fx-canvas { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 9999; }
           #warning-overlay { position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%); color: #ef4444; font-family: 'Orbitron', monospace; font-size: 55px; font-weight: 900; text-shadow: 0 0 20px #dc2626, 0 0 40px #000; z-index: 10000; display: none; pointer-events: none; text-align: center; }
           #warning-overlay.active { display: block; animation: warningFlash 2.5s cubic-bezier(0.1, 0.7, 0.1, 1) forwards; }
           .shake { animation: shake 0.5s both; }
           @keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } 40%, 60% { transform: translate3d(4px, 0, 0); } }
       </style>
    </head>
    <body class="bg-wood" id="main-body">
       <canvas id="fx-canvas"></canvas>
       <div id="warning-overlay">🚨 BOSS STAGE 🚨</div>

       <div class="theme-selector-panel">
           <span>🎨 BG:</span>
           <button class="theme-tab active-theme" onclick="sfx.click(); changeBackgroundTheme('wood', this)" onmouseenter="initAudio(); sfx.hover()">天然木</button>
           <button class="theme-tab" onclick="sfx.click(); changeBackgroundTheme('marble', this)" onmouseenter="initAudio(); sfx.hover()">大理石</button>
           <button class="theme-tab" onclick="sfx.click(); changeBackgroundTheme('concrete', this)" onmouseenter="initAudio(); sfx.hover()">コンクリ</button>
           <button class="theme-tab" onclick="sfx.click(); changeBackgroundTheme('paper', this)" onmouseenter="initAudio(); sfx.hover()">ノート</button>
           <button class="theme-tab" onclick="sfx.click(); changeBackgroundTheme('dark', this)" onmouseenter="initAudio(); sfx.hover()">暗室</button>
           <button class="theme-tab" onclick="sfx.click(); changeBackgroundTheme('cyber', this)" onmouseenter="initAudio(); sfx.hover()">サイバー</button>
       </div>
       
       <div class="container" id="main-container">
           <h1 id="main-title">楽しく勉強ができるクイズ(全学年対応)</h1>

           <div class="brainwave-box" id="brainwave-zone" style="display:none;">
               <div class="brainwave-text" id="brainwave-status">SYSTEM LOADED</div>
               <canvas id="brainwaveCanvas" class="brainwave-canvas"></canvas>
           </div>

           <div id="quiz-screen" class="screen active-screen">
               <div class="progress" id="progress">準備中...</div>
               <div id="setup-timer-zone"></div>
               <div class="question" id="question-text">最初に、設定を確認して勉強したい学年を選んでください。</div>
               <div id="options-container"></div>
               <div class="result" id="result-message"></div>
               
               <div class="action-container">
                   <button id="hint-button" class="btn hint-btn" onclick="toggleHint()" onmouseenter="initAudio(); sfx.hover()">💡 ヒント表示</button>
                   <button id="skip-button" class="btn skip-btn" onclick="nextQuestion()" onmouseenter="initAudio(); sfx.hover()">次へ ➔</button>
                   <button id="terminate-button" class="btn terminate-btn" onclick="terminateQuiz()" onmouseenter="initAudio(); sfx.hover()">🛑 終了</button>
               </div>
               <div id="hint-text-display" class="hint-text-box"></div>
               <div id="explanation-text-display" class="explanation-text-box"></div>
           </div>

           <div id="score-screen" class="screen">
               <h1>RESULT</h1>
               <div class="score-text" id="score-text">0問正解!</div>
               <div id="rank-display"></div>
               <div id="review-retry-zone"></div>
               <button class="btn home-btn" style="width:100%" onclick="sfx.back(); initApp()" onmouseenter="initAudio(); sfx.hover()">タイトルに戻る</button>
               <div class="review-section" id="review-container">
                   <div class="review-title">🔍 復習リスト</div>
                   <div id="review-cards-list"></div>
               </div>
           </div>
       </div>

    <script>
    // ==========================================
    // 🚀 データベース (全学年対応メガボリューム)
    // ==========================================
    const JP_DATABASE = {
       1: [{q: "「いぬ」をカタカナで書くとどれ?", a: "イヌ", d: ["ネコ", "トリ"]}, {q: "「おはよう」のあとに続くあいさつは?", a: "ございます", d: ["さようなら", "おやすみ"]}],
       2: [{q: "反対の意味の言葉の組み合わせはどれ?", a: "大きい - 小さい", d: ["あつい - あたたかい", "きれい - うつくしい"]}, {q: "「春」の読み方は?", a: "はる", d: ["なつ", "あき"]}],
       3: [{q: "「走る」の反対の意味の言葉は?", a: "止まる", d: ["歩く", "飛ぶ"]}],
       4: [{q: "「山」の部首は?", a: "やまへん・やま", d: ["きへん", "さんずい"]}, {q: "「休む」の部首は?", a: "にんべん", d: ["きへん", "てへん"]}],
       5: [{q: "「一石二鳥」の意味は?", a: "一つの行動で二つの利益を得ること", d: ["石を投げて鳥を二羽落とすこと", "鳥を二羽飼うこと"]}],
       6: [{q: "「五月雨」の正しい読み方は?", a: "さみだれ", d: ["ごがつあめ", "さつきあめ"]}, {q: "「急がば回れ」の意味は?", a: "急ぐときほど安全で確実な方法をとるべきだ", d: ["急ぐときは走ったほうがいい", "急ぐときは近道を探せ"]}],
       7: [{q: "「矛盾」の由来となった中国の書物は?", a: "韓非子", d: ["論語", "史記"]}],
       8: [{q: "「吾輩は猫である」の作者は?", a: "夏目漱石", d: ["森鴎外", "芥川龍之介"]}],
       9: [{q: "「枕草子」の冒頭は「春は( )」。", a: "あけぼの", d: ["夕暮れ", "夜"]}],
       10: [{q: "「徒然草」の作者は?", a: "兼好法師(吉田兼好)", d: ["鴨長明", "清少納言"]}],
       11: [{q: "「羅生門」の作者は?", a: "芥川龍之介", d: ["太宰治", "夏目漱石"]}],
       12: [{q: "「山月記」で虎になった主人公の名前は?", a: "李徴", d: ["袁参", "中島敦"]}],
       13: [{q: "「こころ」で「私」に遺書を残した人物は?", a: "先生", d: ["K", "お嬢さん"]}],
       14: [{q: "次のうち、芥川賞を受賞した作品は?", a: "火花", d: ["ノルウェイの森", "人間失格"]}]
    };

    const SCI_DATABASE = {
       1: [{q: "チューリップの花がさく季節は?", a: "はる", d: ["なつ", "あき", "ふゆ"]}, {q: "こおりが とけると なにになる?", a: "みず", d: ["ゆげ", "くうき", "つち"]}, {q: "じしゃくに くっつくのは?", a: "てつのクリップ", d: ["けしゴム", "きのえだ", "ガラス"]}],
       2: [{q: "トマトの 育ち方で 正しいのは?", a: "たね→め→は→はな→み", d: ["たね→はな→め→み→は", "め→たね→はな→は→み"]}, {q: "ザリガニが 身を守るのに 使うのは?", a: "はさみ", d: ["しっぽ", "きば", "つの"]}],
       3: [{q: "昆虫の足の数は?", a: "6本", d: ["4本", "8本", "10本"]}, {q: "チョウの育ち方の順番は?", a: "卵→幼虫→さなぎ→成虫", d: ["卵→さなぎ→幼虫→成虫"]}, {q: "虫めがねで日光を集めると黒い紙はどうなる?", a: "こげる", d: ["白くなる", "凍る"]}],
       4: [{q: "水がふっとうする温度は約何度?", a: "約100度", d: ["約50度", "約0度"]}, {q: "空気はあたためられるとどうなる?", a: "体積が大きくなる", d: ["小さくなる", "重くなる"]}],
       5: [{q: "メダカのオスのしりびれの特徴は?", a: "平行四辺形に近く大きい", d: ["三角形", "丸みを帯びている"]}, {q: "水に食塩が溶ける量には限度がある?", a: "限度がある", d: ["無限に溶ける"]}],
       6: [{q: "物が燃えるときに使われる気体は?", a: "酸素", d: ["二酸化炭素", "窒素"]}, {q: "食べ物を消化し、栄養分を最も多く吸収する器官は?", a: "小腸", d: ["大腸", "胃"]}],
       7: [{q: "ばねののびが重さに比例する関係は?", a: "フックの法則", d: ["アルキメデスの原理", "パスカルの原理"]}, {q: "太陽・月・地球の順に並んだときに起こるのは?", a: "日食", d: ["月食"]}],
       8: [{q: "植物が光合成を行う際に必要な気体は?", a: "二酸化炭素", d: ["酸素", "窒素"]}, {q: "音が空気中を伝わる速さは1秒間に約何m?", a: "約340m", d: ["約1500m", "約30万km"]}],
       9: [{q: "酸化銀を加熱したときに発生する気体は?", a: "酸素", d: ["二酸化炭素", "水素"]}, {q: "V = I × R を何という?", a: "オームの法則", d: ["フレミングの法則", "ジュールの法則"]}],
       10: [{q: "水にとけると電流が流れる物質を何という?", a: "電解質", d: ["非電解質"]}, {q: "位置エネルギーと運動エネルギーの和は?", a: "力学的エネルギー", d: ["熱エネルギー"]}],
       11: [{q: "1molの理想気体が標準状態で占める体積は約何L?", a: "22.4L", d: ["11.2L", "44.8L"]}, {q: "運動方程式は?", a: "ma = F", d: ["F = m / a", "a = mF"]}],
       12: [{q: "気体の体積は絶対温度に比例するという法則は?", a: "シャルルの法則", d: ["ボイルの法則"]}, {q: "酵素の主成分は?", a: "タンパク質", d: ["炭水化物", "脂質"]}],
       13: [{q: "反応熱の総和は経路によらないという法則は?", a: "ヘスの法則", d: ["ル・シャトリエの原理"]}, {q: "浸透圧の式は?", a: "Π = CRT", d: ["P = nRT/V"]}],
       14: [{q: "粒子の状態を記述する波動関数が従う基礎方程式は?", a: "シュレーディンガー方程式", d: ["マクスウェル方程式"]}, {q: "宇宙膨張を示す法則は?", a: "ハッブル=ルメートルの法則", d: ["ケプラーの法則"]}]
    };

    const SOC_DATABASE = {
       1: [{q: "こうえんで あそぶときに たいせつなのは?", a: "じゅんばんを まもる", d: ["ゴミを すてる", "よる おそくまで あそぶ"]}, {q: "はるに さく ピンクの おはなは?", a: "さくら", d: ["ひまわり", "コスモス"]}, {q: "あきの むしは どれ?", a: "すずむし", d: ["カブトムシ", "セミ"]}],
       2: [{q: "まちたんけんで おかいものをする ばしょは?", a: "おみせ(スーパーなど)", d: ["としょかん", "こうばん"]}, {q: "やさいを そだてるときに あげるものは?", a: "みず", d: ["ジュース", "ミルク"]}, {q: "ほんを たくさん かりられる ばしょは?", a: "としょかん", d: ["びょういん", "ゆうびんきょく"]}],
       3: [{q: "地図記号の「文」は何を表す?", a: "学校", d: ["病院", "交番"]}, {q: "火事のときに火を消しに来てくれるのは?", a: "消防署", d: ["警察署", "市役所"]}, {q: "スーパーマーケットの工夫は?", a: "見やすく品物を並べる", d: ["わざと探しにくくする", "値段を隠す"]}],
       4: [{q: "日本の都道府県の数はいくつ?", a: "47", d: ["43", "50", "45"]}, {q: "家庭から出たゴミが運ばれて燃やされる場所は?", a: "清掃工場", d: ["浄水場", "発電所"]}, {q: "水はどこから送られてくる?", a: "浄水場", d: ["下水処理場", "交番"]}],
       5: [{q: "日本で一番多く作られている穀物は?", a: "米", d: ["小麦", "とうもろこし"]}, {q: "日本の自動車工業がさかんな地域は?", a: "中京工業地帯", d: ["北海道", "沖縄"]}, {q: "魚を育てる漁業を何という?", a: "養殖業(栽培漁業)", d: ["遠洋漁業", "沖合漁業"]}],
       6: [{q: "鎌倉幕府を開いたのは誰?", a: "源頼朝", d: ["徳川家康", "豊臣秀吉"]}, {q: "日本国憲法の三大原則は「国民主権」「基本的人権の尊重」ともう一つは?", a: "平和主義", d: ["三権分立", "地方自治"]}, {q: "江戸時代にキリスト教を禁止し、外国との貿易を制限した政策は?", a: "鎖国", d: ["開国", "大化の改新"]}],
       7: [{q: "日本の最南端の島は?", a: "沖ノ鳥島", d: ["択捉島", "与那国島", "南鳥島"]}, {q: "聖徳太子が定めた役人の心構えは?", a: "十七条の憲法", d: ["冠位十二階", "御成敗式目"]}],
       8: [{q: "世界で一番面積の大きい国は?", a: "ロシア", d: ["カナダ", "アメリカ", "中国"]}, {q: "四大文明のうち、ナイル川流域で栄えたのは?", a: "エジプト文明", d: ["メソポタミア文明", "インダス文明"]}],
       9: [{q: "日本の気候で、冬に雪が多く降るのはどちら側?", a: "日本海側", d: ["太平洋側", "瀬戸内海側"]}, {q: "豊臣秀吉が行った、全国の土地を測量する政策は?", a: "太閤検地", d: ["刀狩", "参勤交代"]}],
       10: [{q: "国の政治を行う三権分立で、法律を作るのは?", a: "国会(立法権)", d: ["内閣(行政権)", "裁判所(司法権)"]}, {q: "需要と供給の関係で、需要が供給より大きいと価格はどうなる?", a: "上がる", d: ["下がる", "変わらない"]}],
       11: [{q: "1914年に始まった、ヨーロッパを中心とする大規模な戦争は?", a: "第一次世界大戦", d: ["第二次世界大戦", "冷戦"]}, {q: "日本アルプスを構成する山脈は、飛騨、木曽とあと一つは?", a: "赤石山脈", d: ["奥羽山脈", "日高山脈"]}],
       12: [{q: "1789年に起こった、絶対王政を打倒した市民革命は?", a: "フランス革命", d: ["名誉革命", "産業革命"]}, {q: "国際連合の安全保障理事会で、常任理事国が持つ特別な権限は?", a: "拒否権", d: ["違憲立法審査権", "解散権"]}],
       13: [{q: "1985年に結ばれた、ドル高是正のための国際的合意は?", a: "プラザ合意", d: ["ブレトンウッズ協定", "スミソニアン協定"]}, {q: "16世紀にマルティン・ルターが始めた運動は?", a: "宗教改革", d: ["ルネサンス", "大航海時代"]}],
       14: [{q: "マクロ経済学において、消費、投資、政府支出、純輸出の総和として定義される指標は?", a: "GDP (国内総生産)", d: ["GNI", "NNP"]}, {q: "19世紀後半、ビスマルクによるドイツ統一運動の過程で起こった戦争ではないものは?", a: "クリミア戦争", d: ["普丹戦争", "普仏戦争"]}]
    };

    const INFO_DATABASE = {
       1: [{q: "スマホをつかうとき、おやくそくを まもるのは 誰?", a: "じぶん", d: ["おともだち", "まもらなくていい"]}, {q: "しらない ひとから メッセージが きたら どうする?", a: "おうちのひとに いう", d: ["へんじをする", "あいにいく"]}],
       2: [{q: "インターネットで じぶんの なまえや じゅうしょを かいていい?", a: "だめ", d: ["いいよ", "なまえだけならいい"]}, {q: "ゲームを ずっと やりつづけても いい?", a: "じかんを きめる", d: ["ずっとやる"]}],
       3: [{q: "コンピュータの 画面を うごかす マウス。クリックって なに?", a: "ボタンをおすこと", d: ["もちあげること", "ふること"]}],
       4: [{q: "インターネットで 調べる時に 使う言葉を 何という?", a: "検索キーワード", d: ["パスワード", "アドレス"]}],
       5: [{q: "写真をインターネットに載せるとき、注意することは?", a: "場所がわかるものを写さない", d: ["顔をはっきり写す"]}],
       6: [{q: "他の人が作った文章や画像を、勝手に自分のものとして使うとどうなる?", a: "著作権の侵害になる", d: ["ほめられる", "とくに問題ない"]}],
       7: [{q: "コンピュータウイルスを防ぐために入れるソフトウェアは?", a: "対策ソフト(アンチウイルス)", d: ["ブラウザ", "ワープロソフト"]}],
       8: [{q: "情報を暗号化するとき、暗号化と復号に同じ鍵を使う方式は?", a: "共通鍵暗号", d: ["公開鍵暗号", "ハッシュ関数"]}],
       9: [{q: "悪意のあるソフトウェアの総称は?", a: "マルウェア", d: ["ファイアウォール", "プロトコル"]}],
       10: [{q: "コンピュータの頭脳と呼ばれる、計算や制御を行う装置は?", a: "CPU", d: ["メモリ", "HDD"]}],
       11: [{q: "LANとLANを接続し、最適な経路を選択してデータを送る機器は?", a: "ルータ", d: ["ハブ", "モデム"]}],
       12: [{q: "関係データベースで、表と表を結合する操作は?", a: "結合(JOIN)", d: ["選択(SELECT)", "射影(PROJECT)"]}],
       13: [{q: "OSI基本参照モデルで、最も下位の層(第1層)は?", a: "物理層", d: ["データリンク層", "ネットワーク層"]}],
       14: [{q: "プログラムの制御フローを意図的に複雑化する難読化手法は?", a: "不透明述語", d: ["インライン展開", "ループ展開"]}, {q: "公開鍵暗号方式のアルゴリズムはどれか?", a: "RSA", d: ["AES", "DES"]}]
    };

    const MYST_DATABASE = {
       1: [{q: "パンはパンでも、食べられないパンはなーんだ?", a: "フライパン", d: ["メロンパン", "食パン"], h: "料理を作るときに使う道具です。", r: "フライパンは食べ物ではありません。"}, {q: "逆さにすると「軽い」動物はなーんだ?", a: "イルカ", d: ["ライオン", "キリン"], h: "「いるか」を逆さから読んでみよう。", r: "いるかを逆から読むと「かるい」になります。"}],
       2: [{q: "いつも文句ばかり言っている動物は?", a: "ウシ", d: ["ブタ", "ニワトリ"], h: "鳴き声に注目!", r: "ウシの鳴き声は「モーモー(もう!もう!)」です。"}],
       3: [{q: "ある法則で並んでいます。「月、火、水、木、金、?」 ?に入るのは?", a: "土", d: ["日", "星"], h: "カレンダーを見てみましょう。", r: "曜日の順番です。"}],
       4: [{q: "英語で春夏秋冬の頭文字「S、S、A、?」?は?", a: "W", d: ["F", "Y"], h: "Winterの頭文字です。", r: "Spring, Summer, Autumn, Winter。"}],
       5: [{q: "「春=1、夏=2、秋=3」のとき、「冬」はいくつ?", a: "4", d: ["5", "0"], h: "四季の順番です。", r: "日本の四季を順番に数字で表しています。"}],
       6: [{q: "1階から2階へ階段を20段上ります。1階から5階へ行くには何段?", a: "80段", d: ["100段", "120段"], h: "1階から5階までは何階層分?", r: "4階層分上がるので 20×4=80段です。"}],
       7: [{q: "5人でかくれんぼ中。2人見つかった。残りは何人隠れてる?", a: "2人", d: ["3人", "1人"], h: "鬼の人数を忘れないで!", r: "5人のうち1人は鬼です。隠れている4人のうち2人が見つかりました。"}],
       8: [{q: "A君はB君より背が高く、C君はA君より背が高い。一番背が低いのは?", a: "B君", d: ["A君", "C君"], h: "背の順に並べてみましょう。", r: "C > A > B の順になります。"}],
       9: [{q: "「10、11、12、13、14、15、16、17、20…」この法則は?", a: "8進数", d: ["素数", "16進数"], h: "8と9が使われていません。", r: "8進数で数を数えています。"}],
       10: [{q: "A「当たり」B「ハズレ」C「Aはハズレ」。本当の箱が1つの時、当たりは?", a: "B", d: ["A", "C"], h: "矛盾しないか試しましょう。", r: "Cの「Aはハズレ」が本当だと仮定すると、Bの「ハズレ」は嘘になり当たりとなります。"}],
       11: [{q: "今日が1/1。「一昨日は21歳、来年は24歳になる」誕生日は?", a: "12月31日", d: ["2月29日", "1月1日"], h: "昨日誕生日を迎えて22歳になったとすると?", r: "12/31生まれの人が1/1に発言しています。"}],
       12: [{q: "証言A「Bが犯人」、B「私は違う」、C「Aは嘘つき」。犯人が1人で嘘つきも1人の場合、犯人は?", a: "B", d: ["A", "C"], h: "AとCの証言は完全に矛盾しています。", r: "嘘つきはAとなり、Cは本当。Bは「私は違う」と嘘をついていることになります(ひっかけ)。"}],
       13: [{q: "アルファベットを3文字後ろにずらす暗号で「FDW」は元の言葉は何?", a: "CAT", d: ["DOG", "BAT"], h: "Fの3つ前は?", r: "Fの3つ前はC、Dの3つ前はA、Wの3つ前はT。"}],
       14: [{q: "「モンティ・ホール問題」司会者がハズレのドアを開けた後、選択を変えるべきか?", a: "変えた方が当たる確率が2倍になる", d: ["変えても同じ", "変えない方が当たりやすい"], h: "直感に反する確率のパラドックスです。", r: "最初にハズレ(確率2/3)を選んでいた場合に必ず当たるようになるためです。"}]
    };

    // ==========================================
    // 🚀 AI自動拡張システム (Gemini API)
    // ==========================================
    const GEMINI_API_KEY = "ここに取得したGeminiのAPIキーを貼り付けてください";

    function saveDatabaseToLocal() {
       localStorage.setItem("CUSTOM_SCI_DATABASE", JSON.stringify(SCI_DATABASE));
       localStorage.setItem("CUSTOM_INFO_DATABASE", JSON.stringify(INFO_DATABASE));
       localStorage.setItem("CUSTOM_MYST_DATABASE", JSON.stringify(MYST_DATABASE));
       localStorage.setItem("CUSTOM_SOC_DATABASE", JSON.stringify(SOC_DATABASE));
       localStorage.setItem("CUSTOM_JP_DATABASE", JSON.stringify(JP_DATABASE));
    }

    function loadDatabaseFromLocal() {
       const parseAndMerge = (key, targetObj) => {
           const saved = localStorage.getItem(key);
           if (saved) {
               try {
                   const parsed = JSON.parse(saved);
                   for (const level in parsed) {
                       if (!targetObj[level]) targetObj[level] = [];
                       const existingQs = new Set(targetObj[level].map(item => item.q));
                       parsed[level].forEach(item => { if (!existingQs.has(item.q)) targetObj[level].push(item); });
                   }
               } catch (e) {
                   console.error("Local DB Parse Error:", e);
               }
           }
       };
       parseAndMerge("CUSTOM_SCI_DATABASE", SCI_DATABASE);
       parseAndMerge("CUSTOM_INFO_DATABASE", INFO_DATABASE);
       parseAndMerge("CUSTOM_MYST_DATABASE", MYST_DATABASE);
       parseAndMerge("CUSTOM_SOC_DATABASE", SOC_DATABASE);
       parseAndMerge("CUSTOM_JP_DATABASE", JP_DATABASE);
    }
    window.addEventListener('DOMContentLoaded', loadDatabaseFromLocal);

    let isFetchingAI = false;
    async function autoExpandDatabase(triggerCode) {
       if (!GEMINI_API_KEY || GEMINI_API_KEY.includes("ここに取得した") || isFetchingAI) return false;
       isFetchingAI = true;

       let dbMap = {
           "SCI": { name: "理科", db: SCI_DATABASE },
           "INFO": { name: "情報・セキュリティ・暗号技術", db: INFO_DATABASE },
           "MYST": { name: "論理的推論・探偵謎解き", db: MYST_DATABASE },
           "SOC": { name: "社会(歴史・地理・公民・生活科)", db: SOC_DATABASE },
           "JP": { name: "国語", db: JP_DATABASE }
       };
       
       const prefix = String(triggerCode).split("_")[0];
       if (!dbMap[prefix]) { isFetchingAI = false; return false; }

       let subjectName = dbMap[prefix].name;
       let levelNum = parseInt(String(triggerCode).split("_")[1], 10);
       let targetDB = dbMap[prefix].db;

       const promptText = `あなたはプロの教育者です。「${subjectName}」のレベル${levelNum}(小学1年〜大学相当)に応じた4択問題を新たに10〜20問作成し、以下のJSON配列フォーマットのみを出力してください。
    ※マークダウンや余計なテキストは一切含めず、純粋なJSON配列のみを返してください。
    [ { "q": "問題文", "a": "正解", "d": ["不正解1", "不正解2", "不正解3"], "h": "ヒント", "r": "解説文" } ]`;

       try {
           const statusEl = document.getElementById("brainwave-status");
           if(statusEl) { statusEl.textContent = "🔄 AIと通信中... メガデータを自動生成&格納中"; statusEl.style.color = "#fbbf24"; }

           const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GEMINI_API_KEY}`, {
               method: "POST", headers: { "Content-Type": "application/json" },
               body: JSON.stringify({ contents: [{ parts: [{ text: promptText }] }] })
           });

           const data = await response.json();
           let textResponse = data.candidates[0].content.parts[0].text;
           textResponse = textResponse.replace(/```json/g, "").replace(/```/g, "").trim();
           const newQuestions = JSON.parse(textResponse);

           if (!targetDB[levelNum]) targetDB[levelNum] = [];
           targetDB[levelNum].push(...newQuestions);
           saveDatabaseToLocal();
           console.log(`🤖 AIが${subjectName}(Lv.${levelNum})の新しい問題を ${newQuestions.length} 問追加しました!`);
           
           if(statusEl) {
               statusEl.textContent = isBossStage ? `⚠️ OVERRIDE: BOSS ENGAGED [LV: ${playerLevel}]` : `📡 脳波リンク:アクティブ [LV: ${playerLevel}]`;
               statusEl.style.color = isBossStage ? "#ef4444" : "#38bdf8";
           }
       } catch (error) {
           console.error("AI問題の自動生成に失敗:", error);
       } finally {
           isFetchingAI = false;
       }
    }

    // --- 🎵 サウンドエンジン ---
    let audioCtx = null;
    function initAudio() { if(!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } if(audioCtx.state === 'suspended') audioCtx.resume(); }
    function playTone(freq, type, duration, vol, endFreq = null) {
       if(!audioCtx) return; const osc = audioCtx.createOscillator(), gain = audioCtx.createGain();
       osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
       if(endFreq) osc.frequency.exponentialRampToValueAtTime(endFreq, audioCtx.currentTime + duration);
       gain.gain.setValueAtTime(vol, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
       osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + duration);
    }
    const sfx = {
       hover: () => playTone(1200, 'sine', 0.05, 0.05), click: () => playTone(800, 'sine', 0.05, 0.2), back: () => playTone(400, 'triangle', 0.1, 0.2, 200),
       selectGrade: () => { playTone(600, 'sine', 0.1, 0.2); setTimeout(()=>playTone(900, 'sine', 0.15, 0.2), 100); },
       hint: () => { playTone(1000, 'sine', 0.1, 0.1); setTimeout(()=>playTone(1500, 'sine', 0.2, 0.1), 100); }, tick: () => playTone(1500, 'sine', 0.05, 0.1),
       correct: (combo = 1) => { let base = Math.min(600 + (combo * 40), 1500); playTone(base, 'sine', 0.1, 0.3); setTimeout(()=>playTone(base * 1.5, 'sine', 0.2, 0.3), 100); },
       criticalCorrect: () => { playTone(800, 'square', 0.1, 0.2); setTimeout(()=>playTone(1200, 'square', 0.1, 0.2), 100); setTimeout(()=>playTone(1600, 'square', 0.3, 0.2), 200); },
       wrong: () => { playTone(150, 'sawtooth', 0.2, 0.4, 100); setTimeout(()=>playTone(100, 'sawtooth', 0.3, 0.4, 50), 150); },
       levelUp: () => { [400, 500, 600, 800, 1000, 1200].forEach((f,i)=>setTimeout(()=>playTone(f,'square',0.15,0.2), i*100)); },
       bossAlarm: () => { for(let i=0; i<6; i++){ setTimeout(()=>{playTone(250,'sawtooth',0.4,0.5, 150);}, i*500); } },
       bossHit: () => { playTone(100, 'square', 0.2, 0.5, 50); },
       bossDefeat: () => { [800, 1000, 1200, 1600, 2000].forEach((f,i)=>setTimeout(()=>playTone(f,'sine',0.15,0.4), i*120)); setTimeout(()=>playTone(400, 'sawtooth', 1.0, 0.4, 100), 600); },
       gameStart: () => { [500, 750, 1000, 1500].forEach((f,i)=>setTimeout(()=>playTone(f,'sine',0.2,0.3), i*120)); },
       result: () => { playTone(400, 'sine', 0.2, 0.3); setTimeout(()=>playTone(600, 'sine', 0.2, 0.3), 150); setTimeout(()=>playTone(1000, 'sine', 0.4, 0.3), 300); }
    };

    // --- ✨ パーティクルエンジン ---
    const fxCanvas = document.getElementById('fx-canvas'); const fxCtx = fxCanvas.getContext('2d');
    let particles = [];
    function resizeFx() { fxCanvas.width = window.innerWidth; fxCanvas.height = window.innerHeight; }
    window.addEventListener('resize', resizeFx); resizeFx();
    class Particle {
       constructor(x, y, color, isBurst) {
           this.x = x; this.y = y; const angle = Math.random() * Math.PI * 2; const speed = (Math.random() * 8 + 4) * (isBurst ? 1.5 : 1);
           this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; this.gravity = 0.2; this.life = 1.0; this.decay = Math.random() * 0.02 + 0.015;
           this.color = color; this.size = Math.random() * 5 + 3;
       }
       update() { this.x += this.vx; this.y += this.vy; this.vy += this.gravity; this.life -= this.decay; }
       draw(ctx) { ctx.globalAlpha = Math.max(0, this.life); ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; }
    }
    function spawnParticles(x, y, isBurst) { const colors = isBurst ? ['#fbbf24', '#f59e0b', '#ef4444', '#ffffff'] : ['#34d399', '#10b981', '#ffffff']; const count = isBurst ? 80 : 30; for(let i=0; i<count; i++) particles.push(new Particle(x, y, colors[Math.random()*colors.length|0], isBurst)); }
    function animateFx() { fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height); for(let i=particles.length-1; i>=0; i--) { particles[i].update(); particles[i].draw(fxCtx); if(particles[i].life <= 0) particles.splice(i, 1); } requestAnimationFrame(animateFx); }
    animateFx();

    // --- 🎮 メインロジック ---
    const quizScreen = document.getElementById('quiz-screen'), scoreScreen = document.getElementById('score-screen');
    const progressEl = document.getElementById('progress'), questionEl = document.getElementById('question-text');
    const optionsContainer = document.getElementById('options-container'), resultEl = document.getElementById('result-message');
    const hintButton = document.getElementById('hint-button'), skipBtn = document.getElementById('skip-button'), terminateBtn = document.getElementById('terminate-button');
    const hintTextDisplay = document.getElementById('hint-text-display'), explanationTextDisplay = document.getElementById('explanation-text-display');
    const scoreTextEl = document.getElementById('score-text'), rankDisplay = document.getElementById('rank-display');
    const reviewCardsList = document.getElementById('review-cards-list'), setupTimerZone = document.getElementById('setup-timer-zone'), reviewRetryZone = document.getElementById('review-retry-zone');

    let currentQuestion = null, questionCount = 0, maxQuestions = Infinity;
    let score = 0, canAnswer = true, currentPhase = "GRADE_SELECT";
    let selectedLevelText = "", currentTriggerCode = null, hintClickCount = 0;
    let autoPageInterval = null, secondsToNext = 0;
    let startTimeSession = 0, totalHintUsedCount = 0, maxStreakLog = 0;
    let totalExpPoints = 0, bWaveCanvas = null, bWaveCtx = null, bWaveAnimId = null, bWaveStep = 0;
    let isTypesetting = false;
    let playerLevel = 10, streakCount = 0, reviewQuizData = [], isReviewMode = false, reviewIndex = 0;
    let isBossStage = false, previousTheme = "";
    let askedQuestions = new Set();
    let terminateConfirmStep = false, terminateTimeout = null;

    function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
    function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const temp = array[i]; array[i] = array[j]; array[j] = temp; } }
    function changeBackgroundTheme(themeName, element) { if(isBossStage) return; document.body.className = "bg-" + themeName; document.querySelectorAll('.theme-tab').forEach(tab => tab.classList.remove('active-theme')); if(element) element.classList.add('active-theme'); }
    function updateMathJaxContent(elementsArray) { if (isTypesetting) return; if (window.MathJax && MathJax.typesetPromise) { isTypesetting = true; MathJax.typesetPromise(elementsArray).then(() => { isTypesetting = false; }).catch((err) => { console.log(err); isTypesetting = false; }); } }
    function processAndShuffleOptions(rawArray, correctValueText) { const uniqueOptions = [...new Set(rawArray)].slice(0, 4); if (uniqueOptions.indexOf(correctValueText) === -1) uniqueOptions[0] = correctValueText; shuffleArray(uniqueOptions); return { options: uniqueOptions, answerIndex: uniqueOptions.indexOf(correctValueText) }; }
    function makeFastOptions(correctVal, step = 1) { const uniqueVals = new Set([correctVal]); let offsets = [step, -step, step * 2, -step * 2, step * 3]; for (let offset of offsets) { let v = correctVal + offset; if (v !== correctVal && !uniqueVals.has(v)) uniqueVals.add(v); if (uniqueVals.size === 4) break; } let arr = Array.from(uniqueVals).map(v => String(v)); const ansText = arr[0]; shuffleArray(arr); return { options: arr, answerIndex: arr.indexOf(ansText) }; }

    function getPoolForLevel(db, requestedLevel) {
       let availableLevels = Object.keys(db).map(Number).sort((a, b) => a - b);
       let targetLevel = availableLevels.find(l => l >= requestedLevel) || availableLevels[availableLevels.length - 1];
       return db[targetLevel] || [];
    }

    function generateSingleQuestion(triggerCode) {
       let mult = isBossStage ? 4 : 1;
       let qPrefix = isBossStage ? "💀 BOSS STAGE " : "";
       let effectiveCode = triggerCode;
       let effectivePrefix = String(effectiveCode).split("_")[0];
       let levelNum = parseInt(String(effectiveCode).split("_")[1], 10) || 1;

       if (isBossStage && ["SCI", "INFO", "MYST", "SOC", "JP"].includes(effectivePrefix)) {
           levelNum = Math.min(14, levelNum + 1); effectiveCode = `${effectivePrefix}_${levelNum}`;
       }

       if (["INFO", "MYST", "SCI", "SOC", "JP"].includes(effectivePrefix)) {
           let dbMap = { "INFO": INFO_DATABASE, "MYST": MYST_DATABASE, "SCI": SCI_DATABASE, "SOC": SOC_DATABASE, "JP": JP_DATABASE };
           let nameMap = { "INFO": "情報・セキュリティ", "MYST": "推理・謎解き", "SCI": "理科", "SOC": "社会", "JP": "国語" };
           let pool = getPoolForLevel(dbMap[effectivePrefix], levelNum);
           if(pool.length > 0) {
               let item = pool[getRandomInt(0, pool.length - 1)];
               let opt = processAndShuffleOptions([item.a, ...item.d], item.a);
               return {
                   question: `【${qPrefix}${nameMap[effectivePrefix]}】<br>${item.q}`,
                   options: opt.options.map(String), answer: opt.answerIndex,
                   hints: [item.h || "落ち着いて考えましょう。"], rationale: item.r ? `【解説】<br>${item.r}` : `【解説】正解は「${item.a}」です。`
               };
           }
       }

       // 算数・数学の学年別強化ロジック
       if (["MATH", "KUKU"].includes(effectivePrefix)) {
           let questionText = ""; let ans = 0; let hints = []; let rationale = "";

           if (levelNum === 1) { // 小1: 1桁の計算
               let isAdd = Math.random() > 0.5; let valA = getRandomInt(1, 9); let valB = getRandomInt(1, 9);
               if (isAdd) { ans = valA + valB; questionText = `【算数】<br>${valA} + ${valB} = ?`; hints = ["ゆびをつかって かぞえてみよう。"]; rationale = `【解説】${valA} + ${valB} = ${ans} です。`; }
               else { if (valA < valB) { let temp = valA; valA = valB; valB = temp; } ans = valA - valB; questionText = `【算数】<br>${valA} - ${valB} = ?`; hints = ["ひきざんだよ。のこりはいくつかな?"]; rationale = `【解説】${valA} - ${valB} = ${ans} です。`; }
           } else if (levelNum === 2 || effectivePrefix === "KUKU") { // 小2: 九九、2桁の計算
               if (Math.random() > 0.4 || effectivePrefix === "KUKU") { let valA = getRandomInt(2, 9); let valB = getRandomInt(1, 9); ans = valA * valB; questionText = `【算数(九九)】<br>${valA} × ${valB} = ?`; hints = [`${valA}のだんの九九をおもいだそう。`]; rationale = `【解説】${valA} × ${valB} = ${ans} です。`; }
               else { let valA = getRandomInt(10, 99); let valB = getRandomInt(10, 99); ans = valA + valB; questionText = `【算数】<br>${valA} + ${valB} = ?`; hints = ["ひっ算をかいて けいさんしよう。"]; rationale = `【解説】${valA} + ${valB} = ${ans} です。`; }
           } else if (levelNum === 3) { // 小3: 割り算、3桁の計算
               if (Math.random() > 0.5) { let valB = getRandomInt(2, 9); let ansTemp = getRandomInt(2, 9); let valA = valB * ansTemp; ans = ansTemp; questionText = `【算数(わり算)】<br>${valA} ÷ ${valB} = ?`; hints = [`${valB}に何をかけると${valA}になるかな?`]; rationale = `【解説】${valA} ÷ ${valB} = ${ans} です。`; }
               else { let valA = getRandomInt(100, 999); let valB = getRandomInt(100, 999); ans = valA + valB; questionText = `【算数】<br>${valA} + ${valB} = ?`; hints = ["ひっ算で、くらいをそろえて計算しよう。"]; rationale = `【解説】${valA} + ${valB} = ${ans} です。`; }
           } else if (levelNum >= 4 && levelNum <= 6) { // 小4〜小6: 複雑な四則演算
                let valA = getRandomInt(10, 50); let valB = getRandomInt(5, 20); let valC = getRandomInt(2, 10); ans = valA * valB - valC;
                questionText = `【算数】<br>${valA} × ${valB} - ${valC} = ?`; hints = ["かけ算を先に計算します。"]; rationale = `【解説】${valA} × ${valB} = ${valA * valB}。<br>そこから ${valC} を引いて ${ans} です。`;
           } else if (levelNum >= 7 && levelNum <= 10) { // 中学: 負の数
                let valA = getRandomInt(-20, 20); let valB = getRandomInt(-20, 20); let valC = getRandomInt(1, 10); ans = valA * valB + valC;
                questionText = `【数学】<br>(${valA}) × (${valB}) + ${valC} = ?`; hints = ["負の数のかけ算の符号に注意しましょう。"]; rationale = `【解説】(${valA}) × (${valB}) = ${valA * valB}。<br>これに ${valC} を足して ${ans} です。`;
           } else { // 高校・大学: 累乗
                let valA = getRandomInt(2, 5); let valB = getRandomInt(2, 4); let valC = getRandomInt(1, 5); ans = Math.pow(valA, valB) + valC;
                questionText = `【数学】<br>${valA}^{${valB}} + ${valC} = ? (※ ${valA}の${valB}乗 + ${valC})`; hints = ["累乗を先に計算します。"]; rationale = `【解説】${valA}の${valB}乗は ${Math.pow(valA, valB)} です。<br>これに ${valC} を足して ${ans} となります。`;
           }

           if (isBossStage) {
               let valA = getRandomInt(10, 30 + levelNum * 3); let valB = getRandomInt(5, 20 + levelNum * 2); let valC = getRandomInt(10, 100) * levelNum; ans = (valA * valB) + valC;
               questionText = `【${qPrefix}極限計算】<br>( ${valA} × ${valB} ) + ${valC} = ?`; hints = ["掛け算を先に計算します。"]; rationale = `【解説】${valA} × ${valB} = ${valA * valB}。これに ${valC} を足して ${ans} となります。`;
           }

           let opt = makeFastOptions(ans, Math.max(1, Math.floor(Math.abs(ans) * 0.1) || 1));
           return { question: questionText, options: opt.options.map(String), answer: opt.answerIndex, hints: hints, rationale: rationale };
       }
    }

    // 💡 変更ポイント: AI生成の同期および非同期管理を含んだ取得関数
    async function getUniqueQuestion(triggerCode) {
       let q; let attempts = 0; let effectiveCode = triggerCode; let effectivePrefix = String(triggerCode).split("_")[0]; let levelNum = parseInt(String(effectiveCode).split("_")[1], 10) || 1;
       
       if (["SCI", "INFO", "MYST", "SOC", "JP"].includes(effectivePrefix)) {
           let dbMap = { "SCI": SCI_DATABASE, "INFO": INFO_DATABASE, "MYST": MYST_DATABASE, "SOC": SOC_DATABASE, "JP": JP_DATABASE };
           let pool = getPoolForLevel(dbMap[effectivePrefix], levelNum);
           
           let usedCount = 0;
           // 当該カテゴリ・レベルの問題で既に出題済みのものをカウント
           pool.forEach(item => {
               if (Array.from(askedQuestions).some(asked => asked.includes(item.q))) usedCount++;
           });
           
           let remainingCount = pool.length - usedCount;

           // 💡 追加: 問題ストックが残り10問になったらバックグラウンドで補充
           if (remainingCount <= 10 && remainingCount > 0 && !isFetchingAI) {
               console.log(`問題ストック(${effectivePrefix} Lv.${levelNum})が残り${remainingCount}問です。AI自動拡張をバックグラウンドで開始します...`);
               autoExpandDatabase(effectiveCode); // awaitせず裏で走らせる
           }

           // 💡 追加: 残り0問になったら、生成を待機してから出題する
           if (remainingCount === 0) {
               const hasValidKey = GEMINI_API_KEY && !GEMINI_API_KEY.includes("ここに取得した");
               if (hasValidKey) {
                   console.log(`問題ストック(${effectivePrefix} Lv.${levelNum})が枯渇しました。AI生成完了を待機します...`);
                   const statusEl = document.getElementById("brainwave-status");
                   if (statusEl) { statusEl.textContent = "⏳ AIが新しい問題を生成中..."; statusEl.style.color = "#fbbf24"; }
                   
                   if (isFetchingAI) {
                       // 既に別のトリガーで生成中なら完了を待つ
                       await new Promise(resolve => {
                           const check = setInterval(() => {
                               if (!isFetchingAI) { clearInterval(check); resolve(); }
                           }, 500);
                       });
                   } else {
                       // 新規で生成を開始し、完了を待つ
                       await autoExpandDatabase(effectiveCode);
                   }
               } else {
                   console.log("APIキーが設定されていないため、既出問題を再利用します。");
               }
           }
       }
       
       do {
           q = generateSingleQuestion(triggerCode);
           attempts++;
           if (attempts > 50) break;
       } while (askedQuestions.has(q.question));
       
       askedQuestions.add(q.question);
       return q;
    }

    function startBrainwaveSimulator() {
       stopBrainwaveSimulator(); document.getElementById("brainwave-zone").style.display = "block";
       bWaveCanvas = document.getElementById("brainwaveCanvas"); if(!bWaveCanvas) return;
       bWaveCtx = bWaveCanvas.getContext("2d"); bWaveCanvas.width = bWaveCanvas.offsetWidth; bWaveCanvas.height = 40;
       function render() {
           if(!bWaveCtx) return; bWaveCtx.clearRect(0, 0, bWaveCanvas.width, bWaveCanvas.height); bWaveStep += 0.05;
           let intensity = 1 + (streakCount * 1.5) + (isBossStage ? 10 : 0);
           let statusText = isBossStage ? `⚠️ OVERRIDE: BOSS ENGAGED [LV: ${playerLevel}]` : streakCount >= 5 ? `📡 脳波オーバードライブ [LV: ${playerLevel} | 連勝: ${streakCount}]` : `📡 脳波リンク:アクティブ [LV: ${playerLevel}]`;
           document.getElementById("brainwave-status").textContent = statusText; document.getElementById("brainwave-status").style.color = isBossStage ? "#ef4444" : "#38bdf8";
           drawWave(isBossStage?"#dc2626":"#10b981", 0.02, 8 * intensity, 20); drawWave(isBossStage?"#b91c1c":"#3b82f6", 0.04, 5 * intensity, 20); drawWave(isBossStage?"#f87171":"#ec4899", 0.08, 3 * intensity, 20);
           bWaveAnimId = requestAnimationFrame(render);
       }
       function drawWave(color, freq, amp, yOffset) {
           bWaveCtx.beginPath(); bWaveCtx.strokeStyle = color; bWaveCtx.lineWidth = 1.5;
           for (let x = 0; x < bWaveCanvas.width; x++) { let y = Math.sin(x * freq + bWaveStep) * amp + yOffset; if (x === 0) bWaveCtx.moveTo(x, y); else bWaveCtx.lineTo(x, y); }
           bWaveCtx.stroke();
       }
       render();
    }
    function stopBrainwaveSimulator() { if(bWaveAnimId) { cancelAnimationFrame(bWaveAnimId); bWaveAnimId = null; } document.getElementById("brainwave-zone").style.display = "none"; }

    function terminateQuiz() {
       sfx.click();
       const tBtn = document.getElementById('terminate-button');
       if (!terminateConfirmStep) {
           terminateConfirmStep = true;
           if (tBtn) {
               tBtn.textContent = "⚠️ 本当に終了?(タップ)";
               tBtn.style.background = "linear-gradient(135deg, #b91c1c, #7f1d1d)";
               if(terminateTimeout) clearTimeout(terminateTimeout);
               terminateTimeout = setTimeout(() => {
                   terminateConfirmStep = false;
                   if (tBtn) { tBtn.textContent = "🛑 終了"; tBtn.style.background = ""; }
               }, 3000);
           }
       } else {
           terminateConfirmStep = false;
           if(terminateTimeout) clearTimeout(terminateTimeout);
           sfx.result(); showFinalResults();
       }
    }

    function initApp() {
       stopAutoTimer(); stopBrainwaveSimulator(); isBossStage = false; document.body.className = "bg-wood"; canAnswer = true;
       if (scoreScreen) scoreScreen.classList.remove('active-screen'); if (quizScreen) quizScreen.classList.add('active-screen');
       optionsContainer.innerHTML = ""; optionsContainer.className = "subject-menu"; currentPhase = "GRADE_SELECT";
       playerLevel = 10; streakCount = 0; totalExpPoints = 0; totalHintUsedCount = 0; maxStreakLog = 0; questionCount = 0; reviewQuizData = []; isReviewMode = false; reviewIndex = 0; score = 0; startTimeSession = Date.now(); maxQuestions = Infinity;
       
       terminateConfirmStep = false; if(terminateTimeout) clearTimeout(terminateTimeout);
       if (hintButton) hintButton.style.visibility = "hidden";
       if (skipBtn) skipBtn.style.visibility = "hidden";
       if (terminateBtn) terminateBtn.style.visibility = "hidden";

       if (progressEl) { progressEl.innerHTML = `<span>⚙️ システム起動完了</span>`; progressEl.className = "progress"; }
       if (setupTimerZone) setupTimerZone.innerHTML = "";
       if (questionEl) { questionEl.innerHTML = "最初に、設定を確認して勉強したい学年を選んでください。<br><span style='font-size:12px; color:#ef4444;'>※足りない問題はAIが自動生成・補充します。</span>"; questionEl.className = "question"; }

       const gradeMap = [{ text: "🎒 小1", level: 1 }, { text: "🎒 小2", level: 2 }, { text: "🎒 小3", level: 3 }, { text: "🎒 小4", level: 4 }, { text: "🎒 小5", level: 5 }, { text: "🎒 小6", level: 6 }, { text: "🏫 中学受験", level: 7 }, { text: "🎓 中1", level: 8 }, { text: "🎓 中2", level: 9 }, { text: "🎓 中3", level: 10 }, { text: "📚 高1", level: 11 }, { text: "📚 高2", level: 12 }, { text: "🏛️ 高3/共通", level: 13 }, { text: "🔥 大学/難問", level: 14 }];
       gradeMap.forEach(g => {
           const btn = document.createElement("button"); btn.className = "btn option-btn"; btn.textContent = g.text;
           btn.onmouseenter = () => { initAudio(); sfx.hover(); }; btn.onclick = () => { initAudio(); sfx.selectGrade(); loadSubjectMenu(g.level, g.text); }; optionsContainer.appendChild(btn);
       });
       if (resultEl) { resultEl.innerHTML = ""; resultEl.className = "result"; } updateMathJaxContent([questionEl, optionsContainer]);
    }

    function loadSubjectMenu(levelNum, gradeText) {
       if (setupTimerZone) setupTimerZone.innerHTML = `<div class="timer-setting-box">⏱️ タイマー: <input type="number" id="input-timer-seconds" class="timer-input" min="0" value="0"> 秒<br>🎯 問題数: <select id="input-question-count" class="timer-input" style="width: 90px; height: 35px;"><option value="10">10問</option><option value="20">20問</option><option value="50">50問</option><option value="999999">エンドレス(∞)</option></select></div>`;
       optionsContainer.innerHTML = ""; optionsContainer.className = "subject-menu"; currentPhase = "SUBJECT_SELECT";
       questionEl.innerHTML = `<strong>${gradeText}</strong> の設定を行い、教科を選択してください。`;

       const createBtn = (cls, text, code) => {
           const btn = document.createElement("button"); btn.className = `btn subject-btn ${cls}`; btn.textContent = text;
           btn.onmouseenter = () => { initAudio(); sfx.hover(); }; btn.onclick = () => { sfx.click(); readTimerAndStart(code, `${gradeText} ${text}`); }; return btn;
       };
       
       let mathCode = levelNum === 2 ? "KUKU_2" : `MATH_${levelNum}`;
       optionsContainer.appendChild(createBtn("btn-math", levelNum >= 8 ? "🧮 数学" : "🧮 算数", mathCode));
       optionsContainer.appendChild(createBtn("btn-jp", "📖 国語", `JP_${levelNum}`));
       optionsContainer.appendChild(createBtn("btn-sci", "🧪 理科", `SCI_${levelNum}`));
       let socName = levelNum <= 2 ? "🌻 生活科" : "🌍 社会";
       optionsContainer.appendChild(createBtn("btn-soc", socName, `SOC_${levelNum}`));
       optionsContainer.appendChild(createBtn("btn-info", "💻 情報・暗号", `INFO_${levelNum}`));
       optionsContainer.appendChild(createBtn("btn-myst", "🕵️ 推理・謎解き", `MYST_${levelNum}`));

       const backBtn = document.createElement("button"); backBtn.className = "btn full-width"; backBtn.style.background = "#64748b";
       backBtn.textContent = "⬅️ 学年選択に戻る"; backBtn.onmouseenter = () => { initAudio(); sfx.hover(); };
       backBtn.onclick = () => { sfx.back(); initApp(); }; optionsContainer.appendChild(backBtn);
    }

    function readTimerAndStart(triggerCode, labelText) {
       const timerInputEl = document.getElementById('input-timer-seconds'); secondsToNext = timerInputEl && !isNaN(parseInt(timerInputEl.value)) ? Math.max(0, parseInt(timerInputEl.value)) : 0;
       const qCountEl = document.getElementById('input-question-count'); maxQuestions = qCountEl ? parseInt(qCountEl.value) : 10;
       if (setupTimerZone) setupTimerZone.innerHTML = ""; startQuizSession(triggerCode, labelText);
    }

    function startQuizSession(triggerCode, labelText) {
       sfx.gameStart(); selectedLevelText = labelText; currentPhase = "QUIZ"; currentTriggerCode = triggerCode;
       isReviewMode = false; questionCount = 0; reviewQuizData = []; askedQuestions.clear(); startTimeSession = Date.now(); startBrainwaveSimulator(); nextQuestion();
    }

    function handleOptionClick(selectedIndex, e) {
       if (!canAnswer) return; checkAnswer(selectedIndex);
       if(e && e.target && streakCount > 0) { const rect = e.target.getBoundingClientRect(); spawnParticles(rect.left + rect.width/2, rect.top + rect.height/2, streakCount >= 3 || isBossStage); }
    }

    function checkAnswer(selectedIndex) {
       canAnswer = false; stopAutoTimer(); const optionButtons = optionsContainer.querySelectorAll('.btn'); const isCorrect = (selectedIndex === currentQuestion.answer);
       currentQuestion.userAnswerIndex = selectedIndex; currentQuestion.isUserCorrect = isCorrect;
       
       terminateConfirmStep = false; if(terminateTimeout) clearTimeout(terminateTimeout);

       if (isCorrect) {
           score++; streakCount++; if(isBossStage) sfx.bossDefeat(); else if(streakCount >= 3) sfx.criticalCorrect(); else sfx.correct(streakCount);
           if (streakCount > 0 && streakCount % 5 === 0) { playerLevel += 1; setTimeout(()=>sfx.levelUp(), 400); }
           let comboMultiplier = streakCount >= 3 ? 3 : streakCount >= 2 ? 2 : 1; totalExpPoints += (100 * comboMultiplier * (isBossStage?2:1));
           if (streakCount > maxStreakLog) maxStreakLog = streakCount;
           if (resultEl) { resultEl.innerHTML = `⚡ クリティカル!! +${100 * comboMultiplier * (isBossStage?2:1)} EXP [コンボ x${comboMultiplier}]`; resultEl.className = "result correct"; }
           if (optionButtons[selectedIndex]) { optionButtons[selectedIndex].style.backgroundColor = "#10b981"; optionButtons[selectedIndex].style.transform = "scale(1.05)"; }
       } else {
           streakCount = 0; if(isBossStage) sfx.bossHit(); else sfx.wrong();
           if (resultEl) { resultEl.innerHTML = `❌ 正解: 「 ${currentQuestion.options[currentQuestion.answer]} 」`; resultEl.className = "result wrong"; }
           if (optionButtons[selectedIndex]) optionButtons[selectedIndex].style.backgroundColor = "#ef4444";
           if (optionButtons[currentQuestion.answer]) optionButtons[currentQuestion.answer].style.backgroundColor = "#10b981";
           if(!isReviewMode) reviewQuizData.push({...currentQuestion});
       }
       
       if (explanationTextDisplay) { explanationTextDisplay.innerHTML = currentQuestion.rationale.replace(/\n/g, "<br>"); explanationTextDisplay.style.display = "block"; }
       if (hintButton) hintButton.style.visibility = "hidden"; if (hintTextDisplay) hintTextDisplay.style.display = "none";
       let isFinished = isReviewMode ? (reviewIndex >= reviewQuizData.length) : (questionCount >= maxQuestions);
       let isCheckpoint = !isReviewMode && questionCount > 0 && (questionCount % 10 === 0) && !isFinished;
       
       if (isFinished) {
           if (skipBtn) { skipBtn.textContent = "🏆 結果発表へ ➔"; skipBtn.onclick = () => { sfx.result(); showFinalResults(); }; skipBtn.style.visibility = "visible"; }
           if (terminateBtn && !isReviewMode) { terminateBtn.textContent = "⚔️ このまま続ける"; terminateBtn.onclick = () => { maxQuestions = Infinity; terminateBtn.style.background = ""; sfx.click(); nextQuestion(); }; terminateBtn.style.visibility = "visible"; terminateBtn.style.background = "linear-gradient(135deg, #10b981, #047857)"; } else if (terminateBtn) { terminateBtn.style.visibility = "hidden"; }
       } else if (isCheckpoint) {
           if (skipBtn) { skipBtn.textContent = "⚔️ このまま続ける ➔"; skipBtn.onclick = () => { sfx.click(); nextQuestion(); }; skipBtn.style.visibility = "visible"; }
           if (terminateBtn) { terminateBtn.textContent = "🛑 ここで終了する"; terminateBtn.onclick = () => { sfx.result(); showFinalResults(); }; terminateBtn.style.visibility = "visible"; terminateBtn.style.background = ""; }
       } else {
           if (skipBtn) { skipBtn.textContent = "次のステージへ ➔"; skipBtn.onclick = () => { sfx.click(); nextQuestion(); }; skipBtn.style.visibility = "visible"; }
           if (terminateBtn) { terminateBtn.textContent = "🛑 終了"; terminateBtn.onclick = terminateQuiz; terminateBtn.style.visibility = "visible"; terminateBtn.style.background = ""; }
       }
       updateMathJaxContent([resultEl, explanationTextDisplay]); startAutoTimer(isFinished, isCheckpoint);
    }

    // 💡 変更ポイント: async に変更し、データ取得中の待機状態をサポート
    async function nextQuestion() {
       stopAutoTimer();
       if(!isReviewMode) questionCount++;
       canAnswer = false;
       hintClickCount = 0;
       
       terminateConfirmStep = false; if(terminateTimeout) clearTimeout(terminateTimeout);
       if (resultEl) { resultEl.innerHTML = ""; resultEl.className = "result"; }
       if (hintTextDisplay) { hintTextDisplay.innerHTML = ""; hintTextDisplay.style.display = "none"; }
       if (explanationTextDisplay) { explanationTextDisplay.innerHTML = ""; explanationTextDisplay.style.display = "none"; }
       
       if (terminateBtn) { terminateBtn.textContent = "🛑 終了"; terminateBtn.style.background = ""; terminateBtn.style.visibility = "visible"; terminateBtn.onclick = terminateQuiz; }
       if (hintButton) { hintButton.textContent = "💡 ヒント表示"; hintButton.style.visibility = "visible"; }
       if (skipBtn) { skipBtn.style.visibility = "hidden"; skipBtn.onclick = null; }
       if (optionsContainer) optionsContainer.className = "options-list";
       
       if (isReviewMode) {
           if (reviewIndex < reviewQuizData.length) { currentQuestion = reviewQuizData[reviewIndex]; reviewIndex++; } else { initApp(); return; }
           canAnswer = true;
       } else {
           if (questionCount > 0 && (questionCount % 10 === 0 || questionCount === maxQuestions)) {
               isBossStage = true; previousTheme = document.body.className; document.body.className = "bg-boss"; sfx.bossAlarm();
               const wOverlay = document.getElementById("warning-overlay"); wOverlay.classList.add("active"); document.getElementById("main-container").classList.add("shake");
               setTimeout(()=>{ wOverlay.classList.remove("active"); document.getElementById("main-container").classList.remove("shake"); }, 2500);
           }
           
           // 取得中のUI(ローディング中)
           if (questionEl) {
               questionEl.innerHTML = "🌀 次のデータを準備中...";
               questionEl.className = isBossStage ? "question boss-text" : "question";
           }
           if (optionsContainer) optionsContainer.innerHTML = "";
           
           // 新しいロジックによって問題を待機して取得する
           currentQuestion = await getUniqueQuestion(currentTriggerCode);
           canAnswer = true;
       }
       
       const modeBadge = isReviewMode ? "復習モード" : selectedLevelText;
       let streakHtml = streakCount >= 1 ? ` <span class="streak-badge">🔥 ${streakCount} 連勝</span>` : ""; let lvlHtml = ` <span class="level-badge">⭐ LV ${playerLevel}</span>`;
       let qCountText = isReviewMode ? `${reviewIndex} / ${reviewQuizData.length}` : (maxQuestions > 9999 ? `${questionCount} / ∞` : `${questionCount} / ${maxQuestions}`);
       
       if (progressEl) {
           progressEl.innerHTML = `<span>[${modeBadge}] ステージ ${qCountText}</span>` + streakHtml + lvlHtml;
           if(isBossStage) { progressEl.classList.add('boss-ui'); questionEl.className = "question boss-text"; } else { progressEl.classList.remove('boss-ui'); questionEl.className = "question"; }
       }
       
       if (questionEl) questionEl.innerHTML = currentQuestion.question.replace(/\n/g, "<br>");
       if (optionsContainer) {
           optionsContainer.innerHTML = "";
           currentQuestion.options.forEach((optText, index) => {
               const btn = document.createElement("button"); btn.className = "btn option-btn"; btn.innerHTML = optText;
               btn.onmouseenter = () => { initAudio(); sfx.hover(); }; btn.onclick = (e) => handleOptionClick(index, e); optionsContainer.appendChild(btn);
           });
       }
       updateMathJaxContent([questionEl, optionsContainer, progressEl]);
    }

    function startAutoTimer(isFinished, isCheckpoint) {
       stopAutoTimer(); if (isCheckpoint || secondsToNext <= 0) return; let timeLeft = secondsToNext; let btnBaseText = isFinished ? "結果発表へ" : "次のステージへ";
       if (skipBtn) skipBtn.textContent = `${btnBaseText} (${timeLeft}秒) ➔`;
       autoPageInterval = setInterval(() => {
           timeLeft--; if (timeLeft <= 3 && timeLeft > 0) sfx.tick();
           if (timeLeft <= 0) { stopAutoTimer(); if (isFinished) { sfx.result(); showFinalResults(); } else { sfx.click(); nextQuestion(); } } else { if (skipBtn) skipBtn.textContent = `${btnBaseText} (${timeLeft}秒) ➔`; }
       }, 1000);
    }
    function stopAutoTimer() { if (autoPageInterval) { clearInterval(autoPageInterval); autoPageInterval = null; } }

    function drawAIRadarChart(scoreVal, totalTimeSec, hintsVal, streakVal) {
       let qCount = isReviewMode ? reviewQuizData.length : questionCount; if(qCount === 0) qCount = 1;
       let speedParam = Math.max(20, Math.min(100, Math.round(100 - (totalTimeSec / qCount) * 4))); let accuracyParam = Math.round((scoreVal / qCount) * 100);
       let staminaParam = Math.max(30, Math.min(100, Math.round(40 + (streakVal * 12)))); let knowledgeParam = Math.max(15, Math.min(100, Math.round(accuracyParam * 0.8 + (qCount - hintsVal) * 2))); let intuitionParam = Math.max(20, Math.min(100, Math.round(100 - (hintsVal * 15))));
       const center = 140, radius = 90; const angles = [0, 72, 144, 216, 288]; const params = [speedParam, accuracyParam, staminaParam, knowledgeParam, intuitionParam]; const labels = ["思考速度", "正確性", "粘り強さ", "知識量", "直感力"];
       let points = []; let svgContent = `<circle cx="${center}" cy="${center}" r="${radius}" class="radar-grid" /><circle cx="${center}" cy="${center}" r="${radius * 0.6}" class="radar-grid" /><circle cx="${center}" cy="${center}" r="${radius * 0.3}" class="radar-grid" />`;
       for (let i = 0; i < 5; i++) {
           let angleRad = (angles[i] - 90) * Math.PI / 180; let ax = center + radius * Math.cos(angleRad), ay = center + radius * Math.sin(angleRad);
           svgContent += `<line x1="${center}" y1="${center}" x2="${ax}" y2="${ay}" class="radar-axis" />`;
           let lx = center + (radius + 20) * Math.cos(angleRad), ly = center + (radius + 15) * Math.sin(angleRad) + 4;
           svgContent += `<text x="${lx}" y="${ly}" class="radar-label">${labels[i]}</text>`;
           let px = center + (radius * (params[i] / 100)) * Math.cos(angleRad), py = center + (radius * (params[i] / 100)) * Math.sin(angleRad); points.push(`${px},${py}`);
       }
       svgContent += `<polygon points="${points.join(' ')}" class="radar-polygon" />`; return `<div class="radar-chart-container"><h3>🔍 AI 能力分析</h3><svg class="radar-svg" viewBox="0 0 280 280">${svgContent}</svg></div>`;
    }

    function showFinalResults() {
       stopAutoTimer(); stopBrainwaveSimulator(); currentPhase = "RESULT_SCREEN"; document.getElementById("warning-overlay").classList.remove("active"); document.getElementById("main-container").classList.remove("shake");
       if (quizScreen) quizScreen.classList.remove('active-screen'); if (scoreScreen) scoreScreen.classList.add('active-screen');
       let qCount = isReviewMode ? reviewQuizData.length : questionCount;
       if (scoreTextEl) scoreTextEl.innerHTML = `ミッション完了! 正解数: ${score} / ${qCount}<br><span style="color:#d97706;font-size:20px;font-family:'Share Tech Mono', monospace;font-weight:bold;">🏆 獲得スコア: ${totalExpPoints} EXP [最終LV: ${playerLevel}]</span>`;
       let rankTitle = score >= (qCount*0.8) ? "👑 伝説の数理神 👑" : "🧙 優秀な挑戦者 🧙";
       let totalTimeSec = Math.round((Date.now() - startTimeSession) / 1000); let radarHtml = drawAIRadarChart(score, totalTimeSec, totalHintUsedCount, maxStreakLog);
       if (rankDisplay) rankDisplay.innerHTML = `<div class="rank-box">${rankTitle}</div>` + radarHtml;
       if (reviewRetryZone) {
           reviewRetryZone.innerHTML = "";
           const shareBtn = document.createElement("button"); shareBtn.className = "btn btn-share"; shareBtn.innerHTML = "📝 結果をコピー (ブログ・note用)";
           shareBtn.onclick = () => { sfx.click(); const resultText = `【楽しく勉強ができるクイズ(全学年対応)】\n挑戦カテゴリ: ${selectedLevelText}\nスコア: ${score} / ${qCount}\n獲得EXP: ${totalExpPoints}\n最終レベル: ${playerLevel}\n思考速度/正確性/知識量などのAI分析付き!\n#勉強クイズ`; navigator.clipboard.writeText(resultText).then(() => { shareBtn.innerHTML = "✅ コピーしました!"; setTimeout(() => shareBtn.innerHTML = "📝 結果をコピー (ブログ・note用)", 2000); }); }; reviewRetryZone.appendChild(shareBtn);
           if (reviewQuizData.length > 0 && !isReviewMode) {
               const retryBtn = document.createElement("button"); retryBtn.className = "btn review-retry-btn"; retryBtn.style.marginTop = "10px";
               retryBtn.textContent = `❌ 間違えた問題 (${reviewQuizData.length}問) に再挑戦`; retryBtn.onmouseenter = () => { initAudio(); sfx.hover(); }; retryBtn.onclick = () => { sfx.gameStart(); startReviewQuiz(); }; reviewRetryZone.appendChild(retryBtn);
           }
       }
       buildReviewList();
    }

    function startReviewQuiz() { isReviewMode = true; reviewIndex = 0; score = 0; canAnswer = true; streakCount = 0; if (skipBtn) { skipBtn.style.visibility = "visible"; skipBtn.onclick = () => { sfx.click(); nextQuestion(); }; } if (terminateBtn) { terminateBtn.style.visibility = "visible"; terminateBtn.onclick = terminateQuiz; } if (quizScreen) quizScreen.classList.add('active-screen'); if (scoreScreen) scoreScreen.classList.remove('active-screen'); startBrainwaveSimulator(); nextQuestion(); }
    function buildReviewList() { if (!reviewCardsList) return; reviewCardsList.innerHTML = ""; for (let i = 0; i < reviewQuizData.length; i++) { const item = reviewQuizData[i]; if(item.userAnswerIndex === undefined) continue; const card = document.createElement('div'); card.className = "review-card"; card.innerHTML = `<div class="review-q-text">${item.question}</div><div class="review-correct-ans">正解: ${item.options[item.answer]}</div>`; reviewCardsList.appendChild(card); } }
    function toggleHint() {
       sfx.hint(); const q = currentQuestion; if (hintTextDisplay && hintButton) {
           hintTextDisplay.style.display = "block"; totalHintUsedCount++;
           if (hintClickCount === 0) { hintTextDisplay.style.backgroundColor = "#f0fdf4"; hintTextDisplay.style.borderLeft = "5px solid #0d9488"; hintTextDisplay.innerHTML = `<strong>【STEP 1】</strong><br>${q.hints[0]}`; hintButton.textContent = "💡 さらにヒント (2/3)"; hintClickCount++;
           } else if (hintClickCount === 1) { hintTextDisplay.style.backgroundColor = "#fffbeb"; hintTextDisplay.style.borderLeft = "5px solid #d97706"; hintTextDisplay.innerHTML = `<strong>【STEP 2】</strong><br>${q.hints[1] || "落ち着いて考えれば必ず解けます!"}`; hintButton.textContent = "💡 ヒントを閉じる"; hintClickCount++;
           } else { hintTextDisplay.style.display = "none"; hintButton.textContent = "💡 ヒント表示"; hintClickCount = 0; }
       }
    }
    window.onerror = function(msg, url, line) { stopAutoTimer(); canAnswer = true; isTypesetting = false; if(currentPhase === "QUIZ" && resultEl) { resultEl.innerHTML = "<span style='color:#f59e0b;'>⚠️ システムを自動修復しました。継続タップ可能です。</span>"; } return true; };
    window.onload = initApp;
    </script>
    </body>
    </html>

     
     
    記事をマガジンに追加するのは大歓迎です。😄 数検3級を所持しています。どんどんコメントや質問してください😄 小学6年生です。500フォロワー目標です。

    あなたへのおすすめ