{"id":890,"date":"2026-03-12T13:37:23","date_gmt":"2026-03-12T13:37:23","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=890"},"modified":"2026-03-12T13:37:23","modified_gmt":"2026-03-12T13:37:23","slug":"spanish-interaction-greetings","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/spanish-interaction-greetings\/","title":{"rendered":"Spanish interaction &#8211; Greetings"},"content":{"rendered":"\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>Learn Spanish: Greetings Q&#038;A Practice<\/title>\n  <style>\n    * { box-sizing: border-box; }\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 {\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\n    .menu a {\n      color: #fff;\n      text-decoration: none;\n      margin: 0 8px;\n      font-weight: bold;\n      font-size: 14px;\n      display: inline-block;\n      cursor: pointer;\n      padding: 5px 8px;\n      border-radius: 6px;\n    }\n\n    .menu a.active {\n      background: #fff;\n      color: #28a745;\n    }\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 {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      padding: 10px;\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: 1120px;\n      flex: 1;\n      gap: 16px;\n    }\n\n    .image-container {\n      flex: 1;\n      text-align: center;\n      margin-bottom: 15px;\n      background: #ffffff;\n      border-radius: 15px;\n      box-shadow: 0 0 8px rgba(0,0,0,0.1);\n      padding: 14px;\n    }\n\n    .sentence-text {\n      margin: 10px 0;\n      font-weight: bold;\n      color: #333;\n      font-size: 16px;\n    }\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: 300px;\n      overflow-y: auto;\n      line-height: 1.7;\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      overflow: hidden;\n      display: flex;\n      flex-direction: column;\n      max-height: 760px;\n      flex: 1;\n    }\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\n    .chat-header h2 {\n      margin: 0;\n      font-size: 18px;\n    }\n\n    .chat-header .loader {\n      position: absolute;\n      right: 20px;\n      top: 50%;\n      transform: translateY(-50%);\n      display: none;\n    }\n\n    .chat-messages {\n      padding: 12px;\n      overflow-y: auto;\n      flex: 1;\n      background-color: #fff;\n      max-height: 470px;\n    }\n\n    .message {\n      margin-bottom: 10px;\n      padding: 10px;\n      border-radius: 6px;\n      font-size: 14px;\n      line-height: 1.5;\n      white-space: pre-line;\n    }\n\n    .user-message {\n      background-color: #ffc107;\n      color: #fff;\n      text-align: right;\n    }\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      gap: 8px;\n    }\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: 4px;\n      max-width: 170px;\n      font-size: 16px;\n    }\n\n    .stop-button { 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: 320px;\n    }\n\n    .voice-selection label {\n      display: block;\n      margin-bottom: 5px;\n      font-weight: bold;\n      color: #333;\n    }\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 1s linear infinite;\n      display: inline-block;\n      margin-left: 10px;\n    }\n\n    @keyframes spin {\n      0% { transform: translateY(-50%) rotate(0deg); }\n      100% { transform: translateY(-50%) rotate(360deg); }\n    }\n\n    .face-panel {\n      width: 100%;\n      min-height: 330px;\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      background: radial-gradient(circle at center, #111 0%, #000 70%);\n      border-radius: 15px;\n      padding: 16px 10px;\n      overflow: hidden;\n      position: relative;\n    }\n\n    .speech-bubble {\n      position: relative;\n      background: white;\n      color: #111;\n      border-radius: 20px;\n      padding: 12px 18px;\n      max-width: 340px;\n      opacity: 0;\n      transform: translateY(10px) scale(0.92);\n      transition: all 0.25s ease;\n      box-shadow: 0 10px 30px rgba(255,255,255,0.08);\n      margin-bottom: 12px;\n      font-weight: 600;\n    }\n\n    .speech-bubble.visible {\n      opacity: 1;\n      transform: translateY(0) scale(1);\n    }\n\n    .speech-bubble::after {\n      content: '';\n      position: absolute;\n      bottom: -15px;\n      left: 50%;\n      transform: translateX(-50%);\n      border: 10px solid transparent;\n      border-top-color: white;\n    }\n\n    .face-container {\n      filter: drop-shadow(0 0 45px rgba(255,255,255,0.08));\n      transition: transform 0.15s ease;\n      will-change: transform;\n    }\n\n    .face-shell {\n      transition: transform 0.2s ease;\n      transform-origin: 150px 150px;\n    }\n\n    .eye-white,\n    .pupil,\n    .eyebrow,\n    .mouth-path,\n    .cheek,\n    .tear,\n    .eyelid,\n    .sparkle {\n      transition: all 0.18s ease;\n    }\n\n    @media (orientation: portrait) {\n      .image-chat-wrapper { flex-direction: column; }\n      .image-container, .chat-container { width: 100%; max-width: 100%; }\n    }\n\n    @media (orientation: landscape) and (min-width: 700px) {\n      .image-chat-wrapper {\n        flex-direction: row;\n        justify-content: space-between;\n        gap: 20px;\n      }\n\n      .image-container, .chat-container {\n        width: 48%;\n        max-width: 48%;\n      }\n\n      .word-box { max-height: 240px; }\n      .chat-header h2 { font-size: 20px; }\n      .message { font-size: 15px; }\n      .send-button, .stop-button {\n        font-size: 18px;\n        padding: 14px 20px;\n        max-width: 200px;\n      }\n    }\n\n    @media (max-width: 768px) {\n      .chat-header h2 { font-size: 16px; }\n      .message { font-size: 14px; }\n      .send-button, .stop-button {\n        font-size: 16px;\n        padding: 12px 16px;\n        max-width: 150px;\n      }\n      .word-box { font-size: 15px; }\n      .voice-selection select { font-size: 14px; }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"menu\">\n    <a href=\"#\" class=\"menu-link active\" data-section=\"greetings\">Greetings<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"introductions\">Introductions<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"meeting\">Meeting people<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"polite\">Polite words<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"feelings\">How are you?<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"timeofday\">Time of day<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"goodbye\">Goodbye<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"friends\">Friends<\/a>\n  <\/div>\n\n  <div class=\"container\">\n    <div class=\"image-chat-wrapper\">\n      <div class=\"image-container\">\n        <div class=\"face-panel\">\n          <div id=\"face-speech-bubble\" class=\"speech-bubble\">\n            <p id=\"face-speech-text\"><\/p>\n          <\/div>\n\n          <div class=\"face-container\" id=\"face-container\">\n            <svg width=\"300\" height=\"300\" viewBox=\"0 0 300 300\" id=\"face-svg\" aria-label=\"Expressive face\">\n              <defs>\n                <radialGradient id=\"faceGlow\" cx=\"50%\" cy=\"40%\" r=\"70%\">\n                  <stop offset=\"0%\" stop-color=\"rgba(255,255,255,0.12)\" \/>\n                  <stop offset=\"100%\" stop-color=\"rgba(255,255,255,0.02)\" \/>\n                <\/radialGradient>\n                <radialGradient id=\"cheekGlow\" cx=\"50%\" cy=\"50%\" r=\"60%\">\n                  <stop offset=\"0%\" stop-color=\"rgba(255,160,160,0.8)\" \/>\n                  <stop offset=\"100%\" stop-color=\"rgba(255,160,160,0)\" \/>\n                <\/radialGradient>\n              <\/defs>\n\n              <g id=\"face-shell\" class=\"face-shell\">\n                <ellipse cx=\"150\" cy=\"150\" rx=\"120\" ry=\"140\" fill=\"url(#faceGlow)\" stroke=\"rgba(255,255,255,0.15)\" stroke-width=\"2\"\/>\n                <path id=\"left-eyebrow\" class=\"eyebrow\" d=\"M 60 80 Q 90 75 120 80\" fill=\"none\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"\/>\n                <path id=\"right-eyebrow\" class=\"eyebrow\" d=\"M 180 80 Q 210 75 240 80\" fill=\"none\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"\/>\n                <ellipse id=\"left-blush\" class=\"cheek\" cx=\"70\" cy=\"165\" rx=\"24\" ry=\"12\" fill=\"url(#cheekGlow)\" opacity=\"0\"\/>\n                <ellipse id=\"right-blush\" class=\"cheek\" cx=\"230\" cy=\"165\" rx=\"24\" ry=\"12\" fill=\"url(#cheekGlow)\" opacity=\"0\"\/>\n\n                <g id=\"left-eye-group\">\n                  <ellipse id=\"left-eye-white\" class=\"eye-white\" cx=\"90\" cy=\"110\" rx=\"30\" ry=\"24\" fill=\"white\"\/>\n                  <ellipse id=\"left-eye-lid\" class=\"eyelid\" cx=\"90\" cy=\"110\" rx=\"30\" ry=\"0\" fill=\"black\"\/>\n                  <circle id=\"left-pupil\" class=\"pupil\" cx=\"90\" cy=\"110\" r=\"11.5\" fill=\"black\"\/>\n                  <circle id=\"left-sparkle\" class=\"sparkle\" cx=\"96\" cy=\"104\" r=\"4.2\" fill=\"white\" opacity=\"0.95\"\/>\n                <\/g>\n\n                <g id=\"right-eye-group\">\n                  <ellipse id=\"right-eye-white\" class=\"eye-white\" cx=\"210\" cy=\"110\" rx=\"30\" ry=\"24\" fill=\"white\"\/>\n                  <ellipse id=\"right-eye-lid\" class=\"eyelid\" cx=\"210\" cy=\"110\" rx=\"30\" ry=\"0\" fill=\"black\"\/>\n                  <circle id=\"right-pupil\" class=\"pupil\" cx=\"210\" cy=\"110\" r=\"11.5\" fill=\"black\"\/>\n                  <circle id=\"right-sparkle\" class=\"sparkle\" cx=\"216\" cy=\"104\" r=\"4.2\" fill=\"white\" opacity=\"0.95\"\/>\n                <\/g>\n\n                <path id=\"mouth\" class=\"mouth-path\" d=\"M 100 220 Q 150 232 200 220\" fill=\"none\" stroke=\"white\" stroke-width=\"5\" stroke-linecap=\"round\"\/>\n                <ellipse id=\"mouth-inner\" cx=\"150\" cy=\"225\" rx=\"0\" ry=\"0\" fill=\"rgba(120,0,0,0.75)\" opacity=\"0\"\/>\n                <rect id=\"teeth\" x=\"122\" y=\"220\" width=\"56\" height=\"10\" rx=\"4\" fill=\"white\" opacity=\"0\"\/>\n                <path id=\"left-tear\" class=\"tear\" d=\"M 75 140 Q 69 160 75 171 Q 81 160 75 140\" fill=\"rgba(100,200,255,0)\" \/>\n                <path id=\"right-tear\" class=\"tear\" d=\"M 225 140 Q 219 160 225 171 Q 231 160 225 140\" fill=\"rgba(100,200,255,0)\" \/>\n              <\/g>\n            <\/svg>\n          <\/div>\n        <\/div>\n\n        <div class=\"sentence-text\">\n          Listen to the question and answer in simple Spanish.\n        <\/div>\n        <div id=\"word-box\" class=\"word-box\"><\/div>\n      <\/div>\n\n      <div class=\"chat-container\">\n        <div class=\"chat-header\">\n          <h2 id=\"section-title\">Greetings<\/h2>\n          <div class=\"loader\" id=\"synthesis-loader\"><\/div>\n        <\/div>\n        <div class=\"chat-messages\" id=\"chat-messages\"><\/div>\n        <div class=\"chat-input\">\n          <div class=\"voice-selection\">\n            <label for=\"spanish-voice-select\">Choose Spanish Voice:<\/label>\n            <select id=\"spanish-voice-select\" aria-label=\"Select Spanish Voice\">\n              <option value=\"\">Loading Spanish voices&#8230;<\/option>\n            <\/select>\n          <\/div>\n          <button id=\"start-btn\" class=\"send-button\">Start<\/button>\n          <button id=\"stop-btn\" class=\"stop-button\">Stop<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"menu\">\n    <a href=\"#\" class=\"menu-link active\" data-section=\"greetings\">Greetings<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"introductions\">Introductions<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"meeting\">Meeting people<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"polite\">Polite words<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"feelings\">How are you?<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"timeofday\">Time of day<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"goodbye\">Goodbye<\/a>\n    <a href=\"#\" class=\"menu-link\" data-section=\"friends\">Friends<\/a>\n  <\/div>\n\n  <script>\n    const sectionData = {\n      greetings: {\n        title: \"Greetings\",\n        intro: \"Hoy vamos a practicar saludos en espa\u00f1ol.\",\n        items: [\n          {\n            questionEs: \"\u00bfDices hola?\",\n            questionEn: \"Do you say hello?\",\n            suggestedAnswer: \"S\u00ed, digo hola.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"hola\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfSaludas a tu amigo?\",\n            questionEn: \"Do you greet your friend?\",\n            suggestedAnswer: \"S\u00ed, saludo a mi amigo.\",\n            keywords: [\"s\u00ed\", \"si\", \"saludo\", \"amigo\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices buenos d\u00edas?\",\n            questionEn: \"Do you say good morning?\",\n            suggestedAnswer: \"S\u00ed, digo buenos d\u00edas.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"buenos\", \"d\u00edas\", \"dias\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfHablas con una sonrisa?\",\n            questionEn: \"Do you speak with a smile?\",\n            suggestedAnswer: \"S\u00ed, hablo con una sonrisa.\",\n            keywords: [\"s\u00ed\", \"si\", \"hablo\", \"sonrisa\", \"no\"]\n          }\n        ]\n      },\n\n      introductions: {\n        title: \"Introductions\",\n        intro: \"Hoy vamos a practicar presentaciones simples.\",\n        items: [\n          {\n            questionEs: \"\u00bfC\u00f3mo te llamas?\",\n            questionEn: \"What is your name?\",\n            suggestedAnswer: \"Me llamo Ana.\",\n            keywords: [\"me\", \"llamo\", \"soy\", \"ana\", \"juan\", \"maria\"]\n          },\n          {\n            questionEs: \"\u00bfDices tu nombre?\",\n            questionEn: \"Do you say your name?\",\n            suggestedAnswer: \"S\u00ed, digo mi nombre.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"nombre\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfTe presentas ahora?\",\n            questionEn: \"Do you introduce yourself now?\",\n            suggestedAnswer: \"S\u00ed, me presento ahora.\",\n            keywords: [\"s\u00ed\", \"si\", \"presento\", \"ahora\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices mucho gusto?\",\n            questionEn: \"Do you say nice to meet you?\",\n            suggestedAnswer: \"S\u00ed, digo mucho gusto.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"mucho\", \"gusto\", \"no\"]\n          }\n        ]\n      },\n\n      meeting: {\n        title: \"Meeting people\",\n        intro: \"Hoy vamos a practicar cuando conoces a una persona.\",\n        items: [\n          {\n            questionEs: \"\u00bfConoces a una persona nueva?\",\n            questionEn: \"Do you meet a new person?\",\n            suggestedAnswer: \"S\u00ed, conozco a una persona nueva.\",\n            keywords: [\"s\u00ed\", \"si\", \"conozco\", \"persona\", \"nueva\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices hola primero?\",\n            questionEn: \"Do you say hello first?\",\n            suggestedAnswer: \"S\u00ed, digo hola primero.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"hola\", \"primero\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDas la mano?\",\n            questionEn: \"Do you shake hands?\",\n            suggestedAnswer: \"S\u00ed, doy la mano.\",\n            keywords: [\"s\u00ed\", \"si\", \"doy\", \"mano\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfHablas con calma?\",\n            questionEn: \"Do you speak calmly?\",\n            suggestedAnswer: \"S\u00ed, hablo con calma.\",\n            keywords: [\"s\u00ed\", \"si\", \"hablo\", \"calma\", \"no\"]\n          }\n        ]\n      },\n\n      polite: {\n        title: \"Polite words\",\n        intro: \"Hoy vamos a practicar palabras amables.\",\n        items: [\n          {\n            questionEs: \"\u00bfDices por favor?\",\n            questionEn: \"Do you say please?\",\n            suggestedAnswer: \"S\u00ed, digo por favor.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"por\", \"favor\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices gracias?\",\n            questionEn: \"Do you say thank you?\",\n            suggestedAnswer: \"S\u00ed, digo gracias.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"gracias\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices perd\u00f3n?\",\n            questionEn: \"Do you say sorry?\",\n            suggestedAnswer: \"S\u00ed, digo perd\u00f3n.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"perd\u00f3n\", \"perdon\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfEres amable?\",\n            questionEn: \"Are you kind?\",\n            suggestedAnswer: \"S\u00ed, soy amable.\",\n            keywords: [\"s\u00ed\", \"si\", \"soy\", \"amable\", \"no\"]\n          }\n        ]\n      },\n\n      feelings: {\n        title: \"How are you?\",\n        intro: \"Hoy vamos a practicar c\u00f3mo te sientes.\",\n        items: [\n          {\n            questionEs: \"\u00bfC\u00f3mo est\u00e1s?\",\n            questionEn: \"How are you?\",\n            suggestedAnswer: \"Estoy bien.\",\n            keywords: [\"estoy\", \"bien\", \"muy\", \"mal\", \"feliz\", \"cansado\"]\n          },\n          {\n            questionEs: \"\u00bfEst\u00e1s bien hoy?\",\n            questionEn: \"Are you well today?\",\n            suggestedAnswer: \"S\u00ed, estoy bien hoy.\",\n            keywords: [\"s\u00ed\", \"si\", \"estoy\", \"bien\", \"hoy\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfEst\u00e1s feliz?\",\n            questionEn: \"Are you happy?\",\n            suggestedAnswer: \"S\u00ed, estoy feliz.\",\n            keywords: [\"s\u00ed\", \"si\", \"estoy\", \"feliz\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices estoy bien?\",\n            questionEn: \"Do you say I am fine?\",\n            suggestedAnswer: \"S\u00ed, digo estoy bien.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"estoy\", \"bien\", \"no\"]\n          }\n        ]\n      },\n\n      timeofday: {\n        title: \"Time of day\",\n        intro: \"Hoy vamos a practicar saludos del d\u00eda.\",\n        items: [\n          {\n            questionEs: \"\u00bfDices buenos d\u00edas?\",\n            questionEn: \"Do you say good morning?\",\n            suggestedAnswer: \"S\u00ed, digo buenos d\u00edas.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"buenos\", \"d\u00edas\", \"dias\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices buenas tardes?\",\n            questionEn: \"Do you say good afternoon?\",\n            suggestedAnswer: \"S\u00ed, digo buenas tardes.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"buenas\", \"tardes\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices buenas noches?\",\n            questionEn: \"Do you say good evening\/night?\",\n            suggestedAnswer: \"S\u00ed, digo buenas noches.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"buenas\", \"noches\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfSaludas por la ma\u00f1ana?\",\n            questionEn: \"Do you greet in the morning?\",\n            suggestedAnswer: \"S\u00ed, saludo por la ma\u00f1ana.\",\n            keywords: [\"s\u00ed\", \"si\", \"saludo\", \"ma\u00f1ana\", \"manana\", \"no\"]\n          }\n        ]\n      },\n\n      goodbye: {\n        title: \"Goodbye\",\n        intro: \"Hoy vamos a practicar despedidas simples.\",\n        items: [\n          {\n            questionEs: \"\u00bfDices adi\u00f3s?\",\n            questionEn: \"Do you say goodbye?\",\n            suggestedAnswer: \"S\u00ed, digo adi\u00f3s.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"adi\u00f3s\", \"adios\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices hasta luego?\",\n            questionEn: \"Do you say see you later?\",\n            suggestedAnswer: \"S\u00ed, digo hasta luego.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"hasta\", \"luego\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfTe despides ahora?\",\n            questionEn: \"Do you say goodbye now?\",\n            suggestedAnswer: \"S\u00ed, me despido ahora.\",\n            keywords: [\"s\u00ed\", \"si\", \"despido\", \"ahora\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfSonr\u00edes al salir?\",\n            questionEn: \"Do you smile when leaving?\",\n            suggestedAnswer: \"S\u00ed, sonr\u00edo al salir.\",\n            keywords: [\"s\u00ed\", \"si\", \"sonr\u00edo\", \"sonrio\", \"salir\", \"no\"]\n          }\n        ]\n      },\n\n      friends: {\n        title: \"Friends\",\n        intro: \"Hoy vamos a practicar saludos con amigos.\",\n        items: [\n          {\n            questionEs: \"\u00bfSaludas a tus amigos?\",\n            questionEn: \"Do you greet your friends?\",\n            suggestedAnswer: \"S\u00ed, saludo a mis amigos.\",\n            keywords: [\"s\u00ed\", \"si\", \"saludo\", \"amigos\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfDices hola amigo?\",\n            questionEn: \"Do you say hi friend?\",\n            suggestedAnswer: \"S\u00ed, digo hola amigo.\",\n            keywords: [\"s\u00ed\", \"si\", \"digo\", \"hola\", \"amigo\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfHablas con tus amigos?\",\n            questionEn: \"Do you talk with your friends?\",\n            suggestedAnswer: \"S\u00ed, hablo con mis amigos.\",\n            keywords: [\"s\u00ed\", \"si\", \"hablo\", \"amigos\", \"no\"]\n          },\n          {\n            questionEs: \"\u00bfEst\u00e1s feliz con tus amigos?\",\n            questionEn: \"Are you happy with your friends?\",\n            suggestedAnswer: \"S\u00ed, estoy feliz con mis amigos.\",\n            keywords: [\"s\u00ed\", \"si\", \"estoy\", \"feliz\", \"amigos\", \"no\"]\n          }\n        ]\n      }\n    };\n\n    const positiveFeedback = [\n      \"Muy bien. Tu respuesta es clara.\",\n      \"Excelente. Respondiste bien.\",\n      \"Muy bien hecho. Tu respuesta es correcta.\",\n      \"Buen trabajo. La respuesta coincide con la pregunta.\",\n      \"Muy bien. Usaste palabras correctas.\"\n    ];\n\n    const partialFeedback = [\n      \"Bien. Tu respuesta est\u00e1 bien, pero puedes decir un poco m\u00e1s.\",\n      \"Buen intento. Usa una frase un poco m\u00e1s completa.\",\n      \"Vas bien. A\u00f1ade una palabra clave m\u00e1s.\",\n      \"Bien. Responde con una frase m\u00e1s clara.\",\n      \"Buen trabajo. Puedes mejorar con m\u00e1s informaci\u00f3n.\"\n    ];\n\n    const constructiveFeedback = [\n      \"Intenta responder con palabras de la pregunta.\",\n      \"Escucha otra vez y usa una respuesta simple en espa\u00f1ol.\",\n      \"Trata de usar palabras clave importantes.\",\n      \"Tu respuesta necesita m\u00e1s relaci\u00f3n con la pregunta.\",\n      \"Usa una oraci\u00f3n corta y clara en espa\u00f1ol.\"\n    ];\n\n    const stopKeywords = [\"goodbye\", \"bye\", \"stop\", \"end lesson\", \"adi\u00f3s\", \"adios\"];\n\n    let currentSection = \"greetings\";\n    let lessons = sectionData[currentSection].items;\n    let currentLessonIndex = 0;\n    let isLessonActive = false;\n    let speechRecognitionActive = false;\n    let pendingNextQuestionTimeout = null;\n    let retryCount = 0;\n    const maxRetriesPerQuestion = 2;\n\n    const speechSynthesisSupported = 'speechSynthesis' in window;\n    const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;\n    const speechRecognitionSupported = !!SpeechRecognitionClass;\n\n    let voices = [];\n    let spanishVoices = [];\n    let selectedSpanishVoice = null;\n    let recognition = null;\n    let micAccessGranted = false;\n    let currentListeningItem = null;\n\n    let currentEmotion = 'neutral';\n    let isFaceTalking = false;\n    let faceTalkTimer = null;\n    let faceBubbleTimer = null;\n    let blinkTimer = null;\n    let saccadeTimer = null;\n    let breatheRAF = null;\n\n    const faceShell = document.getElementById('face-shell');\n    const mouth = document.getElementById('mouth');\n    const mouthInner = document.getElementById('mouth-inner');\n    const teeth = document.getElementById('teeth');\n    const leftEyeWhite = document.getElementById('left-eye-white');\n    const rightEyeWhite = document.getElementById('right-eye-white');\n    const leftLid = document.getElementById('left-eye-lid');\n    const rightLid = document.getElementById('right-eye-lid');\n    const leftPupil = document.getElementById('left-pupil');\n    const rightPupil = document.getElementById('right-pupil');\n    const leftSparkle = document.getElementById('left-sparkle');\n    const rightSparkle = document.getElementById('right-sparkle');\n    const leftEyebrow = document.getElementById('left-eyebrow');\n    const rightEyebrow = document.getElementById('right-eyebrow');\n    const leftBlush = document.getElementById('left-blush');\n    const rightBlush = document.getElementById('right-blush');\n    const leftTear = document.getElementById('left-tear');\n    const rightTear = document.getElementById('right-tear');\n    const faceSpeechBubble = document.getElementById('face-speech-bubble');\n    const faceSpeechText = document.getElementById('face-speech-text');\n\n    function initialize() {\n      loadVoices();\n      setFaceEmotion('neutral');\n      scheduleBlink();\n      scheduleSaccade();\n      startFaceBreathing();\n      bindSectionMenus();\n      createRecognitionInstance();\n      updateSectionUI();\n      clearWordBox();\n      addSectionHelp();\n\n      appendMessage(\"Click Start to begin the Spanish speaking practice.\", \"bot\");\n\n      document.getElementById('start-btn').onclick = async function() {\n        if (isLessonActive) return;\n        if (!speechSynthesisSupported || !speechRecognitionSupported) {\n          appendMessage(\"Your browser needs speech synthesis and speech recognition.\", \"bot\");\n          return;\n        }\n\n        clearChat();\n        clearWordBox();\n        addSectionHelp();\n        stopAllAudioAndRecognition();\n        clearPendingTimeout();\n\n        const micOk = await requestMicrophoneOnce();\n        if (!micOk) {\n          appendMessage(\"Microphone access is required for speaking practice.\", \"bot\");\n          return;\n        }\n\n        isLessonActive = true;\n        currentLessonIndex = 0;\n        retryCount = 0;\n\n        appendMessage(sectionData[currentSection].intro, 'bot');\n        faceSpeak(sectionData[currentSection].intro, \"happy\");\n        showSynthesisLoader(true);\n\n        speakSpanish(sectionData[currentSection].intro).then(() => {\n          showSynthesisLoader(false);\n          startLesson();\n        });\n      };\n\n      document.getElementById('stop-btn').onclick = function() {\n        endLesson(true);\n      };\n    }\n\n    function setFaceEmotion(emotion) {\n      const presets = {\n        neutral: {\n          mouth: 'M 100 220 Q 150 232 200 220',\n          leftEyebrow: 'M 60 80 Q 90 75 120 80',\n          rightEyebrow: 'M 180 80 Q 210 75 240 80',\n          eyeRY: 24,\n          blush: 0,\n          tears: 0,\n          teeth: 0,\n          innerOpacity: 0,\n          innerRx: 0,\n          innerRy: 0\n        },\n        happy: {\n          mouth: 'M 92 212 Q 150 278 208 212',\n          leftEyebrow: 'M 60 75 Q 90 66 120 75',\n          rightEyebrow: 'M 180 75 Q 210 66 240 75',\n          eyeRY: 17,\n          blush: 0.48,\n          tears: 0,\n          teeth: 0.92,\n          innerOpacity: 0.25,\n          innerRx: 24,\n          innerRy: 8\n        },\n        sad: {\n          mouth: 'M 112 240 Q 150 203 188 240',\n          leftEyebrow: 'M 68 72 Q 90 88 120 76',\n          rightEyebrow: 'M 180 76 Q 210 88 232 72',\n          eyeRY: 15,\n          blush: 0,\n          tears: 0.78,\n          teeth: 0,\n          innerOpacity: 0,\n          innerRx: 0,\n          innerRy: 0\n        },\n        confused: {\n          mouth: 'M 110 224 Q 140 230 160 221 Q 182 236 190 226',\n          leftEyebrow: 'M 60 86 Q 90 68 120 80',\n          rightEyebrow: 'M 180 70 Q 210 86 240 76',\n          eyeRY: 22,\n          blush: 0,\n          tears: 0,\n          teeth: 0,\n          innerOpacity: 0,\n          innerRx: 0,\n          innerRy: 0\n        }\n      };\n\n      const preset = presets[emotion] || presets.neutral;\n      currentEmotion = emotion;\n      mouth.setAttribute('d', preset.mouth);\n      leftEyebrow.setAttribute('d', preset.leftEyebrow);\n      rightEyebrow.setAttribute('d', preset.rightEyebrow);\n      leftEyeWhite.setAttribute('ry', preset.eyeRY);\n      rightEyeWhite.setAttribute('ry', preset.eyeRY);\n      leftBlush.setAttribute('opacity', preset.blush);\n      rightBlush.setAttribute('opacity', preset.blush);\n      leftTear.setAttribute('fill', `rgba(100,200,255,${preset.tears})`);\n      rightTear.setAttribute('fill', `rgba(100,200,255,${preset.tears})`);\n      teeth.setAttribute('opacity', preset.teeth);\n      mouthInner.setAttribute('opacity', preset.innerOpacity);\n      mouthInner.setAttribute('rx', preset.innerRx);\n      mouthInner.setAttribute('ry', preset.innerRy);\n    }\n\n    function blinkFace(duration = 110) {\n      const currentL = parseFloat(leftEyeWhite.getAttribute('ry'));\n      const currentR = parseFloat(rightEyeWhite.getAttribute('ry'));\n      leftLid.setAttribute('ry', currentL + 1);\n      rightLid.setAttribute('ry', currentR + 1);\n      setTimeout(() => {\n        leftLid.setAttribute('ry', 0);\n        rightLid.setAttribute('ry', 0);\n      }, duration);\n    }\n\n    function scheduleBlink() {\n      clearTimeout(blinkTimer);\n      const next = 1500 + Math.random() * 3500;\n      blinkTimer = setTimeout(() => {\n        blinkFace(90 + Math.random() * 80);\n        scheduleBlink();\n      }, next);\n    }\n\n    function idleSaccade() {\n      if (isFaceTalking) return;\n      const dx = (Math.random() * 2 - 1) * 4.5;\n      const dy = (Math.random() * 2 - 1) * 3.2;\n      leftPupil.setAttribute('cx', 90 + dx);\n      leftPupil.setAttribute('cy', 110 + dy);\n      rightPupil.setAttribute('cx', 210 + dx);\n      rightPupil.setAttribute('cy', 110 + dy);\n      leftSparkle.setAttribute('cx', 96 + dx * 0.6);\n      leftSparkle.setAttribute('cy', 104 + dy * 0.6);\n      rightSparkle.setAttribute('cx', 216 + dx * 0.6);\n      rightSparkle.setAttribute('cy', 104 + dy * 0.6);\n    }\n\n    function scheduleSaccade() {\n      clearTimeout(saccadeTimer);\n      const next = 900 + Math.random() * 1800;\n      saccadeTimer = setTimeout(() => {\n        idleSaccade();\n        scheduleSaccade();\n      }, next);\n    }\n\n    function startFaceBreathing() {\n      let t0 = performance.now();\n      function frame(now) {\n        const t = (now - t0) \/ 1000;\n        const breathe = Math.sin(t * 1.8) * 1.8;\n        const sway = Math.sin(t * 0.9) * 1.4;\n        const tilt = Math.sin(t * 0.75) * 0.8;\n        if (!isFaceTalking) {\n          faceShell.setAttribute(\n            'transform',\n            `translate(${sway.toFixed(2)}, ${breathe.toFixed(2)}) rotate(${tilt.toFixed(2)} 150 150)`\n          );\n        }\n        breatheRAF = requestAnimationFrame(frame);\n      }\n      cancelAnimationFrame(breatheRAF);\n      breatheRAF = requestAnimationFrame(frame);\n    }\n\n    function showFaceBubble(text, duration) {\n      faceSpeechText.textContent = text;\n      faceSpeechBubble.classList.add('visible');\n      clearTimeout(faceBubbleTimer);\n      faceBubbleTimer = setTimeout(() => {\n        faceSpeechBubble.classList.remove('visible');\n      }, duration + 600);\n    }\n\n    function stopFaceTalking() {\n      isFaceTalking = false;\n      clearInterval(faceTalkTimer);\n      faceTalkTimer = null;\n      setFaceEmotion(currentEmotion);\n    }\n\n    function talkingFrame(emotion) {\n      const shapes = {\n        neutral: [\n          { d: 'M 102 220 Q 150 240 198 220', rx: 14, ry: 7, teeth: 0.1, inner: 0.25 },\n          { d: 'M 96 214 Q 150 262 204 214', rx: 22, ry: 11, teeth: 0.28, inner: 0.45 }\n        ],\n        happy: [\n          { d: 'M 92 212 Q 150 280 208 212', rx: 24, ry: 10, teeth: 0.9, inner: 0.35 },\n          { d: 'M 96 216 Q 150 266 204 216', rx: 18, ry: 8, teeth: 0.75, inner: 0.28 }\n        ],\n        sad: [\n          { d: 'M 112 238 Q 150 212 188 238', rx: 10, ry: 4, teeth: 0, inner: 0.1 }\n        ],\n        confused: [\n          { d: 'M 110 224 Q 140 230 160 221 Q 182 236 190 226', rx: 10, ry: 4, teeth: 0, inner: 0.1 }\n        ]\n      };\n\n      const list = shapes[emotion] || shapes.neutral;\n      const chosen = list[Math.floor(Math.random() * list.length)];\n      mouth.setAttribute('d', chosen.d);\n      mouthInner.setAttribute('opacity', chosen.inner);\n      mouthInner.setAttribute('rx', chosen.rx);\n      mouthInner.setAttribute('ry', chosen.ry);\n      teeth.setAttribute('opacity', chosen.teeth);\n    }\n\n    function faceSpeak(text, emotion = \"neutral\", durationHint = null) {\n      const clean = (text || \"\").trim();\n      if (!clean) return;\n      clearInterval(faceTalkTimer);\n      isFaceTalking = true;\n      setFaceEmotion(emotion);\n      const duration = durationHint || Math.max(1600, clean.length * 70);\n      showFaceBubble(clean, duration);\n      faceTalkTimer = setInterval(() => talkingFrame(emotion), 120);\n      setTimeout(() => stopFaceTalking(), duration);\n    }\n\n    function bindSectionMenus() {\n      document.querySelectorAll('.menu-link').forEach(link => {\n        link.addEventListener('click', function(e) {\n          e.preventDefault();\n          switchSection(this.dataset.section);\n        });\n      });\n    }\n\n    function switchSection(section) {\n      if (!sectionData[section]) return;\n      stopAllAudioAndRecognition();\n      isLessonActive = false;\n      currentListeningItem = null;\n      currentSection = section;\n      lessons = sectionData[currentSection].items;\n      currentLessonIndex = 0;\n      retryCount = 0;\n      clearChat();\n      clearWordBox();\n      updateSectionUI();\n      addSectionHelp();\n      appendMessage(`Section changed to ${sectionData[currentSection].title}. Click Start to begin.`, 'bot');\n    }\n\n    function updateSectionUI() {\n      document.getElementById('section-title').textContent = sectionData[currentSection].title;\n      document.querySelectorAll('.menu-link').forEach(link => {\n        link.classList.toggle('active', link.dataset.section === currentSection);\n      });\n    }\n\n    function addSectionHelp() {\n      addSentenceToBox(`Section: ${sectionData[currentSection].title}`);\n      addSentenceToBox(`Answer the questions in Spanish.`);\n      addSentenceToBox(`Use simple Spanish words for this greetings topic.`);\n      addSentenceToBox(`Say a short and clear answer.`);\n      addSentenceToBox(`--------------------`);\n    }\n\n    async function requestMicrophoneOnce() {\n      if (micAccessGranted) return true;\n      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\n        appendMessage(\"Your browser does not support microphone access.\", \"bot\");\n        return false;\n      }\n      try {\n        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n        stream.getTracks().forEach(track => track.stop());\n        micAccessGranted = true;\n        return true;\n      } catch (error) {\n        appendMessage(\"Microphone access was blocked. Please allow microphone access and try again.\", \"bot\");\n        faceSpeak(\"Micr\u00f3fono bloqueado.\", \"sad\");\n        return false;\n      }\n    }\n\n    function createRecognitionInstance() {\n      if (!speechRecognitionSupported) return;\n\n      recognition = new SpeechRecognitionClass();\n      recognition.lang = 'es-ES';\n      recognition.interimResults = false;\n      recognition.continuous = false;\n      recognition.maxAlternatives = 1;\n\n      recognition.onresult = function(event) {\n        if (!isLessonActive || !speechRecognitionActive || !currentListeningItem) return;\n\n        const transcript = event.results[0][0].transcript.trim();\n        const formattedInput = formatSentence(transcript);\n\n        speechRecognitionActive = false;\n        showRecognitionLoader(false);\n        appendMessage(formattedInput, 'user');\n\n        if (checkStopKeywords(formattedInput.toLowerCase())) {\n          endLesson(true);\n          return;\n        }\n\n        const feedback = analyzeResponse(formattedInput, currentListeningItem.keywords);\n        appendMessage(feedback.message, 'bot');\n        faceSpeak(feedback.shortFaceText, feedback.emotion);\n        showSynthesisLoader(true);\n\n        speakSpanish(feedback.message).then(() => {\n          showSynthesisLoader(false);\n          if (!isLessonActive) return;\n\n          if (feedback.level === \"low\" && retryCount < maxRetriesPerQuestion) {\n            retryCount++;\n            clearPendingTimeout();\n            pendingNextQuestionTimeout = setTimeout(() => {\n              appendMessage(\"Vamos otra vez con la misma pregunta.\", \"bot\");\n              faceSpeak(\"Otra vez.\", \"neutral\", 1200);\n              askCurrentQuestion();\n            }, 1200);\n            return;\n          }\n\n          currentLessonIndex++;\n          retryCount = 0;\n          clearPendingTimeout();\n          pendingNextQuestionTimeout = setTimeout(() => startLesson(), 1600);\n        });\n      };\n\n      recognition.onspeechend = function() {\n        if (speechRecognitionActive && recognition) {\n          try { recognition.stop(); } catch (e) {}\n        }\n      };\n\n      recognition.onerror = function(event) {\n        speechRecognitionActive = false;\n        showRecognitionLoader(false);\n        if (!isLessonActive || !currentListeningItem) return;\n\n        if (event.error === \"not-allowed\") {\n          micAccessGranted = false;\n          const msg = \"Microphone access was blocked. Please allow microphone access and try again.\";\n          appendMessage(msg, \"bot\");\n          faceSpeak(\"Micr\u00f3fono bloqueado.\", \"sad\");\n          return;\n        }\n\n        if (event.error === \"no-speech\") {\n          retryCount++;\n          if (retryCount <= maxRetriesPerQuestion) {\n            const msg = \"No escuch\u00e9 tu respuesta. Intenta otra vez en espa\u00f1ol.\";\n            appendMessage(msg, \"bot\");\n            faceSpeak(\"Intenta otra vez.\", \"confused\");\n            showSynthesisLoader(true);\n            speakSpanish(msg).then(() => {\n              showSynthesisLoader(false);\n              listenUserResponse(currentListeningItem);\n            });\n          } else {\n            const msg = \"Vamos a la siguiente pregunta.\";\n            appendMessage(msg, \"bot\");\n            faceSpeak(\"Siguiente pregunta.\", \"neutral\");\n            showSynthesisLoader(true);\n            speakSpanish(msg).then(() => {\n              showSynthesisLoader(false);\n              currentLessonIndex++;\n              retryCount = 0;\n              startLesson();\n            });\n          }\n          return;\n        }\n\n        const msg = \"Hubo un error de voz. Intenta responder otra vez.\";\n        appendMessage(msg, \"bot\");\n        faceSpeak(\"Intenta otra vez.\", \"sad\");\n        showSynthesisLoader(true);\n        speakSpanish(msg).then(() => {\n          showSynthesisLoader(false);\n          listenUserResponse(currentListeningItem);\n        });\n      };\n\n      recognition.onend = function() {\n        speechRecognitionActive = false;\n        showRecognitionLoader(false);\n      };\n    }\n\n    function loadVoices() {\n      const fillVoices = () => {\n        voices = window.speechSynthesis.getVoices();\n        spanishVoices = voices.filter(v =>\n          \/^es(-|_)\/i.test(v.lang) || \/spanish|espa\u00f1ol\/i.test(v.name)\n        );\n\n        spanishVoices.sort((a, b) => getSpanishVoicePriority(a) - getSpanishVoicePriority(b));\n        selectedSpanishVoice = spanishVoices[0] || null;\n        populateSpanishVoiceList();\n      };\n\n      fillVoices();\n      if (typeof speechSynthesis !== \"undefined\") {\n        speechSynthesis.onvoiceschanged = fillVoices;\n      }\n    }\n\n    function getSpanishVoicePriority(voice) {\n      const name = voice.name.toLowerCase();\n      if (name.includes(\"google\") && (name.includes(\"espa\u00f1ol\") || name.includes(\"spanish\"))) return 1;\n      if (name.includes(\"microsoft\") && (name.includes(\"espa\u00f1ol\") || name.includes(\"spanish\"))) return 2;\n      if (name.includes(\"spanish\") || name.includes(\"espa\u00f1ol\")) return 3;\n      return 10;\n    }\n\n    function populateSpanishVoiceList() {\n      const voiceSelect = document.getElementById('spanish-voice-select');\n      voiceSelect.innerHTML = '';\n\n      if (!spanishVoices.length) {\n        const option = document.createElement('option');\n        option.value = \"\";\n        option.textContent = \"No Spanish voices available\";\n        voiceSelect.appendChild(option);\n        voiceSelect.disabled = true;\n        selectedSpanishVoice = null;\n        return;\n      }\n\n      spanishVoices.forEach((voice, index) => {\n        const option = document.createElement('option');\n        option.value = String(index);\n        option.textContent = `${voice.name} (${voice.lang})`;\n        voiceSelect.appendChild(option);\n      });\n\n      selectedSpanishVoice = spanishVoices[0];\n      voiceSelect.value = \"0\";\n      voiceSelect.disabled = false;\n\n      voiceSelect.onchange = () => {\n        const index = parseInt(voiceSelect.value, 10);\n        selectedSpanishVoice = spanishVoices[index] || spanishVoices[0];\n      };\n    }\n\n    function startLesson() {\n      if (!isLessonActive) return;\n      if (currentLessonIndex >= lessons.length) {\n        endLesson(false);\n        return;\n      }\n      askCurrentQuestion();\n    }\n\n    function askCurrentQuestion() {\n      if (!isLessonActive) return;\n\n      const item = lessons[currentLessonIndex];\n      const questionNumber = currentLessonIndex + 1;\n\n      const displayText = `Question ${questionNumber}\\nSpanish: ${item.questionEs}\\nEnglish: ${item.questionEn}`;\n      appendMessage(displayText, 'bot');\n\n      addSentenceToBox(`Question: ${item.questionEs}`);\n      addSentenceToBox(`Meaning: ${item.questionEn}`);\n      addSentenceToBox(`Sample answer: ${item.suggestedAnswer}`);\n      addSentenceToBox(`--------------------`);\n\n      faceSpeak(item.questionEs, \"neutral\");\n      showSynthesisLoader(true);\n\n      speakSpanish(item.questionEs).then(() => {\n        showSynthesisLoader(false);\n        listenUserResponse(item);\n      });\n    }\n\n    function listenUserResponse(item) {\n      if (!isLessonActive || !recognition) return;\n\n      stopRecognitionOnly();\n      currentListeningItem = item;\n      recognition.lang = 'es-ES';\n      speechRecognitionActive = true;\n      showRecognitionLoader(true);\n      appendMessage(\"Listening... Answer with simple Spanish.\", \"bot\");\n      faceSpeak(\"Responde en espa\u00f1ol.\", \"neutral\", 1400);\n\n      try {\n        recognition.start();\n      } catch (err) {\n        speechRecognitionActive = false;\n        showRecognitionLoader(false);\n        appendMessage(\"The microphone could not start. Please try again.\", \"bot\");\n        faceSpeak(\"Intenta otra vez.\", \"sad\");\n      }\n    }\n\n    function containsKeyword(input, keyword) {\n      const escaped = keyword.replace(\/[.*+?^${}()|[\\]\\\\]\/g, \"\\\\$&\");\n      const pattern = new RegExp(`\\\\b${escaped}\\\\b`, \"i\");\n      return pattern.test(input);\n    }\n\n    function analyzeResponse(userInput, keywords) {\n      const input = userInput.toLowerCase();\n      let matchCount = 0;\n\n      keywords.forEach(keyword => {\n        if (containsKeyword(input, keyword.toLowerCase())) matchCount++;\n      });\n\n      const wordCount = input.split(\/\\s+\/).filter(Boolean).length;\n\n      if (matchCount >= 2 || (matchCount >= 1 && wordCount >= 4)) {\n        return {\n          level: \"high\",\n          message: pickRandom(positiveFeedback),\n          emotion: \"happy\",\n          shortFaceText: \"Muy bien\"\n        };\n      }\n\n      if (matchCount === 1 || wordCount >= 3) {\n        return {\n          level: \"mid\",\n          message: pickRandom(partialFeedback),\n          emotion: \"neutral\",\n          shortFaceText: \"Bien\"\n        };\n      }\n\n      return {\n        level: \"low\",\n        message: pickRandom(constructiveFeedback),\n        emotion: \"confused\",\n        shortFaceText: \"Otra vez\"\n      };\n    }\n\n    function checkStopKeywords(userInput) {\n      const pattern = new RegExp(\"\\\\b(\" + stopKeywords.map(escapeRegex).join(\"|\") + \")\\\\b\", \"i\");\n      return pattern.test(userInput);\n    }\n\n    function formatSentence(text) {\n      if (!text) return \"\";\n      text = text.trim();\n      text = text.charAt(0).toUpperCase() + text.slice(1);\n      if (!\/[.?!]$\/.test(text)) text += \".\";\n      return text;\n    }\n\n    function addSentenceToBox(sentence) {\n      const wordBox = document.getElementById('word-box');\n      const sentenceElement = document.createElement('div');\n      sentenceElement.innerText = sentence;\n      wordBox.appendChild(sentenceElement);\n      wordBox.scrollTop = wordBox.scrollHeight;\n    }\n\n    function clearWordBox() {\n      document.getElementById('word-box').innerHTML = \"\";\n    }\n\n    function clearChat() {\n      document.getElementById('chat-messages').innerHTML = \"\";\n    }\n\n    function endLesson(userStopped = false) {\n      stopAllAudioAndRecognition();\n      isLessonActive = false;\n      currentListeningItem = null;\n\n      const message = userStopped\n        ? \"Gracias. Adi\u00f3s.\"\n        : `Excelente. Terminaste la secci\u00f3n ${sectionData[currentSection].title}. Sigue practicando.`;\n\n      appendMessage(message, \"bot\");\n      faceSpeak(message, userStopped ? \"neutral\" : \"happy\");\n      showSynthesisLoader(true);\n      speakSpanish(message).then(() => showSynthesisLoader(false));\n    }\n\n    function speakText(text, lang, voiceObj) {\n      return new Promise(resolve => {\n        if (!speechSynthesisSupported) {\n          resolve();\n          return;\n        }\n        window.speechSynthesis.cancel();\n        const utterance = new SpeechSynthesisUtterance(text);\n        utterance.lang = lang;\n        utterance.rate = 0.9;\n        utterance.pitch = 1.0;\n        if (voiceObj) utterance.voice = voiceObj;\n        utterance.onend = () => resolve();\n        utterance.onerror = () => resolve();\n        window.speechSynthesis.speak(utterance);\n      });\n    }\n\n    function speakSpanish(text) {\n      return speakText(text, \"es-ES\", selectedSpanishVoice);\n    }\n\n    function 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(sender === 'bot' ? 'bot-message' : 'user-message');\n      messageElement.innerText = text;\n      messageContainer.appendChild(messageElement);\n      messageContainer.scrollTop = messageContainer.scrollHeight;\n    }\n\n    function showSynthesisLoader(show) {\n      const loader = document.getElementById('synthesis-loader');\n      loader.style.display = show ? 'inline-block' : 'none';\n      loader.style.borderTop = '4px solid #17a2b8';\n    }\n\n    function showRecognitionLoader(show) {\n      const loader = document.getElementById('synthesis-loader');\n      loader.style.display = show ? 'inline-block' : 'none';\n      loader.style.borderTop = show ? '4px solid #ffc107' : '4px solid #17a2b8';\n    }\n\n    function clearPendingTimeout() {\n      if (pendingNextQuestionTimeout) {\n        clearTimeout(pendingNextQuestionTimeout);\n        pendingNextQuestionTimeout = null;\n      }\n    }\n\n    function stopRecognitionOnly() {\n      if (recognition) {\n        try { recognition.stop(); } catch (e) {}\n      }\n      speechRecognitionActive = false;\n      showRecognitionLoader(false);\n    }\n\n    function stopAllAudioAndRecognition() {\n      clearPendingTimeout();\n      stopRecognitionOnly();\n      if (speechSynthesisSupported) window.speechSynthesis.cancel();\n      showSynthesisLoader(false);\n      stopFaceTalking();\n      faceSpeechBubble.classList.remove('visible');\n    }\n\n    function pickRandom(arr) {\n      return arr[Math.floor(Math.random() * arr.length)];\n    }\n\n    function escapeRegex(str) {\n      return str.replace(\/[.*+?^${}()|[\\]\\\\]\/g, \"\\\\$&\");\n    }\n\n    window.onload = initialize;\n  <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Learn Spanish: Greetings Q&#038;A Practice Greetings Introductions Meeting people Polite words How are you? Time of day Goodbye Friends Listen<\/p>\n","protected":false},"author":1,"featured_media":72,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[65,54],"tags":[],"class_list":["post-890","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced","category-spanish"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/890","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=890"}],"version-history":[{"count":1,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/890\/revisions"}],"predecessor-version":[{"id":891,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/890\/revisions\/891"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/72"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=890"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=890"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}