<!DOCTYPE html>

<html lang="ja">

<head>

  <meta charset="UTF-8" />

  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

  <title>IELTS Speaking Coach (Pages版)</title>

  <script src="https://cdn.tailwindcss.com"></script>

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

  <style>

    body { touch-action: manipulation; background:#f8fafc; }

    .message-bubble { max-width: 90%; border-radius: 1.25rem; }

    .recording { animation: pulse 1.5s infinite; background-color: #ef4444 !important; }

    @keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.05)} }

    #start-overlay, #topic-overlay, #prep-overlay {

      position:absolute; inset:0; z-index:50; display:flex; flex-direction:column;

      align-items:center; justify-content:center; color:#fff; text-align:center; padding:1.5rem;

    }

    #start-overlay { background: linear-gradient(135deg, #4f46e5 0%, #3730a3 100%); }

    #topic-overlay { background:#312e81; display:none; overflow-y:auto; }

    #prep-overlay { background: rgba(0,0,0,0.92); display:none; }

    .menu-card, .topic-card {

      background: rgba(255,255,255,0.12);

      border:1px solid rgba(255,255,255,0.20);

      border-radius: 14px;

      padding: 16px;

      width: 100%;

      max-width: 340px;

      transition: all .2s;

      cursor: pointer;

      margin-bottom: 12px;

    }

    .menu-card:hover, .topic-card:hover { background: rgba(255,255,255,0.20); transform: translateY(-2px); }


    .btn-action {

      padding: 8px 14px;

      border-radius: 999px;

      font-weight: 700;

      font-size: 12px;

      display: inline-flex;

      align-items: center;

      gap: 8px;

      transition: all .15s;

      box-shadow: 0 2px 6px rgba(0,0,0,0.08);

    }

    .btn-repeat { background:#f59e0b; color:#fff; }

    .btn-next { background:#4f46e5; color:#fff; }

    .btn-repeat.active { background:#ef4444; transform: scale(0.98); }

    .small-note { font-size: 11px; color:#334155; }

  </style>

</head>

<body class="min-h-screen font-sans text-slate-900">

  <div id="app" class="max-w-md mx-auto h-screen flex flex-col shadow-2xl bg-white relative overflow-hidden">


    <!-- Start -->

    <div id="start-overlay">

      <i class="fas fa-graduation-cap text-6xl mb-4 text-indigo-200"></i>

      <h1 class="text-3xl font-extrabold mb-2">IELTS Coach</h1>

      <p class="mb-6 text-indigo-100 text-sm">まずはトピックを選んで練習開始</p>


      <div class="space-y-2 w-full flex flex-col items-center">

        <div class="menu-card" onclick="openTopicSelection('Part 1')">

          <b>Part 1: Interview</b><br><span class="text-xs opacity-80">5.0–6.0向け(短く・簡単に)</span>

        </div>

        <div class="menu-card" onclick="openTopicSelection('Part 2')">

          <b>Part 2: Long Turn</b><br><span class="text-xs opacity-80">1分準備 → 1.5〜2分</span>

        </div>

        <div class="menu-card" onclick="openTopicSelection('Part 3')">

          <b>Part 3: Discussion</b><br><span class="text-xs opacity-80">理由+例(難語は少なめ)</span>

        </div>

      </div>


      <div class="mt-6 text-xs text-indigo-100 opacity-90">

        ※今は「配布できる箱」を先に作る段階です。Gemini連携はあとでOK。

      </div>

    </div>


    <!-- Topic -->

    <div id="topic-overlay">

      <button onclick="showStartMenuUI()" class="absolute top-6 left-6 text-white opacity-80">

        <i class="fas fa-arrow-left"></i> 戻る

      </button>

      <h2 id="topic-title" class="text-xl font-bold mb-6 mt-12">Topics</h2>

      <div id="topic-list" class="w-full flex flex-col items-center pb-8"></div>

    </div>


    <!-- Part2 Prep -->

    <div id="prep-overlay">

      <div class="text-indigo-300 text-xs font-bold mb-2 uppercase tracking-widest">Preparation Time</div>

      <h2 class="text-xl font-bold mb-6 px-4" id="prep-topic-text">--</h2>

      <div id="prep-timer" class="text-8xl font-mono font-bold mb-6 text-white">60</div>

      <p class="text-sm text-slate-300 mb-8 px-8 italic">メモを取ってOK。時間が来たら開始します。</p>

      <button onclick="skipPrep()" class="bg-indigo-600 hover:bg-indigo-500 px-10 py-4 rounded-full font-bold transition shadow-xl">

        今すぐ始める

      </button>

    </div>


    <!-- Header -->

    <header class="bg-indigo-700 text-white p-4 flex justify-between items-center shadow-lg shrink-0">

      <div class="flex items-center gap-3">

        <button onclick="confirmReset()" class="p-2 hover:bg-indigo-600 rounded-full" title="ホームへ">

          <i class="fas fa-home"></i>

        </button>

        <div>

          <div id="header-part" class="text-[10px] uppercase font-bold text-indigo-200 leading-none">Ready</div>

          <div id="header-topic" class="text-sm font-bold">Select Topic</div>

        </div>

      </div>

      <button id="tts-toggle" onclick="toggleTts()" class="p-2 bg-indigo-800 rounded-full" title="音声ON/OFF">

        <i id="tts-icon" class="fas fa-volume-up"></i>

      </button>

    </header>


    <!-- Chat -->

    <main id="chat-container" class="flex-grow overflow-y-auto p-4 space-y-4 bg-slate-50"></main>


    <!-- Footer -->

    <footer class="p-4 bg-white border-t shrink-0">

      <div id="rec-hint" class="hidden text-[10px] text-red-500 font-bold mb-2 animate-pulse uppercase tracking-tighter">

        <i class="fas fa-circle mr-1"></i> Recording...

      </div>

      <div class="flex items-center gap-2">

        <button id="mic-btn" onclick="toggleMic()" class="w-12 h-12 rounded-full bg-indigo-600 text-white shadow-lg flex items-center justify-center transition active:scale-90">

          <i class="fas fa-microphone text-lg"></i>

        </button>

        <input id="user-input" type="text" placeholder="英語で答えてください..." class="flex-grow border border-slate-300 rounded-full py-2.5 px-4 text-sm focus:ring-2 focus:ring-indigo-500 outline-none" />

        <button onclick="handleSend()" class="w-11 h-11 bg-indigo-100 text-indigo-700 rounded-full flex items-center justify-center hover:bg-indigo-200 transition">

          <i class="fas fa-paper-plane text-sm"></i>

        </button>

      </div>

      <div class="mt-2 small-note">

        ※マイク入力を「確実に」動かすには、次にCloudflare WorkersでGemini(STT)をつなぎます。

      </div>

    </footer>

  </div>


  <script>

    // ★ここは後でWorkersを作ったらURLに置き換える(今は空でOK)

    const WORKER_BASE_URL = ""; // 例: "https://xxxx.yourname.workers.dev"


    const topicsData = {

      "Part 1": [

        {t:"Hometown", d:"地元の紹介"},

        {t:"Work or Study", d:"仕事や学業"},

        {t:"Daily Routine", d:"日課"},

        {t:"Hobbies", d:"趣味"},

        {t:"Technology", d:"スマホ・SNS"},

        {t:"Transport", d:"移動手段"}

      ],

      "Part 2": [

        {t:"Describe a Person", d:"人物"},

        {t:"Describe a Place", d:"場所"},

        {t:"Describe an Experience", d:"体験"}

      ],

      "Part 3": [

        {t:"Society & Technology", d:"AI/広告/仕事"},

        {t:"Education & Career", d:"教育/キャリア"},

        {t:"Tourism & History", d:"観光/歴史"}

      ]

    };


    let state = {

      menu: "",

      topic: "",

      isTts: true,

      isRec: false,

      isProcessing: false,

      prepInterval: null,

      repeatingMode: false,

      currentModelAnswer: ""

    };


    // -------- UI navigation --------

    function showStartMenuUI(){

      document.getElementById("start-overlay").style.display = "flex";

      document.getElementById("topic-overlay").style.display = "none";

    }


    function openTopicSelection(menu) {

      state.menu = menu;

      document.getElementById("start-overlay").style.display = "none";

      document.getElementById("topic-overlay").style.display = "flex";

      document.getElementById("topic-title").innerText = menu + " Topics";

      const list = document.getElementById("topic-list");

      list.innerHTML = "";

      topicsData[menu].forEach(item => {

        const d = document.createElement("div");

        d.className = "topic-card";

        d.innerHTML = `<div class="font-bold text-white">${item.t}</div><div class="text-[10px] text-indigo-200">${item.d}</div>`;

        d.onclick = () => selectTopic(item.t);

        list.appendChild(d);

      });

    }


    function selectTopic(t) {

      state.topic = t;

      document.getElementById("topic-overlay").style.display = "none";

      document.getElementById("header-part").innerText = state.menu;

      document.getElementById("header-topic").innerText = t;


      if (state.menu === "Part 2") startPart2Prep();

      else {

        addMessage("ai", `✅ ${state.menu} (${t}) を開始します。まずは質問を出します。`);

        askNextQuestion();

      }

    }


    function startPart2Prep() {

      document.getElementById("prep-overlay").style.display = "flex";

      document.getElementById("prep-topic-text").innerText = `Topic: ${state.topic}`;

      let time = 60;

      document.getElementById("prep-timer").innerText = time;


      state.prepInterval = setInterval(() => {

        time--;

        document.getElementById("prep-timer").innerText = time;

        if (time <= 0) skipPrep();

      }, 1000);

    }


    function skipPrep() {

      clearInterval(state.prepInterval);

      document.getElementById("prep-overlay").style.display = "none";

      addMessage("ai", "⏱ 準備時間が終了しました。質問を出します。");

      askNextQuestion();

    }


    function confirmReset() {

      if (confirm("ホームに戻るとチャット履歴が消えます。戻りますか?")) location.reload();

    }


    // -------- Chat rendering --------

    function addMessage(role, text, withActions=false) {

      const container = document.getElementById("chat-container");

      const wrap = document.createElement("div");

      wrap.className = `flex flex-col ${role==='user'?'items-end':'items-start'} w-full`;


      const bubble = document.createElement("div");

      bubble.className = `message-bubble p-4 text-sm shadow-sm ${

        role==='user' ? 'bg-indigo-600 text-white' : 'bg-white border border-slate-200 text-slate-800'

      }`;


      bubble.innerHTML = formatText(text);

      wrap.appendChild(bubble);


      if (withActions) {

        const actions = document.createElement("div");

        actions.className = "flex gap-2 mt-2";

        actions.innerHTML = `

          <button class="btn-action btn-repeat" onclick="startRepeat(this)">

            <i class="fas fa-redo"></i> リピート練習

          </button>

          <button class="btn-action btn-next" onclick="askNextQuestion()">

            次の質問 <i class="fas fa-arrow-right"></i>

          </button>

        `;

        wrap.appendChild(actions);

      }


      container.appendChild(wrap);

      container.scrollTop = container.scrollHeight;

    }


    function formatText(text) {

      // 表示用の整形(タグを見やすく)

      const safe = String(text)

        .replace(/\[Scores\]/g, "📊 <b class='text-indigo-600'>Estimated Score</b><br>")

        .replace(/\[Feedback\]/g, "<br>💡 <b class='text-amber-600'>Advice</b><br>")

        .replace(/\[Model Answer\]/g, "<br>📘 <b class='text-blue-600'>Model Answer</b><br>")

        .replace(/\[Next Question\]/g, "<br>❓ <b class='text-indigo-600'>Question</b><br>");

      return safe.replace(/\n/g, "<br>");

    }


    // -------- Speech (cute-ish on iPhone) --------

    function speak(text) {

      if (!state.isTts) return;

      try { window.speechSynthesis.cancel(); } catch(e){}


      // Feedbackは読まない(英語部分中心)

      let speakText = "";

      const modelMatch = text.match(/\[Model Answer\]:?\s*([\s\S]*?)(?=\[|$)/i);

      const qMatch = text.match(/\[Next Question\]:?\s*([\s\S]*?)$/i);

      if (modelMatch) speakText += modelMatch[1].trim() + " ";

      if (qMatch) speakText += qMatch[1].trim();

      if (!speakText) speakText = String(text);


      const ut = new SpeechSynthesisUtterance(speakText.replace(/<br>/g, " "));

      ut.lang = "en-US";


      // iPhone Safari:声の種類は端末依存。取れたら英語声を優先

      const voices = window.speechSynthesis.getVoices?.() || [];

      const v = voices.find(x => x.lang && x.lang.startsWith("en")) || null;

      if (v) ut.voice = v;


      // かわいく寄せる(やりすぎると聞き取りにくいので控えめ)

      ut.pitch = 1.18;

      ut.rate = 0.92;


      window.speechSynthesis.speak(ut);

    }

    window.speechSynthesis.onvoiceschanged = () => { try { window.speechSynthesis.getVoices(); } catch(e){} };


    function toggleTts() {

      state.isTts = !state.isTts;

      document.getElementById("tts-icon").className = state.isTts ? "fas fa-volume-up" : "fas fa-volume-mute";

      if (!state.isTts) { try { window.speechSynthesis.cancel(); } catch(e){} }

    }


    // -------- API helpers (Workerが無い間は案内を返す) --------

    async function callGemini(prompt) {

      if (!WORKER_BASE_URL) {

        return "[Feedback]\n今はGemini未接続です。まずPagesで配布できる状態を作っています。\n\n[Model Answer]\nI can answer in simple English. (Gemini is not connected yet.)\n\n[Next Question]\nPlease type your answer for now.";

      }


      const res = await fetch(`${WORKER_BASE_URL}/api/generate`, {

        method: "POST",

        headers: { "Content-Type":"application/json" },

        body: JSON.stringify({ prompt })

      });

      const data = await res.json();

      return data?.text || "[Error] No response.";

    }


    // -------- Core flow --------

    async function askNextQuestion() {

      if (!state.menu || !state.topic) return;

      state.isProcessing = true;


      const prompt = `As an IELTS examiner, ask ONE question for ${state.menu} about "${state.topic}". Start with [Next Question]. Keep it short.`;

      const q = await callGemini(prompt);


      state.isProcessing = false;

      addMessage("ai", q);

      speak(q);

    }


    async function handleSend() {

      const input = document.getElementById("user-input");

      const text = input.value.trim();

      if (!text || state.isProcessing) return;


      addMessage("user", text);

      input.value = "";


      // リピート練習中:モデル文と比較

      if (state.repeatingMode) {

        state.repeatingMode = false;

        const prompt = `Compare User to Model. Give short feedback in Japanese.

Model: "${state.currentModelAnswer}"

User: "${text}"`;

        state.isProcessing = true;

        const fb = await callGemini(prompt);

        state.isProcessing = false;

        addMessage("ai", fb);

        speak(fb);

        return;

      }


      // 通常:スコア/FB/モデル/次質問

      const levelInstruction = (() => {

        if (state.menu === "Part 1") {

          return "Model Answer must be IELTS Band 5.0-6.0: simple words, 2-3 sentences, about 25-35 words. Use Answer + Reason (+ small detail).";

        }

        if (state.menu === "Part 2") {

          return "Model Answer should be Band 5.5-6.5: clear structure, simple vocabulary, 120-160 words, 1.5-2 minutes style.";

        }

        return "Model Answer should be Band 5.5-6.5: clear reasons + one example, avoid rare words, 60-90 words.";

      })();


      const prompt = `You are an IELTS Speaking Coach.

Menu: ${state.menu}

Topic: ${state.topic}

User Answer: "${text}"


${levelInstruction}


Respond with:

1. [Scores] Estimated Band 0-9 (rough)

2. [Feedback] concise advice in Japanese (2-4 bullets)

3. [Model Answer] (English)

4. [Next Question] (one follow-up question)`;


      state.isProcessing = true;

      const response = await callGemini(prompt);

      state.isProcessing = false;


      // モデル答え抽出(リピート用)

      const m = response.match(/\[Model Answer\]:?\s*([\s\S]*?)(?=\[|$)/i);

      state.currentModelAnswer = m ? m[1].trim() : "";


      addMessage("ai", response, !!state.currentModelAnswer);

      speak(response);

    }


    // -------- Repeat practice --------

    function startRepeat(btn) {

      if (!state.currentModelAnswer) return;


      // ボタン見た目

      document.querySelectorAll(".btn-repeat").forEach(b => b.classList.remove("active"));

      btn.classList.add("active");


      state.repeatingMode = true;

      speak(`[Model Answer]: ${state.currentModelAnswer}`);


      addMessage("ai",

        "[Feedback]\nリピート練習:今のModel Answerを聞いて、できるだけ同じ内容で言ってみてください。\n(音声入力はWorkers接続後に安定します。今はタイピングでもOK)"

      );

    }


    // -------- Mic (今は “録音はするがSTTはWorkers接続後” の設計) --------

    let mediaRecorder = null;

    let recStream = null;

    let audioChunks = [];


    async function toggleMic() {

      const micBtn = document.getElementById("mic-btn");

      const hint = document.getElementById("rec-hint");


      // stop

      if (state.isRec && mediaRecorder) {

        mediaRecorder.stop();

        return;

      }


      // start

      try {

        recStream = await navigator.mediaDevices.getUserMedia({ audio: true });

        audioChunks = [];

        mediaRecorder = new MediaRecorder(recStream);


        mediaRecorder.onstart = () => {

          state.isRec = true;

          micBtn.classList.add("recording");

          hint.classList.remove("hidden");

        };


        mediaRecorder.ondataavailable = (e) => {

          if (e.data && e.data.size > 0) audioChunks.push(e.data);

        };


        mediaRecorder.onstop = async () => {

          state.isRec = false;

          micBtn.classList.remove("recording");

          hint.classList.add("hidden");


          if (recStream) {

            recStream.getTracks().forEach(t => t.stop());

            recStream = null;

          }


          // Workers未接続なら案内だけ

          if (!WORKER_BASE_URL) {

            addMessage("ai", "[Feedback]\nマイク録音はできました。\n次にCloudflare WorkersでGemini(STT)をつなぐと、録音→文字起こし→自動送信ができます。\n今はタイピングで進めてOKです。");

            return;

          }


          // ここはWorkers接続後に有効化(/api/stt)

          try {

            const blob = new Blob(audioChunks, { type: mediaRecorder.mimeType || "audio/webm" });

            audioChunks = [];


            const base64 = await blobToBase64(blob);

            const res = await fetch(`${WORKER_BASE_URL}/api/stt`, {

              method: "POST",

              headers: { "Content-Type":"application/json" },

              body: JSON.stringify({ audioBase64: base64, mimeType: blob.type || "audio/webm" })

            });

            const data = await res.json();

            const transcript = (data?.text || "").trim();


            if (!transcript) {

              addMessage("ai", "[Feedback]\nうまく聞き取れませんでした。もう一度ゆっくり話してください。");

              return;

            }

            document.getElementById("user-input").value = transcript;

            setTimeout(handleSend, 120);

          } catch(e) {

            addMessage("ai", "[Error]\nSTT通信に失敗しました。");

          }

        };


        mediaRecorder.start();

      } catch (e) {

        addMessage("ai", "[Feedback]\nマイクの許可が必要です。Safariの設定でマイクを許可してください。");

      }

    }


    function blobToBase64(blob) {

      return new Promise((resolve, reject) => {

        const r = new FileReader();

        r.onloadend = () => resolve(String(r.result).split(",")[1]);

        r.onerror = reject;

        r.readAsDataURL(blob);

      });

    }


    // Enterで送信

    document.getElementById("user-input").addEventListener("keydown", (e) => {

      if (e.key === "Enter") handleSend();

    });

  </script>

</body>

</html>