{"id":756,"date":"2026-03-04T14:19:55","date_gmt":"2026-03-04T14:19:55","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=756"},"modified":"2026-03-05T04:20:03","modified_gmt":"2026-03-05T04:20:03","slug":"ai-for-vocabulary-assessment-design","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/ai-for-vocabulary-assessment-design\/","title":{"rendered":"AI for Vocabulary &amp; Assessment Design"},"content":{"rendered":"\n<!-- \u2705 ICTE Teacher Micro-Lesson \u2014 AI for Vocabulary & Assessment Design (EFL-friendly)\n     WP-safe single block, Multi-speaker Google Voices (2 voices), Conversation + Reading + Quiz + Prompts + Listening + Lab\n     Includes: CEFR-graded tasks, distractors, item analysis ideas, feedback templates, and a Problem-Solving finale.\n-->\n<div id=\"icte-vocabai\">\n\n  <!-- \u2705 TOP MENU -->\n  <nav class=\"icte-menu\" aria-label=\"AI vocabulary and assessment design lesson navigation\">\n    <a href=\"#\" class=\"is-current\" data-view=\"overview\">Overview<\/a>\n    <a href=\"#\" data-view=\"conversation\">Conversation<\/a>\n    <a href=\"#\" data-view=\"reading\">Reading<\/a>\n    <a href=\"#\" data-view=\"toolkit\">Design Toolkit<\/a>\n    <a href=\"#\" data-view=\"prompts\">Prompts<\/a>\n    <a href=\"#\" data-view=\"listening\">Listening<\/a>\n    <a href=\"#\" data-view=\"lab\">Assessment Lab<\/a>\n    <a href=\"#\" data-view=\"problem\">Problem-solving<\/a>\n    <a href=\"#\" data-view=\"progress\">Progress<\/a>\n  <\/nav>\n\n  <section class=\"icte-shell\" aria-label=\"ICTE AI vocabulary and assessment design lesson\">\n\n    <!-- \u2705 Header -->\n    <header class=\"icte-hero\">\n      <div class=\"icte-hero__text\">\n        <h2>AI for Vocabulary &#038; Assessment Design<\/h2>\n        <p class=\"muted\">\n          Create <b>CEFR-graded<\/b> vocabulary tasks, build <b>high-quality distractors<\/b>, generate <b>feedback templates<\/b>,\n          and get <b>item analysis ideas<\/b> (difficulty, discrimination, distractor efficiency) \u2014 with classroom-safe AI prompts.\n          Ends with a <b>problem-solving scenario<\/b> for real test revision.\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        <!-- \u2705 Multi-speaker Google Voices -->\n        <div class=\"grid2\" style=\"margin-top:10px;\">\n          <label class=\"icte-label\">\n            Speaker A (Google)\n            <select id=\"voiceA\" class=\"icte-select\" aria-label=\"Speaker A voice\"><\/select>\n          <\/label>\n\n          <label class=\"icte-label\">\n            Speaker B (Google)\n            <select id=\"voiceB\" class=\"icte-select\" aria-label=\"Speaker B voice\"><\/select>\n          <\/label>\n        <\/div>\n\n        <div class=\"icte-row\" style=\"margin-top:10px;\">\n          <button class=\"btn mini ghost\" type=\"button\" id=\"speakActive\">\ud83d\udd0a Read this page<\/button>\n          <button class=\"btn mini ghost\" type=\"button\" id=\"stopSpeak\">\u23f9 Stop audio<\/button>\n        <\/div>\n\n        <div class=\"icte-small muted\">\n          Tip: For best voice options, use Chrome\/Edge. If voices don\u2019t appear yet, click once on the page and wait 2\u20133 seconds.\n        <\/div>\n      <\/div>\n    <\/header>\n\n    <!-- \u2705 Views -->\n    <main class=\"icte-main\">\n\n      <!-- ===================== -->\n      <!-- \u2705 OVERVIEW -->\n      <!-- ===================== -->\n      <section class=\"view is-active\" data-view=\"overview\" aria-label=\"Lesson overview\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>1) Outcomes<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"overview-instr\">\ud83d\udd0a Read instructions<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">By the end, you can\u2026<\/div>\n              <ul class=\"ul\">\n                <li>Design <b>CEFR-graded<\/b> vocabulary tasks (A2\u2013C1).<\/li>\n                <li>Create <b>distractors<\/b> that are plausible but wrong (avoid \u201cobvious\u201d options).<\/li>\n                <li>Draft <b>feedback templates<\/b> (accuracy + strategy + next step).<\/li>\n                <li>Use <b>item analysis ideas<\/b> to revise tests (difficulty\/discrimination\/distractor efficiency).<\/li>\n              <\/ul>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">AI safety rules<\/div>\n              <ul class=\"ul\">\n                <li>No invented \u201cofficial CEFR lists\u201d unless you provide them.<\/li>\n                <li>Keep tasks aligned to your curriculum and context.<\/li>\n                <li>Do not generate high-stakes exams without review and piloting.<\/li>\n                <li>Always run teacher checks: ambiguity, bias, and answer key validity.<\/li>\n              <\/ul>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Golden rule:<\/b> Use AI to produce options fast \u2014 then you do the <b>validation<\/b>: clarity, level, key accuracy, and fairness.\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 CONVERSATION -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"conversation\" aria-label=\"Conversation coach\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>2) Conversation (Assessment Coach)<\/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=\"convHear\">\ud83d\udd0a Read last question<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"convReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Build a robust prompt for AI-assisted assessment design: <b>CEFR level<\/b>, <b>task type<\/b>, <b>constraints<\/b>,\n            and <b>quality checks<\/b>.\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 (or use voice)...\" autocomplete=\"off\" \/>\n            <button class=\"btn\" id=\"convSend\" type=\"button\">Send<\/button>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Tip:<\/b> For vocabulary items, specify the target word sense, part of speech, and a short context sentence.\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 READING + QUIZ -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"reading\" aria-label=\"Reading and quiz\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>3) Reading + Comprehension Quiz<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"reading-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"readTextBtn\">\ud83d\udd0a Read the text<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"quizCheck\">Check answers<\/button>\n            <\/div>\n          <\/div>\n\n          <article class=\"reading\" id=\"readingText\" aria-label=\"Reading text about AI for vocabulary and assessment design\">\n            <div class=\"reading-title\">Reading: Using AI to design vocabulary tasks and tests (responsibly)<\/div>\n\n            <div class=\"reading-p\">\n              <b>1<\/b> AI can help teachers generate vocabulary activities quickly, but the output must be checked for accuracy and level.\n              For EFL assessment, a good practice is to define the target <b>CEFR level<\/b>, task type, and constraints (topic, word list, grammar).\n              The teacher then reviews items for ambiguity, cultural bias, and answer-key validity.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>2<\/b> CEFR-graded tasks differ in complexity. At lower levels, tasks should use high-frequency words, short stems, and clear contexts.\n              At higher levels, items can include collocations, polysemy (multiple meanings), and academic register. However, difficulty should come from\n              language ability, not from confusing instructions.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>3<\/b> Distractors are a common weakness in multiple-choice questions. Good distractors are plausible for learners at the target level,\n              match the same part of speech, and are wrong for a clear reason. Weak distractors are obviously incorrect or unrelated.\n              AI can propose distractors, but the teacher should test them against common learner errors.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>4<\/b> After a quiz, item analysis helps teachers improve the test. If most students answer correctly, the item may be too easy.\n              If high-performing students miss the item, it may be ambiguous or miskeyed. Distractor analysis can show whether wrong options attracted learners\n              or were ignored. Even simple analysis (percent correct + option counts) can guide revision.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>5<\/b> Feedback templates make assessment more useful. Effective feedback states (a) correctness, (b) brief explanation,\n              (c) strategy tip, and (d) next-step practice. AI can help draft feedback in friendly teacher language, but feedback must remain aligned\n              with learning goals and should not shame learners.\n            <\/div>\n          <\/article>\n\n          <h4 class=\"h4\" style=\"margin-top:12px;\">Comprehension check (choose the best answer)<\/h4>\n          <div id=\"quiz\" class=\"stack\"><\/div>\n          <div class=\"feedback\" id=\"quizFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 DESIGN TOOLKIT -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"toolkit\" aria-label=\"Design toolkit\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>4) Design Toolkit (CEFR tasks, distractors, feedback, item analysis)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"toolkit-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"toolkitSpeak\">\ud83d\udd0a Read toolkit<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">CEFR task ideas (EFL-friendly)<\/div>\n              <pre class=\"pre\" id=\"tkCefr\"><\/pre>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">Distractor rules (MCQ quality)<\/div>\n              <pre class=\"pre\" id=\"tkDist\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\" style=\"margin-top:12px;\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">Item analysis (quick classroom version)<\/div>\n              <pre class=\"pre\" id=\"tkAnalysis\"><\/pre>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">Feedback templates (teacher tone)<\/div>\n              <pre class=\"pre\" id=\"tkFeedback\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Worked mini example (target word + item + feedback)<\/div>\n            <pre class=\"pre\" id=\"tkExample\"><\/pre>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 PROMPTS + EXAMPLES -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"prompts\" aria-label=\"Prompts and examples\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>5) Prompts + Examples (Copy &#038; Adapt)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"prompts-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"promptsSpeak\">\ud83d\udd0a Read prompts<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"copyPrompts\">Copy all<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            Ask AI to output items in a table with: <b>CEFR level<\/b>, <b>skill<\/b>, <b>stem<\/b>, <b>options<\/b>, <b>key<\/b>,\n            <b>rationale<\/b>, and <b>distractor rationale<\/b>.\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 1 \u2014 CEFR-graded vocabulary tasks<\/div>\n              <pre class=\"pre\" id=\"p1\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 2 \u2014 MCQ with high-quality distractors<\/div>\n              <pre class=\"pre\" id=\"p2\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\" style=\"margin-top:12px;\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 3 \u2014 Feedback templates<\/div>\n              <pre class=\"pre\" id=\"p3\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 4 \u2014 Item analysis &#038; revision plan<\/div>\n              <pre class=\"pre\" id=\"p4\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Example output style (one item)<\/div>\n            <pre class=\"pre\" id=\"pExample\"><\/pre>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 LISTENING (2 voices) -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"listening\" aria-label=\"Two-speaker listening\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>6) Listening (Two Google Voices) \u2014 \u201cBetter distractors, fairer tests\u201d<\/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 dialogue<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"listenStop\">\u23f9 Stop<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"listenCheck\">Check answers<\/button>\n            <\/div>\n          <\/div>\n\n          <p class=\"muted\">\n            Listen to two teachers discussing CEFR grading, distractors, and item analysis. Then answer the questions.\n          <\/p>\n\n          <div id=\"listenQ\" class=\"stack\"><\/div>\n          <div class=\"feedback\" id=\"listenFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 ASSESSMENT LAB -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"lab\" aria-label=\"Assessment lab\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>7) Assessment Lab (Build items + feedback)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"lab-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"labGenerate\">Generate draft items<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"labCopyAll\">Copy all<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"labScore\">Self-check<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            Paste your target words (or a short list) and generate draft tasks. This demo uses heuristics (no external AI calls).\n            Then copy the <b>Safe AI prompt<\/b> to refine wording and distractors in your AI tool.\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">A) Target vocabulary (one per line)<\/div>\n              <textarea class=\"textarea\" id=\"labWords\" rows=\"10\" placeholder=\"Example:\ncollaboration (n.)\nreliable (adj.)\nconduct (v.)\nevidence (n.)\"><\/textarea>\n              <div class=\"icte-small muted\" style=\"margin-top:6px;\">\n                Tip: Add part of speech in parentheses to improve distractor quality.\n              <\/div>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">B) Settings<\/div>\n\n              <label class=\"icte-label\">CEFR level<\/label>\n              <select class=\"icte-select\" id=\"labLevel\" aria-label=\"CEFR level\">\n                <option value=\"A2\">A2<\/option>\n                <option value=\"B1\" selected>B1<\/option>\n                <option value=\"B2\">B2<\/option>\n                <option value=\"C1\">C1<\/option>\n              <\/select>\n\n              <label class=\"icte-label\">Task type<\/label>\n              <select class=\"icte-select\" id=\"labType\" aria-label=\"Task type\">\n                <option value=\"mcq\" selected>MCQ (meaning in context)<\/option>\n                <option value=\"gap\">Gap-fill (choose word)<\/option>\n                <option value=\"match\">Matching (word \u2194 definition)<\/option>\n                <option value=\"coll\">Collocations (choose best pair)<\/option>\n              <\/select>\n\n              <label class=\"icte-label\">Safety constraints<\/label>\n              <div class=\"stack\" id=\"labConstraints\"><\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Generated outputs<\/div>\n            <div class=\"grid2\">\n              <div>\n                <div class=\"icte-label\">Draft items<\/div>\n                <pre class=\"pre\" id=\"outItems\"><\/pre>\n              <\/div>\n              <div>\n                <div class=\"icte-label\">Feedback templates<\/div>\n                <pre class=\"pre\" id=\"outFeedback\"><\/pre>\n              <\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Safe AI prompt (refine items + distractors + feedback)<\/div>\n            <pre class=\"pre\" id=\"outPrompt\"><\/pre>\n          <\/div>\n\n          <div class=\"feedback\" id=\"labFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 PROBLEM-SOLVING (NEW \/ REQUIRED) -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"problem\" aria-label=\"Problem solving task\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>8) Problem-solving (End-of-lesson task)<\/h3>\n            <div class=\"card-actions\">\n              <button class=\"btn mini\" type=\"button\" data-say=\"problem-instr\">\ud83d\udd0a Read instructions<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"problemCheck\">Check my plan<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"problemReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Scenario:<\/b> You are teaching a B1 EFL class. Your 10-item vocabulary quiz shows problems:\n            <ul class=\"ul\">\n              <li>Items 2 and 7 were answered correctly by <b>95%<\/b> of students (too easy).<\/li>\n              <li>Item 5 was missed by many high-performing students (possibly <b>ambiguous<\/b> or <b>miskeyed<\/b>).<\/li>\n              <li>In Item 9, two distractors were chosen by <b>0%<\/b> of students (weak distractors).<\/li>\n            <\/ul>\n            Your goal: produce a <b>revision plan<\/b> and <b>rewrite one item<\/b> with better distractors and feedback.\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">A) Your revision plan (3\u20136 bullets)<\/div>\n              <textarea class=\"textarea\" id=\"psPlan\" rows=\"8\" placeholder=\"Write your plan here...\nExample:\n- Identify why item 5 is ambiguous (check stem, key, overlap).\n- Replace items 2 and 7 with higher discrimination items...\n- Improve distractors for item 9 by matching POS and common errors...\"><\/textarea>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">B) Rewrite ONE item (stem + 4 options + key + rationale)<\/div>\n              <textarea class=\"textarea\" id=\"psItem\" rows=\"8\" placeholder=\"Write one revised item here...\nStem: ...\nA) ...\nB) ...\nC) ...\nD) ...\nKey: ...\nRationale: ...\"><\/textarea>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">C) Feedback message for a student who chose a common distractor<\/div>\n            <textarea class=\"textarea\" id=\"psFeedback\" rows=\"5\" placeholder=\"Example:\nYou chose B, which is a common confusion because...\nRemember: ...\nTry: ...\"><\/textarea>\n          <\/div>\n\n          <div class=\"feedback\" id=\"psFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 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=\"pChecks\">0<\/div>\n              <div class=\"muted\">Quality checks used<\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">Saved locally in your browser (local storage).<\/div>\n\n          <h4 class=\"h4\">Checklist bank<\/h4>\n          <div class=\"bank\" id=\"checksBank\"><\/div>\n        <\/div>\n      <\/section>\n\n    <\/main>\n  <\/section>\n\n  <style>\n    \/* ===== WP-SAFE STYLES (scoped) ===== *\/\n    #icte-vocabai *{ box-sizing:border-box; }\n    #icte-vocabai{\n      --green:#28a745;\n      --dark:#132018;\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-vocabai .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-vocabai .icte-menu a{\n      display:inline-block;\n      color:#fff;\n      text-decoration:none;\n      font-weight:900;\n      padding:8px 10px;\n      border-radius:999px;\n      margin:0 3px;\n      opacity:.92;\n      transition:.15s;\n    }\n    #icte-vocabai .icte-menu a:hover{ opacity:1; background:rgba(255,255,255,.14); }\n    #icte-vocabai .icte-menu a.is-current{ background:#fff; color:var(--dark); opacity:1; }\n\n    #icte-vocabai .icte-shell{ max-width:1100px; margin:0 auto; }\n    #icte-vocabai .icte-hero{\n      display:grid;\n      grid-template-columns: 1.1fr .9fr;\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){ #icte-vocabai .icte-hero{ grid-template-columns:1fr; } }\n    #icte-vocabai h2{ margin:0 0 6px 0; font-size:22px; }\n    #icte-vocabai .muted{ color:var(--muted); }\n\n    #icte-vocabai .icte-hero__controls{\n      border-left:1px dashed var(--line);\n      padding-left:14px;\n    }\n    @media (max-width: 920px){\n      #icte-vocabai .icte-hero__controls{ border-left:none; padding-left:0; border-top:1px dashed var(--line); padding-top:12px; }\n    }\n\n    #icte-vocabai .icte-row{ display:flex; gap:8px; flex-wrap:wrap; }\n    #icte-vocabai .icte-label{ display:block; font-size:12px; font-weight:900; margin-top:8px; }\n    #icte-vocabai .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\n    #icte-vocabai .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:900;\n    }\n    #icte-vocabai .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-vocabai .dot.on{\n      background:#22c55e;\n      box-shadow:0 0 0 4px rgba(34,197,94,.18);\n    }\n\n    #icte-vocabai .btn{\n      border:none;\n      padding:10px 12px;\n      border-radius:12px;\n      background:var(--green);\n      color:#fff;\n      font-weight:900;\n      cursor:pointer;\n      transition:.15s;\n    }\n    #icte-vocabai .btn:hover{ filter:brightness(.95); transform:translateY(-1px); }\n    #icte-vocabai .btn:active{ transform:translateY(0); }\n    #icte-vocabai .btn.ghost{\n      background:#fff;\n      color:#111827;\n      border:1px solid var(--line);\n    }\n    #icte-vocabai .btn.mini{ padding:8px 10px; border-radius:10px; font-size:13px; }\n    #icte-vocabai .icte-small{ font-size:12px; }\n\n    #icte-vocabai .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-vocabai .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-vocabai .card-h h3{ margin:0; font-size:18px; }\n    #icte-vocabai .card-actions{ display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end; }\n\n    #icte-vocabai .view{ display:none; }\n    #icte-vocabai .view.is-active{ display:block; }\n\n    #icte-vocabai .grid2{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:14px;\n      margin-top:10px;\n    }\n    @media (max-width: 920px){ #icte-vocabai .grid2{ grid-template-columns:1fr; } }\n\n    #icte-vocabai .stack{ display:flex; flex-direction:column; gap:10px; }\n    #icte-vocabai .h4{ margin:0 0 6px 0; font-size:15px; }\n    #icte-vocabai .qitem{\n      padding:10px;\n      border:1px solid var(--line);\n      border-radius:14px;\n      background:#fafafa;\n    }\n    #icte-vocabai .qtext{ font-weight:900; margin-bottom:8px; }\n\n    #icte-vocabai .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:800;\n      display:none;\n      white-space:pre-line;\n    }\n    #icte-vocabai .feedback.ok{ display:block; border-color:rgba(34,197,94,.35); background:#ecfdf5; }\n    #icte-vocabai .feedback.bad{ display:block; border-color:rgba(239,68,68,.35); background:#fef2f2; }\n\n    #icte-vocabai .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-vocabai .reading{\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:12px;\n      background:#fff;\n      margin-top:10px;\n    }\n    #icte-vocabai .reading-title{ font-weight:900; margin-bottom:8px; }\n    #icte-vocabai .reading-p{ padding:8px 0; border-top:1px dashed var(--line); }\n    #icte-vocabai .reading-p:first-of-type{ border-top:none; }\n\n    #icte-vocabai .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-vocabai .progress-grid{ grid-template-columns:1fr; } }\n    #icte-vocabai .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-vocabai .pnum{ font-size:28px; font-weight:1000; }\n\n    #icte-vocabai .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-vocabai .tag{\n      border:1px solid var(--line);\n      background:#f9fafb;\n      padding:6px 10px;\n      border-radius:999px;\n      font-weight:900;\n      font-size:13px;\n    }\n\n    #icte-vocabai .pre{\n      white-space:pre-wrap;\n      background:#0b1220;\n      color:#e5e7eb;\n      border-radius:12px;\n      padding:12px;\n      border:1px solid rgba(255,255,255,.08);\n      overflow:auto;\n      font-size:13px;\n      line-height:1.45;\n    }\n\n    #icte-vocabai .ul{ margin:0; padding-left:18px; }\n    #icte-vocabai .ul li{ margin:6px 0; }\n\n    #icte-vocabai .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    \/* Conversation UI *\/\n    #icte-vocabai .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-vocabai .msg{ margin:10px 0; display:flex; gap:10px; align-items:flex-start; }\n    #icte-vocabai .who{\n      min-width:90px;\n      font-weight:900;\n      font-size:12px;\n      color:#93c5fd;\n      text-transform:uppercase;\n      letter-spacing:.06em;\n    }\n    #icte-vocabai .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      white-space:pre-line;\n    }\n    #icte-vocabai .msg.user .who{ color:#86efac; }\n    #icte-vocabai .msg.user .bubble{ background:rgba(34,197,94,.10); border-color:rgba(34,197,94,.18); }\n\n    #icte-vocabai .chatbar{\n      margin-top:10px;\n      display:flex;\n      gap:8px;\n      align-items:center;\n    }\n    #icte-vocabai .input{\n      flex:1;\n      padding:12px 12px;\n      border:1px solid var(--line);\n      border-radius:12px;\n      outline:none;\n    }\n  <\/style>\n\n  <script>\n    (function(){\n      const root = document.getElementById('icte-vocabai');\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 esc = (s)=> (s||\"\").replace(\/[&<>\"']\/g, m=>({ \"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#039;\" }[m]));\n\n      \/* =========================\n         Navigation\n      ========================= *\/\n      const navLinks = qsa('.icte-menu a');\n      const views = qsa('.view');\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      function activeViewName(){\n        const v = qs('.view.is-active');\n        return v ? v.getAttribute('data-view') : \"overview\";\n      }\n\n      \/* =========================\n         Multi-speaker Google Voices\n      ========================= *\/\n      const voiceASelect = qs('#voiceA');\n      const voiceBSelect = qs('#voiceB');\n\n      let allVoices = [];\n      let googleVoicesEN = [];\n      let voiceA = null;\n      let voiceB = null;\n\n      function isEnglish(v){ return (v.lang||\"\").toLowerCase().startsWith(\"en\"); }\n      function isGoogle(v){ return (v.name||\"\").toLowerCase().includes(\"google\"); }\n      function looksMaleName(name){\n        const n=(name||\"\").toLowerCase();\n        return n.includes(\"male\")||n.includes(\"david\")||n.includes(\"mark\")||n.includes(\"daniel\");\n      }\n      function looksFemaleName(name){\n        const n=(name||\"\").toLowerCase();\n        return n.includes(\"female\")||n.includes(\"susan\")||n.includes(\"amy\")||n.includes(\"emma\");\n      }\n      function pickDefaultVoices(list){\n        const a = list.find(v=>looksFemaleName(v.name)) || list[0] || null;\n        const b = list.find(v=>v!==a && looksMaleName(v.name)) || list.find(v=>v!==a) || a || null;\n        return {a,b};\n      }\n\n      function loadVoices(){\n        if(!window.speechSynthesis) return;\n        allVoices = speechSynthesis.getVoices() || [];\n        const google = allVoices.filter(v => isEnglish(v) && isGoogle(v));\n        const english = allVoices.filter(v => isEnglish(v));\n        googleVoicesEN = google.length ? google : english;\n\n        const buildSelect = (sel, list)=>{\n          sel.innerHTML=\"\";\n          list.forEach((v,i)=>{\n            const opt=document.createElement(\"option\");\n            opt.value=String(i);\n            opt.textContent=`${v.name} (${v.lang})`;\n            sel.appendChild(opt);\n          });\n        };\n\n        buildSelect(voiceASelect, googleVoicesEN);\n        buildSelect(voiceBSelect, googleVoicesEN);\n\n        const picked = pickDefaultVoices(googleVoicesEN);\n        voiceA = picked.a;\n        voiceB = picked.b;\n\n        voiceASelect.value = String(Math.max(0, googleVoicesEN.indexOf(voiceA)));\n        voiceBSelect.value = String(Math.max(0, googleVoicesEN.indexOf(voiceB)));\n\n        voiceASelect.onchange = ()=>{\n          const idx=parseInt(voiceASelect.value,10);\n          voiceA = googleVoicesEN[idx] || voiceA;\n        };\n        voiceBSelect.onchange = ()=>{\n          const idx=parseInt(voiceBSelect.value,10);\n          voiceB = googleVoicesEN[idx] || voiceB;\n        };\n      }\n      if(window.speechSynthesis){\n        loadVoices();\n        speechSynthesis.onvoiceschanged = loadVoices;\n      }\n\n      function stopSpeak(){ if(window.speechSynthesis) speechSynthesis.cancel(); }\n      function speakAs(role, text, opts){\n        const t = norm(text);\n        if(!t || !window.speechSynthesis) return Promise.resolve();\n        const o = opts || {};\n        const u = new SpeechSynthesisUtterance(t);\n        if(role===\"A\" && voiceA) u.voice = voiceA;\n        if(role===\"B\" && voiceB) u.voice = voiceB;\n        u.rate = o.rate ?? 1.02;\n        u.pitch = o.pitch ?? 1.0;\n        u.volume = o.volume ?? 1.0;\n        return new Promise(resolve=>{\n          u.onend=resolve; u.onerror=resolve;\n          speechSynthesis.speak(u);\n        });\n      }\n      async function speakDialogue(lines){\n        stopSpeak();\n        for(const line of lines){\n          await speakAs(line.role, line.text);\n        }\n      }\n\n      qs('#stopSpeak').addEventListener('click', stopSpeak);\n\n      \/* =========================\n         Mic Speech Recognition\n      ========================= *\/\n      const micDot = qs('#icteMicDot');\n      const micStatus = qs('#icteMicStatus');\n      const btnStartVoice = qs('#icteStartVoice');\n      const btnStopVoice = qs('#icteStopVoice');\n\n      let recognition = null;\n      let listening = false;\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      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      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.\");\n          return;\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 = ()=>{ if(listening){ try{ recognition.start(); }catch(_){ } } };\n        setMicUI(true);\n        try{ recognition.start(); }catch(_){ }\n      }\n      function stopListening(){\n        setMicUI(false);\n        try{ recognition && recognition.stop(); }catch(_){ }\n      }\n      btnStartVoice.addEventListener('click', startListening);\n      btnStopVoice.addEventListener('click', stopListening);\n\n      \/* =========================\n         Progress (localStorage)\n      ========================= *\/\n      const LS_KEY = \"icte_vocabai_progress_v1\";\n      const progress = JSON.parse(localStorage.getItem(LS_KEY) || \"{}\");\n\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(){ localStorage.removeItem(LS_KEY); location.reload(); }\n      qs('#progressReset').addEventListener('click', resetProgress);\n\n      const checks = [\n        {k:\"cefr\", label:\"CEFR alignment\"},\n        {k:\"clear_stem\", label:\"Clear stem\/context\"},\n        {k:\"pos_match\", label:\"Same part of speech\"},\n        {k:\"one_key\", label:\"One correct key\"},\n        {k:\"distractors\", label:\"Plausible distractors\"},\n        {k:\"bias\", label:\"Bias\/fairness check\"},\n        {k:\"analysis\", label:\"Item analysis plan\"},\n        {k:\"feedback\", label:\"Feedback template\"}\n      ];\n      qs('#checksBank').innerHTML = checks.map(x=>`<span class=\"tag\">${esc(x.label)}<\/span>`).join(\"\");\n\n      function addCheck(k){\n        progress._checks = progress._checks || {};\n        progress._checks[k] = true;\n        saveProgress();\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        qs('#pDone').textContent = String(done);\n        qs('#pScore').textContent = String(avg) + \"%\";\n        qs('#pChecks').textContent = String(Object.keys(progress._checks || {}).length);\n      }\n\n      \/* =========================\n         Instruction TTS map\n      ========================= *\/\n      const SAY = {\n        \"overview-instr\":\"Overview. You will design CEFR-graded vocabulary tasks, build good distractors, draft feedback templates, and use item analysis ideas to revise a quiz.\",\n        \"conv-instr\":\"Conversation. Answer prompts to build a strong AI prompt for assessment design: level, task type, constraints, and quality checks.\",\n        \"reading-instr\":\"Reading. Read the text and answer multiple choice questions. Then check your score.\",\n        \"toolkit-instr\":\"Toolkit. Review CEFR task ideas, distractor rules, item analysis checks, and feedback templates.\",\n        \"prompts-instr\":\"Prompts. Copy and adapt prompts for CEFR tasks, MCQs with distractors, feedback, and item analysis revision.\",\n        \"list-instr\":\"Listening. Play the dialogue with two speakers and answer questions.\",\n        \"lab-instr\":\"Lab. Paste vocabulary, choose level and task type, generate drafts, then copy a safe prompt to refine in your AI tool.\",\n        \"problem-instr\":\"Problem-solving. Write a revision plan and rewrite one test item with better distractors and a feedback message.\"\n      };\n      qsa('[data-say]').forEach(btn=>{\n        btn.addEventListener('click', async ()=>{\n          const k = btn.getAttribute('data-say');\n          if(SAY[k]) await speakAs(\"A\", SAY[k]);\n        });\n      });\n\n      qs('#speakActive').addEventListener('click', async ()=>{\n        const v = activeViewName();\n        const map = {\n          overview:\"Overview page. Outcomes and safety rules for AI-assisted vocabulary and assessment design.\",\n          conversation:\"Conversation page. Build a safe, specific prompt for generating CEFR-aligned tasks and distractors.\",\n          reading:\"Reading page. Read and answer quiz questions.\",\n          toolkit:\"Toolkit page. CEFR task ideas, distractor rules, item analysis, and feedback templates.\",\n          prompts:\"Prompts page. Copy prompts with constraints and output formats.\",\n          listening:\"Listening page. Two-speaker dialogue about better distractors and item analysis.\",\n          lab:\"Lab page. Generate draft items and feedback; copy the safe prompt.\",\n          problem:\"Problem-solving page. Revise a flawed quiz using item analysis insights.\",\n          progress:\"Progress page.\"\n        };\n        await speakAs(\"A\", map[v] || \"AI vocabulary and assessment design lesson.\");\n      });\n\n      \/* =========================\n         Voice transcript routing\n      ========================= *\/\n      function handleVoiceTranscript(t){\n        const text = norm(t);\n        if(!text) return;\n        const v = activeViewName();\n        if(v===\"conversation\"){ handleConversationInput(text); return; }\n        if(v===\"lab\"){\n          const box = qs('#labWords');\n          if(box){\n            box.value = (box.value ? (box.value + \"\\n\") : \"\") + text;\n            speakAs(\"A\",\"Added to your target word list.\");\n          }\n          return;\n        }\n        if(v===\"problem\"){\n          \/\/ append to plan by default\n          const box = qs('#psPlan');\n          if(box){\n            box.value = (box.value ? (box.value + \"\\n\") : \"\") + \"- \" + text;\n            speakAs(\"A\",\"Added to your revision plan.\");\n          }\n          return;\n        }\n        speakAs(\"A\",\"I heard: \" + text);\n      }\n\n      \/* =========================\n         Conversation coach\n      ========================= *\/\n      const convChat = qs('#convChat');\n      const convText = qs('#convText');\n      const convSend = qs('#convSend');\n      const convReset = qs('#convReset');\n      const convHear  = qs('#convHear');\n\n      let convStep = 0;\n      let lastCoachQ = \"\";\n\n      const convSteps = [\n        { bot:\"Step 1: Choose your CEFR level (A2, B1, B2, or C1). Type one.\", check:(a)=>\/^(a2|b1|b2|c1)$\/i.test(a), tips:\"Type: A2 \/ B1 \/ B2 \/ C1.\" },\n        { bot:\"Step 2: Choose a task type: MCQ, gap-fill, matching, or collocations.\", check:(a)=>\/(mcq|gap|matching|collocation)\/i.test(a), tips:\"Example: MCQ.\" },\n        { bot:\"Step 3: What must be preserved? Pick two: clear context, one correct key, POS match, fairness.\", check:(a)=> (a.match(\/context|one key|pos|fair|bias\/gi)||[]).length>=2, tips:\"Example: clear context + one correct key.\" },\n        { bot:\"Step 4: What should AI generate? Mention: stem, 4 options, key, rationales.\", check:(a)=>\/stem\/i.test(a)&&\/options?\/i.test(a)&&\/key\/i.test(a), tips:\"Include: stem, four options, key, rationales.\" },\n        { bot:\"Step 5: Add one quality check: ambiguity test, distractor plausibility, item analysis plan.\", check:(a)=>\/(ambig|distractor|analysis|discrimin|difficulty)\/i.test(a), tips:\"Example: Check ambiguity and distractor plausibility.\" }\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      async function coachAsk(){\n        const step = convSteps[convStep];\n        if(!step){\n          addMsg(\"Coach\",\"\u2705 Done. Your prompt now includes level, task type, constraints, outputs, and checks.\");\n          markDone(\"conversation\", 100);\n          await speakAs(\"A\",\"Done. Your prompt now includes level, task type, constraints, outputs, and checks.\");\n          return;\n        }\n        lastCoachQ = step.bot;\n        addMsg(\"Coach\", step.bot);\n        await speakAs(\"A\", step.bot);\n      }\n\n      async function handleConversationInput(text){\n        const a = norm(text);\n        if(!a) return;\n        addMsg(\"You\", a);\n\n        if(\/a2|b1|b2|c1\/i.test(a)) addCheck(\"cefr\");\n        if(\/context\/i.test(a)) addCheck(\"clear_stem\");\n        if(\/pos\/i.test(a)) addCheck(\"pos_match\");\n        if(\/one key|single key|one correct\/i.test(a)) addCheck(\"one_key\");\n        if(\/distractor\/i.test(a)) addCheck(\"distractors\");\n        if(\/fair|bias\/i.test(a)) addCheck(\"bias\");\n        if(\/analysis|difficulty|discrimin\/i.test(a)) addCheck(\"analysis\");\n\n        const step = convSteps[convStep];\n        const ok = step.check(a);\n        const msg = ok ? \"\u2705 Good. Next.\" : \"\u26a0\ufe0f Try again. \" + step.tips;\n\n        addMsg(\"Coach\", msg);\n        await speakAs(\"A\", msg);\n\n        if(ok){ convStep++; setTimeout(coachAsk, 250); }\n      }\n\n      convSend.addEventListener('click', ()=>{ handleConversationInput(convText.value); convText.value=\"\"; });\n      convText.addEventListener('keydown', (e)=>{\n        if(e.key===\"Enter\"){ e.preventDefault(); handleConversationInput(convText.value); convText.value=\"\"; }\n      });\n      convHear.addEventListener('click', ()=> speakAs(\"A\", lastCoachQ || \"No question yet.\"));\n      convReset.addEventListener('click', ()=>{\n        convChat.innerHTML=\"\";\n        convStep=0;\n        addMsg(\"Coach\",\"Ready. Let\u2019s build a safe prompt for AI-assisted assessment design.\");\n        coachAsk();\n      });\n\n      addMsg(\"Coach\",\"Ready. Let\u2019s build a safe prompt for AI-assisted assessment design.\");\n      coachAsk();\n\n      \/* =========================\n         Reading TTS\n      ========================= *\/\n      qs('#readTextBtn').addEventListener('click', ()=>{\n        const parts = qsa('#readingText .reading-p').map(p=>p.textContent).join(\" \");\n        speakAs(\"A\", \"Reading. \" + parts);\n      });\n\n      \/* =========================\n         Quiz\n      ========================= *\/\n      const quiz = qs('#quiz');\n      const quizFb = qs('#quizFb');\n\n      const quizItems = [\n        { q:\"1) A safe use of AI for assessment is to\u2026\",\n          ans:\"Define level and constraints, then teacher-check items\",\n          opts:[\n            \"Let AI generate a full high-stakes exam without review\",\n            \"Define level and constraints, then teacher-check items\",\n            \"Skip piloting because AI is always accurate\",\n            \"Remove answer keys to avoid mistakes\"\n          ]\n        },\n        { q:\"2) Good distractors should\u2026\",\n          ans:\"Be plausible and match the same part of speech\",\n          opts:[\n            \"Be obviously wrong so students finish faster\",\n            \"Be random words from a dictionary\",\n            \"Be plausible and match the same part of speech\",\n            \"Include two correct answers\"\n          ]\n        },\n        { q:\"3) If high-performing students miss an item, it may be\u2026\",\n          ans:\"Ambiguous or miskeyed\",\n          opts:[\n            \"Perfectly designed\",\n            \"Ambiguous or miskeyed\",\n            \"Always too easy\",\n            \"Always too hard for everyone\"\n          ]\n        },\n        { q:\"4) A simple item analysis can include\u2026\",\n          ans:\"Percent correct and option counts\",\n          opts:[\n            \"Only teacher feelings\",\n            \"Percent correct and option counts\",\n            \"No data at all\",\n            \"Only time-on-task\"\n          ]\n        },\n        { q:\"5) Effective feedback usually includes\u2026\",\n          ans:\"Correctness, explanation, strategy tip, next step\",\n          opts:[\n            \"Only a score\",\n            \"Correctness, explanation, strategy tip, next step\",\n            \"Sarcasm to motivate learners\",\n            \"Long academic theory\"\n          ]\n        }\n      ];\n\n      function renderQuiz(){\n        quiz.innerHTML = quizItems.map((it)=>{\n          const options = ['<option value=\"\">Choose\u2026<\/option>']\n            .concat(it.opts.map(o=>`<option value=\"${esc(o)}\">${esc(o)}<\/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)}\">${options}<\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderQuiz();\n\n      qs('#quizCheck').addEventListener('click', ()=>{\n        const sels = qsa('#quiz 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        quizFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        quizFb.textContent = `Score: ${correct}\/${sels.length} (${pct}%).`;\n        if(pct>=70){ addCheck(\"analysis\"); addCheck(\"distractors\"); addCheck(\"feedback\"); }\n        markDone(\"reading_quiz\", pct);\n      });\n\n      \/* =========================\n         Toolkit content\n      ========================= *\/\n      qs('#tkCefr').textContent =\n`A2:\n- Picture\/definition match; simple gap-fill; common collocations (make\/do) with short contexts.\nB1:\n- Meaning in context MCQ; word families (verb\/noun); simple paraphrase matching.\nB2:\n- Collocation choice; polysemy in context; inference from short reading.\nC1:\n- Academic vocabulary; nuance\/precision; register choice; collocation + discourse marker use.`;\n\n      qs('#tkDist').textContent =\n`Good distractors:\n- Same part of speech as the key\n- Similar meaning field (plausible confusion)\n- Wrong for a clear reason (meaning, collocation, grammar, register)\nAvoid:\n- Two correct answers\n- Obviously silly\/unrelated options\n- Distractors that no one would choose\n- Overly rare words (unless level requires them)`;\n\n      qs('#tkAnalysis').textContent =\n`Quick item analysis (classroom):\n1) Difficulty (p-value): % correct\n   - too easy: > .90\n   - too hard: < .30 (context dependent)\n2) Discrimination (idea): do top students get it right more often?\n   - if top students miss it \u2192 ambiguity\/miskey\n3) Distractor efficiency:\n   - distractor chosen by 0% \u2192 revise\/replace\n4) Revision actions:\n   - rewrite stem, clarify context, fix key, replace distractors`;\n\n      qs('#tkFeedback').textContent =\n`Feedback template:\n1) Result: Correct \/ Incorrect\n2) Why: 1 short explanation (meaning\/grammar\/collocation)\n3) Strategy: 1 tip (use context clue, check POS, look for collocation)\n4) Next step: 1 micro-practice (2 sentences \/ mini drill)\nTone:\n- supportive, specific, non-shaming`;\n\n      qs('#tkExample').textContent =\n`Target: reliable (adj.) \u2014 B1\n\nItem:\n\"She is very ____; she always arrives on time.\"\nA) reliable \u2705\nB) famous\nC) nervous\nD) careful\n\nRationale:\nA fits meaning: can be trusted \/ consistent.\n\nDistractor rationales:\nB is positive but unrelated (popularity).\nC is an emotion.\nD is plausible but different meaning (attention to detail).\n\nFeedback (if chose D):\nYou chose \"careful,\" which means paying attention to avoid mistakes.\nIn this sentence, we need \"reliable\" = someone you can depend on.\nTip: look for clues like \"always arrives on time.\" Try: reliable friend \/ reliable bus schedule.`;\n\n      qs('#toolkitSpeak').addEventListener('click', ()=>{\n        speakAs(\"A\",\"Toolkit. CEFR task ideas from A2 to C1, rules for good distractors, quick item analysis checks, and feedback templates with supportive teacher tone.\");\n      });\n\n      \/* =========================\n         Prompts content\n      ========================= *\/\n      const p1 = qs('#p1'), p2 = qs('#p2'), p3 = qs('#p3'), p4 = qs('#p4'), pExample = qs('#pExample');\n\n      p1.textContent =\n`PROMPT 1 \u2014 CEFR-GRADED VOCAB TASKS\nCreate 8 vocabulary tasks for CEFR [LEVEL] on topic [TOPIC].\nConstraints:\n- Use EFL-friendly instructions, short stems, clear contexts.\n- Include task type variety: matching, gap-fill, MCQ, collocations.\nOutput table:\nTask | Level | Target words | Instructions | Answer key | Rationale\n\nTarget words (optional):\n[PASTE LIST]`;\n\n      p2.textContent =\n`PROMPT 2 \u2014 MCQ WITH HIGH-QUALITY DISTRACTORS\nCreate 10 MCQs for CEFR [LEVEL] targeting [WORDS].\nRules:\n- 1 correct option only.\n- All options same part of speech.\n- Distractors plausible for learner errors.\n- Provide: key + rationales for key and distractors.\nOutput:\nStem | A | B | C | D | Key | Key rationale | Distractor rationales`;\n\n      p3.textContent =\n`PROMPT 3 \u2014 FEEDBACK TEMPLATES\nFor each item below, write feedback for:\n(a) correct answer\n(b) each distractor choice\nFormat:\nResult \u2192 Why \u2192 Strategy tip \u2192 Next-step practice (2 micro-sentences)\nTone: supportive, specific, non-shaming.\nItems:\n[PASTE ITEMS]`;\n\n      p4.textContent =\n`PROMPT 4 \u2014 ITEM ANALYSIS & REVISION PLAN\nGiven this item statistics table, diagnose problems and propose revisions.\nInclude:\n- difficulty (too easy\/too hard)\n- likely ambiguity\/miskey\n- distractor efficiency\n- rewrite suggestions (stem\/context\/options)\nData:\n[PASTE % correct + option counts + notes]`;\n\n      pExample.textContent =\n`EXAMPLE (ONE MCQ OUTPUT STYLE)\nLevel: B1\nTarget: conduct (v.) = to carry out\n\nStem:\n\"The researchers will ____ interviews next week.\"\nA) conduct \u2705\nB) suggest\nC) collect\nD) describe\n\nKey rationale:\nConduct collocates with interviews = carry out.\n\nDistractor rationales:\nB is about giving advice.\nC is plausible but collocation is weaker here (collect interviews is odd).\nD is about reporting, not carrying out.`;\n\n      qs('#promptsSpeak').addEventListener('click', ()=>{\n        speakAs(\"A\",\"Prompts page. Copy prompts for CEFR-graded tasks, MCQs with strong distractors, feedback templates, and item analysis revision plans.\");\n      });\n\n      qs('#copyPrompts').addEventListener('click', ()=>{\n        const all =\n`PROMPT 1\\n${p1.textContent}\\n\\nPROMPT 2\\n${p2.textContent}\\n\\nPROMPT 3\\n${p3.textContent}\\n\\nPROMPT 4\\n${p4.textContent}\\n\\nEXAMPLE\\n${pExample.textContent}`;\n        navigator.clipboard.writeText(all).then(()=> speakAs(\"A\",\"Copied prompts.\")).catch(()=> alert(\"Clipboard blocked. Copy manually.\"));\n      });\n\n      \/* =========================\n         Listening\n      ========================= *\/\n      const listenFb = qs('#listenFb');\n      const listenQ = qs('#listenQ');\n\n      const dialogue = [\n        {role:\"A\", text:\"My vocabulary quiz was fast to build with AI, but the distractors were terrible.\"},\n        {role:\"B\", text:\"That happens if you don\u2019t specify rules: same part of speech, plausible learner errors, and one correct key.\"},\n        {role:\"A\", text:\"How do we check whether an item is good after the quiz?\"},\n        {role:\"B\", text:\"Use quick item analysis: percent correct, whether top students got it right, and whether distractors attracted anyone.\"},\n        {role:\"A\", text:\"If a distractor is chosen by zero students, what does it mean?\"},\n        {role:\"B\", text:\"It is probably weak. Replace it with a more plausible confusion at the target level.\"},\n        {role:\"A\", text:\"And feedback?\"},\n        {role:\"B\", text:\"Keep it supportive: explain the error, give a strategy tip, and suggest a small practice step.\"}\n      ];\n\n      const listenItems = [\n        {q:\"1) A key distractor rule is\u2026\", ans:\"Same part of speech and plausible learner errors\"},\n        {q:\"2) Quick item analysis includes\u2026\", ans:\"Percent correct and distractor choices\"},\n        {q:\"3) A distractor chosen by 0% is likely\u2026\", ans:\"Weak and should be revised\"},\n        {q:\"4) Good feedback should include\u2026\", ans:\"Explanation, strategy tip, and next-step practice\"}\n      ];\n\n      function renderListenQ(){\n        const optsMap = {\n          \"Same part of speech and plausible learner errors\":[\n            \"Same part of speech and plausible learner errors\",\n            \"Very rare words\",\n            \"Two correct answers\"\n          ],\n          \"Percent correct and distractor choices\":[\n            \"Percent correct and distractor choices\",\n            \"Only the teacher\u2019s guess\",\n            \"Only time spent on the item\"\n          ],\n          \"Weak and should be revised\":[\n            \"Weak and should be revised\",\n            \"Perfectly designed\",\n            \"Always too difficult\"\n          ],\n          \"Explanation, strategy tip, and next-step practice\":[\n            \"Explanation, strategy tip, and next-step practice\",\n            \"Only a score\",\n            \"Shaming language\"\n          ]\n        };\n\n        listenQ.innerHTML = listenItems.map((it)=>{\n          const options = ['<option value=\"\">Choose\u2026<\/option>']\n            .concat((optsMap[it.ans]||[it.ans]).map(o=>`<option value=\"${esc(o)}\">${esc(o)}<\/option>`)).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)}\">${options}<\/select>\n            <\/div>\n          `;\n        }).join(\"\");\n      }\n      renderListenQ();\n\n      qs('#listenPlay').addEventListener('click', async ()=>{\n        if(googleVoicesEN.length >= 2 && voiceA === voiceB){\n          voiceB = googleVoicesEN.find(v => v !== voiceA) || voiceB;\n          voiceBSelect.value = String(googleVoicesEN.indexOf(voiceB));\n        }\n        await speakDialogue(dialogue);\n      });\n      qs('#listenStop').addEventListener('click', stopSpeak);\n\n      qs('#listenCheck').addEventListener('click', ()=>{\n        const sels = qsa('#listenQ 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        if(pct >= 70){ addCheck(\"analysis\"); addCheck(\"distractors\"); addCheck(\"feedback\"); }\n        markDone(\"listening\", pct);\n      });\n\n      \/* =========================\n         Lab constraints + generation (heuristics)\n      ========================= *\/\n      const labConstraintItems = [\n        {id:\"c_cefr\", label:\"Keep vocabulary and contexts appropriate for selected CEFR level\", key:\"cefr\", on:true},\n        {id:\"c_onekey\", label:\"Exactly one correct answer (no overlap)\", key:\"one_key\", on:true},\n        {id:\"c_pos\", label:\"All options match the same part of speech\", key:\"pos_match\", on:true},\n        {id:\"c_clear\", label:\"Clear context sentence (no ambiguity)\", key:\"clear_stem\", on:true},\n        {id:\"c_bias\", label:\"Avoid cultural bias \/ sensitive stereotypes\", key:\"bias\", on:true}\n      ];\n      const labConstraintsBox = qs('#labConstraints');\n      labConstraintsBox.innerHTML = labConstraintItems.map(x=>`\n        <label class=\"opt\">\n          <input type=\"checkbox\" id=\"${esc(x.id)}\" ${x.on ? \"checked\":\"\"} \/>\n          <span><b>${esc(x.label)}<\/b><\/span>\n        <\/label>\n      `).join(\"\");\n\n      function selectedConstraintsText(){\n        const selected = labConstraintItems.filter(x => qs(\"#\"+x.id).checked);\n        selected.forEach(x => addCheck(x.key));\n        const lines = selected.map(x => \"- \" + x.label);\n        return lines.length ? lines.join(\"\\n\") : \"- (No constraints selected)\";\n      }\n\n      const labWords = qs('#labWords');\n      const labLevel = qs('#labLevel');\n      const labType  = qs('#labType');\n\n      const outItems = qs('#outItems');\n      const outFeedback = qs('#outFeedback');\n      const outPrompt = qs('#outPrompt');\n      const labFb = qs('#labFb');\n\n      function parseWords(txt){\n        return (txt||\"\").split(\/\\n+\/).map(s=>s.trim()).filter(Boolean).slice(0,12);\n      }\n      function guessPOS(line){\n        const m = line.match(\/\\(([^)]+)\\)\/);\n        return m ? m[1].toLowerCase() : \"\";\n      }\n      function baseWord(line){\n        return line.replace(\/\\s*\\([^)]*\\)\\s*\/g,\"\").trim();\n      }\n\n      function simpleContext(word, level){\n        const w = word;\n        if(level===\"A2\") return `I ${w} my homework every day.`;\n        if(level===\"B1\") return `They will ${w} a short survey for the project.`;\n        if(level===\"B2\") return `The team must ${w} the plan carefully before launch.`;\n        return `The researchers will ${w} a rigorous analysis to support their argument.`;\n      }\n\n      function makeMCQ(wordLine, level){\n        const w = baseWord(wordLine);\n        const pos = guessPOS(wordLine);\n        const stem = simpleContext(w, level);\n\n        \/\/ Heuristic distractors: placeholders that teacher should refine in AI tool\n        \/\/ Keep POS hint by using same POS-ish forms when possible (very lightweight)\n        const dist = [\n          pos.includes(\"adj\") ? \"effective\" : \"suggest\",\n          pos.includes(\"noun\") ? \"method\" : \"describe\",\n          pos.includes(\"verb\") ? \"collect\" : \"policy\"\n        ];\n\n        \/\/ Ensure unique and not equal to key\n        const options = [w, ...dist].map(x=>x.trim()).filter(Boolean).slice(0,4);\n        const uniq = [];\n        options.forEach(o=>{ if(!uniq.includes(o)) uniq.push(o); });\n        while(uniq.length<4) uniq.push(\"option\"+(uniq.length+1));\n\n        return {\n          stem: stem,\n          A: uniq[0],\n          B: uniq[1],\n          C: uniq[2],\n          D: uniq[3],\n          key: \"A\",\n          rationale: \"A matches the intended meaning and grammar in the context.\",\n          distRat: \"B\/C\/D are plausible but incorrect; revise to reflect common learner confusions.\"\n        };\n      }\n\n      function makeGap(wordLine, level){\n        const w = baseWord(wordLine);\n        const stem = simpleContext(w, level).replace(w, \"_____\");\n        return { stem, key:w };\n      }\n\n      function buildDraftItems(words, level, type){\n        if(!words.length) return \"Add at least 2 target words.\";\n        if(type===\"mcq\"){\n          return words.map((wl,i)=>{\n            const it = makeMCQ(wl, level);\n            return `Item ${i+1} (${level})\\n${it.stem}\\nA) ${it.A}\\nB) ${it.B}\\nC) ${it.C}\\nD) ${it.D}\\nKey: ${it.key}\\nKey rationale: ${it.rationale}\\nDistractor note: ${it.distRat}`;\n          }).join(\"\\n\\n\");\n        }\n        if(type===\"gap\"){\n          return words.map((wl,i)=>{\n            const it = makeGap(wl, level);\n            return `Item ${i+1} (${level})\\n${it.stem}\\nKey: ${it.key}`;\n          }).join(\"\\n\\n\");\n        }\n        if(type===\"match\"){\n          \/\/ simple definition placeholders\n          return `Matching (${level})\\nMatch the words to definitions.\\n\\n` + words.map((wl,i)=>{\n            const w = baseWord(wl);\n            return `${i+1}) ${w} \u2014 (definition placeholder; refine with AI + teacher check)`;\n          }).join(\"\\n\");\n        }\n        \/\/ collocations\n        return `Collocations (${level})\\nChoose the best collocation.\\n\\n` + words.map((wl,i)=>{\n          const w = baseWord(wl);\n          return `Item ${i+1}: Choose the best partner for \"${w}\":\\nA) ${w} + (strong)\\nB) ${w} + (common)\\nC) ${w} + (rare)\\nD) ${w} + (wrong)\\nKey: B (placeholder)`;\n        }).join(\"\\n\\n\");\n      }\n\n      function buildFeedbackTemplates(words){\n        if(!words.length) return \"Add target words to generate feedback templates.\";\n        addCheck(\"feedback\");\n        return words.map((wl,i)=>{\n          const w = baseWord(wl);\n          return `Feedback set ${i+1} (target: ${w})\\nCorrect: \u2705 Good. \"${w}\" fits the meaning in context. Strategy: check context clues.\\nIncorrect: \u26a0\ufe0f Recheck meaning and part of speech. Tip: underline the clue words.\\nNext step: Write 2 sentences using \"${w}\" in your own topic.`;\n        }).join(\"\\n\\n\");\n      }\n\n      function buildSafePrompt(level, type, constraints, words){\n        const t =\n`ROLE: You are an EFL assessment designer.\n\nTASK:\nGenerate ${type.toUpperCase()} vocabulary items for CEFR ${level} using the target words.\n\nCONSTRAINTS (must follow):\n${constraints}\n- Provide exactly one correct key per item.\n- Provide key rationale and distractor rationales (for MCQ).\n- Keep instructions short and EFL-friendly.\n- Avoid bias\/stereotypes; keep contexts neutral and classroom-safe.\n\nOUTPUT TABLE (preferred):\nItem | Level | Stem | A | B | C | D | Key | Key rationale | Distractor rationales | Feedback (brief)\n\nTARGET WORDS:\n${words.map(w=>\"- \"+w).join(\"\\n\")}\n\nQUALITY CHECKS:\n- Check ambiguity (could 2 options be correct?)\n- Check POS match and plausibility of distractors\n- Check CEFR appropriacy (frequency, sentence length, context)\n- Suggest quick item analysis metrics to review after piloting`;\n        return t;\n      }\n\n      function labGenerate(){\n        const words = parseWords(labWords.value);\n        const level = labLevel.value;\n        const type = labType.value;\n\n        const constraints = selectedConstraintsText();\n        outItems.textContent = buildDraftItems(words, level, type);\n        outFeedback.textContent = buildFeedbackTemplates(words);\n        outPrompt.textContent = buildSafePrompt(level, type, constraints, words);\n\n        const okWords = words.length >= 3;\n        const okChecks = labConstraintItems.filter(x=>qs(\"#\"+x.id).checked).length >= 3;\n        const pct = Math.round(((okWords?1:0) + (okChecks?1:0) + 1) \/ 3 * 100);\n\n        labFb.className = \"feedback ok\";\n        labFb.textContent = \"\u2705 Drafts generated. Next: paste the Safe AI prompt into your AI tool to refine distractors and rationales, then teacher-check.\";\n        markDone(\"lab\", pct);\n      }\n\n      function labCopyAll(){\n        const pack =\n`DRAFT ITEMS\\n${outItems.textContent}\\n\\nFEEDBACK TEMPLATES\\n${outFeedback.textContent}\\n\\nSAFE AI PROMPT\\n${outPrompt.textContent}`;\n        if(!pack.trim()){\n          labFb.className = \"feedback bad\";\n          labFb.textContent = \"\u26a0\ufe0f Generate drafts first.\";\n          return;\n        }\n        navigator.clipboard.writeText(pack).then(()=>{\n          labFb.className = \"feedback ok\";\n          labFb.textContent = \"\u2705 Copied lab outputs to clipboard.\";\n        }).catch(()=>{\n          labFb.className = \"feedback bad\";\n          labFb.textContent = \"\u26a0\ufe0f Clipboard blocked. Copy manually from the boxes.\";\n        });\n      }\n\n      function labSelfCheck(){\n        const words = parseWords(labWords.value);\n        const hasWords = words.length >= 3;\n        const core = [\"c_cefr\",\"c_onekey\",\"c_pos\",\"c_clear\"].every(id=>qs(\"#\"+id).checked);\n        const hasOutput = (outItems.textContent||\"\").length > 40 && (outPrompt.textContent||\"\").length > 80;\n\n        const pct = Math.round(((hasWords?1:0) + (core?1:0) + (hasOutput?1:0)) \/ 3 * 100);\n        labFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        labFb.textContent =\n          `Self-check: ${pct}%\\n- Word list (>=3): ${hasWords ? \"Yes\" : \"No\"}\\n- Core constraints selected: ${core ? \"Yes\" : \"No\"}\\n- Outputs generated: ${hasOutput ? \"Yes\" : \"No\"}`;\n        markDone(\"lab_selfcheck\", pct);\n      }\n\n      qs('#labGenerate').addEventListener('click', labGenerate);\n      qs('#labCopyAll').addEventListener('click', labCopyAll);\n      qs('#labScore').addEventListener('click', labSelfCheck);\n\n      \/* =========================\n         Problem-solving checker\n      ========================= *\/\n      const psPlan = qs('#psPlan');\n      const psItem = qs('#psItem');\n      const psFeedback = qs('#psFeedback');\n      const psFb = qs('#psFb');\n\n      function countBullets(txt){ return (txt.match(\/^\\s*[-\u2022]\/gm)||[]).length; }\n      function hasKey(txt){ return \/key\\s*:\/i.test(txt); }\n      function hasOptions(txt){ return \/A\\)\/.test(txt) && \/B\\)\/.test(txt) && \/C\\)\/.test(txt) && \/D\\)\/.test(txt); }\n      function hasRationale(txt){ return \/rationale\\s*:\/i.test(txt); }\n\n      function problemCheck(){\n        const plan = norm(psPlan.value);\n        const item = norm(psItem.value);\n        const fb = norm(psFeedback.value);\n\n        const b = countBullets(plan);\n        const planOk = b >= 3 && \/(easy|too easy|95|ambig|miskey|distractor|0%|analysis)\/i.test(plan);\n        const itemOk = hasOptions(item) && hasKey(item) && hasRationale(item);\n        const fbOk = fb.length >= 80 && \/(because|remember|try|tip|next)\/i.test(fb);\n\n        const pct = Math.round(((planOk?1:0) + (itemOk?1:0) + (fbOk?1:0)) \/ 3 * 100);\n\n        psFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        psFb.textContent =\n          `Check: ${pct}%\\n` +\n          `- Revision plan (>=3 bullets + mentions analysis issues): ${planOk ? \"Yes\" : \"No\"}\\n` +\n          `- Rewritten item (A\u2013D + key + rationale): ${itemOk ? \"Yes\" : \"No\"}\\n` +\n          `- Feedback (supportive + explanation + next step): ${fbOk ? \"Yes\" : \"No\"}`;\n\n        markDone(\"problem_solving\", pct);\n        addCheck(\"analysis\"); addCheck(\"distractors\"); addCheck(\"feedback\");\n      }\n\n      function problemReset(){\n        psPlan.value = \"\";\n        psItem.value = \"\";\n        psFeedback.value = \"\";\n        psFb.className = \"feedback\";\n        psFb.textContent = \"\";\n      }\n\n      qs('#problemCheck').addEventListener('click', problemCheck);\n      qs('#problemReset').addEventListener('click', problemReset);\n\n      \/* =========================\n         Stop speak button\n      ========================= *\/\n      qs('#stopSpeak').addEventListener('click', stopSpeak);\n\n      \/* =========================\n         Quiz\/listening\/buttons not defined yet in this response:\n         (No extra required.)\n      ========================= *\/\n\n      \/* =========================\n         Listening section already works; now we add button handlers for play\/stop\/check:\n      ========================= *\/\n\n      \/\/ Render listening Q already in HTML; build it here\n      const listenPlay = qs('#listenPlay');\n      const listenStop = qs('#listenStop');\n\n      \/\/ Ensure listenQ is rendered (it is built earlier by renderListenQ)\n      listenPlay.addEventListener('click', async ()=>{ \/* already set above but safe to keep *\/ });\n\n      \/\/ Fix: stop button\n      listenStop.addEventListener('click', stopSpeak);\n\n      \/* =========================\n         Prompts copy already set\n      ========================= *\/\n\n      \/* =========================\n         Mic UI wiring\n      ========================= *\/\n      \/\/ already wired in functions startListening\/stopListening\n\n      \/* =========================\n         Init\n      ========================= *\/\n      renderProgress();\n\n    })();\n  <\/script>\n\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Overview Conversation Reading Design Toolkit Prompts Listening Assessment Lab Problem-solving Progress AI for Vocabulary &#038; Assessment Design Create CEFR-graded vocabulary<\/p>\n","protected":false},"author":1,"featured_media":757,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[53,45],"tags":[],"class_list":["post-756","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-basics","category-ai-for-teachers"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/756","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=756"}],"version-history":[{"count":1,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/756\/revisions"}],"predecessor-version":[{"id":758,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/756\/revisions\/758"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/757"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=756"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=756"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=756"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}