{"id":708,"date":"2026-01-28T08:22:13","date_gmt":"2026-01-28T08:22:13","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=708"},"modified":"2026-01-28T16:12:47","modified_gmt":"2026-01-28T16:12:47","slug":"city-living","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/city-living\/","title":{"rendered":"City Living"},"content":{"rendered":"\n<!-- \u2705 ICTE Chatbot Lesson \u2014 Personal Best (B1) Unit 7A + 7B (WP-safe single block) -->\n<div id=\"icte-u7ab\">\n\n  <!-- \u2705 TOP MENU -->\n  <nav class=\"icte-menu\" aria-label=\"Unit 7 lesson navigation\">\n    <a href=\"#\" class=\"is-current\" data-view=\"conversation\">Conversation<\/a>\n    <a href=\"#\" data-view=\"vocab\">Vocabulary<\/a>\n    <a href=\"#\" data-view=\"grammar\">Grammar<\/a>\n    <a href=\"#\" data-view=\"discussion\">Discussion<\/a>\n    <a href=\"#\" data-view=\"reading\">Reading<\/a>\n    <a href=\"#\" data-view=\"listening\">Listening<\/a>\n    <a href=\"#\" data-view=\"problemsolving\">Problem-solving<\/a>\n    <a href=\"#\" data-view=\"progress\">Progress<\/a>\n  <\/nav>\n\n  <section class=\"icte-shell\" aria-label=\"ICTE Unit 7A\u20137B practice\">\n\n    <!-- \u2705 Header -->\n    <header class=\"icte-hero\">\n      <div class=\"icte-hero__text\">\n        <h2>Unit 7 \u2014 City Living &#038; The Daily Commute<\/h2>\n        <p class=\"muted\">\n          Practice <b>present perfect with <i>yet<\/i> \/ <i>already<\/i><\/b>, city features, commuting vocabulary,\n          and listening for <b>facts &#038; figures<\/b>.\n        <\/p>\n      <\/div>\n\n      <div class=\"icte-hero__controls\">\n        <div class=\"icte-pill\">\n          <span class=\"dot\" id=\"icteMicDot\" aria-hidden=\"true\"><\/span>\n          <span id=\"icteMicStatus\">Mic: Off<\/span>\n        <\/div>\n\n        <div class=\"icte-row\">\n          <button class=\"btn\" id=\"icteStartVoice\" type=\"button\">Start Voice<\/button>\n          <button class=\"btn ghost\" id=\"icteStopVoice\" type=\"button\">Stop<\/button>\n        <\/div>\n\n        <label class=\"icte-label\">\n          Voice (Narrator)\n          <select id=\"icteVoiceSelect\" class=\"icte-select\" aria-label=\"Voice selection\"><\/select>\n        <\/label>\n\n        <div class=\"icte-small muted\">\n          Tip: If you don\u2019t see voices yet, click anywhere once and wait 2\u20133 seconds.\n        <\/div>\n      <\/div>\n    <\/header>\n\n    <!-- \u2705 Views -->\n    <main class=\"icte-main\">\n\n      <!-- ===================== -->\n      <!-- \u2705 1) CONVERSATION -->\n      <!-- ===================== -->\n      <section class=\"view is-active\" data-view=\"conversation\" aria-label=\"Conversation practice\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>1) Conversation (Voice Interactive)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"conv-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"convReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Speak your answers. Try to use <b>yet<\/b> in questions\/negatives and <b>already<\/b> in affirmative\n            sentences (present perfect). Example: <i>I haven\u2019t found a parking space yet.<\/i> \/ <i>I\u2019ve already taken the subway.<\/i>\n          <\/p>\n\n          <div class=\"chat\" id=\"convChat\" aria-live=\"polite\" aria-label=\"Conversation chat log\"><\/div>\n\n          <div class=\"chatbar\">\n            <input id=\"convText\" class=\"input\" type=\"text\" placeholder=\"Type your answer (optional)...\" autocomplete=\"off\" \/>\n            <button class=\"btn\" id=\"convSend\" type=\"button\">Send<\/button>\n            <button class=\"btn ghost\" id=\"convHear\" type=\"button\">\ud83d\udd0a Read last question<\/button>\n          <\/div>\n\n          <div class=\"muted icte-small\">\n            This conversation targets Unit 7A city life + Unit 7B commuting vocabulary\n            (e.g., <i>traffic jam, rush hour, platform, public transportation, parking lot\/space<\/i>).\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 2) VOCABULARY -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"vocab\" aria-label=\"Vocabulary practice\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>2) Vocabulary Matching + Use It<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"vocab-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"vocabReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Match the words to the meanings, then complete the sentences using the same vocabulary.\n            (City features list :contentReference[oaicite:4]{index=4}; transportation list :contentReference[oaicite:5]{index=5})\n          <\/p>\n\n          <div class=\"grid2\">\n            <div>\n              <h4 class=\"h4\">A) Matching<\/h4>\n              <div id=\"vocabMatch\" class=\"stack\"><\/div>\n              <button class=\"btn\" id=\"vocabCheck\" type=\"button\">Check<\/button>\n              <div class=\"feedback\" id=\"vocabFb\" aria-live=\"polite\"><\/div>\n            <\/div>\n\n            <div>\n              <h4 class=\"h4\">B) Use the words (Fill in the blanks)<\/h4>\n              <div id=\"vocabUse\" class=\"stack\"><\/div>\n              <button class=\"btn\" id=\"vocabUseCheck\" type=\"button\">Check<\/button>\n              <div class=\"feedback\" id=\"vocabUseFb\" aria-live=\"polite\"><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 3) GRAMMAR -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"grammar\" aria-label=\"Grammar practice\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>3) Grammar \u2014 Present perfect with <i>yet<\/i> \/ <i>already<\/i><\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"gram-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"gramReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Rule reminder:<\/b> Use <b>already<\/b> for something earlier than expected; use <b>yet<\/b> in questions\/negatives for something expected before now\n            (present perfect focus :contentReference[oaicite:6]{index=6}).\n          <\/div>\n\n          <div id=\"gramQuiz\" class=\"stack\"><\/div>\n          <button class=\"btn\" id=\"gramCheck\" type=\"button\">Check<\/button>\n          <div class=\"feedback\" id=\"gramFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 4) DISCUSSION -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"discussion\" aria-label=\"Discussion practice\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>4) Discussion (Voice or typing)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"disc-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"discNext\">Next question<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Answer with reasons. Try to include at least <b>2 vocabulary items<\/b> and at least <b>one<\/b> present perfect sentence using <i>yet<\/i> or <i>already<\/i>.\n          <\/p>\n\n          <div class=\"qa\">\n            <div class=\"q\" id=\"discQ\">Click <b>Next question<\/b> to begin.<\/div>\n            <textarea id=\"discA\" class=\"textarea\" rows=\"4\" placeholder=\"Write your answer here...\"><\/textarea>\n            <div class=\"row\">\n              <button class=\"btn\" id=\"discCheck\" type=\"button\">Check my language<\/button>\n              <button class=\"btn ghost\" id=\"discSpeakQ\" type=\"button\">\ud83d\udd0a Read question<\/button>\n            <\/div>\n            <div class=\"feedback\" id=\"discFb\" aria-live=\"polite\"><\/div>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 5) READING -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"reading\" aria-label=\"Reading comprehension\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>5) Reading Comprehension \u2014 \u201cFirst impressions of city life\u201d<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"read-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini\" type=\"button\" id=\"readTextBtn\">\ud83d\udd0a Read the text<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Read the text, then answer the questions. (Source text about Alex &#038; Megan :contentReference[oaicite:7]{index=7})\n          <\/p>\n\n          <article class=\"reading\" id=\"readingText\">\n            <div class=\"reading-title\">San Francisco \u2014 first impressions<\/div>\n            <div class=\"reading-p\">\n              <b>1<\/b> There are so many tech companies here; it\u2019s really exciting. I\u2019ve joined a networking group\n              to meet people from the industry, and I\u2019ve already been to a couple of events. Have I found a job yet?\n              No \u2026 but I\u2019m sure I will soon!\n            <\/div>\n            <div class=\"reading-p\">\n              <b>2<\/b> In my apartment building, there are people of all nationalities. I haven\u2019t met all my neighbors yet,\n              but I love living in a city where everyone is different.\n            <\/div>\n            <div class=\"reading-p\">\n              <b>3<\/b> Whatever you\u2019re interested in, there\u2019s always something going on. I\u2019ve already been to the theater twice\n              and I\u2019ve seen a baseball game, and I only arrived two weeks ago!\n            <\/div>\n            <div class=\"reading-p\">\n              <b>4<\/b> San Francisco is expensive and you need a good salary to live here. I\u2019ve rented a room in a shared house,\n              but after I\u2019ve paid the rent, it doesn\u2019t leave me with much money for other things.\n            <\/div>\n            <div class=\"reading-p\">\n              <b>5<\/b> The summers wouldn\u2019t be as hot as back home. It hasn\u2019t been too cold yet, but it can get really foggy,\n              especially when you\u2019re near the ocean.\n            <\/div>\n            <div class=\"reading-p\">\n              <b>6<\/b> The traffic here is terrible, so forget driving! Cable cars are fun, but public transportation can get really crowded,\n              so I just bought myself a bike. The bike lanes are great.\n            <\/div>\n          <\/article>\n\n          <div class=\"grid2\">\n            <div>\n              <h4 class=\"h4\">A) Main idea<\/h4>\n              <div class=\"qitem\">\n                <div class=\"qtext\">1) Who is more positive about the city?<\/div>\n                <select class=\"icte-select\" id=\"rQ1\">\n                  <option value=\"\">Choose\u2026<\/option>\n                  <option>Alex<\/option>\n                  <option>Megan<\/option>\n                <\/select>\n              <\/div>\n\n              <h4 class=\"h4\" style=\"margin-top:14px;\">B) Headings \u2192 Paragraphs<\/h4>\n              <p class=\"muted icte-small\">\n                Match each heading to a paragraph number (1\u20136). (Teacher\u2019s answers list :contentReference[oaicite:8]{index=8})\n              <\/p>\n              <div id=\"readMatch\" class=\"stack\"><\/div>\n\n              <button class=\"btn\" id=\"readCheck\" type=\"button\">Check<\/button>\n              <div class=\"feedback\" id=\"readFb\" aria-live=\"polite\"><\/div>\n            <\/div>\n\n            <div>\n              <h4 class=\"h4\">C) Find evidence (short answers)<\/h4>\n              <div id=\"readShort\" class=\"stack\"><\/div>\n              <button class=\"btn\" id=\"readShortCheck\" type=\"button\">Check<\/button>\n              <div class=\"feedback\" id=\"readShortFb\" aria-live=\"polite\"><\/div>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 6) LISTENING -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"listening\" aria-label=\"Listening comprehension\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>6) Listening Comprehension \u2014 Commuting around the world<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"list-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini\" type=\"button\" id=\"listenPlay\">\u25b6\ufe0f Play listening (TTS)<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"listenStop\">\u23f9 Stop<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Click <b>Play listening<\/b>. Listen for <b>facts and figures<\/b>, then complete the gaps.\n            Target answers include: <i>40 minutes, two hours, 7:30, eight million, push, white, 40 million, cars, argued, big<\/i>\n            (answer set :contentReference[oaicite:9]{index=9}).\n          <\/p>\n\n          <div class=\"note\">\n            Listening focus: identify numbers\/times and key nouns (e.g., <i>platform, passengers, parking space, public transportation<\/i>). (Skill + vocab focus :contentReference[oaicite:10]{index=10})\n          <\/div>\n\n          <div id=\"listenGaps\" class=\"stack\"><\/div>\n          <button class=\"btn\" id=\"listenCheck\" type=\"button\">Check<\/button>\n          <div class=\"feedback\" id=\"listenFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 7) PROBLEM-SOLVING -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"problemsolving\" aria-label=\"Problem-solving tasks\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>7) Problem-solving (Real-life city scenarios)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"prob-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"probReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Choose the best solution. Then write one sentence using present perfect with <i>yet<\/i> or <i>already<\/i>.\n          <\/p>\n\n          <div id=\"probSet\" class=\"stack\"><\/div>\n          <button class=\"btn\" id=\"probCheck\" type=\"button\">Check<\/button>\n          <div class=\"feedback\" id=\"probFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 8) PROGRESS -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"progress\" aria-label=\"Progress tracking\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>Progress<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini ghost\" type=\"button\" id=\"progressReset\">Clear progress<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"progress-grid\">\n            <div class=\"pbox\">\n              <div class=\"pnum\" id=\"pDone\">0<\/div>\n              <div class=\"muted\">Activities completed<\/div>\n            <\/div>\n            <div class=\"pbox\">\n              <div class=\"pnum\" id=\"pScore\">0%<\/div>\n              <div class=\"muted\">Average score<\/div>\n            <\/div>\n            <div class=\"pbox\">\n              <div class=\"pnum\" id=\"pVocab\">0<\/div>\n              <div class=\"muted\">Vocab used (detected)<\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            Completion is saved in your browser (local storage). If you change device\/browser, progress resets.\n          <\/div>\n\n          <h4 class=\"h4\">Vocabulary Bank (Unit 7A + 7B)<\/h4>\n          <div class=\"bank\" id=\"vocabBank\"><\/div>\n        <\/div>\n      <\/section>\n\n    <\/main>\n  <\/section>\n\n  <style>\n    \/* ===== WP-SAFE STYLES (scoped) ===== *\/\n    #icte-u7ab *{ box-sizing:border-box; }\n    #icte-u7ab{\n      --green:#28a745;\n      --dark:#132018;\n      --bg:#f4f6f8;\n      --card:#ffffff;\n      --muted:#6b7280;\n      --line:#e5e7eb;\n      --shadow:0 8px 24px rgba(17,24,39,.08);\n      --radius:16px;\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n      color:#111827;\n    }\n    #icte-u7ab .icte-menu{\n      width:100%;\n      background:var(--green);\n      padding:10px 12px;\n      text-align:center;\n      overflow-x:auto;\n      white-space:nowrap;\n      position:sticky;\n      top:0;\n      z-index:999;\n      box-shadow:0 2px 10px rgba(0,0,0,.12);\n      border-radius:12px;\n      margin-bottom:12px;\n    }\n    #icte-u7ab .icte-menu a{\n      display:inline-block;\n      color:#fff;\n      text-decoration:none;\n      font-weight:700;\n      padding:8px 10px;\n      border-radius:999px;\n      margin:0 3px;\n      opacity:.92;\n      transition:.15s;\n    }\n    #icte-u7ab .icte-menu a:hover{ opacity:1; background:rgba(255,255,255,.14); }\n    #icte-u7ab .icte-menu a.is-current{ background:#fff; color:var(--dark); opacity:1; }\n\n    #icte-u7ab .icte-shell{ max-width:1100px; margin:0 auto; }\n    #icte-u7ab .icte-hero{\n      display:grid;\n      grid-template-columns: 1.2fr .8fr;\n      gap:14px;\n      background:linear-gradient(135deg,#e9fff0, #ffffff);\n      border:1px solid var(--line);\n      border-radius:var(--radius);\n      padding:16px;\n      box-shadow:var(--shadow);\n      margin-bottom:14px;\n    }\n    @media (max-width: 920px){\n      #icte-u7ab .icte-hero{ grid-template-columns:1fr; }\n    }\n    #icte-u7ab h2{ margin:0 0 6px 0; font-size:22px; }\n    #icte-u7ab .muted{ color:var(--muted); }\n    #icte-u7ab .icte-hero__controls{\n      border-left:1px dashed var(--line);\n      padding-left:14px;\n    }\n    @media (max-width: 920px){\n      #icte-u7ab .icte-hero__controls{ border-left:none; padding-left:0; border-top:1px dashed var(--line); padding-top:12px; }\n    }\n    #icte-u7ab .icte-row{ display:flex; gap:8px; flex-wrap:wrap; }\n    #icte-u7ab .icte-label{ display:block; font-size:12px; font-weight:800; margin-top:10px; }\n    #icte-u7ab .icte-select{\n      width:100%;\n      padding:10px 10px;\n      border:1px solid var(--line);\n      border-radius:12px;\n      background:#fff;\n      outline:none;\n      margin-top:6px;\n    }\n    #icte-u7ab .icte-pill{\n      display:flex; align-items:center; gap:8px;\n      padding:10px 12px;\n      border:1px solid var(--line);\n      border-radius:999px;\n      background:#fff;\n      margin-bottom:10px;\n      font-weight:800;\n    }\n    #icte-u7ab .dot{\n      width:10px; height:10px; border-radius:50%;\n      background:#9ca3af;\n      box-shadow:0 0 0 4px rgba(156,163,175,.18);\n    }\n    #icte-u7ab .dot.on{\n      background:#22c55e;\n      box-shadow:0 0 0 4px rgba(34,197,94,.18);\n    }\n\n    #icte-u7ab .btn{\n      border:none;\n      padding:10px 12px;\n      border-radius:12px;\n      background:var(--green);\n      color:#fff;\n      font-weight:800;\n      cursor:pointer;\n      transition:.15s;\n    }\n    #icte-u7ab .btn:hover{ filter:brightness(.95); transform:translateY(-1px); }\n    #icte-u7ab .btn:active{ transform:translateY(0); }\n    #icte-u7ab .btn.ghost{\n      background:#fff;\n      color:#111827;\n      border:1px solid var(--line);\n    }\n    #icte-u7ab .btn.mini{ padding:8px 10px; border-radius:10px; font-size:13px; }\n    #icte-u7ab .icte-small{ font-size:12px; }\n\n    #icte-u7ab .card{\n      background:var(--card);\n      border:1px solid var(--line);\n      border-radius:var(--radius);\n      padding:14px;\n      box-shadow:var(--shadow);\n      margin-bottom:14px;\n    }\n    #icte-u7ab .card-h{\n      display:flex; gap:10px; align-items:flex-start; justify-content:space-between;\n      border-bottom:1px solid var(--line);\n      padding-bottom:10px;\n      margin-bottom:10px;\n    }\n    #icte-u7ab .card-h h3{ margin:0; font-size:18px; }\n    #icte-u7ab .card-actions{ display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end; }\n\n    #icte-u7ab .view{ display:none; }\n    #icte-u7ab .view.is-active{ display:block; }\n\n    #icte-u7ab .chat{\n      background:#0b1220;\n      color:#e5e7eb;\n      border-radius:14px;\n      padding:12px;\n      min-height:220px;\n      max-height:420px;\n      overflow:auto;\n      border:1px solid rgba(255,255,255,.08);\n    }\n    #icte-u7ab .msg{ margin:10px 0; display:flex; gap:10px; align-items:flex-start; }\n    #icte-u7ab .who{\n      min-width:72px;\n      font-weight:900;\n      font-size:12px;\n      color:#93c5fd;\n      text-transform:uppercase;\n      letter-spacing:.06em;\n    }\n    #icte-u7ab .bubble{\n      flex:1;\n      background:rgba(255,255,255,.06);\n      padding:10px 10px;\n      border-radius:12px;\n      line-height:1.45;\n      border:1px solid rgba(255,255,255,.06);\n    }\n    #icte-u7ab .msg.user .who{ color:#86efac; }\n    #icte-u7ab .msg.user .bubble{ background:rgba(34,197,94,.10); border-color:rgba(34,197,94,.18); }\n\n    #icte-u7ab .chatbar{\n      margin-top:10px;\n      display:flex;\n      gap:8px;\n      align-items:center;\n    }\n    #icte-u7ab .input{\n      flex:1;\n      padding:12px 12px;\n      border:1px solid var(--line);\n      border-radius:12px;\n      outline:none;\n    }\n    #icte-u7ab .textarea{\n      width:100%;\n      padding:12px;\n      border:1px solid var(--line);\n      border-radius:12px;\n      outline:none;\n      resize:vertical;\n    }\n\n    #icte-u7ab .grid2{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:14px;\n      margin-top:10px;\n    }\n    @media (max-width: 920px){ #icte-u7ab .grid2{ grid-template-columns:1fr; } }\n\n    #icte-u7ab .stack{ display:flex; flex-direction:column; gap:10px; }\n    #icte-u7ab .h4{ margin:0 0 6px 0; font-size:15px; }\n    #icte-u7ab .qitem{\n      padding:10px;\n      border:1px solid var(--line);\n      border-radius:14px;\n      background:#fafafa;\n    }\n    #icte-u7ab .qtext{ font-weight:800; margin-bottom:8px; }\n    #icte-u7ab .row{ display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }\n\n    #icte-u7ab .feedback{\n      margin-top:10px;\n      padding:10px 12px;\n      border-radius:14px;\n      border:1px solid var(--line);\n      background:#f9fafb;\n      font-weight:700;\n      display:none;\n    }\n    #icte-u7ab .feedback.ok{ display:block; border-color:rgba(34,197,94,.35); background:#ecfdf5; }\n    #icte-u7ab .feedback.bad{ display:block; border-color:rgba(239,68,68,.35); background:#fef2f2; }\n\n    #icte-u7ab .note{\n      background:#eff6ff;\n      border:1px solid rgba(59,130,246,.22);\n      padding:10px 12px;\n      border-radius:14px;\n      margin:10px 0;\n    }\n\n    #icte-u7ab .reading{\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:12px;\n      background:#fff;\n      margin-top:10px;\n    }\n    #icte-u7ab .reading-title{ font-weight:900; margin-bottom:8px; }\n    #icte-u7ab .reading-p{ padding:8px 0; border-top:1px dashed var(--line); }\n    #icte-u7ab .reading-p:first-of-type{ border-top:none; }\n\n    #icte-u7ab .qa .q{\n      padding:12px;\n      border-radius:14px;\n      border:1px solid var(--line);\n      background:#fff;\n      font-weight:900;\n    }\n\n    #icte-u7ab .progress-grid{\n      display:grid;\n      grid-template-columns:repeat(3,1fr);\n      gap:12px;\n      margin-top:10px;\n      margin-bottom:10px;\n    }\n    @media (max-width: 920px){ #icte-u7ab .progress-grid{ grid-template-columns:1fr; } }\n    #icte-u7ab .pbox{\n      border:1px solid var(--line);\n      border-radius:16px;\n      background:#fff;\n      padding:12px;\n      text-align:center;\n      box-shadow:var(--shadow);\n    }\n    #icte-u7ab .pnum{ font-size:28px; font-weight:1000; }\n    #icte-u7ab .bank{\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:12px;\n      background:#fff;\n      display:flex;\n      flex-wrap:wrap;\n      gap:8px;\n    }\n    #icte-u7ab .tag{\n      border:1px solid var(--line);\n      background:#f9fafb;\n      padding:6px 10px;\n      border-radius:999px;\n      font-weight:800;\n      font-size:13px;\n    }\n  <\/style>\n\n  <script>\n    (function(){\n      const root = document.getElementById('icte-u7ab');\n      if(!root) return;\n\n      \/* =========================\n         Helpers\n      ========================= *\/\n      const qs = (sel, el=root) => el.querySelector(sel);\n      const qsa = (sel, el=root) => Array.from(el.querySelectorAll(sel));\n      const clamp = (n,min,max)=>Math.max(min,Math.min(max,n));\n      const norm = (s)=> (s||\"\").toString().trim();\n      const lower = (s)=> norm(s).toLowerCase();\n      const esc = (s)=> (s||\"\").replace(\/[&<>\"']\/g, m=>({ \"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#039;\" }[m]));\n      const shuffle = (arr)=> arr.map(v=>({v, r:Math.random()})).sort((a,b)=>a.r-b.r).map(o=>o.v);\n\n      \/* =========================\n         Navigation\n      ========================= *\/\n      const navLinks = qsa('.icte-menu a');\n      const views = qsa('.view');\n\n      function showView(name){\n        views.forEach(v=> v.classList.toggle('is-active', v.getAttribute('data-view')===name));\n        navLinks.forEach(a=> a.classList.toggle('is-current', a.getAttribute('data-view')===name));\n        window.scrollTo({top: root.offsetTop - 10, behavior:'smooth'});\n      }\n      navLinks.forEach(a=>{\n        a.addEventListener('click', (e)=>{\n          e.preventDefault();\n          const v = a.getAttribute('data-view');\n          if(v) showView(v);\n        });\n      });\n\n      \/* =========================\n         Speech (TTS + ASR)\n      ========================= *\/\n      const micDot = qs('#icteMicDot');\n      const micStatus = qs('#icteMicStatus');\n      const voiceSel = qs('#icteVoiceSelect');\n      const btnStartVoice = qs('#icteStartVoice');\n      const btnStopVoice = qs('#icteStopVoice');\n\n      let voices = [];\n      let chosenVoice = null;\n      let recognition = null;\n      let listening = false;\n      let lastPromptSpoken = \"\";\n\n      function pickPreferredVoice(vs){\n        const preferred = vs.filter(v=>{\n          const n = (v.name||\"\").toLowerCase();\n          const l = (v.lang||\"\").toLowerCase();\n          const isEN = l.startsWith('en');\n          const isUSUK = (l.includes('us') || l.includes('gb') || l.includes('uk'));\n          const isGoogle = n.includes('google');\n          return isEN && isUSUK && isGoogle;\n        });\n        return preferred[0] || vs.find(v=>(v.lang||\"\").toLowerCase().startsWith('en')) || vs[0] || null;\n      }\n\n      function loadVoices(){\n        voices = window.speechSynthesis ? speechSynthesis.getVoices() : [];\n        \/\/ Prefer Google US\/UK if available; fallback to any English voice\n        let list = voices.filter(v=>{\n          const l = (v.lang||\"\").toLowerCase();\n          const n = (v.name||\"\").toLowerCase();\n          const isEN = l.startsWith('en');\n          const isUSUK = (l.includes('us') || l.includes('gb') || l.includes('uk'));\n          const isGoogle = n.includes('google');\n          return isEN && isUSUK && isGoogle;\n        });\n        if(list.length === 0){\n          list = voices.filter(v => (v.lang||\"\").toLowerCase().startsWith('en'));\n        }\n        if(list.length === 0) list = voices;\n\n        voiceSel.innerHTML = \"\";\n        list.forEach((v,i)=>{\n          const opt = document.createElement('option');\n          opt.value = i;\n          opt.textContent = `${v.name} (${v.lang})`;\n          voiceSel.appendChild(opt);\n        });\n\n        chosenVoice = pickPreferredVoice(list);\n        if(chosenVoice){\n          const idx = list.indexOf(chosenVoice);\n          if(idx >= 0) voiceSel.value = String(idx);\n        }\n\n        voiceSel.onchange = ()=>{\n          const idx = parseInt(voiceSel.value, 10);\n          chosenVoice = list[idx] || chosenVoice;\n        };\n      }\n\n      if(window.speechSynthesis){\n        loadVoices();\n        speechSynthesis.onvoiceschanged = loadVoices;\n      }\n\n      function speak(text){\n        const t = norm(text);\n        if(!t || !window.speechSynthesis) return;\n        speechSynthesis.cancel();\n        const u = new SpeechSynthesisUtterance(t);\n        if(chosenVoice) u.voice = chosenVoice;\n        u.rate = 1.02;\n        u.pitch = 1.0;\n        u.volume = 1.0;\n        speechSynthesis.speak(u);\n      }\n      function stopSpeak(){\n        if(window.speechSynthesis) speechSynthesis.cancel();\n      }\n\n      function setMicUI(on){\n        listening = on;\n        micDot.classList.toggle('on', on);\n        micStatus.textContent = on ? \"Mic: Listening\u2026\" : \"Mic: Off\";\n      }\n\n      function initRecognition(){\n        const SR = window.SpeechRecognition || window.webkitSpeechRecognition;\n        if(!SR) return null;\n        const r = new SR();\n        r.lang = 'en-US';\n        r.continuous = true;\n        r.interimResults = false;\n        r.maxAlternatives = 1;\n        return r;\n      }\n\n      function startListening(){\n        if(listening) return;\n        recognition = recognition || initRecognition();\n        if(!recognition){\n          alert(\"Speech Recognition is not available in this browser. Use Chrome\/Edge, or type your answers.\");\n          return;\n        }\n\n        recognition.onresult = (e)=>{\n          const res = e.results[e.results.length - 1];\n          const transcript = res && res[0] ? res[0].transcript : \"\";\n          handleVoiceTranscript(transcript);\n        };\n        recognition.onerror = ()=> setMicUI(false);\n        recognition.onend = ()=>{\n          \/\/ auto-restart if user didn't press Stop\n          if(listening){\n            try{ recognition.start(); }catch(_){}\n          }\n        };\n\n        setMicUI(true);\n        try{ recognition.start(); }catch(_){}\n      }\n\n      function stopListening(){\n        setMicUI(false);\n        try{ recognition && recognition.stop(); }catch(_){}\n      }\n\n      btnStartVoice.addEventListener('click', startListening);\n      btnStopVoice.addEventListener('click', stopListening);\n\n      \/* =========================\n         Progress (localStorage)\n      ========================= *\/\n      const LS_KEY = \"icte_u7ab_progress_v1\";\n      const progress = JSON.parse(localStorage.getItem(LS_KEY) || \"{}\");\n      function saveProgress(){ localStorage.setItem(LS_KEY, JSON.stringify(progress)); renderProgress(); }\n      function markDone(key, scorePct){\n        progress[key] = { done:true, score: clamp(scorePct,0,100), ts: Date.now() };\n        saveProgress();\n      }\n      function resetProgress(){\n        localStorage.removeItem(LS_KEY);\n        location.reload();\n      }\n\n      function renderProgress(){\n        const items = Object.values(progress).filter(p=>p && p.done);\n        const done = items.length;\n        const avg = done ? Math.round(items.reduce((s,p)=>s+(p.score||0),0)\/done) : 0;\n\n        qs('#pDone').textContent = String(done);\n        qs('#pScore').textContent = String(avg) + \"%\";\n\n        \/\/ detect vocab usage from conversation + discussion text\n        const used = new Set();\n        const txt = (progress._vocabUsedText||\"\");\n        vocabAll.forEach(w=>{\n          if(lower(txt).includes(lower(w.word))) used.add(w.word);\n        });\n        qs('#pVocab').textContent = String(used.size);\n      }\n\n      qs('#progressReset').addEventListener('click', resetProgress);\n\n      \/* =========================\n         Content: Vocabulary bank\n      ========================= *\/\n      const vocab7A = [\n        {word:\"apartment building\", def:\"a building with many apartments\"},\n        {word:\"bench\", def:\"a long seat in a public place\"},\n        {word:\"bike lane\", def:\"a part of the road for bicycles\"},\n        {word:\"bridge\", def:\"a structure over a river\/road\"},\n        {word:\"intersection\", def:\"where two roads meet\"},\n        {word:\"crosswalk\", def:\"painted lines for people to cross\"},\n        {word:\"fountain\", def:\"a public water feature\"},\n        {word:\"sidewalk\", def:\"the path beside the road for walking\"},\n        {word:\"street sign\", def:\"a sign with street name\/directions\"},\n        {word:\"statue\", def:\"a sculpture of a person\/thing\"},\n        {word:\"streetlight\", def:\"a light on the street\"},\n        {word:\"traffic lights\", def:\"red\/amber\/green lights controlling traffic\"},\n        {word:\"trash can\", def:\"a container for rubbish\"},\n        {word:\"tunnel\", def:\"a passage under the ground\/through a hill\"}\n      ];\n\n      const vocab7B = [\n        {word:\"arrive on time\", def:\"get somewhere at the planned time\"},\n        {word:\"commuter\", def:\"a person who travels regularly to work\/school\"},\n        {word:\"delayed\", def:\"late; happening later than planned\"},\n        {word:\"drive\", def:\"use a car (or similar vehicle)\"},\n        {word:\"give (someone) a ride\", def:\"take someone in your car\"},\n        {word:\"go by taxi\", def:\"travel using a taxi\"},\n        {word:\"it takes (me) \u2026\", def:\"the time needed to travel\"},\n        {word:\"parking lot\", def:\"a place for parking cars (AmE)\"},\n        {word:\"parking space\", def:\"one place to park a car\"},\n        {word:\"passenger\", def:\"a person traveling (not the driver)\"},\n        {word:\"platform\", def:\"a raised area at a station where trains stop\"},\n        {word:\"public transportation\", def:\"buses\/trains\/subway used by the public\"},\n        {word:\"ride a bike\", def:\"travel by bicycle\"},\n        {word:\"ride a motorcycle\", def:\"travel by motorbike\"},\n        {word:\"rush hour\", def:\"the busiest traffic time (morning\/evening)\"},\n        {word:\"start out (early)\", def:\"begin a journey (earlier than usual)\"},\n        {word:\"take the bus\", def:\"travel by bus\"},\n        {word:\"take the train\", def:\"travel by train\"},\n        {word:\"take the subway\", def:\"travel by underground train\"},\n        {word:\"traffic jam\", def:\"a line of cars that can\u2019t move well\"},\n        {word:\"walk \/ on foot\", def:\"travel by walking\"}\n      ];\n\n      const vocabAll = [...vocab7A, ...vocab7B];\n\n      \/\/ render bank\n      const bank = qs('#vocabBank');\n      bank.innerHTML = vocabAll.map(v=>`<span class=\"tag\" title=\"${esc(v.def)}\">${esc(v.word)}<\/span>`).join(\"\");\n\n      \/* =========================\n         Instruction TTS map\n      ========================= *\/\n      const SAY = {\n        \"conv-instr\":\n          \"Conversation. Start Voice, then answer the chatbot. Try to use present perfect with yet and already. Use city and commuting vocabulary, like traffic jam, rush hour, platform, parking space, and public transportation.\",\n        \"vocab-instr\":\n          \"Vocabulary. First, match the words to meanings. Then complete the sentences using the same words. Check your answers when you finish.\",\n        \"gram-instr\":\n          \"Grammar. Choose yet or already and complete each sentence in the present perfect. Remember: yet is common in questions and negative sentences, and already is used in affirmative sentences for something earlier than expected.\",\n        \"disc-instr\":\n          \"Discussion. Answer with reasons. Include at least two vocabulary items and one present perfect sentence using yet or already.\",\n        \"read-instr\":\n          \"Reading. Read the text about two people who moved to San Francisco. Then answer the questions: main idea, headings, and evidence.\",\n        \"list-instr\":\n          \"Listening. Click Play listening. Listen for facts and figures such as times, numbers, and key nouns. Then fill the gaps and check your answers.\",\n        \"prob-instr\":\n          \"Problem-solving. Choose the best solution for each city or commuting problem. Then write one sentence using present perfect with yet or already.\"\n      };\n\n      qsa('[data-say]').forEach(btn=>{\n        btn.addEventListener('click', ()=>{\n          const k = btn.getAttribute('data-say');\n          if(SAY[k]) speak(SAY[k]);\n        });\n      });\n\n      \/* =========================\n         1) Conversation engine\n      ========================= *\/\n      const convChat = qs('#convChat');\n      const convText = qs('#convText');\n      const convSend = qs('#convSend');\n      const convHear = qs('#convHear');\n      const convReset = qs('#convReset');\n\n      let convStep = 0;\n      const convSteps = [\n        {\n          bot: \"Hi! Welcome to Unit 7. First: Do you live in a city, a town, or a village? Tell me one good thing and one bad thing.\",\n          check: (a)=> a.length >= 10,\n          tips: \"Try to include one city feature word (e.g., sidewalk, bike lane, traffic lights).\"\n        },\n        {\n          bot: \"Have you joined a gym yet? Answer in a full sentence.\",\n          check: (a)=> \/yet\\b\/i.test(a) && \/(have|has|haven't|hasn't)\\b\/i.test(a),\n          tips: \"Use present perfect + yet. Example: I haven't joined a gym yet.\"\n        },\n        {\n          bot: \"Tell me something you have already done this week in your city.\",\n          check: (a)=> \/\\balready\\b\/i.test(a) && \/\\b(i'?ve|i have|has|have)\\b\/i.test(a),\n          tips: \"Use already in an affirmative present perfect sentence.\"\n        },\n        {\n          bot: \"Commuting: How do you get to work or school? (walk, drive, take the bus\/train\/subway, ride a bike...)\",\n          check: (a)=> \/(walk|on foot|drive|take the bus|take the train|take the subway|ride a bike|go by taxi)\/i.test(a),\n          tips: \"Use transportation vocabulary.\"\n        },\n        {\n          bot: \"Do you ever get delayed because of traffic jams or rush hour? Use 'yet' or 'already' in your answer.\",\n          check: (a)=> (\/\\byet\\b\/i.test(a) || \/\\balready\\b\/i.test(a)) && \/(traffic jam|rush hour|delayed|traffic)\/i.test(a),\n          tips: \"Example: I haven't arrived on time yet because of the traffic jam.\"\n        },\n        {\n          bot: \"Last one: Describe a safe route in your city using at least 3 city features (e.g., crosswalk, intersection, streetlight).\",\n          check: (a)=> {\n            const hits = [\"crosswalk\",\"intersection\",\"streetlight\",\"sidewalk\",\"traffic lights\",\"bridge\",\"tunnel\",\"street sign\",\"trash can\",\"bench\",\"bike lane\",\"fountain\",\"statue\",\"apartment building\"]\n              .filter(w=> lower(a).includes(lower(w)));\n            return hits.length >= 3;\n          },\n          tips: \"Use three or more city feature words.\"\n        }\n      ];\n\n      function addMsg(who, text){\n        const div = document.createElement('div');\n        div.className = 'msg ' + (who==='You' ? 'user' : 'bot');\n        div.innerHTML = `<div class=\"who\">${esc(who)}<\/div><div class=\"bubble\">${esc(text)}<\/div>`;\n        convChat.appendChild(div);\n        convChat.scrollTop = convChat.scrollHeight;\n      }\n\n      function botAsk(){\n        const step = convSteps[convStep];\n        if(!step){\n          addMsg(\"Bot\",\"Great work! You used Unit 7 city + commute language. You can repeat or go to other sections.\");\n          speak(\"Great work! You can repeat or go to other sections.\");\n          markDone(\"conversation\", 100);\n          return;\n        }\n        addMsg(\"Bot\", step.bot);\n        lastPromptSpoken = step.bot;\n        speak(step.bot);\n      }\n\n      function scoreAnswer(a){\n        const step = convSteps[convStep];\n        if(!step) return {ok:true, msg:\"\"};\n        const ok = step.check(norm(a));\n        let msg = ok ? \"\u2705 Good. Nice use of the target language.\" : \"\u26a0\ufe0f Try again. \" + step.tips;\n        \/\/ simple grammar nudges:\n        if(!ok){\n          if(convStep===1 && !\/\\byet\\b\/i.test(a)) msg += \" Remember: use 'yet' at the end.\";\n          if(convStep===2 && !\/\\balready\\b\/i.test(a)) msg += \" Remember: use 'already' in the middle (I\u2019ve already \u2026).\";\n        }\n        return {ok, msg};\n      }\n\n      function handleConversationInput(text){\n        const a = norm(text);\n        if(!a) return;\n        addMsg(\"You\", a);\n\n        \/\/ track vocab usage text for progress\n        progress._vocabUsedText = (progress._vocabUsedText||\"\") + \" \" + a;\n        saveProgress();\n\n        const {ok, msg} = scoreAnswer(a);\n        addMsg(\"Bot\", msg);\n        speak(msg);\n\n        if(ok){\n          convStep++;\n          setTimeout(botAsk, 500);\n        }else{\n          \/\/ repeat question with guidance\n          setTimeout(()=>{ speak(\"Please try again. \" + convSteps[convStep].bot); }, 600);\n        }\n      }\n\n      convSend.addEventListener('click', ()=> handleConversationInput(convText.value));\n      convText.addEventListener('keydown', (e)=>{\n        if(e.key === \"Enter\"){ e.preventDefault(); handleConversationInput(convText.value); }\n      });\n      convHear.addEventListener('click', ()=> speak(lastPromptSpoken || \"No question yet.\"));\n      convReset.addEventListener('click', ()=>{\n        convChat.innerHTML = \"\";\n        convStep = 0;\n        addMsg(\"Bot\",\"Ready! Start Voice (optional) and answer. Let\u2019s begin.\");\n        botAsk();\n      });\n\n      \/\/ initial\n      addMsg(\"Bot\",\"Ready! Start Voice (optional) and answer. Let\u2019s begin.\");\n      botAsk();\n\n      \/* =========================\n         Route voice transcripts\n      ========================= *\/\n      function activeViewName(){\n        const v = qs('.view.is-active');\n        return v ? v.getAttribute('data-view') : \"conversation\";\n      }\n\n      function handleVoiceTranscript(t){\n        const text = norm(t);\n        if(!text) return;\n\n        const v = activeViewName();\n        if(v === \"conversation\"){\n          handleConversationInput(text);\n          convText.value = \"\";\n          return;\n        }\n        if(v === \"discussion\"){\n          const ta = qs('#discA');\n          ta.value = (ta.value ? (ta.value + \" \") : \"\") + text;\n          speak(\"I heard: \" + text);\n          return;\n        }\n\n        \/\/ fallback: put into current input if exists\n        const input = qs('.view.is-active input.input');\n        if(input){\n          input.value = text;\n          speak(\"I heard: \" + text);\n        }else{\n          speak(\"I heard: \" + text);\n        }\n      }\n\n      \/* =========================\n         2) Vocabulary matching + use\n      ========================= *\/\n      const matchSet = shuffle([\n        {word:\"sidewalk\", def:\"the path beside the road for walking\"},\n        {word:\"crosswalk\", def:\"painted lines for people to cross\"},\n        {word:\"traffic lights\", def:\"red\/amber\/green lights controlling traffic\"},\n        {word:\"bike lane\", def:\"a part of the road for bicycles\"},\n        {word:\"platform\", def:\"a raised area at a station where trains stop\"},\n        {word:\"public transportation\", def:\"buses\/trains\/subway used by the public\"},\n        {word:\"traffic jam\", def:\"a line of cars that can\u2019t move well\"},\n        {word:\"rush hour\", def:\"the busiest traffic time\"},\n        {word:\"parking space\", def:\"one place to park a car\"},\n        {word:\"commuter\", def:\"a person who travels regularly to work\/school\"}\n      ]);\n\n      const vocabMatch = qs('#vocabMatch');\n      const vocabFb = qs('#vocabFb');\n\n      function renderVocabMatching(){\n        const defs = shuffle(matchSet.map(x=>x.def));\n        vocabMatch.innerHTML = matchSet.map((x,i)=>{\n          const options = ['<option value=\"\">Choose meaning\u2026<\/option>']\n            .concat(defs.map(d=>`<option value=\"${esc(d)}\">${esc(d)}<\/option>`))\n            .join(\"\");\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${i+1}) <b>${esc(x.word)}<\/b><\/div>\n              <select class=\"icte-select\" data-ans=\"${esc(x.def)}\">${options}<\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderVocabMatching();\n\n      qs('#vocabCheck').addEventListener('click', ()=>{\n        const sels = qsa('#vocabMatch select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const pct = Math.round((correct \/ sels.length) * 100);\n        vocabFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        vocabFb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        markDone(\"vocab_match\", pct);\n      });\n\n      qs('#vocabReset').addEventListener('click', ()=>{\n        renderVocabMatching();\n        vocabFb.style.display=\"none\";\n        qs('#vocabUseFb').style.display=\"none\";\n        renderVocabUse();\n      });\n\n      \/\/ Use it\n      const vocabUse = qs('#vocabUse');\n      const useItems = [\n        {sent:\"I always walk on the ____ when the road is busy.\", ans:\"sidewalk\"},\n        {sent:\"At the ____, I wait for the train and then get on.\", ans:\"platform\"},\n        {sent:\"During ____, the streets are crowded and commuters get delayed.\", ans:\"rush hour\"},\n        {sent:\"I haven\u2019t found a ____ yet, so I parked far away.\", ans:\"parking space\"},\n        {sent:\"There was a huge ____, so I went by taxi.\", ans:\"traffic jam\"},\n        {sent:\"I take ____ because it\u2019s cheaper than driving.\", ans:\"public transportation\"}\n      ];\n\n      function renderVocabUse(){\n        const bank = shuffle(useItems.map(x=>x.ans));\n        vocabUse.innerHTML = useItems.map((x,i)=>{\n          const opts = ['<option value=\"\">Choose\u2026<\/option>']\n            .concat(bank.map(w=>`<option value=\"${esc(w)}\">${esc(w)}<\/option>`)).join(\"\");\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${i+1}) ${esc(x.sent)}<\/div>\n              <select class=\"icte-select\" data-ans=\"${esc(x.ans)}\">${opts}<\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderVocabUse();\n\n      qs('#vocabUseCheck').addEventListener('click', ()=>{\n        const sels = qsa('#vocabUse select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const pct = Math.round((correct \/ sels.length) * 100);\n        const fb = qs('#vocabUseFb');\n        fb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        fb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        markDone(\"vocab_use\", pct);\n      });\n\n      \/* =========================\n         3) Grammar quiz\n      ========================= *\/\n      const gramQuiz = qs('#gramQuiz');\n      const gramFb = qs('#gramFb');\n\n      const gramItems = [\n        {q:\"Have you found a job ____ ?\", ans:\"yet\"},\n        {q:\"I\u2019ve ____ been to the theater twice.\", ans:\"already\"},\n        {q:\"She hasn\u2019t arrived ____ .\", ans:\"yet\"},\n        {q:\"I\u2019ve ____ paid the rent.\", ans:\"already\"},\n        {q:\"Have you taken the subway ____ ?\", ans:\"yet\"},\n        {q:\"We\u2019ve ____ started out early to avoid rush hour.\", ans:\"already\"},\n        {q:\"I haven\u2019t used public transportation ____ today.\", ans:\"yet\"},\n        {q:\"He\u2019s ____ bought a bike for the bike lane.\", ans:\"already\"},\n        {q:\"They haven\u2019t had dinner ____ .\", ans:\"yet\"},\n        {q:\"I\u2019ve ____ found a parking space.\", ans:\"already\"}\n      ];\n\n      function renderGrammar(){\n        gramQuiz.innerHTML = gramItems.map((it,i)=>{\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${i+1}) ${esc(it.q)}<\/div>\n              <select class=\"icte-select\" data-ans=\"${esc(it.ans)}\">\n                <option value=\"\">Choose\u2026<\/option>\n                <option value=\"yet\">yet<\/option>\n                <option value=\"already\">already<\/option>\n              <\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderGrammar();\n\n      qs('#gramCheck').addEventListener('click', ()=>{\n        const sels = qsa('#gramQuiz select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const pct = Math.round((correct \/ sels.length) * 100);\n        gramFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        gramFb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        markDone(\"grammar\", pct);\n      });\n\n      qs('#gramReset').addEventListener('click', ()=>{\n        renderGrammar();\n        gramFb.style.display=\"none\";\n      });\n\n      \/* =========================\n         4) Discussion\n      ========================= *\/\n      const discQ = qs('#discQ');\n      const discA = qs('#discA');\n      const discFb = qs('#discFb');\n\n      const discQs = [\n        \"What are the best and worst things about living in a big city? Mention at least two city features.\",\n        \"Is public transportation in your city reliable, or do commuters often get delayed? Give reasons.\",\n        \"How can a city reduce traffic jams during rush hour? Suggest at least two solutions.\",\n        \"Do you prefer to drive, take the bus\/train\/subway, or ride a bike? Why?\",\n        \"Have you already found a good place to study or work in your city? If not, what haven\u2019t you done yet?\"\n      ];\n      let discIdx = -1;\n\n      function nextDiscQ(){\n        discIdx = (discIdx + 1) % discQs.length;\n        discQ.innerHTML = esc(discQs[discIdx]);\n        discA.value = \"\";\n        discFb.style.display = \"none\";\n        speak(discQs[discIdx]);\n      }\n      qs('#discNext').addEventListener('click', nextDiscQ);\n      qs('#discSpeakQ').addEventListener('click', ()=> speak(discQs[Math.max(0,discIdx)] || \"Click next question to begin.\"));\n\n      function detectTargets(text){\n        const t = lower(text);\n        const vocabHits = vocabAll.filter(v => t.includes(lower(v.word))).map(v=>v.word);\n        const hasYetAlready = \/\\byet\\b\/i.test(text) || \/\\balready\\b\/i.test(text);\n        const hasPP = \/\\b(i'?ve|we'?ve|they'?ve|you'?ve|he'?s|she'?s|it'?s|i have|we have|they have|you have|he has|she has|it has|haven't|hasn't)\\b\/i.test(text);\n        return { vocabHits, hasYetAlready, hasPP };\n      }\n\n      qs('#discCheck').addEventListener('click', ()=>{\n        const ans = norm(discA.value);\n        if(!ans){\n          discFb.className = \"feedback bad\";\n          discFb.textContent = \"Please write or speak an answer first.\";\n          return;\n        }\n\n        progress._vocabUsedText = (progress._vocabUsedText||\"\") + \" \" + ans;\n        saveProgress();\n\n        const {vocabHits, hasYetAlready, hasPP} = detectTargets(ans);\n        const okV = vocabHits.length >= 2;\n        const okG = hasYetAlready && hasPP;\n\n        let score = 0;\n        if(okV) score += 50;\n        if(okG) score += 50;\n\n        discFb.className = \"feedback \" + (score>=70 ? \"ok\":\"bad\");\n        discFb.textContent =\n          `Detected vocab: ${vocabHits.slice(0,8).join(\", \") || \"none\"}.\n           Grammar: ${okG ? \"\u2705 present perfect + yet\/already\" : \"\u26a0\ufe0f try adding yet\/already in present perfect\"}.\n           Score: ${score}%.`;\n\n        markDone(\"discussion\", score);\n      });\n\n      \/* =========================\n         5) Reading\n      ========================= *\/\n      const readMatch = qs('#readMatch');\n      const readFb = qs('#readFb');\n\n      const headings = [\n        \"Job opportunities\",\"The people\",\"Entertainment\",\"Cost of living\",\"The weather\",\"Transportation\"\n      ];\n      \/\/ Correct mapping from TB answers: 1 Job opportunities, 2 The people, 3 Entertainment, 4 Cost of living, 5 The weather, 6 Transportation :contentReference[oaicite:11]{index=11}\n      const headAns = {\n        \"Job opportunities\":\"1\",\n        \"The people\":\"2\",\n        \"Entertainment\":\"3\",\n        \"Cost of living\":\"4\",\n        \"The weather\":\"5\",\n        \"Transportation\":\"6\"\n      };\n\n      function renderReadMatch(){\n        readMatch.innerHTML = headings.map(h=>{\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${esc(h)}<\/div>\n              <select class=\"icte-select\" data-ans=\"${esc(headAns[h])}\">\n                <option value=\"\">Paragraph\u2026<\/option>\n                <option>1<\/option><option>2<\/option><option>3<\/option><option>4<\/option><option>5<\/option><option>6<\/option>\n              <\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderReadMatch();\n\n      const readShort = qs('#readShort');\n      const readShortFb = qs('#readShortFb');\n\n      const shortItems = [\n        {q:\"Which paragraph mentions 'foggy' weather near the ocean?\", ans:\"5\"},\n        {q:\"Which paragraph mentions 'public transportation' and buying a bike?\", ans:\"6\"},\n        {q:\"Which paragraph shows networking and events but no job yet?\", ans:\"1\"},\n        {q:\"Which paragraph mentions expensive rent and not much money left?\", ans:\"4\"}\n      ];\n\n      function renderReadShort(){\n        readShort.innerHTML = shortItems.map((it,i)=>`\n          <div class=\"qitem\">\n            <div class=\"qtext\">${i+1}) ${esc(it.q)}<\/div>\n            <select class=\"icte-select\" data-ans=\"${esc(it.ans)}\">\n              <option value=\"\">Choose\u2026<\/option>\n              <option>1<\/option><option>2<\/option><option>3<\/option><option>4<\/option><option>5<\/option><option>6<\/option>\n            <\/select>\n          <\/div>\n        `).join(\"\");\n      }\n      renderReadShort();\n\n      qs('#readCheck').addEventListener('click', ()=>{\n        let score = 0;\n        const q1 = qs('#rQ1');\n        const ok1 = q1.value === \"Alex\"; \/\/ Alex more positive :contentReference[oaicite:12]{index=12}\n        if(ok1) score += 40;\n        q1.style.borderColor = ok1 ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n\n        const sels = qsa('#readMatch select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const part = Math.round((correct \/ sels.length) * 60);\n        score += part;\n\n        readFb.className = \"feedback \" + (score>=70 ? \"ok\":\"bad\");\n        readFb.textContent = `Score: ${score}%. (Main idea + headings)`;\n        markDone(\"reading\", score);\n      });\n\n      qs('#readShortCheck').addEventListener('click', ()=>{\n        const sels = qsa('#readShort select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const pct = Math.round((correct \/ sels.length) * 100);\n        readShortFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        readShortFb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        markDone(\"reading_evidence\", pct);\n      });\n\n      \/\/ Reading TTS\n      qs('#readTextBtn').addEventListener('click', ()=>{\n        const text = qsa('#readingText .reading-p').map(p=>p.textContent).join(\" \");\n        speak(\"Reading text. \" + text);\n      });\n\n      \/* =========================\n         6) Listening (TTS script + gaps)\n      ========================= *\/\n      \/\/ Build a short, teacher-friendly listening script using the provided facts & vocabulary.\n      \/\/ Facts\/figures align with TB answer set :contentReference[oaicite:13]{index=13} and narrative source :contentReference[oaicite:14]{index=14}\n      const listeningScript =\n        \"Welcome to Learning Curve. Today we talk about commuting. Worldwide, the average journey time to work is 40 minutes. \" +\n        \"But in some cities it is worse. In Bangkok, the average commuting time is two hours, especially at rush hour between 7:30 and 9:30. \" +\n        \"Bangkok has a population of eight million people, and traffic jams can be terrible. \" +\n        \"In Tokyo, pushers push passengers from the platform onto the trains, and they wear white gloves. Tokyo's train system carries 40 million passengers a day. \" +\n        \"In New Delhi, seven million cars come into the city every day. Twenty four percent of people have argued about a parking space in the last year, and parking lots are so big that people can lose their cars.\";\n\n      const gapItems = [\n        {q:\"1) The global average commuting time is ____.\", ans:\"40 minutes\"},\n        {q:\"2) In Bangkok, the average commuting time is ____.\", ans:\"two hours\"},\n        {q:\"3) Rush hour is between ____ and 9:30 in the morning.\", ans:\"7:30\"},\n        {q:\"4) Bangkok has a population of ____ people.\", ans:\"eight million\"},\n        {q:\"5) Companies employ pushers to ____ passengers onto trains.\", ans:\"push\"},\n        {q:\"6) Pushers wear ____ gloves.\", ans:\"white\"},\n        {q:\"7) Tokyo's train system carries ____ passengers a day.\", ans:\"40 million\"},\n        {q:\"8) Seven million ____ come into New Delhi every day.\", ans:\"cars\"},\n        {q:\"9) 24% of people have ____ about a parking space.\", ans:\"argued\"},\n        {q:\"10) Parking lots are so ____ that people can lose their cars.\", ans:\"big\"}\n      ];\n\n      const listenGaps = qs('#listenGaps');\n      const listenFb = qs('#listenFb');\n\n      function renderListeningGaps(){\n        const wordBank = shuffle([\n          \"40 minutes\",\"two hours\",\"7:30\",\"eight million\",\"push\",\"white\",\"40 million\",\"cars\",\"argued\",\"big\"\n        ]);\n        listenGaps.innerHTML = `\n          <div class=\"note\"><b>Word bank:<\/b> ${wordBank.map(w=>`<span class=\"tag\">${esc(w)}<\/span>`).join(\" \")}<\/div>\n        ` + gapItems.map((it,i)=>{\n          const opts = ['<option value=\"\">Choose\u2026<\/option>']\n            .concat(wordBank.map(w=>`<option value=\"${esc(w)}\">${esc(w)}<\/option>`))\n            .join(\"\");\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${esc(it.q)}<\/div>\n              <select class=\"icte-select\" data-ans=\"${esc(it.ans)}\">${opts}<\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderListeningGaps();\n\n      qs('#listenPlay').addEventListener('click', ()=> speak(listeningScript));\n      qs('#listenStop').addEventListener('click', stopSpeak);\n\n      qs('#listenCheck').addEventListener('click', ()=>{\n        const sels = qsa('#listenGaps select');\n        let correct = 0;\n        sels.forEach(s=>{\n          const ok = s.value === s.getAttribute('data-ans');\n          s.style.borderColor = ok ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n          if(ok) correct++;\n        });\n        const pct = Math.round((correct \/ sels.length) * 100);\n        listenFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        listenFb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        markDone(\"listening\", pct);\n      });\n\n      \/* =========================\n         7) Problem-solving\n      ========================= *\/\n      const probSet = qs('#probSet');\n      const probFb = qs('#probFb');\n\n      const problems = [\n        {\n          scenario:\n            \"Scenario 1: You start out late and there\u2019s a traffic jam at the intersection near the bridge. You must arrive on time. What\u2019s the best plan?\",\n          choices: [\n            \"Drive faster through the crosswalk to save time.\",\n            \"Take public transportation and walk from the station platform to your office.\",\n            \"Park in a bike lane so you can get out quickly.\"\n          ],\n          correct: 1,\n          followup:\n            \"Write ONE sentence using present perfect with yet\/already about what you have done to fix the problem.\"\n        },\n        {\n          scenario:\n            \"Scenario 2: You can\u2019t find a parking space near your apartment building during rush hour. What should you do?\",\n          choices: [\n            \"Give your friend a ride and look for parking together.\",\n            \"Take the bus or subway, then walk on the sidewalk.\",\n            \"Stop at traffic lights until a space appears.\"\n          ],\n          correct: 1,\n          followup:\n            \"Write ONE sentence using yet\/already (e.g., I haven\u2019t found a parking space yet\u2026).\"\n        },\n        {\n          scenario:\n            \"Scenario 3: A commuter feels unsafe at night because the streetlight near the tunnel is broken. What\u2019s the best solution?\",\n          choices: [\n            \"Cross anywhere; the crosswalk is optional.\",\n            \"Report the problem and choose a route with traffic lights and a well-lit sidewalk.\",\n            \"Ignore it; it\u2019s already safe.\"\n          ],\n          correct: 1,\n          followup:\n            \"Write ONE sentence using present perfect with yet\/already about safety.\"\n        }\n      ];\n\n      function renderProblems(){\n        probSet.innerHTML = problems.map((p,idx)=>{\n          const opts = p.choices.map((c,i)=>`\n            <label class=\"opt\">\n              <input type=\"radio\" name=\"prob_${idx}\" value=\"${i}\">\n              <span>${esc(c)}<\/span>\n            <\/label>\n          `).join(\"\");\n          return `\n            <div class=\"qitem\">\n              <div class=\"qtext\">${idx+1}) ${esc(p.scenario)}<\/div>\n              <div class=\"stack\" style=\"gap:8px;margin-top:8px\">${opts}<\/div>\n              <div class=\"muted icte-small\" style=\"margin-top:8px\">${esc(p.followup)}<\/div>\n              <textarea class=\"textarea\" rows=\"2\" data-followup=\"${idx}\" placeholder=\"Write your sentence here...\"><\/textarea>\n            <\/div>\n          `;\n        }).join(\"\");\n\n        \/\/ style options\n        qsa('.opt').forEach(l=>{\n          l.style.display=\"flex\";\n          l.style.gap=\"10px\";\n          l.style.alignItems=\"flex-start\";\n          l.style.padding=\"8px 10px\";\n          l.style.border=\"1px solid var(--line)\";\n          l.style.borderRadius=\"12px\";\n          l.style.background=\"#fff\";\n        });\n      }\n      renderProblems();\n\n      qs('#probCheck').addEventListener('click', ()=>{\n        let score = 0;\n        const total = problems.length * 2; \/\/ 1 point choice + 1 point sentence\n        problems.forEach((p,idx)=>{\n          const sel = qs(`input[name=\"prob_${idx}\"]:checked`);\n          const okChoice = sel && parseInt(sel.value,10) === p.correct;\n          if(okChoice) score++;\n\n          const ta = qs(`textarea[data-followup=\"${idx}\"]`);\n          const txt = norm(ta.value);\n          const okSentence = (\/\\byet\\b\/i.test(txt) || \/\\balready\\b\/i.test(txt)) && \/\\b(i'?ve|i have|haven't|hasn't|we'?ve|they'?ve|you'?ve)\\b\/i.test(txt);\n          if(okSentence) score++;\n\n          ta.style.borderColor = okSentence ? \"rgba(34,197,94,.6)\" : \"rgba(239,68,68,.6)\";\n        });\n\n        const pct = Math.round((score \/ total) * 100);\n        probFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        probFb.textContent =\n          `Score: ${score}\/${total} (${pct}%). Tip: Your follow-up sentence must include present perfect + yet\/already.`;\n        markDone(\"problemsolving\", pct);\n      });\n\n      qs('#probReset').addEventListener('click', ()=>{\n        renderProblems();\n        probFb.style.display=\"none\";\n      });\n\n      \/* =========================\n         Final: keep progress visible\n      ========================= *\/\n      renderProgress();\n\n    })();\n  <\/script>\n\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Conversation Vocabulary Grammar Discussion Reading Listening Problem-solving Progress Unit 7 \u2014 City Living &#038; The Daily Commute Practice present perfect<\/p>\n","protected":false},"author":1,"featured_media":706,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[37],"tags":[],"class_list":["post-708","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-b1"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/708","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=708"}],"version-history":[{"count":7,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/708\/revisions"}],"predecessor-version":[{"id":715,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/708\/revisions\/715"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/706"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=708"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=708"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=708"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}