LEARNING EVIDENCE · RESPONSIVE TEACHING · CARE

From Play to Proof
with CARE

把一次性、玩完就結束的互動網頁,升級成「留下學習證據、看得到全班狀況、能立即回饋教學」的數據導向教育網頁。

紀欣妤老師 (Sylvia)|國立新豐高中英文老師
hsinyuchi@sfsh.tn.edu.tw
PART 0

Prep|開始前先準備好

  • 強烈建議用桌機或筆電;若能準備雙載具,邊看邊操作會更方便。
  • 選一篇你想拿來操作的英文課文/教材;若已經有 AI 教學網頁,先準備好 HTML 程式碼。
  • Google 個人帳號通常最容易操作。學校帳號也可以,但每校管理設定不同;操作時盡量只登入一個帳號。
PART 1

Warm-up|先看看我們現在在哪裡

開場調查

Warm-up

Sylvia’s Data Flow Wall|開場彈幕

點擊前往

開始之前

不是每堂課都需要網頁。如果紙筆能更快、更好,就用紙筆。

Every student responds.
Everyone gets instant feedback.
The teacher sees the whole class.
PART 2

Presentation|Experience the why

先當一次學生

先體驗學生端,再看教師端。

Demo Course

Goodbye, John|示範課程

點擊前往

先問自己:好不好玩?如果到這裡就結束,老師其實知道了什麼?

互動活動
Learning Report
Teacher Dashboard
下一步教學

三個教學轉變

轉變真正的價值
Every student responds.從少數幾位主動學生的聲音,變成每個人都留下思考痕跡。
The teacher sees the whole class.Dashboard 不是代替老師,而是先把 30–40 位學生的訊號整理給老師看。
Data tells you what NOT to teach.數據不只是告訴你要補教什麼,也能告訴你哪些內容可以不用重教。

Behind the magic

前端 HTML
學生操作
Universal GAS
固定資料通道
Google Sheet
留下資料
Teacher Dashboard
看見全班

你不需要自己會寫這些程式。今天真正要學的是:先決定什麼 Learning Evidence 值得留下,再讓 AI 幫我們把前端、後端與資料接起來。

PART 3

Practice|Learn the CARE workflow

From Play to Proof is the goal; CARE is how we get there.

C
Create先有一個學生可以操作的互動網頁。
A
AugmentAI 先規劃 Learning Evidence;老師確認後才改。
R
Record把確認後的資料真正寫進 Sheet、讀回 Dashboard。
E
Evaluate看資料、做判斷、決定下一步教學。

Technical Prep|先把固定後台準備好

為了降低現場除錯風險,我們先把全場共用、已驗證的資料通道準備完成。這一步只是先把技術底座準備好;真正的 R,會在 A 階段確認資料後才發生。

Prep.1|建立 Google Sheet

  1. 在 Google Drive 新增一份 Google 試算表。
  2. 不需要先設計表頭;Universal GAS 會依前端送來的資料自動建立/擴充 Responses 欄位。
  3. 從試算表上方選單開啟:擴充功能 → Apps Script。

Prep.2|貼上 Universal GAS Backend

  1. 刪除 Apps Script 內的預設程式碼。
  2. 複製下方完整 Universal GAS Backend。
  3. 貼上後儲存。
Universal GAS Backend本場研習標準後端
// ======================================================
// From Play to Proof|CARE 萬用 GAS 後台
// 功能:
// 1. doPost() 接收學生資料,自動建立 / 擴充表頭
// 2. doGet() 讀取 Responses 工作表,供 Teacher Dashboard 使用
// 3. 使用 LockService,降低多人同時提交造成資料衝突的風險
// ======================================================
const SHEET_NAME = 'Responses';
// ======================================================
// 1. 接收前端寫入請求(POST)
// ======================================================
function doPost(e) {
  const lock = LockService.getScriptLock();
  try {
    // 最多等待 30 秒取得鎖,避免多人同時修改表頭或寫入資料
    lock.waitLock(30000);
    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    // 固定使用 Responses 工作表
    let sheet = spreadsheet.getSheetByName(SHEET_NAME);
    // 若尚未存在,則自動建立
    if (!sheet) {
      sheet = spreadsheet.insertSheet(SHEET_NAME);
    }
    // 檢查是否真的收到 POST 資料
    if (!e || !e.postData || !e.postData.contents) {
      throw new Error('未收到 POST 資料');
    }
    const data = JSON.parse(e.postData.contents);
    // 自動加入時間戳記
    data.Timestamp = Utilities.formatDate(
      new Date(),
      Session.getScriptTimeZone(),
      'yyyy-MM-dd HH:mm:ss'
    );
    // --------------------------------------------------
    // 將陣列 / 物件轉成 JSON 字串,方便存進 Sheet
    // --------------------------------------------------
    Object.keys(data).forEach(function (key) {
      const value = data[key];
      if (value !== null && typeof value === 'object') {
        data[key] = JSON.stringify(value);
      }
    });
    // --------------------------------------------------
    // 讀取現有表頭
    // --------------------------------------------------
    const lastCol = sheet.getLastColumn();
    let headers = [];
    if (lastCol > 0) {
      headers = sheet
        .getRange(1, 1, 1, lastCol)
        .getValues()[0]
        .map(function (header) {
          return String(header).trim();
        })
        .filter(function (header) {
          return header !== '';
        });
    }
    // --------------------------------------------------
    // 自動加入尚未存在的新欄位
    // --------------------------------------------------
    Object.keys(data).forEach(function (key) {
      if (headers.indexOf(key) === -1) {
        headers.push(key);
      }
    });
    // 若目前完全沒有表頭,或增加了新欄位,就重新寫入第一列
    if (headers.length > 0) {
      sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
    }
    // --------------------------------------------------
    // 依照表頭順序建立學生資料列
    // --------------------------------------------------
    const rowData = headers.map(function (header) {
      return data[header] !== undefined && data[header] !== null
        ? data[header]
        : '';
    });
    sheet.appendRow(rowData);
    return ContentService
      .createTextOutput(
        JSON.stringify({
          status: 'success',
          message: '資料已接收'
        })
      )
      .setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    return ContentService
      .createTextOutput(
        JSON.stringify({
          status: 'error',
          message: error.toString()
        })
      )
      .setMimeType(ContentService.MimeType.JSON);
  } finally {
    // 只有真的取得 lock 時才釋放
    if (lock.hasLock()) {
      lock.releaseLock();
    }
  }
}
// ======================================================
// 2. 接收前端讀取請求(GET)
//    供 Teacher Dashboard 使用
// ======================================================
function doGet(e) {
  try {
    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getSheetByName(SHEET_NAME);
    // 沒有 Responses 工作表時,直接回傳空陣列
    if (!sheet) {
      return ContentService
        .createTextOutput(JSON.stringify([]))
        .setMimeType(ContentService.MimeType.JSON);
    }
    const lastRow = sheet.getLastRow();
    const lastCol = sheet.getLastColumn();
    // 尚無學生資料
    if (lastRow < 2 || lastCol < 1) {
      return ContentService
        .createTextOutput(JSON.stringify([]))
        .setMimeType(ContentService.MimeType.JSON);
    }
    const values = sheet
      .getRange(1, 1, lastRow, lastCol)
      .getValues();
    const headers = values[0];
    const result = [];
    // --------------------------------------------------
    // 將每一列學生資料轉成 JSON object
    // --------------------------------------------------
    for (let i = 1; i < values.length; i++) {
      const rowObject = {};
      let hasData = false;
      for (let j = 0; j < headers.length; j++) {
        const key = String(headers[j]).trim();
        if (!key) continue;
        const value = values[i][j];
        rowObject[key] = value;
        if (value !== '' && value !== null) {
          hasData = true;
        }
      }
      // 避免把完全空白列送到 Dashboard
      if (hasData) {
        result.push(rowObject);
      }
    }
    return ContentService
      .createTextOutput(JSON.stringify(result))
      .setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    return ContentService
      .createTextOutput(
        JSON.stringify({
          error: error.toString()
        })
      )
      .setMimeType(ContentService.MimeType.JSON);
  }
}

Prep.3|部署 GAS,先拿到自己的 GAS URL

  1. 點右上角「部署」→「新增部署作業」。
  2. 類型選「網頁應用程式」。
  3. 執行身分選「我」;誰可以存取務必選「所有人 (Anyone)」
  4. 🚨 研習翻車第一名陷阱:存取權限務必選「所有人 (Anyone)」!
    Google Apps Script 預設值往往是「僅限我自己 (Only myself)」。若忘記改選為「所有人 (Anyone)」,網頁看起來完全正常,但學生送出作答時會全部被 Google 默默擋掉(403 錯誤),試算表完全收不到任何資料! 請老師在點擊部署前務必再次確認!
  5. 完成授權後,複製 Web App URL,先留在記事本備用。

完成後,今天後面通常不需要再回 Apps Script 改程式;接下來主要都在 AI 與 HTML 中操作。

C — Create|先有一份能正常操作的 HTML

已經有自己的 HTML 就直接使用,不需要重做。沒有的話,可以用講師 Starter HTML、把紙本學習單交給 AI,或從 10 個 Starter Prompts 挑一個快速生成。

C — RESOURCE

Starter HTML|Animal Sleep

一份刻意保持簡單、尚未加入身分/資料提交/Learning Report/Dashboard 的演練起點。

Starter HTML_B1L3_Animal_Sleep完整 HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Do Animals Sleep like You and Me? — Starter Activity</title>
<style>
:root{
  --navy:#20304a;--blue:#6ec6e8;--pink:#ef6f9a;--yellow:#ffd86b;
  --green:#66c7a6;--purple:#8b7ed8;--ink:#263238;--muted:#60717b;
}
*{box-sizing:border-box}
body{margin:0;font-family:"Segoe UI",Arial,sans-serif;color:var(--ink);
background:linear-gradient(180deg,#dff4ff 0%,#f8fcff 50%,#fffaf4 100%);min-height:100vh}
.wrap{max-width:960px;margin:auto;padding:28px 18px 56px}
header{background:linear-gradient(135deg,var(--navy),#324d78);color:#fff;border-radius:28px;padding:30px;
box-shadow:0 14px 40px rgba(32,48,74,.18);position:relative;overflow:hidden}
header:after{content:"💤";position:absolute;right:28px;top:14px;font-size:74px;opacity:.16;transform:rotate(-10deg)}
.eyebrow{font-weight:800;letter-spacing:.12em;text-transform:uppercase;font-size:.78rem;color:#bfeaff}
h1{margin:.25rem 0 .45rem;font-size:clamp(2rem,5vw,3.5rem);line-height:1.05}
header p{max-width:720px;margin:0;color:#e6f6ff;font-size:1.05rem}
.pillrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
.pill{background:rgba(255,255,255,.12);padding:8px 12px;border-radius:999px;font-size:.88rem}
.card{background:#fff;border-radius:22px;padding:24px;margin-top:20px;box-shadow:0 10px 30px rgba(55,84,103,.10);
border:1px solid rgba(95,145,175,.16)}
.section-title{display:flex;align-items:center;gap:12px;margin:0 0 8px;font-size:1.45rem}
.num{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;color:#fff;font-weight:900;background:var(--pink);flex:none}
.hint{margin:.25rem 0 1rem;color:var(--muted);line-height:1.6}
.fact-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.fact{padding:16px;border-radius:18px;min-height:135px;display:flex;flex-direction:column;justify-content:space-between}
.fact strong{font-size:1.04rem}.fact span{font-size:2rem}.fact small{line-height:1.45;color:#42515b}
.f1{background:#fff4c9}.f2{background:#e8fff6}.f3{background:#edf0ff}.f4{background:#ffeaf1}
.question{padding:16px 0;border-top:1px dashed #ccd8df}.question:first-of-type{border-top:none}
.qtext{font-weight:800;margin-bottom:10px;line-height:1.5}.options{display:grid;gap:9px}
.option{display:flex;gap:10px;align-items:flex-start;border:1px solid #d8e3e9;border-radius:14px;padding:12px 14px;
cursor:pointer;transition:.18s;background:#fbfdff}
.option:hover{transform:translateY(-1px);border-color:#9ccfe3;background:#f3fbff}
input[type=radio]{margin-top:4px;accent-color:var(--pink)}
select,textarea{width:100%;border:1.5px solid #cbdbe4;border-radius:14px;padding:12px;font:inherit;background:#fff;color:var(--ink)}
textarea{min-height:100px;resize:vertical;line-height:1.5}
.cause-grid{display:grid;grid-template-columns:180px 1fr;gap:10px 14px;align-items:center}
.animal-label{font-weight:800;background:#f4f8fb;border-radius:12px;padding:12px}
.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px}
button{border:0;border-radius:14px;padding:13px 18px;font:inherit;font-weight:800;cursor:pointer;transition:.18s}
button:hover{transform:translateY(-1px)}.primary{background:var(--navy);color:#fff}.secondary{background:#eef4f7;color:var(--navy)}
#result{display:none;margin-top:16px;padding:16px;border-radius:16px;background:#f0fbf7;border:1px solid #bde9d8}
.feedback{font-size:.92rem;margin-top:8px;font-weight:700}.correct{color:#1f9d68}.incorrect{color:#d9534f}
.footer-note{margin-top:18px;color:#6c7b85;font-size:.86rem;text-align:center;line-height:1.5}
@media(max-width:760px){.fact-grid{grid-template-columns:repeat(2,1fr)}.cause-grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<header>
  <div class="eyebrow">Lesson 3 · Starter HTML</div>
  <h1>Do Animals Sleep like You and Me?</h1>
  <p>A short interactive reading check about four surprising animal sleeping habits.</p>
  <div class="pillrow">
    <span class="pill">🦒 Giraffe</span><span class="pill">🦦 Sea Otter</span>
    <span class="pill">🐬 Dolphin</span><span class="pill">🐦 Alpine Swift</span>
  </div>
</header>

<section class="card">
  <h2 class="section-title"><span class="num">1</span>Quick Facts</h2>
  <p class="hint">Read these short clues before you answer.</p>
  <div class="fact-grid">
    <div class="fact f1"><span>🦒</span><strong>Giraffe</strong><small>Some giraffes sleep only about 30 minutes a day and avoid long naps when predators are nearby.</small></div>
    <div class="fact f2"><span>🦦</span><strong>Sea otter</strong><small>Sea otters may float on their backs and hold paws so they do not drift away from one another.</small></div>
    <div class="fact f3"><span>🐬</span><strong>Dolphin</strong><small>Only half of a dolphin's brain sleeps at one time so it can keep swimming, breathing, and watching for danger.</small></div>
    <div class="fact f4"><span>🐦</span><strong>Alpine swift</strong><small>These migratory birds can take short naps while flying, helping them avoid danger on the ground.</small></div>
  </div>
</section>

<section class="card">
  <h2 class="section-title"><span class="num" style="background:var(--green)">2</span>Reading Check</h2>
  <p class="hint">Choose the best answer. Then click <strong>Check My Answers</strong>.</p>

  <div class="question">
    <div class="qtext">1. Which animal sleeps very little and avoids long naps because it may need to escape predators quickly?</div>
    <div class="options">
      <label class="option"><input type="radio" name="q1" value="A">A. Sea otter</label>
      <label class="option"><input type="radio" name="q1" value="B">B. Giraffe</label>
      <label class="option"><input type="radio" name="q1" value="C">C. Dolphin</label>
      <label class="option"><input type="radio" name="q1" value="D">D. Alpine swift</label>
    </div><div class="feedback" id="fb-q1"></div>
  </div>

  <div class="question">
    <div class="qtext">2. Why do sea otters hold their paws together while sleeping?</div>
    <div class="options">
      <label class="option"><input type="radio" name="q2" value="A">A. To keep warm in the air</label>
      <label class="option"><input type="radio" name="q2" value="B">B. To avoid floating away</label>
      <label class="option"><input type="radio" name="q2" value="C">C. To help them dive faster</label>
      <label class="option"><input type="radio" name="q2" value="D">D. To hide from birds</label>
    </div><div class="feedback" id="fb-q2"></div>
  </div>

  <div class="question">
    <div class="qtext">3. What is special about a dolphin's sleep?</div>
    <div class="options">
      <label class="option"><input type="radio" name="q3" value="A">A. It sleeps only in groups.</label>
      <label class="option"><input type="radio" name="q3" value="B">B. It sleeps only on land.</label>
      <label class="option"><input type="radio" name="q3" value="C">C. Half of its brain stays awake.</label>
      <label class="option"><input type="radio" name="q3" value="D">D. It never sleeps.</label>
    </div><div class="feedback" id="fb-q3"></div>
  </div>

  <div class="question">
    <div class="qtext">4. What common idea best explains these animals' unusual sleeping habits?</div>
    <div class="options">
      <label class="option"><input type="radio" name="q4" value="A">A. They want to sleep longer than humans.</label>
      <label class="option"><input type="radio" name="q4" value="B">B. They are trying to survive and avoid danger.</label>
      <label class="option"><input type="radio" name="q4" value="C">C. They all live in the same environment.</label>
      <label class="option"><input type="radio" name="q4" value="D">D. They all eat the same food.</label>
    </div><div class="feedback" id="fb-q4"></div>
  </div>
</section>

<section class="card">
  <h2 class="section-title"><span class="num" style="background:var(--purple)">3</span>Cause → Effect</h2>
  <p class="hint">Choose the effect that matches each animal's situation.</p>
  <div class="cause-grid">
    <div class="animal-label">🦒 Giraffe</div>
    <select id="ce1"><option value="">Choose an effect...</option><option value="A">It avoids long naps.</option><option value="B">It locks paws with others.</option><option value="C">Half of its brain stays awake.</option><option value="D">It takes short naps in flight.</option></select>
    <div class="animal-label">🦦 Sea otter</div>
    <select id="ce2"><option value="">Choose an effect...</option><option value="A">It avoids long naps.</option><option value="B">It locks paws with others.</option><option value="C">Half of its brain stays awake.</option><option value="D">It takes short naps in flight.</option></select>
    <div class="animal-label">🐬 Dolphin</div>
    <select id="ce3"><option value="">Choose an effect...</option><option value="A">It avoids long naps.</option><option value="B">It locks paws with others.</option><option value="C">Half of its brain stays awake.</option><option value="D">It takes short naps in flight.</option></select>
    <div class="animal-label">🐦 Alpine swift</div>
    <select id="ce4"><option value="">Choose an effect...</option><option value="A">It avoids long naps.</option><option value="B">It locks paws with others.</option><option value="C">Half of its brain stays awake.</option><option value="D">It takes short naps in flight.</option></select>
  </div>
  <div class="feedback" id="fb-ce"></div>
</section>

<section class="card">
  <h2 class="section-title"><span class="num" style="background:var(--pink)">4</span>Think & Reflect</h2>
  <p class="hint">There is no single correct answer here.</p>
  <label for="reflection"><strong>Which animal sleeping habit surprised you the most? Why?</strong></label>
  <textarea id="reflection" placeholder="I was most surprised by... because..."></textarea>
</section>

<section class="card">
  <h2 class="section-title"><span class="num" style="background:var(--yellow);color:#5d4c00">5</span>Finish</h2>
  <div class="actions">
    <button class="primary" onclick="checkAnswers()">✅ Check My Answers</button>
    <button class="secondary" onclick="resetActivity()">↻ Reset</button>
  </div>
  <div id="result"></div>
</section>

<div class="footer-note">Workshop starter page — intentionally simple: no student identity, no cloud submission, no learning report, and no teacher dashboard yet.</div>
</div>

<script>
const answerKey={q1:"B",q2:"B",q3:"C",q4:"B"};
const causeEffectKey={ce1:"A",ce2:"B",ce3:"C",ce4:"D"};

function getRadioValue(name){
  const s=document.querySelector(`input[name="${name}"]:checked`);
  return s?s.value:"";
}
function checkAnswers(){
  let correct=0,answered=0;
  Object.entries(answerKey).forEach(([q,key])=>{
    const value=getRadioValue(q),fb=document.getElementById(`fb-${q}`);
    if(value) answered++;
    if(value===key){correct++;fb.textContent="✓ Correct";fb.className="feedback correct";}
    else if(!value){fb.textContent="Please choose an answer.";fb.className="feedback incorrect";}
    else{fb.textContent=`Not quite. The best answer is ${key}.`;fb.className="feedback incorrect";}
  });
  let ceCorrect=0;
  Object.entries(causeEffectKey).forEach(([id,key])=>{
    const value=document.getElementById(id).value;
    if(value) answered++;
    if(value===key){ceCorrect++;correct++;}
  });
  const ceFb=document.getElementById("fb-ce");
  if(ceCorrect===4){ceFb.textContent="✓ Great! All four matches are correct.";ceFb.className="feedback correct";}
  else{ceFb.textContent=`${ceCorrect}/4 matches are correct. Review the facts and try again.`;ceFb.className="feedback incorrect";}

  const reflection=document.getElementById("reflection").value.trim();
  const result=document.getElementById("result");
  result.style.display="block";
  result.innerHTML=`<strong>Objective score: ${correct} / 8</strong><br>Answered objective items: ${answered} / 8`+
    (reflection?"":"<br><small>Tip: add your reflection before you finish.</small>");
  result.scrollIntoView({behavior:"smooth",block:"center"});
}
function resetActivity(){
  document.querySelectorAll('input[type="radio"]').forEach(el=>el.checked=false);
  document.querySelectorAll('select').forEach(el=>el.value="");
  document.getElementById("reflection").value="";
  document.querySelectorAll('.feedback').forEach(el=>{el.textContent="";el.className="feedback";});
  const result=document.getElementById("result");result.style.display="none";result.innerHTML="";
  window.scrollTo({top:0,behavior:"smooth"});
}
</script>
</body>
</html>
C — RESOURCE

10 個高中英文網頁 Starter Prompts

沒有現成 HTML 時,挑一個最接近你課堂需求的起點。

① 單字複習網頁Starter Prompt
請根據我提供的英文教材,製作一個適合高中生使用的互動式單字複習網頁。

請包含:
1. 重要單字與中文解釋
2. 單字例句
3. 點擊可查看意思
4. 10 題單字選擇或配對活動
5. 作答後立即顯示正確答案與簡短回饋

請產生完整單一 HTML 程式碼,CSS 與 JavaScript 都包含在同一份檔案中。
網頁需適合電腦、Chromebook 與平板操作。
② 閱讀理解互動學習單Starter Prompt
請將我提供的英文文章製作成高中英文互動閱讀網頁。

請保留原文,並加入:
1. 重要單字或片語點擊提示
2. 段落重點提示
3. 5–8 題閱讀理解題
4. 包含主旨、細節、推論等不同層次
5. 學生作答後立即看到回饋
6. 最後加入一題簡短 Reflection

請產生完整、可直接使用的單一 HTML 程式碼。
③ 文法練習網頁Starter Prompt
請根據我提供的文法教材,製作一個高中英文文法互動練習網頁。

流程為:

文法重點簡短說明

→ 範例

→ 引導練習

→ 獨立練習

→ 即時回饋

請避免只有大量選擇題,可以混合:

- 選擇

- 填空

- 句型改寫

- 找錯

- 造句

請產生完整單一 HTML 程式碼,適合學生自行操作。
④ Exit Ticket|離場卷Starter Prompt
請根據今天的課程內容,設計一個 5 分鐘可以完成的英文課 Exit Ticket 網頁。

內容包含:

1. 2 題知識理解題

2. 1 題應用題

3. 1 題「今天最清楚的是什麼?」

4. 1 題「還有什麼地方不懂?」

畫面簡潔,不需要複雜動畫。

學生完成後請在畫面上整理出他的回答摘要。

請產生完整單一 HTML 程式碼。
⑤ Vocabulary Mission|單字任務Starter Prompt
請把我提供的高中英文單字與課文內容設計成一個「Vocabulary Mission」互動任務網頁。

學生需要完成 3 個小任務:

Mission 1:單字配對

Mission 2:情境選字

Mission 3:用線索破解最後密碼

難度適合高中生,不要太幼稚。

每完成一關立即給簡短英文或中文回饋。

請產生完整單一 HTML 程式碼。
⑥ Reading Detective|閱讀偵探Starter Prompt
請將我提供的英文文章改造成「Reading Detective 閱讀偵探」網頁。

學生閱讀文章後,需要找出:

1. 關鍵人物

2. 重要事件

3. 文本證據

4. 一個推論

5. 故事或文章的核心訊息

設計成逐關解鎖的任務感,但不要改變原文事實。

請產生完整單一 HTML 程式碼。
⑦ Choose Your Response|情境選擇Starter Prompt
請根據我提供的英文主題,設計一個情境式英文互動網頁。

學生會遇到 4–6 個情境,每個情境需要選擇最適合的英文回應。

選完後:

- 告訴學生這個回答是否恰當

- 解釋語氣或用法

- 讓學生進入下一個情境

最後顯示學生的選擇結果與簡短回顧。

請產生完整單一 HTML 程式碼。
⑧ AI Role-play Warm-up|口說前暖身Starter Prompt
請根據我提供的英文單元主題,製作一個高中英文口說前暖身網頁。

學生先選擇或輸入自己想說的內容,再透過網頁完成簡短的英文情境任務。

請提供:

1. 情境說明

2. 2–3 個 sentence starters

3. 可使用的關鍵字

4. 一個簡短任務

5. 最後請學生整理出自己準備對真人同學說的英文

目的不是讓 AI 取代真人口說,而是作為真人 pair work 前的鷹架。

請產生完整單一 HTML 程式碼。
⑨ Escape Room Lite|迷你密室Starter Prompt
請根據我提供的高中英文教材,設計一個 15–20 分鐘可以完成的英文 Mini Escape Room 網頁。

設計 4 關:

1. Vocabulary

2. Grammar

3. Reading

4. Final Code

學生答對後才能取得下一關線索。

請讓挑戰有故事感,但不要因故事包裝而犧牲真正的英文學習內容。

最後顯示完成任務畫面。

請產生完整單一 HTML 程式碼。
⑩ What Would You Do?|沒有標準答案的討論型Starter Prompt
請根據我提供的英文文本或主題,製作一個「What Would You Do?」互動網頁。

學生閱讀 3–5 個與文本相關的情境後,需要做選擇並簡短說明原因。

部分題目可以沒有唯一正確答案。

目標是讓學生:

- 理解文本

- 做出判斷

- 表達個人觀點

- 為後續小組討論做準備

最後整理學生所有選擇與文字回答。

請產生完整單一 HTML 程式碼。

A — Augment|先規劃,再讓 AI 動手

這一步先不要寫程式。如果您使用一般 AI,把以下三樣材料放進同一個 AI 對話,並搭配 12 字黃金指令:

  1. 自己的 HTML 網頁程式碼(例如 Starter HTML 或您自製的單元網頁)
  2. CARE Universal Upgrade Prompt(下方核心提示詞)
  3. 剛才取得的自己的 GAS Web App URL(第二步複製的網址)

(💡 備註:萬用後台 Code.gs 已經在第二步部署完成,升級前端時完全不需要再貼給 AI,省去繁瑣代碼複製!)

這份 Prompt 會幫你做什麼?

功能白話版
保留原本活動不會為了加新功能,就把你好不容易做好的網頁整個重做。
判斷哪些學習資料值得留下AI 先幫你想:哪些學生表現真的值得記錄,而不是什麼都收。
老師先確認AI 第一回合只提規劃;你說「可以,照這個做」之後才開始改。
產生學生個人學習報告學生做完不只看到分數,也能回顧自己的作答與學習。
沒有後台也能完成即使資料暫時送不出去,學生仍可完成活動、看到報告並下載成果。
有後台時自動收資料學生作答可送進 Google Sheet,老師不用逐份整理。
整理全班學習狀況建立 Teacher Dashboard,快速看出哪些題目或觀念需要處理。
自動檢查與防呆盡量保護原本已成功的功能;出錯時只修必要部分。
🌟 兩種升級途徑 | CHOOSE YOUR PATH

途徑 A:直接使用 Sylvia 專屬客製化 Gem(最推薦、零代碼)

講師已將完整的 CARE 3.0 規範與萬用後台邏輯封裝進專屬 Gemini Gem!老師完全不需要複製長篇 Prompt 或後台代碼,直接點開專用 Gem,把 HTML 丟給它,一秒完成升級!

【12 字黃金指令】(免貼後台代碼,通用所有 AI)

萬一以後不用 Gem、或習慣使用 ChatGPT / Claude / 一般 Gemini,只要在對話框貼上以下「12 字黃金指令」,並附上 (1) 你的 HTML (2) CARE Prompt (3) 你的 GAS URL 三樣材料,AI 就會完全按照 CARE 規範嚴謹升級:

請依照 CARE 規範,幫我升級這份教學網頁。
A — CORE RESOURCE

CARE Universal Upgrade Prompt

可直接貼給一般 AI,也可日後自行封裝成 Gem、Custom GPT、Skill 或交給 Agent。

CARE Universal Upgrade Prompt核心 Prompt
用途:把既有教學 HTML 升級成能留下 Learning Evidence、產生個人 Learning Report、選擇性寫入 Google Sheet,並支援 Teacher Dashboard 的「雙棲智慧教學網頁」。這份 Prompt 不依賴 Gem,可直接貼給 ChatGPT、Gemini、Claude 等一般 AI,也可自行封裝成 Gem/Skill 或交給 AI Agent 使用。
# From Play to Proof|CARE 教學網頁升級助手
## 1. 角色與任務
你是一位專精於教育科技、高中教學活動設計、HTML/CSS/JavaScript、Google Apps Script、Google Sheets、Google Sites、Learning Analytics、Formative Assessment 與 Teacher Dashboard 設計的教育網頁升級助手。
你的主要服務對象是:沒有程式設計背景、但已經使用 AI 產生教學網頁的老師。
你的任務不是從零重新設計另一個網站,而是在盡量保留原始教學網頁的教學目標、核心玩法、題目內容、活動流程與視覺風格的前提下,把它升級成能留下 Learning Evidence 的雙棲智慧教學網頁。
## 2. 核心升級目標
所有新增功能都必須服務於:留下有意義的學習證據,並支援教師做下一步教學判斷。不要為了蒐集資料而蒐集資料。
## 3. 雙棲架構|沒有 GAS 也能完成
同一個網頁必須保留兩條使用路徑:
A|沒有 GAS/GAS 暫時失敗:學生仍可完成活動 → 看到個人 Learning Report → 下載 PNG 學習成果 → 上傳 Google Classroom/LMS。
B|有 GAS:學生完成活動 → 資料送至 Google Sheet → 老師由右上角 🔒 進入 Teacher Dashboard → 讀取全班 KPI、圖表與 Student Response Wall → 調整下一步教學。
原則:GAS 是即時資料回收路徑,不是學生完成活動的入場券。
## 4. Preserve First|Augment, don't replace
除非原本功能明顯故障,否則不要刪除原活動、任意改寫題目、改變正確答案、改變主要流程、重做成完全不同的活動、破壞既有遊戲/配對/拖曳/閱讀/作答/計時/回饋/動畫,也不要任意改變原本視覺風格。
## 5. 前端與發布原則
最終學生網頁原則上維持一個完整、可直接使用的 self-contained HTML;HTML、CSS、JavaScript 放在同一份檔案中。預設要能在 Google Sites iframe/一般學校網路/Chromebook/Windows/平板/手機中合理運作。不要建立必須使用 Node.js、npm 或 build 才能執行的架構。
核心操作不要依賴瀏覽器原生 prompt()、alert()、confirm();請使用 HTML Modal、Toast、Notice Box、Inline Message 或 Inline Validation。Teacher Login 必須使用自訂 HTML Modal。
## 6. 固定兩階段工作流程
不要一收到 HTML 就直接輸出新版程式碼。
第一階段:只分析與規劃。即使使用者已同時提供 Universal GAS Backend 與 GAS Web App URL,也不得因此提前修改程式。
第二階段:老師確認後才正式產生完整 HTML。
## 7. 第一階段|先理解原網頁
先分析:學習目標、學生實際操作、可自動評分與不適合自動評分的內容、值得留下的學生行為、能幫教師做教學判斷的資料、適合的 KPI/圖表,以及值得進入 Student Response Wall 的質性回答。
請主動做教育判斷。除非真的無法判斷,不要把資料欄位設計工作全部丟回給老師。
## 8. 第一階段固定回覆格式
請依序回覆:
1. 原本值得保留的設計
2. 建議留下的 Learning Evidence
3. 個人 Learning Report 應呈現什麼
4. Teacher Dashboard 建議的 3~6 個 KPI
5. 建議圖表,以及每張圖回答的教學問題
6. Student Response Wall:哪些質性回答值得保留,順序必須依學生前端實際出現順序
7. Google Sheet 最後大致會留下哪些資料
最後只問老師:「如果這份規劃可以,請回覆『可以,照這個做。』;若要增刪資料,請現在告訴我。」
第一階段不要輸出完整 HTML,也不要修改我提供的 Universal GAS Backend。
## 9. Teacher Checkpoint
只有老師明確確認後,才進入第二階段。若老師提出增刪需求,先更新規劃並再次確認。
## 10. 第二階段|正式升級 HTML
老師確認後,請產生完整、可直接使用的單一 HTML,而不是零散修改片段。
升級內容至少包括:必要的學生身分欄位、Learning Evidence 收集、適合的自動評量、個人 Learning Report、PNG 下載備援、Teacher Dashboard、KPI、必要圖表、Student Response Wall、Refresh/Close,以及安全的錯誤狀態。
## 11. 自動評量原則
單選、多選、是非、配對、拖曳、填空、具明確答案的文法/句型題可依需要自動評量;可記錄總分、正確率、各區塊得分、各題正誤、選項分布、作答時間與有教學價值的 retryCount。
Reflection、SEL、個人意見、創意寫作、開放式閱讀回答等沒有單一標準答案的內容,不要硬判正誤;保留學生原文,呈現在 Learning Report、Google Sheet 與 Student Response Wall。
## 12. 個人 Learning Report
Learning Report 不可只顯示一個總分。可依活動包含班級、座號、姓名、日期、作答時間、總分、正確率、各區塊表現、答題摘要、參考答案、自評、Reflection、開放式回答及其他重要 Learning Evidence。
Learning Report 必須支援下載 PNG;即使 GAS 未設定或提交失敗,學生仍能完成活動、看到 Learning Report 並下載成果。
## 13. Payload 與 Universal GAS 相容
請依活動建立結構清楚的 JSON payload,並讓同一資料概念使用一致的 canonical key。前端送出的欄位名稱,必須和 Teacher Dashboard 讀取時使用的名稱一致。
本研習會提供一份 Universal GAS Backend。請把它視為固定的標準後端:不要重寫、不要改造,也不要自行另外產生 Code.gs。使用者也可能在一開始就提供自己的 GAS Web App URL;第一階段只保留這個資訊,不要動程式。老師確認後,第二階段才把該 URL 正確寫入完整 HTML。你的工作是調整前端 HTML,使 payload 可以被該後端接收,並使 Dashboard 能正確使用 doGet() 回傳的資料。
Universal GAS 會依收到的 JSON key 自動建立/擴充 Responses 表頭,array/object 會以 JSON 字串保存;活動決定收什麼,Universal GAS 負責穩定接收與讀出。
## 14. 質性資料
若活動有多個質性回答,請保留各自清楚的欄位;若原架構適合,也可額外建立 textResponses 作為 Dashboard 的質性回答備援。Student Response Wall 的題目順序必須和學生端實際出現順序完全一致。
## 15. 提交與 no-cors
若前端使用 fetch + mode:'no-cors' 送資料,請記住前端無法可靠驗證伺服器 response status/body,因此可以顯示『資料已送出』,不要在未驗證時宣稱『已成功寫入 Google 試算表』。
Submit 後要防止重複連點,顯示送出中/完成狀態;提交失敗不得破壞學生端既有活動與 Learning Report。
## 16. Teacher Dashboard
右上角固定提供清楚可辨識的 🔒 Teacher 入口,不藏在 footer 或漢堡選單。
點擊後立即顯示自訂 Teacher Login Modal;預設示範密碼為 teacher。這只是防止學生誤觸的輕量入口,不要宣稱是真正的後端 authentication。
登入後先打開 Dashboard 外框並顯示『正在讀取資料……』,再呼叫資料;若讀取失敗,Dashboard 仍保持開啟並顯示錯誤,不要讓老師以為按鈕壞了。
Dashboard 至少具備 Refresh Data 與 Close Dashboard。
## 17. Dashboard 內容原則
KPI 只選 3~6 個真正有教學意義的指標,例如提交人數、平均得分/正確率、平均自評、平均作答時間、核心題型正確率或完成率;不要為了湊數量增加無意義 KPI。
圖表只畫能回答實際教學問題的內容,例如各題正確率、錯誤選項分布、區塊表現、自評分布;不要為了視覺效果畫圖。
Student Response Wall 讓老師不用逐格讀 Sheet;顯示名稱要用老師看得懂的題目標籤,不要只顯示 q1Rewrite 之類的底層 key。
## 18. Dashboard 容錯
部分資料錯誤不等於整個 Dashboard 故障。某 KPI 沒資料顯示 —;某題沒有文字顯示『目前無資料』;圖表資料不足顯示『資料不足』;doGet() 失敗則顯示『資料讀取失敗』。其他可用部分仍應正常。
避免 undefined/null 因 .trim() 等操作造成整頁 crash;Chart.js 重畫前先 destroy 舊 instance。
## 19. End-to-End Self Check
輸出前請逐項內部檢查:學生題目 → 前端取得答案 → payload → Universal GAS doPost() → Google Sheet → doGet() → Teacher Dashboard。
至少確認:學生基本資料存在;原活動與自動評量仍正常;Learning Report 正常;PNG 可下載;沒有 GAS 時仍能完成;payload 包含 Dashboard 未來要讀的資料;GAS_URL 有清楚設定位置;Submit 防連點;🔒、Login Modal、teacher 密碼、Dashboard、Refresh、Close 都可運作;空資料不產生 NaN/undefined;每一個質性回答都能從前端一路對到 Sheet 與 Text Wall。
## 20. JavaScript 健全性
確認所有 getElementById() 對應 id 都存在、沒有重複 id、按鈕都有 handler、沒有呼叫不存在的函式、沒有變數重複宣告;Modal/Dashboard 可開關;Google Sites 不會因原生 Dialog 阻塞核心功能。
## 21. 除錯原則|Minimum Necessary Change
若後續故障,不要立刻整份重寫。先定位問題在 UI → JavaScript event → payload → POST → GAS → Sheet → GET → mapping → Dashboard render 哪一層,再做最小必要修改。
已成功的功能不要重寫;若 Sheet 已收到資料,不要優先重寫提交流程;若 GAS URL 已設定好,不得任意移除或換回 placeholder;若只要求修 Dashboard,不要順便重新設計整個網站。
## 22. 第二階段輸出規則
正式輸出完整單一 HTML。不要輸出新的 Code.gs,因為本研習使用提供的 Universal GAS Backend。
完成後只給沒有程式背景老師真正需要的下一步,且一次一個步驟;不要一次塞成工程教科書。
## 23. GAS URL 串接規則
如果老師在一開始就已提供 GAS Web App URL:第一階段不要使用它修改程式;Teacher Checkpoint 確認後,第二階段直接把它放到正確位置。如果老師是在第二階段後才補上 URL,再進行最小必要修改。無論哪種情況,其他已成功內容不要改;重新輸出完整 HTML,並再次檢查前端 payload 能被 Universal GAS 接收、Teacher Dashboard 能正確讀取 GET 回傳資料。
## 24. 最終品質標準
最終成果要讓沒有程式背景的老師只需要處理:先部署一次 Universal GAS 並取得 GAS URL、把 HTML+CARE Prompt+Universal GAS+GAS URL 交給 AI、確認 AI 的 Learning Evidence 規劃、測試一筆資料、最後把完成版 HTML 放到 Google Sites;不需要自行理解或修改其他程式碼。
學生端仍應像正常的教學活動,而不是在填資料庫;Dashboard 只呈現真正有助於教學判斷的資料;沒有 GAS 時仍能完成活動與取得 Learning Report/PNG;有 GAS 時則進一步支援全班即時 Learning Evidence 分析。
## 25. 可攜與封裝
這份 CARE Universal Prompt 是核心規則,不依賴任何單一 AI 產品。使用者未來可直接貼給一般 AI,也可以自行封裝成 Gem、Custom GPT、Skill,或交給具備檔案/終端機/部署能力的 AI Agent。
若使用 AI Agent,也不得跳過第一階段與 Teacher Checkpoint;Agent 能自動執行更多工程步驟,不代表可以跳過老師對 Learning Evidence 的確認。
一句話版本:先看懂 → 先規劃 → 我確認 → 再全部做。

第一回合|AI 先規劃,不改程式

AI 先讀懂原始活動,只提出值得留下的 Learning Evidence、Learning Report、Teacher Dashboard、圖表與質性回答規劃。這時不應輸出新版 HTML,也不應修改 Universal GAS。

Teacher Checkpoint|老師確認後再做

規劃合理就只要回覆:「可以,照這個做。」 如果有不想收的資料或缺少的重要證據,先在這一步調整。

第二回合|AI 直接產生已串好 GAS 的完整 HTML

確認後,AI 才依同一份 CARE Prompt 產生完整新版 HTML,並直接使用你已提供的 GAS Web App URL。

R — Record|只測一筆,不要一次全班上

R 的目標,是把 A 階段已確認的 Learning Evidence 真正接進資料流。後端已經準備好,這裡不再回頭部署;只驗證資料有沒有真的走通。

  1. 開啟完成版 HTML,確認原本活動、Learning Report、右上角 Teacher 入口都正常。
  2. 以學生身分只送 1 筆測試資料。
  3. 確認 Google Sheet 出現資料,而且欄位符合剛才確認的 Learning Evidence。
  4. 打開 Teacher Dashboard,確認 KPI、圖表與開放式回答都能讀回同一筆資料。
Safety Checkpoint通過標準
1|HTML可以正常開啟與操作。
2|Google Sheet收得到 1 筆測試資料。
3|Teacher Dashboard讀得到同一筆資料。
Alternative Route|今天不想碰 GAS,也沒關係
學生完成 → Learning Report → Download PNG → Classroom/LMS → 課後再用 AI/NotebookLM 整理。
GAS 是即時資料回收的進階路徑,不是使用教學網頁的入場券。

E — Evaluate & Respond|看到資料後,下一步怎麼教?

打開 Dashboard,問自己:「看到這些資料後,我下一步會怎麼教?」

層次用途
課堂當下直接看 Teacher Dashboard,處理「現在我要怎麼教?」
課後分析把 Sheet 資料交給 AI,處理「這批資料還告訴我什麼?」
E — ANALYZE

Learning Evidence Analyzer Prompt

不只摘要數字,而是找出已掌握、需要處理、可能迷思、可少教什麼,以及下一步教學建議。

Learning Evidence Analyzer課堂學習資料分析助手
Learning Evidence Analyzer|課堂學習資料分析助手
你是一位協助教師進行形成性評量(formative assessment)的教學分析助手,請分析這份學生學習資料。
## 分析原則
1. 不要只摘要數據,要找出「對下一步教學有意義的模式」。
2. 不要捏造資料中不存在的資訊,若資料不足,請直接說資料不足,不要猜測。
3. 請清楚區分:
- 資料直接顯示的事實(Evidence)
- 根據資料做出的可能推論(Possible interpretation)
4. 不要只找學生不會的地方,也要指出:
- 已經掌握、可能不需要再花時間重教的內容
- 學生共同的優勢
5. 若有開放式回答或 Reflection,請整理:
- 常見想法
- 有代表性的不同觀點
- 可能的迷思或困惑
- 值得帶回全班討論的主題
6. 若資料不足以做某項判斷,請直接說「目前資料不足」,不要猜測。
## 請用以下格式回答
### 1. Quick Snapshot|快速總覽
用 3–5 點告訴我這一班目前最值得注意的狀況。
### 2. What They Already Know|已掌握
列出學生整體表現良好、可能不需要重新詳細教學的內容。
### 3. What Needs Attention|需要處理
依重要程度列出 1–3 個最值得處理的學習問題,並提供資料證據。
### 4. Possible Misconceptions|可能的迷思
若某些錯誤呈現共同模式,說明可能的原因。
請標示這是「推論」,不是已證實事實。
### 5. Student Voices|學生想法
若資料有開放式回答,整理主要觀點、差異與值得全班討論的內容。
### 6. Who May Need Support|可能需要額外支持
若資料足夠,指出哪些學生或哪些類型的學生可能需要:
- 再教一次
- 額外鷹架
- 個別確認
- 延伸挑戰
不要進行人格或能力標籤。
### 7. What NOT to Reteach|可以少教什麼
明確指出哪些內容學生已大致掌握,可以快速帶過或不必重新完整講解。
### 8. Next Teaching Moves|下一步教學建議
請給我最多 3 個具體、可立即執行的教學行動。
每一項說明:
- 為什麼值得做
- 適合全班/小組/個別
- 建議花多少時間
### 9. Teacher To-do List|教師待辦
最後列出一份簡短待辦清單,依優先順序分成:
- 🔴 下一堂課前一定處理
- 🟡 有時間再處理
- 🟢 暫時不用處理
### 10. One-sentence Insight|一句話洞察
最後請用一句話回答:
「如果我是這個班的老師,看到這份資料後,下一步最值得做的是什麼?」
---
以下是我的學生學習資料:
【請把 Google Sheet 資料、CSV、表格或學生回答貼在這裡】
E — VISUALIZE

Classroom Data Visualization Assistant Prompt

讓 AI 判斷什麼值得畫、用什麼圖表,以及每張圖能回答什麼教學問題。

Classroom Data Visualization Assistant形成性評量資料視覺化
你是一位協助教師分析形成性評量資料的資料視覺化助手。我會提供學生學習資料。請先分析資料結構,再判斷哪些圖表真正有助於教師理解學生學習狀況。
請遵守以下原則:
1. 不要為了畫圖而畫圖。
2. 每張圖都必須能回答一個明確的教學問題。
3. 不要捏造缺少的資料。
4. 計算比例或平均值前先確認分母與缺漏值。
5. 清楚區分原始資料與 AI 推論。
6. 優先使用容易讓老師理解的圖表:
- Bar chart
- Grouped bar chart
- Distribution chart
- Scatter plot
7. 除非資料真的適合 part-to-whole,否則避免使用 pie chart。
8. 若開放式文字無法合理分類,不要強迫視覺化。
請先告訴我:
### A. What is worth visualizing?
列出最多 3 個值得視覺化的問題,並說明原因。
### B. Recommended charts
每一張圖請提供:
- 圖表名稱
- 要回答的教學問題
- 圖表類型
- X 軸
- Y 軸
- 分組方式
- 需要使用哪些資料欄位
### C. Create the charts
依照上述建議製作圖表。
### D. Teaching Insight
每張圖最後用一句話回答:「老師看到這張圖後,最值得採取什麼行動?」
若資料不足以產生某張圖,請直接說明,不要猜測。

Optional|Google Sheets Canvas

如果你的 Google Sheets 已經出現 Canvas 功能,也可以直接在 Sheet 裡把資料做成可互動的視覺介面。沒有這項功能也完全不影響 CARE。

Google Sheets Canvas Quick PromptOptional
請根據這份學生學習資料,建立一個教師容易閱讀的互動式 dashboard。優先呈現:1. 全班完成狀況;2. 重要題目或區塊表現;3. 常見錯誤或迷思;4. 學生自評或 Reflection。不要為了好看加入沒有教學意義的圖表。目標是讓老師快速看出下一步最值得處理什麼。
PART 4

Production|Use it for real

Production 的目標不是再寫一次程式,而是讓另一個人真的使用,而且老師真的收到 Learning Evidence。

Step 1|Publish

R 的三個 Safety Checkpoints 都通過後,再把完成版 HTML 放到 Google Sites。

  1. 開啟 Google Sites,進入要放學習單的頁面。
  2. 使用「嵌入程式碼」貼上 AI 產出的完整 HTML。
  3. 預覽正常後插入並發布。

Google Sites 是免費、穩定、低門檻的發布方式;未來也可以學習 Netlify、GitHub Pages 等其他方式。

Step 2|One real response

把網站交給一位夥伴老師實際作答,再確認:學生端 → Sheet → Dashboard 整條資料流在正式發布環境中也成功。

Step 3|Production Check

完成標準不是「我有一份程式碼」;而是另一個人真的可以使用這個網站,而且老師真的看得到 Learning Evidence。

PART 5

Wrap-up|What survives when tools change?

回到最初的問題

When is technology worth the trouble?

當它能讓原本看不見的學習變得看得見,而且那些資訊真的會改變我的下一步教學時。

What’s Next?|From Proof to Agent

階段 流程 白話說明
Today|今天 Technical Prep 技術準備 → Existing HTML 現有網頁 → CARE Prompt → Teacher Approval 老師確認 → Ready-to-use HTML 完成版網頁 → Test 測試 → Evaluate 判讀 今天我們把流程拆開來做,先學會每一層在做什麼,也保留老師最後確認 Learning Evidence 的角色。
Next|之後 CARE Prompt → Gem/Skill/AI Agent 熟悉流程後,可以把同一份 CARE Prompt 封裝成 Gem 或 Skill,或交給 AI Agent 協助完成更多修改、串接、測試與部署工作。

CARE 決定「應該做什麼」;Prompt、Skill、Gem、Agent 只是不同的執行方式。

工具會改變,但工作流程應該留下來。
The tools will change. The workflow should survive.

REFERENCE

Reference Links|課後延伸

🌟 研習限定課後大禮包 | POST-WORKSHOP CHALLENGE

英語歌曲互動網頁版學習單:Count on Me

這是一份為臺灣高中職 A1~A2 英語課堂設計的旗艦互動學習單!具備 10 題行內三選一聽力挖空、單字語音朗讀 (TTS)、雙語鷹架句型引導 (Sentence Starters) 與成果認證小卡
💡 課後進階挑戰:請嘗試將今天學到的 CARE 3.0 工作流 套用到這份學習單上,為它接上萬用 Google Sheets 後台,打造屬於你的第二個學習歷程收集神器!

🎵 課後挑戰教材:Count on Me 互動學習單原始碼 HTML5 獨立單檔

完整單一 HTML 檔案,包含所有 CSS、Tailwind、JavaScript、聽力題庫與自製認證小卡機制。下載後直接以瀏覽器開啟即可離線使用或進行二次創作。

<!DOCTYPE html>

<html lang="zh-TW">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>🎧 英語歌曲互動學習單 | Bruno Mars - Count on Me</title>

  <!-- Tailwind CSS -->

  <script src="https://cdn.tailwindcss.com"></script>

  <!-- Canvas Confetti -->

  <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>

  <!-- html2canvas -->

  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>

  <!-- Google Fonts -->

  <link rel="preconnect" href="https://fonts.googleapis.com">

  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Sans+TC:wght@400;500;700;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">



  <script>

    tailwind.config = {

      theme: {

        extend: {

          colors: {

            brand: {

              50: '#fef7ee',

              100: '#fdedd6',

              200: '#fbd7ac',

              500: '#f59e0b',

              600: '#d97706',

              700: '#b45309'

            },

            accent: {

              blue: '#3b82f6',

              indigo: '#6366f1',

              emerald: '#10b981',

              rose: '#f43f5e'

            }

          },

          fontFamily: {

            sans: ['"Plus Jakarta Sans"', '"Noto Sans TC"', 'sans-serif'],

            mono: ['"JetBrains Mono"', 'monospace']

          }

        }

      }

    }

  </script>



  <style>

    body {

      background-color: #fafaf9;

      background-image: radial-gradient(#e7e5e4 1px, transparent 1px);

      background-size: 24px 24px;

    }



    /* Inline Popover Cloze Styling */

    .lyric-line {

      position: relative;

      z-index: 1;

    }



    .lyric-line.active-line {

      z-index: 500 !important;

    }



    .cloze-blank {

      position: relative;

      z-index: 2;

      display: inline-flex;

      align-items: center;

      justify-content: center;

      min-width: 90px;

      padding: 2px 10px;

      margin: 0 3px;

      border-bottom: 2.5px dashed #f59e0b;

      color: #78350f;

      font-weight: 700;

      font-size: 0.95em;

      cursor: pointer;

      border-radius: 6px;

      background-color: #fef3c7;

      transition: all 0.15s ease;

      user-select: none;

    }



    .cloze-blank:hover {

      background-color: #fde68a;

      border-bottom-color: #d97706;

      transform: translateY(-1px);

    }



    .cloze-blank.has-popover {

      z-index: 1000 !important;

      background-color: #fde68a;

      box-shadow: 0 0 0 2px #d97706;

    }



    .cloze-blank.selected {

      background-color: #e0e7ff;

      color: #3730a3;

      border-bottom: 2.5px solid #6366f1;

    }



    .cloze-blank.answered {

      cursor: default;

    }



    .cloze-blank.correct {

      background-color: #dcfce7;

      color: #15803d;

      border-bottom: 2.5px solid #22c55e;

      text-decoration: none;

    }



    .cloze-blank.wrong {

      background-color: #fee2e2;

      color: #b91c1c;

      border-bottom: 2.5px solid #ef4444;

    }



    /* Popover Container */

    .inline-popover {

      position: absolute;

      top: calc(100% + 8px);

      left: 50%;

      transform: translateX(-50%);

      display: inline-flex;

      gap: 6px;

      background: #ffffff;

      padding: 6px;

      border-radius: 12px;

      box-shadow: 0 12px 28px -4px rgba(0, 0, 0, 0.25), 0 8px 12px -6px rgba(0, 0, 0, 0.15);

      border: 1.5px solid #cbd5e1;

      z-index: 1000;

      min-width: max-content;

      animation: popFade 0.15s cubic-bezier(0.16, 1, 0.3, 1);

    }



    /* Pointer arrow pointing to blank */

    .inline-popover::before {

      content: '';

      position: absolute;

      top: -6px;

      left: 50%;

      transform: translateX(-50%);

      border-width: 0 6px 6px 6px;

      border-style: solid;

      border-color: transparent transparent #cbd5e1 transparent;

    }



    .inline-popover::after {

      content: '';

      position: absolute;

      top: -4.5px;

      left: 50%;

      transform: translateX(-50%);

      border-width: 0 5px 5px 5px;

      border-style: solid;

      border-color: transparent transparent #ffffff transparent;

    }



    /* Smart upward popover when close to screen bottom */

    .inline-popover.pop-up {

      top: auto;

      bottom: calc(100% + 8px);

      animation: popFadeUp 0.15s cubic-bezier(0.16, 1, 0.3, 1);

    }



    .inline-popover.pop-up::before {

      top: auto;

      bottom: -6px;

      border-width: 6px 6px 0 6px;

      border-color: #cbd5e1 transparent transparent transparent;

    }



    .inline-popover.pop-up::after {

      top: auto;

      bottom: -4.5px;

      border-width: 5px 5px 0 5px;

      border-color: #ffffff transparent transparent transparent;

    }



    @keyframes popFade {

      from { opacity: 0; transform: translate(-50%, -6px); }

      to { opacity: 1; transform: translate(-50%, 0); }

    }



    @keyframes popFadeUp {

      from { opacity: 0; transform: translate(-50%, 6px); }

      to { opacity: 1; transform: translate(-50%, 0); }

    }



    .pop-opt-btn {

      font-size: 0.9rem;

      padding: 5px 12px;

      background-color: #f8fafc;

      border-radius: 8px;

      cursor: pointer;

      color: #334155;

      font-weight: 700;

      transition: all 0.12s ease;

      border: 1px solid #cbd5e1;

    }



    .pop-opt-btn:hover {

      background-color: #4f46e5;

      color: #ffffff;

      border-color: #4f46e5;

      transform: scale(1.05);

    }



    /* Gamified Visual Effects */

    @keyframes float {

      0% { transform: translateY(0px); opacity: 0; }

      50% { opacity: 1; }

      100% { transform: translateY(-100vh); opacity: 0; }

    }



    .balloon {

      position: fixed;

      bottom: -50px;

      animation: float 4s ease-in infinite;

      z-index: 60;

      pointer-events: none;

    }



    @keyframes rain {

      0% { transform: translateY(-100vh); }

      100% { transform: translateY(100vh); }

    }



    .rain-drop {

      position: fixed;

      top: -50px;

      animation: rain 2s linear infinite;

      z-index: 60;

      pointer-events: none;

    }



    .grayscale-mode {

      filter: grayscale(85%);

      transition: filter 1s ease;

    }



    .shake {

      animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;

    }



    @keyframes shake {

      10%, 90% { transform: translate3d(-1px, 0, 0); }

      20%, 80% { transform: translate3d(2px, 0, 0); }

      30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }

      40%, 60% { transform: translate3d(4px, 0, 0); }

    }

  </style>

</head>

<body class="text-slate-800 antialiased selection:bg-amber-200">



  <!-- Container -->

  <div class="max-w-4xl mx-auto px-4 py-8 relative z-10">



    <!-- Header Section -->

    <header class="bg-gradient-to-r from-amber-500 via-orange-500 to-amber-600 rounded-3xl p-6 md:p-8 text-white shadow-xl shadow-amber-500/20 mb-8 relative overflow-hidden">

      <div class="absolute -right-10 -bottom-10 opacity-15 text-white pointer-events-none">

        <svg class="w-64 h-64" fill="currentColor" viewBox="0 0 24 24"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>

      </div>



      <div class="flex flex-wrap justify-between items-center gap-4 relative z-10">

        <div>

          <div class="inline-flex items-center gap-2 bg-white/20 backdrop-blur-md px-3 py-1 rounded-full text-xs font-bold tracking-wide uppercase mb-2">

            <span>✨ 課堂延伸主題:情意素養與人際互助 (True Friendship)</span>

          </div>

          <h1 class="text-2xl md:text-4xl font-extrabold tracking-tight">

            Bruno Mars - Count on Me

          </h1>

          <p class="text-amber-100 text-sm md:text-base font-medium mt-1">

            🎧 英語歌曲互動網頁學習單 | 10 題行內三選一聽力挑戰

          </p>

        </div>



        <!-- Timer Card -->

        <div class="bg-white/15 backdrop-blur-md border border-white/30 rounded-2xl px-5 py-3 text-center min-w-[120px]">

          <div class="text-[11px] font-bold uppercase tracking-wider text-amber-100">作答時間 (Timer)</div>

          <div id="live-timer" class="text-2xl font-black font-mono tracking-wider">00:00</div>

        </div>

      </div>

    </header>



    <!-- Video Player Section -->

    <section class="bg-white rounded-3xl p-4 md:p-6 shadow-md border border-slate-200 mb-8">

      <div class="flex flex-wrap justify-between items-center mb-4 gap-2">

        <div class="flex items-center gap-2">

          <span class="w-3 h-3 rounded-full bg-red-500 animate-ping"></span>

          <h2 class="text-lg font-bold text-slate-800">🎬 官方歌詞版 MV (Music Video Player)</h2>

        </div>

        <!-- YouTube Fallback Button -->

        <a href="https://www.youtube.com/watch?v=6k8cpUkKK4c" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 text-xs font-bold text-red-600 bg-red-50 hover:bg-red-100 px-3.5 py-1.5 rounded-full transition border border-red-200">

          <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"/></svg>

          ▶️ 若無法直接播放,點此開啟 YouTube 觀看

        </a>

      </div>



      <div class="relative w-full aspect-video rounded-2xl overflow-hidden shadow-inner bg-black">

        <iframe 

          class="w-full h-full"

          src="https://www.youtube-nocookie.com/embed/6k8cpUkKK4c?rel=0&enablejsapi=1" 

          title="Bruno Mars - Count on Me (Official Lyric Video)" 

          frameborder="0" 

          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 

          referrerpolicy="strict-origin-when-cross-origin"

          allowfullscreen>

        </iframe>

      </div>

      <p class="text-xs text-slate-400 mt-2 text-center">💡 點擊播放後,請邊聽邊對照下方歌詞,並點選空格填入正確單字!</p>

    </section>



    <!-- Quiz Area Section -->

    <section id="quiz-area" class="bg-white rounded-3xl p-5 md:p-8 shadow-md border border-slate-200 mb-8 relative">

      

      <!-- Sticky Progress Bar -->

      <div class="sticky top-4 bg-white/95 backdrop-blur-md p-3.5 rounded-2xl shadow-lg z-30 border border-slate-200 mb-6">

        <div class="flex justify-between items-center mb-2">

          <div class="flex items-center gap-2">

            <span class="bg-amber-100 text-amber-800 text-xs font-bold px-2.5 py-1 rounded-md">

              🎵 聽力三選一 (10 題,每題 10 分)

            </span>

            <span class="text-xs text-slate-500 font-medium">手機點擊空格即可作答</span>

          </div>

          <div class="text-sm font-bold text-slate-600">

            作答進度:<span id="progress-text" class="text-indigo-600 font-mono text-base font-extrabold">0/10</span>

          </div>

        </div>

        <div class="w-full bg-slate-100 rounded-full h-2.5 overflow-hidden">

          <div id="progress-bar" class="bg-gradient-to-r from-amber-500 to-indigo-600 h-2.5 rounded-full transition-all duration-300" style="width: 0%"></div>

        </div>

      </div>



      <!-- Lyrics Container -->

      <div id="lyrics-container" class="space-y-3.5 text-slate-800 leading-relaxed text-base md:text-lg">

        <!-- Rendered by JavaScript -->

      </div>



      <!-- Lyrics Quick Actions -->

      <div class="mt-8 text-center flex flex-wrap justify-center gap-3">

        <button onclick="checkAnswersOnly()" class="inline-flex items-center gap-2 bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold text-sm py-2.5 px-5 rounded-xl border border-slate-300 transition">

          <span>✅ 即時對答案 (Check Answers)</span>

        </button>

        <a href="#reflection-section" class="inline-flex items-center gap-2 bg-amber-50 hover:bg-amber-100 text-amber-800 font-bold text-sm py-2.5 px-5 rounded-xl border border-amber-200 transition">

          <span>⬇️ 繼續往下學習與填寫反思</span>

        </a>

      </div>

    </section>



    <!-- Vocabulary & Pronunciation Section -->

    <section class="bg-white rounded-3xl p-6 md:p-8 shadow-md border border-slate-200 mb-8">

      <div class="flex items-center gap-2 mb-4">

        <span class="p-2 rounded-xl bg-indigo-50 text-indigo-600 font-bold">📚</span>

        <div>

          <h2 class="text-xl font-bold text-slate-800">重點字彙與片語庫 (Vocabulary & Collocations)</h2>

          <p class="text-xs text-slate-500">點擊 🔊 按鈕可直接聆聽標準美式發音</p>

        </div>

      </div>



      <div class="grid grid-cols-1 md:grid-cols-2 gap-4">

        <!-- Vocab 1 -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 hover:border-indigo-300 transition">

          <div class="flex justify-between items-center mb-1">

            <span class="text-lg font-bold text-indigo-700">count on</span>

            <button onclick="speakWord('count on')" class="p-1.5 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 text-xs font-bold transition flex items-center gap-1">

              🔊 發音

            </button>

          </div>

          <p class="text-xs text-slate-500 mb-2"><span class="font-semibold text-slate-700">(phr.)</span> 依靠、指望、信賴</p>

          <div class="text-xs text-slate-600 space-y-1 mb-2">

            <p><span class="font-bold text-amber-600">常用搭配:</span>count on sb for help (尋求某人協助)、count on sb to V (指望某人去做...)</p>

          </div>

          <div class="bg-white p-2.5 rounded-xl border border-slate-200 text-xs">

            <div class="flex justify-between items-start">

              <span class="text-slate-700 italic">"You can always count on true friends when you are in trouble."</span>

              <button onclick="speakWord('You can always count on true friends when you are in trouble.')" class="text-slate-400 hover:text-indigo-600 ml-1">🔊</button>

            </div>

            <span class="text-slate-500 block mt-1">(當你身陷困境時,你永遠可以依靠真正的朋友。)</span>

          </div>

        </div>



        <!-- Vocab 2 -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 hover:border-indigo-300 transition">

          <div class="flex justify-between items-center mb-1">

            <span class="text-lg font-bold text-indigo-700">guide</span>

            <button onclick="speakWord('guide')" class="p-1.5 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 text-xs font-bold transition flex items-center gap-1">

              🔊 發音

            </button>

          </div>

          <p class="text-xs text-slate-500 mb-2"><span class="font-semibold text-slate-700">(v. / n.)</span> 指引、引導;指南、導遊</p>

          <div class="text-xs text-slate-600 space-y-1 mb-2">

            <p><span class="font-bold text-amber-600">常用搭配:</span>guide sb through sth (引領某人度過難關)、a tour guide (導遊)</p>

          </div>

          <div class="bg-white p-2.5 rounded-xl border border-slate-200 text-xs">

            <div class="flex justify-between items-start">

              <span class="text-slate-700 italic">"The kind teacher guided the lost student back to the classroom."</span>

              <button onclick="speakWord('The kind teacher guided the lost student back to the classroom.')" class="text-slate-400 hover:text-indigo-600 ml-1">🔊</button>

            </div>

            <span class="text-slate-500 block mt-1">(善良的老師引導迷路的學生回到教室。)</span>

          </div>

        </div>



        <!-- Vocab 3 -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 hover:border-indigo-300 transition">

          <div class="flex justify-between items-center mb-1">

            <span class="text-lg font-bold text-indigo-700">remind</span>

            <button onclick="speakWord('remind')" class="p-1.5 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 text-xs font-bold transition flex items-center gap-1">

              🔊 發音

            </button>

          </div>

          <p class="text-xs text-slate-500 mb-2"><span class="font-semibold text-slate-700">(v.)</span> 提醒、使想起</p>

          <div class="text-xs text-slate-600 space-y-1 mb-2">

            <p><span class="font-bold text-amber-600">常用搭配:</span>remind sb of sth (使某人想起某事)、remind sb to V (提醒某人去做某事)</p>

          </div>

          <div class="bg-white p-2.5 rounded-xl border border-slate-200 text-xs">

            <div class="flex justify-between items-start">

              <span class="text-slate-700 italic">"Please remind me to submit my English assignment tomorrow."</span>

              <button onclick="speakWord('Please remind me to submit my English assignment tomorrow.')" class="text-slate-400 hover:text-indigo-600 ml-1">🔊</button>

            </div>

            <span class="text-slate-500 block mt-1">(明天請提醒我繳交英文作業。)</span>

          </div>

        </div>



        <!-- Vocab 4 -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 hover:border-indigo-300 transition">

          <div class="flex justify-between items-center mb-1">

            <span class="text-lg font-bold text-indigo-700">supposed to</span>

            <button onclick="speakWord('supposed to')" class="p-1.5 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 text-xs font-bold transition flex items-center gap-1">

              🔊 發音

            </button>

          </div>

          <p class="text-xs text-slate-500 mb-2"><span class="font-semibold text-slate-700">(adj. phr.)</span> 應當、理應、本該</p>

          <div class="text-xs text-slate-600 space-y-1 mb-2">

            <p><span class="font-bold text-amber-600">常用搭配:</span>be supposed to V (理應做某事)、not supposed to V (按理不該做某事)</p>

          </div>

          <div class="bg-white p-2.5 rounded-xl border border-slate-200 text-xs">

            <div class="flex justify-between items-start">

              <span class="text-slate-700 italic">"We are supposed to respect and help each other in class."</span>

              <button onclick="speakWord('We are supposed to respect and help each other in class.')" class="text-slate-400 hover:text-indigo-600 ml-1">🔊</button>

            </div>

            <span class="text-slate-500 block mt-1">(在課堂上我們理應彼此尊重、互相幫助。)</span>

          </div>

        </div>



        <!-- Vocab 5 -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 hover:border-indigo-300 transition md:col-span-2">

          <div class="flex justify-between items-center mb-1">

            <span class="text-lg font-bold text-indigo-700">fall asleep</span>

            <button onclick="speakWord('fall asleep')" class="p-1.5 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 text-xs font-bold transition flex items-center gap-1">

              🔊 發音

            </button>

          </div>

          <p class="text-xs text-slate-500 mb-2"><span class="font-semibold text-slate-700">(phr.)</span> 入睡、睡著 (注意:asleep 為形容詞,不可與 sleep 混淆)</p>

          <div class="text-xs text-slate-600 space-y-1 mb-2">

            <p><span class="font-bold text-amber-600">常用搭配:</span>fall fast asleep (熟睡)、struggle to fall asleep (輾轉難以入眠)</p>

          </div>

          <div class="bg-white p-2.5 rounded-xl border border-slate-200 text-xs">

            <div class="flex justify-between items-start">

              <span class="text-slate-700 italic">"After an exhausting training session, he fell asleep right away."</span>

              <button onclick="speakWord('After an exhausting training session, he fell asleep right away.')" class="text-slate-400 hover:text-indigo-600 ml-1">🔊</button>

            </div>

            <span class="text-slate-500 block mt-1">(在筋疲力盡的訓練之後,他立刻就睡著了。)</span>

          </div>

        </div>

      </div>

    </section>



    <!-- Grammar & Sentence Pattern Section -->

    <section class="bg-white rounded-3xl p-6 md:p-8 shadow-md border border-slate-200 mb-8">

      <div class="flex items-center gap-2 mb-4">

        <span class="p-2 rounded-xl bg-amber-50 text-amber-600 font-bold">🎯</span>

        <div>

          <h2 class="text-xl font-bold text-slate-800">高中核心句型解析 (Target Sentence Patterns)</h2>

          <p class="text-xs text-slate-500">掌握高中大考高頻文法架構</p>

        </div>

      </div>



      <div class="space-y-4">

        <!-- Pattern 1 -->

        <div class="p-4 rounded-2xl bg-amber-50/50 border border-amber-200">

          <h3 class="font-bold text-amber-900 text-base mb-1">

            📌 句型一:If + S + V (現在簡單式), S + will / can + V (原形動詞) ...

          </h3>

          <p class="text-xs text-slate-600 mb-2">

            【文法考點】此為高中核心「真實條件句 (First Conditional)」。用以描述未來可能發生之情境。<strong>注意:If 子句須用現在式代替未來式</strong>,主要子句則使用助動詞 will / can。

          </p>

          <div class="space-y-1 text-xs">

            <p class="font-mono bg-white p-2 rounded-lg border border-amber-200 text-slate-700">

              🎵 歌詞典範:If you ever <span class="text-amber-700 font-bold">find</span> yourself lost in the dark, I <span class="text-indigo-700 font-bold">will be</span> the light to guide you.

            </p>

            <p class="font-mono bg-white p-2 rounded-lg border border-amber-200 text-slate-700">

              📝 升學造句:If it <span class="text-amber-700 font-bold">rains</span> tomorrow, our class picnic <span class="text-indigo-700 font-bold">will be</span> canceled.

            </p>

          </div>

        </div>



        <!-- Pattern 2 -->

        <div class="p-4 rounded-2xl bg-indigo-50/50 border border-indigo-200">

          <h3 class="font-bold text-indigo-900 text-base mb-1">

            📌 句型二:find + oneself + 受詞補語 (形容詞 / 介系詞片語 / 分詞) ...

          </h3>

          <p class="text-xs text-slate-600 mb-2">

            【文法考點】表示<strong>「不知不覺中發現自己身處於某種狀況或情緒」</strong>。受詞為反身代名詞 (oneself),補語可用形容詞、現在分詞 (-ing) 或介系詞片語。

          </p>

          <div class="space-y-1 text-xs">

            <p class="font-mono bg-white p-2 rounded-lg border border-indigo-200 text-slate-700">

              🎵 歌詞典範:If you ever find <span class="text-indigo-700 font-bold">yourself stuck</span> in the middle of the sea... (stuck 為形容詞表受困)

            </p>

            <p class="font-mono bg-white p-2 rounded-lg border border-indigo-200 text-slate-700">

              📝 升學造句:After studying late at night, she found <span class="text-indigo-700 font-bold">herself falling</span> asleep at her desk. (分詞表持續動作)

            </p>

          </div>

        </div>

      </div>

    </section>



    <!-- Appreciation Section -->

    <section class="bg-gradient-to-br from-slate-900 via-slate-900 to-indigo-950 border border-slate-800 text-white rounded-3xl p-6 md:p-8 shadow-xl mb-8">

      <div class="flex items-center gap-3 mb-4">

        <span class="text-2xl">🎧</span>

        <h2 class="text-xl font-black text-white">聽懂這首歌:青春共鳴與背後寓意 (Behind the Song)</h2>

      </div>



      <!-- 歌手檔案與榮譽戰績卡 -->

      <div class="bg-slate-800/90 rounded-2xl p-4 md:p-5 mb-5 border border-slate-700/80 flex flex-col md:flex-row md:items-center justify-between gap-3 shadow-md">

        <div class="flex items-center gap-3">

          <span class="text-3xl">🏆</span>

          <div>

            <h3 class="text-sm md:text-base font-black text-amber-400">【歌手檔案與超狂戰績】Bruno Mars 火星人布魯諾</h3>

            <p class="text-xs text-slate-300 mt-0.5">榮獲 15 座葛萊美獎、史上首位擁有 6 首鑽石認證單曲的超級流行天王</p>

          </div>

        </div>

        <div class="inline-flex items-center text-[11px] font-bold text-emerald-400 bg-emerald-950/80 px-3.5 py-1.5 rounded-full border border-emerald-800/60 self-start md:self-auto shrink-0">

          🎸 美國 RIAA 4 白金認證 + 全球畢業季友誼第一神曲

        </div>

      </div>



      <!-- 寫給高中職生的對話式賞析短文 -->

      <div class="space-y-3 text-sm md:text-base leading-relaxed text-slate-300">

        <p>一把簡單溫暖的烏克麗麗,加上磁性動人的嗓音,這首歌是全世界公認最純粹的友誼贊歌。在高中最難熬的時刻——可能是被考卷狠狠打擊的挫折、跟家人吵架的委屈、或是失戀的眼淚,最棒的解藥往往不是大道理,而是朋友的一句『我在』。</p>

        <p>歌詞唱道:『If you ever find yourself stuck in the middle of the sea, I'll sail the world to find you.』真正的友誼不需要華麗的承諾,而是在你掉眼淚時默默遞上衛生紙,在你跌倒時伸出手說『數到一二三,我就在你身邊』。聽完這首歌,不妨轉過頭,給坐在你旁邊的好朋友一個擊掌或一句發自內心的感謝吧!</p>

      </div>

    </section>



    <!-- Reflection & Rating Section (Bilingual Scaffolding for A1-A2) -->

    <section id="reflection-section" class="bg-white rounded-3xl p-6 md:p-8 shadow-md border border-slate-200 mb-8 scroll-mt-6">

      <div class="flex items-center gap-2 mb-4">

        <span class="p-2 rounded-xl bg-emerald-50 text-emerald-600 font-bold">✍️</span>

        <div>

          <h2 class="text-xl font-bold text-slate-800">Part 3: Reflection & Rating (學生自我反思與推薦)</h2>

          <p class="text-xs text-slate-500">

            💡 Feel free to answer in English or Chinese! (歡迎用英文或中文作答,寫出真實想法最重要!)

          </p>

        </div>

      </div>



      <div class="space-y-6">

        <!-- 1-5 Star Range Slider -->

        <div class="p-4 rounded-2xl bg-slate-50 border border-slate-200">

          <label class="block text-sm font-bold text-slate-800 mb-1">

            1. Song Rating (歌曲推薦指數,請拉動 1 ~ 5 星):

          </label>

          <p class="text-xs text-slate-500 mb-2">How much do you like this song? (你有多喜歡這首歌曲?)</p>

          <div class="flex items-center gap-4">

            <input 

              type="range" 

              id="rating-slider" 

              min="1" 

              max="5" 

              value="5" 

              class="w-full h-2.5 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-amber-500"

              oninput="updateRatingDisplay(this.value)"

            >

            <div id="rating-badge" class="font-extrabold text-amber-600 bg-amber-100 px-3 py-1 rounded-xl text-sm min-w-[200px] text-center">

              ⭐⭐⭐⭐⭐ (5/5) 絕世神曲!

            </div>

          </div>

        </div>



        <!-- Reflection Q1 -->

        <div>

          <label class="block text-sm font-bold text-slate-800 mb-1">

            2. Why do you give this rating? (你給予這個星等的原因是什麼?):

          </label>

          <p class="text-xs text-slate-500 mb-2">

            Tell us about the melody, rhythm, or your feelings. (說說你對旋律、節奏或聽完的感受)

          </p>

          <!-- Scaffolding Box -->

          <div class="bg-amber-50/70 border border-amber-200 rounded-xl p-2.5 mb-2 text-xs text-slate-600">

            <span class="font-bold text-amber-800">💡 Sentence Starter (你可以這樣開頭):</span>

            <div class="mt-1 space-y-0.5 font-mono text-[11px]">

              <p>• <strong>I like this song because...</strong> (我喜歡這首歌,因為...)</p>

              <p>• <strong>The melody is... and it makes me feel...</strong> (旋律很...,讓我覺得...)</p>

              <p class="text-slate-500">📝 Example: <span class="text-slate-700 italic">"I like this song because the melody is warm and sweet."</span> (我喜歡這首歌,因為旋律很溫暖甜蜜。)</p>

            </div>

          </div>

          <input 

            type="text" 

            id="student-reason" 

            placeholder="I like this song because the melody is warm. / 旋律很溫暖放鬆,聽完心情很好..." 

            class="w-full p-3 text-sm rounded-xl border border-slate-300 focus:outline-none focus:border-amber-500 transition"

          >

        </div>



        <!-- Reflection Q2 -->

        <div>

          <label class="block text-sm font-bold text-slate-800 mb-1">

            3. Which lyric line touched you the most? What did you learn about friendship? (哪句歌詞最打動你?關於友誼你學到了什麼?):

          </label>

          <p class="text-xs text-slate-500 mb-2">

            Pick one line you like and share your thought. (挑選一句你最喜歡的歌詞並分享心得)

          </p>

          <!-- Scaffolding Box -->

          <div class="bg-indigo-50/70 border border-indigo-200 rounded-xl p-2.5 mb-2 text-xs text-slate-600">

            <span class="font-bold text-indigo-800">💡 Sentence Starter (你可以這樣開頭):</span>

            <div class="mt-1 space-y-0.5 font-mono text-[11px]">

              <p>• <strong>My favorite line is "..." because...</strong> (我最喜歡這句歌詞,因為...)</p>

              <p>• <strong>From this song, I learned that a good friend...</strong> (從這首歌中,我學到一個好朋友會...)</p>

              <p class="text-slate-500">📝 Example: <span class="text-slate-700 italic">"I like 'I'll be there.' It reminds me to help my friends."</span> (我最喜歡「我會守候在你身旁」,提醒我要主動幫助朋友。)</p>

            </div>

          </div>

          <textarea 

            id="student-quote" 

            rows="3" 

            placeholder="My favorite line is 'You can count on me.' It reminds me to be a good friend. / 最喜歡 'You can count on me',提醒我朋友需要時要挺身而出..." 

            class="w-full p-3 text-sm rounded-xl border border-slate-300 focus:outline-none focus:border-amber-500 transition"

          ></textarea>

        </div>

      </div>

    </section>



    <!-- Final Submission Section (Page Footer) -->

    <section class="text-center py-4 mb-12">

      <div class="bg-gradient-to-r from-amber-500 via-orange-500 to-amber-600 rounded-3xl p-6 md:p-10 text-white shadow-xl shadow-amber-500/25 relative overflow-hidden">

        <div class="absolute -right-6 -bottom-6 opacity-15 pointer-events-none">

          <svg class="w-48 h-48" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>

        </div>

        <div class="relative z-10">

          <div class="inline-flex items-center gap-2 bg-white/20 backdrop-blur-md px-3.5 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-3">

            <span>🎓 學習任務總結算 | 108課綱學習歷程數位佐證</span>

          </div>

          <h3 class="text-2xl md:text-3xl font-black mb-2 tracking-tight">

            恭喜完成《Count on Me》英語歌曲學習單!

          </h3>

          <p class="text-amber-100 text-xs md:text-sm max-w-xl mx-auto mb-6 leading-relaxed">

            您已完成 10 題聽力挖空、5 大核心單字發音、高中大考文法句型,並完成了自我反思。<br class="hidden sm:inline">

            請點擊下方按鈕結算成績,領取收錄完整反思的<strong>專屬學習歷程成果認證卡</strong>!

          </p>

          <button onclick="submitQuiz()" class="inline-flex items-center gap-3 bg-white hover:bg-amber-50 text-amber-900 font-black text-lg md:text-xl py-4 px-10 rounded-2xl shadow-2xl transform transition hover:scale-105 active:scale-95">

            <span>✨ 結算成績並生成成果認證卡 (Submit & Get Certificate)</span>

            <span class="text-lg">➜</span>

          </button>

          <div class="mt-6 pt-4 border-t border-white/20 flex flex-wrap justify-between items-center text-xs text-white/80">

            <span>Designed with ❤️ by hsinyuchi (Sylvia) | 本學習單僅供學術教學非商業使用,音樂與歌詞版權歸原出版唱片公司所有</span>

            <span>授權條款:CC BY-NC-SA 4.0 姓名標示-非商業性-相同方式分享</span>

          </div>

        </div>

      </div>

    </section>



  </div>



  <!-- Effects Container for Confetti/Balloons/Rain -->

  <div id="effects-container" class="fixed inset-0 pointer-events-none z-50 overflow-hidden"></div>



  <!-- Custom In-DOM Alert / Confirm Dialog (100% Google Sites & iframe Sandbox Compatible) -->

  <div id="custom-dialog-modal" class="fixed inset-0 z-[100] hidden flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">

    <div class="bg-white rounded-3xl p-6 max-w-md w-full text-center shadow-2xl transform scale-95 transition-all duration-200 border border-slate-100" id="custom-dialog-box">

      <div id="dialog-icon" class="text-5xl mb-3">💡</div>

      <h3 id="dialog-title" class="text-xl font-black text-slate-800 mb-2">提示訊息</h3>

      <p id="dialog-message" class="text-sm text-slate-600 mb-6 leading-relaxed whitespace-pre-line">內容說明</p>

      <div class="flex gap-3 justify-center" id="dialog-actions">

        <button id="dialog-cancel-btn" class="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold py-3 rounded-xl transition text-sm">

          取消

        </button>

        <button id="dialog-confirm-btn" class="flex-1 bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 text-white font-extrabold py-3 rounded-xl shadow-md transition text-sm">

          確定

        </button>

      </div>

    </div>

  </div>



  <!-- Result Modal & Certificate Modal -->

  <div id="result-modal" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 overflow-y-auto">

    <div class="bg-white rounded-3xl p-4 md:p-6 max-w-xl w-full text-center shadow-2xl transform scale-95 opacity-0 transition-all duration-300 relative my-8" id="result-content">

      

      <!-- Top Close Button -->

      <button onclick="closeResultModal()" class="absolute top-4 right-4 w-9 h-9 rounded-full bg-slate-100 hover:bg-slate-200 text-slate-500 hover:text-slate-800 flex items-center justify-center font-bold text-lg transition z-20" title="關閉視窗">

        ✕

      </button>



      <!-- Step 1: Input Student Information -->

      <div id="input-step" class="py-6 px-4">

        <div id="result-icon" class="text-6xl mb-3">🎉</div>

        <h2 class="text-2xl md:text-3xl font-black text-slate-800 mb-1">作答結算完成!</h2>

        <p class="text-sm text-slate-500 mb-6">您的聽力測驗得分為 <span id="modal-score-preview" class="text-amber-600 font-extrabold text-xl">100</span> 分!請填寫資料領取認證卡:</p>

        

        <div class="grid grid-cols-2 gap-3 mb-3">

          <input type="text" id="student-class" placeholder="班級 (例如: 高一仁)" class="w-full p-3 border-2 border-slate-200 rounded-xl text-center text-sm font-bold focus:outline-none focus:border-amber-500 transition">

          <input type="text" id="student-seat" placeholder="座號 (例如: 08)" class="w-full p-3 border-2 border-slate-200 rounded-xl text-center text-sm font-bold focus:outline-none focus:border-amber-500 transition">

        </div>

        <input type="text" id="student-name" placeholder="學生姓名 (例如: 王小明)" class="w-full p-3 border-2 border-slate-200 rounded-xl text-center text-base font-bold mb-6 focus:outline-none focus:border-amber-500 transition">

        

        <button onclick="generateCertificate()" class="w-full bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 text-white font-extrabold py-3.5 rounded-xl shadow-lg transition">

          🎖️ 生成學習成果認證卡 (Generate Certificate)

        </button>

      </div>



      <!-- Step 2: The High-Resolution Certificate Card -->

      <div id="certificate-step" class="hidden">

        

        <!-- Certificate Canvas Target -->

        <div id="certificate-card" class="p-6 md:p-8 rounded-2xl text-left bg-gradient-to-br from-amber-50 via-white to-orange-50 border-4 border-amber-300 shadow-inner relative overflow-hidden my-2">

          

          <!-- Decorative Background Badge -->

          <div class="absolute -right-8 -bottom-8 opacity-10 pointer-events-none">

            <svg class="w-48 h-48 text-amber-600" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>

          </div>



          <!-- Header -->

          <div class="border-b-2 border-dashed border-amber-200 pb-3 mb-3 flex justify-between items-center">

            <div>

              <div class="text-[10px] font-extrabold uppercase tracking-widest text-amber-700 bg-amber-100 px-2 py-0.5 rounded-full inline-block">

                🎵 英語歌曲學習成果認證卡

              </div>

              <h1 class="text-xl md:text-2xl font-black text-slate-800 tracking-tight mt-1">

                Certificate of Achievement

              </h1>

              <p class="text-xs font-semibold text-slate-500">Track: Bruno Mars - Count on Me</p>

            </div>

            <div class="text-right">

              <span id="cert-date" class="text-xs font-mono text-slate-400 block">2026.09.11</span>

              <span id="cert-time" class="text-xs font-mono text-amber-600 font-bold block">耗時 02:45</span>

            </div>

          </div>



          <!-- Student Info & Score -->

          <div class="flex justify-between items-center py-2">

            <div>

              <p class="text-xs text-slate-400 font-bold uppercase tracking-wider">This certifies that</p>

              <div class="flex items-baseline gap-2 mt-0.5">

                <h2 id="cert-name" class="text-2xl md:text-3xl font-black text-slate-900 underline decoration-amber-400 decoration-4 underline-offset-4">王小明</h2>

                <span id="cert-class-seat" class="text-xs font-bold text-slate-500">(高一仁 08號)</span>

              </div>

              <p class="text-xs text-slate-500 mt-1">has completed the lyrics listening challenge with a score of:</p>

            </div>

            

            <div class="text-right pl-4">

              <div class="text-5xl md:text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-amber-500 to-orange-600 font-mono" id="cert-score">

                100

              </div>

              <span class="text-[10px] font-bold text-slate-400 tracking-widest block uppercase">out of 100</span>

            </div>

          </div>



          <!-- Motivational Remark -->

          <div class="bg-white/80 backdrop-blur-sm p-3 rounded-xl border border-amber-200 mt-2">

            <div class="text-[11px] font-bold text-amber-700 uppercase tracking-wider mb-0.5">🌟 挑戰回饋 (Challenge Feedback)</div>

            <p id="cert-teacher-remark" class="text-xs font-semibold text-slate-800">

              "Amazing job! You heard every word correctly! You are a super listener!"<br>

              <span class="text-[11px] font-normal text-slate-500">(太棒了!你每個字都聽對了,你是超級聽力大師!)</span>

            </p>

          </div>



          <!-- Student Reflection & Learning Portfolio Proof -->

          <div class="mt-3 p-3.5 rounded-xl bg-white/95 border-2 border-amber-200/80 shadow-sm text-xs space-y-2.5">

            <div class="flex items-center justify-between border-b border-amber-100 pb-1.5">

              <span class="font-extrabold text-amber-900 flex items-center gap-1.5 text-xs">

                <span>📝</span> 課程學習心得與反思 (Student Reflection)

              </span>

              <span class="text-[10px] text-amber-700 bg-amber-100 px-2 py-0.5 rounded-md font-bold">心得與回饋</span>

            </div>



            <!-- Q1: Rating & Reason -->

            <div>

              <div class="font-bold text-slate-800 flex items-center justify-between mb-1">

                <span>1. 推薦指數與原因 (Rating & Reason):</span>

                <span id="cert-rating-stars" class="text-amber-600 font-extrabold text-xs">⭐⭐⭐⭐⭐ (5/5)</span>

              </div>

              <div id="cert-reason-content" class="text-slate-700 bg-amber-50/70 p-2.5 rounded-lg border border-amber-200/60 text-xs leading-relaxed whitespace-pre-wrap">

                (學生填寫內容)

              </div>

            </div>



            <!-- Q2: Touched Lyric & Takeaway -->

            <div>

              <div class="font-bold text-slate-800 mb-1">

                <span>2. 觸動歌詞與友誼啟發 (Touched Lyric & Key Takeaway):</span>

              </div>

              <div id="cert-quote-content" class="text-slate-700 bg-indigo-50/60 p-2.5 rounded-lg border border-indigo-200/60 text-xs leading-relaxed whitespace-pre-wrap">

                (學生填寫內容)

              </div>

            </div>

          </div>



          <!-- Certificate Footer -->

          <div class="mt-2.5 pt-2 border-t border-slate-200 text-xs text-slate-400 flex justify-between items-center text-[10px] font-medium">

            <span>English Song Listening Challenge</span>

            <span>Keep Learning & Singing! 🎵</span>

          </div>

        </div>



        <!-- Generated PNG Image Preview Section (Guaranteed Save in Google Sites / Mobile) -->

        <div id="cert-image-preview-wrapper" class="hidden mt-3 p-4 bg-amber-50/90 rounded-2xl border-2 border-amber-200 text-center">

          <div class="flex items-center justify-center gap-1.5 text-xs font-extrabold text-amber-900 mb-2">

            <span>✨ 成果認證卡已成功生成!</span>

          </div>

          <p class="text-[11px] text-slate-600 mb-3 leading-relaxed">

            📱 手機/平板用戶:請直接<strong>「長按下方圖片」</strong>點選「儲存影像」<br>

            💻 電腦用戶:若瀏覽器未自動下載,請在圖片上<strong>「點右鍵 ➜ 另存影像」</strong>

          </p>

          <img id="cert-image-preview" class="max-w-full rounded-xl shadow-lg mx-auto border-2 border-amber-300" alt="成果認證卡高解析預覽圖">

          <div class="mt-3 flex flex-wrap justify-center gap-2">

            <button id="open-newtab-btn" onclick="openCertInNewTab()" class="px-3.5 py-1.5 rounded-lg bg-white hover:bg-slate-50 text-slate-700 border border-slate-300 text-xs font-bold transition shadow-sm">

              🔍 在新分頁檢視大圖

            </button>

          </div>

        </div>



        <!-- Action Buttons -->

        <div class="p-3 flex flex-col sm:flex-row gap-3">

          <button id="download-cert-btn" onclick="downloadCertificate()" class="flex-1 bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 text-white font-extrabold py-3 px-4 rounded-xl shadow-md transition flex items-center justify-center gap-2 text-sm">

            <span id="download-btn-icon">📥</span>

            <span id="download-btn-text">生成並下載成果認證卡 (PNG)</span>

          </button>

          <button onclick="closeResultModal()" class="sm:w-1/3 bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold py-3 px-4 rounded-xl transition text-sm">

            關閉視窗

          </button>

        </div>

      </div>



    </div>

  </div>



  <!-- JavaScript Logic -->

  <script>

    // --- 1. LYRICS & CLOZE DATA (10 Blanks with Phonological Distractors) ---

    const lyricsData = [

      { type: "section", title: "Intro (輕快烏克麗麗前奏 - 0:00)" },

      { en: "Oh-oh-oh", zh: "(烏克麗麗輕快旋律開場)" },

      { type: "section", title: "Verse 1 (航向汪洋・化作指引的光 - 0:05)" },

      { en: "If you ever find yourself stuck in the [middle] of the sea", zh: "若你曾發現自己受困在大海中央", options: ["middle", "needle", "riddle"] },

      { en: "I'll [sail] the world to find you", zh: "我會航行穿越全世界去尋找你", options: ["sail", "sale", "tail"] },

      { en: "If you ever find yourself lost in the dark and you can't see", zh: "若你曾發現自己在黑暗中迷航、看不清前方" },

      { en: "I'll be the light to [guide] you", zh: "我願化作一道光指引你前行", options: ["guide", "hide", "glide"] },

      { type: "section", title: "Pre-Chorus 1 (患難見真情 - 0:28)" },

      { en: "We'll find out what we're made of", zh: "我們才發現自己的本質多麼堅韌" },

      { en: "When we are called to help our friends in need", zh: "當我們被召喚去幫助身處困境的友人" },

      { type: "section", title: "Chorus 1 (數 1, 2, 3 我就在你身旁 - 0:37)" },

      { en: "You can [count] on me like 1, 2, 3, I'll be there", zh: "你可以像數 1, 2, 3 般依靠著我,我定會陪伴身旁", options: ["count", "mount", "doubt"] },

      { en: "And I know when I need it", zh: "而我也深知當我有所匱乏時" },

      { en: "I can count on you like 4, 3, 2, and you'll be there", zh: "我也能如數 4, 3, 2 般依傍著你,你定會即刻守候" },

      { en: "'Cause that's what friends are [supposed] to do, oh, yeah", zh: "因為那正是真正朋友理應彼此相挺的模樣", options: ["supposed", "exposed", "opposed"] },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(溫暖和聲)" },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(溫暖和聲)" },

      { en: "Ooh, yeah, yeah", zh: "(輕快旋律)" },

      { type: "section", title: "Verse 2 (輾轉難眠・身旁的溫柔歌聲 - 1:10)" },

      { en: "If you tossin' and you're turnin' and you just can't fall [asleep]", zh: "若你輾轉難眠、翻來覆去就是無法入睡", options: ["asleep", "awake", "afloat"] },

      { en: "I'll sing a song beside you", zh: "我會在你身旁輕輕唱首歌" },

      { en: "And if you ever [forget] how much you really mean to me", zh: "若你曾不經意淡忘了你對我有多麼重要", options: ["forget", "forgive", "regret"] },

      { en: "Every day I will [remind] you, oh", zh: "每一天,我都定會輕聲提醒你", options: ["remind", "remain", "rewind"] },

      { type: "section", title: "Pre-Chorus 2 (堅定互助 - 1:33)" },

      { en: "We'll find out what we're made of", zh: "我們才發現自己的內心多麼強大" },

      { en: "When we are called to help our friends in need", zh: "每當身旁的朋友需要援手與陪伴" },

      { type: "section", title: "Chorus 2 (再次相挺 - 1:42)" },

      { en: "You can count on me like 1, 2, 3, I'll be there", zh: "你可以像數 1, 2, 3 般信賴著我,我隨時都在" },

      { en: "And I know when I need it", zh: "而我也清楚知道當我脆弱無助時" },

      { en: "I can count on you like 4, 3, 2, and you'll be there", zh: "我也能像數 4, 3, 2 般依靠著你,你定會守候" },

      { en: "'Cause that's what friends are supposed to do, oh, yeah", zh: "因為那正是真正摯友之間最真切的模樣" },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(真摯和聲)" },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(真摯和聲)" },

      { en: "Ooh, yeah, yeah", zh: "(溫暖旋律)" },

      { type: "section", title: "Bridge (哭泣時的肩膀・永不放手 - 2:14)" },

      { en: "You'll always have my [shoulder] when you cry", zh: "當你傷心落淚時,我的肩膀永遠讓你依靠", options: ["shoulder", "soldier", "shadow"] },

      { en: "I'll never let go, never say [goodbye]", zh: "我絕不放手,也絕不輕言道別", options: ["goodbye", "goodnight", "goodwill"] },

      { type: "section", title: "Chorus 3 (高潮合唱 - 2:33)" },

      { en: "You know you can count on me like 1, 2, 3, I'll be there", zh: "你心裡明白,你可以隨時像數 1, 2, 3 般依靠著我,我必守候身旁" },

      { en: "And I know when I need it", zh: "而我也無比確信,當我身處困境時" },

      { en: "I can count on you like 4, 3, 2, and you'll be there", zh: "我也能像數 4, 3, 2 般依賴著你,你必定會在身側" },

      { en: "'Cause that's what friends are supposed to do, oh, yeah", zh: "因為那就是真正朋友理所當然的相互扶持" },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(全場大合唱)" },

      { en: "Ooh-ooh-ooh-ooh-ooh", zh: "(全場大合唱)" },

      { en: "Ooh", zh: "(深情尾奏)" },

      { type: "section", title: "Outro (相互依靠的誓言 - 3:05)" },

      { en: "You can count on me 'cause I can count on you", zh: "你可以永遠依靠著我,因為我也能永遠信賴著你。" },

    ];



    let userAnswers = {};

    let finalScore = 0;

    const totalQuestions = 10;

    let timerSeconds = 0;

    let timerInterval = null;



    // --- 2. TIMER LOGIC ---

    function startTimer() {

      timerInterval = setInterval(() => {

        timerSeconds++;

        const mins = String(Math.floor(timerSeconds / 60)).padStart(2, '0');

        const secs = String(timerSeconds % 60).padStart(2, '0');

        document.getElementById('live-timer').innerText = `${mins}:${secs}`;

      }, 1000);

    }



    // --- 3. RENDER LYRICS & INLINE POPOVER ---

    function renderLyrics() {

      const container = document.getElementById('lyrics-container');

      let blankCounter = 0;



      lyricsData.forEach((line) => {

        if (line.type === 'section') {

          const sec = document.createElement('div');

          sec.className = "pt-3 pb-1 border-b border-slate-200 font-extrabold text-xs tracking-wider uppercase text-amber-700 flex items-center gap-2";

          sec.innerHTML = `<span>🎵 ${line.title}</span>`;

          container.appendChild(sec);

          return;

        }



        let html = line.en;



        if (html.includes('[')) {

          html = html.replace(/\[(.*?)\]/g, (match, word) => {

            const id = blankCounter++;

            const safeOptions = line.options ? line.options.map(o => o.replace(/'/g, "\\'")) : [];

            return `<span id="blank-${id}" 

                          class="cloze-blank" 

                          data-correct="${word}"

                          data-options="${safeOptions.join(',')}"

                          onclick="toggleInlinePopover(${id}, event)">

                          <span class="text-xs text-amber-600/70 mr-1 font-mono">${id + 1}.</span>____

                    </span>`;

          });

        }



        const div = document.createElement('div');

        div.className = "lyric-line py-1 flex flex-col md:block";

        div.innerHTML = `<span class="font-semibold text-slate-800">${html}</span> <span class="text-xs text-slate-400 font-normal md:ml-3 block md:inline">${line.zh}</span>`;

        container.appendChild(div);

      });



      // Global click outside to close popover

      document.addEventListener('click', (e) => {

        if (!e.target.closest('.cloze-blank')) {

          closeAllPopovers();

        }

      });

    }



    // --- 4. INLINE POPOVER MECHANICS ---

    function toggleInlinePopover(id, event) {

      event.stopPropagation();

      const blankEl = document.getElementById(`blank-${id}`);

      

      // If already has popover, toggle close

      if (blankEl.querySelector('.inline-popover')) {

        closeAllPopovers();

        return;

      }



      closeAllPopovers();



      // Elevate z-index of the active blank and its line

      blankEl.classList.add('has-popover');

      const lineEl = blankEl.closest('.lyric-line');

      if (lineEl) lineEl.classList.add('active-line');



      const optionsStr = blankEl.dataset.options;

      if (!optionsStr) return;

      const options = optionsStr.split(',');

      const shuffled = [...options].sort(() => Math.random() - 0.5);



      const popover = document.createElement('div');

      popover.className = 'inline-popover';



      // Smart flip: if close to viewport bottom, display upwards

      const rect = blankEl.getBoundingClientRect();

      if (rect.bottom + 65 > window.innerHeight && rect.top > 65) {

        popover.classList.add('pop-up');

      }



      shuffled.forEach(opt => {

        const btn = document.createElement('button');

        btn.className = 'pop-opt-btn';

        btn.innerText = opt;

        btn.onclick = (e) => selectOption(id, opt, e);

        popover.appendChild(btn);

      });



      blankEl.appendChild(popover);

    }



    function closeAllPopovers() {

      document.querySelectorAll('.inline-popover').forEach(el => el.remove());

      document.querySelectorAll('.cloze-blank').forEach(el => el.classList.remove('has-popover'));

      document.querySelectorAll('.lyric-line').forEach(el => el.classList.remove('active-line'));

    }



    function selectOption(id, word, event) {

      event.stopPropagation();

      userAnswers[id] = word;



      const blankEl = document.getElementById(`blank-${id}`);

      blankEl.innerHTML = `<span class="text-xs text-indigo-500 mr-1 font-mono font-bold">${id + 1}.</span>${word}`;

      blankEl.classList.add('selected');

      closeAllPopovers();



      updateProgress();

    }



    function updateProgress() {

      const answeredCount = Object.keys(userAnswers).length;

      const pct = (answeredCount / totalQuestions) * 100;

      document.getElementById('progress-text').innerText = `${answeredCount}/${totalQuestions}`;

      document.getElementById('progress-bar').style.width = `${pct}%`;

    }



    // --- CUSTOM DIALOG & MODAL UTILITIES (100% IFRAME & GOOGLE SITES SAFE) ---

    function showCustomAlert(title, message, icon = '💡') {

      const modal = document.getElementById('custom-dialog-modal');

      const iconEl = document.getElementById('dialog-icon');

      const titleEl = document.getElementById('dialog-title');

      const msgEl = document.getElementById('dialog-message');

      const cancelBtn = document.getElementById('dialog-cancel-btn');

      const confirmBtn = document.getElementById('dialog-confirm-btn');



      iconEl.innerText = icon;

      titleEl.innerText = title;

      msgEl.innerText = message;

      cancelBtn.classList.add('hidden');

      confirmBtn.innerText = '我知道了';

      confirmBtn.onclick = () => {

        modal.classList.add('hidden');

      };

      modal.classList.remove('hidden');

      modal.scrollIntoView({ behavior: 'smooth', block: 'center' });

    }



    function showCustomConfirm({ title, message, icon = '⚠️', confirmText = '確定', cancelText = '取消', onConfirm, onCancel }) {

      const modal = document.getElementById('custom-dialog-modal');

      const iconEl = document.getElementById('dialog-icon');

      const titleEl = document.getElementById('dialog-title');

      const msgEl = document.getElementById('dialog-message');

      const cancelBtn = document.getElementById('dialog-cancel-btn');

      const confirmBtn = document.getElementById('dialog-confirm-btn');



      iconEl.innerText = icon;

      titleEl.innerText = title;

      msgEl.innerText = message;

      cancelBtn.classList.remove('hidden');

      cancelBtn.innerText = cancelText;

      confirmBtn.innerText = confirmText;



      cancelBtn.onclick = () => {

        modal.classList.add('hidden');

        if (typeof onCancel === 'function') onCancel();

      };

      confirmBtn.onclick = () => {

        modal.classList.add('hidden');

        if (typeof onConfirm === 'function') onConfirm();

      };



      modal.classList.remove('hidden');

      modal.scrollIntoView({ behavior: 'smooth', block: 'center' });

    }



    function closeResultModal() {

      const modal = document.getElementById('result-modal');

      const content = document.getElementById('result-content');

      content.classList.remove('scale-100', 'opacity-100');

      content.classList.add('scale-95', 'opacity-0');

      setTimeout(() => {

        modal.classList.add('hidden');

      }, 200);

    }



    // --- 5. CHECK ANSWERS ONLY (LIGHTWEIGHT) ---

    function checkAnswersOnly() {

      const answeredCount = Object.keys(userAnswers).length;

      if (answeredCount === 0) {

        showCustomAlert("尚未作答", "請先點擊歌詞中的黃色空格進行聽力作答,再來對答案喔!", "✍️");

        return;

      }

      let correctCount = 0;

      const blanks = document.querySelectorAll('.cloze-blank');

      blanks.forEach((btn, idx) => {

        const correct = btn.dataset.correct;

        const userAns = userAnswers[idx];

        if (!userAns) return; // don't reveal unanswered

        const qNum = `<span class="text-xs font-mono font-bold text-slate-500 mr-1">${idx + 1}.</span>`;

        if (userAns === correct) {

          correctCount++;

          btn.classList.remove('selected');

          btn.classList.add('correct');

          btn.innerHTML = `${qNum}${userAns} <span class="text-[10px] font-black">✓</span>`;

        } else {

          btn.classList.remove('selected');

          btn.classList.add('wrong');

          btn.innerHTML = `${qNum}<del class="opacity-70">${userAns}</del> <span class="text-[11px] font-bold text-green-700 ml-1">(${correct})</span>`;

        }

      });

      showCustomAlert(

        "即時對題結果",

        `🎉 目前已作答的題目中,您答對了 ${correctCount} 題!\n請繼續向下閱讀單字句型並完成「自我反思」,最後於頁尾領取認證卡!`,

        "🎯"

      );

    }



    // --- 6. FINAL SUBMISSION (AT FOOTER) & EFFECTS TRIGGER ---

    function submitQuiz() {

      const answeredCount = Object.keys(userAnswers).length;

      if (answeredCount < totalQuestions) {

        showCustomConfirm({

          title: "聽力尚未作答完畢",

          message: `⚠️ 聽力挖空還有 ${totalQuestions - answeredCount} 題尚未作答!\n確定要直接結算成績嗎?`,

          icon: "⚠️",

          confirmText: "直接結算",

          cancelText: "回去作答",

          onConfirm: () => checkReflectionAndProceed(),

          onCancel: () => {

            const firstUnanswered = document.querySelector('.cloze-blank:not(.selected)');

            if (firstUnanswered) firstUnanswered.scrollIntoView({ behavior: 'smooth', block: 'center' });

          }

        });

        return;

      }



      checkReflectionAndProceed();

    }



    function checkReflectionAndProceed() {

      const sReason = document.getElementById('student-reason').value.trim();

      const sQuote = document.getElementById('student-quote').value.trim();

      if (!sReason && !sQuote) {

        showCustomConfirm({

          title: "學習反思尚未填寫",

          message: "💡 貼心提醒:您尚未填寫最後的「學習反思問答」!\n若未填寫,成果認證卡將會缺少學習歷程的反思文字。\n\n確定要直接結算領取小卡嗎?",

          icon: "📝",

          confirmText: "直接結算",

          cancelText: "回去填寫",

          onConfirm: () => finalizeSubmission(),

          onCancel: () => {

            const refSec = document.getElementById('reflection-section');

            if (refSec) refSec.scrollIntoView({ behavior: 'smooth', block: 'center' });

            document.getElementById('student-reason').focus();

          }

        });

        return;

      }



      finalizeSubmission();

    }



    function finalizeSubmission() {

      clearInterval(timerInterval);

      let score = 0;

      const blanks = document.querySelectorAll('.cloze-blank');



      blanks.forEach((btn, idx) => {

        const correct = btn.dataset.correct;

        const userAns = userAnswers[idx];



        btn.removeAttribute('onclick');

        btn.classList.add('answered');

        const qNum = `<span class="text-xs font-mono font-bold text-slate-500 mr-1">${idx + 1}.</span>`;



        if (userAns === correct) {

          score += 10;

          btn.classList.remove('selected');

          btn.classList.add('correct');

          btn.innerHTML = `${qNum}${userAns} <span class="text-[10px] font-black">✓</span>`;

        } else {

          btn.classList.remove('selected');

          btn.classList.add('wrong');

          btn.innerHTML = `${qNum}<del class="opacity-70">${userAns || '未答'}</del> <span class="text-[11px] font-bold text-green-700 ml-1">(${correct})</span>`;

        }

      });



      finalScore = score;

      document.getElementById('modal-score-preview').innerText = finalScore;



      const modal = document.getElementById('result-modal');

      const content = document.getElementById('result-content');

      modal.classList.remove('hidden');



      setTimeout(() => {

        content.classList.remove('scale-95', 'opacity-0');

        content.classList.add('scale-100', 'opacity-100');

        modal.scrollIntoView({ behavior: 'smooth', block: 'center' });

      }, 50);



      triggerEffects(finalScore);

    }



    // --- 6. GAMIFIED DYNAMIC EFFECTS ---

    function triggerEffects(score) {

      document.body.classList.remove('grayscale-mode');

      const fx = document.getElementById('effects-container');

      fx.innerHTML = '';

      const icon = document.getElementById('result-icon');



      if (score >= 80) {

        icon.innerText = "🧙♀️✨";

        if (typeof window.confetti === 'function') {

          triggerConfetti();

        }

      } else if (score >= 60) {

        icon.innerText = "🎈☁️";

        createBalloons(12);

      } else if (score >= 40) {

        icon.innerText = "😬🧹";

        document.body.classList.add('shake');

        setTimeout(() => document.body.classList.remove('shake'), 1000);

      } else {

        icon.innerText = "🌧️😭";

        document.body.classList.add('grayscale-mode');

        createRain();

      }

    }



    function triggerConfetti() {

      const duration = 3 * 1000;

      const end = Date.now() + duration;

      const interval = setInterval(() => {

        if (Date.now() > end) return clearInterval(interval);

        confetti({

          particleCount: 40,

          spread: 360,

          startVelocity: 30,

          origin: { x: Math.random() * 0.3 + 0.1, y: Math.random() - 0.2 }

        });

        confetti({

          particleCount: 40,

          spread: 360,

          startVelocity: 30,

          origin: { x: Math.random() * 0.3 + 0.6, y: Math.random() - 0.2 }

        });

      }, 250);

    }



    function createBalloons(count) {

      const container = document.getElementById('effects-container');

      for (let i = 0; i < count; i++) {

        const b = document.createElement('div');

        b.className = 'balloon';

        b.innerText = ['🎈', '🎉', '🌟', '🎸'][Math.floor(Math.random() * 4)];

        b.style.left = Math.random() * 95 + 'vw';

        b.style.animationDuration = (Math.random() * 2 + 3) + 's';

        b.style.fontSize = (Math.random() * 1.5 + 2) + 'rem';

        container.appendChild(b);

      }

    }



    function createRain() {

      const container = document.getElementById('effects-container');

      for (let i = 0; i < 35; i++) {

        const d = document.createElement('div');

        d.className = 'rain-drop';

        d.innerText = '💧';

        d.style.left = Math.random() * 100 + 'vw';

        d.style.animationDuration = (Math.random() * 0.8 + 0.6) + 's';

        d.style.animationDelay = Math.random() * 1.5 + 's';

        container.appendChild(d);

      }

    }



    // --- 7. SLIDER LOGIC ---

    function updateRatingDisplay(val) {

      const badge = document.getElementById('rating-badge');

      const ratings = {

        '5': '⭐⭐⭐⭐⭐ (5/5) 絕世神曲!不聽後悔',

        '4': '⭐⭐⭐⭐☆ (4/5) 旋律溫暖,超值得推薦',

        '3': '⭐⭐⭐☆☆ (3/5) 節奏輕快,感覺還不錯',

        '2': '⭐⭐☆☆☆ (2/5) 普普通通,不是我的菜',

        '1': '⭐☆☆☆☆ (1/5) 不太有共鳴'

      };

      badge.innerText = ratings[val] || '⭐⭐⭐⭐⭐';

    }



    // --- 8. TTS AUDIO PRONUNCIATION ---

    function speakWord(text) {

      if (!('speechSynthesis' in window)) {

        showCustomAlert("語音提示", "您的瀏覽器暫不支援語音朗讀功能!", "🔊");

        return;

      }

      window.speechSynthesis.cancel();

      const utter = new SpeechSynthesisUtterance(text);

      utter.lang = 'en-US';

      utter.rate = 0.85; // slightly slower for EFL learners

      window.speechSynthesis.speak(utter);

    }



    // --- 9. CERTIFICATE GENERATION & HIGH-SPEED PNG EXPORT ---

    let cachedCertDataUrl = null;



    function generateCertificate() {

      const sClass = document.getElementById('student-class').value.trim();

      const sSeat = document.getElementById('student-seat').value.trim();

      const sName = document.getElementById('student-name').value.trim();



      if (!sClass || !sSeat || !sName) {

        showCustomAlert("請填妥資料", "請完整輸入班級、座號與姓名,方可領取專屬認證卡!", "⚠️");

        if (!sClass) document.getElementById('student-class').focus();

        else if (!sSeat) document.getElementById('student-seat').focus();

        else document.getElementById('student-name').focus();

        return;

      }



      cachedCertDataUrl = null; // reset cache for new info

      document.getElementById('input-step').classList.add('hidden');

      document.getElementById('certificate-step').classList.remove('hidden');



      // Populate Certificate Data

      document.getElementById('cert-name').innerText = sName;

      document.getElementById('cert-class-seat').innerText = `(${sClass} ${sSeat}號)`;

      document.getElementById('cert-score').innerText = finalScore;

      document.getElementById('cert-date').innerText = new Date().toLocaleDateString();

      document.getElementById('cert-time').innerText = `作答耗時 ${document.getElementById('live-timer').innerText}`;



      // Star rating display

      const ratingVal = document.getElementById('rating-slider').value;

      const starsMap = { '5': '⭐⭐⭐⭐⭐', '4': '⭐⭐⭐⭐☆', '3': '⭐⭐⭐☆☆', '2': '⭐⭐☆☆☆', '1': '⭐☆☆☆☆' };

      document.getElementById('cert-rating-stars').innerText = `${starsMap[ratingVal]} (${ratingVal}/5)`;



      // Full Student Reflection Answers (Proof of Learning for Portfolio)

      const sReason = document.getElementById('student-reason').value.trim() || "(學生未填寫推薦原因)";

      const sQuote = document.getElementById('student-quote').value.trim() || "(學生未填寫反思心得)";



      document.getElementById('cert-reason-content').innerText = sReason;

      document.getElementById('cert-quote-content').innerText = sQuote;



      // Smart Teacher Remarks based on score (Tailored for A1-A2 levels)

      const remarkEl = document.getElementById('cert-teacher-remark');

      if (finalScore === 100) {

        remarkEl.innerHTML = `"Amazing job! You heard every word correctly! You are a super listener!"<br><span class="text-[11px] font-normal text-slate-500">(太棒了!你每個字都聽對了,你是超級聽力大師!)</span>`;

      } else if (finalScore >= 80) {

        remarkEl.innerHTML = `"Great job! Your English listening is wonderful! Keep it up!"<br><span class="text-[11px] font-normal text-slate-500">(做得好!你的英文聽力很棒,繼續保持!)</span>`;

      } else if (finalScore >= 60) {

        remarkEl.innerHTML = `"Good work! You can catch the beat! Practice more and you will be even better!"<br><span class="text-[11px] font-normal text-slate-500">(很棒!你抓到歌曲節奏了,多練習會更進步!)</span>`;

      } else {

        remarkEl.innerHTML = `"Keep trying! Just like a good friend, English will always be here for you! Try again!"<br><span class="text-[11px] font-normal text-slate-500">(別放棄!就像好朋友一樣,英文永遠陪伴你,再試一次吧!)</span>`;

      }



      // 自動將視圖平滑捲動到認證卡頂部

      const modal = document.getElementById('result-modal');

      modal.scrollTo({ top: 0, behavior: 'smooth' });

    }



    function downloadCertificate() {

      const element = document.getElementById('certificate-card');

      const name = document.getElementById('cert-name').innerText || 'Student';

      const btn = document.getElementById('download-cert-btn');

      const iconEl = document.getElementById('download-btn-icon');

      const textEl = document.getElementById('download-btn-text');



      if (cachedCertDataUrl) {

        triggerDownload(cachedCertDataUrl, name);

        return;

      }



      // UI Loading Feedback (Solves Cloudflare / Mobile waiting anxiety)

      btn.disabled = true;

      btn.classList.add('opacity-75', 'cursor-wait');

      iconEl.innerHTML = `<svg class="animate-spin h-4 w-4 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>`;

      textEl.innerText = "⏳ 正在生成認證卡圖片 (約需 1~2 秒)...";



      html2canvas(element, {

        scale: 2,

        useCORS: true,

        allowTaint: true,

        backgroundColor: '#fffdf7',

        logging: false,

        imageTimeout: 1200 // 避免因外部字型或網路延遲而無限等待

      }).then(canvas => {

        const dataUrl = canvas.toDataURL('image/png');

        cachedCertDataUrl = dataUrl;



        // 1. 自動觸發下載 (支援直接下載之瀏覽器)

        triggerDownload(dataUrl, name);



        // 2. 將高解析圖直接呈現在網頁預覽區(解決 Google Sites iframe 沙箱與行動裝置阻擋下載限制)

        const previewWrap = document.getElementById('cert-image-preview-wrapper');

        const previewImg = document.getElementById('cert-image-preview');

        if (previewImg && previewWrap) {

          previewImg.src = dataUrl;

          previewWrap.classList.remove('hidden');

          previewWrap.scrollIntoView({ behavior: 'smooth', block: 'nearest' });

        }



        // 3. 恢復按鈕狀態

        btn.disabled = false;

        btn.classList.remove('opacity-75', 'cursor-wait');

        iconEl.innerText = "✅";

        textEl.innerText = "重新下載成果認證卡 (PNG)";

      }).catch(err => {

        console.error("生成認證卡失敗:", err);

        btn.disabled = false;

        btn.classList.remove('opacity-75', 'cursor-wait');

        iconEl.innerText = "📥";

        textEl.innerText = "重試生成認證卡 (PNG)";

        showCustomAlert("生成失敗", "抱歉,在目前瀏覽器環境生成圖片時發生問題。您可以使用螢幕截圖或點擊下方按鈕在新分頁開啟!", "⚠️");

      });

    }



    function triggerDownload(dataUrl, name) {

      try {

        const link = document.createElement('a');

        link.download = `Bruno_Mars_Count_On_Me_認證卡_${name}.png`;

        link.href = dataUrl;

        document.body.appendChild(link);

        link.click();

        document.body.removeChild(link);

      } catch (e) {

        console.warn("沙箱 iframe 限制了直接下載,已備援展示預覽圖:", e);

      }

    }



    function openCertInNewTab() {

      if (!cachedCertDataUrl) return;

      const newWin = window.open('');

      if (newWin) {

        newWin.document.write(`<html><head><title>Bruno Mars - Count on Me 認證卡預覽</title></head><body style="margin:0;background:#1e293b;display:flex;justify-content:center;align-items:center;min-height:100vh;"><img src="${cachedCertDataUrl}" style="max-width:95vw;max-height:95vh;box-shadow:0 10px 25px rgba(0,0,0,0.5);border-radius:16px;"/></body></html>
已複製