{"id":694,"date":"2026-01-14T05:59:10","date_gmt":"2026-01-14T05:59:10","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=694"},"modified":"2026-01-14T05:59:10","modified_gmt":"2026-01-14T05:59:10","slug":"ielts-speaking-test-5","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/ielts-speaking-test-5\/","title":{"rendered":"IELTs &#8211; Speaking &#8211; Test 5"},"content":{"rendered":"\n<!-- \u2705 IELTS SPEAKING CHATBOT (Fixed: valid HTML + no JS syntax errors + Part 2 wait + balanced fonts) -->\n<div id=\"icte-speaking-test\">\n\n <!-- \u2705 TOP NAV MENU (GREEN, STICKY, SCROLLABLE) -->\n  <div class=\"icte-menu\" role=\"navigation\" aria-label=\"IELTS practice navigation\">\n     <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking-overview\/\">Overview<\/a>\n     <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking\/\">Test 1<\/a>\n    <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking-1\/\">Test 2<\/a>\n    <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking-2\/\">Test 3<\/a>\n     <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking-test-4\/\">Test 4<\/a>\n     <a href=\"https:\/\/i-cte.org\/robot\/ielts-speaking-test-5\/\" class=\"is-current\">Test 5<\/a>\n    <a href=\"https:\/\/i-cte.org\/robot\/ielts-listening-overview\/\">Listening<\/a>\n    <a href=\"https:\/\/i-cte.org\/robot\/ielts-reading-overview\/\">Reading<\/a>\n    <a href=\"https:\/\/i-cte.org\/robot\/ielts-writing-overview\/\">Writing<\/a>\n\n  <\/div>\n\n  <section class=\"icte-ielts\" aria-label=\"IELTS Speaking Test\">\n\n    <!-- \u2705 Robot hero header -->\n    <section class=\"icte-hero\" aria-label=\"Robot examiner header\">\n      <div class=\"icte-hero__left\">\n        <div class=\"icte-badges\">\n          <span class=\"icte-badge\">IELTS Speaking<\/span>\n          <span class=\"icte-badge icte-badge--blue\">Robot Examiner<\/span>\n        <\/div>\n\n        <h2 class=\"icte-hero__title\">Practice with a Robot Examiner<\/h2>\n        <p class=\"icte-hero__sub\">\n          Choose <strong>Part 1<\/strong>, <strong>Part 2<\/strong>, or <strong>Part 3<\/strong>. The examiner reads questions aloud.\n          Recording stays ON until you click Stop or say <strong>\u201cbye \/ goodbye\u201d<\/strong>.\n        <\/p>\n\n        <div class=\"icte-hero__note\">\n          Tip: Answer + explain + give an example to sound more natural and score higher.\n        <\/div>\n      <\/div>\n\n      <div class=\"icte-hero__right\">\n        <div class=\"icte-robotCard\" aria-label=\"Robot examiner image\">\n          <img decoding=\"async\"\n            src=\"https:\/\/i-cte.org\/robot\/wp-content\/uploads\/2026\/01\/9.webp\"\n            alt=\"A friendly robot examiner interviewing a student\"\n            onerror=\"this.onerror=null;this.src='https:\/\/i-cte.org\/robot\/wp-content\/uploads\/2026\/01\/9.webp';\"\n          \/>\n          <div class=\"icte-robotOverlay\">\n            <div class=\"t1\">Robot Examiner<\/div>\n            <div class=\"t2\">IELTS Speaking Practice<\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"icte-heroHint\" role=\"note\">\n          <strong>Audio note:<\/strong> On mobile, tap <em>Begin<\/em> once to allow voice playback.\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- \u2705 How to use (moved OUTSIDE hero to avoid layout break) -->\n    <section class=\"icte-ielts__panel\" aria-label=\"How to use this page\">\n      <div class=\"icte-ielts__panelHead\">\n        <h3 class=\"icte-ielts__h3\">How to Use the Buttons<\/h3>\n      <\/div>\n\n      <div class=\"icte-ielts__reading\">\n        <div class=\"icte-quickGuide\">\n          <div class=\"icte-quickGuide__grid\">\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">1<\/span> Select a Part<\/div>\n              <div class=\"icte-step__text\">\n                Click <strong>Full test<\/strong>, <strong>Part 1<\/strong>, <strong>Part 2<\/strong>, or <strong>Part 3<\/strong>.\n                The active part turns <strong>green<\/strong>.\n              <\/div>\n            <\/div>\n\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">2<\/span> Begin the Test<\/div>\n              <div class=\"icte-step__text\">\n                Click <strong>Begin the Test<\/strong>. The robot shows the first question and (if voice is ON) reads it aloud.\n              <\/div>\n            <\/div>\n\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">3<\/span> Start \/ Stop Recording<\/div>\n              <div class=\"icte-step__text\">\n                Click <strong>Start Recording<\/strong> and speak. Click <strong>Stop Recording<\/strong> anytime.\n                If <strong>Auto-advance<\/strong> is ON, the robot moves on after the silence time.\n              <\/div>\n            <\/div>\n\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">4<\/span> Next Question<\/div>\n              <div class=\"icte-step__text\">\n                When you finish, click <strong>Next Question<\/strong>. Your answer is saved into <strong>Full test transcript<\/strong>.\n              <\/div>\n            <\/div>\n\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">5<\/span> Sample Answer<\/div>\n              <div class=\"icte-step__text\">\n                Click <strong>Sample Answer<\/strong> to view an example response for the current question.\n              <\/div>\n            <\/div>\n\n            <div class=\"icte-step\">\n              <div class=\"icte-step__title\"><span class=\"icte-step__num\">6<\/span> Band Score<\/div>\n              <div class=\"icte-step__text\">\n                After answering several questions, click <strong>Band Score<\/strong> to get practice feedback.\n                Adjust the <strong>Pronunciation self-rating<\/strong> slider if needed.\n              <\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"icte-quickGuide__footer\">\n            <strong>Tip:<\/strong> You can also type your answer in <em>Current answer (live)<\/em>.\n            Say <strong>\u201cbye\u201d<\/strong> or <strong>\u201cgoodbye\u201d<\/strong> to end the test.\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <header class=\"icte-ielts__intro\">\n      <h3 class=\"icte-ielts__title\">IELTS Speaking Test<\/h3>\n      <p class=\"icte-ielts__sub\">\n        Choose Part 1 \/ Part 2 \/ Part 3. The examiner reads questions aloud. Recording stays ON until you click Stop or say \u201cbye\/goodbye\u201d.\n      <\/p>\n    <\/header>\n\n    <!-- Mode \/ Settings -->\n    <section class=\"icte-ielts__panel\" aria-label=\"Mode &#038; Settings\">\n      <div class=\"icte-ielts__panelHead\">\n        <h3 class=\"icte-ielts__h3\">Choose Part<\/h3>\n        <div class=\"icte-ielts__headRight\">\n          <span class=\"icte-pill\" data-el=\"modePill\">Mode: Full test<\/span>\n          <span class=\"icte-pill\" data-el=\"autoPill\">Auto-advance: ON<\/span>\n          <span class=\"icte-pill\" data-el=\"micPill\">\ud83c\udf99 Mic: Checking\u2026<\/span>\n          <span class=\"icte-pill\" data-el=\"ttsPill\">\ud83d\udd0a Voice: Checking\u2026<\/span>\n        <\/div>\n      <\/div>\n\n      <div class=\"icte-ielts__qArea\">\n        <div class=\"icte-modeRow\">\n          <button class=\"icte-btn icte-btn--primary is-active\" type=\"button\" data-action=\"mode\" data-mode=\"full\">Full test<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"mode\" data-mode=\"p1\">Part 1<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"mode\" data-mode=\"p2\">Part 2<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"mode\" data-mode=\"p3\">Part 3<\/button>\n          <button class=\"icte-btn icte-btn--dark\" type=\"button\" data-action=\"toggle-auto\">Auto-advance<\/button>\n\n          <label class=\"icte-small\" style=\"display:flex;align-items:center;gap:.5rem;\">\n            Silence (sec):\n            <input data-el=\"silenceSec\" type=\"number\" min=\"2\" max=\"20\" step=\"0.5\" value=\"6\"\n              style=\"width:92px;padding:.45rem .55rem;border-radius:12px;border:1px solid rgba(0,0,0,.18);font-size:1rem;\">\n          <\/label>\n        <\/div>\n\n        <div class=\"icte-ttsRow\" style=\"margin-top:.8rem;\">\n          <div class=\"icte-ttsCol\">\n            <label class=\"icte-small\"><strong>Examiner Voice<\/strong><\/label>\n            <select data-el=\"voiceSelect\" aria-label=\"Select examiner voice\">\n              <option value=\"\">Loading voices\u2026<\/option>\n            <\/select>\n          <\/div>\n\n          <div class=\"icte-ttsCol\">\n            <label class=\"icte-small\"><strong>Voice Options<\/strong><\/label>\n            <div style=\"display:flex;gap:.6rem;flex-wrap:wrap;align-items:center;\">\n              <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"toggle-voice\">Toggle Voice<\/button>\n              <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"stop-voice\">Stop Voice<\/button>\n              <span class=\"icte-small\" data-el=\"voiceHint\" style=\"opacity:.85\"><\/span>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"icte-warn\" data-el=\"supportBox\" style=\"display:none;margin-top:.85rem;\">\n          Speech Recognition is not supported on this browser\/device. Use Chrome on desktop, or type your answer and click \u201cNext Question\u201d.\n        <\/div>\n\n        <div class=\"icte-warn\" data-el=\"ttsBox\" style=\"display:none;margin-top:.85rem;\">\n          Robot examiner moves to the next question when you stop speaking for the set silence time.\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- Part Instructions -->\n    <section class=\"icte-ielts__panel\" aria-label=\"Part instructions\">\n      <div class=\"icte-ielts__panelHead\">\n        <h3 class=\"icte-ielts__h3\">Part Instructions<\/h3>\n      <\/div>\n      <div class=\"icte-ielts__reading\">\n        <div class=\"icte-partInfo\" data-el=\"partInfo\"><\/div>\n      <\/div>\n    <\/section>\n\n    <!-- Conversation -->\n    <section class=\"icte-ielts__panel\" aria-label=\"Examiner\">\n      <div class=\"icte-ielts__panelHead\">\n        <h3 class=\"icte-ielts__h3\">Conversation<\/h3>\n        <div class=\"icte-ielts__headRight\">\n          <button class=\"icte-btn icte-btn--info\" type=\"button\" data-action=\"begin\">\u25b6 Begin the Test<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"repeat\" disabled>Repeat<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"model\" disabled>Sample Answer<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"reset\">Reset<\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"icte-ielts__reading\">\n        <div class=\"icte-chat\" data-el=\"chat\" role=\"region\" aria-label=\"Chat\"><\/div>\n\n        <div class=\"icte-statusRow\">\n          <div class=\"icte-status\">\n            <strong>Current:<\/strong> <span data-el=\"currentMeta\">\u2014<\/span><br>\n            <span class=\"icte-qNow\" data-el=\"currentQ\">\u2014<\/span>\n            <div class=\"icte-small\" data-el=\"needHint\" style=\"margin-top:.45rem;\"><\/div>\n          <\/div>\n          <div class=\"icte-status\">\n            <strong>Recording:<\/strong> <span data-el=\"recState\">OFF<\/span><br>\n            <strong>Current answer words:<\/strong> <span data-el=\"ansWords\">0<\/span>\n            <div class=\"icte-small\">Say \u201cbye\/goodbye\u201d to end the test.<\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- Controls -->\n    <section class=\"icte-ielts__panel\" aria-label=\"Controls\">\n      <div class=\"icte-ielts__panelHead\">\n        <h3 class=\"icte-ielts__h3\">Controls<\/h3>\n        <div class=\"icte-ielts__headRight\">\n          <span class=\"icte-loader\" data-el=\"loader\" aria-hidden=\"true\"><\/span>\n        <\/div>\n      <\/div>\n\n      <div class=\"icte-ielts__qArea\">\n        <div class=\"icte-ielts__btnRow\">\n          <button class=\"icte-btn icte-btn--primary\" type=\"button\" data-action=\"start-rec\">\u25b6 Start Recording<\/button>\n          <button class=\"icte-btn icte-btn--danger\" type=\"button\" data-action=\"stop-rec\" disabled>\u25a0 Stop Recording<\/button>\n          <button class=\"icte-btn icte-btn--info\" type=\"button\" data-action=\"next\">Next Question \u25b6<\/button>\n          <button class=\"icte-btn icte-btn--dark\" type=\"button\" data-action=\"score\">Band Score<\/button>\n          <button class=\"icte-btn icte-btn--ghost\" type=\"button\" data-action=\"clear-current\">Clear Current Answer<\/button>\n        <\/div>\n\n        <div class=\"icte-grid2\">\n          <div>\n            <div class=\"icte-small\"><strong>Current answer (live)<\/strong> \u2014 speech-to-text goes here. You can also type.<\/div>\n            <div class=\"icte-transcript\" contenteditable=\"true\" data-el=\"currentAnswer\" role=\"textbox\" aria-label=\"Current answer\" spellcheck=\"true\"><\/div>\n          <\/div>\n          <div>\n            <div class=\"icte-small\"><strong>Full test transcript<\/strong> (all answers)<\/div>\n            <div class=\"icte-transcript\" data-el=\"fullTranscript\" style=\"min-height:120px;background:rgba(255,255,255,.65);\" contenteditable=\"false\"><\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"icte-scoreBox\" data-el=\"scoreBox\" aria-live=\"polite\">\n          <div style=\"display:flex;gap:.8rem;flex-wrap:wrap;align-items:center;justify-content:space-between;\">\n            <div style=\"font-size:22px;font-weight:1000;\">Band: <span data-el=\"bandOut\">\u2014<\/span><\/div>\n            <div style=\"min-width:280px;\">\n              <div class=\"icte-small\"><strong>Pronunciation self-rating<\/strong> (0\u20139)<\/div>\n              <input class=\"icte-range\" type=\"range\" data-el=\"pronSelf\" min=\"0\" max=\"9\" step=\"0.5\" value=\"6.5\">\n              <div class=\"icte-small\">Value: <span data-el=\"pronVal\">6.5<\/span><\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"icte-scoreGrid\">\n            <div class=\"icte-metric\"><div class=\"k\">Fluency<\/div><div class=\"v\" data-el=\"mFlu\">\u2014<\/div><div class=\"n\" data-el=\"nFlu\"><\/div><\/div>\n            <div class=\"icte-metric\"><div class=\"k\">Lexical<\/div><div class=\"v\" data-el=\"mLex\">\u2014<\/div><div class=\"n\" data-el=\"nLex\"><\/div><\/div>\n            <div class=\"icte-metric\"><div class=\"k\">Grammar<\/div><div class=\"v\" data-el=\"mGram\">\u2014<\/div><div class=\"n\" data-el=\"nGram\"><\/div><\/div>\n            <div class=\"icte-metric\"><div class=\"k\">Pronunciation<\/div><div class=\"v\" data-el=\"mPron\">\u2014<\/div><div class=\"n\" data-el=\"nPron\"><\/div><\/div>\n          <\/div>\n\n          <div class=\"icte-tips\" data-el=\"tips\" style=\"display:none\"><\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <style>\n\n\/* =========================\n   BALANCED TYPE SCALE (WP-safe)\n   Headings smaller, content bigger, overall nearly equal\n   ========================= *\/\n#icte-speaking-test{\n  font-family: Arial, sans-serif;\n  font-size: 14px;            \/* base for the whole widget *\/\n  line-height: 1.7;\n  color: #0f172a;\n}\n\n\/* Text helpers *\/\n#icte-speaking-test .icte-small{ font-size: 1rem; opacity:.88; }\n\n\/* Pills \/ badges \/ small UI labels *\/\n#icte-speaking-test .icte-pill{ font-size: 1rem; }\n#icte-speaking-test .icte-badge{ font-size: 1rem; }\n\n\/* Buttons *\/\n#icte-speaking-test .icte-btn{ font-size: 1rem; }\n\n\/* Main headings: keep modest *\/\n#icte-speaking-test .icte-ielts__title{ font-size: 1.3rem; }\n#icte-speaking-test .icte-ielts__h3{ font-size: 1.1rem; }\n#icte-speaking-test .icte-hero__title{ font-size: 1.35rem; }\n#icte-speaking-test .icte-hero__sub{ font-size: 1rem; }\n#icte-speaking-test .icte-ielts__sub{ font-size: 1rem; }\n\n\/* \u2705 The important parts: chat + transcript bigger *\/\n#icte-speaking-test .icte-chat{ font-size: 1.05rem; }\n#icte-speaking-test .icte-bubble{ font-size: 1.05rem; }\n#icte-speaking-test .icte-transcript{ font-size: 1.05rem; }\n\n\/* Mini chat bubbles (top demo) *\/\n#icte-speaking-test .icte-miniBubble .who{ font-size: 1rem; }\n#icte-speaking-test .icte-miniBubble .txt{ font-size: 1.05rem; }\n\n\n      \/* =========================\n         QUICK GUIDE\n         ========================= *\/\n      #icte-speaking-test .icte-quickGuide{\n        border:1px solid rgba(0,0,0,.10);\n        border-radius:14px;\n        background:rgba(255,255,255,.72);\n        padding:1rem;\n      }\n      #icte-speaking-test .icte-quickGuide__grid{\n        display:grid;\n        grid-template-columns:repeat(2, minmax(0, 1fr));\n        gap:.75rem;\n      }\n      @media (max-width: 900px){\n        #icte-speaking-test .icte-quickGuide__grid{ grid-template-columns:1fr; }\n      }\n      #icte-speaking-test .icte-step{\n        border:1px solid rgba(0,0,0,.08);\n        border-radius:14px;\n        background:rgba(248,250,252,.75);\n        padding:.85rem .9rem;\n        box-shadow:0 8px 16px rgba(0,0,0,.05);\n      }\n      #icte-speaking-test .icte-step__title{\n        display:flex;\n        align-items:center;\n        gap:.6rem;\n        font-weight:1000;\n        font-size:1.02rem;\n        margin-bottom:.35rem;\n        color:#0f172a;\n      }\n      #icte-speaking-test .icte-step__num{\n        width:30px; height:30px;\n        border-radius:999px;\n        display:inline-flex;\n        align-items:center;\n        justify-content:center;\n        font-weight:1000;\n        color:#064e3b;\n        background:rgba(34,197,94,.16);\n        border:1px solid rgba(34,197,94,.25);\n        flex:0 0 auto;\n      }\n      #icte-speaking-test .icte-step__text{\n        font-size:1rem;\n        color:#334155;\n        line-height:1.65;\n      }\n      #icte-speaking-test .icte-quickGuide__footer{\n        margin-top:.85rem;\n        padding:.75rem .85rem;\n        border-radius:14px;\n        border:1px solid rgba(14,165,233,.22);\n        background:rgba(14,165,233,.10);\n        color:#1e3a8a;\n        font-size:1rem;\n      }\n\n      \/* =========================\n         EXISTING STYLES + polish\n         ========================= *\/\n      #icte-speaking-test .icte-menu{\n        width:100%; max-width:100%; box-sizing:border-box;\n        display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center; align-items:center;\n        padding:.8rem .95rem; margin:0 0 1rem 0;\n        background:#16a34a; border-radius:14px; box-shadow:0 2px 8px rgba(0,0,0,.10);\n      }\n      #icte-speaking-test .icte-menu a{\n        display:inline-block; text-decoration:none; font-weight:900; font-size:1rem; color:#fff;\n        padding:.6rem .9rem; border-radius:999px; border:1px solid rgba(255,255,255,.35);\n        background:rgba(255,255,255,.12);\n      }\n\n      #icte-speaking-test .icte-ielts{ width:100%; margin:1rem 0; }\n      #icte-speaking-test .icte-ielts__intro{\n        padding:1rem 1rem; border:1px solid rgba(0,0,0,.10); border-radius:14px;\n        background:rgba(255,255,255,.7); margin: 0 0 1rem 0;\n      }\n      #icte-speaking-test .icte-ielts__title{ margin:0 0 .35rem; font-weight:900; }\n      #icte-speaking-test .icte-ielts__sub{ margin:0; opacity:.9; }\n\n      #icte-speaking-test .icte-ielts__panel{\n        border:1px solid rgba(0,0,0,.10); border-radius:14px; background:rgba(255,255,255,.85);\n        overflow:hidden; margin-bottom:1rem;\n      }\n      #icte-speaking-test .icte-ielts__panelHead{\n        display:flex; align-items:center; justify-content:space-between; gap:.75rem;\n        padding:.9rem 1rem; border-bottom:1px solid rgba(0,0,0,.08); background:rgba(0,0,0,.03);\n        flex-wrap:wrap;\n      }\n      #icte-speaking-test .icte-ielts__h3{ margin:0; font-weight:900; }\n      #icte-speaking-test .icte-ielts__headRight{ display:flex; gap:.55rem; align-items:center; flex-wrap:wrap; }\n\n      #icte-speaking-test .icte-ielts__reading{ padding:1rem 1rem 1.05rem; }\n      #icte-speaking-test .icte-ielts__qArea{ padding:1rem 1rem 1.05rem; }\n\n      #icte-speaking-test .icte-btn{\n        appearance:none; border:1px solid transparent; border-radius:12px;\n        padding:.7rem .95rem; font-weight:900; cursor:pointer; font:inherit;\n      }\n      #icte-speaking-test .icte-btn:disabled{ opacity:.55; cursor:not-allowed; }\n      #icte-speaking-test .icte-btn--primary{ background:#16a34a; color:#fff; }\n      #icte-speaking-test .icte-btn--info{ background:#0ea5e9; color:#fff; }\n      #icte-speaking-test .icte-btn--danger{ background:#dc2626; color:#fff; }\n      #icte-speaking-test .icte-btn--dark{ background:#334155; color:#fff; }\n      #icte-speaking-test .icte-btn--ghost{ background:transparent; border-color:rgba(0,0,0,.20); color:inherit; }\n      #icte-speaking-test .icte-btn.is-active{\n        background:#16a34a !important;\n        color:#fff !important;\n        border-color:transparent !important;\n      }\n\n      #icte-speaking-test .icte-pill{\n        display:inline-block; padding:.3rem .65rem; border-radius:999px;\n        border:1px solid rgba(0,0,0,.12); background:rgba(255,255,255,.75);\n        font-weight:900;\n      }\n\n      #icte-speaking-test .icte-loader{\n        width:18px; height:18px; border-radius:999px;\n        border:3px solid rgba(0,0,0,.15); border-top-color:#0ea5e9;\n        display:none; animation: icteSpin 1s linear infinite;\n      }\n      @keyframes icteSpin{ to{ transform: rotate(360deg); } }\n\n      #icte-speaking-test .icte-chat{\n        border:1px solid rgba(0,0,0,.12);\n        border-radius:14px; background:rgba(255,255,255,.65);\n        padding:.85rem; min-height:220px; max-height:380px; overflow:auto;\n      }\n      #icte-speaking-test .icte-bubble{\n        max-width:92%; padding:.75rem .85rem; border-radius:14px; margin:.55rem 0;\n        border:1px solid rgba(0,0,0,.10); background:#fff;\n      }\n      #icte-speaking-test .icte-bubble.examiner{ background:#ecfdf5; border-color:rgba(22,163,74,.25); }\n      #icte-speaking-test .icte-bubble.student{ margin-left:auto; background:#fff; }\n\n      #icte-speaking-test .icte-statusRow{ display:flex; gap:.85rem; flex-wrap:wrap; margin-top:.85rem; }\n      #icte-speaking-test .icte-status{\n        flex:1; min-width:240px;\n        border:1px solid rgba(0,0,0,.10); border-radius:14px; background:rgba(255,255,255,.7);\n        padding:.85rem .95rem;\n      }\n      #icte-speaking-test .icte-qNow{ font-weight:1000; display:block; margin-top:.35rem; }\n\n      #icte-speaking-test .icte-modeRow{ display:flex; gap:.6rem; flex-wrap:wrap; align-items:center; }\n      #icte-speaking-test .icte-ttsRow{ display:flex; gap:.85rem; flex-wrap:wrap; align-items:flex-end; }\n      #icte-speaking-test .icte-ttsCol{ flex:1; min-width:260px; }\n      #icte-speaking-test select{\n        width:100%; max-width:520px; padding:.7rem .75rem; border-radius:12px;\n        border:1px solid rgba(0,0,0,.18); background:#fff; font:inherit; font-size:1rem;\n      }\n\n      #icte-speaking-test .icte-ielts__btnRow{ display:flex; gap:.6rem; flex-wrap:wrap; align-items:center; }\n      #icte-speaking-test .icte-grid2{ display:grid; grid-template-columns: 1fr 1fr; gap:.85rem; }\n      @media (max-width: 900px){ #icte-speaking-test .icte-grid2{ grid-template-columns:1fr; } }\n\n      #icte-speaking-test .icte-transcript{\n        border:1px solid rgba(0,0,0,.12); border-radius:14px;\n        background:rgba(255,255,255,.75);\n        padding:.95rem; min-height:130px; white-space:pre-wrap;\n      }\n\n      #icte-speaking-test .icte-warn{\n        border:1px solid #fecaca; background:#fff5f5; color:#7f1d1d;\n        border-radius:14px; padding:.85rem .95rem;\n      }\n\n      #icte-speaking-test .icte-range{ width:100%; }\n\n      #icte-speaking-test .icte-scoreBox{\n        margin-top:1rem;\n        border:1px solid rgba(14,165,233,.25); border-radius:14px;\n        background:rgba(219,234,254,.55);\n        padding:.95rem 1rem;\n      }\n      #icte-speaking-test .icte-scoreGrid{\n        display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.75rem; margin-top:.85rem;\n      }\n      @media (max-width: 700px){ #icte-speaking-test .icte-scoreGrid{ grid-template-columns:1fr; } }\n      #icte-speaking-test .icte-metric{\n        border:1px solid rgba(0,0,0,.10); border-radius:14px; background:#fff;\n        padding:.75rem .85rem;\n      }\n      #icte-speaking-test .icte-metric .k{ font-weight:900; font-size:.95rem; opacity:.75; }\n      #icte-speaking-test .icte-metric .v{ font-weight:1000; font-size:1.2rem; margin-top:.15rem; }\n      #icte-speaking-test .icte-metric .n{ font-size:.95rem; opacity:.75; margin-top:.25rem; }\n      #icte-speaking-test .icte-tips{\n        margin-top:.85rem;\n        border:1px solid rgba(22,163,74,.25); background:rgba(240,253,244,.85);\n        border-radius:14px; padding:.85rem .95rem;\n      }\n      #icte-speaking-test .icte-tips ul{ margin:.4rem 0 0 1.1rem; }\n      #icte-speaking-test .icte-tips li{ margin:.35rem 0; }\n\n      #icte-speaking-test .icte-partInfo{\n        border:1px solid rgba(0,0,0,.10);\n        border-radius:14px;\n        background:rgba(255,255,255,.75);\n        padding:.95rem 1rem;\n      }\n      #icte-speaking-test .icte-partInfo h4{\n        margin:.1rem 0 .55rem;\n        font-size:1.1rem;\n        font-weight:1000;\n      }\n      #icte-speaking-test .icte-partInfo .muted{ opacity:.88; }\n      #icte-speaking-test .icte-partInfo ul{ margin:.45rem 0 0 1.15rem; }\n      #icte-speaking-test .icte-partInfo li{ margin:.3rem 0; }\n      #icte-speaking-test .icte-partBadge{\n        display:inline-block;\n        font-weight:1000;\n        font-size:1rem;\n        padding:.22rem .6rem;\n        border-radius:999px;\n        border:1px solid rgba(22,163,74,.25);\n        background:rgba(240,253,244,.9);\n        margin-right:.5rem;\n      }\n\n      \/* =========================\n         HERO (Robot examiner)\n         ========================= *\/\n      #icte-speaking-test .icte-hero{\n        display:grid;\n        grid-template-columns: 1.1fr .9fr;\n        gap:14px;\n        align-items:stretch;\n        border:1px solid rgba(0,0,0,.10);\n        border-radius:16px;\n        padding:14px;\n        margin-bottom:14px;\n        background:\n          radial-gradient(900px 300px at 20% -10%, rgba(34,197,94,.18), transparent 60%),\n          radial-gradient(900px 300px at 100% 0%, rgba(14,165,233,.14), transparent 55%),\n          rgba(255,255,255,.85);\n        box-shadow:0 10px 24px rgba(0,0,0,.06);\n      }\n      #icte-speaking-test .icte-badges{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px; }\n      #icte-speaking-test .icte-badge{\n        display:inline-block;\n        padding:.35rem .7rem;\n        border-radius:999px;\n        font-weight:1000;\n        color:#064e3b;\n        background:rgba(34,197,94,.14);\n        border:1px solid rgba(34,197,94,.25);\n      }\n      #icte-speaking-test .icte-badge--blue{\n        color:#1e3a8a;\n        background:rgba(14,165,233,.12);\n        border-color:rgba(14,165,233,.20);\n      }\n      #icte-speaking-test .icte-hero__title{\n        margin:0 0 6px;\n        font-weight:1000;\n        color:#0f5132;\n      }\n      #icte-speaking-test .icte-hero__sub{\n        margin:0;\n        color:#334155;\n        opacity:.95;\n      }\n      #icte-speaking-test .icte-hero__note{\n        margin-top:10px;\n        color:#334155;\n        padding:.65rem .8rem;\n        border-radius:14px;\n        border:1px solid rgba(34,197,94,.22);\n        background:rgba(240,253,244,.85);\n      }\n      #icte-speaking-test .icte-robotCard{\n        position:relative;\n        border-radius:16px;\n        overflow:hidden;\n        border:1px solid rgba(0,0,0,.10);\n        background:#f8fafc;\n        min-height: 260px;\n        box-shadow:0 12px 22px rgba(0,0,0,.08);\n      }\n      #icte-speaking-test .icte-robotCard img{\n        width:100%;\n        height:100%;\n        object-fit:cover;\n        display:block;\n      }\n      #icte-speaking-test .icte-robotOverlay{\n        position:absolute;\n        left:0; right:0; bottom:0;\n        padding:12px 14px;\n        background: linear-gradient(180deg, transparent, rgba(2,6,23,.65));\n        color:#fff;\n      }\n      #icte-speaking-test .icte-robotOverlay .t1{ font-weight:1000; font-size:1.05rem; }\n      #icte-speaking-test .icte-robotOverlay .t2{ font-size:.95rem; opacity:.92; }\n      #icte-speaking-test .icte-heroHint{\n        margin-top:10px;\n        border-radius:14px;\n        border:1px solid rgba(14,165,233,.22);\n        background: rgba(14,165,233,.10);\n        color:#1e3a8a;\n        padding:.75rem .85rem;\n      }\n      @media (max-width: 980px){\n        #icte-speaking-test .icte-hero{ grid-template-columns:1fr; }\n      }\n    <\/style>\n\n    <script>\n      document.addEventListener(\"DOMContentLoaded\", function(){\n        const root = document.getElementById(\"icte-speaking-test\");\n        if(!root) return;\nfunction cancelGuideSpeech(){\n  guideIsPlaying = false;\n  guideRunId++;      \/\/ invalidate the current guide run immediately\n  stopVoice();       \/\/ stop TTS now\n}\n\n        const $ = (sel)=> root.querySelector(sel);\n        const clamp = (n,a,b)=> Math.max(a, Math.min(b,n));\n        const round1 = (n)=> Math.round(n*10)\/10;\n\/\/ \u2705 Speak and call done() only when TTS finishes (prevents cut-off)\nfunction speakExaminerWait(text, done){\n  if(!speechOk || !ttsEnabled){\n    done && done();\n    return;\n  }\n\n  stopVoice(); \/\/ cancel anything currently speaking\n\n  try{\n    const u = new SpeechSynthesisUtterance(String(text || \"\"));\n    u.lang = (examinerVoice && examinerVoice.lang) ? examinerVoice.lang : \"en-US\";\n    if(examinerVoice) u.voice = examinerVoice;\n    u.rate = 1.0;\n    u.pitch = 1.0;\n\n    u.onend = ()=> { done && done(); };\n    u.onerror = ()=> { done && done(); };\n\n    window.speechSynthesis.speak(u);\n  }catch(e){\n    done && done();\n  }\n}\n\n        function escapeHtml(str){\n          return (str||\"\")\n            .replace(\/&\/g,\"&amp;\").replace(\/<\/g,\"&lt;\").replace(\/>\/g,\"&gt;\")\n            .replace(\/\"\/g,\"&quot;\").replace(\/'\/g,\"&#039;\");\n        }\n\n        function showLoader(show){\n          const el = $('[data-el=\"loader\"]');\n          if(el) el.style.display = show ? \"inline-block\" : \"none\";\n        }\n\n        \/\/ ===== SETTINGS =====\n        let autoAdvance = true;\n\n        function setModePill(mode){\n          const el = $('[data-el=\"modePill\"]');\n          const map = { full:\"Full test\", p1:\"Part 1\", p2:\"Part 2\", p3:\"Part 3\" };\n          el.textContent = \"Mode: \" + (map[mode] || \"Full test\");\n        }\n\n        function refreshAutoPill(){\n          const pill = $('[data-el=\"autoPill\"]');\n          pill.textContent = \"Auto-advance: \" + (autoAdvance ? \"ON\" : \"OFF\");\n          pill.style.background = autoAdvance ? \"rgba(240,253,244,.85)\" : \"rgba(254,226,226,.9)\";\n          pill.style.borderColor = autoAdvance ? \"rgba(22,163,74,.25)\" : \"rgba(220,38,38,.25)\";\n        }\n        refreshAutoPill();\n\n        function setMicPill(text, good){\n          const el = $('[data-el=\"micPill\"]');\n          el.textContent = text;\n          el.style.background = good ? \"rgba(240,253,244,.85)\" : \"rgba(254,226,226,.9)\";\n          el.style.borderColor = good ? \"rgba(22,163,74,.25)\" : \"rgba(220,38,38,.25)\";\n        }\n\n        \/\/ ===== TEXT-TO-SPEECH =====\n        const speechOk = (\"speechSynthesis\" in window) && (\"SpeechSynthesisUtterance\" in window);\n        let ttsEnabled = true;\n        let voicesUsable = [];\n        let examinerVoice = null;\n        let speakRunId = 0;\n\n        function setTtsPill(text, good){\n          const el = $('[data-el=\"ttsPill\"]');\n          el.textContent = text;\n          el.style.background = good ? \"rgba(240,253,244,.85)\" : \"rgba(254,226,226,.9)\";\n          el.style.borderColor = good ? \"rgba(22,163,74,.25)\" : \"rgba(220,38,38,.25)\";\n        }\n\n        function stopVoice(){\n          if(!speechOk) return;\n          speakRunId++;\n          try{ window.speechSynthesis.cancel(); }catch(e){}\n        }\n\n        function speakExaminer(text){\n          if(!speechOk || !ttsEnabled) return;\n          stopVoice();\n          const myRun = ++speakRunId;\n\n          try{\n            const u = new SpeechSynthesisUtterance(String(text||\"\"));\n            u.lang = (examinerVoice && examinerVoice.lang) ? examinerVoice.lang : \"en-US\";\n            if(examinerVoice) u.voice = examinerVoice;\n            u.rate = 1.0;\n            u.pitch = 1.0;\n            if(myRun !== speakRunId) return;\n            window.speechSynthesis.speak(u);\n          }catch(e){}\n        }\n\n        function populateVoices(){\n          if(!speechOk) return;\n\n          const voicesAll = window.speechSynthesis.getVoices() || [];\n          const en = voicesAll.filter(v => (v.lang||\"\").toLowerCase().startsWith(\"en\"));\n          voicesUsable = en.length ? en : voicesAll;\n\n          const sel = $('[data-el=\"voiceSelect\"]');\n          if(!sel) return;\n\n          sel.innerHTML = \"\";\n          if(!voicesUsable.length){\n            sel.innerHTML = '<option value=\"\">No voices found<\/option>';\n            setTtsPill(\"\ud83d\udd0a Voice: Not available\", false);\n            const ttsBox = $('[data-el=\"ttsBox\"]');\n            if(ttsBox) ttsBox.style.display = \"block\";\n            return;\n          }\n\n          voicesUsable.forEach((v, idx) => {\n            const opt = document.createElement(\"option\");\n            opt.value = String(idx);\n            opt.textContent = `${v.name} (${v.lang})`;\n            sel.appendChild(opt);\n          });\n\n          examinerVoice = voicesUsable[0] || null;\n          sel.value = \"0\";\n          sel.onchange = () => {\n            examinerVoice = voicesUsable[Number(sel.value)] || voicesUsable[0] || null;\n          };\n\n          setTtsPill(\"\ud83d\udd0a Voice: Ready\", true);\n          const hint = $('[data-el=\"voiceHint\"]');\n          if(hint) hint.textContent = \"Tip: click Begin once to allow audio in the browser.\";\n        }\n\n        function initTts(){\n          if(!speechOk){\n            setTtsPill(\"\ud83d\udd0a Voice: Not supported\", false);\n            const ttsBox = $('[data-el=\"ttsBox\"]');\n            if(ttsBox) ttsBox.style.display = \"block\";\n            return;\n          }\n          setTtsPill(\"\ud83d\udd0a Voice: Loading\u2026\", true);\n          populateVoices();\n          window.speechSynthesis.onvoiceschanged = populateVoices;\n        }\n\n        \/\/ ===== Part descriptions & instructions =====\nconst PART_INFO = {\n  p1: {\n    title: \"Part 1\",\n    desc: \"The examiner asks the candidate about him\/herself, his\/her home, work or studies and other familiar topics.\",\n    notesTitle: \"Example topic: Art\",\n    bullets: [\n      \"Did you enjoy doing art lessons when you were a child? Why\/Why not?\",\n      \"Do you ever draw or paint pictures now? Why\/Why not?\",\n      \"When was the last time you went to an art gallery or exhibition? Why?\",\n      \"What kind of pictures do you like having in your home? Why?\"\n    ]\n  },\n  p2: {\n    title: \"Part 2\",\n    desc: \"You will have to talk about the topic for one to two minutes.\",\n    bullets: [\n      \"You have one minute to think about what you are going to say.\",\n      \"You can make some notes to help you if you wish.\"\n    ]\n  },\n  p3: {\n    title: \"Part 3\",\n    desc: \"Discussion topics and more general questions related to the Part 2 theme.\",\n    bullets: [\n      \"Different kinds of workplaces (example questions)\",\n      \"The importance of work (example questions)\"\n    ]\n  }\n};\n\n        function renderPartInfo(key, highlightPartTitle){\n          const box = root.querySelector('[data-el=\"partInfo\"]');\n          if(!box) return;\n\n          const info = PART_INFO[key];\n          if(!info){\n            box.innerHTML = '<div class=\"muted\">Choose a part to see instructions.<\/div>';\n            return;\n          }\n\n          let html = `\n            <h4><span class=\"icte-partBadge\">${escapeHtml(highlightPartTitle || info.title)}<\/span>${escapeHtml(info.title)} Instructions<\/h4>\n            <div class=\"muted\">${escapeHtml(info.desc)}<\/div>\n          `;\n\n          if(info.notesTitle){\n            html += `<div style=\"margin-top:.6rem;font-weight:1000;\">${escapeHtml(info.notesTitle)}<\/div>`;\n          }\n\n          if(info.bullets && info.bullets.length){\n            html += \"<ul>\" + info.bullets.map(b => `<li>${escapeHtml(b)}<\/li>`).join(\"\") + \"<\/ul>\";\n          }\n\n          box.innerHTML = html;\n        }\n\n        function modeToInfoKey(mode){\n          if(mode === \"p1\") return \"p1\";\n          if(mode === \"p2\") return \"p2\";\n          if(mode === \"p3\") return \"p3\";\n          return \"p1\";\n        }\n\n        \/\/ ===== CONTENT =====\nconst P1 = [\n  {\n    q: \"Did you enjoy doing art lessons when you were a child? Why or why not?\",\n    model:\n      \"Yes, I did. Art lessons were relaxing and fun, and I liked being creative without worrying too much about getting the \u2018right\u2019 answer.\"\n  },\n  {\n    q: \"Do you ever draw or paint pictures now? Why or why not?\",\n    model:\n      \"Not very often. I\u2019m usually busy, but sometimes I doodle or do simple sketching because it helps me reduce stress and clear my mind.\"\n  },\n  {\n    q: \"When was the last time you went to an art gallery or exhibition? Why?\",\n    model:\n      \"I went a few months ago with a friend. We wanted to do something different on the weekend, and it was inspiring to see various styles and ideas.\"\n  },\n  {\n    q: \"What kind of pictures do you like having in your home? Why?\",\n    model:\n      \"I prefer calm and simple pictures, like landscapes or minimalist art. They make the space feel peaceful and help me relax after a busy day.\"\n  }\n];\n\nconst P2 = [{\n  title: \"Describe a time when you visited a friend or family member at their workplace.\",\n  body:\n`You should say:\n\u2022 who you visited\n\u2022 where this person worked\n\u2022 why you visited this person's workplace\n\nand explain how you felt about visiting this person's workplace.`,\n  model:\n    \"I\u2019d like to describe a time when I visited my older sister at her workplace. \" +\n    \"She worked at a modern office in the city center for a marketing company. \" +\n    \"I visited her because she invited me to have lunch together, and I also wanted to see what her working environment was like. \" +\n    \"When I arrived, I noticed the office was bright, well-organised, and quite friendly, with open spaces and small meeting rooms. \" +\n    \"I felt impressed and a little motivated because the environment looked professional, but it was also comfortable and welcoming.\"\n}];\n\nconst P3 = [\n  \/\/ Different kinds of workplaces\n  {\n    group: \"Different kinds of workplaces\",\n    q: \"What things make an office comfortable to work in?\",\n    model:\n      \"I think good lighting, fresh air, and comfortable chairs are essential. Also, a quiet space for concentration and a friendly atmosphere can make a big difference.\"\n  },\n  {\n    group: \"Different kinds of workplaces\",\n    q: \"Why do some people prefer to work outdoors?\",\n    model:\n      \"Some people enjoy fresh air and variety, and they may dislike sitting inside all day. Outdoor work can also feel more active and less stressful for certain personalities.\"\n  },\n  {\n    group: \"Different kinds of workplaces\",\n    q: \"Do you agree that the building people work in is more important than the colleagues they work with?\",\n    model:\n      \"I don\u2019t fully agree. A good building helps productivity, but supportive colleagues and a positive team culture matter more because they affect motivation and job satisfaction.\"\n  },\n\n  \/\/ The importance of work\n  {\n    group: \"The importance of work\",\n    q: \"What would life be like if people didn't have to work?\",\n    model:\n      \"Life might be more relaxed, and people could spend more time with family or hobbies. However, many people might feel bored or lose a sense of purpose without meaningful work.\"\n  },\n  {\n    group: \"The importance of work\",\n    q: \"Are all jobs of equal importance?\",\n    model:\n      \"In different ways, yes. Some jobs are essential for health and safety, while others support education, entertainment, or the economy. Society needs a wide range of roles to function well.\"\n  },\n  {\n    group: \"The importance of work\",\n    q: \"Why do some people become workaholics?\",\n    model:\n      \"Some people feel pressure to succeed or fear falling behind. Others genuinely enjoy their work and get a sense of identity from it, but it can become unhealthy if they never rest.\"\n  }\n];\n\n\n        \/\/ ===== CHAT =====\n        const chat = $('[data-el=\"chat\"]');\n        function addBubble(role, text){\n          const div = document.createElement(\"div\");\n          div.className = \"icte-bubble \" + (role===\"examiner\" ? \"examiner\" : \"student\");\n          div.innerHTML = escapeHtml(text).replace(\/\\n\/g,\"<br>\");\n          chat.appendChild(div);\n          chat.scrollTop = chat.scrollHeight;\n        }\n\n        \/\/ ===== FLOW =====\n        let flow = [];\n        let idx = -1;\n        let running = false;\n        let awaitingAnswer = false;\n\/\/ \u2705 Guide (spoken instructions) control\nlet guideIsPlaying = false;\nlet guideRunId = 0; \/\/ increment to cancel an ongoing guide speech sequence\n\n        let answerStartTs = 0;      \/\/ when an answer begins\n        let lastAutoAdvanceTs = 0;  \/\/ anti-double-trigger safety\n\n        let currentAnswerText = \"\";\n        let fullTranscriptText = \"\";\n\n        function stopRecording(clearCurrent){\n          manualStop = true;\n          if(recognition && recognizing){\n            try{ recognition.stop(); }catch(e){}\n          }\n          if(clearCurrent){\n            currentAnswerText = \"\";\n            $('[data-el=\"currentAnswer\"]').textContent = \"\";\n            $('[data-el=\"ansWords\"]').textContent = \"0\";\n          }\n        }\n\n        function setCurrentCard(card){\n          $('[data-el=\"currentMeta\"]').textContent = card.part + \" \u2022 \" + card.meta;\n          $('[data-el=\"currentQ\"]').textContent = card.prompt;\n          $('[data-el=\"needHint\"]').textContent = card.needsAnswer\n            ? \"Speak your answer (recording can stay ON). Auto-advance happens after silence OR click Next Question.\"\n            : \"Instruction\/transition (auto-continue).\";\n\n          root.querySelector('[data-action=\"repeat\"]').disabled = false;\n          root.querySelector('[data-action=\"model\"]').disabled = !card.model;\n\n          const startBtn = root.querySelector('[data-action=\"start-rec\"]');\n          const stopBtn  = root.querySelector('[data-action=\"stop-rec\"]');\n\n          if(card.lockRec){\n            stopRecording(false);\n            if(startBtn) startBtn.disabled = true;\n            if(stopBtn)  stopBtn.disabled = true;\n          } else {\n            if(startBtn && !recognizing) startBtn.disabled = false;\n            if(stopBtn) stopBtn.disabled = !recognizing;\n          }\n        }\n\n        function buildFlow(mode){\n          const out = [];\n\n          out.push({\n            part:\"Start\",\n            meta:\"Identity\",\n            prompt:\"Good morning. My name is Robot examiner. Can you tell me your full name, please?\",\n            model:\"My full name is Bella.\",\n            needsAnswer:true\n          });\n\n          if(mode === \"p1\"){\n            out.push({ part:\"Part 1\", meta:\"Start\", prompt:\"We will practice Part 1 only. Let\u2019s begin.\", model:\"\", needsAnswer:false });\n          }\n          if(mode === \"p3\"){\n            out.push({ part:\"Part 3\", meta:\"Start\", prompt:\"We will practice Part 3 only. Let\u2019s begin.\", model:\"\", needsAnswer:false });\n          }\n\n          \/\/ \u2705 Part 1\n          if(mode===\"full\" || mode===\"p1\"){\n            out.push({ part:\"Part 1\", meta:\"Health\", prompt:\"Let\u2019s begin with some questions about health.\", model:\"\", needsAnswer:false });\n            P1.forEach((it,i)=> out.push({\n              part:\"Part 1\",\n              meta:\"Q\"+(i+1),\n              prompt:it.q,\n              model:it.model,\n              needsAnswer:true\n            }));\n          }\n\n          \/\/ \u2705 Part 2 (ONLY ONCE \u2014 fixed duplicate)\n          if(mode===\"full\" || mode===\"p2\"){\n  const card = P2[0];\n\n  \/\/ 1) Tell them what will happen (robot speaks this)\n  out.push({\n    part:\"Part 2\",\n    meta:\"Instructions\",\n    prompt:\"Now I\u2019m going to give you a topic. You have one minute to prepare, then speak for one to two minutes.\",\n    model:\"\",\n    needsAnswer:false,\n    autoDelay: 900\n  });\n\n  \/\/ 2) \u2705 SHOW TOPIC IMMEDIATELY + lock recording + wait 60 seconds\n  out.push({\n    part:\"Part 2\",\n    meta:\"Cue card (Preparation time: 1 minute)\",\n    prompt:\n      \"Preparation time starts now. You have 1 minute. You may write notes. (Recording is locked during preparation.)\\n\\n\"\n      + card.title + \"\\n\\n\" + card.body,\n    model:\"\",\n    needsAnswer:false,\n    autoDelay: 60000,   \/\/ \u2705 wait 60 seconds while the topic is visible\n    lockRec: true       \/\/ \u2705 lock Start\/Stop Recording during prep\n  });\n\n  \/\/ 3) After 60 seconds, allow speaking (show cue again + needsAnswer true)\n  out.push({\n    part:\"Part 2\",\n    meta:\"Answer\",\n    prompt:\"Time is up. Please begin speaking now.\\n\\n\" + card.title + \"\\n\\n\" + card.body,\n    model: card.model,\n    needsAnswer:true\n  });\n}\n\n\n          \/\/ \u2705 Part 3\n          if(mode===\"full\" || mode===\"p3\"){\n            out.push({\n              part:\"Part 3\",\n              meta:\"Transition\",\n              prompt:\"Now I\u2019d like to discuss some more general questions.\",\n              model:\"\",\n              needsAnswer:false\n            });\n\nout.push({\n  part:\"Part 3\",\n  meta:\"Transition\",\n  prompt:\"Now I\u2019d like to discuss some more general questions.\",\n  model:\"\",\n  needsAnswer:false\n});\n\nout.push({ part:\"Part 3\", meta:\"Different kinds of workplaces\", prompt:\"First, let\u2019s talk about different kinds of workplaces.\", model:\"\", needsAnswer:false });\nP3.filter(x=>x.group===\"Different kinds of workplaces\").forEach((it,i)=> out.push({\n  part:\"Part 3\",\n  meta:\"Different kinds of workplaces \u2022 Q\"+(i+1),\n  prompt:it.q,\n  model:it.model,\n  needsAnswer:true\n}));\n\nout.push({ part:\"Part 3\", meta:\"The importance of work\", prompt:\"Now, let\u2019s move on to the importance of work.\", model:\"\", needsAnswer:false });\nP3.filter(x=>x.group===\"The importance of work\").forEach((it,i)=> out.push({\n  part:\"Part 3\",\n  meta:\"The importance of work \u2022 Q\"+(i+1),\n  prompt:it.q,\n  model:it.model,\n  needsAnswer:true\n}));\n\n          }\n\n          out.push({\n            part:\"End\",\n            meta:\"Finish\",\n            prompt:\"Thank you. That is the end of the speaking test.\",\n            model:\"\",\n            needsAnswer:false\n          });\n\n          return out;\n        }\n\n        function getActiveMode(){\n          const activeBtn = root.querySelector('[data-action=\"mode\"].is-active');\n          return activeBtn ? (activeBtn.getAttribute(\"data-mode\") || \"full\") : \"full\";\n        }\n\n        function askNext(){\n          if(!running) return;\n\n          idx++;\n          if(idx >= flow.length){\n            running = false;\n            awaitingAnswer = false;\n            addBubble(\"examiner\", \"Test finished.\");\n            speakExaminer(\"Thank you. That is the end of the speaking test.\");\n            return;\n          }\n\n          const card = flow[idx];\n\n          \/\/ full-test: update instruction panel while moving\n          const activeMode = getActiveMode();\n          if(activeMode === \"full\"){\n            if(card.part === \"Part 1\") renderPartInfo(\"p1\", \"Now: Part 1\");\n            else if(card.part === \"Part 2\") renderPartInfo(\"p2\", \"Now: Part 2\");\n            else if(card.part === \"Part 3\") renderPartInfo(\"p3\", \"Now: Part 3\");\n          }\n\n          setCurrentCard(card);\n\n          addBubble(\"examiner\", card.prompt);\n\nawaitingAnswer = !!card.needsAnswer;\n\n\/\/ \u2705 If it's an instruction\/transition card, WAIT for speech to finish, THEN continue\nif(!awaitingAnswer){\n  const delayAfterSpeech =\n    (typeof card.autoDelay === \"number\") ? card.autoDelay : 350; \/\/ small natural pause after speech\n\n  speakExaminerWait(card.prompt, ()=>{\n    setTimeout(()=>{ if(running) askNext(); }, delayAfterSpeech);\n  });\n\n} else {\n  \/\/ \u2705 For real questions, also ensure the whole question is spoken (but do NOT auto-advance)\n  speakExaminerWait(card.prompt, ()=>{ \/* ready for student *\/ });\n\n  currentAnswerText = \"\";\n  $('[data-el=\"currentAnswer\"]').textContent = \"\";\n  $('[data-el=\"ansWords\"]').textContent = \"0\";\n}\n\n        }\n\n        function repeatQ(){\n          if(idx < 0 || idx >= flow.length) return;\n          const card = flow[idx];\n          addBubble(\"examiner\", \"(Repeat) \" + card.prompt);\n          speakExaminerWait(card.prompt, ()=>{});\n\n        }\n\n        function showModel(){\n          if(idx < 0 || idx >= flow.length) return;\n          const card = flow[idx];\n          if(!card.model) return;\n          addBubble(\"examiner\", \"Sample answer:\\n\" + card.model);\n        }\n\n        \/\/ ===== Recognition (continuous) =====\n        const SR = window.SpeechRecognition || window.webkitSpeechRecognition;\n        let recognition = null;\n        let recognizing = false;\n        let manualStop = false;\n\n        let lastResultTs = 0;\n        let silenceInterval = null;\n\n        function getSilenceMs(){\n          const base = Number($('[data-el=\"silenceSec\"]').value || 6);\n          const part = (flow[idx] && flow[idx].part) ? flow[idx].part : \"\";\n\n          let minSilence = 5;         \/\/ Part 1 \/ general\n          if(part === \"Part 2\") minSilence = 10;\n          if(part === \"Part 3\") minSilence = 7;\n\n          const sec = clamp(Math.max(base, minSilence), 2, 20);\n          return sec * 1000;\n        }\n\n        function getMinAnswerMs(){\n          const part = (flow[idx] && flow[idx].part) ? flow[idx].part : \"\";\n          if(part === \"Part 2\") return 45000;\n          if(part === \"Part 3\") return 12000;\n          if(part === \"Part 1\") return 6000;\n          return 5000;\n        }\n\n        function startSilenceWatcher(){\n          stopSilenceWatcher();\n          silenceInterval = setInterval(()=>{\n            if(!autoAdvance) return;\n            if(!running) return;\n            if(!recognizing) return;\n            if(!awaitingAnswer) return;\n\n            const typedNow = ($('[data-el=\"currentAnswer\"]').textContent||\"\").trim();\n            if(!currentAnswerText.trim() && !typedNow) return;\n\n            const now = Date.now();\n            const silenceMs = getSilenceMs();\n\n            if(lastResultTs && (now - lastResultTs) >= silenceMs){\n              const answeredLongEnough = (answerStartTs && (now - answerStartTs) >= getMinAnswerMs());\n              if(answeredLongEnough){\n                if(now - lastAutoAdvanceTs > 1200){\n                  lastAutoAdvanceTs = now;\n                  finalizeAnswerAndAdvance(true);\n                }\n              }\n            }\n          }, 250);\n        }\n\n        function stopSilenceWatcher(){\n          if(silenceInterval){\n            clearInterval(silenceInterval);\n            silenceInterval = null;\n          }\n        }\n\n        function norm(text){\n          return String(text||\"\")\n            .toLowerCase()\n            .replace(\/[^\\w\\s']\/g,\" \")\n            .replace(\/\\s+\/g,\" \")\n            .trim();\n        }\n\n        function containsStopCommand(text){\n          const t = \" \" + norm(text) + \" \";\n          return (\n            t.includes(\" bye \") ||\n            t.includes(\" goodbye \") ||\n            t.includes(\" good bye \") ||\n            t.includes(\" stop \")\n          );\n        }\n\n        function initSpeech(){\n          if(!SR){\n            setMicPill(\"\ud83c\udf99 Mic: Not supported\", false);\n            const supportBox = $('[data-el=\"supportBox\"]');\n            if(supportBox) supportBox.style.display = \"block\";\n            return;\n          }\n          setMicPill(\"\ud83c\udf99 Mic: Ready\", true);\n\n          recognition = new SR();\n          recognition.lang = \"en-US\";\n          recognition.continuous = true;\n          recognition.interimResults = false;\n\n          recognition.onstart = ()=>{\n            recognizing = true;\n            $('[data-el=\"recState\"]').textContent = \"ON\";\n            setMicPill(\"\ud83c\udf99 Recording\u2026\", true);\n            root.querySelector('[data-action=\"start-rec\"]').disabled = true;\n            root.querySelector('[data-action=\"stop-rec\"]').disabled = false;\n            startSilenceWatcher();\n          };\n\n          recognition.onresult = (event)=>{\n            let chunk = \"\";\n            for(let i=event.resultIndex;i<event.results.length;i++){\n              chunk += (event.results[i][0].transcript || \"\") + \" \";\n            }\n            chunk = chunk.trim();\n            if(!chunk) return;\n\n            lastResultTs = Date.now();\n\n            if(containsStopCommand(chunk)){\n              addBubble(\"student\", chunk);\n              stopRecording(false);\n              running = false;\n              awaitingAnswer = false;\n              addBubble(\"examiner\", \"Thank you. The test is finished. Goodbye!\");\n              speakExaminer(\"Thank you. The test is finished. Goodbye!\");\n              return;\n            }\n\n            if(awaitingAnswer){\n              currentAnswerText = (currentAnswerText ? (currentAnswerText + \" \" + chunk) : chunk).replace(\/\\s+\/g,\" \").trim();\n              $('[data-el=\"currentAnswer\"]').textContent = currentAnswerText;\n\n              const wc = currentAnswerText.trim() ? currentAnswerText.trim().split(\/\\s+\/).length : 0;\n              $('[data-el=\"ansWords\"]').textContent = String(wc);\n            }\n          };\n\n          recognition.onend = ()=>{\n            recognizing = false;\n            $('[data-el=\"recState\"]').textContent = \"OFF\";\n            setMicPill(\"\ud83c\udf99 Stopped\", true);\n            root.querySelector('[data-action=\"start-rec\"]').disabled = false;\n            root.querySelector('[data-action=\"stop-rec\"]').disabled = true;\n            stopSilenceWatcher();\n\n            if(running && !manualStop){\n              try{ recognition.start(); }catch(e){}\n            }\n            manualStop = false;\n          };\n\n          recognition.onerror = ()=>{\n            recognizing = false;\n            $('[data-el=\"recState\"]').textContent = \"OFF\";\n            setMicPill(\"\ud83c\udf99 Mic error\", false);\n            root.querySelector('[data-action=\"start-rec\"]').disabled = false;\n            root.querySelector('[data-action=\"stop-rec\"]').disabled = true;\n            stopSilenceWatcher();\n          };\n        }\n\/\/ \u2705 Robot \u201cHow to use\u201d script (spoken before the test starts)\nconst HOW_TO_SPEAK = [\n  \"Before we start, here is a quick guide to use the buttons.\",\n  \"Step 1. Click Full test, Part 1, Part 2, or Part 3.\",\n  \"Step 2. Click Begin the Test. The robot shows the first question.\",\n  \"Step 3. Click Start Recording and speak. Click Stop Recording anytime.\",\n  \"Step 4. When you finish, click Next Question. Your answer is saved into the full test transcript.\",\n  \"Step 5. Click Sample Answer to view an example response for the current question.\",\n  \"Step 6. After answering several questions, click Band Score to get practice feedback. You can adjust the pronunciation self rating slider if needed.\",\n  \"Okay. Let\u2019s begin.\"\n];\n\n\/\/ \u2705 Speak lines in order (waits for each sentence to finish)\nfunction speakSequence(lines, done){\n  \/\/ Start a NEW guide run; any old run becomes invalid\n  const myGuideRun = ++guideRunId;\n  guideIsPlaying = true;\n\n  if(!speechOk || !ttsEnabled){\n    guideIsPlaying = false;\n    done && done();\n    return;\n  }\n\n  stopVoice(); \/\/ cancel anything currently speaking\n\n  const list = (lines || []).filter(Boolean);\n  let i = 0;\n\n  const speakNext = ()=>{\n    \/\/ \u2705 If user canceled (mode change \/ next \/ reset), stop immediately\n    if(myGuideRun !== guideRunId || !guideIsPlaying){\n      guideIsPlaying = false;\n      return;\n    }\n\n    if(i >= list.length){\n      guideIsPlaying = false;\n      done && done();\n      return;\n    }\n\n    const u = new SpeechSynthesisUtterance(String(list[i++]));\n    u.lang = (examinerVoice && examinerVoice.lang) ? examinerVoice.lang : \"en-US\";\n    if(examinerVoice) u.voice = examinerVoice;\n    u.rate = 1.0;\n    u.pitch = 1.0;\n\n    u.onend = ()=> speakNext();\n    u.onerror = ()=> speakNext();\n\n    try{ window.speechSynthesis.speak(u); }catch(e){ speakNext(); }\n  };\n\n  speakNext();\n}\n\n\n        function startRecording(){\n          if(!recognition) return;\n          manualStop = false;\n          try{ recognition.start(); }catch(e){}\n        }\n\n        \/\/ (stopRecording is declared above)\n\n        function finalizeAnswerAndAdvance(auto){\n          if(!running) return;\n\n          if(!awaitingAnswer){\n            askNext();\n            return;\n          }\n\n          const typed = ($('[data-el=\"currentAnswer\"]').textContent || \"\").trim();\n          const finalAnswer = (typed || currentAnswerText || \"\").trim();\n\n          if(!finalAnswer){\n            if(!auto) askNext();\n            return;\n          }\n\n          addBubble(\"student\", finalAnswer.length > 320 ? (finalAnswer.slice(0,317) + \"\u2026\") : finalAnswer);\n\n          const q = (flow[idx] && flow[idx].prompt) ? flow[idx].prompt : \"\";\n          fullTranscriptText += (fullTranscriptText ? \"\\n\\n\" : \"\") + \"Q: \" + q + \"\\nA: \" + finalAnswer;\n          $('[data-el=\"fullTranscript\"]').textContent = fullTranscriptText;\n\n          currentAnswerText = \"\";\n          $('[data-el=\"currentAnswer\"]').textContent = \"\";\n          $('[data-el=\"ansWords\"]').textContent = \"0\";\n          awaitingAnswer = false;\n\n          setTimeout(()=>{ if(running) askNext(); }, auto ? 450 : 250);\n        }\n\n        \/\/ ===== Band scoring (unchanged logic) =====\n        function tokenize(text){\n          return (text||\"\").toLowerCase().replace(\/[^a-z0-9\\s']\/g,\" \").split(\/\\s+\/).filter(Boolean);\n        }\n        const STOP = new Set((\"a an the and or but so because if when while where which that to of in on at for with without from as is are was were be been being do does did have has had will would can could should may might i you we they he she it my your our their me him her us them this these those there here just really very\").split(\" \"));\n        function lexicalDiversity(tokens){\n          const content=tokens.filter(t=>!STOP.has(t));\n          const total=content.length||1;\n          return (new Set(content).size)\/total;\n        }\n        function countFillers(tokens){\n          const ph=[[\"um\"],[\"uh\"],[\"erm\"],[\"like\"],[\"you\",\"know\"],[\"sort\",\"of\"],[\"kind\",\"of\"],[\"actually\"]];\n          let c=0;\n          for(let i=0;i<tokens.length;i++){\n            for(const p of ph){\n              let ok=true;\n              for(let j=0;j<p.length;j++){ if(tokens[i+j]!==p[j]){ ok=false; break; } }\n              if(ok) c++;\n            }\n          }\n          return c;\n        }\n        function grammarSignals(text){\n          const t=(text||\"\").toLowerCase();\n          return {\n            hasPast:\/\\b\\w+ed\\b\/.test(t),\n            hasFuture:\/\\bwill\\b|\\bgoing to\\b\/.test(t),\n            hasPerfect:\/\\b(have|has|had)\\b\/.test(t),\n            modals:(t.match(\/\\b(would|could|should|might|may)\\b\/g)||[]).length,\n            linkers:(t.match(\/\\b(however|although|because|therefore|for example|for instance|on the other hand|whereas|despite|in addition)\\b\/g)||[]).length\n          };\n        }\n        function sentenceStats(text){\n          const parts=(text||\"\").split(\/[.!?]+\/).map(s=>s.trim()).filter(Boolean);\n          const lens=parts.map(s=>tokenize(s).length).filter(n=>n>0);\n          const count=parts.length||0;\n          const avg=lens.length?(lens.reduce((a,b)=>a+b,0)\/lens.length):0;\n          return {count,avg};\n        }\n        function clearScore(){\n          $('[data-el=\"bandOut\"]').textContent=\"\u2014\";\n          $('[data-el=\"mFlu\"]').textContent=\"\u2014\";\n          $('[data-el=\"mLex\"]').textContent=\"\u2014\";\n          $('[data-el=\"mGram\"]').textContent=\"\u2014\";\n          $('[data-el=\"mPron\"]').textContent=\"\u2014\";\n          $('[data-el=\"nFlu\"]').textContent=\"\";\n          $('[data-el=\"nLex\"]').textContent=\"\";\n          $('[data-el=\"nGram\"]').textContent=\"\";\n          $('[data-el=\"nPron\"]').textContent=\"\";\n          const tips = $('[data-el=\"tips\"]');\n          tips.style.display=\"none\";\n          tips.innerHTML=\"\";\n        }\n        function scoreNow(){\n          const text = (fullTranscriptText || \"\").trim();\n          if(!text){ alert(\"No transcript yet. Answer at least one question first.\"); return; }\n\n          const tokens = tokenize(text);\n          const words = tokens.length;\n          const minutes = Math.max(1, words \/ 130);\n          const wpm = words \/ minutes;\n\n          const fillerPerMin = countFillers(tokens)\/minutes;\n          const ttr = lexicalDiversity(tokens);\n          const gs = grammarSignals(text);\n          const sent = sentenceStats(text);\n          const pronSelf = Number($('[data-el=\"pronSelf\"]').value || 0);\n\n          let flu=6.0;\n          if(wpm>=120 && wpm<=170) flu+=1.0;\n          else if(wpm>=100 && wpm<120) flu+=0.4;\n          else if(wpm<85) flu-=1.0;\n          if(fillerPerMin<=2) flu+=0.5;\n          else if(fillerPerMin>5) flu-=0.9;\n          if(gs.linkers>=2) flu+=0.4;\n          if(sent.count>=3 && sent.avg>=9) flu+=0.3;\n          flu=clamp(flu,0,9);\n\n          let lex=6.0;\n          const lenF=clamp(words\/80,0.6,1.0);\n          if(ttr>=0.55) lex+=1.2*lenF;\n          else if(ttr>=0.45) lex+=0.6*lenF;\n          else if(ttr<0.35) lex-=0.8*lenF;\n          if(gs.linkers>=2) lex+=0.3;\n          lex=clamp(lex,0,9);\n\n          let gram=6.0;\n          let variety=0;\n          if(gs.hasPast) variety++;\n          if(gs.hasFuture) variety++;\n          if(gs.hasPerfect) variety++;\n          if(gs.modals>=1) variety++;\n          if(variety>=3) gram+=0.8;\n          else if(variety===2) gram+=0.4;\n          else if(variety===0) gram-=0.8;\n          if(sent.avg>0 && sent.avg<7) gram-=0.6;\n          gram=clamp(gram,0,9);\n\n          const pron = clamp(pronSelf,0,9);\n          const overall = round1((flu+lex+gram+pron)\/4);\n\n          $('[data-el=\"bandOut\"]').textContent = overall;\n          $('[data-el=\"mFlu\"]').textContent = round1(flu);\n          $('[data-el=\"mLex\"]').textContent = round1(lex);\n          $('[data-el=\"mGram\"]').textContent = round1(gram);\n          $('[data-el=\"mPron\"]').textContent = round1(pron);\n\n          $('[data-el=\"nFlu\"]').textContent = \"WPM~ \" + Math.round(wpm) + \" \u2022 Fillers\/min: \" + round1(fillerPerMin);\n          $('[data-el=\"nLex\"]').textContent = \"Lexical variety (TTR): \" + round1(ttr);\n          $('[data-el=\"nGram\"]').textContent = \"Sentences: \" + sent.count + \" \u2022 Avg length: \" + round1(sent.avg);\n          $('[data-el=\"nPron\"]').textContent = \"Self-rated pronunciation\";\n\n          const tipsEl = $('[data-el=\"tips\"]');\n          const tips = [];\n          if(words<40) tips.push(\"Speak longer and develop your ideas with reasons and examples.\");\n          if(fillerPerMin>5) tips.push(\"Reduce fillers (um\/uh\/like). Pause silently instead.\");\n          if(gs.linkers<2) tips.push(\"Use more linking words: because, however, for example, in addition.\");\n          if(variety<2) tips.push(\"Show more grammar range: past, future, modals, present perfect.\");\n          if(sent.avg<7) tips.push(\"Combine short sentences into complex ones.\");\n\n          tipsEl.style.display=\"block\";\n          tipsEl.innerHTML = \"<strong>Feedback:<\/strong><ul>\" +\n            (tips.length ? tips : [\"Good work! Add clearer examples and use more precise vocabulary.\"])\n            .map(t=>\"<li>\"+escapeHtml(t)+\"<\/li>\").join(\"\") +\n            \"<\/ul>\";\n        }\n\n        \/\/ ===== Mode handling =====\n        function setActiveMode(mode){\n          root.querySelectorAll('[data-action=\"mode\"]').forEach(b=>{\n            b.classList.remove(\"is-active\",\"icte-btn--primary\");\n            b.classList.add(\"icte-btn--ghost\");\n          });\n          const btn = root.querySelector('[data-action=\"mode\"][data-mode=\"'+mode+'\"]');\n          if(btn){\n            btn.classList.add(\"is-active\",\"icte-btn--primary\");\n            btn.classList.remove(\"icte-btn--ghost\");\n          }\n          setModePill(mode);\n        }\n        setActiveMode(\"full\");\n        renderPartInfo(\"p1\");\n\n        \/\/ ===== Buttons wiring =====\n        function bind(action, fn){\n          const el = root.querySelector('[data-action=\"'+action+'\"]');\n          if(el) el.addEventListener(\"click\", fn);\n        }\n\n        root.querySelectorAll('[data-action=\"mode\"]').forEach(btn=>{\n  btn.addEventListener(\"click\", ()=>{\n    cancelGuideSpeech(); \/\/ \u2705 stop reading instructions if user switches parts\n\n    const mode = btn.getAttribute(\"data-mode\") || \"full\";\n    setActiveMode(mode);\n    renderPartInfo(modeToInfoKey(mode));\n\n    if(running){\n      chat.innerHTML = \"\";\n      clearScore();\n      currentAnswerText = \"\";\n      fullTranscriptText = \"\";\n      $('[data-el=\"currentAnswer\"]').textContent = \"\";\n      $('[data-el=\"fullTranscript\"]').textContent = \"\";\n      $('[data-el=\"ansWords\"]').textContent = \"0\";\n      idx = -1;\n\n      flow = buildFlow(mode);\n      running = true;\n      awaitingAnswer = false;\n\n      \/\/ \u2705 Start immediately after switching modes (no guide re-read)\n      askNext();\n    }\n  });\n});\n\n\n        bind(\"toggle-auto\", ()=>{\n          autoAdvance = !autoAdvance;\n          refreshAutoPill();\n        });\n\n        bind(\"toggle-voice\", ()=>{\n          ttsEnabled = !ttsEnabled;\n          if(!ttsEnabled) stopVoice();\n          setTtsPill(\"\ud83d\udd0a Voice: \" + (ttsEnabled ? \"ON\" : \"OFF\"), true);\n        });\n\n        bind(\"stop-voice\", ()=>{ cancelGuideSpeech(); });\n\n        bind(\"begin\", ()=>{\n  const mode = getActiveMode();\n  setModePill(mode);\n  renderPartInfo(modeToInfoKey(mode)); \/\/ show matching instructions\n\n  showLoader(false);\n  chat.innerHTML = \"\";\n  clearScore();\n\n  currentAnswerText = \"\";\n  fullTranscriptText = \"\";\n  $('[data-el=\"currentAnswer\"]').textContent = \"\";\n  $('[data-el=\"fullTranscript\"]').textContent = \"\";\n  $('[data-el=\"ansWords\"]').textContent = \"0\";\n  $('[data-el=\"needHint\"]').textContent = \"\";\n  idx = -1;\n\n  flow = buildFlow(mode);\n  running = true;\n  root.querySelector('[data-action=\"repeat\"]').disabled = false;\n\n  \/\/ \u2705 Speak instructions first, THEN start the test\n  speakSequence(HOW_TO_SPEAK, ()=>{\n    if(running) askNext();\n  });\n});\n\n\n        bind(\"repeat\", repeatQ);\n        bind(\"model\", showModel);\n\n        bind(\"reset\", ()=>{\n  cancelGuideSpeech(); \/\/ \u2705 stop guide if playing\n          running = false;\n          awaitingAnswer = false;\n          idx = -1;\n          chat.innerHTML = \"\";\n          currentAnswerText = \"\";\n          fullTranscriptText = \"\";\n          $('[data-el=\"currentAnswer\"]').textContent = \"\";\n          $('[data-el=\"fullTranscript\"]').textContent = \"\";\n          $('[data-el=\"ansWords\"]').textContent = \"0\";\n          $('[data-el=\"currentMeta\"]').textContent = \"\u2014\";\n          $('[data-el=\"currentQ\"]').textContent = \"\u2014\";\n          $('[data-el=\"needHint\"]').textContent = \"\";\n          clearScore();\n          stopVoice();\n          stopRecording(true);\n          renderPartInfo(modeToInfoKey(getActiveMode()));\n        });\n\n        bind(\"start-rec\", ()=> startRecording());\n        bind(\"stop-rec\", ()=> stopRecording(false));\n        bind(\"next\", ()=>{\n  \/\/ \u2705 If the guide is speaking, stop it and start the test immediately\n  if(guideIsPlaying){\n    cancelGuideSpeech();\n    if(running && idx === -1){\n      askNext(); \/\/ start first card right away\n    }\n    return;\n  }\n\n  \/\/ normal behavior during the test\n  finalizeAnswerAndAdvance(false);\n});\n\n        bind(\"clear-current\", ()=>{\n          currentAnswerText = \"\";\n          $('[data-el=\"currentAnswer\"]').textContent = \"\";\n          $('[data-el=\"ansWords\"]').textContent = \"0\";\n        });\n        bind(\"score\", scoreNow);\n\n        $('[data-el=\"pronSelf\"]').addEventListener(\"input\", function(){\n          $('[data-el=\"pronVal\"]').textContent = this.value;\n        });\n\n        \/\/ ===== Init mic + TTS =====\n        initSpeech();\n        initTts();\n      });\n    <\/script>\n\n  <\/section>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Overview Test 1 Test 2 Test 3 Test 4 Test 5 Listening Reading Writing IELTS Speaking Robot Examiner Practice with<\/p>\n","protected":false},"author":1,"featured_media":581,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[25,29,35],"tags":[],"class_list":["post-694","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ielts","category-speaking","category-test-5"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/694","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/comments?post=694"}],"version-history":[{"count":1,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/694\/revisions"}],"predecessor-version":[{"id":695,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/694\/revisions\/695"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/581"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=694"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=694"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=694"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}