GeminiAI_Adventure / index.html
Lashtw's picture
Update index.html
ab00528 verified
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Google AI 教育探險</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.min.js"></script>
<!-- NEW: Firebase SDKs -->
<script type="module">
// 載入 Firebase SDK
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, signInWithCustomToken } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, setDoc, doc, collection, getDocs, query, where, serverTimestamp, setLogLevel, Timestamp, addDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// 將 SDK 功能掛載到 window 物件,讓下方的非模組 <script> 可以存取
window.firebaseSDK = {
initializeApp,
getAuth, signInAnonymously, signInWithCustomToken,
getFirestore, setDoc, doc, collection, getDocs, query, where, serverTimestamp, setLogLevel, Timestamp, addDoc
};
</script>
<style>
body {
font-family: 'Noto Sans TC', sans-serif;
background-color: #f8f9fa;
}
.game-container {
max-width: 800px;
margin: 2rem auto;
background: white;
border-radius: 1.5rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.game-screen {
display: none;
padding: 2.5rem;
animation: fadeIn 0.5s ease-in-out;
position: relative; /* 新增:為了讓愛心定位 */
}
.game-screen.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.pixel-art-explorer {
width: 80px;
height: 80px;
background-image: url('https://placehold.co/80x80/60a5fa/ffffff?text=Explorer');
image-rendering: pixelated;
border-radius: 50%;
border: 4px solid #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 9999px;
font-weight: bold;
transition: all 0.3s ease;
cursor: pointer;
border: none;
}
.btn-primary {
background-color: #4285F4;
color: white;
}
.btn-primary:hover {
background-color: #357ae8;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(66, 133, 244, 0.4);
}
/* Drag and Drop Styles */
.drop-zone {
border: 2px dashed #d1d5db;
border-radius: 0.75rem;
padding: 1rem;
min-height: 150px;
transition: background-color 0.3s;
}
.drop-zone.over {
background-color: #e0f2fe;
border-color: #3b82f6;
}
.draggable {
cursor: grab;
user-select: none;
transition: transform 0.2s, box-shadow 0.2s;
}
.draggable.dragging {
opacity: 0.5;
}
.draggable:active {
cursor: grabbing;
transform: scale(1.05);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
/* Level 4 Bubble Styles */
.bubble {
position: absolute;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
cursor: pointer;
transition: transform 0.3s ease, width 0.3s ease, height 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.bubble:hover {
transform: scale(1.1);
}
.plus-text-container {
font-size: 5rem;
font-weight: bold;
color: #e0e0e0;
position: relative;
}
.plus-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background: linear-gradient(90deg, #4285F4, #34A853, #FBBC05, #EA4335);
color: transparent;
-webkit-background-clip: text;
background-clip: text;
transition: width 0.5s ease;
white-space: nowrap;
overflow: hidden;
}
/* NEW: Level 5 Right Click Zone */
#right-click-zone {
border: 2px dashed #d1d5db;
border-radius: 0.75rem;
padding: 2rem;
min-height: 200px;
display: flex;
flex-direction: column; /* NEW */
align-items: center;
justify-content: center;
color: #9ca3af;
font-size: 1.125rem;
transition: all 0.3s;
position: relative; /* For counter */
}
#right-click-zone.success {
border-color: #22c55e;
border-style: solid;
color: #16a34a;
}
#right-click-counter {
font-size: 2rem;
font-weight: bold;
margin-top: 1rem;
color: #3b82f6; /* blue-500 */
}
#right-click-zone.success #right-click-counter {
color: #16a34a; /* green-700 */
}
/* NEW: Level 5 Paste Zone */
#paste-zone {
border: 2px dashed #d1d5db;
border-radius: 0.75rem;
padding: 2rem;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: #9ca3af;
font-size: 1.125rem;
transition: all 0.3s;
}
#paste-zone:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
#paste-zone.success {
border-color: #22c55e;
border-style: solid;
color: #16a34a;
}
#paste-zone img {
max-height: 200px;
max-width: 100%;
object-fit: contain;
border-radius: 0.5rem;
}
/* Level 7 Connect Dots Styles */
#connect-game-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.connect-item {
z-index: 1;
position: relative;
cursor: pointer;
transition: background-color 0.3s;
}
.connect-item.selected {
background-color: #fef08a; /* yellow-200 */
}
/* New Styles for Level 7 Path Game */
.path-grid-container {
display: grid;
gap: 2px;
background-color: #9ca3af; /* gray-400 */
border: 2px solid #9ca3af;
}
.path-cell {
background-color: #f1f5f9; /* slate-100 */
cursor: pointer;
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
}
.path-cell svg {
width: 100%;
height: 100%;
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
stroke-linecap: round;
stroke-linejoin: round;
}
.connect-col-item {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 0.75rem;
border: 2px solid #d1d5db;
border-radius: 0.5rem;
background-color: white;
flex-grow: 1;
}
.connect-col-item.path-correct {
border-color: #22c55e; /* green-500 */
background-color: #dcfce7; /* green-100 */
}
/* Level 8: Hidden Object Styles */
#icon-grid-8-container { /* Added container */
position: relative; /* Needed for absolute positioning of icons */
/* Removed min-height, will be set by JS */
background-color: #f1f5f9; /* slate-100 */
overflow: hidden; /* Keep icons inside */
border-radius: 0.5rem;
}
.icon-item {
position: absolute;
width: calc(100% / 12 - 4px);
aspect-ratio: 1 / 1;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, background-color 0.3s ease; /* Added transform */
border-radius: 0.375rem;
border: 1px solid transparent;
background-color: transparent;
padding: 1px;
object-fit: contain;
will-change: transform;
}
.icon-item:hover {
transform: scale(1.15);
background-color: rgba(255, 255, 255, 0.6);
z-index: 10;
}
.icon-item.hint-flash {
animation: flash 0.5s 6; /* Flash for 3 seconds */
}
/* NEW: Shrunk state for bonus round */
.icon-item.shrunk {
transform: scale(0.5);
border: 1px solid #cbd5e1; /* slate-300 */
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.icon-item.shrunk:hover {
transform: scale(0.9); /* Slightly larger hover for small icons */
z-index: 10;
background-color: rgba(255, 255, 255, 0.8);
}
@keyframes flash {
0%, 100% { box-shadow: 0 0 10px 3px #FBBC05; border-color: #FBBC05; background-color: rgba(251, 188, 5, 0.2); }
50% { box-shadow: none; border-color: transparent; background-color: transparent; }
}
#hint-btn-8:disabled {
background-color: #d1d5db; /* gray-300 */
color: #6b7280; /* gray-500 */
cursor: not-allowed;
}
/* Bonus phase counters */
.bonus-counter {
color: #db2777; /* Pink color for emphasis */
}
/* Emergency Modal Flash */
.emergency-flash {
animation: emergencyFlash 0.6s infinite alternate;
}
/* NEW: Success Flash for Level 4 */
.success-flash {
animation: successFlash 0.8s infinite alternate;
}
@keyframes emergencyFlash {
from { border-color: #ef4444; box-shadow: 0 0 15px 5px rgba(239, 68, 68, 0.7); }
to { border-color: transparent; box-shadow: none; }
}
/* NEW */
@keyframes successFlash {
from { border-color: #22c55e; box-shadow: 0 0 15px 5px rgba(34, 197, 94, 0.7); }
to { border-color: transparent; box-shadow: none; }
}
/* NEW: Level 9 Poll Styles */
.poll-card {
border: 2px solid #e5e7eb; /* gray-200 */
border-radius: 1rem;
padding: 1.5rem;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.poll-card img {
width: 100%;
height: 450px; /* Fixed height */
object-fit: contain; /* 改成 contain 來完整顯示圖片 */
background-color: #f9fafb; /* gray-50,為圖片加上底色 */
border-radius: 0.75rem;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.poll-card img:hover {
transform: scale(1.03);
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
}
.floating-heart {
position: absolute;
font-size: 2rem;
color: #ef4444; /* red-500 */
opacity: 0.8;
transition: transform 1.5s ease-out, opacity 1.5s ease-out;
pointer-events: none;
z-index: 100;
user-select: none;
}
/* NEW: Level 10 Drag & Drop */
.l10-card-container {
min-height: 180px; /* Height for the card */
padding: 1rem;
}
.l10-card {
background-color: white;
border: 2px solid #cbd5e1; /* slate-300 */
border-radius: 0.75rem;
padding: 1.5rem;
text-align: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
cursor: grab;
user-select: none;
transition: transform 0.2s, box-shadow 0.2s;
max-width: 500px;
width: 100%;
}
.l10-card.dragging {
opacity: 0.5;
background-color: #f8fafc; /* slate-50 */
}
.l10-card:active {
cursor: grabbing;
transform: scale(1.03);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.l10-drop-zone {
border: 3px dashed #d1d5db; /* gray-300 */
border-radius: 1rem;
padding: 2rem 1rem;
transition: all 0.3s ease;
text-align: center;
font-size: 1.25rem;
font-weight: bold;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.l10-drop-zone.over {
transform: scale(1.03);
}
#l10-red-zone.over { border-color: #ef4444; background-color: #fee2e2; }
#l10-yellow-zone.over { border-color: #f59e0b; background-color: #fef9c3; }
#l10-green-zone.over { border-color: #22c55e; background-color: #dcfce7; }
.l10-drop-zone.flash-red {
animation: flash-red-border 0.7s 2;
}
@keyframes flash-red-border {
0%, 100% { border-color: #ef4444; background-color: #fee2e2; }
50% { border-color: #d1d5db; background-color: white; }
}
/* NEW: Footer Credit */
.footer-credit {
position: fixed;
bottom: 8px;
right: 12px;
font-size: 0.75rem; /* 12px */
color: #9ca3af; /* gray-400 */
z-index: 1000;
}
/* NEW: Progress Bar Styles */
.progress-bar-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
margin: 1rem 2rem 0;
position: relative;
}
.progress-bar-line {
position: absolute;
left: 1.5rem; /* Start after first node */
right: 1.5rem; /* End before last node */
top: 50%;
height: 4px;
background-color: #e5e7eb; /* gray-200 */
z-index: 0;
transform: translateY(-50%);
}
.progress-node {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #d1d5db; /* gray-300 */
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
z-index: 1;
border: 2px solid white;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
transition: background-color 0.4s ease;
}
.progress-node.completed {
background-color: #4285F4;
}
/* NEW: Replay Button Styles */
#replay-levels .btn {
padding: 0.5rem; /* Tighter padding for longer text */
font-size: 0.75rem; /* 12px, Smaller text */
line-height: 1.2; /* Better than 1 */
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
text-align: center; /* Add for good measure */
}
#replay-levels .bg-yellow-400 {
color: #78350f; /* yellow-900 */
}
#replay-levels .bg-yellow-400:hover {
background-color: #f59e0b; /* yellow-500 */
}
/* NEW: Score Display */
.score-display {
text-align: center;
font-size: 1.25rem; /* 20px */
/* ... existing code ... -->
</style>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="game-container w-full">
<!-- NEW: Progress Bar & Score -->
<div class="score-display">
總分 (Total Score): <span id="score-value">0</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-line"></div>
<div id="progress-node-1" class="progress-node">1</div>
<div id="progress-node-2" class="progress-node">2</div>
<div id="progress-node-3" class="progress-node">3</div>
<div id="progress-node-4" class="progress-node">4</div>
<div id="progress-node-5" class="progress-node">5</div>
<div id="progress-node-6" class="progress-node">6</div>
<div id="progress-node-7" class="progress-node">7</div>
<div id="progress-node-8" class="progress-node">8</div>
<div id="progress-node-9" class="progress-node">9</div>
<div id="progress-node-10" class="progress-node">10</div>
</div>
<!-- END: Progress Bar & Score -->
<div id="screen-0" class="game-screen active text-center">
<h1 class="text-4xl font-bold text-gray-800 mb-4 pt-10">Google AI 教育探險</h1>
<p class="text-lg text-gray-600 mb-8">歡迎來到Google AI教育宇宙!您,一位充滿熱情的教育探險家,即將踏上一段未知的旅程。準備好解開AI的秘密,並為您的課堂帶回最強大的寶藏了嗎?</p>
<button onclick="startGame()" class="btn btn-primary text-xl">讓我們開始探險吧!</button>
</div>
<div id="screen-1" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第一關:建立你的探險隊</h2>
<p class="text-center text-gray-600 mb-8">在Google的教育宇宙中,每位探險家都能找到自己的定位。請將下方的徽章拖到正確的分類中!</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="font-bold text-xl mb-3 text-center text-blue-600">個人成長</h3>
<div id="personal-growth" class="drop-zone bg-blue-50 h-full flex flex-col items-center justify-center space-y-2"></div>
</div>
<div>
<h3 class="font-bold text-xl mb-3 text-center text-green-600">團隊榮譽</h3>
<div id="team-honor" class="drop-zone bg-green-50 h-full flex flex-col items-center justify-center space-y-2"></div>
</div>
</div>
<div id="draggable-items" class="flex flex-wrap justify-center gap-4 mt-12">
<div id="gct" draggable="true" class="draggable p-3 bg-white border-2 border-blue-400 text-blue-800 font-semibold rounded-lg shadow-sm" data-type="personal">講師 (GCT)</div>
<div id="gci" draggable="true" class="draggable p-3 bg-white border-2 border-blue-400 text-blue-800 font-semibold rounded-lg shadow-sm" data-type="personal">創意家 (GCI)</div>
<div id="gcc" draggable="true" class="draggable p-3 bg-white border-2 border-blue-400 text-blue-800 font-semibold rounded-lg shadow-sm" data-type="personal">教練 (GCC)</div>
<div id="grc" draggable="true" class="draggable p-3 bg-white border-2 border-green-400 text-green-800 font-semibold rounded-lg shadow-sm" data-type="team">認證學校 (GRC)</div>
</div>
<div id="feedback-1" class="text-center font-bold mt-6 h-6"></div>
</div>
<div id="screen-2" class="game-screen text-center">
<h2 class="text-3xl font-bold mb-2">第二關:接收秘密通訊</h2>
<p class="text-gray-600 mb-6">探險家,你截獲了一段神秘的訊息,似乎是一個秘密組織的通關密語...</p>
<div class="bg-yellow-100 border-l-4 border-yellow-400 text-yellow-800 p-6 rounded-lg text-left max-w-2xl mx-auto">
<p class="mb-4">你知道有個叫「<strong>GEG Taiwan</strong>」の酷東西嗎?聽起來像個秘密特務組織,其實是 Google 為老師們成立的教育家社群啦!</p>
<p class="mb-4">這群老師的通關密語超親切,不是什麼複雜的口號,而是最台的問候:「<strong>呷飽沒?</strong></p>
<p>沒錯,他們相信,無論是要用多厲害的 Google 工具翻轉教育,都得先填飽肚子!畢竟,老師和學生,「呷飽」才有力氣改變世界嘛!</p>
</div>
<p class="mt-8 mb-4 font-semibold text-lg">立刻掃描下方的通訊信標(QR Code),加入這群熱血的夥伴吧!</p>
<div class="flex justify-center mb-8">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://www.facebook.com/groups/2294169410829101" alt="GEG Taiwan QR Code">
</div>
<button onclick="completeLevel2()" class="btn btn-primary">我已加入!前往下一關</button>
</div>
<div id="screen-3" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第三關:鑑定探險裝備</h2>
<p class="text-center text-gray-600 mb-8">身為一位專業的探險家,你必須辨別出哪一項才是 Chromebook 真正擁有的「神功能」組合?</p>
<div id="quiz-3-options" class="space-y-4 max-w-xl mx-auto">
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="chromebook-features" value="A" class="mr-3"> (A) 會自動寫作業、能當暖暖包、還內建隱形功能,上課偷打電動老師都看不到。
</label>
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="chromebook-features" value="B" class="mr-3"> (B) 尊爵不凡的信仰標誌、價格跟傳家寶一樣貴、機身輕到可以當飛盤玩。
</label>
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="chromebook-features" value="C" class="mr-3"> (C) 超快開機、續電力強、不會中毒、不怕你摔,1.2公尺摔不壞。
</label>
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="chromebook-features" value="D" class="mr-3"> (D) 內建AI管家會幫你泡咖啡、螢幕可以當鏡子、作業系統每天都要拜拜才能順暢運行。
</label>
</div>
<div class="text-center mt-8">
<button onclick="checkAnswer(3, 'C')" class="btn btn-primary">確認鑑定</button>
</div>
<div id="feedback-3" class="text-center font-bold mt-6 h-6"></div>
</div>
<div id="screen-4" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第四關:注入能量</h2>
<p class="text-center text-gray-600 mb-6">前方便是能量閘門,注入正確的能量才能通過。通關密語是Google最新推出的機型!</p>
<div class="flex justify-center items-center my-8">
<div class="plus-text-container">
<span>Chromebook </span>
<span class="relative inline-block">
<span class="text-gray-300">Plus</span>
<span id="plus-fill" class="plus-fill absolute top-0 left-0">Plus</span>
</span>
</div>
</div>
<div id="bubble-container" class="relative h-64 md:h-80 w-full bg-slate-100 rounded-lg overflow-hidden">
</div>
<div class="text-center mt-4">
<button onclick="resetBubbles()" class="bg-gray-500 text-white py-2 px-4 rounded-full hover:bg-gray-600 transition">重置泡泡大小</button>
</div>
<div id="feedback-4" class="text-center font-bold mt-6 h-6"></div>
</div>
<div id="screen-5" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第五關 (1/2):整備雙重技能</h2> <!-- UPDATED -->
<p class="text-center text-gray-600 mb-6">探險開始前,我們先來實戰演練!請完成下列兩項探險家基本技能。</p> <!-- UPDATED -->
<div class="bg-gray-100 p-4 rounded-lg text-center max-w-xl mx-auto mb-6">
<p class="font-semibold mb-2">快捷鍵提示:</p>
<p class="mb-2"><strong>右鍵 (或兩指輕觸)</strong>:開啟情境選單</p>
<p><code class="bg-gray-300 px-2 py-1 rounded">Ctrl</code> + <code class="bg-gray-300 px-2 py-1 rounded">☐||</code> (全螢幕) / <code class="bg-gray-300 px-2 py-1 rounded">Ctrl</code> + <code class="bg-gray-300 px-2 py-1 rounded">Shift</code> + <code class="bg-gray-300 px-2 py-1 rounded">☐||</code> (自訂範圍)</p>
</div>
<!-- NEW: 2-column layout -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
<!-- NEW: Task 1 -->
<div class="flex flex-col">
<p class="text-center text-lg text-gray-700 mb-4"><strong>技能 1:</strong> 在下方方框內點擊 <strong>右鍵 (或兩指輕觸)</strong> 3 次。</p>
<div id="right-click-zone" class="flex-1">
點擊右鍵
<div id="right-click-counter">0 / 3</div>
</div>
</div>
<!-- Task 2 (Existing) -->
<div class="flex flex-col">
<p class="text-center text-lg text-gray-700 mb-4"><strong>技能 2:</strong> 成功截圖後,點擊下方方框並按下 <strong>Ctrl + V</strong> 貼上圖片。</p>
<div id="paste-zone" contenteditable="true" class="mx-auto w-full flex-1"> <!-- UPDATED: w-full, mx-auto, flex-1 -->
點擊此處,然後按下 Ctrl + V
</div>
</div>
</div>
<div id="feedback-5" class="text-center font-bold mt-6 h-6"></div>
</div>
<div id="screen-5-2" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第五關 (2/2):整備技能 - 探險家報到</h2>
<p class="text-center text-gray-600 mb-8">太棒了!接下來,請練習打字並完成你的探險家報到程序。這也是為了等等的「AI Prompt 互動」做暖身喔!</p>
<div class="max-w-lg mx-auto space-y-4">
<div class="flex flex-col sm:flex-row items-center gap-2 text-lg">
<span>我是來自</span>
<input id="l5-school" type="text" class="flex-1 p-2 border-2 border-gray-300 rounded-md focus:border-blue-500 focus:ring-blue-500 w-full sm:w-auto" placeholder="學校">
<span>(學校) 的</span>
</div>
<div class="flex flex-col sm:flex-row items-center gap-2 text-lg">
<input id="l5-name" type="text" class="w-full sm:w-32 p-2 border-2 border-gray-300 rounded-md focus:border-blue-500 focus:ring-blue-500" placeholder="姓名">
<span>(姓名) 老師,</span>
</div>
<div class="flex flex-col sm:flex-row items-center gap-2 text-lg">
<span>在學校擔任</span>
<input id="l5-job" type="text" class="flex-1 p-2 border-2 border-gray-300 rounded-md focus:border-blue-500 focus:ring-blue-500 w-full sm:w-auto" placeholder="職務">
<span>(職務) 工作。</span>
</div>
</div>
<div class="text-center mt-8">
<button onclick="checkLevel5_2()" class="btn btn-primary">完成報到</button>
</div>
<div id="feedback-5-2" class="text-center font-bold mt-6 h-6"></div>
</div>
<!-- --- (FIXED BLOCK: Level 6) --- -->
<div id="screen-6" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第六關:洞察AI的蹤跡</h2>
<p class="text-center text-gray-600 mb-8">AI早已融入我們的生活,下面哪一個常見的生活情境,其實「沒有」用到 AI 技術呢?</p>
<div id="quiz-6-options" class="space-y-4 max-w-2xl mx-auto">
<!-- NEW Option A (Plausible AI) -->
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="ai-myth" value="A" class="mr-3"> A. 網頁翻譯工具,能即時將日文新聞翻譯成中文。
</label>
<!-- NEW Option B (Plausible AI) -->
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="ai-myth" value="B" class="mr-3"> B. 手機相簿自動將照片分類為「寵物」、「食物」、「風景」。
</label>
<!-- FIXED Option C (Was outside) -->
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="ai-myth" value="C" class="mr-3"> C. YouTube 自動推薦下一部你可能會喜歡的搞笑影片。
</label>
<!-- FIXED Option D (Was outside) -->
<label class="block p-4 border-2 border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer has-[:checked]:bg-blue-50 has-[:checked]:border-blue-400">
<input type="radio" name="ai-myth" value="D" class="mr-3"> D. 用微波爐加熱隔夜的便當,設定「中火、3分鐘」。
</label>
</div>
<!-- FIXED This div was split into two and contained wrong buttons -->
<div class="text-center mt-8">
<button onclick="showHint(6)" class="bg-yellow-400 text-yellow-900 font-bold py-2 px-4 rounded-full hover:bg-yellow-500 transition mr-4">需要一點線索嗎?</button>
<button onclick="checkAnswer(6, 'D')" class="btn btn-primary">確認答案</button>
</div>
<div id="feedback-6" class="text-center font-bold mt-6 h-6"></div>
<div id="hint-6" class="text-center text-blue-600 bg-blue-100 p-3 rounded-lg mt-4 max-w-xl mx-auto hidden"></div>
</div>
<!-- --- (END OF FIXED BLOCK) --- -->
<div id="screen-7" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第七關:解碼AI神諭</h2>
<p id="level-7-instructions" class="text-center text-gray-600 mb-4 h-12">請將指定的詞彙與解釋正確配對。</p>
<div id="matched-pairs-display" class="mb-4 p-3 bg-gray-100 rounded-lg min-h-[50px] text-center space-x-2 space-y-2">
</div>
<div id="connect-game-wrapper" class="relative flex justify-center items-stretch gap-1 md:gap-2 text-xs md:text-sm">
<div id="keywords-col" class="flex flex-col gap-2 w-1/4">
<div id="term-ai" class="connect-col-item">人工智慧 (AI)</div>
<div id="term-ml" class="connect-col-item">機器學習 (ML)</div>
<div id="term-genai" class="connect-col-item">生成式AI</div>
<div id="term-llm" class="connect-col-item">大型語言模型 (LLM)</div>
<div id="term-chatbot" class="connect-col-item">聊天機器人</div>
</div>
<div id="path-grid-container" class="path-grid-container">
</div>
<div id="definitions-col" class="flex flex-col gap-2 w-1/4">
<div id="def-genai" class="connect-col-item" data-def-id="def-genai">能創造新東西的AI</div>
<div id="def-chatbot" class="connect-col-item" data-def-id="def-chatbot">能與你對話的機器人</div>
<div id="def-ai" class="connect-col-item" data-def-id="def-ai">機器能像人一樣聰明</div>
<div id="def-llm" class="connect-col-item" data-def-id="def-llm">語言大師
</div>
<div id="def-ml" class="connect-col-item" data-def-id="def-ml">會自己學習的機器</div>
</div>
<svg id="path-svg-overlay" class="absolute top-0 left-0 w-full h-full pointer-events-none" style="display: none;"></svg>
</div>
<div class="text-center mt-8">
<button id="check-path-btn" onclick="checkRotatedPathConnection()" class="btn btn-primary" style="display: none;">確認路徑</button>
</div>
<div id="feedback-7" class="text-center font-bold mt-6 h-6"></div>
</div>
<div id="screen-8" class="game-screen">
<h2 id="level-8-title" class="text-3xl font-bold text-center mb-2">第八關:發掘 Workspace 新星</h2>
<p id="level-8-subtitle" class="text-center text-gray-600 mb-2">探險家,Google 的工具宇宙中誕生了一顆新星——Google Vids!</p>
<p id="level-8-warning" class="text-center text-lg font-bold text-red-600 mb-4">(很重要,考試會考!😉)</p>
<p id="level-8-main-instruction" class="text-center text-gray-600 mb-6">它就隱藏在我們熟悉的工具夥伴中。快睜大眼睛,在下方的工具海中找出 <strong>3</strong> 個 [Google Vids] 的 icon 吧!</p>
<div class="flex flex-col sm:flex-row justify-between items-center mb-4 p-3 bg-gray-100 rounded-lg gap-2">
<div class="text-lg font-bold">計時器: <span id="timer-8">00:00</span></div>
<div id="vids-counter-container" class="text-lg font-bold">已找到 Vids: <span id="found-counter-8">0</span> / 3</div>
<div id="bonus-counters-container" class="text-lg font-bold hidden flex space-x-4">
<span class="bonus-counter">Gemini: <span id="found-gemini-8">0</span> / 5</span>
<span class="bonus-counter">NotebookLM: <span id="found-notebooklm-8">0</span> / 5</span>
</div>
<button id="hint-btn-8" onclick="useHintLevel8()" class="bg-yellow-400 text-yellow-900 font-bold py-2 px-4 rounded-full hover:bg-yellow-500 transition">
提示按鈕 (剩下 1 次)
</button>
</div>
<div id="icon-grid-8-container" class="p-2 rounded-lg relative">
<!-- Icons will be generated by JS -->
</div>
</div>
<!-- Screen 9: NEW AI Poll -->
<div id="screen-9" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">第九關:AI男神人氣投票!</h2>
<p class="text-center text-gray-600 mb-8">探險家,在了解了兩位AI男神的魅力之後,是時候表達你的心意了!為你喜歡的AI男神點讚,讓他獲得更多人氣吧!</p>
<!-- NEW: Poll Stats -->
<div class="flex justify-around items-center text-center mb-6 p-4 bg-gray-100 rounded-lg">
<div class="text-2xl font-bold text-red-600">
倒數計時: <span id="poll-timer">20</span>s
</div>
<div class="text-2xl font-bold text-blue-600">
總點擊數: <span id="poll-clicks">0</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-stretch">
<!-- Gemini Card -->
<div class="poll-card bg-blue-50 border-blue-200">
<div>
<img id="gemini-img" src="https://imgtolinkx.com/i/UjO8oPQV" alt="Gemini 圖片" class="mb-4">
<h3 class="text-2xl font-bold text-blue-800 mb-2">Gemini</h3>
<p class="text-gray-700 mb-4">知識淵博、創意無限的智慧夥伴,擅長多模態溝通與複雜推理。</p>
</div>
<div id="gemini-likes-display" class="text-2xl font-bold text-red-500">❤️ Gemini 人氣: 0</div>
</div>
<!-- NotebookLM Card -->
<div class="poll-card bg-purple-50 border-purple-200">
<div>
<img id="notebooklm-img" src="https://imgtolinkx.com/i/aNzFNvv4" alt="NotebookLM 圖片" class="mb-4">
<h3 class="text-2xl font-bold text-purple-800 mb-2">NotebookLM</h3>
<p class="text-gray-700 mb-4">你的專屬研究助理,精通文件閱讀與重點摘要,是學術與工作的好幫手。</p>
</div>
<div id="notebooklm-likes-display" class="text-2xl font-bold text-red-500">❤️ NotebookLM 人氣: 0</div>
</div>
</div>
<p class="text-center text-gray-500 mt-6 text-lg">點擊圖片為你的男神應援!</p>
</div>
<!-- Screen 10: NEW AI Ethics -->
<div id="screen-10" class="game-screen">
<h2 class="text-3xl font-bold text-center mb-2">最終挑戰:AI 倫理守門人</h2>
<p class="text-center text-gray-600 mb-6">恭喜你來到最後一關!身為一位專業的教育探險家,你現在的任務是審核下列教學情境。請依據AI倫理「紅綠燈」原則,將這些「教案申請卡」拖曳到正確的區域!</p>
<div class="text-center text-xl font-bold text-gray-700 mb-6">
已審核:<span id="l10-progress-counter">0</span> / <span id="l10-progress-total">6</span>
</div>
<!-- Card Container -->
<div id="l10-card-container" class="l10-card-container flex items-center justify-center">
<!-- Card will be injected by JS -->
</div>
<!-- Drop Zones -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-8">
<div id="l10-red-zone" class="l10-drop-zone text-red-600 bg-red-50 border-red-300">
🟥 紅燈區 (停止使用)
</div>
<div id="l10-yellow-zone" class="l10-drop-zone text-yellow-600 bg-yellow-50 border-yellow-300">
🟨 黃燈區 (謹慎前行)
</div>
<div id="l10-green-zone" class="l10-drop-zone text-green-600 bg-green-50 border-green-300">
🟩 綠燈區 (放心探索)
</div>
</div>
</div>
<!-- Screen 11: End (Old Screen 10) -->
<div id="screen-11" class="game-screen text-center">
<h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-green-500 mb-4">恭喜!</h1>
<p class="text-lg text-gray-600 mb-8">您已成功完成 Google AI 教育探險!您現在已經掌握了關鍵的 AI 知識,準備好在您的課堂上發光發熱了!</p>
<!-- NEW: Final Score -->
<p id="final-score-display" class="text-2xl font-bold text-gray-800 mb-8">你的總分是:0 分!</p>
<!-- NEW: Replay Section -->
<hr class="my-6">
<h3 class="text-xl font-bold text-gray-700 mb-2">關卡挑戰室</h3>
<p class="text-gray-600 mb-4">點擊下方按鈕可重玩關卡。<strong class="text-yellow-700">黃色按鈕</strong>代表該關卡未達滿分,回去挑戰並「一次通關」可獲得 5 分獎勵 (僅限一次)!</p>
<div id="replay-levels" class="grid grid-cols-5 gap-2 max-w-lg mx-auto mb-8">
<!-- JS will generate buttons here -->
</div>
<!-- END: Replay Section -->
<button onclick="restartGame()" class="btn btn-primary">再次探險</button>
</div>
</div>
<!-- Level 8 Modal -->
<div id="level-8-modal-container" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 hidden z-50">
<div id="level-8-modal-content" class="bg-white rounded-lg shadow-xl p-8 text-center max-w-md w-full border-4 border-transparent transition-all duration-300">
<h3 id="level-8-modal-title" class="text-2xl font-bold mb-4">太棒了!</h3>
<p id="level-8-feedback" class="text-lg text-gray-700 mb-6"></p>
<button id="level-8-modal-button" class="btn btn-primary w-full"></button>
<button id="level-8-modal-button-skip" class="btn bg-gray-300 text-gray-700 hover:bg-gray-400 mt-2 w-full" style="display: none;">以後再說</button>
</div>
</div>
<!-- New Level 10 Modal -->
<div id="level-10-modal-container" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 hidden z-50">
<div id="level-10-modal-content" class="bg-white rounded-lg shadow-xl p-8 text-center max-w-md w-full border-4 border-red-500 transition-all duration-300">
<h3 id="level-10-modal-title" class="text-2xl font-bold mb-4 text-red-600">哎呀,不對喔!</h3>
<p id="level-10-feedback" class="text-lg text-gray-700 mb-6"></p>
<button id="level-10-modal-button" class="btn btn-primary" onclick="hideLevel10Modal(false)">再試一次</button>
</div>
</div>
<!-- NEW: Footer Credit -->
<div class="footer-credit">
程式設計者:新竹縣精華國中 藍星宇 與 Gemini Canvas
</div>
<script>
let currentScreen = 0;
const totalScreens = 11; // UPDATED from 10 to 11
let correctSound, wrongSound, successSound;
let cheatCodeBuffer = '';
// --- NEW: Firebase Globals ---
let db, auth;
let userId = null; // Firebase Auth User ID
let playerIdentifier = ""; // L5-2 儲存的玩家名稱
let isFirebaseReady = false;
let fbSDK = null; // 用來存放 window.firebaseSDK
// --- END NEW ---
// --- NEW: Scoring and State ---
let totalScore = 0;
let levelScores = {}; // Stores the *best* score for each level (1-10)
let replayBonusAwarded = {}; // Stores if replay bonus was given (e.g., { 3: true })
let isReplaying = false; // Flag for replay mode
const MAX_SCORES = {
1: 100, 2: 100, 3: 100, 4: 100, 5: 100,
6: 100, 7: 100, 8: 100, 9: 100, 10: 120
}; // L7 = 5*20, L10 = 12*10
let level1WrongDrop = false; // L1
let level3Tries = 0; // L3
let level4ResetUsed = false; // L4
let level5Part1Score = 0; // L5
let level5RightClicks = 0; // L5 - NEW
let level5RightClickDone = false; // L5 - NEW
let level5PasteDone = false; // L5 - NEW
let level6Tries = 0; // L6
let level7Score = 0; // L7
let level7FirstTry = true; // L7
let level7CurrentStageTries = 0; // L7
let level8BonusTime = 0; // L8
let pollGameTimer = null; // L9
let pollCountdownInterval = null; // L9
let pollClickCount = 0; // L9
let level10Score = 0; // L10
let level10FirstTry = true; // L10
let level10CurrentCardTries = 0; // L10
// --- END: Scoring and State ---
// --- NEW: Image Preloader ---
function preloadImages() {
const imageUrls = [
'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://www.facebook.com/groups/2294169410829101',
// Level 8 Icons
'https://imgtolinkx.com/i/XLPlZVZw', // Vids
'https://imgtolinkx.com/i/ByK3AKuQ', // Gemini
'https://imgtolinkx.com/i/kZvAtWwv', // NotebookLM
'https://www.gstatic.com/images/branding/product/1x/gmail_2020q4_48dp.png',
'https://ssl.gstatic.com/images/branding/product/2x/drive_48dp.png',
'https://ssl.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_15_2x.png',
'https://ssl.gstatic.com/images/branding/product/2x/docs_48dp.png',
'https://ssl.gstatic.com/images/branding/product/2x/sheets_48dp.png',
'https://ssl.gstatic.com/images/branding/product/2x/keep_48dp.png',
'https://fonts.gstatic.com/s/i/productlogos/meet_2020q4/v1/web-96dp/logo_meet_2020q4_color_1x_web_96dp.png',
// Level 9 Icons
'https://imgtolinkx.com/i/UjO8oPQV', // Gemini Poll
'https://imgtolinkx.com/i/aNzFNvv4' // NotebookLM Poll
];
imageUrls.forEach(url => {
const img = new Image();
img.src = url;
img.onload = () => console.log(`Preloaded: ${url}`);
img.onerror = () => console.warn(`Failed to preload: ${url}`);
});
}
preloadImages(); // <--- NEW: 立即呼叫預載
// --- Cheat Codes ---
document.addEventListener('keydown', e => {
// NEW FIX: 僅將單一、可列印的字元添加到緩衝區
// 這能防止 "Shift", "Control", "Backspace" 等鍵污染密碼
if (e.key.length > 1) {
return; // 忽略 "Shift", "Control" 等非字元鍵
}
cheatCodeBuffer += e.key;
// 增加緩衝區長度以容納 scoreboard
if (cheatCodeBuffer.length > 10) {
cheatCodeBuffer = cheatCodeBuffer.substring(cheatCodeBuffer.length - 10);
}
// NEW: Scoreboard Trigger
// FIX: 轉換為小寫以進行不分大小寫的比對
if (cheatCodeBuffer.toLowerCase().endsWith('scoreboard')) {
console.log("Scoreboard activated!");
showScoreboard(); // 呼叫新函數
cheatCodeBuffer = ''; // Reset
}
// END NEW
if (cheatCodeBuffer.endsWith('101010')) { // Special check for 10
console.log(`Cheat code activated: Go to screen 10`);
goToScreen(10);
cheatCodeBuffer = ''; // Reset buffer
}
for (let i = 1; i <= totalScreens; i++) {
if (i === 10) continue; // Skip 10, has custom code
let repeatNum = (i === 11) ? 6 : 5; // 6 repeats for 11, 5 for others
// Use (i % 10) to get '1' from 11.
if (cheatCodeBuffer.endsWith(String(i % 10).repeat(repeatNum))) {
console.log(`Cheat code activated: Go to screen ${i}`);
goToScreen(i);
cheatCodeBuffer = ''; // Reset buffer
}
}
});
// --- Sound Engine ---
function setupSounds() {
if (typeof Tone !== 'undefined') {
if (Tone.context.state !== 'running') {
Tone.start();
}
correctSound = new Tone.Synth({
oscillator: { type: 'sine' },
envelope: { attack: 0.005, decay: 0.1, sustain: 0.3, release: 1 }
}).toDestination();
wrongSound = new Tone.Synth({
oscillator: { type: 'square' },
envelope: { attack: 0.01, decay: 0.2, sustain: 0.2, release: 0.2 }
}).toDestination();
successSound = new Tone.Synth({
envelope: { attack: 0.02, decay: 0.2, sustain: 0.5, release: 0.8 }
}).toDestination();
} else {
console.warn("Tone.js not loaded, sounds will be disabled.");
}
}
function playSound(type) {
if (!correctSound) return;
try {
if (type === 'correct') {
correctSound.triggerAttackRelease('C5', '8n');
} else if (type === 'wrong') {
wrongSound.triggerAttackRelease('A2', '8n');
} else if (type === 'success') {
successSound.triggerAttackRelease('C4', '8n', Tone.now());
successSound.triggerAttackRelease('E4', '8n', Tone.now() + 0.2);
successSound.triggerAttackRelease('G4', '8n', Tone.now() + 0.4);
}
} catch (e) {
console.error("Audio playback error:", e);
}
}
// --- NEW: Score & Progress Functions ---
/**
* Records the score for a level.
* Handles replay bonuses and stores only the best score.
*/
function recordLevelScore(levelNum, score, isFirstTrySuccess = false) {
levelNum = Math.floor(levelNum);
if (!levelNum || levelNum < 1 || levelNum > 10) {
console.error("Invalid levelNum:", levelNum);
return;
}
let oldScore = levelScores[levelNum] || 0;
let maxScore = MAX_SCORES[levelNum];
// 1. Award replay bonus?
// Only if: replaying, old score was bad, new score is "first try", bonus not already given
if (isReplaying && oldScore < maxScore && isFirstTrySuccess && !replayBonusAwarded[levelNum]) {
// Award 5 bonus points
replayBonusAwarded[levelNum] = true;
// We don't add to totalScore directly, updateTotalScore will handle it
}
// 2. Update the level's best score
if (score > oldScore) {
levelScores[levelNum] = score;
}
// 3. Update progress bar (always, even if score isn't better)
updateProgressBar(levelNum);
// 4. Recalculate and update total score display
updateTotalScore();
}
/**
* Calculates the total score from levelScores and bonuses, then updates the display.
*/
function updateTotalScore() {
let newTotal = 0;
// Sum all best level scores
for (let i = 1; i <= 10; i++) {
newTotal += (levelScores[i] || 0);
}
// Add any replay bonuses that were awarded
for (const level in replayBonusAwarded) {
if (replayBonusAwarded[level]) newTotal += 5;
}
totalScore = newTotal;
updateScoreDisplay();
}
function updateScoreDisplay() {
const scoreEl = document.getElementById('score-value');
if (scoreEl) scoreEl.textContent = totalScore;
}
function updateProgressBar(levelNum) {
if (levelNum < 1 || levelNum > 10) return;
const node = document.getElementById(`progress-node-${Math.floor(levelNum)}`); // Use floor for 5.2
if (node) {
node.classList.add('completed');
}
}
// --- END: Score & Progress Functions ---
// --- Game Flow ---
function goToScreen(screenNumber) {
// Stop activities if leaving specific screens
if (currentScreen === 8) {
clearInterval(level8Timer);
}
if (currentScreen === 4) {
clearInterval(bubbleGrowInterval);
}
if (currentScreen === 9) { // NEW: Cleanup for poll screen
stopPollGame();
}
// NEW: No cleanup needed for L10 yet
// Handle non-integer screen numbers for sub-screens
const screenId = `screen-${String(screenNumber).replace('.', '-')}`;
const currentScreenEl = document.getElementById(`screen-${String(currentScreen).replace('.', '-')}`);
if (currentScreenEl) {
currentScreenEl.classList.remove('active');
}
currentScreen = screenNumber;
const nextScreenEl = document.getElementById(screenId);
if (nextScreenEl) {
nextScreenEl.classList.add('active');
} else {
console.error(`Screen with id ${screenId} not found!`);
// Fallback to screen 0
document.getElementById('screen-0').classList.add('active');
currentScreen = 0;
}
if(currentScreen === 4) {
initBubbleGame();
}
if(currentScreen === 5) { // NEW
initLevel5();
}
if(currentScreen === 7) {
currentPathStage = 0;
initConnectGame();
}
if(currentScreen === 8) {
initLevel8Game();
}
if(currentScreen === 9) { // NEW: Init for poll screen
initPollGame();
}
if(currentScreen === 10) { // NEW: Init for L10
initLevel10();
}
if(currentScreen === 11) { // NEW: Show final score
isReplaying = false; // We have arrived at the end
updateTotalScore(); // Recalculate score
document.getElementById('final-score-display').textContent = `你的總分是:${totalScore} 分!`;
generateReplayButtons(); // NEW: Generate replay buttons
// --- NEW: Save score to Firebase ---
// 每當玩家到達結束畫面(無論是第一次或重玩),都儲存/更新分數
saveScoreToFirebase();
// --- END NEW ---
}
}
async function startGame() { // NEW: 設為 async 函數
setupSounds();
// NEW: Initialize Firebase
await initializeFirebase(); // NEW: 加入 await,強制等待 Firebase 連線完成
// END NEW
goToScreen(1);
}
<!-- --- NEW: Firebase Init Function ---
async function initializeFirebase() {
if (isFirebaseReady) return;
// 等待 SDK 模組載入完成
while (!window.firebaseSDK) {
await new Promise(resolve => setTimeout(resolve, 50));
}
fbSDK = window.firebaseSDK;
try {
// --- NEW: Correct Firebase Config Logic ---
// 檢查 __firebase_config 變數是否存在
if (typeof __firebase_config === 'undefined') {
console.warn("Firebase config (__firebase_config) not found. Scoreboard will not work.");
return;
}
const firebaseConfig = JSON.parse(__firebase_config);
// --- END NEW ---
if (!firebaseConfig.apiKey) {
console.warn("Firebase config is invalid. Scoreboard will not work.");
return;
}
const app = fbSDK.initializeApp(firebaseConfig);
db = fbSDK.getFirestore(app);
auth = fbSDK.getAuth(app);
fbSDK.setLogLevel('Debug'); // 方便除錯
// --- NEW: Correct Authentication Logic ---
// 檢查 __initial_auth_token 並登入
if (typeof __initial_auth_token !== 'undefined') {
console.log("Signing in with custom token...");
await fbSDK.signInWithCustomToken(auth, __initial_auth_token);
} else {
console.log("Signing in anonymously...");
await fbSDK.signInAnonymously(auth);
}
// --- END NEW ---
// 取得並儲存這個(匿名)使用者的唯一 ID
userId = auth.currentUser ? auth.currentUser.uid : crypto.randomUUID();
isFirebaseReady = true;
console.log("Firebase initialized, User ID:", userId);
} catch (error) {
console.error("Firebase initialization failed:", error);
}
}
// --- END NEW ---
// --- NEW: Firebase Save Score Function ---
async function saveScoreToFirebase() {
if (!isFirebaseReady || !userId || !fbSDK) {
console.warn("Firebase not ready or user not logged in. Cannot save score.");
return;
}
// 如果玩家跳過了 L5-2,提供一個預設名稱
const displayName = playerIdentifier || `探險家-${userId.substring(0, 4)}`;
try {
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
// --- FIX: 改為 addDoc 來儲存每一次的紀錄 ---
// 1. 取得 collection 的參照
const scoresCollectionRef = fbSDK.collection(db, `artifacts/${appId}/public/data/scores`);
// 2. 使用 addDoc 來新增文件,Firebase 會自動產生ID
// 這樣就能儲存每一次的遊玩紀錄,而不是覆蓋舊紀錄
await fbSDK.addDoc(scoresCollectionRef, {
displayName: displayName,
score: totalScore, // 全域變數中的最新分數
lastUpdated: fbSDK.serverTimestamp(), // 儲存伺服器時間
userId: userId // 儲存玩家ID以便追蹤
});
// --- END FIX ---
console.log("Score saved successfully to Firebase:", totalScore);
} catch (error) {
console.error("Error saving score to Firebase:", error);
}
}
// --- END NEW ---
function restartGame() {
location.reload();
}
// --- NEW: Replay Functions ---
function generateReplayButtons() {
const container = document.getElementById('replay-levels');
if (!container) return;
container.innerHTML = '';
// NEW: Level Names
const levelNames = [
"1. 探險隊", "2. 秘密通訊", "3. 鑑定裝備", "4. 注入能量", "5. 整備技能",
"6. 洞察AI", "7. 解碼神諭", "8. 發掘新星", "9. AI男神", "10. 倫理守門人"
];
for (let i = 1; i <= 10; i++) {
const btn = document.createElement('button');
btn.textContent = levelNames[i-1]; // NEW
btn.onclick = () => replayLevel(i);
let score = levelScores[i] || 0;
let max = MAX_SCORES[i];
// For level 10, max is 120, but 60 is also "complete"
let isFullScore = score >= max;
if (i === 10 && score >= 60 && score < 120) {
// Special case: Completed L10 but not bonus, still show yellow
isFullScore = false;
}
if (!isFullScore) {
btn.className = 'btn bg-yellow-400 text-yellow-900 hover:bg-yellow-500'; // Needs improvement
} else {
btn.className = 'btn btn-primary'; // Full score
}
container.appendChild(btn);
}
}
function replayLevel(levelNum) {
isReplaying = true;
// Reset level-specific "tries" or "state" variables
level1WrongDrop = false;
level3Tries = 0;
level4ResetUsed = false;
level5Part1Score = 0; // Reset for L5
level6Tries = 0;
level7Score = 0; // Reset for L7
level7FirstTry = true;
level7CurrentStageTries = 0;
// L8 and L9 state are reset by their init functions
level10Score = 0; // Reset for L10
level10FirstTry = true;
level10CurrentCardTries = 0;
goToScreen(levelNum);
}
// --- END Replay ---
function showFeedback(screenNum, message, isCorrect) {
const feedbackEl = document.getElementById(`feedback-${screenNum}`);
if (!feedbackEl) { // Check if feedback element exists
console.warn(`Feedback element for screen ${screenNum} not found.`);
return;
}
feedbackEl.textContent = message;
feedbackEl.className = `text-center font-bold mt-6 h-6 ${isCorrect ? 'text-green-600' : 'text-red-600'}`;
if (isCorrect) {
playSound('success');
// Updated: Go to screen 8 from 7, otherwise go to screen+1
const nextScreen = (screenNum === 7) ? 8 : screenNum + 1;
// DO NOT ADVANCE for level 5.2, it's handled separately
if (screenNum !== 5.2 && screenNum !== 3 && screenNum !== 6 && screenNum !== 7) {
// OLD: setTimeout(() => goToScreen(nextScreen), 1500);
// NEW: Handle replay nav
setTimeout(() => {
if (isReplaying) goToScreen(11);
else goToScreen(nextScreen);
}, 1500);
}
// NEW: Handle score-advancing screens
if (screenNum === 3 || screenNum === 6 || screenNum === 7) {
// OLD: setTimeout(() => goToScreen(nextScreen), 1500);
// NEW: Handle replay nav
setTimeout(() => {
if (isReplaying) goToScreen(11);
else goToScreen(nextScreen);
}, 1500);
}
} else {
playSound('wrong');
}
}
function checkAnswer(screenNum, correctAnswer) {
let userAnswer;
if (screenNum === 3 || screenNum === 6) {
// NEW: Track tries
if (screenNum === 3) level3Tries++;
if (screenNum === 6) level6Tries++;
const selectedOption = document.querySelector(`input[name="${screenNum === 3 ? 'chromebook-features' : 'ai-myth'}"]:checked`);
userAnswer = selectedOption ? selectedOption.value : null;
} else if (screenNum === 5) {
// OLD LOGIC REMOVED
return; // Level 5 logic is now custom
}
if (userAnswer === correctAnswer) {
// NEW: Scoring logic
if (screenNum === 3) {
// OLD: addScore(level3Tries === 1 ? 100 : 75);
recordLevelScore(3, level3Tries === 1 ? 100 : 75, level3Tries === 1);
// OLD: updateProgressBar(3);
}
if (screenNum === 6) {
// OLD: addScore(level6Tries === 1 ? 100 : 75);
recordLevelScore(6, level6Tries === 1 ? 100 : 75, level6Tries === 1);
// OLD: updateProgressBar(6);
}
const correctMessages = {
3: '鑑定成功!這才是探險家的標準配備!',
// 5: '正確!你找到了寶藏地圖!', // REMOVED
6: '觀察入微!你已經掌握了分辨AI的基本能力!'
};
showFeedback(screenNum, correctMessages[screenNum], true);
} else {
const wrongMessages = {
3: '哎呀!看來你被小華唬住了,再試一次吧!',
// 5: '網址不對喔,再想想看!', // REMOVED
6: '這個情境有用AI喔,再試一次吧!'
};
showFeedback(screenNum, wrongMessages[screenNum], false);
}
}
function showHint(screenNum) {
const hintEl = document.getElementById(`hint-${screenNum}`);
if (screenNum === 6) {
hintEl.textContent = '試著想想看,哪個選項的運作方式只是在執行一個「固定不變」的指令呢?';
}
hintEl.style.display = 'block';
}
// --- NEW: Level 2 Completion ---
function completeLevel2() {
// OLD: addScore(100); // Level 2 can't fail
// OLD: updateProgressBar(2);
recordLevelScore(2, 100, true);
goToScreen(3);
}
// --- Level 1: Drag & Drop Logic ---
document.addEventListener('DOMContentLoaded', () => {
// preloadImages(); // NEW: Call preloader <-- REMOVED: 已移到更早的位置
const draggables = document.querySelectorAll('.draggable');
const dropZones = document.querySelectorAll('.drop-zone');
let draggedItem = null;
// Mouse Events
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', e => {
draggedItem = e.target;
e.dataTransfer.setData('text/plain', e.target.id);
setTimeout(() => e.target.classList.add('dragging'), 0);
});
draggable.addEventListener('dragend', e => {
e.target.classList.remove('dragging');
});
});
dropZones.forEach(zone => {
zone.addEventListener('dragover', e => {
e.preventDefault();
zone.classList.add('over');
});
zone.addEventListener('dragleave', () => zone.classList.remove('over'));
zone.addEventListener('drop', e => {
e.preventDefault();
zone.classList.remove('over');
// Find the dragged item by ID if it's not set by touch
if (!draggedItem) {
const id = e.dataTransfer.getData('text/plain');
draggedItem = document.getElementById(id);
}
handleDrop(draggedItem, zone);
draggedItem = null; // Reset after drop
});
});
// Touch Events
let touchDragClone = null;
draggables.forEach(draggable => {
draggable.addEventListener('touchstart', e => {
draggedItem = e.target;
// Create clone
touchDragClone = draggedItem.cloneNode(true);
touchDragClone.style.position = 'absolute';
touchDragClone.style.zIndex = '1000';
touchDragClone.style.opacity = '0.8';
touchDragClone.style.pointerEvents = 'none';
document.body.appendChild(touchDragClone);
const touch = e.touches[0];
touchDragClone.style.left = `${touch.clientX - touchDragClone.offsetWidth / 2}px`;
touchDragClone.style.top = `${touch.clientY - touchDragClone.offsetHeight / 2}px`;
draggedItem.classList.add('dragging');
}, { passive: false });
draggable.addEventListener('touchmove', e => {
e.preventDefault();
if (!touchDragClone) return;
const touch = e.touches[0];
touchDragClone.style.left = `${touch.clientX - touchDragClone.offsetWidth / 2}px`;
touchDragClone.style.top = `${touch.clientY - touchDragClone.offsetHeight / 2}px`;
dropZones.forEach(zone => {
const rect = zone.getBoundingClientRect();
if (touch.clientX > rect.left && touch.clientX < rect.right &&
touch.clientY > rect.top && touch.clientY < rect.bottom) {
zone.classList.add('over');
} else {
zone.classList.remove('over');
}
});
}, { passive: false });
draggable.addEventListener('touchend', e => {
if (!touchDragClone) return;
const touch = e.changedTouches[0];
// Find the element *under* the touch point
touchDragClone.style.display = 'none'; // Hide clone temporarily
const dropTarget = document.elementFromPoint(touch.clientX, touch.clientY);
touchDragClone.style.display = 'block'; // Show again
// Check if dropTarget is a drop-zone or inside a drop-zone
let actualZone = dropTarget ? dropTarget.closest('.drop-zone') : null;
if (actualZone) {
handleDrop(draggedItem, actualZone);
}
document.body.removeChild(touchDragClone);
touchDragClone = null;
draggedItem.classList.remove('dragging');
dropZones.forEach(zone => zone.classList.remove('over'));
draggedItem = null; // Reset after drop
});
});
function handleDrop(item, zone) {
if (!item) return;
const isPersonal = item.dataset.type === 'personal';
const isTeam = item.dataset.type === 'team';
if ((zone.id === 'personal-growth' && isPersonal) || (zone.id === 'team-honor' && isTeam)) {
zone.appendChild(item);
item.setAttribute('draggable', 'false');
item.style.cursor = 'default';
playSound('correct');
} else {
playSound('wrong');
level1WrongDrop = true; // NEW: Track wrong drop
}
checkLevel1Completion(); // NEW: Check completion
}
// NEW: Level 1 Completion Check
function checkLevel1Completion() {
const personalZone = document.getElementById('personal-growth');
const teamZone = document.getElementById('team-honor');
if (personalZone.children.length === 3 && teamZone.children.length === 1) {
// Disable all draggables to prevent further moves
document.querySelectorAll('.draggable').forEach(d => {
d.setAttribute('draggable', 'false');
d.style.cursor = 'default';
});
// NEW: Add score
// OLD: addScore(level1WrongDrop ? 75 : 100);
// OLD: updateProgressBar(1);
recordLevelScore(1, level1WrongDrop ? 75 : 100, !level1WrongDrop);
showFeedback(1, '探險隊成立!正在前往下一關...', true);
}
}
});
// --- NEW: Level 5 (Screenshot & Typing) Logic ---
function initLevel5() {
const pasteZone = document.getElementById('paste-zone');
// NEW: Right click zone
const rightClickZone = document.getElementById('right-click-zone');
const rightClickCounter = document.getElementById('right-click-counter');
if (pasteZone) {
// Clear old content and state
pasteZone.innerHTML = '點擊此處,然後按下 Ctrl + V';
pasteZone.classList.remove('success', 'border-green-500', 'border-solid');
pasteZone.onpaste = handlePasteLevel5;
}
// NEW: Reset right click zone
if (rightClickZone) {
rightClickZone.classList.remove('success');
rightClickZone.oncontextmenu = handleRightClickLevel5; // Attach event
}
if (rightClickCounter) {
rightClickCounter.textContent = '0 / 3';
}
const feedbackEl = document.getElementById('feedback-5');
if (feedbackEl) {
feedbackEl.innerHTML = '';
}
// Reset Part 2 fields
const school = document.getElementById('l5-school');
const name = document.getElementById('l5-name');
const job = document.getElementById('l5-job');
if(school) school.value = '';
if(name) name.value = '';
if(job) job.value = '';
const feedbackEl2 = document.getElementById('feedback-5-2');
if(feedbackEl2) feedbackEl2.innerHTML = '';
// NEW: Reset flags
level5RightClicks = 0;
level5RightClickDone = false;
level5PasteDone = false;
level5Part1Score = 0; // Already global, but good to reset
}
// NEW: Handle Right Click
function handleRightClickLevel5(e) {
e.preventDefault(); // Stop context menu
if (level5RightClickDone) return; // Already completed
level5RightClicks++;
playSound('correct'); // Play soft click sound
const rightClickCounter = document.getElementById('right-click-counter');
if (level5RightClicks >= 3) {
level5RightClicks = 3; // Cap at 3
level5RightClickDone = true;
if (rightClickCounter) rightClickCounter.textContent = '✅ 完成!';
document.getElementById('right-click-zone').classList.add('success');
document.getElementById('right-click-zone').oncontextmenu = (e) => e.preventDefault(); // Keep blocking menu
checkLevel5Part1Completion();
} else {
if (rightClickCounter) rightClickCounter.textContent = `${level5RightClicks} / 3`;
}
}
function handlePasteLevel5(e) {
e.preventDefault();
const feedbackEl = document.getElementById('feedback-5');
const pasteZone = document.getElementById('paste-zone');
const files = e.clipboardData.files;
if (files.length > 0 && files[0].type.startsWith('image/')) {
const file = files[0];
const url = URL.createObjectURL(file);
pasteZone.innerHTML = `<img src="${url}" alt="貼上的截圖">`;
pasteZone.classList.add('success');
// feedbackEl.textContent = '✅ 截圖成功!正在前往下一階段...'; // MOVED
// feedbackEl.className = 'text-center font-bold mt-6 h-6 text-green-600'; // MOVED
// playSound('success'); // MOVED
level5PasteDone = true; // NEW
// level5Part1Score = 50; // MOVED to checkLevel5Part1Completion
// Remove paste listener to prevent multiple triggers
pasteZone.onpaste = null;
checkLevel5Part1Completion(); // NEW
// setTimeout(() => goToScreen(5.2), 2000); // MOVED to checkLevel5Part1Completion
} else {
feedbackEl.textContent = '哎呀,你需要貼上一張『圖片』喔!';
feedbackEl.className = 'text-center font-bold mt-6 h-6 text-red-600';
playSound('wrong');
}
}
// NEW: Check for completion of *both* tasks
function checkLevel5Part1Completion() {
if (level5RightClickDone && level5PasteDone) {
const feedbackEl = document.getElementById('feedback-5');
feedbackEl.textContent = '✅ 技能整備完成!正在前往下一階段...';
feedbackEl.className = 'text-center font-bold mt-6 h-6 text-green-600';
playSound('success');
level5Part1Score = 50; // Grant score
setTimeout(() => goToScreen(5.2), 2000); // Go to Part 2
}
}
function checkLevel5_2() {
const school = document.getElementById('l5-school').value;
const name = document.getElementById('l5-name').value;
const job = document.getElementById('l5-job').value;
const feedbackEl = document.getElementById('feedback-5-2');
if (school.trim() && name.trim() && job.trim()) {
// --- NEW: Store Player Identifier ---
// 儲存玩家在 L5-2 輸入的名稱
playerIdentifier = `${school.trim()} - ${name.trim()}`;
console.log("Player identifier set:", playerIdentifier);
// --- END NEW ---
feedbackEl.textContent = '報到完成!準備進入下一關...';
feedbackEl.className = 'text-center font-bold mt-6 h-6 text-green-600';
playSound('success');
// OLD: addScore(50); // NEW: Add 50 points
// OLD: updateProgressBar(5); // NEW: Mark level 5 complete
// NEW: Record full score for L5
recordLevelScore(5, level5Part1Score + 50, true); // Assume part 1 was also first try
// OLD: setTimeout(() => goToScreen(6), 1500);
setTimeout(() => {
if (isReplaying) goToScreen(11);
else goToScreen(6);
}, 1500);
} else {
feedbackEl.textContent = '請填寫所有欄位喔!';
feedbackEl.className = 'text-center font-bold mt-6 h-6 text-red-600';
playSound('wrong');
}
}
// --- Level 4: Bubble Game Logic ---
let plusClicked = 0;
const plusNeeded = 5;
const googleColors = ['#EA4335', '#4285F4', '#FBBC05', '#34A853']; // Red, Blue, Yellow, Green
let bubbleGrowInterval;
// let level4ResetUsed = false; // Already global
function initBubbleGame() {
const container = document.getElementById('bubble-container');
container.innerHTML = '';
plusClicked = 0;
level4ResetUsed = false; // NEW: Reset flag
updatePlusFill();
clearInterval(bubbleGrowInterval);
const words = ['Plus', 'Pause', 'Play', 'Power', 'Pigs', 'Plus', 'Plus', 'Pause', 'Play', 'Plus', 'Power', 'Plus', 'Pigs', 'Play', 'Plus', 'Power', 'Pause', 'Play', 'Pigs', 'Power', 'Plus', 'Plus', 'Play','Power', 'Pause', 'Play', 'Pigs', 'Power', 'Plus'];
words.forEach(word => {
const bubble = document.createElement('div');
const size = Math.random() * 40 + 30;
bubble.classList.add('bubble');
bubble.dataset.word = word;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
bubble.style.left = `${Math.random() * (container.offsetWidth - size)}px`;
bubble.style.top = `${Math.random() * (container.offsetHeight - size)}px`;
bubble.style.backgroundColor = word === 'Plus'
? googleColors[1]
: googleColors[Math.floor(Math.random() * 4)];
bubble.textContent = word;
if (word === 'Plus') {
bubble.addEventListener('click', handleCorrectBubble);
} else {
bubble.addEventListener('click', handleWrongBubbleManualClick);
}
container.appendChild(bubble);
animateBubbleMovement(bubble); // Renamed function for clarity
});
bubbleGrowInterval = setInterval(growWrongBubblesAutomatically, 3000);
}
function animateBubbleMovement(bubble) { // Renamed
const container = document.getElementById('bubble-container');
let xSpeed = (Math.random() - 0.5) * 1.5;
let ySpeed = (Math.random() - 0.5) * 1.5;
let x = parseFloat(bubble.style.left);
let y = parseFloat(bubble.style.top);
function move() {
if (!bubble.parentNode) return;
x += xSpeed;
y += ySpeed;
const currentWidth = bubble.offsetWidth;
const currentHeight = bubble.offsetHeight;
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
if (x <= 0) {
xSpeed = Math.abs(xSpeed);
x = 0;
} else if (x + currentWidth >= containerWidth) {
xSpeed = -Math.abs(xSpeed);
x = containerWidth - currentWidth;
}
if (y <= 0) {
ySpeed = Math.abs(ySpeed);
y = 0;
} else if (y + currentHeight >= containerHeight) {
ySpeed = -Math.abs(ySpeed);
y = containerHeight - currentHeight;
}
bubble.style.left = `${x}px`;
bubble.style.top = `${y}px`;
requestAnimationFrame(move);
}
move();
}
function handleCorrectBubble(e) {
playSound('correct');
plusClicked++;
e.target.remove();
updatePlusFill();
if (plusClicked >= plusNeeded) {
clearInterval(bubbleGrowInterval);
// UPDATED:
// const feedbackMessage = '能量注入完成!沒錯,就是「Chromebook Plus」!';
// showFeedback(4, feedbackMessage, true);
// NEW: Call modal
showLevel4SuccessModal();
}
}
function handleWrongBubbleManualClick(e) {
playSound('wrong');
growBubble(e.target);
}
function growBubble(bubble) {
if (!bubble || !bubble.parentNode) return;
const container = document.getElementById('bubble-container');
const maxSize = Math.min(container.offsetWidth, container.offsetHeight) * 0.8;
const currentSize = bubble.offsetWidth;
let newSize = currentSize * 1.15;
if (newSize > maxSize) {
newSize = maxSize;
}
bubble.style.width = `${newSize}px`;
bubble.style.height = `${newSize}px`;
bubble.style.zIndex = (parseInt(bubble.style.zIndex) || 10) + 1;
}
function growWrongBubblesAutomatically() {
const wrongBubbles = document.querySelectorAll('#bubble-container .bubble');
wrongBubbles.forEach(bubble => {
if (bubble.dataset.word !== 'Plus') {
growBubble(bubble);
}
});
}
function updatePlusFill() {
const fillPercentage = (plusClicked / plusNeeded) * 100;
document.getElementById('plus-fill').style.width = `${fillPercentage}%`;
}
function resetBubbles() {
const wrongBubbles = document.querySelectorAll('#bubble-container .bubble');
wrongBubbles.forEach(bubble => {
if (bubble.dataset.word !== 'Plus') {
const originalSize = Math.random() * 40 + 30;
bubble.style.width = `${originalSize}px`;
bubble.style.height = `${originalSize}px`;
bubble.style.zIndex = '10';
}
});
playSound('correct');
level4ResetUsed = true; // NEW: Set flag
}
// --- Level 7: Connect Game Logic ---
const GRID_ROWS = 5;
const GRID_COLS = 4;
const pathGridState = [];
let currentPathStage = 0;
let level7Phase = 'SELECTING';
// let level7Score = 0; // Already global
// let level7FirstTry = true; // Already global
// let level7CurrentStageTries = 0; // Already global
const solutionOrder = [ 'term-ai', 'term-ml', 'term-genai', 'term-llm', 'term-chatbot' ];
const stageLayouts = [
// Stage 1 (index 0): Row 0 -> 2
[
['L', 'I', 'I', 'I'],
['I', 'I', 'L', 'I'],
['L', 'I', 'I', 'I'],
['I', 'L', 'I', 'I'],
['L', 'I', 'L', 'I']
],
// Stage 2 (index 1): Row 1 -> 4
[
['L', 'I', 'L', 'I'],
['I', 'L', 'I', 'I'],
['I', 'L', 'L', 'I'],
['I', 'L', 'I', 'L'],
['L', 'I', 'L', 'I']
],
// Stage 3 (index 2): Row 2 -> 0
[
['I', 'I', 'I', 'L'],
['L', 'I', 'I', 'L'],
['L', 'I', 'I', 'I'],
['I', 'I', 'I', 'I'],
['I', 'I', 'I', 'I']
],
// Stage 4 (index 3): Row 3 -> 3
[
['I', 'L', 'L', 'I'],
['I', 'I', 'I', 'I'],
['I', 'I', 'I', 'I'],
['I', 'L', 'L', 'I'],
['I', 'I', 'I', 'I']
],
// Stage 5 (index 4): Row 4 -> 1
[
['I', 'I', 'I', 'I'],
['I', 'I', 'L', 'I'],
['L', 'I', 'I', 'I'],
['L', 'I', 'L', 'I'],
['L', 'I', 'I', 'I']
]
];
const puzzleSolution = {
'term-ai': 'def-ai', 'term-ml': 'def-ml', 'term-genai': 'def-genai',
'term-llm': 'def-llm', 'term-chatbot': 'def-chatbot'
};
function initConnectGame() {
if (currentPathStage >= solutionOrder.length) return;
level7Phase = 'SELECTING';
if (currentPathStage === 0) {
document.getElementById('matched-pairs-display').innerHTML = '';
level7Score = 0; // NEW: Reset score on L7 init
level7FirstTry = true; // NEW: Reset first try flag
}
level7CurrentStageTries = 0; // NEW: Reset tries for the stage
const currentKeywordId = solutionOrder[currentPathStage];
const activeKeywordEl = document.getElementById(currentKeywordId);
const instructionsEl = document.getElementById('level-7-instructions');
instructionsEl.innerHTML = `階段 ${currentPathStage + 1} / 5: 請為 <strong class="text-blue-600">${activeKeywordEl.textContent}</strong> 選擇正確的解釋。`;
document.getElementById('feedback-7').textContent = '';
document.getElementById('path-grid-container').style.visibility = 'hidden';
document.getElementById('check-path-btn').style.display = 'none';
document.getElementById('definitions-col').style.visibility = 'visible';
document.querySelectorAll('#keywords-col .connect-col-item').forEach(el => el.classList.add('opacity-30', 'bg-gray-50'));
activeKeywordEl.classList.remove('opacity-30', 'bg-gray-50');
document.querySelectorAll('#definitions-col .connect-col-item').forEach(el => {
const isAlreadyMatched = Array.from(document.getElementById('matched-pairs-display').children).some(p => p.dataset.defId === el.dataset.defId);
const newEl = el.cloneNode(true);
el.parentNode.replaceChild(newEl, el);
if (isAlreadyMatched) {
newEl.classList.add('opacity-30', 'bg-gray-50', 'cursor-not-allowed');
} else {
newEl.classList.remove('opacity-30', 'bg-gray-50', 'cursor-not-allowed');
newEl.classList.add('cursor-pointer', 'hover:bg-yellow-100');
newEl.addEventListener('click', handleDefinitionSelection);
}
});
}
function handleDefinitionSelection(event) {
if (level7Phase !== 'SELECTING') return;
const selectedDefEl = event.currentTarget;
const selectedDefId = selectedDefEl.dataset.defId;
const currentKeywordId = solutionOrder[currentPathStage];
const correctDefId = puzzleSolution[currentKeywordId];
if (selectedDefId === correctDefId) {
playSound('correct');
level7Phase = 'CONNECTING';
document.querySelectorAll('#definitions-col .connect-col-item').forEach(el => {
const newEl = el.cloneNode(true);
el.parentNode.replaceChild(newEl, el);
if (newEl.dataset.defId !== correctDefId) {
newEl.classList.add('opacity-30', 'bg-gray-50');
}
newEl.classList.remove('cursor-pointer', 'hover:bg-yellow-100');
newEl.classList.add('cursor-not-allowed');
});
document.getElementById('level-7-instructions').innerHTML = `選擇正確!現在請旋轉方塊,<strong class="text-green-600">連接路徑</strong>。`;
document.getElementById('path-grid-container').style.visibility = 'visible';
document.getElementById('check-path-btn').style.display = 'inline-block';
setupRotatablePathGrid();
} else {
playSound('wrong');
level7CurrentStageTries++; // NEW: Increment wrong tries
const feedbackEl = document.getElementById('feedback-7');
feedbackEl.textContent = `這個解釋不對喔,再試一次!`;
feedbackEl.className = `text-center font-bold mt-6 h-6 text-red-600`;
selectedDefEl.classList.add('border-red-500');
setTimeout(() => {
selectedDefEl.classList.remove('border-red-500');
feedbackEl.textContent = '';
}, 1500);
}
}
function setupRotatablePathGrid() {
const gridContainer = document.getElementById('path-grid-container');
gridContainer.innerHTML = '';
pathGridState.length = 0;
gridContainer.style.gridTemplateColumns = `repeat(${GRID_COLS}, 1fr)`;
gridContainer.style.gridTemplateRows = `repeat(${GRID_ROWS}, 1fr)`;
const currentLayout = stageLayouts[currentPathStage];
for (let r = 0; r < GRID_ROWS; r++) {
pathGridState[r] = [];
for (let c = 0; c < GRID_COLS; c++) {
const cell = document.createElement('div');
cell.classList.add('path-cell');
const type = currentLayout[r][c];
const rotation = Math.floor(Math.random() * 4) * 90;
cell.innerHTML = getPathSvg(type);
cell.firstElementChild.style.transform = `rotate(${rotation}deg)`;
const cellState = { type, rotation, element: cell };
pathGridState[r][c] = cellState;
cell.addEventListener('click', () => {
playSound('correct');
cellState.rotation = (cellState.rotation + 90) % 360;
cell.firstElementChild.style.transform = `rotate(${cellState.rotation}deg)`;
});
gridContainer.appendChild(cell);
}
}
}
function checkRotatedPathConnection() {
if (level7Phase !== 'CONNECTING') return;
const keywordElements = document.querySelectorAll('#keywords-col .connect-col-item');
const definitionElements = document.querySelectorAll('#definitions-col .connect-col-item');
const currentKeywordId = solutionOrder[currentPathStage];
const correctDefId = puzzleSolution[currentKeywordId];
const startRow = Array.from(keywordElements).findIndex(el => el.id === currentKeywordId);
const expectedEndRow = Array.from(definitionElements).findIndex(el => el.dataset.defId === correctDefId);
const tracedEndRow = tracePath(startRow);
if (tracedEndRow === expectedEndRow) {
playSound('success');
const activeKeywordEl = document.getElementById(currentKeywordId);
const correctDefEl = document.querySelector(`[data-def-id="${correctDefId}"]`);
const matchedDisplay = document.getElementById('matched-pairs-display');
const newPair = document.createElement('span');
newPair.className = 'bg-green-100 text-green-800 font-semibold p-2 rounded-md inline-block m-1 text-xs sm:text-sm';
newPair.textContent = `${activeKeywordEl.textContent}${correctDefEl.textContent}`;
newPair.dataset.defId = correctDefId;
matchedDisplay.appendChild(newPair);
// NEW: Add score
// OLD: addScore(level7CurrentStageTries === 0 ? 20 : 15);
if (level7CurrentStageTries > 0) level7FirstTry = false; // Mark as not first try
level7Score += (level7CurrentStageTries === 0 ? 20 : 15); // Add to partial score
currentPathStage++;
if (currentPathStage < solutionOrder.length) {
const feedbackEl = document.getElementById(`feedback-7`);
feedbackEl.textContent = '路徑連接成功!準備下一題...';
feedbackEl.className = `text-center font-bold mt-6 h-6 text-green-600`;
setTimeout(() => initConnectGame(), 2000);
} else {
// Go to screen 8
// OLD: updateProgressBar(7); // NEW: Mark level 7 complete
recordLevelScore(7, level7Score, level7FirstTry); // NEW: Record final L7 score
showFeedback(7, '解碼完成!你已獲得AI的智慧!', true);
}
} else {
playSound('wrong');
const feedbackEl = document.getElementById('feedback-7');
if (tracedEndRow === -1) {
feedbackEl.textContent = `路徑不通或走出了邊界,請檢查!`;
} else {
feedbackEl.textContent = `路徑連接到錯誤的答案了,請再試一次!`;
}
feedbackEl.className = `text-center font-bold mt-6 h-6 text-red-600`;
}
}
function getPathSvg(type) {
const color = "#4b5563"; // gray-600
if (type === 'I') { // Straight
return `<svg viewBox="0 0 10 10"><path d="M0 5 H 10" stroke="${color}" stroke-width="2" fill="none"/></svg>`;
} else { // Corner
return `<svg viewBox="0 0 10 10"><path d="M0 5 H 5 V 10" stroke="${color}" stroke-width="2" fill="none"/></svg>`;
}
}
function getExits(cell) {
const exits = [];
const r = cell.rotation;
if (cell.type === 'I') {
if (r === 0 || r === 180) exits.push('left', 'right');
else exits.push('top', 'bottom');
} else { // type 'L' - Path is from left to bottom at 0deg
if (r === 0) exits.push('left', 'bottom');
if (r === 90) exits.push('top', 'left');
if (r === 180) exits.push('right', 'top');
if (r === 270) exits.push('bottom', 'right');
}
return exits;
}
function tracePath(startRow) {
let pos = { r: startRow, c: 0 };
let fromDir = 'left';
for(let i = 0; i < (GRID_COLS * GRID_ROWS + 5); i++) { // Failsafe loop
if (pos.c < 0 || pos.r < 0 || pos.r >= GRID_ROWS) {
return -1; // Went off grid
}
if (pos.c >= GRID_COLS) {
return pos.r; // Reached the end
}
const cell = pathGridState[pos.r][pos.c];
if (!cell) {
console.error("Error: Cell is undefined at", pos);
return -1;
}
const exits = getExits(cell);
if (!exits.includes(fromDir)) return -1; // Dead end
const exitDir = exits.find(dir => dir !== fromDir);
if (!exitDir) return -1; // Should not happen
// Move to next cell
if (exitDir === 'right') { pos.c++; fromDir = 'left'; }
else if (exitDir === 'left') { pos.c--; fromDir = 'right'; }
else if (exitDir === 'bottom') { pos.r++; fromDir = 'top'; }
else if (exitDir === 'top') { pos.r--; fromDir = 'bottom'; }
}
return -1; // Path is too long or looping
}
// --- Level 8: Hidden Object Game Logic ---
let level8Timer;
let time_taken_vids = 0;
let foundVids = 0;
let hint_used = false;
const totalVids = 3;
let level8Phase = 'FINDING_VIDS';
let foundGemini = 0;
let foundNotebookLM = 0;
const neededGemini = 5;
const neededNotebookLM = 5;
function initLevel8Game() {
// Reset game state
clearInterval(level8Timer);
time_taken_vids = 0;
level8BonusTime = 0; // NEW: Reset bonus time
foundVids = 0;
hint_used = false;
level8Phase = 'FINDING_VIDS';
foundGemini = 0;
foundNotebookLM = 0;
document.getElementById('vids-counter-container').classList.remove('hidden');
document.getElementById('bonus-counters-container').classList.add('hidden');
document.getElementById('level-8-title').textContent = "第八關:發掘 Workspace 新星";
document.getElementById('level-8-subtitle').classList.remove('hidden');
document.getElementById('level-8-warning').classList.remove('hidden');
document.getElementById('level-8-main-instruction').innerHTML = `它就隱藏在我們熟悉的工具夥伴中。快睜大眼睛,在下方的工具海中找出 <strong>3</strong> 個 [Google Vids] 的 icon 吧!`;
const hintBtn = document.getElementById('hint-btn-8');
hintBtn.disabled = false;
hintBtn.textContent = '提示按鈕 (剩下 1 次)';
hintBtn.classList.remove('hidden');
document.getElementById('level-8-modal-container').classList.add('hidden');
const gridContainer = document.getElementById('icon-grid-8-container');
gridContainer.innerHTML = '';
// Define icons
const vidsIcon = { type: 'vids', src: 'https://imgtolinkx.com/i/XLPlZVZw', alt: 'Google Vids Icon' };
const geminiIcon = { type: 'gemini', src: 'https://imgtolinkx.com/i/ByK3AKuQ', alt: 'Gemini Icon'};
const notebooklmIcon = { type: 'notebooklm', src: 'https://imgtolinkx.com/i/kZvAtWwv', alt: 'NotebookLM Icon'};
const iconPool = [
{ type: 'gmail', src: 'https://www.gstatic.com/images/branding/product/1x/gmail_2020q4_48dp.png', alt: 'Gmail Icon' },
{ type: 'drive', src: 'https://ssl.gstatic.com/images/branding/product/2x/drive_48dp.png', alt: 'Drive Icon' },
{ type: 'cal', src: 'https://ssl.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_15_2x.png', alt: 'Calendar Icon' },
{ type: 'docs', src: 'https://ssl.gstatic.com/images/branding/product/2x/docs_48dp.png', alt: 'Docs Icon' },
{ type: 'sheets', src: 'https://ssl.gstatic.com/images/branding/product/2x/sheets_48dp.png', alt: 'Sheets Icon' },
{ type: 'keep', src: 'https://ssl.gstatic.com/images/branding/product/2x/keep_48dp.png', alt: 'Keep Icon' },
{ type: 'meet', src: 'https://fonts.gstatic.com/s/i/productlogos/meet_2020q4/v1/web-96dp/logo_meet_2020q4_color_1x_web_96dp.png', alt: 'Meet Icon'},
geminiIcon,
notebooklmIcon
];
let finalIconList = [ vidsIcon, vidsIcon, vidsIcon ];
for(let i=0; i < neededGemini; i++) finalIconList.push(geminiIcon);
for(let i=0; i < neededNotebookLM; i++) finalIconList.push(notebooklmIcon);
const neededOthers = 120 - finalIconList.length;
for(let i=0; i < neededOthers; i++) {
const poolWithoutTargets = iconPool.filter(icon => icon.type !== 'gemini' && icon.type !== 'notebooklm');
if (poolWithoutTargets.length > 0) {
finalIconList.push(poolWithoutTargets[i % poolWithoutTargets.length]);
} else {
finalIconList.push(iconPool[i % iconPool.length]);
}
}
finalIconList.sort(() => Math.random() - 0.5);
finalIconList.sort(() => Math.random() - 0.5);
// --- NEW: Calculate container height ---
const numCols = 12;
const numRows = 10; // 120 icons / 12 cols = 10 rows
const containerWidth = gridContainer.offsetWidth || 700;
const iconWidth = containerWidth / numCols;
const iconHeight = iconWidth; // Square icons
const calculatedContainerHeight = numRows * iconHeight;
gridContainer.style.height = `${calculatedContainerHeight}px`; // Explicitly set height
// --- END NEW ---
finalIconList.forEach((icon, index) => {
const img = document.createElement('img');
img.src = icon.src;
img.dataset.type = icon.type;
img.classList.add('icon-item');
img.alt = icon.alt;
const row = Math.floor(index / numCols);
const col = index % numCols;
let leftPos = col * iconWidth;
let topPos = row * iconHeight;
// Apply boundary checks using calculated dimensions
leftPos = Math.max(0, Math.min(leftPos, containerWidth - iconWidth));
topPos = Math.max(0, Math.min(topPos, calculatedContainerHeight - iconHeight)); // Use calculated height
img.style.left = `${leftPos}px`;
img.style.top = `${topPos}px`;
img.style.width = `${iconWidth}px`;
img.onerror = () => {
console.warn(`Failed to load icon: ${icon.src}`);
let fallbackText = icon.alt.split(' ')[0] || 'Err';
img.src = `https://placehold.co/${Math.round(iconWidth)}x${Math.round(iconHeight)}/cccccc/FFFFFF?text=${fallbackText}`;
img.alt = `${icon.alt} (failed to load)`;
}
img.addEventListener('click', handleIconClick);
gridContainer.appendChild(img);
});
level8Timer = setInterval(() => {
if (level8Phase === 'FINDING_VIDS') {
time_taken_vids++;
}
if (level8Phase === 'BONUS_PHASE') { // NEW: Time bonus round
level8BonusTime++;
}
// Always update timer display regardless of phase (or just for active phases)
const totalTime = time_taken_vids + level8BonusTime;
const minutes = String(Math.floor(totalTime / 60)).padStart(2, '0');
const seconds = String(totalTime % 60).padStart(2, '0');
document.getElementById('timer-8').textContent = `${minutes}:${seconds}`;
}, 1000);
}
function handleIconClick(e) {
const icon = e.target;
if (level8Phase === 'MODAL' || !icon.parentNode) return;
if (level8Phase === 'FINDING_VIDS') {
if (icon.dataset.type === 'vids') {
playSound('correct');
icon.remove();
foundVids++;
document.getElementById('found-counter-8').textContent = foundVids;
if (foundVids === totalVids) {
level8Phase = 'MODAL';
// DO NOT clear interval yet
// clearInterval(level8Timer);
showVidsFoundModal();
}
} else {
playSound('wrong');
}
} else if (level8Phase === 'BONUS_PHASE') {
if (icon.dataset.type === 'gemini') {
if (foundGemini < neededGemini) {
playSound('correct');
// Removed: cancelIconAnimation(icon);
icon.remove();
foundGemini++;
document.getElementById('found-gemini-8').textContent = foundGemini;
} else {
playSound('wrong');
}
} else if (icon.dataset.type === 'notebooklm') {
if (foundNotebookLM < neededNotebookLM) {
playSound('correct');
// Removed: cancelIconAnimation(icon);
icon.remove();
foundNotebookLM++;
document.getElementById('found-notebooklm-8').textContent = foundNotebookLM;
} else {
playSound('wrong');
}
} else {
// Do nothing for wrong clicks in bonus
}
if (foundGemini === neededGemini && foundNotebookLM === neededNotebookLM) {
level8Phase = 'MODAL';
clearInterval(level8Timer); // NEW: Stop timer *here*
handleWinLevel8BonusPhase();
}
}
}
function useHintLevel8() {
if (hint_used) return;
hint_used = true;
const hintBtn = document.getElementById('hint-btn-8');
hintBtn.disabled = true;
hintBtn.textContent = '提示已使用';
if (level8Phase === 'FINDING_VIDS') {
const vidsIcon = document.querySelector('.icon-item[data-type="vids"]');
if (vidsIcon) {
vidsIcon.classList.add('hint-flash');
setTimeout(() => vidsIcon.classList.remove('hint-flash'), 3000);
}
} else if (level8Phase === 'BONUS_PHASE') {
const geminiIcon = document.querySelector('.icon-item[data-type="gemini"]');
const notebookIcon = document.querySelector('.icon-item[data-type="notebooklm"]');
if (geminiIcon) {
geminiIcon.classList.add('hint-flash');
setTimeout(() => geminiIcon.classList.remove('hint-flash'), 3000);
}
if (notebookIcon) {
notebookIcon.classList.add('hint-flash');
setTimeout(() => notebookIcon.classList.remove('hint-flash'), 3000);
}
}
}
function showVidsFoundModal() {
playSound('success');
// --- BUG FIX: Remove Modal & Auto-start Bonus Phase ---
// const modalContainer = document.getElementById('level-8-modal-container');
// const modalContent = document.getElementById('level-8-modal-content');
// const modalTitle = document.getElementById('level-8-modal-title');
// const modalFeedback = document.getElementById('level-8-feedback');
// const modalButton = document.getElementById('level-8-modal-button');
// const modalSkipButton = document.getElementById('level-8-modal-button-skip');
// modalTitle.textContent = '太棒了!';
// modalFeedback.innerHTML = `您在 <strong class="text-xl text-blue-600">${time_taken_vids} 秒</strong> 內找到了 Vids!<br><br>...等等,雷達偵測到另外兩股強大的AI能量!<br><br>是否要挑戰<strong class="bonus-counter">「找出 5 個 Gemini 和 5 個 NotebookLM」</strong>的獎勵關卡?`;
// modalButton.textContent = '接受挑戰!';
// modalButton.onclick = startLevel8BonusPhase;
// modalSkipButton.textContent = '跳過獎勵關卡';
// modalSkipButton.style.display = 'block';
// modalSkipButton.onclick = skipLevel8BonusPhase;
// modalContent.classList.add('emergency-flash');
// modalContainer.classList.remove('hidden');
// NEW: Go directly to phase 2
startLevel8BonusPhase();
// --- END BUG FIX ---
}
function startLevel8BonusPhase() {
level8Phase = 'BONUS_PHASE';
document.getElementById('level-8-modal-container').classList.add('hidden');
document.getElementById('level-8-modal-button-skip').style.display = 'none';
// Update UI for bonus phase
document.getElementById('level-8-modal-content').classList.remove('emergency-flash');
document.getElementById('level-8-title').textContent = "第八關:獎勵挑戰!";
document.getElementById('level-8-subtitle').classList.add('hidden');
document.getElementById('level-8-warning').classList.add('hidden');
document.getElementById('level-8-main-instruction').innerHTML = `在 <strong class="bonus-counter">Gemini</strong> 和 <strong class="bonus-counter">NotebookLM</strong> 消失前,盡可能找出所有 icon!`;
document.getElementById('vids-counter-container').classList.add('hidden');
document.getElementById('bonus-counters-container').classList.remove('hidden');
// Reset hint button for bonus round
hint_used = false;
const hintBtn = document.getElementById('hint-btn-8');
hintBtn.disabled = false;
hintBtn.textContent = '提示按鈕 (剩下 1 次)';
// --- NEW: Shrink non-target icons ---
const allIcons = document.querySelectorAll('.icon-item');
allIcons.forEach(icon => {
const type = icon.dataset.type;
// BUG FIX: Shrink ALL icons
icon.classList.add('shrunk');
if (type !== 'gemini' && type !== 'notebooklm') {
// icon.classList.add('shrunk'); // MOVED
icon.style.pointerEvents = 'none'; // Make them unclickable
} else {
// NEW: Ensure targets are not shrunk
// icon.classList.remove('shrunk'); // REMOVED (per user request to shrink all)
icon.style.pointerEvents = 'auto';
}
});
}
function skipLevel8BonusPhase() {
level8Phase = 'MODAL'; // Set phase to modal
clearInterval(level8Timer); // Stop timer
// --- NEW: Scoring Logic for Skipping ---
const totalLevel8Time = time_taken_vids; // Only Vids time
let level8Score = 0;
let titleText = '';
if (totalLevel8Time <= 10) {
titleText = `S級 (${totalLevel8Time}秒)`;
level8Score = 100;
} else if (totalLevel8Time <= 15) {
titleText = `A級 (${totalLevel8Time}秒)`;
level8Score = 95;
} else if (totalLevel8Time <= 20) {
titleText = `B級 (${totalLevel8Time}秒)`;
level8Score = 90;
} else {
titleText = `C級 (${totalLevel8Time}秒)`;
level8Score = 85;
}
// OLD: addScore(level8Score);
recordLevelScore(8, level8Score, totalLevel8Time <= 10); // S-Class counts as "first try"
// --- END: Scoring Logic ---
const modalContainer = document.getElementById('level-8-modal-container');
const modalTitle = document.getElementById('level-8-modal-title');
const modalFeedback = document.getElementById('level-8-feedback');
const modalButton = document.getElementById('level-8-modal-button');
const modalSkipButton = document.getElementById('level-8-modal-button-skip');
modalTitle.textContent = `跳過獎勵關卡 (${titleText})`; // Show rank
modalFeedback.textContent = "已記錄您的 Vids 成績。正在前往下一關...";
modalButton.textContent = '前往第九關';
modalButton.onclick = closeLevel8ModalAndAdvance;
modalSkipButton.style.display = 'none';
}
/* --- (FIXED: Added function wrapper) --- */
function handleWinLevel8BonusPhase() {
playSound('success');
// --- NEW: Scoring Logic ---
const totalLevel8Time = time_taken_vids + level8BonusTime;
let level8Score = 0;
let titleText = '';
// (Vids time + Bonus time)
if (totalLevel8Time <= 10) {
titleText = `S級 (${totalLevel8Time}秒)`;
level8Score = 100;
} else if (totalLevel8Time <= 15) {
titleText = `A級 (${totalLevel8Time}秒)`;
level8Score = 95;
} else if (totalLevel8Time <= 20) {
titleText = `B級 (${totalLevel8Time}秒)`;
level8Score = 90;
} else {
titleText = `C級 (${totalLevel8Time}D`;
level8Score = 85;
}
// OLD: addScore(level8Score);
recordLevelScore(8, level8Score, totalLevel8Time <= 10); // S-Class counts as "first try"
// --- END: Scoring Logic ---
// This is the fixed feedback text from Request 4
const feedbackText = `太強了!您在 ${time_taken_vids} 秒內找到 Vids,並在 ${level8BonusTime} 秒內完成獎勵關卡!總計 ${totalLevel8Time} 秒!`;
const modalContainer = document.getElementById('level-8-modal-container');
const modalContent = document.getElementById('level-8-modal-content');
const modalTitle = document.getElementById('level-8-modal-title');
const modalFeedback = document.getElementById('level-8-feedback');
const modalButton = document.getElementById('level-8-modal-button');
modalTitle.textContent = `獎勵挑戰完成! (${titleText})`; // Show rank
modalFeedback.textContent = feedbackText;
modalButton.textContent = '前往第九關';
modalButton.onclick = closeLevel8ModalAndAdvance;
modalContent.classList.remove('emergency-flash');
modalContainer.classList.remove('hidden');
}
/* --- (END OF FIX) --- */
function closeLevel8ModalAndAdvance() {
document.getElementById('level-8-modal-container').classList.add('hidden');
// OLD: updateProgressBar(8); // NEW: Mark level 8 complete (Handled by recordLevelScore)
// OLD: goToScreen(9); // UPDATED: Go to the new poll screen (9)
if (isReplaying) goToScreen(11); // NEW: Handle replay nav
else goToScreen(9);
}
// --- NEW: Level 9 Poll Game Logic ---
function initPollGame() {
pollClickCount = 0; // Already global
let geminiLikes = 0;
let notebookLMLikes = 0;
let timeRemaining = 20;
const timerEl = document.getElementById('poll-timer');
const clicksEl = document.getElementById('poll-clicks');
const geminiImg = document.getElementById('gemini-img');
const notebookImg = document.getElementById('notebooklm-img');
const geminiLikesEl = document.getElementById('gemini-likes-display');
const notebookLikesEl = document.getElementById('notebooklm-likes-display');
// Reset UI
timerEl.textContent = timeRemaining;
clicksEl.textContent = pollClickCount;
geminiLikesEl.textContent = `❤️ Gemini 人氣: 0`;
notebookLikesEl.textContent = `❤️ NotebookLM 人氣: 0`;
// Clear old intervals if any
if (pollCountdownInterval) clearInterval(pollCountdownInterval);
if (pollGameTimer) clearTimeout(pollGameTimer);
function handleClick(e, type) {
if (timeRemaining <= 0) return;
pollClickCount++;
clicksEl.textContent = pollClickCount;
createFloatingHeart(e); // Create visual effect
playSound('correct'); // Play click sound
if (type === 'gemini') {
geminiLikes++;
geminiLikesEl.textContent = `❤️ Gemini 人氣: ${geminiLikes}`;
} else {
notebookLMLikes++;
notebookLikesEl.textContent = `❤️ NotebookLM 人氣: ${notebookLMLikes}`;
}
}
// --- Re-add event listeners ---
// Clone and replace to remove old listeners
const newGeminiImg = geminiImg.cloneNode(true);
geminiImg.parentNode.replaceChild(newGeminiImg, geminiImg);
newGeminiImg.addEventListener('click', (e) => handleClick(e, 'gemini'));
const newNotebookImg = notebookImg.cloneNode(true);
notebookImg.parentNode.replaceChild(newNotebookImg, notebookImg);
newNotebookImg.addEventListener('click', (e) => handleClick(e, 'notebooklm'));
// --- End re-add ---
// Start Countdown
pollCountdownInterval = setInterval(() => {
timeRemaining--;
timerEl.textContent = timeRemaining;
if (timeRemaining <= 0) {
stopPollGame();
}
}, 1000);
// Set game end timer
pollGameTimer = setTimeout(() => {
stopPollGame();
}, 20000);
}
function stopPollGame() {
// --- BUG FIX: Add guard to prevent double-trigger ---
if (pollGameTimer === null) return;
if (pollCountdownInterval) clearInterval(pollCountdownInterval);
if (pollGameTimer) clearTimeout(pollGameTimer);
pollGameTimer = null; // Prevent multiple triggers
// Calculate score
let pollScore = 0;
if (pollClickCount > 80) {
pollScore = 100;
} else if (pollClickCount >= 65) {
pollScore = 90;
} else {
pollScore = 80;
}
// OLD: addScore(pollScore);
recordLevelScore(9, pollScore, pollScore === 100); // 100-score counts as "first try"
// Show end modal after a short delay
setTimeout(() => showPollEndModal(pollScore, pollClickCount), 500); // NEW: Pass score
}
// --- Level 8 Modal Logic (Shared Modals) ---
<!-- (FIX) This function was missing. I am re-adding it here. -->
<!-- It belongs with the other modal functions -->
function showLevel4SuccessModal() {
playSound('success');
// --- BUG FIX: Add score and progress bar update ---
const score = level4ResetUsed ? 75 : 100;
recordLevelScore(4, score, !level4ResetUsed);
// --- END BUG FIX ---
const modalContainer = document.getElementById('level-8-modal-container');
const modalContent = document.getElementById('level-8-modal-content');
const modalTitle = document.getElementById('level-8-modal-title');
const modalFeedback = document.getElementById('level-8-feedback');
const modalButton = document.getElementById('level-8-modal-button');
modalTitle.textContent = '能量注入完成!';
modalFeedback.textContent = '沒錯,就是「Chromebook Plus」!';
modalButton.textContent = '前往下一關 (第五關)';
modalButton.onclick = () => {
hideLevel4Modal();
if (isReplaying) goToScreen(11); // NEW: Handle replay nav
else goToScreen(5);
};
modalContent.classList.remove('emergency-flash'); // Cleanup
/* --- (FIXED: This was the part that caused the 'else' error) --- */
/*
This block was incorrectly copy-pasted.
*/
/* --- (END OF FIX) --- */
modalContent.classList.add('success-flash'); // NEW
modalContainer.classList.remove('hidden');
}
<!-- (END OF FIX) -->
function hideLevel4Modal() { // NEW: Specific for L4
document.getElementById('level-8-modal-container').classList.add('hidden');
document.getElementById('level-8-modal-content').classList.remove('success-flash'); // NEW
}
function showPollEndModal(pollScore, pollClickCount) { // NEW: Accept score
const modalContainer = document.getElementById('level-8-modal-container');
const modalContent = document.getElementById('level-8-modal-content');
const modalTitle = document.getElementById('level-8-modal-title');
const modalFeedback = document.getElementById('level-8-feedback');
const modalButton = document.getElementById('level-8-modal-button');
modalTitle.textContent = '投票結束!';
modalFeedback.textContent = `你在20秒內點擊了 ${pollClickCount} 次!獲得 ${pollScore} 分!`; // NEW: Show score
modalButton.textContent = '前往最終挑戰!'; // UPDATED
modalButton.onclick = () => {
modalContainer.classList.add('hidden');
// OLD: updateProgressBar(9); // NEW: Mark level 9 complete (Handled by recordLevelScore)
// OLD: goToScreen(10); // Go to NEW ethics screen (10)
if (isReplaying) goToScreen(11); // NEW: Handle replay nav
else goToScreen(10);
};
modalContent.classList.remove('emergency-flash');
modalContent.classList.remove('success-flash'); // NEW: Cleanup
modalContainer.classList.remove('hidden');
}
function createFloatingHeart(event) {
const heart = document.createElement('div');
heart.innerHTML = '❤️';
heart.className = 'floating-heart';
const screenRect = document.getElementById('screen-9').getBoundingClientRect();
const x = event.clientX - screenRect.left;
const y = event.clientY - screenRect.top;
heart.style.left = `${x - 16}px`; // Adjust for heart size
heart.style.top = `${y - 16}px`;
document.getElementById('screen-9').appendChild(heart);
// Trigger animation
setTimeout(() => {
heart.style.transform = 'translateY(-100px)';
heart.style.opacity = '0';
}, 10);
// Remove from DOM
setTimeout(() => {
heart.remove();
}, 1600);
}
// --- END: Level 9 Poll Game Logic ---
// --- NEW: Level 10 Ethics Game Logic ---
const level10Cards = [
{
id: 0,
title: "激發靈感",
content: "學生使用 Gemini 針對特定主題(例如:環保)發想點子,產生心智圖或不同觀點。",
correctZone: "green",
hints: {
red: "哎呀,太嚴格了!激發創意是『綠燈區』鼓勵的用法喔。記住,AI 很適合作為創意的『起點』,而不是『終點』!",
yellow: "哎呀,太嚴格了!激發創意是『綠燈區』鼓勵的用法喔。記住,AI 很適合作為創意的『起點』,而不是『終點』!"
},
correctHint: "你抓到重點了!AI 是很棒的『個人化學習』工具,能幫助學生真正理解,是『綠燈區』鼓勵的用法。" // NEW
},
{
id: 1,
title: "正式考試",
content: "學生在期末考的申論題中,使用 AI 工具來撰寫答案。",
correctZone: "red",
hints: {
yellow: "等等!這太危險了!為了確保學習的真實性與公平性,在『正式考試』中,原則上『一律禁止』使用 AI。",
green: "等等!這太危險了!為了確保學習的真實性與公平性,在『正式考試』中,原則上『一律禁止』使用 AI。"
},
correctHint: "完全正確!為了確保公平性與學術誠信,『正式考試』中原則上禁止使用 AI。"
},
{
id: 2,
title: "輔助除錯 (Debug)",
content: "老師在程式設計課中,指導學生如何使用 AI 尋求程式碼的錯誤,並理解其建議。",
correctZone: "yellow",
hints: {
red: "也不用完全禁止啦!在『老師的指引』下,AI 是個很好的學習助手。這應該是『黃燈區』的範圍。",
green: "很接近了!但因為需要『老師的指引』來確保學生真正理解,而不只是盲目複製,所以這屬於『黃燈區』喔!"
},
correctHint: "答對了!關鍵在於學生必須能『解釋』AI的建議,而不只是複製貼上,因此需要老師指引。"
},
{
id: 3,
title: "最終成果",
content: "學生將 AI 生成的完整文章,當作自己的「最終專題報告」繳交。",
correctZone: "red",
hints: {
yellow: "不行!『最終成果』或『專題報告』強調的是個人原創性。過度依賴 AI 會削弱核心能力的培養,這屬於『紅燈區』喔!",
green: "不行!『最終成果』或『專題報告』強調的是個人原創性。過度依賴 AI 會削弱核心能力的培養,這屬於『紅燈區』喔!"
},
correctHint: "沒錯!『最終成果』強調個人觀點與創造力,不能完全依賴 AI,這會削弱核心能力的培養。"
},
{
id: 4,
title: "角色扮演",
content: "老師設計情境,讓 AI 扮演特定角色(如:科學家、歷史人物),學生與其進行訪談。",
correctZone: "yellow",
hints: {
red: "別擔心!在老師的『規範與引導』下,AI 角色扮演是很好的『黃燈區』應用,可以深化學習。",
green: "差一點!这个情境需要『老師設計』與『引導』學生進行有深度的討論,所以是需要師長指引的『黃燈區』。"
},
correctHint: "非常準確!『黃燈區』的重點在於『老師的設計與引導』,才能提出有深度的問題。"
},
{
id: 5,
title: "個人學習",
content: "學生使用可汗學院 (Khan Academy) 等具備 AI 功能的平台,進行客製化的學習路徑與練習。",
correctZone: "green",
hints: {
red: "別怕!AI 在『個人化學習與練習』上是個好夥伴,這是『綠燈區』鼓勵的用法,目的是幫助學生真正理解學習內容。",
yellow: "別怕!AI 在『個人化學習與練習』上是個好夥伴,這是『綠燈區』鼓勵的用法,目的是幫助學生真正理解學習內容。"
},
correctHint: "你抓到重點了!AI 是很棒的『個人化學習』工具,能幫助學生真正理解,是『綠燈區』鼓勵的用法。"
}
];
// --- NEW: Bonus Cards ---
const level10BonusCards = [
{
id: 6,
title: "涉及個人隱私",
content: "老師引導學生將自己的家庭住址、電話等個資輸入 AI,請 AI 幫忙規劃週末出遊。",
correctZone: "red",
hints: {
yellow: "非常危險!『個人隱私』資料絕對不可輸入公開的 AI 模型,這是『紅燈區』的基本原則!",
green: "非常危險!『個人隱私』資料絕對不可輸入公開的 AI 模型,這是『紅燈區』的基本原則!"
},
correctHint: "完全正確!『個人隱私』資料絕對不可輸入公開的 AI 模型,這是『紅燈區』的基本原則!"
},
{
id: 7,
title: "事實核查",
content: "老師請學生直接詢問 AI 某個爭議性新聞的「標準答案」,並以此作為唯一的報告來源。",
correctZone: "red",
hints: {
yellow: "不行喔!『事實核查』的核心是培養思辨能力,應先由學生獨立完成,而非直接尋求 AI 的『標準答案』。",
green: "不行喔!『事實核查』的核心是培養思辨能力,應先由學生獨立完成,而非直接尋求 AI 的『標準答案』。"
},
correctHint: "答對了!『事實核查』應由學生獨立完成,AI 只能當作對照的觀點之一,不能當作『標準答案』。"
},
{
id: 8,
title: "草稿撰寫",
content: "在老師指導下,學生使用 AI 生成文章大綱,並學習如何「下指令」(Prompting) 來修改與重寫內容。",
correctZone: "yellow",
hints: {
red: "不用這麼嚴格!在老師指導下,學習如何『下指令』重寫草稿,是『黃燈區』很好的練習喔。",
green: "很接近!但因為這需要『老師指導』學生如何下指令及修改,所以是『黃燈區』。"
},
correctHint: "沒錯!在『老師指導』下學習 Prompting 和修改草稿,是『黃燈區』的絕佳應用。"
},
{
id: 9,
title: "數據分析",
content: "在專題研究中,老師引導學生使用 AI 工具協助處理數據、製作圖表,並『解釋圖表背後的意義』。",
correctZone: "yellow",
hints: {
red: "太可惜了!在老師引導下,AI 是處理數據的好幫手,只要學生能『解釋意義』,就是很好的『黃燈區』應用。",
green: "差一點!關鍵在於『老師的引導』與學生必須『解釋意義』,而不只是操作工具,所以是『黃燈區』。"
},
correctHint: "答對了!重點是學生必須能『解釋圖表意義』,而不只是當個操作工具的『黑手』。"
},
{
id: 10,
title: "資料整理",
content: "學生利用 AI 快速彙整大量資料,作為研究的背景知識,並在老師指導下學習『驗證資訊真偽』。",
correctZone: "green",
hints: {
red: "別擔心!AI 用在『資料整理』是『綠燈區』的好用法,只要記得去『驗證真偽』就沒問題了!",
yellow: "別擔心!AI 用在『資料整理』是『綠燈區』的好用法,只要記得去『驗證真偽』就沒問題了!"
},
correctHint: "完全正確!AI 是『資料整理』的好幫手,是『綠燈區』的應用,但別忘了要『驗證真偽』喔!"
},
{
id: 11,
title: "語言學習發音",
content: "學生利用 AI 進行外語口說練習,獲得即時的發音與文法反饋,並內化為自己的語言能力。",
correctZone: "green",
hints: {
red: "別怕!AI 是提升語言溝通能力的絕佳夥伴,這是『綠燈區』鼓勵的用法!",
yellow: "別怕!AI 是提升語言溝通能力的絕佳夥伴,這是『綠燈區』鼓勵的用法!"
},
correctHint: "太棒了!AI 用在『語言學習』是『綠燈區』的絕佳應用,能有效提升溝通能力。"
}
];
let currentCardIndex_L10 = 0;
let correctCount_L10 = 0;
let draggableItem_L10 = null;
let touchDragClone_L10 = null;
const l10DropZones = []; // Will populate in init
// let level10Score = 0; // Already global
// let level10FirstTry = true; // Already global
// let level10CurrentCardTries = 0; // Already global
function initLevel10() {
currentCardIndex_L10 = 0;
correctCount_L10 = 0;
draggableItem_L10 = null;
touchDragClone_L10 = null;
level10CurrentCardTries = 0; // NEW: Reset card tries
level10Score = 0; // NEW: Reset L10 score
level10FirstTry = true; // NEW: Reset L10 first try flag
level10Cards.splice(6); // IMPORTANT: Reset to only 6 cards on init
document.getElementById('l10-progress-total').textContent = '6'; // Reset total
updateLevel10Progress();
// Clear any old listeners by cloning nodes
l10DropZones.forEach(zone => {
const newZone = zone.cloneNode(true);
zone.parentNode.replaceChild(newZone, zone);
});
l10DropZones.length = 0; // Empty the array
// Setup Drop Zones
const zones = ['l10-red-zone', 'l10-yellow-zone', 'l10-green-zone'];
zones.forEach(id => {
const zone = document.getElementById(id);
if (zone) {
zone.addEventListener('dragover', e => {
e.preventDefault();
zone.classList.add('over');
});
zone.addEventListener('dragleave', handleL10DragLeave);
zone.addEventListener('drop', handleL10Drop);
l10DropZones.push(zone);
}
});
showLevel10Card();
}
function handleL10DragLeave(e) {
e.currentTarget.classList.remove('over');
}
function updateLevel10Progress() {
document.getElementById('l10-progress-counter').textContent = correctCount_L10;
}
function showLevel10Card() {
const container = document.getElementById('l10-card-container');
container.innerHTML = ''; // Clear previous card
// --- NEW: Check for bonus round trigger ---
if (currentCardIndex_L10 === 6 && level10Cards.length === 6) {
// Just finished the first 6 cards
setTimeout(showLevel10BonusModal, 300);
return; // Stop here, wait for user input
}
if (currentCardIndex_L10 >= level10Cards.length) {
// Game complete (either 6 or 12)
// --- NEW: Check if this is final completion ---
if (!(currentCardIndex_L10 === 6 && level10Cards.length === 6)) {
// This means we are at 12/12, or we were at 6/6 and skipped bonus
playSound('success');
// OLD: updateProgressBar(10); // NEW: Mark level 10 complete
recordLevelScore(10, level10Score, level10FirstTry); // NEW: Record L10 score
setTimeout(() => goToScreen(11), 1000); // Go to final screen (11)
}
return;
}
const cardData = level10Cards[currentCardIndex_L10];
const card = document.createElement('div');
card.id = `l10-card-${cardData.id}`;
card.classList.add('l10-card');
card.setAttribute('draggable', 'true');
card.dataset.cardId = cardData.id;
card.innerHTML = `
<h4 class="text-xl font-bold mb-2 text-gray-800">${cardData.title}</h4>
<p class="text-gray-600">${cardData.content}</p>
`;
// Mouse Drag Events
card.addEventListener('dragstart', handleL10DragStart);
card.addEventListener('dragend', handleL10DragEnd);
// Touch Drag Events
card.addEventListener('touchstart', handleL10TouchStart, { passive: false });
card.addEventListener('touchmove', handleL10TouchMove, { passive: false });
card.addEventListener('touchend', handleL10TouchEnd);
container.appendChild(card);
}
function handleL10DragStart(e) {
draggableItem_L10 = e.target;
e.dataTransfer.setData('text/plain', e.target.dataset.cardId);
setTimeout(() => e.target.classList.add('dragging'), 0);
}
function handleL10DragEnd(e) {
if(e.target) e.target.classList.remove('dragging');
draggableItem_L10 = null;
l10DropZones.forEach(zone => zone.classList.remove('over'));
}
function handleL10TouchStart(e) {
draggableItem_L10 = e.currentTarget;
// Create clone
touchDragClone_L10 = draggableItem_L10.cloneNode(true);
touchDragClone_L10.style.position = 'absolute';
touchDragClone_L10.style.zIndex = '1000';
touchDragClone_L10.style.opacity = '0.8';
touchDragClone_L10.style.pointerEvents = 'none';
document.body.appendChild(touchDragClone_L10);
const touch = e.touches[0];
touchDragClone_L10.style.width = `${draggableItem_L10.offsetWidth}px`; // Match width
touchDragClone_L10.style.left = `${touch.clientX - touchDragClone_L10.offsetWidth / 2}px`;
touchDragClone_L10.style.top = `${touch.clientY - touchDragClone_L10.offsetHeight / 2}px`;
draggableItem_L10.classList.add('dragging');
}
function handleL10TouchMove(e) {
e.preventDefault();
if (!touchDragClone_L10) return;
const touch = e.touches[0];
touchDragClone_L10.style.left = `${touch.clientX - touchDragClone_L10.offsetWidth / 2}px`;
touchDragClone_L10.style.top = `${touch.clientY - touchDragClone_L10.offsetHeight / 2}px`;
l10DropZones.forEach(zone => {
const rect = zone.getBoundingClientRect();
if (touch.clientX > rect.left && touch.clientX < rect.right &&
touch.clientY > rect.top && touch.clientY < rect.bottom) {
zone.classList.add('over');
} else {
zone.classList.remove('over');
}
});
}
function handleL10TouchEnd(e) {
if (!touchDragClone_L10) return;
const touch = e.changedTouches[0];
touchDragClone_L10.style.display = 'none'; // Hide clone temporarily
const dropTarget = document.elementFromPoint(touch.clientX, touch.clientY);
touchDragClone_L10.style.display = 'block'; // Show again
let actualZone = dropTarget ? dropTarget.closest('.l10-drop-zone') : null;
if (actualZone) {
processL10Drop(draggableItem_L10, actualZone);
}
document.body.removeChild(touchDragClone_L10);
touchDragClone_L10 = null;
if(draggableItem_L10) draggableItem_L10.classList.remove('dragging');
l10DropZones.forEach(zone => zone.classList.remove('over'));
draggableItem_L10 = null;
}
function handleL10Drop(e) {
e.preventDefault();
const zone = e.currentTarget;
zone.classList.remove('over');
// Find the dragged item (either from mouse or touch)
let droppedCard = draggableItem_L10;
if (!droppedCard) {
const cardId = e.dataTransfer.getData('text/plain');
droppedCard = document.querySelector(`[data-card-id="${cardId}"]`);
}
if (droppedCard) {
processL10Drop(droppedCard, zone);
}
draggableItem_L10 = null; // Clear item after drop
}
function processL10Drop(card, zone) {
if (!card) return;
const cardId = card.dataset.cardId;
const cardData = level10Cards.find(c => c.id == cardId);
let droppedZoneType;
if (zone.id === 'l10-red-zone') droppedZoneType = 'red';
else if (zone.id === 'l10-yellow-zone') droppedZoneType = 'yellow';
else if (zone.id === 'l10-green-zone') droppedZoneType = 'green';
if (cardData.correctZone === droppedZoneType) {
// --- CORRECT ---
playSound('success');
// NEW: Add score
// OLD: addScore(level10CurrentCardTries === 0 ? 10 : 5);
if (level10CurrentCardTries > 0) level10FirstTry = false; // Mark as not first try
level10Score += (level10CurrentCardTries === 0 ? 10 : 5); // Add to partial score
const hint = cardData.correctHint || "答對了!";
showLevel10Modal(hint, true); // Show "Correct" modal
} else {
// --- INCORRECT ---
playSound('wrong');
level10CurrentCardTries++; // NEW: Increment wrong tries
zone.classList.add('flash-red');
setTimeout(() => zone.classList.remove('flash-red'), 1400);
// Show modal with the specific hint
const hint = cardData.hints[droppedZoneType];
showLevel10Modal(hint, false); // Show "Incorrect" modal
}
}
function showLevel10Modal(message, isCorrect) {
const modal = document.getElementById('level-10-modal-container');
const title = document.getElementById('level-10-modal-title');
const content = document.getElementById('level-10-modal-content');
const feedback = document.getElementById('level-10-feedback');
const button = document.getElementById('level-10-modal-button');
feedback.textContent = message;
if (isCorrect) {
title.textContent = "答對了!";
title.className = "text-2xl font-bold mb-4 text-green-600";
content.className = content.className.replace('border-red-500', 'border-green-500');
button.textContent = "下一題";
button.onclick = () => hideLevel10Modal(true);
} else {
title.textContent = "哎呀,不對喔!";
title.className = "text-2xl font-bold mb-4 text-red-600";
content.className = content.className.replace('border-green-500', 'border-red-500'); // Ensure it's red
button.textContent = "再試一次";
button.onclick = () => hideLevel10Modal(false);
}
modal.classList.remove('hidden');
}
function hideLevel10Modal(isCorrect) {
document.getElementById('level-10-modal-container').classList.add('hidden');
if (isCorrect) {
correctCount_L10++;
updateLevel10Progress();
level10CurrentCardTries = 0; // NEW: Reset tries for next card
// Find and remove the card that was just answered
const card = document.querySelector(`[data-card-id="${level10Cards[currentCardIndex_L10].id}"]`);
if (card) card.remove();
currentCardIndex_L10++;
setTimeout(showLevel10Card, 300); // Show next card (or bonus modal)
}
// if !isCorrect, do nothing, user tries again
}
function showLevel10BonusModal() {
const modal = document.getElementById('level-8-modal-container');
const title = document.getElementById('level-8-modal-title');
const feedback = document.getElementById('level-8-feedback');
const button = document.getElementById('level-8-modal-button');
const skipButton = document.getElementById('level-8-modal-button-skip');
title.textContent = "挑戰完成!";
feedback.textContent = "太棒了,你已完成基本審核!是否要挑戰 6 個額外的「進階情境」?";
button.textContent = "開始挑戰!";
button.onclick = startLevel10Bonus;
skipButton.textContent = "以後再說"; // Just in case
skipButton.style.display = 'block';
skipButton.onclick = skipLevel10Bonus;
modal.classList.remove('hidden');
}
function startLevel10Bonus() {
document.getElementById('level-8-modal-container').classList.add('hidden');
document.getElementById('level-8-modal-button-skip').style.display = 'none';
level10Cards.push(...level10BonusCards); // Add the 6 bonus cards
document.getElementById('l10-progress-total').textContent = '12'; // Update total
level10CurrentCardTries = 0; // NEW: Reset tries
showLevel10Card(); // This will now show card index 6
}
function skipLevel10Bonus() {
document.getElementById('level-8-modal-container').classList.add('hidden');
document.getElementById('level-8-modal-button-skip').style.display = 'none';
// OLD: updateProgressBar(10); // NEW: Mark level 10 complete even if skipped
recordLevelScore(10, level10Score, level10FirstTry); // NEW: Record L10 score (even if just 60)
goToScreen(11); // Go to final completion screen
}
// --- END: Level 10 Ethics Game Logic ---
// --- NEW: Scoreboard Logic ---
async function showScoreboard() {
if (!isFirebaseReady || !fbSDK) {
console.warn("Firebase not ready. Cannot show scoreboard.");
// alert() is blocked, just log to console
return;
}
// 重複使用 L8 的彈出視窗
const modalContainer = document.getElementById('level-8-modal-container');
const modalContent = document.getElementById('level-8-modal-content');
const modalTitle = document.getElementById('level-8-modal-title');
const modalFeedback = document.getElementById('level-8-feedback');
const modalButton = document.getElementById('level-8-modal-button');
const modalSkipButton = document.getElementById('level-8-modal-button-skip');
// 1. 顯示讀取中...
modalTitle.textContent = "排行榜 (今日戰況)";
modalFeedback.innerHTML = '<p class="text-lg">讀取中...</p>';
modalButton.textContent = "關閉";
modalButton.onclick = () => modalContainer.classList.add('hidden');
modalSkipButton.style.display = 'none';
modalContent.classList.remove('emergency-flash', 'success-flash');
modalContainer.classList.remove('hidden');
try {
// 2. 取得「今天凌晨 0 點」的時間戳記
const now = new Date();
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
const startOfTodayTimestamp = fbSDK.Timestamp.fromDate(startOfDay);
// 3. 查詢 Firebase
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const scoresCollectionRef = fbSDK.collection(db, `artifacts/${appId}/public/data/scores`);
// 查詢指令:找出 "lastUpdated" >= "今天凌晨" 的所有文件
const q = fbSDK.query(scoresCollectionRef, fbSDK.where("lastUpdated", ">=", startOfTodayTimestamp));
const querySnapshot = await fbSDK.getDocs(q);
let scores = [];
querySnapshot.forEach((doc) => {
scores.push(doc.data());
});
// 4. 在 JavaScript 中進行排序(由高到低)
scores.sort((a, b) => b.score - a.score);
// 5. 建立排行榜的 HTML 內容
let leaderboardHtml = '<ol class="list-decimal list-inside text-left space-y-2 max-h-60 overflow-y-auto">';
if (scores.length === 0) {
leaderboardHtml = '<p class="text-lg">今天還沒有人完成遊戲!</p>';
} else {
// --- FIX: 移除 .slice(0, 10) 來顯示當天所有成績 ---
scores.forEach((entry, index) => {
leaderboardHtml += `
<li class="p-2 rounded ${index === 0 ? 'bg-yellow-100 border border-yellow-300' : ''}">
<span class="font-bold text-lg text-blue-600">${entry.score} 分</span> -
<span class="text-gray-700">${entry.displayName}</span>
</li>
`;
});
}
leaderboardHtml += '</ol>';
modalFeedback.innerHTML = leaderboardHtml;
} catch (error) {
console.error("Error fetching scoreboard:", error);
modalFeedback.innerHTML = '<p class="text-red-500">讀取排行榜時發生錯誤。</p>';
}
}
// --- END NEW ---
</script>
</body>
</html>