{"id":380,"date":"2025-12-03T06:09:30","date_gmt":"2025-12-03T06:09:30","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=380"},"modified":"2025-12-03T06:09:30","modified_gmt":"2025-12-03T06:09:30","slug":"vacation-listening","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/vacation-listening\/","title":{"rendered":"Vacation &#8211; Listening"},"content":{"rendered":"\n<p>This Robot is used for practicing English at level A2. In order to interact with this Robot smoothly, please use a laptop, desktop, or iPad. Do not use Smartphones, though they are sometimes fine. It depends on the updated browsers and Internet connection.<\/p>\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Listening \u2013 Manuel\u2019s Vacation<\/title>\n    <style>\n        \/* Reset and Base Styles *\/\n        * {\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: Arial, sans-serif;\n            background-color: #f4f6f8;\n            margin: 0;\n            padding: 0;\n            display: flex;\n            flex-direction: column;\n            min-height: 100vh;\n        }\n\n        \/* Menu Styles *\/\n        .menu {\n            width: 100%;\n            background-color: #28a745;\n            padding: 10px;\n            text-align: center;\n            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n            margin-bottom: 10px;\n            overflow-x: auto;\n            white-space: nowrap;\n        }\n        .menu a {\n            color: #fff;\n            text-decoration: none;\n            margin: 0 10px;\n            font-weight: bold;\n            font-size: 14px;\n            display: inline-block;\n        }\n        .menu a:hover,\n        .menu a:focus {\n            text-decoration: underline;\n            outline: 2px dashed #fff;\n            outline-offset: 4px;\n        }\n\n        \/* Container Styles *\/\n        .container {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            padding: 10px;\n            box-sizing: border-box;\n            flex: 1 0 auto;\n            width: 100%;\n        }\n\n        .image-chat-wrapper {\n            display: flex;\n            flex-direction: column;\n            width: 100%;\n            max-width: 900px;\n            flex: 1;\n        }\n\n        .image-container {\n            flex: 1;\n            text-align: center;\n            margin-bottom: 15px;\n        }\n        .image-container img {\n            width: 100%;\n            height: auto;\n            max-height: 260px;\n            object-fit: cover;\n            border-radius: 15px;\n            box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);\n        }\n        .sentence-text {\n            margin: 10px 0;\n            font-weight: bold;\n            color: #333;\n            font-size: 16px;\n        }\n        .word-box {\n            margin-top: 10px;\n            padding: 10px;\n            background-color: #fff;\n            border: 2px solid #28a745;\n            border-radius: 8px;\n            box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);\n            font-size: 15px;\n            font-weight: bold;\n            color: #28a745;\n            text-align: left;\n            max-height: 220px;\n            overflow-y: auto;\n        }\n\n        .audio-player {\n            margin-top: 10px;\n        }\n        .audio-player audio {\n            width: 100%;\n        }\n\n        .script-button {\n            margin-top: 8px;\n            background-color: #17a2b8;\n            color: #fff;\n            border: none;\n            border-radius: 5px;\n            padding: 8px 12px;\n            font-size: 14px;\n            font-weight: bold;\n            cursor: pointer;\n        }\n        .script-button:hover,\n        .script-button:focus {\n            background-color: #138496;\n            outline: none;\n        }\n\n        .chat-container {\n            background-color: #28a745;\n            border-radius: 15px;\n            box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);\n            border: 3px solid #007bff;\n            overflow: hidden;\n            display: flex;\n            flex-direction: column;\n            height: auto;\n            max-height: 580px;\n            flex: 1;\n        }\n        .chat-header {\n            background-color: #1e7e34;\n            color: #fff;\n            padding: 12px;\n            text-align: center;\n            border-bottom: 1px solid #155724;\n            position: relative;\n        }\n        .chat-header h2 {\n            margin: 0;\n            font-size: 18px;\n        }\n        .chat-header .loader {\n            position: absolute;\n            right: 20px;\n            top: 50%;\n            transform: translateY(-50%);\n            display: none;\n        }\n        .chat-messages {\n            padding: 12px;\n            overflow-y: auto;\n            flex: 1;\n            background-color: #fff;\n            max-height: 400px;\n        }\n        .message {\n            margin-bottom: 10px;\n            padding: 10px;\n            border-radius: 6px;\n            font-size: 14px;\n            line-height: 1.5;\n        }\n        .bot-message {\n            background-color: #17a2b8;\n            color: #fff;\n            text-align: left;\n        }\n\n        .chat-input {\n            display: flex;\n            align-items: center;\n            background-color: #c3e6cb;\n            padding: 10px;\n            justify-content: space-between;\n            flex-wrap: wrap;\n        }\n        .send-button, .stop-button {\n            background-color: #17a2b8;\n            color: #fff;\n            font-weight: bold;\n            border: none;\n            border-radius: 5px;\n            padding: 12px 16px;\n            cursor: pointer;\n            transition: background-color 0.3s;\n            flex: 1;\n            margin: 6px;\n            max-width: 150px;\n            font-size: 16px;\n        }\n        .stop-button {\n            background-color: #dc3545;\n        }\n        .send-button:hover,\n        .stop-button:hover,\n        .send-button:focus,\n        .stop-button:focus {\n            background-color: #218838;\n            outline: none;\n        }\n\n        .voice-selection {\n            margin: 10px 0;\n            width: 100%;\n            max-width: 300px;\n        }\n        .voice-selection label {\n            display: block;\n            margin-bottom: 5px;\n            font-weight: bold;\n            color: #333;\n        }\n        .voice-selection select {\n            width: 100%;\n            padding: 8px;\n            border-radius: 5px;\n            border: 1px solid #ccc;\n            font-size: 14px;\n        }\n\n        .loader {\n            border: 4px solid #f3f3f3;\n            border-top: 4px solid #17a2b8;\n            border-radius: 50%;\n            width: 20px;\n            height: 20px;\n            animation: spin 2s linear infinite;\n            display: inline-block;\n            margin-left: 10px;\n        }\n\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n\n        .short-answer-input,\n        .blank-input {\n            width: 100%;\n            max-width: 280px;\n            padding: 4px 6px;\n            font-size: 13px;\n            border-radius: 4px;\n            border: 1px solid #ccc;\n        }\n\n        .journal-block {\n            margin-top: 6px;\n            padding: 6px;\n            border-radius: 4px;\n        }\n\n        .journal-city {\n            font-weight: bold;\n            margin-bottom: 2px;\n        }\n\n        .correct {\n            background-color: #d4edda !important;\n        }\n        .incorrect {\n            background-color: #f8d7da !important;\n        }\n        .unanswered {\n            background-color: #fff3cd !important;\n        }\n\n        \/* Responsive *\/\n        @media (orientation: portrait) {\n            .image-chat-wrapper {\n                flex-direction: column;\n            }\n            .image-container, .chat-container {\n                width: 100%;\n                max-width: 100%;\n            }\n        }\n\n        @media (orientation: landscape) and (min-width: 700px) {\n            .image-chat-wrapper {\n                flex-direction: row;\n                justify-content: space-between;\n            }\n            .image-container, .chat-container {\n                width: 48%;\n                max-width: 48%;\n            }\n        }\n    <\/style>\n<\/head>\n<body>\n    <div class=\"menu\">\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-conversation\/\">Conversation<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-vocabulary\/\">Vocabulary<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-real-life\/\">Real Life<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-describing-pictures\/\">Describing Pictures<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-reading-comprehension\/\">Reading<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-listening\/\">Listening<\/a>\n    <\/div>\n\n    <div class=\"container\">\n        <div class=\"image-chat-wrapper\">\n            <div class=\"image-container\">\n                <!-- You can replace this with Manuel\u2019s photo or the map image -->\n                <img decoding=\"async\" src=\"http:\/\/asiacall.info\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-03-at-11.56.31-AM.png\"\n                     alt=\"Manuel talking about his summer vacation in Europe\">\n                <div class=\"sentence-text\">\n                    Listen to Manuel, from Mexico, talk about his summer vacation. Then complete his journal.\n                <\/div>\n\n                <div class=\"audio-player\">\n                    <!-- \ud83d\udd0a Change the src below to the real URL of Track 76 on your site -->\n                    <audio id=\"manuel-audio\" controls>\n                        <source src=\"\/mnt\/data\/BTP_L1_SB_076.mp3\" type=\"audio\/mpeg\">\n                        Your browser does not support the audio element.\n                    <\/audio>\n                <\/div>\n\n                <!-- Button to play the script with two robot voices -->\n                <button id=\"play-script-btn\" class=\"script-button\" type=\"button\">\n                    Play Script (Robot Voices)\n                <\/button>\n\n                <div id=\"word-box\" class=\"word-box\">\n                    PART 1<br>\n                    1. How many days was Manuel\u2019s vacation?<br><br>\n                    PART 2 \u2013 Listen again and complete Manuel\u2019s journal.<br>\n                    London \u2013 Paris \u2013 Lucerne \u2013 Florence \u2013 Rome \u2013 Venice \u2013 Innsbruck \u2013 Bonn \u2013 Amsterdam.\n                <\/div>\n            <\/div>\n\n            <div class=\"chat-container\">\n                <div class=\"chat-header\">\n                    <h2>Listening \u2013 Manuel\u2019s Vacation<\/h2>\n                    <div class=\"loader\" id=\"synthesis-loader\"><\/div>\n                <\/div>\n                <div class=\"chat-messages\" id=\"chat-messages\">\n                    <!-- Questions and blanks will appear here -->\n                <\/div>\n                <div class=\"chat-input\">\n                    <div class=\"voice-selection\">\n                        <label for=\"voice-select\">Choose Voice for Feedback:<\/label>\n                        <select id=\"voice-select\" aria-label=\"Select Voice\">\n                            <option value=\"\">Loading voices&#8230;<\/option>\n                        <\/select>\n                    <\/div>\n                    <button id=\"start-btn\" class=\"send-button\" aria-label=\"Check Answers\">Check Answers<\/button>\n                    <button id=\"stop-btn\" class=\"stop-button\" aria-label=\"Stop Voice\">Stop Voice<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"menu\">\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-conversation\/\">Conversation<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-vocabulary\/\">Vocabulary<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-real-life\/\">Real Life<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-describing-pictures\/\">Describing Pictures<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-reading-comprehension\/\">Reading<\/a>\n            <a href=\"https:\/\/i-cte.org\/robot\/vacation-listening\/\">Listening<\/a>\n    <\/div>\n\n<script>\n\/* ============================\n   Listening \u2013 Manuel\u2019s Vacation\n   ============================ *\/\n\n\/* Script: Interviewer = I, Manuel = M *\/\nconst listeningScript = `I: So, Manuel. You went on vacation to Europe last summer. How was it?\nM: It was great. I went on a bus tour and visited many countries.\nI: That sounds exciting!\nM: Yes, it was. First we flew to London and went sightseeing. I saw Big Ben and the London Eye, and Buckingham Palace, of course.\nI: Was the Queen there?\nM: I don\u2019t think so. I didn\u2019t see her! Anyway, then we went by bus to Paris. We stayed there for one day, and visited a lot of museums. I went to the top of the Eiffel Tower.\nI: Wow! What was the view like?\nM: Amazing. Then we drove to Switzerland and I went hiking in the mountains near Lucerne. And after that was Italy \u2026 Florence, Rome and Venice.\nI: Oh, Venice! It\u2019s my favorite city.\nM: Really? I loved it. I bought a lot of souvenirs there. And then we came back through Innsbruck, in Austria. I had a guided tour of the city, and then in Germany I went on a cruise on the Rhine, near Bonn.\nI: Wow! What was that like?\nM: I\u2019m not sure. I fell asleep! We finished in Amsterdam, in Holland. I ate a lot of cheese, and flew back from there.\nI: That\u2019s a big trip.\nM: Yes. All in eight days! It was hard, but a lot of fun.`;\n\n\/* PART 1 \u2013 short answer *\/\nconst part1Question = {\n  id: 1,\n  text: \"1) How many days was Manuel\u2019s vacation?\",\n  keywordSets: [[\"eight\"], [\"8\"]]\n};\n\n\/* PART 2 \u2013 journal blanks *\/\nconst journalItems = [\n  {\n    id: \"lon_big\",\n    city: \"London\",\n    promptBefore: \"saw Big\",\n    promptAfter: \"the London Eye and Buckingham Palace (but didn\u2019t see the ________)\",\n    firstBlankLabel: \"1) Big ________\",\n    secondBlank: false\n  }\n];\n\n\/* For easier checking, define all blanks separately *\/\nconst blankItems = [\n  { id: \"lon_big\", city: \"London\", label: \"London: saw Big ________\", answers: [\"ben\"] },\n  { id: \"lon_eye\", city: \"London\", label: \"London: the London ________\", answers: [\"eye\"] },\n  { id: \"lon_queen\", city: \"London\", label: \"London: ... and Buckingham Palace (but didn\u2019t see the ________)\", answers: [\"queen\"] },\n\n  { id: \"par_mus\", city: \"Paris\", label: \"Paris: visited a lot of ________\", answers: [\"museums\",\"museum\"] },\n  { id: \"par_top\", city: \"Paris\", label: \"Paris: went to the ________ of the Eiffel Tower\", answers: [\"top\"] },\n\n  { id: \"luc_hike\", city: \"Lucerne\", label: \"Lucerne: went ________ in the mountains\", answers: [\"hiking\",\"walking\",\"hiking in the mountains\"] },\n\n  \/\/ Florence and Rome sentence \u2013 no blank; just info\n  { id: \"ven_souv\", city: \"Venice\", label: \"Venice: bought a lot of ________\", answers: [\"souvenirs\",\"souvenir\"] },\n\n  { id: \"inn_tour\", city: \"Innsbruck\", label: \"Innsbruck: had a guided ________\", answers: [\"tour\"] },\n\n  { id: \"bon_cruise\", city: \"Bonn\", label: \"Bonn: a ________ on the Rhine (but I fell asleep!)\", answers: [\"cruise\"] },\n\n  { id: \"ams_cheese\", city: \"Amsterdam\", label: \"Amsterdam: ate a lot of ________\", answers: [\"cheese\"] }\n];\n\nconst summaryMessages = [\n  \"Nice listening! Keep going.\",\n  \"Good job. Listen again and try to improve your score.\",\n  \"You\u2019re doing well. Practice this activity again later.\"\n];\n\nconst positiveMessages = [\n  \"Excellent comprehension!\",\n  \"Great work! You understood Manuel\u2019s vacation.\",\n  \"Very good! Your answers match the audio quite well.\"\n];\n\n\/* Speech synthesis *\/\nconst speechSynthesisSupported = \"speechSynthesis\" in window;\nlet voices = [];\nlet selectedVoice = null; \/\/ feedback\nlet interviewerVoice = null;\nlet manuelVoice = null;\n\nconst desiredVoiceNames = [\n  \"Google US English\",\n  \"Google US English Female\",\n  \"Google UK English Male\",\n  \"Google UK English Female\"\n];\n\n\/* ========== INITIALISE ========== *\/\n\nfunction initialize() {\n  loadVoices();\n\n  if (!speechSynthesisSupported) {\n    appendMessage(\"Sorry, your browser does not support speech synthesis.\", \"bot\");\n  }\n\n  document.getElementById(\"start-btn\").onclick = checkAnswers;\n  document.getElementById(\"stop-btn\").onclick = stopSpeaking;\n  document.getElementById(\"play-script-btn\").onclick = playScript;\n\n  buildTasks();\n\n  const intro =\n    \"First, listen to Manuel talk about his summer vacation. Then listen again and answer the question and complete his journal.\";\n  appendMessage(intro, \"bot\");\n  if (speechSynthesisSupported) {\n    showSynthesisLoader(true);\n    sayText(intro, \"en-US\").then(() => showSynthesisLoader(false));\n  }\n}\n\n\/* ========== VOICES ========== *\/\n\nfunction loadVoices() {\n  if (!speechSynthesisSupported) return;\n  voices = window.speechSynthesis.getVoices();\n  if (voices.length === 0) {\n    window.speechSynthesis.onvoiceschanged = () => {\n      voices = window.speechSynthesis.getVoices();\n      populateVoiceList();\n    };\n  } else {\n    populateVoiceList();\n  }\n}\n\nfunction chooseDialogueVoices(filteredVoices) {\n  interviewerVoice =\n    filteredVoices.find(v => \/male\/i.test(v.name)) ||\n    filteredVoices.find(v => \/UK English Male\/i.test(v.name)) ||\n    filteredVoices[0];\n\n  manuelVoice =\n    filteredVoices.find(v => v !== interviewerVoice && \/female\/i.test(v.name)) ||\n    filteredVoices.find(v => v !== interviewerVoice) ||\n    interviewerVoice;\n}\n\nfunction populateVoiceList() {\n  const voiceSelect = document.getElementById(\"voice-select\");\n  voiceSelect.innerHTML = \"\";\n\n  if (!speechSynthesisSupported) {\n    const option = document.createElement(\"option\");\n    option.value = \"\";\n    option.textContent = \"No voices available\";\n    voiceSelect.appendChild(option);\n    voiceSelect.disabled = true;\n    return;\n  }\n\n  const filteredVoices = voices.filter((voice) =>\n    desiredVoiceNames.includes(voice.name) || voice.lang.startsWith(\"en-\")\n  );\n\n  if (filteredVoices.length === 0) {\n    const option = document.createElement(\"option\");\n    option.value = \"\";\n    option.textContent = \"Default browser voice\";\n    voiceSelect.appendChild(option);\n    voiceSelect.disabled = true;\n    return;\n  }\n\n  filteredVoices.forEach((voice, index) => {\n    const option = document.createElement(\"option\");\n    option.value = index;\n    option.textContent = `${voice.name} (${voice.lang})`;\n    voiceSelect.appendChild(option);\n  });\n\n  voiceSelect.selectedIndex = 0;\n  selectedVoice = filteredVoices[0];\n\n  chooseDialogueVoices(filteredVoices);\n\n  voiceSelect.onchange = () => {\n    const selectedIndex = voiceSelect.value;\n    selectedVoice = filteredVoices[selectedIndex];\n  };\n}\n\n\/* ========== BUILD UI ========== *\/\n\nfunction buildTasks() {\n  const chat = document.getElementById(\"chat-messages\");\n  chat.innerHTML = \"\";\n\n  \/\/ PART 1\n  const p1Title = document.createElement(\"h3\");\n  p1Title.textContent = \"PART 1 \u2013 Answer the question.\";\n  p1Title.style.margin = \"4px 0\";\n  p1Title.style.fontSize = \"16px\";\n  chat.appendChild(p1Title);\n\n  const p1Wrapper = document.createElement(\"div\");\n  p1Wrapper.style.marginBottom = \"8px\";\n  p1Wrapper.id = `p1-q${part1Question.id}`;\n\n  const p1Label = document.createElement(\"label\");\n  p1Label.textContent = part1Question.text;\n  p1Label.style.display = \"block\";\n  p1Label.style.marginBottom = \"2px\";\n\n  const p1Input = document.createElement(\"input\");\n  p1Input.type = \"text\";\n  p1Input.className = \"short-answer-input\";\n  p1Input.id = `p1-input-${part1Question.id}`;\n\n  p1Wrapper.appendChild(p1Label);\n  p1Wrapper.appendChild(p1Input);\n  chat.appendChild(p1Wrapper);\n\n  \/\/ PART 2 \u2013 journal\n  const p2Title = document.createElement(\"h3\");\n  p2Title.textContent = \"PART 2 \u2013 Complete Manuel\u2019s journal.\";\n  p2Title.style.marginTop = \"10px\";\n  p2Title.style.fontSize = \"16px\";\n  chat.appendChild(p2Title);\n\n  let currentCity = \"\";\n  blankItems.forEach(item => {\n    const block = document.createElement(\"div\");\n    block.className = \"journal-block\";\n    block.id = `p2-block-${item.id}`;\n\n    if (item.city !== currentCity) {\n      currentCity = item.city;\n      const cityLabel = document.createElement(\"div\");\n      cityLabel.className = \"journal-city\";\n      cityLabel.textContent = currentCity + \":\";\n      block.appendChild(cityLabel);\n    }\n\n    const line = document.createElement(\"div\");\n    line.style.marginBottom = \"2px\";\n\n    const text = document.createElement(\"span\");\n    text.textContent = item.label.replace(\"______\", \"\");\n    line.appendChild(text);\n\n    const input = document.createElement(\"input\");\n    input.type = \"text\";\n    input.className = \"blank-input\";\n    input.id = `p2-input-${item.id}`;\n    line.appendChild(input);\n\n    block.appendChild(line);\n    chat.appendChild(block);\n  });\n\n  const note = document.createElement(\"p\");\n  note.style.fontSize = \"12px\";\n  note.style.color = \"#555\";\n  note.textContent = \"Tip: Use one or two words in each blank. Spelling counts!\";\n  chat.appendChild(note);\n}\n\n\/* ========== CHECK ANSWERS ========== *\/\n\nfunction matchesKeywordSets(answer, keywordSets) {\n  const text = answer.toLowerCase();\n  if (!text) return false;\n  return keywordSets.some(set =>\n    set.every(kw => text.includes(kw.toLowerCase()))\n  );\n}\n\nfunction checkAnswers() {\n  let score = 0;\n  let unanswered = 0;\n  const totalPart1 = 1;\n  const totalPart2 = blankItems.length;\n\n  \/\/ reset styles\n  const p1Wrapper = document.getElementById(`p1-q${part1Question.id}`);\n  p1Wrapper.classList.remove(\"correct\", \"incorrect\", \"unanswered\");\n  blankItems.forEach(item => {\n    const block = document.getElementById(`p2-block-${item.id}`);\n    block.classList.remove(\"correct\", \"incorrect\", \"unanswered\");\n  });\n\n  \/\/ PART 1\n  const p1Input = document.getElementById(`p1-input-${part1Question.id}`);\n  const ans1 = p1Input.value.trim();\n  if (!ans1) {\n    unanswered++;\n    p1Wrapper.classList.add(\"unanswered\");\n  } else if (matchesKeywordSets(ans1, part1Question.keywordSets)) {\n    score++;\n    p1Wrapper.classList.add(\"correct\");\n  } else {\n    p1Wrapper.classList.add(\"incorrect\");\n  }\n\n  \/\/ PART 2\n  blankItems.forEach(item => {\n    const input = document.getElementById(`p2-input-${item.id}`);\n    const block = document.getElementById(`p2-block-${item.id}`);\n    const value = input.value.trim().toLowerCase();\n\n    if (!value) {\n      unanswered++;\n      block.classList.add(\"unanswered\");\n      return;\n    }\n\n    const correct = item.answers.some(ans => value === ans.toLowerCase());\n    if (correct) {\n      score++;\n      block.classList.add(\"correct\");\n    } else {\n      block.classList.add(\"incorrect\");\n    }\n  });\n\n  const totalItems = totalPart1 + totalPart2;\n  const resultMessage =\n    `You got ${score} out of ${totalItems} blanks correct.` +\n    (unanswered > 0 ? ` (${unanswered} item(s) not answered.)` : \"\");\n\n  appendMessage(resultMessage, \"bot\");\n\n  let extra;\n  if (score === totalItems && unanswered === 0) {\n    extra = \"Perfect score! \" + positiveMessages[Math.floor(Math.random() * positiveMessages.length)];\n  } else if (score >= Math.round(totalItems * 0.7)) {\n    extra = positiveMessages[Math.floor(Math.random() * positiveMessages.length)];\n  } else {\n    extra = summaryMessages[Math.floor(Math.random() * summaryMessages.length)];\n  }\n\n  appendMessage(extra, \"bot\");\n\n  if (speechSynthesisSupported) {\n    showSynthesisLoader(true);\n    sayText(resultMessage + \" \" + extra, \"en-US\").then(() => {\n      showSynthesisLoader(false);\n    });\n  }\n}\n\n\/* ========== SCRIPT PLAYER WITH TWO VOICES (I & M) ========== *\/\n\nfunction playScript() {\n  if (!speechSynthesisSupported) {\n    appendMessage(\"Your browser cannot play the script with robot voices.\", \"bot\");\n    return;\n  }\n  if (!interviewerVoice || !manuelVoice) {\n    appendMessage(\"Voices are still loading. Please wait a moment and try again.\", \"bot\");\n    loadVoices();\n    return;\n  }\n\n  stopSpeaking();\n  showSynthesisLoader(true);\n\n  const lines = listeningScript.split(\"\\n\").map(l => l.trim()).filter(l => l.length > 0);\n\n  function speakLine(index) {\n    if (index >= lines.length) {\n      showSynthesisLoader(false);\n      return;\n    }\n\n    let line = lines[index];\n    let voiceForLine = selectedVoice || voices[0];\n    let text = line;\n\n    if (line.startsWith(\"I:\")) {\n      text = line.replace(\/^I:\\s*\/, \"\");\n      voiceForLine = interviewerVoice || voiceForLine;\n    } else if (line.startsWith(\"M:\")) {\n      text = line.replace(\/^M:\\s*\/, \"\");\n      voiceForLine = manuelVoice || voiceForLine;\n    }\n\n    const utterance = new SpeechSynthesisUtterance(text);\n    utterance.lang = \"en-US\";\n    utterance.rate = 1.0;\n    utterance.pitch = 1.0;\n    utterance.voice = voiceForLine;\n\n    utterance.onend = function () {\n      speakLine(index + 1);\n    };\n    utterance.onerror = function () {\n      speakLine(index + 1);\n    };\n\n    window.speechSynthesis.speak(utterance);\n  }\n\n  speakLine(0);\n}\n\n\/* ========== GENERIC SPEECH HELPERS (feedback) ========== *\/\n\nfunction stopSpeaking() {\n  if (speechSynthesisSupported) {\n    window.speechSynthesis.cancel();\n    showSynthesisLoader(false);\n  }\n}\n\nfunction sayText(text, lang) {\n  return new Promise((resolve) => {\n    if (!speechSynthesisSupported) {\n      resolve();\n      return;\n    }\n    const utterance = new SpeechSynthesisUtterance(text);\n    utterance.lang = lang;\n    utterance.rate = 1.0;\n    utterance.pitch = 1.0;\n\n    if (selectedVoice) {\n      utterance.voice = selectedVoice;\n    }\n\n    utterance.onend = function () {\n      resolve();\n    };\n    utterance.onerror = function () {\n      resolve();\n    };\n\n    window.speechSynthesis.speak(utterance);\n  });\n}\n\n\/* ========== UI HELPERS ========== *\/\n\nfunction appendMessage(text, sender) {\n  const messageContainer = document.getElementById(\"chat-messages\");\n  const messageElement = document.createElement(\"div\");\n  messageElement.classList.add(\"message\");\n  messageElement.classList.add(\"bot-message\");\n  messageElement.innerText = text;\n  messageContainer.appendChild(messageElement);\n  messageContainer.scrollTop = messageContainer.scrollHeight;\n}\n\nfunction showSynthesisLoader(show) {\n  const loader = document.getElementById(\"synthesis-loader\");\n  loader.style.display = show ? \"inline-block\" : \"none\";\n  if (show) {\n    loader.style.borderTop = \"4px solid #17a2b8\";\n  }\n}\n\nwindow.onload = initialize;\n<\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>This Robot is used for practicing English at level A2. In order to interact with this Robot smoothly, please use<\/p>\n","protected":false},"author":1,"featured_media":84,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[18,5],"tags":[],"class_list":["post-380","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-a2","category-chatbot"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/380","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=380"}],"version-history":[{"count":1,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/380\/revisions"}],"predecessor-version":[{"id":381,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/380\/revisions\/381"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/84"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=380"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=380"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=380"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}