{"id":788,"date":"2026-03-05T04:59:19","date_gmt":"2026-03-05T04:59:19","guid":{"rendered":"https:\/\/i-cte.org\/robot\/?p=788"},"modified":"2026-03-05T16:23:00","modified_gmt":"2026-03-05T16:23:00","slug":"ai-for-qualitative-research","status":"publish","type":"post","link":"https:\/\/i-cte.org\/robot\/ai-for-qualitative-research\/","title":{"rendered":"AI for Qualitative research"},"content":{"rendered":"\n<!-- \u2705 ICTE Teacher Micro-Lesson \u2014 AI for Qualitative Research Support\n     WP-safe single block, Multi-speaker Google Voices (2 voices)\n     Includes: Overview + Conversation + Reading + Quiz + Toolkit + Prompts + Listening + Qual Lab + Problem-solving + Progress\n     Focus: Interview guide generation, coding frameworks, thematic analysis scaffolds, memoing\u2014while keeping researcher control.\n-->\n<div id=\"icte-qualai\">\n\n  <!-- \u2705 TOP MENU -->\n  <nav class=\"icte-menu\" aria-label=\"Qualitative AI 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\">Qual Toolkit<\/a>\n    <a href=\"#\" data-view=\"prompts\">Prompts<\/a>\n    <a href=\"#\" data-view=\"listening\">Listening<\/a>\n    <a href=\"#\" data-view=\"lab\">Qual Analysis 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 qualitative AI lesson\">\n\n    <!-- \u2705 Header -->\n    <header class=\"icte-hero\">\n      <div class=\"icte-hero__text\">\n        <div class=\"hero-top\">\n          <img decoding=\"async\"\n            class=\"hero-img\"\n            src=\"https:\/\/i-cte.org\/robot\/wp-content\/uploads\/2026\/03\/Screenshot-2026-03-05-at-11.53.24.png\"\n            alt=\"AI for qualitative research support banner\"\n            loading=\"lazy\"\n          \/>\n          <div class=\"hero-title\">\n            <h2>AI for Qualitative Research<\/h2>\n            <p class=\"muted\">\n              Use AI to support <b>interview guide design<\/b>, <b>coding frameworks<\/b>, <b>thematic analysis scaffolds<\/b>, and <b>memo writing<\/b>\u2014\n              while keeping <b>researcher control<\/b>, transparency, and ethics.\n              Includes a hands-on <b>analysis lab<\/b> and an end-of-lesson <b>problem-solving<\/b> task.\n            <\/p>\n          <\/div>\n        <\/div>\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>Generate and refine an <b>interview guide<\/b> aligned to a research question.<\/li>\n                <li>Build a <b>coding framework<\/b> (a priori + inductive codes) with clear definitions.<\/li>\n                <li>Create a <b>thematic analysis scaffold<\/b> (codes \u2192 categories \u2192 themes).<\/li>\n                <li>Write <b>analytic memos<\/b> that document decisions and reflexivity.<\/li>\n                <li>Apply <b>researcher control<\/b>: transparency, audit trail, and ethical safeguards.<\/li>\n              <\/ul>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">Researcher control rules<\/div>\n              <ul class=\"ul\">\n                <li>AI suggests; <b>you decide<\/b>. Never accept codes\/themes uncritically.<\/li>\n                <li>Keep an <b>audit trail<\/b>: prompts, versions, decisions, and rationale.<\/li>\n                <li>Protect confidentiality: <b>de-identify<\/b> transcripts before sharing.<\/li>\n                <li>Check interpretive bias: use <b>negative cases<\/b> and <b>alternative explanations<\/b>.<\/li>\n              <\/ul>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Principle:<\/b> AI is best used for <b>structure<\/b> (scaffolds, checklists, prompts), not for final interpretation.\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 (Qual Research 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            Practice a disciplined workflow: research question \u2192 sampling \u2192 interview guide \u2192 coding plan \u2192 memoing \u2192 trustworthiness checks.\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> A strong qualitative prompt begins with: \u201cHere is my context, lens, and what I mean by key terms\u2026\u201d\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 qualitative AI support\">\n            <div class=\"reading-title\">Reading: Using AI responsibly in qualitative research<\/div>\n\n            <div class=\"reading-p\">\n              <b>1<\/b> AI can speed up qualitative research by producing <b>draft structures<\/b> such as interview questions, codebooks, and thematic scaffolds.\n              However, qualitative analysis is interpretive. AI output should be treated as a <b>provisional suggestion<\/b> rather than a final claim.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>2<\/b> A helpful use is <b>interview guide generation<\/b>: AI can propose open-ended questions, probes, and follow-ups aligned to your research question.\n              Researcher control means you refine wording for your participants, context, and ethical constraints, and you pilot-test the guide.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>3<\/b> For analysis, AI can help build a <b>coding framework<\/b>: definitions, inclusion\/exclusion criteria, and example quotes.\n              But codes must be grounded in your data and theoretical lens. You should check for overgeneralization and ensure codes remain meaningful.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>4<\/b> AI is also useful for <b>memoing<\/b>: analytic memos capture emerging patterns, surprising cases, and reflexive notes about your assumptions.\n              Good memos document decisions and create an audit trail, strengthening transparency.\n            <\/div>\n\n            <div class=\"reading-p\">\n              <b>5<\/b> Trustworthiness requires deliberate checks: <b>triangulation<\/b>, <b>negative case analysis<\/b>, and clear reporting of limitations.\n              If AI assisted your workflow, report how it was used and what steps ensured researcher oversight.\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 TOOLKIT -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"toolkit\" aria-label=\"Qualitative toolkit\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>4) Qualitative Toolkit (Interview guides, coding, themes, memos)<\/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\">Interview guide template<\/div>\n              <pre class=\"pre\" id=\"tkInterview\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Codebook template<\/div>\n              <pre class=\"pre\" id=\"tkCodebook\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\" style=\"margin-top:12px;\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">Thematic analysis scaffold<\/div>\n              <pre class=\"pre\" id=\"tkTheme\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Memo prompts (analytic + reflexive)<\/div>\n              <pre class=\"pre\" id=\"tkMemo\"><\/pre>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Trustworthiness checklist (printable)<\/div>\n            <pre class=\"pre\" id=\"tkTrust\"><\/pre>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 PROMPTS -->\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            These prompts enforce researcher control: definitions, boundaries, evidence in data excerpts, and audit trail documentation.\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 1 \u2014 Interview guide aligned to RQ<\/div>\n              <pre class=\"pre\" id=\"p1\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 2 \u2014 Initial codebook (with boundaries)<\/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 Thematic scaffold + negative cases<\/div>\n              <pre class=\"pre\" id=\"p3\"><\/pre>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">Prompt 4 \u2014 Memo writing + reflexivity<\/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\">Mini example (expected output style)<\/div>\n            <pre class=\"pre\" id=\"pExample\"><\/pre>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 LISTENING -->\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 \u201cAI suggests; the researcher decides\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\">Listen to two instructors discussing how AI can support (not replace) qualitative analysis.<\/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 QUAL LAB -->\n      <!-- ===================== -->\n      <section class=\"view\" data-view=\"lab\" aria-label=\"Qualitative analysis lab\">\n        <div class=\"card\">\n          <div class=\"card-h\">\n            <h3>7) Qual Analysis Lab (Paste transcript excerpt \u2192 generate scaffolds)<\/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=\"labRun\">Run lab<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"labCopy\">Copy report<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"labReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            Paste a <b>de-identified<\/b> excerpt. The lab produces:\n            (a) candidate codes + definitions, (b) code \u2192 category \u2192 theme scaffold,\n            (c) memo prompts, and (d) trustworthiness checks to apply.\n            <br><b>Heuristic only:<\/b> you must confirm with data and revise.\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">A) Transcript excerpt (de-identified)<\/div>\n              <textarea class=\"textarea\" id=\"txText\" rows=\"14\" placeholder=\"Paste an excerpt (8\u201325 lines) here...\"><\/textarea>\n              <div class=\"icte-small muted\" style=\"margin-top:6px;\">\n                Tip: Replace names\/places with [P1], [School], etc.\n              <\/div>\n            <\/div>\n\n            <div class=\"qitem\">\n              <div class=\"qtext\">B) Your lens + RQ (optional but recommended)<\/div>\n              <textarea class=\"textarea\" id=\"txContext\" rows=\"14\" placeholder=\"Research question, theoretical lens, participants, setting, and what you mean by key terms...\"><\/textarea>\n              <div class=\"icte-small muted\" style=\"margin-top:6px;\">\n                Tip: Lens examples: sociocultural, critical pedagogy, TPACK, SRL, etc.\n              <\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Lab output: Coding + Themes + Memos<\/div>\n            <pre class=\"pre\" id=\"labOut\"><\/pre>\n          <\/div>\n\n          <div class=\"qitem\" style=\"margin-top:12px;\">\n            <div class=\"qtext\">Safe AI prompt (for your assistant, with researcher control)<\/div>\n            <pre class=\"pre\" id=\"safePrompt\"><\/pre>\n          <\/div>\n\n          <div class=\"feedback\" id=\"labFb\" aria-live=\"polite\"><\/div>\n        <\/div>\n      <\/section>\n\n      <!-- ===================== -->\n      <!-- \u2705 PROBLEM-SOLVING -->\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<\/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=\"psCheck\">Check my solution<\/button>\n              <button class=\"btn mini ghost\" type=\"button\" id=\"psReset\">Reset<\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"note\">\n            <b>Scenario:<\/b> You have 12 interview transcripts about teachers using AI for speaking practice.\n            A teammate proposes themes generated by AI, but you worry they are too generic and not grounded in data.\n            <br><br>\n            Your task:\n            <ul class=\"ul\">\n              <li>Write a <b>control plan<\/b> (5\u20138 bullets) to keep analysis grounded and transparent.<\/li>\n              <li>Propose <b>3 trustworthiness checks<\/b> (e.g., negative cases, triangulation, member reflections).<\/li>\n              <li>Write <b>one analytic memo<\/b> (6\u201310 lines) about an emerging pattern + a competing explanation.<\/li>\n              <li>Rewrite an overly strong theme into a <b>data-aligned<\/b> theme statement with boundaries.<\/li>\n            <\/ul>\n          <\/div>\n\n          <div class=\"grid2\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">A) Control plan (5\u20138 bullets)<\/div>\n              <textarea class=\"textarea\" id=\"psPlan\" rows=\"10\" placeholder=\"- Define the coding approach (deductive\/inductive\/hybrid)...\"><\/textarea>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">B) Trustworthiness checks (3 bullets)<\/div>\n              <textarea class=\"textarea\" id=\"psTrust\" rows=\"10\" placeholder=\"- Negative case analysis...&#10;- Triangulate with observation\/documents...&#10;- Peer debriefing...\"><\/textarea>\n            <\/div>\n          <\/div>\n\n          <div class=\"grid2\" style=\"margin-top:12px;\">\n            <div class=\"qitem\">\n              <div class=\"qtext\">C) Analytic memo (6\u201310 lines)<\/div>\n              <textarea class=\"textarea\" id=\"psMemo\" rows=\"9\" placeholder=\"Memo: I notice that... Evidence: (quote\/paraphrase)... Alternative explanation: ... Next step: ...\"><\/textarea>\n            <\/div>\n            <div class=\"qitem\">\n              <div class=\"qtext\">D) Theme rewrite with boundaries<\/div>\n              <textarea class=\"textarea\" id=\"psRewrite\" rows=\"9\" placeholder=\"Overly strong: 'AI always increases motivation.'&#10;Rewrite: 'In this sample, some teachers described... (conditions\/boundaries)...'\"><\/textarea>\n            <\/div>\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\">Checklist items 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-qualai *{ box-sizing:border-box; }\n    #icte-qualai{\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-qualai .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-qualai .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-qualai .icte-menu a:hover{ opacity:1; background:rgba(255,255,255,.14); }\n    #icte-qualai .icte-menu a.is-current{ background:#fff; color:var(--dark); opacity:1; }\n\n    #icte-qualai .icte-shell{ max-width:1100px; margin:0 auto; }\n    #icte-qualai .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){ #icte-qualai .icte-hero{ grid-template-columns:1fr; } }\n    #icte-qualai h2{ margin:0 0 6px 0; font-size:22px; }\n    #icte-qualai .muted{ color:var(--muted); }\n\n    #icte-qualai .hero-top{ display:flex; gap:12px; align-items:center; }\n    #icte-qualai .hero-img{\n      width:140px;\n      height:auto;\n      border-radius:14px;\n      border:1px solid var(--line);\n      background:#111;\n      box-shadow:var(--shadow);\n    }\n    @media (max-width: 920px){\n      #icte-qualai .hero-top{ flex-direction:column; align-items:flex-start; }\n      #icte-qualai .hero-img{ width:100%; max-width:520px; }\n    }\n\n    #icte-qualai .icte-hero__controls{\n      border-left:1px dashed var(--line);\n      padding-left:14px;\n    }\n    @media (max-width: 920px){\n      #icte-qualai .icte-hero__controls{ border-left:none; padding-left:0; border-top:1px dashed var(--line); padding-top:12px; }\n    }\n\n    #icte-qualai .icte-row{ display:flex; gap:8px; flex-wrap:wrap; }\n    #icte-qualai .icte-label{ display:block; font-size:12px; font-weight:900; margin-top:8px; }\n    #icte-qualai .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-qualai .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-qualai .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-qualai .dot.on{\n      background:#22c55e;\n      box-shadow:0 0 0 4px rgba(34,197,94,.18);\n    }\n\n    #icte-qualai .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-qualai .btn:hover{ filter:brightness(.95); transform:translateY(-1px); }\n    #icte-qualai .btn:active{ transform:translateY(0); }\n    #icte-qualai .btn.ghost{\n      background:#fff;\n      color:#111827;\n      border:1px solid var(--line);\n    }\n    #icte-qualai .btn.mini{ padding:8px 10px; border-radius:10px; font-size:13px; }\n    #icte-qualai .icte-small{ font-size:12px; }\n\n    #icte-qualai .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-qualai .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-qualai .card-h h3{ margin:0; font-size:18px; }\n    #icte-qualai .card-actions{ display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end; }\n\n    #icte-qualai .view{ display:none; }\n    #icte-qualai .view.is-active{ display:block; }\n\n    #icte-qualai .grid2{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:14px;\n      margin-top:10px;\n    }\n    @media (max-width: 920px){ #icte-qualai .grid2{ grid-template-columns:1fr; } }\n\n    #icte-qualai .stack{ display:flex; flex-direction:column; gap:10px; }\n    #icte-qualai .h4{ margin:0 0 6px 0; font-size:15px; }\n    #icte-qualai .qitem{\n      padding:10px;\n      border:1px solid var(--line);\n      border-radius:14px;\n      background:#fafafa;\n    }\n    #icte-qualai .qtext{ font-weight:900; margin-bottom:8px; }\n\n    #icte-qualai .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-qualai .feedback.ok{ display:block; border-color:rgba(34,197,94,.35); background:#ecfdf5; }\n    #icte-qualai .feedback.bad{ display:block; border-color:rgba(239,68,68,.35); background:#fef2f2; }\n\n    #icte-qualai .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-qualai .reading{\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:12px;\n      background:#fff;\n      margin-top:10px;\n    }\n    #icte-qualai .reading-title{ font-weight:900; margin-bottom:8px; }\n    #icte-qualai .reading-p{ padding:8px 0; border-top:1px dashed var(--line); }\n    #icte-qualai .reading-p:first-of-type{ border-top:none; }\n\n    #icte-qualai .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-qualai .progress-grid{ grid-template-columns:1fr; } }\n    #icte-qualai .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-qualai .pnum{ font-size:28px; font-weight:1000; }\n\n    #icte-qualai .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-qualai .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-qualai .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-qualai .ul{ margin:0; padding-left:18px; }\n    #icte-qualai .ul li{ margin:6px 0; }\n\n    #icte-qualai .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-qualai .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-qualai .msg{ margin:10px 0; display:flex; gap:10px; align-items:flex-start; }\n    #icte-qualai .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-qualai .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-qualai .msg.user .who{ color:#86efac; }\n    #icte-qualai .msg.user .bubble{ background:rgba(34,197,94,.10); border-color:rgba(34,197,94,.18); }\n\n    #icte-qualai .chatbar{\n      margin-top:10px;\n      display:flex;\n      gap:8px;\n      align-items:center;\n    }\n    #icte-qualai .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-qualai');\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\n      function looksMaleName(name){\n        const n=(name||\"\").toLowerCase();\n        return n.includes(\"male\") || n.includes(\"david\") || n.includes(\"mark\") || n.includes(\"daniel\") || n.includes(\"james\") || n.includes(\"john\") || n.includes(\"michael\");\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.includes(\"olivia\") || n.includes(\"sophia\") || n.includes(\"ava\");\n      }\n\n      function pickDefaultVoices(list){\n        if(!list || !list.length) return {a:null,b:null};\n        const a = list.find(v=>looksFemaleName(v.name)) || list[0] || null;\n        let b = list.find(v=>v!==a && looksMaleName(v.name)) || list.find(v=>v!==a) || a || null;\n        if(b === a && list.length > 1) b = list.find(v=>v!==a) || b;\n        return {a,b};\n      }\n\n      function 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      function loadVoices(){\n        if(!window.speechSynthesis) return;\n        allVoices = speechSynthesis.getVoices() || [];\n\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        buildSelect(voiceASelect, googleVoicesEN);\n        buildSelect(voiceBSelect, googleVoicesEN);\n\n        const picked = pickDefaultVoices(googleVoicesEN);\n        voiceA = picked.a;\n        voiceB = picked.b;\n\n        const idxA = Math.max(0, googleVoicesEN.indexOf(voiceA));\n        const idxB = Math.max(0, googleVoicesEN.indexOf(voiceB));\n\n        voiceASelect.value = String(idxA);\n        voiceBSelect.value = String(idxB);\n\n        voiceASelect.onchange = ()=>{\n          const idx=parseInt(voiceASelect.value,10);\n          voiceA = googleVoicesEN[idx] || voiceA;\n          if(voiceB === voiceA && googleVoicesEN.length > 1){\n            voiceB = googleVoicesEN.find(v=>v!==voiceA) || voiceB;\n            voiceBSelect.value = String(googleVoicesEN.indexOf(voiceB));\n          }\n        };\n        voiceBSelect.onchange = ()=>{\n          const idx=parseInt(voiceBSelect.value,10);\n          voiceB = googleVoicesEN[idx] || voiceB;\n          if(voiceB === voiceA && googleVoicesEN.length > 1){\n            voiceB = googleVoicesEN.find(v=>v!==voiceA) || voiceB;\n            voiceBSelect.value = String(googleVoicesEN.indexOf(voiceB));\n          }\n        };\n      }\n\n      if(window.speechSynthesis){\n        loadVoices();\n        speechSynthesis.onvoiceschanged = loadVoices;\n        setTimeout(loadVoices, 700);\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      qs('#stopSpeak').addEventListener('click', stopSpeak);\n\n      \/* =========================\n         Mic Speech Recognition (ASR)\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\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 = ()=>{ if(listening){ try{ recognition.start(); }catch(_){ } } };\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_qualai_progress_v1\";\n      let progress = {};\n      try{ progress = JSON.parse(localStorage.getItem(LS_KEY) || \"{}\") || {}; }catch(_){ progress = {}; }\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:\"rq\", label:\"RQ & lens clarified\"},\n        {k:\"guide\", label:\"Interview guide drafted\"},\n        {k:\"codes\", label:\"Codebook drafted\"},\n        {k:\"themes\", label:\"Themes scaffolded\"},\n        {k:\"memo\", label:\"Memo written\"},\n        {k:\"trust\", label:\"Trustworthiness checks\"},\n        {k:\"audit\", label:\"Audit trail noted\"},\n        {k:\"ethics\", label:\"Ethics\/privacy checked\"}\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\n      ========================= *\/\n      const SAY = {\n        \"overview-instr\":\"Overview. Learn how AI can support interview guides, coding frameworks, thematic scaffolds, and memoing while keeping researcher control and ethics.\",\n        \"conv-instr\":\"Conversation. Clarify research question and lens, plan your guide, define codes, and apply trustworthiness checks.\",\n        \"reading-instr\":\"Reading. Read about responsible AI use in qualitative research and answer the quiz.\",\n        \"toolkit-instr\":\"Toolkit. Use templates for interview guides, codebooks, thematic scaffolds, memo prompts, and trustworthiness checks.\",\n        \"prompts-instr\":\"Prompts. Copy prompts that force boundaries, definitions, negative cases, and audit trail documentation.\",\n        \"list-instr\":\"Listening. Two instructors discuss why AI suggests but the researcher decides.\",\n        \"lab-instr\":\"Lab. Paste a de-identified excerpt and generate coding and memo scaffolds. You must validate them in your data.\",\n        \"problem-instr\":\"Problem-solving. Create a control plan and trustworthiness checks, write an analytic memo, and rewrite an overly strong theme.\"\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 researcher-control rules.\",\n          conversation:\"Conversation page. Plan your qualitative workflow.\",\n          reading:\"Reading page. Read and take the quiz.\",\n          toolkit:\"Toolkit page. Templates for interview guides, codebooks, themes, memos, and trustworthiness.\",\n          prompts:\"Prompts page. Copy and adapt prompts for qualitative AI support.\",\n          listening:\"Listening page. Two-speaker dialogue about researcher control.\",\n          lab:\"Lab page. Paste excerpt and generate scaffolds.\",\n          problem:\"Problem-solving page. Control plan, trustworthiness checks, memo, and theme rewrite.\",\n          progress:\"Progress page.\"\n        };\n        await speakAs(\"A\", map[v] || \"AI for qualitative research support lesson.\");\n      });\n\n      \/* =========================\n         Conversation coach (steps)\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        {\n          bot:\"Step 1: State your research question (one sentence) and your lens (one phrase).\",\n          check:(a)=> a.split(\/\\s+\/).length >= 10 && \/(lens|sociocultural|critical|phenomen|grounded|case study|ethnograph|narrative|discourse|srl|tpack)\/i.test(a),\n          tips:\"Include BOTH RQ and lens (e.g., sociocultural \/ critical \/ phenomenology \/ grounded theory).\"\n        },\n        {\n          bot:\"Step 2: Name your participants and setting (who, where) + one ethical safeguard.\",\n          check:(a)=> \/(teacher|student|participant|interview|classroom|school|university|online)\/i.test(a) && \/(consent|anonym|de-ident|privacy|confidential|ethic)\/i.test(a),\n          tips:\"Include one ethics safeguard (consent, anonymization, de-identification).\"\n        },\n        {\n          bot:\"Step 3: Write 3 interview questions (open-ended) + 1 probe question.\",\n          check:(a)=> (a.match(\/\\?\/g)||[]).length >= 3 && \/(probe|follow|tell me more|can you describe|what happened next)\/i.test(a),\n          tips:\"Use open-ended questions and add at least one probe (tell me more \/ can you describe).\"\n        },\n        {\n          bot:\"Step 4: Propose 4 initial codes with short definitions (one line each).\",\n          check:(a)=> (a.match(\/\\n\/g)||[]).length >= 3 && \/(code|definition|means|includes|excludes)\/i.test(a),\n          tips:\"Format as code: definition (include\/exclude hints).\"\n        },\n        {\n          bot:\"Step 5: Suggest 2 trustworthiness checks you will use (e.g., negative cases, triangulation, peer debriefing).\",\n          check:(a)=> \/(negative case|triang|peer debrief|member|audit trail|thick description|reflex)\/i.test(a),\n          tips:\"Name at least two checks (negative case, triangulation, peer debriefing, audit trail).\"\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      async function coachAsk(){\n        const step = convSteps[convStep];\n        if(!step){\n          addMsg(\"Coach\",\"\u2705 Done. You clarified RQ\/lens, set ethics, drafted guide questions, proposed codes, and selected trustworthiness checks.\");\n          markDone(\"conversation\", 100);\n          addCheck(\"rq\"); addCheck(\"ethics\"); addCheck(\"guide\"); addCheck(\"codes\"); addCheck(\"trust\"); addCheck(\"audit\");\n          await speakAs(\"A\",\"Done. You clarified the research question and lens, ethics safeguards, interview guide, initial codes, and trustworthiness 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(\/lens|grounded|phenomen|case study|ethnograph|discourse|srl|tpack|critical\/i.test(a)) addCheck(\"rq\");\n        if(\/consent|anonym|de-ident|privacy|confidential|ethic\/i.test(a)) addCheck(\"ethics\");\n        if((a.match(\/\\?\/g)||[]).length >= 2) addCheck(\"guide\");\n        if(\/code|definition|includes|excludes\/i.test(a)) addCheck(\"codes\");\n        if(\/negative case|triang|peer debrief|member|audit trail|reflex\/i.test(a)) addCheck(\"trust\");\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, 200); }\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 design a qualitative AI-supported workflow.\");\n        coachAsk();\n      });\n\n      addMsg(\"Coach\",\"Ready. Let\u2019s design a qualitative AI-supported workflow.\");\n      coachAsk();\n\n      \/* =========================\n         Voice transcript routing\n      ========================= *\/\n      function appendToField(el, text){\n        if(!el) return;\n        const t = norm(text);\n        if(!t) return;\n        el.value = (el.value ? (el.value + \" \") : \"\") + t;\n      }\n\n      function handleVoiceTranscript(t){\n        const text = norm(t);\n        if(!text) return;\n\n        const v = activeViewName();\n\n        if(v === \"conversation\"){\n          handleConversationInput(text);\n          return;\n        }\n\n        if(v === \"lab\"){\n          const tx = qs('#txText');\n          const ctx = qs('#txContext');\n          if(tx && !norm(tx.value)) appendToField(tx, text);\n          else appendToField(ctx, text);\n          speakAs(\"A\", \"I heard: \" + text);\n          return;\n        }\n\n        if(v === \"problem\"){\n          const plan = qs('#psPlan');\n          const trust = qs('#psTrust');\n          const memo = qs('#psMemo');\n          const rw = qs('#psRewrite');\n\n          const targets = [plan, trust, memo, rw].filter(Boolean);\n          const target = targets.find(x => !norm(x.value)) || targets[0];\n          appendToField(target, text);\n          speakAs(\"A\", \"I heard: \" + text);\n          return;\n        }\n\n        speakAs(\"A\",\"I heard: \" + text);\n      }\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) In qualitative research, AI output should be treated as\u2026\",\n          ans:\"A provisional suggestion that the researcher must validate\",\n          opts:[\n            \"Final interpretation\",\n            \"A provisional suggestion that the researcher must validate\",\n            \"A replacement for fieldwork\",\n            \"A guarantee of validity\"\n          ]\n        },\n        { q:\"2) Researcher control includes\u2026\",\n          ans:\"Audit trail, de-identification, and explicit decision-making\",\n          opts:[\n            \"Hiding prompts and decisions\",\n            \"Audit trail, de-identification, and explicit decision-making\",\n            \"Copying themes without checking\",\n            \"Skipping ethics review\"\n          ]\n        },\n        { q:\"3) A codebook should include\u2026\",\n          ans:\"Definitions plus inclusion\/exclusion criteria and example quotes\",\n          opts:[\n            \"Only one-word labels\",\n            \"Definitions plus inclusion\/exclusion criteria and example quotes\",\n            \"Only AI-generated categories\",\n            \"Only frequencies\"\n          ]\n        },\n        { q:\"4) Trustworthiness can be strengthened by\u2026\",\n          ans:\"Triangulation and negative case analysis\",\n          opts:[\n            \"One transcript only\",\n            \"Triangulation and negative case analysis\",\n            \"Avoiding memos\",\n            \"Removing contradictions\"\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(\"rq\"); addCheck(\"trust\"); addCheck(\"audit\"); }\n        markDone(\"reading_quiz\", pct);\n      });\n\n      \/* =========================\n         Toolkit content\n      ========================= *\/\n      qs('#tkInterview').textContent =\n`INTERVIEW GUIDE (template)\nA) Warm-up:\n- Can you tell me about your role\/context?\nB) Core questions (aligned to RQ):\n1) Can you describe a recent experience with [phenomenon]?\n   Probes: What happened next? Why? How did you feel?\n2) What supports or constraints shaped this experience?\n3) What changed over time (before\/after)?\nC) Sensitive\/ethics:\n- What information should not be shared?\nD) Closing:\n- Is there anything I did not ask that matters?`;\n\n      qs('#tkCodebook').textContent =\n`CODEBOOK (template)\nCode name:\nDefinition (what it means):\nIncludes (inclusion criteria):\nExcludes (exclusion criteria):\nExample quote (verbatim\/paraphrase):\nNotes (links to lens \/ context):`;\n\n      qs('#tkTheme').textContent =\n`THEMATIC SCAFFOLD\nData excerpts \u2192 Codes \u2192 Categories \u2192 Themes\nFor each theme:\n- Theme label (bounded, not universal)\n- What it captures (definition)\n- Conditions\/contexts where it applies\n- Representative excerpts (2\u20134)\n- Negative cases \/ contradictions\n- Alternative explanation(s)`;\n\n      qs('#tkMemo').textContent =\n`MEMO PROMPTS\nAnalytic memo:\n- What pattern am I seeing?\n- What data supports it (excerpts)?\n- What is surprising or contradictory?\n- What might explain it (mechanisms)?\nReflexive memo:\n- What assumptions am I bringing?\n- How might my position affect interpretation?\n- What did I choose to code and why?`;\n\n      qs('#tkTrust').textContent =\n`TRUSTWORTHINESS CHECKLIST\n[ ] De-identify data before AI use\n[ ] Document prompts + versions (audit trail)\n[ ] Code definitions + boundaries (include\/exclude)\n[ ] Negative case analysis (seek contradictions)\n[ ] Triangulation (sources\/methods\/analysts)\n[ ] Peer debriefing \/ critical friend review\n[ ] Thick description (context and participants)\n[ ] Report limitations + uncertainty`;\n\n      qs('#toolkitSpeak').addEventListener('click', ()=>{\n        speakAs(\"A\",\"Toolkit. Use interview guide, codebook, thematic scaffold, memo prompts, and trustworthiness checklist to keep the researcher in control.\");\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 INTERVIEW GUIDE (aligned to RQ)\nContext:\n- Research question: [RQ]\n- Lens: [lens]\n- Participants + setting: [who\/where]\nTask:\nDraft a semi-structured interview guide:\n- 6 open-ended questions\n- 2 probes for each question\n- 2 ethical\/sensitive handling prompts\nRules:\n- Keep language participant-friendly\n- No leading questions\n- Output as a numbered guide`;\n\n      p2.textContent =\n`PROMPT 2 \u2014 INITIAL CODEBOOK (researcher control)\nInput:\n- Lens: [lens]\n- Definitions of key terms: [your definitions]\nTask:\nPropose 10 candidate codes with:\n- definition\n- includes \/ excludes\n- 1 example quote placeholder\nRules:\n- Mark which codes are a priori vs inductive\n- Do NOT claim these are final; label as draft`;\n\n      p3.textContent =\n`PROMPT 3 \u2014 THEMES + NEGATIVE CASES\nGiven these codes + excerpts:\n[PASTE]\nTask:\n- Cluster codes into 3\u20135 categories\n- Propose 2\u20134 draft themes with boundaries\/conditions\n- Identify 2 possible negative cases (what would contradict the theme?)\nRules:\n- Use cautious language: suggests \/ may \/ in this sample`;\n\n      p4.textContent =\n`PROMPT 4 \u2014 MEMO WRITING + REFLEXIVITY\nGiven this excerpt and my lens:\n[PASTE]\nWrite:\nA) Analytic memo (150\u2013250 words): pattern, evidence, competing explanation, next step\nB) Reflexive memo (80\u2013150 words): assumptions, positionality risks, what to double-check\nRules:\n- Keep decisions explicit (audit trail style)`;\n\n      pExample.textContent =\n`EXAMPLE (snippet)\nDraft theme: \"AI as a time-saver under workload pressure\"\nBoundary: Mainly reported by novice teachers in high-stakes exam periods.\nEvidence: Excerpts describing lesson preparation time and admin demands.\nNegative case: Teacher who rejected AI due to integrity concerns.\nMemo note: Alternative explanation\u2014time pressure, not AI, drives perceived benefit.`;\n\n      qs('#promptsSpeak').addEventListener('click', ()=>{\n        speakAs(\"A\",\"Prompts. Use structured prompts to draft interview guides, codebooks with boundaries, themes with negative cases, and analytic and reflexive memos.\");\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)\n          .then(()=> speakAs(\"A\",\"Copied prompts.\"))\n          .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:\"I asked AI to code my transcript and it gave me four themes immediately.\"},\n        {role:\"B\", text:\"Use that as a scaffold, not a conclusion. Ask for code definitions, boundaries, and example excerpts.\"},\n        {role:\"A\", text:\"So I should verify by checking the actual data lines?\"},\n        {role:\"B\", text:\"Exactly. You decide what fits, search for negative cases, and write memos explaining your choices.\"},\n        {role:\"A\", text:\"What about ethics?\"},\n        {role:\"B\", text:\"De-identify participants, avoid sensitive details, and document how AI assisted your workflow.\"},\n        {role:\"A\", text:\"So AI suggests; the researcher decides.\"},\n        {role:\"B\", text:\"Yes. Researcher control protects trustworthiness and integrity.\"}\n      ];\n\n      const listenItems = [\n        {q:\"1) AI-generated themes should be treated as\u2026\", ans:\"A scaffold that must be validated in the data\"},\n        {q:\"2) A key step to strengthen themes is\u2026\", ans:\"Searching for negative cases and contradictions\"},\n        {q:\"3) Researcher control requires\u2026\", ans:\"Code boundaries, memos, and an audit trail\"},\n        {q:\"4) Ethical AI use includes\u2026\", ans:\"De-identifying data and avoiding sensitive details\"}\n      ];\n\n      function renderListenQ(){\n        const optsMap = {\n          \"A scaffold that must be validated in the data\":[\n            \"A scaffold that must be validated in the data\",\n            \"Final results\",\n            \"A replacement for interviews\"\n          ],\n          \"Searching for negative cases and contradictions\":[\n            \"Searching for negative cases and contradictions\",\n            \"Removing contradictory quotes\",\n            \"Avoiding alternative explanations\"\n          ],\n          \"Code boundaries, memos, and an audit trail\":[\n            \"Code boundaries, memos, and an audit trail\",\n            \"Only AI summaries\",\n            \"Skipping documentation\"\n          ],\n          \"De-identifying data and avoiding sensitive details\":[\n            \"De-identifying data and avoiding sensitive details\",\n            \"Uploading full identifiable transcripts\",\n            \"Sharing raw names and locations\"\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(\"themes\"); addCheck(\"audit\"); addCheck(\"ethics\"); }\n        markDone(\"listening\", pct);\n      });\n\n      \/* =========================\n         Qual Lab (heuristic scaffolds)\n      ========================= *\/\n      const txText = qs('#txText');\n      const txContext = qs('#txContext');\n      const labOut = qs('#labOut');\n      const safePrompt = qs('#safePrompt');\n      const labFb = qs('#labFb');\n\n      function linesOf(text){\n        return (text||\"\").split(\/\\n+\/).map(x=>x.trim()).filter(Boolean);\n      }\n\n      function extractKeywords(lines){\n        const stop = new Set((\"a an the and or but if then so because as is are was were be been being to of in on for with at from by about into over after before under between among this that these those i we you they he she it my our your their\").split(\" \"));\n        const freq = new Map();\n        lines.join(\" \").toLowerCase()\n          .replace(\/[^a-z0-9\\s'-]\/g,\" \")\n          .split(\/\\s+\/)\n          .filter(w=>w.length>=4 && !stop.has(w))\n          .forEach(w=> freq.set(w, (freq.get(w)||0)+1));\n        const arr = Array.from(freq.entries()).sort((a,b)=>b[1]-a[1]).slice(0,18).map(x=>x[0]);\n        return arr;\n      }\n\n      function suggestCodes(lines, keywords){\n        \/\/ simple heuristics: turn frequent keywords into draft codes + add common qual code families\n        const base = [\n          {name:\"Perceived benefits\", def:\"Statements describing advantages or positive outcomes.\"},\n          {name:\"Perceived constraints\", def:\"Statements describing barriers, costs, or limitations.\"},\n          {name:\"Agency & control\", def:\"Statements about autonomy, decision-making, and control over tools\/practices.\"},\n          {name:\"Ethics & integrity\", def:\"Statements about fairness, plagiarism, privacy, or academic integrity.\"},\n          {name:\"Support & training\", def:\"Statements about guidance, resources, professional development, or peer support.\"},\n          {name:\"Contextual pressure\", def:\"Statements about workload, time pressure, exams, policy, or institutional demands.\"}\n        ];\n        const kw = keywords.slice(0,8).map(k=>({\n          name: k.replace(\/^\\w\/, c=>c.toUpperCase()) + \" (draft)\",\n          def:\"Data segments where participants discuss or emphasize '\" + k + \"'.\"\n        }));\n        \/\/ de-duplicate by name\n        const seen = new Set();\n        const out = [];\n        [...base, ...kw].forEach(x=>{\n          const key = x.name.toLowerCase();\n          if(!seen.has(key)){ seen.add(key); out.push(x); }\n        });\n        return out.slice(0,10);\n      }\n\n      function buildThemeScaffold(codes){\n        const clusters = [\n          {cat:\"Practice & Pedagogy\", picks:[\"Perceived benefits\",\"Support & training\",\"Agency & control\"]},\n          {cat:\"Constraints & Risk\", picks:[\"Perceived constraints\",\"Ethics & integrity\",\"Contextual pressure\"]}\n        ];\n        const themes = [\n          {t:\"AI as a practical helper under constraints\", b:\"Often when time\/workload pressures are salient; not universal.\"},\n          {t:\"Researcher\/teacher agency shapes outcomes\", b:\"Effects depend on choices, skill, and tool literacy.\"},\n          {t:\"Ethics and integrity as a boundary condition\", b:\"Concerns may limit adoption or change use patterns.\"}\n        ];\n        return {clusters, themes};\n      }\n\n      function buildMemoPrompts(){\n        return [\n          \"What pattern is emerging, and what are 2\u20133 excerpts that support it?\",\n          \"What contradicts this pattern (negative case), and how will you handle it?\",\n          \"What alternative explanation could account for the same data?\",\n          \"How might my lens\/position shape what I notice or ignore?\",\n          \"What decision did I make today (code merge, rename, boundary), and why?\"\n        ];\n      }\n\n      function buildLabReport(text, ctx){\n        const lines = linesOf(text);\n        const kw = extractKeywords(lines);\n        const codes = suggestCodes(lines, kw);\n        const {clusters, themes} = buildThemeScaffold(codes);\n        const memoQs = buildMemoPrompts();\n\n        let score = 100;\n        if(lines.length < 6) score -= 30;\n        if(text.length < 250) score -= 20;\n        if(!ctx) score -= 10;\n        score = clamp(score, 0, 100);\n\n        addCheck(\"codes\"); addCheck(\"themes\"); addCheck(\"memo\"); addCheck(\"audit\"); addCheck(\"ethics\");\n\n        const out = [];\n        out.push(`QUAL ANALYSIS LAB OUTPUT (heuristic scaffold)\\nGrounding score (higher is better): ${score}%\\n`);\n        out.push(`Context\/lens provided: ${ctx ? \"Yes\" : \"No (add for better alignment)\"}\\n`);\n\n        out.push(`A) Candidate codes (draft \u2014 you must validate in data)`);\n        codes.forEach((c,i)=>{\n          out.push(`${i+1}. ${c.name}\\n   - Definition: ${c.def}\\n   - Includes: (you decide)\\n   - Excludes: (you decide)\\n   - Example excerpt: [add line\/quote here]`);\n        });\n\n        out.push(`\\nB) Code \u2192 Category \u2192 Theme scaffold (draft)`);\n        clusters.forEach(cl=>{\n          out.push(`- Category: ${cl.cat}`);\n          cl.picks.forEach(p=> out.push(`  \u2022 Code family: ${p}`));\n        });\n\n        out.push(`\\nC) Draft theme statements (with boundaries)`);\n        themes.forEach((t,i)=>{\n          out.push(`${i+1}. Theme: ${t.t}\\n   - Boundary\/conditions: ${t.b}\\n   - Evidence to attach: 2\u20134 excerpts\\n   - Negative case to seek: [what would contradict this?]`);\n        });\n\n        out.push(`\\nD) Memo prompts (choose 2\u20133 today)`);\n        memoQs.forEach(q=> out.push(`- ${q}`));\n\n        out.push(`\\nE) Trustworthiness checks to apply next`);\n        out.push(`- Negative case analysis: actively search contradictions and refine boundaries.`);\n        out.push(`- Triangulation: compare across participant groups, documents, observations, or another analyst.`);\n        out.push(`- Audit trail: record prompts, decisions, code changes, and why.`);\n\n        out.push(`\\nF) Researcher control reminders`);\n        out.push(`- Do not report themes without attaching evidence excerpts and boundaries.`);\n        out.push(`- Document where AI helped and what you changed (transparency statement).`);\n        out.push(`- Re-check de-identification before sharing any data with tools.`);\n\n        return {score, text: out.join(\"\\n\")};\n      }\n\n      function buildSafePrompt(excerpt, ctx){\n        return `ROLE: You are a qualitative research assistant.\n\nCONTEXT (researcher-provided):\n${ctx || \"[Add RQ, lens, participants, setting, and definitions here]\"}\n\nDATA (de-identified excerpt):\n${excerpt}\n\nTASK (STRICT):\n1) Propose DRAFT codes (max 10) grounded in the excerpt ONLY.\n   For each code: definition + includes\/excludes + 1 supporting excerpt (quote fragments ok).\n2) Cluster codes into categories and propose 2\u20134 DRAFT themes with boundaries\/conditions.\n3) Identify 2 negative cases or contradictions to search for in the full dataset.\n4) Write 1 short analytic memo (150\u2013200 words) with a competing explanation.\n\nRULES:\n- Do NOT claim results are final.\n- If evidence is missing, say \"INSUFFICIENT DATA\".\n- Keep an audit-trail tone: list decisions and why.\n- Avoid sensitive identifiers; preserve confidentiality.`;\n      }\n\n      qs('#labRun').addEventListener('click', ()=>{\n        const txt = norm(txText.value);\n        const ctx = norm(txContext.value);\n        if(txt.length < 160){\n          labFb.className = \"feedback bad\";\n          labFb.textContent = \"\u26a0\ufe0f Paste a longer excerpt (at least ~160 characters \/ several lines).\";\n          return;\n        }\n        const rr = buildLabReport(txt, ctx);\n        labOut.textContent = rr.text;\n        safePrompt.textContent = buildSafePrompt(txt, ctx);\n\n        markDone(\"lab\", rr.score);\n        labFb.className = \"feedback \" + (rr.score>=70 ? \"ok\":\"bad\");\n        labFb.textContent = `Lab output generated. Grounding score: ${rr.score}%. Next: attach evidence excerpts, seek negative cases, and memo your decisions.`;\n      });\n\n      qs('#labCopy').addEventListener('click', async ()=>{\n        const pack = `LAB OUTPUT\\n${labOut.textContent}\\n\\nSAFE AI PROMPT\\n${safePrompt.textContent}`;\n        if(!pack.trim()){\n          labFb.className = \"feedback bad\";\n          labFb.textContent = \"\u26a0\ufe0f Run the lab first.\";\n          return;\n        }\n        try{\n          await navigator.clipboard.writeText(pack);\n          labFb.className = \"feedback ok\";\n          labFb.textContent = \"\u2705 Copied lab output + safe prompt.\";\n        }catch(_){\n          labFb.className = \"feedback bad\";\n          labFb.textContent = \"\u26a0\ufe0f Clipboard blocked. Copy manually from the boxes.\";\n        }\n      });\n\n      qs('#labReset').addEventListener('click', ()=>{\n        txText.value = \"\";\n        txContext.value = \"\";\n        labOut.textContent = \"\";\n        safePrompt.textContent = \"\";\n        labFb.className = \"feedback\";\n        labFb.textContent = \"\";\n      });\n\n      \/* =========================\n         Problem-solving checker\n      ========================= *\/\n      const psPlan = qs('#psPlan');\n      const psTrust = qs('#psTrust');\n      const psMemo = qs('#psMemo');\n      const psRewrite = qs('#psRewrite');\n      const psFb = qs('#psFb');\n\n      function countBullets(txt){ return (txt.match(\/^\\s*[-\u2022]\/gm)||[]).length; }\n      function countLines(txt){ return (norm(txt).split(\/\\n\/).filter(x=>x.trim()).length); }\n\n      qs('#psCheck').addEventListener('click', ()=>{\n        const planOk = countBullets(psPlan.value) >= 5 && \/(audit|code|memo|boundary|excerpt|negative case|triang|ethic|de-ident)\/i.test(psPlan.value);\n        const trustOk = countBullets(psTrust.value) >= 3;\n        const memoOk = countLines(psMemo.value) >= 6 && \/(evidence|excerpt|alternative|competing|next step|decision)\/i.test(psMemo.value);\n        const rewriteOk = psRewrite.value.length >= 90 && \/(in this sample|some|may|might|suggest|under|when|context|boundary)\/i.test(psRewrite.value);\n\n        const pct = Math.round(((planOk?1:0)+(trustOk?1:0)+(memoOk?1:0)+(rewriteOk?1:0))\/4*100);\n\n        psFb.className = \"feedback \" + (pct>=70 ? \"ok\":\"bad\");\n        psFb.textContent =\n          `Check: ${pct}%\\n` +\n          `- Control plan (>=5 bullets + key terms): ${planOk ? \"Yes\" : \"No\"}\\n` +\n          `- Trustworthiness checks (>=3 bullets): ${trustOk ? \"Yes\" : \"No\"}\\n` +\n          `- Analytic memo (>=6 lines + evidence\/alternative): ${memoOk ? \"Yes\" : \"No\"}\\n` +\n          `- Theme rewrite (bounded + cautious language): ${rewriteOk ? \"Yes\" : \"No\"}`;\n\n        markDone(\"problem_solving\", pct);\n        addCheck(\"trust\"); addCheck(\"memo\"); addCheck(\"audit\"); addCheck(\"themes\");\n      });\n\n      qs('#psReset').addEventListener('click', ()=>{\n        psPlan.value = \"\";\n        psTrust.value = \"\";\n        psMemo.value = \"\";\n        psRewrite.value = \"\";\n        psFb.className = \"feedback\";\n        psFb.textContent = \"\";\n      });\n\n      \/* =========================\n         Start state\n      ========================= *\/\n      renderProgress();\n\n    })();\n  <\/script>\n\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Overview Conversation Reading Qual Toolkit Prompts Listening Qual Analysis Lab Problem-solving Progress AI for Qualitative Research Use AI to support<\/p>\n","protected":false},"author":1,"featured_media":787,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_layout":"default_layout","footnotes":""},"categories":[52,45],"tags":[],"class_list":["post-788","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-for-research","category-ai-for-teachers"],"_links":{"self":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/788","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=788"}],"version-history":[{"count":6,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/788\/revisions"}],"predecessor-version":[{"id":798,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/posts\/788\/revisions\/798"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media\/787"}],"wp:attachment":[{"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/media?parent=788"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/categories?post=788"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i-cte.org\/robot\/wp-json\/wp\/v2\/tags?post=788"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}