Lashtw commited on
Commit
c0b6856
·
verified ·
1 Parent(s): 8cb7f12

Upload 5 files

Browse files
Files changed (5) hide show
  1. algebra.html +571 -0
  2. gemstone.html +694 -0
  3. harbor.html +654 -0
  4. index.html +180 -19
  5. philosophy.html +429 -0
algebra.html ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>數學探險島 - 代數之丘</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Noto Sans TC', sans-serif;
14
+ touch-action: none; /* 防止在觸控拖曳時滾動頁面 */
15
+ background-image: url('https://i.meee.com.tw/AF01kln.png');
16
+ background-size: cover;
17
+ background-position: center;
18
+ }
19
+ /* 自訂積木顏色 */
20
+ .block-a-squared { background-color: #fca5a5; } /* red-300 */
21
+ .block-b-squared { background-color: #818cf8; } /* indigo-400 */
22
+ .block-ab { background-color: #fcd34d; } /* amber-300 */
23
+ .block-distractor { background-color: #9ca3af; } /* gray-400 */
24
+
25
+ /* 拼圖區格線 */
26
+ #grid-container {
27
+ display: grid;
28
+ background-image:
29
+ linear-gradient(to right, #d1d5db 1px, transparent 1px),
30
+ linear-gradient(to bottom, #d1d5db 1px, transparent 1px);
31
+ background-size: var(--unit-size) var(--unit-size);
32
+ border: 2px solid #6b7280;
33
+ }
34
+ /* 拖曳中的複製物件 */
35
+ .dragging-clone {
36
+ position: absolute;
37
+ pointer-events: none; /* 讓滑鼠事件穿透複製物件 */
38
+ z-index: 1000;
39
+ opacity: 0.8;
40
+ border: 2px dashed #4f46e5;
41
+ }
42
+ /* 已放置在選項區的積木 */
43
+ .palette-block.placed {
44
+ opacity: 0.3;
45
+ cursor: not-allowed;
46
+ pointer-events: none;
47
+ }
48
+ /* 已放置在拼圖區的積木 */
49
+ .placed-block {
50
+ position: absolute;
51
+ cursor: pointer;
52
+ border: 2px solid #1f2937;
53
+ transition: all 0.2s ease-in-out;
54
+ }
55
+ .placed-block:hover {
56
+ transform: scale(1.05);
57
+ box-shadow: 0 0 15px rgba(0,0,0,0.5);
58
+ }
59
+ /* 過關時的特別框線 */
60
+ .highlight-win {
61
+ background-color: #fef9c3; /* yellow-100 */
62
+ border: 2px solid #f59e0b; /* amber-500 */
63
+ border-radius: 8px;
64
+ padding: 2px 6px;
65
+ display: inline-block;
66
+ }
67
+ /* 隱藏滾動條,但在觸控設備上仍可滾動 */
68
+ #block-palette::-webkit-scrollbar {
69
+ display: none;
70
+ }
71
+ #block-palette {
72
+ -ms-overflow-style: none; /* IE and Edge */
73
+ scrollbar-width: none; /* Firefox */
74
+ }
75
+ </style>
76
+ </head>
77
+ <body class="w-screen h-screen overflow-hidden flex items-center justify-center relative">
78
+
79
+ <!-- 任務說明畫面 -->
80
+ <div id="start-screen" class="absolute inset-0 w-screen h-screen flex items-center justify-center p-4 transition-opacity duration-500 bg-gray-900/50">
81
+ <div class="container mx-auto p-6 md:p-8 bg-white/90 backdrop-blur-sm rounded-xl shadow-2xl max-w-2xl text-center">
82
+ <h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-6">任務說明:代數之丘</h1>
83
+ <p class="text-lg text-gray-700 mb-4">歡迎來到代數之丘!在這裡,我們不用死背公式,而是用雙手「拼」出數學!</p>
84
+ <p class="text-lg text-gray-700 mb-8">你的任務是拖曳下方的彩色積木,將左邊的灰色正方形完全填滿,親身體驗 <span class="font-mono font-bold text-indigo-700">(a+b)² = a² + 2ab + b²</span> 這個公式是如何誕生的!</p>
85
+ <button id="start-button" class="w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
86
+ 開始挑戰
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <!-- 遊戲主畫面 -->
92
+ <div id="game-container" class="flex flex-row w-full h-full max-w-screen-xl mx-auto p-4 lg:p-8 gap-8 items-start transition-opacity duration-500 opacity-0 hidden">
93
+
94
+ <!-- 左欄:包含拼圖區和過關訊息 -->
95
+ <div class="w-1/3 flex flex-col items-center justify-start gap-6">
96
+ <div id="grid-container" class="relative bg-gray-200/80 backdrop-blur-sm shadow-xl rounded-lg">
97
+ <!-- 拼圖區的積木會由 JS 動態加入這裡 -->
98
+ </div>
99
+ <!-- 過關訊息與按鈕 (已移至此處) -->
100
+ <div id="win-message-container" class="w-full text-center p-4 bg-white/90 backdrop-blur-sm rounded-xl shadow-lg hidden">
101
+ <p class="text-2xl font-bold text-green-600">恭喜過關!</p>
102
+ <p id="win-formula-explanation" class="text-lg text-gray-700 mt-2"></p>
103
+ <button id="next-level-button" class="mt-4 w-full bg-indigo-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-indigo-700 transition-colors shadow-md">
104
+ 挑戰下一關
105
+ </button>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- 右欄:控制項 -->
110
+ <div id="controls-container" class="w-2/3 flex-shrink-0 flex flex-col bg-white/90 backdrop-blur-sm rounded-xl shadow-lg p-6 h-full">
111
+ <h1 class="text-3xl font-bold text-gray-800 text-center">代數之丘</h1>
112
+ <hr class="my-4">
113
+
114
+ <!-- 算式區 -->
115
+ <div class="bg-blue-50/80 p-4 rounded-lg mb-4 space-y-2">
116
+ <!-- 題目行 -->
117
+ <div class="grid grid-cols-4 items-baseline">
118
+ <p class="text-lg text-gray-600 font-semibold col-span-1">題目:</p>
119
+ <p id="equation-title" class="col-span-3 text-2xl font-bold text-indigo-700 text-center"></p>
120
+ </div>
121
+ <!-- 面積結果行 -->
122
+ <div class="grid grid-cols-4 items-baseline">
123
+ <p class="text-lg text-gray-600 font-semibold col-span-1">面積結果:</p>
124
+ <div id="equation-result" class="col-span-3 text-2xl font-mono text-gray-800 text-center"></div>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- 說明文字 -->
129
+ <div id="instructions" class="text-center text-gray-500 mb-4 text-sm">
130
+ <p>請從下方拖曳積木,拼滿左邊的正方形。</p>
131
+ <p class="mt-1">💡 <span class="font-semibold">提示:</span>點擊已放置的積木可以將它移除。</p>
132
+ </div>
133
+
134
+ <!-- 積木選項區 (包含捲動按鈕) -->
135
+ <div class="relative mt-auto">
136
+ <div id="block-palette" class="flex flex-row flex-nowrap overflow-x-auto items-center gap-4 p-4 bg-gray-100/80 rounded-md">
137
+ <!-- 積木選項會由 JS 動態加入這裡 -->
138
+ </div>
139
+ <!-- 左捲動按鈕 -->
140
+ <button id="scroll-left-button" class="absolute left-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-1 shadow-md hidden transition-opacity">
141
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
142
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
143
+ </svg>
144
+ </button>
145
+ <!-- 右捲動按鈕 -->
146
+ <button id="scroll-right-button" class="absolute right-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-1 shadow-md hidden transition-opacity">
147
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
148
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
149
+ </svg>
150
+ </button>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- 秘密揭曉畫面 (從 secret.html 整合進來) -->
156
+ <div id="secret-view" class="absolute inset-0 w-screen h-screen flex items-center justify-center p-4 opacity-0 hidden transition-opacity duration-500 bg-gray-900/50">
157
+ <div class="container mx-auto p-6 md:p-8 bg-white rounded-xl shadow-2xl max-w-4xl text-center">
158
+ <h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-8">代數之丘最大的秘密</h1>
159
+
160
+ <div class="grid md:grid-cols-3 gap-6 md:gap-8 mb-8">
161
+ <!-- Image 1 -->
162
+ <div class="flex flex-col items-center p-2 border rounded-lg">
163
+ <img src="https://i.meee.com.tw/Sx68qrS.png" alt="拼圖 (6+4)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/fca5a5/ffffff?text=(6%2B4)%C2%B2';">
164
+ <p class="text-lg md:text-xl font-mono font-bold">(6+4)² = 6² + <span class="highlight-win">2×6×4</span> + 4²</p>
165
+ </div>
166
+ <!-- Image 2 -->
167
+ <div class="flex flex-col items-center p-2 border rounded-lg">
168
+ <img src="https://i.meee.com.tw/7gGAB80.png" alt="拼圖 (4+9)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/818cf8/ffffff?text=(4%2B9)%C2%B2';">
169
+ <p class="text-lg md:text-xl font-mono font-bold">(4+9)² = 4² + <span class="highlight-win">2×4×9</span> + 9²</p>
170
+ </div>
171
+ <!-- Image 3 -->
172
+ <div class="flex flex-col items-center p-2 border rounded-lg">
173
+ <img src="https://i.meee.com.tw/0hdfRvP.png" alt="拼圖 (7+5)²" class="rounded-md shadow-md mb-4 w-full" onerror="this.onerror=null;this.src='https://placehold.co/400x400/fcd34d/ffffff?text=(7%2B5)%C2%B2';">
174
+ <p class="text-lg md:text-xl font-mono font-bold">(7+5)² = 7² + <span class="highlight-win">2×7×5</span> + 5²</p>
175
+ </div>
176
+ </div>
177
+
178
+ <div class="text-left text-base md:text-lg text-gray-700 space-y-4 border-t-2 pt-8">
179
+ <p>選舉最大的祕密就是:票多的贏,票少的輸...<span class="italic text-gray-500">咳咳不是這個啦</span></p>
180
+ <p class="text-xl md:text-2xl font-bold text-red-600">代數之丘的最大秘密就是:(a+b)²展開後一定會有ab這項,而且還是<span class="underline decoration-wavy decoration-amber-500">2ab</span>,學會這個,這單元就已經學會一半了!</p>
181
+ <p class="font-semibold text-indigo-700">(請在學習單上做紀錄!)</p>
182
+ </div>
183
+
184
+ <div class="mt-12">
185
+ <a href="index.html" class="inline-block w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
186
+ 回到探險島地圖
187
+ </a>
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+
193
+ <script>
194
+ document.addEventListener('DOMContentLoaded', () => {
195
+ // --- 常數與設定 ---
196
+ const UNIT_SIZE = 30; // 每個單位格的像素大小 (px)
197
+ const levels = [
198
+ { a: 6, b: 4, distractors: [] },
199
+ { a: 4, b: 9, distractors: [{w: 5, h: 8}, {w: 7, h: 6}] },
200
+ { a: 7, b: 5, distractors: [{w: 8, h: 6}, {w: 6, h: 8}] }
201
+ ];
202
+ let currentLevel = 0;
203
+
204
+ // --- DOM 元素 ---
205
+ const startScreen = document.getElementById('start-screen');
206
+ const startButton = document.getElementById('start-button');
207
+ const gameContainer = document.getElementById('game-container');
208
+ const secretView = document.getElementById('secret-view');
209
+ const gridContainer = document.getElementById('grid-container');
210
+ const blockPalette = document.getElementById('block-palette');
211
+ const equationTitle = document.getElementById('equation-title');
212
+ const equationResult = document.getElementById('equation-result');
213
+ const winMessageContainer = document.getElementById('win-message-container');
214
+ const winFormulaExplanation = document.getElementById('win-formula-explanation');
215
+ const nextLevelButton = document.getElementById('next-level-button');
216
+ const scrollLeftButton = document.getElementById('scroll-left-button');
217
+ const scrollRightButton = document.getElementById('scroll-right-button');
218
+
219
+ // --- 遊戲狀態 ---
220
+ let gridState = []; // 2D 陣列,記錄拼圖區每個格子的佔用情況
221
+ let placedBlocks = new Map(); // 記錄已放置的積木 (key: placedId, value: { el, originalId, type })
222
+ let draggingElement = null; // 目前正在拖曳的原始積木
223
+ let clone = null; // 拖曳時的複製物件
224
+ let offset = { x: 0, y: 0 }; // 拖曳時的滑鼠偏移量
225
+
226
+ // --- 核心函式 ---
227
+
228
+ function initLevel(levelIndex) {
229
+ currentLevel = levelIndex;
230
+ const level = levels[levelIndex];
231
+ const totalSize = level.a + level.b;
232
+
233
+ gridContainer.innerHTML = '';
234
+ blockPalette.innerHTML = '';
235
+ winMessageContainer.classList.add('hidden');
236
+ placedBlocks.clear();
237
+
238
+ gridContainer.style.width = `${totalSize * UNIT_SIZE}px`;
239
+ gridContainer.style.height = `${totalSize * UNIT_SIZE}px`;
240
+ gridContainer.style.setProperty('--unit-size', `${UNIT_SIZE}px`);
241
+
242
+ gridState = Array(totalSize).fill(null).map(() => Array(totalSize).fill(null));
243
+
244
+ createBlocks(level);
245
+ updateEquation();
246
+
247
+ if (currentLevel === levels.length - 1) {
248
+ nextLevelButton.textContent = '查看代數的秘密';
249
+ } else {
250
+ nextLevelButton.textContent = '挑戰下一關';
251
+ }
252
+
253
+ // 延遲執行以確保 DOM 尺寸已更新
254
+ setTimeout(updateScrollButtonsVisibility, 100);
255
+ }
256
+
257
+ function createBlocks(level) {
258
+ const { a, b, distractors } = level;
259
+ const blocksData = [
260
+ { id: 'a_squared', w: a, h: a, type: 'correct', class: 'block-a-squared', label: `${a}×${a}` },
261
+ { id: 'b_squared', w: b, h: b, type: 'correct', class: 'block-b-squared', label: `${b}×${b}` },
262
+ { id: 'ab1', w: a, h: b, type: 'correct', class: 'block-ab', label: `${a}×${b}` },
263
+ { id: 'ab2', w: b, h: a, type: 'correct', class: 'block-ab', label: `${b}×${a}` },
264
+ ...distractors.map((d, i) => ({
265
+ id: `distractor_${i}`, w: d.w, h: d.h, type: 'distractor', class: 'block-distractor', label: `${d.w}×${d.h}`
266
+ }))
267
+ ];
268
+
269
+ blocksData.sort(() => Math.random() - 0.5);
270
+
271
+ blocksData.forEach(data => {
272
+ const blockEl = document.createElement('div');
273
+ blockEl.id = `palette-${data.id}`;
274
+ blockEl.className = `palette-block flex-shrink-0 ${data.class} rounded-md shadow-sm cursor-grab flex items-center justify-center text-white font-bold text-base`;
275
+ blockEl.style.width = `${data.w * UNIT_SIZE}px`;
276
+ blockEl.style.height = `${data.h * UNIT_SIZE}px`;
277
+ blockEl.textContent = data.label;
278
+
279
+ blockEl.dataset.id = data.id;
280
+ blockEl.dataset.w = data.w;
281
+ blockEl.dataset.h = data.h;
282
+ blockEl.dataset.type = data.type;
283
+ blockEl.dataset.class = data.class;
284
+
285
+ blockPalette.appendChild(blockEl);
286
+ });
287
+ }
288
+
289
+ function updateEquation() {
290
+ const level = levels[currentLevel];
291
+ equationTitle.textContent = `(${level.a} + ${level.b})²`;
292
+
293
+ const placedCorrectBlocks = Array.from(placedBlocks.values()).filter(b => b.type === 'correct');
294
+
295
+ let parts = [];
296
+ if (placedCorrectBlocks.some(b => b.originalId === 'a_squared')) parts.push(`${level.a}²`);
297
+ if (placedCorrectBlocks.some(b => b.originalId === 'b_squared')) parts.push(`${level.b}²`);
298
+
299
+ const abCount = placedCorrectBlocks.filter(b => b.originalId.startsWith('ab')).length;
300
+ if (abCount === 1) parts.push(`(${level.a}×${level.b})`);
301
+ if (abCount === 2) parts.push(`2(${level.a}×${level.b})`);
302
+
303
+ if (parts.length > 0) {
304
+ equationResult.innerHTML = parts.join(' + ');
305
+ } else {
306
+ equationResult.innerHTML = '...';
307
+ }
308
+ }
309
+
310
+ // --- 拖曳事件處理 ---
311
+
312
+ function onDragStart(e) {
313
+ const target = e.target.closest('.palette-block');
314
+ if (!target || target.classList.contains('placed')) return;
315
+
316
+ e.preventDefault();
317
+ draggingElement = target;
318
+
319
+ const rect = target.getBoundingClientRect();
320
+ const pointer = getPointer(e);
321
+ offset.x = pointer.x - rect.left;
322
+ offset.y = pointer.y - rect.top;
323
+
324
+ clone = target.cloneNode(true);
325
+ clone.classList.remove('palette-block');
326
+ clone.classList.add('dragging-clone');
327
+ clone.style.width = `${target.dataset.w * UNIT_SIZE}px`;
328
+ clone.style.height = `${target.dataset.h * UNIT_SIZE}px`;
329
+ document.body.appendChild(clone);
330
+
331
+ moveClone(pointer.x, pointer.y);
332
+
333
+ document.addEventListener('mousemove', onDragMove);
334
+ document.addEventListener('touchmove', onDragMove, { passive: false });
335
+ document.addEventListener('mouseup', onDragEnd);
336
+ document.addEventListener('touchend', onDragEnd);
337
+ }
338
+
339
+ function onDragMove(e) {
340
+ if (!clone) return;
341
+ e.preventDefault();
342
+ const pointer = getPointer(e);
343
+ moveClone(pointer.x, pointer.y);
344
+ }
345
+
346
+ function onDragEnd(e) {
347
+ if (!draggingElement || !clone) return;
348
+
349
+ const gridRect = gridContainer.getBoundingClientRect();
350
+ const pointer = getPointer(e);
351
+
352
+ const blockTopLeftX = pointer.x - gridRect.left - offset.x;
353
+ const blockTopLeftY = pointer.y - gridRect.top - offset.y;
354
+
355
+ const gridX = Math.round(blockTopLeftX / UNIT_SIZE);
356
+ const gridY = Math.round(blockTopLeftY / UNIT_SIZE);
357
+
358
+ const w = parseInt(draggingElement.dataset.w);
359
+ const h = parseInt(draggingElement.dataset.h);
360
+ const type = draggingElement.dataset.type;
361
+
362
+ if (canPlace(gridX, gridY, w, h, type)) {
363
+ placeBlock(gridX, gridY, w, h);
364
+ }
365
+
366
+ document.body.removeChild(clone);
367
+ clone = null;
368
+ draggingElement = null;
369
+ document.removeEventListener('mousemove', onDragMove);
370
+ document.removeEventListener('touchmove', onDragMove);
371
+ document.removeEventListener('mouseup', onDragEnd);
372
+ document.removeEventListener('touchend', onDragEnd);
373
+ }
374
+
375
+ function canPlace(gridX, gridY, w, h, type) {
376
+ const totalSize = levels[currentLevel].a + levels[currentLevel].b;
377
+
378
+ if (currentLevel === 1 && type === 'distractor') {
379
+ return false;
380
+ }
381
+
382
+ if (gridX < 0 || gridY < 0 || gridX + w > totalSize || gridY + h > totalSize) {
383
+ return false;
384
+ }
385
+
386
+ for (let i = gridY; i < gridY + h; i++) {
387
+ for (let j = gridX; j < gridX + w; j++) {
388
+ if (i >= totalSize || j >= totalSize || gridState[i][j] !== null) {
389
+ return false;
390
+ }
391
+ }
392
+ }
393
+ return true;
394
+ }
395
+
396
+ function placeBlock(gridX, gridY, w, h) {
397
+ const placedId = `placed-${Date.now()}`;
398
+
399
+ for (let i = gridY; i < gridY + h; i++) {
400
+ for (let j = gridX; j < gridX + w; j++) {
401
+ gridState[i][j] = placedId;
402
+ }
403
+ }
404
+
405
+ const placedEl = document.createElement('div');
406
+ placedEl.id = placedId;
407
+ placedEl.className = `placed-block ${draggingElement.dataset.class} flex items-center justify-center text-white font-bold`;
408
+ placedEl.style.left = `${gridX * UNIT_SIZE}px`;
409
+ placedEl.style.top = `${gridY * UNIT_SIZE}px`;
410
+ placedEl.style.width = `${w * UNIT_SIZE}px`;
411
+ placedEl.style.height = `${h * UNIT_SIZE}px`;
412
+ placedEl.textContent = `${w}×${h}`;
413
+ gridContainer.appendChild(placedEl);
414
+
415
+ placedBlocks.set(placedId, {
416
+ el: placedEl,
417
+ originalId: draggingElement.dataset.id,
418
+ type: draggingElement.dataset.type,
419
+ gridX, gridY, w, h
420
+ });
421
+
422
+ draggingElement.classList.add('placed');
423
+ placedEl.addEventListener('click', () => removeBlock(placedId));
424
+
425
+ updateEquation();
426
+ checkWinCondition();
427
+ }
428
+
429
+ function removeBlock(placedId) {
430
+ const block = placedBlocks.get(placedId);
431
+ if (!block) return;
432
+
433
+ gridContainer.removeChild(block.el);
434
+
435
+ for (let i = block.gridY; i < block.gridY + block.h; i++) {
436
+ for (let j = block.gridX; j < block.gridX + block.w; j++) {
437
+ if (gridState[i][j] === placedId) {
438
+ gridState[i][j] = null;
439
+ }
440
+ }
441
+ }
442
+
443
+ placedBlocks.delete(placedId);
444
+
445
+ const originalBlock = document.getElementById(`palette-${block.originalId}`);
446
+ if (originalBlock) {
447
+ originalBlock.classList.remove('placed');
448
+ }
449
+
450
+ updateEquation();
451
+ }
452
+
453
+ function checkWinCondition() {
454
+ const level = levels[currentLevel];
455
+ const totalSize = level.a + level.b;
456
+
457
+ const placedCorrectCount = Array.from(placedBlocks.values()).filter(b => b.type === 'correct').length;
458
+
459
+ if (placedCorrectCount !== 4) return;
460
+
461
+ let isFull = true;
462
+ for (let i = 0; i < totalSize; i++) {
463
+ for (let j = 0; j < totalSize; j++) {
464
+ if (gridState[i][j] === null) {
465
+ isFull = false;
466
+ break;
467
+ }
468
+ }
469
+ if (!isFull) break;
470
+ }
471
+
472
+ if (isFull) {
473
+ showWinState();
474
+ }
475
+ }
476
+
477
+ function showWinState() {
478
+ const level = levels[currentLevel];
479
+ const finalFormula = `(${level.a}+${level.b})² = ${level.a}² + <span class="highlight-win">2×${level.a}×${level.b}</span> + ${level.b}²`;
480
+ winFormulaExplanation.innerHTML = finalFormula;
481
+ winMessageContainer.classList.remove('hidden');
482
+
483
+ blockPalette.style.pointerEvents = 'none';
484
+ gridContainer.style.pointerEvents = 'none';
485
+ }
486
+
487
+ // --- 捲動與輔助函式 ---
488
+
489
+ function updateScrollButtonsVisibility() {
490
+ const palette = blockPalette;
491
+ const scrollLeft = palette.scrollLeft;
492
+ const scrollWidth = palette.scrollWidth;
493
+ const clientWidth = palette.clientWidth;
494
+
495
+ if (scrollWidth <= clientWidth) {
496
+ scrollLeftButton.classList.add('hidden');
497
+ scrollRightButton.classList.add('hidden');
498
+ return;
499
+ }
500
+
501
+ if (scrollLeft > 0) {
502
+ scrollLeftButton.classList.remove('hidden');
503
+ } else {
504
+ scrollLeftButton.classList.add('hidden');
505
+ }
506
+
507
+ if (scrollLeft < scrollWidth - clientWidth - 1) {
508
+ scrollRightButton.classList.remove('hidden');
509
+ } else {
510
+ scrollRightButton.classList.add('hidden');
511
+ }
512
+ }
513
+
514
+ function getPointer(e) {
515
+ if (e.touches && e.touches.length > 0) {
516
+ return { x: e.touches[0].clientX, y: e.touches[0].clientY };
517
+ }
518
+ return { x: e.clientX, y: e.clientY };
519
+ }
520
+
521
+ function moveClone(x, y) {
522
+ if (!clone) return;
523
+ clone.style.left = `${x - offset.x}px`;
524
+ clone.style.top = `${y - offset.y}px`;
525
+ }
526
+
527
+ // --- 事件監聽 ---
528
+ startButton.addEventListener('click', () => {
529
+ startScreen.style.opacity = '0';
530
+ gameContainer.classList.remove('hidden');
531
+ setTimeout(() => {
532
+ startScreen.classList.add('hidden');
533
+ gameContainer.style.opacity = '1';
534
+ initLevel(currentLevel);
535
+ }, 500);
536
+ });
537
+
538
+ blockPalette.addEventListener('mousedown', onDragStart);
539
+ blockPalette.addEventListener('touchstart', onDragStart, { passive: false });
540
+ blockPalette.addEventListener('scroll', updateScrollButtonsVisibility);
541
+ window.addEventListener('resize', updateScrollButtonsVisibility);
542
+
543
+ scrollLeftButton.addEventListener('click', () => {
544
+ blockPalette.scrollBy({ left: -200, behavior: 'smooth' });
545
+ });
546
+
547
+ scrollRightButton.addEventListener('click', () => {
548
+ blockPalette.scrollBy({ left: 200, behavior: 'smooth' });
549
+ });
550
+
551
+ nextLevelButton.addEventListener('click', () => {
552
+ if (currentLevel < levels.length - 1) {
553
+ initLevel(currentLevel + 1);
554
+ blockPalette.style.pointerEvents = 'auto';
555
+ gridContainer.style.pointerEvents = 'auto';
556
+ } else {
557
+ // 顯示秘密畫面
558
+ gameContainer.style.opacity = '0';
559
+ secretView.classList.remove('hidden');
560
+ setTimeout(() => {
561
+ gameContainer.classList.add('hidden');
562
+ secretView.style.opacity = '1';
563
+ }, 500);
564
+ }
565
+ });
566
+
567
+ });
568
+ </script>
569
+
570
+ </body>
571
+ </html>
gemstone.html ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>數學探險島 - 寶石洞窟</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Noto Sans TC', sans-serif;
14
+ background-image: url('https://i.meee.com.tw/pI0c5ue.png');
15
+ background-size: cover;
16
+ background-position: center;
17
+ }
18
+ canvas {
19
+ background-color: #2d241d;
20
+ display: block;
21
+ border-radius: 0.5rem;
22
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
23
+ }
24
+ .action-button {
25
+ transition: all 0.2s ease-in-out;
26
+ box-shadow: 0 5px #995f00;
27
+ }
28
+ .action-button:active {
29
+ transform: translateY(3px);
30
+ box-shadow: 0 2px #995f00;
31
+ }
32
+ .move-button {
33
+ transition: all 0.2s ease-in-out;
34
+ box-shadow: 0 5px #1e3a8a;
35
+ }
36
+ .move-button:active {
37
+ transform: translateY(3px);
38
+ box-shadow: 0 2px #1e3a8a;
39
+ }
40
+ .gem-button {
41
+ transition: transform 0.1s ease-in-out;
42
+ }
43
+ .gem-button:active {
44
+ transform: scale(0.9);
45
+ }
46
+ .secret-bg {
47
+ background-image: url('https://i.meee.com.tw/EKZpYKI.png');
48
+ background-size: cover;
49
+ background-position: center;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body class="flex items-center justify-center h-screen overflow-hidden">
54
+
55
+ <div id="container" class="w-full max-w-md mx-auto text-white p-4 flex flex-col h-full">
56
+
57
+ <!-- 引導畫面 -->
58
+ <div id="start-screen" class="flex flex-col items-center justify-center text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto">
59
+ <h1 class="text-4xl font-bold text-amber-300 mb-4">寶石洞窟</h1>
60
+ <p class="text-lg text-gray-200 mb-8">這是一個已經廢棄的寶石洞窟,接下來你必須駕駛一台礦車,成功閃避障礙物來獲得璀璨寶石!</p>
61
+ <button id="start-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-4 px-8 rounded-lg text-2xl">
62
+ 開始採礦
63
+ </button>
64
+ </div>
65
+
66
+ <!-- 遊戲畫面 -->
67
+ <div id="game-screen" class="hidden flex-col h-full">
68
+ <div id="canvas-wrapper" class="relative">
69
+ <canvas id="gameCanvas"></canvas>
70
+ <!-- 操作說明畫面 -->
71
+ <div id="instructions-screen" class="hidden absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center text-center p-4 rounded-lg">
72
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">操作說明</h2>
73
+ <div class="space-y-4 text-lg">
74
+ <p>點擊畫面<span class="text-blue-400 font-bold">左半邊</span>或按<span class="text-blue-400 font-bold">左方向鍵</span> ← 向左移動</p>
75
+ <p>點擊畫面<span class="text-blue-400 font-bold">右半邊</span>或按<span class="text-blue-400 font-bold">右方向鍵</span> → 向右移動</p>
76
+ <p class="mt-4">也可以使用下方的按鈕操作!</p>
77
+ </div>
78
+ <button id="play-game-button" class="action-button bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
79
+ 了解!
80
+ </button>
81
+ </div>
82
+ <!-- 遊戲結束畫面 -->
83
+ <div id="game-over-screen" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center text-center p-4 rounded-lg">
84
+ <h2 class="text-4xl font-bold text-red-500 mb-4">挑戰失敗!</h2>
85
+ <p class="text-xl mb-6">礦車撞毀了!</p>
86
+ <button id="restart-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-3 px-6 rounded-lg text-xl">
87
+ 重新挑戰
88
+ </button>
89
+ </div>
90
+ <!-- 遊戲獲勝畫面 -->
91
+ <div id="win-screen" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center text-center p-4 rounded-lg">
92
+ <h2 class="text-4xl font-bold text-green-400 mb-4">成功!</h2>
93
+ <p class="text-xl mb-2">你成功抵達了寶石區域!</p>
94
+ <p id="win-perfect-dodges" class="text-2xl text-amber-300 font-bold mb-6"></p>
95
+ <button id="continue-button" class="action-button bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg text-xl">
96
+ 繼續前進
97
+ </button>
98
+ </div>
99
+ </div>
100
+ <!-- 移動按鈕 -->
101
+ <div id="controls" class="flex justify-between mt-4">
102
+ <button id="move-left-button" class="move-button bg-blue-600 hover:bg-blue-700 text-white font-bold p-4 rounded-lg w-24 h-16">
103
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
104
+ </button>
105
+ <button id="move-right-button" class="move-button bg-blue-600 hover:bg-blue-700 text-white font-bold p-4 rounded-lg w-24 h-16">
106
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
107
+ </button>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- 寶石解謎畫面 -->
112
+ <div id="puzzle-screen" class="hidden flex-col h-full p-6 bg-black bg-opacity-60 rounded-lg">
113
+ <div class="flex-grow overflow-y-auto">
114
+ <h1 class="text-3xl font-bold text-amber-300 text-center mb-4">寶石組合挑戰</h1>
115
+ <p class="text-center mb-4">觀察大寶石,點擊下方的小寶石,組合出正確的配方!</p>
116
+
117
+ <div class="flex justify-center mb-4">
118
+ <img id="large-gem-image" src="" class="h-40 object-contain" alt="[大寶石的Image]">
119
+ </div>
120
+
121
+ <div id="answer-slots" class="bg-gray-900/50 min-h-[80px] rounded-lg p-2 flex flex-wrap items-center justify-center gap-2 mb-4 border-2 border-amber-400"></div>
122
+
123
+ <div id="small-gem-options" class="flex justify-center items-center gap-4 mb-6 flex-wrap"></div>
124
+
125
+ <div id="puzzle-feedback" class="text-center text-xl font-bold min-h-[32px] mb-4"></div>
126
+ </div>
127
+
128
+ <div class="flex gap-4 mt-auto pt-4">
129
+ <button id="reset-puzzle-button" class="w-1/2 bg-gray-500 hover:bg-gray-600 text-white font-bold py-3 rounded-lg">重置</button>
130
+ <button id="check-puzzle-button" class="w-1/2 bg-green-500 hover:bg-green-600 text-white font-bold py-3 rounded-lg">鑑定寶石</button>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- 過場畫面 -->
135
+ <div id="intermission-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto">
136
+ <h1 class="text-3xl font-bold text-amber-300 mb-4">準備挑戰下一區!</h1>
137
+ <p class="text-lg text-gray-200 mb-8">前方的礦道更加危險,速度會更快!</p>
138
+ <button id="start-next-stage-button" class="action-button bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-4 px-8 rounded-lg text-2xl">
139
+ 繼續採礦
140
+ </button>
141
+ </div>
142
+
143
+ <!-- 最終通關畫面 -->
144
+ <div id="final-win-screen" class="hidden flex-col text-center p-8 bg-black bg-opacity-60 rounded-lg my-auto">
145
+ <h1 class="text-4xl font-bold text-green-400 mb-4">恭喜你完成所有寶石挑戰!</h1>
146
+ <button id="unlock-secret-button" class="action-button bg-purple-600 hover:bg-purple-700 text-white font-bold py-4 px-8 rounded-lg text-2xl">
147
+ 解鎖寶石洞窟的秘密
148
+ </button>
149
+ </div>
150
+ </div>
151
+
152
+ <!-- 秘密揭曉畫面 (獨立於 container 之外) -->
153
+ <div id="secret-screen" class="hidden absolute inset-0 w-screen h-screen secret-bg p-8 text-white flex-col items-center justify-center">
154
+ <div class="w-full max-w-2xl bg-black bg-opacity-70 p-8 rounded-xl">
155
+ <div id="secret-question">
156
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">你認為這個洞窟的秘密是...</h2>
157
+ <div class="space-y-4 text-lg">
158
+ <button data-choice="A" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">A. 如何完美駕駛礦車,一次通關</button>
159
+ <button data-choice="B" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">B. 如何增加完美閃避的次數</button>
160
+ <button data-choice="C" class="secret-choice w-full text-left p-4 bg-white/10 hover:bg-white/20 rounded-lg transition-colors">C. 寶石分析術</button>
161
+ </div>
162
+ <p id="secret-feedback" class="mt-6 min-h-[28px] text-xl"></p>
163
+ </div>
164
+ <div id="secret-reveal" class="hidden text-left space-y-4">
165
+ <p class="text-lg">從最小的寶石到浩瀚的星辰,萬事萬物都由更基本的元素構成。你學會的『寶石分析術』,在數學的世界裡,它被稱為『因式分解』。</p>
166
+ <p class="text-2xl font-bold text-amber-300">這個洞窟的秘密就是:學會分析與拆解,就是看透事物本質的第一步。</p>
167
+ <p class="text-lg">無論是分析一顆寶石的成分、一道數學題的結構,還是未來你遇到的任何複雜問題,這種化繁為簡的智慧,才是你從這裡帶走、永不消失的真正寶藏。</p>
168
+ </div>
169
+ <div class="mt-12 text-center">
170
+ <a href="index.html" id="back-to-map-button" class="action-button inline-block bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg text-xl">
171
+ 回到探險島地圖
172
+ </a>
173
+ </div>
174
+ </div>
175
+ </div>
176
+
177
+
178
+ <script>
179
+ document.addEventListener('DOMContentLoaded', () => {
180
+ // --- 畫面元素 ---
181
+ const container = document.getElementById('container');
182
+ const startScreen = document.getElementById('start-screen');
183
+ const gameScreen = document.getElementById('game-screen');
184
+ const puzzleScreen = document.getElementById('puzzle-screen');
185
+ const intermissionScreen = document.getElementById('intermission-screen');
186
+ const finalWinScreen = document.getElementById('final-win-screen');
187
+ const secretScreen = document.getElementById('secret-screen');
188
+ const startButton = document.getElementById('start-button');
189
+ const instructionsScreen = document.getElementById('instructions-screen');
190
+ const playGameButton = document.getElementById('play-game-button');
191
+ const gameOverScreen = document.getElementById('game-over-screen');
192
+ const winScreen = document.getElementById('win-screen');
193
+ const winPerfectDodges = document.getElementById('win-perfect-dodges');
194
+ const restartButton = document.getElementById('restart-button');
195
+ const continueButton = document.getElementById('continue-button');
196
+ const startNextStageButton = document.getElementById('start-next-stage-button');
197
+ const unlockSecretButton = document.getElementById('unlock-secret-button');
198
+ const canvas = document.getElementById('gameCanvas');
199
+ const canvasWrapper = document.getElementById('canvas-wrapper');
200
+ const moveLeftButton = document.getElementById('move-left-button');
201
+ const moveRightButton = document.getElementById('move-right-button');
202
+ const ctx = canvas.getContext('2d');
203
+
204
+ // --- 寶石解謎元素 ---
205
+ const largeGemImage = document.getElementById('large-gem-image');
206
+ const answerSlots = document.getElementById('answer-slots');
207
+ const smallGemOptions = document.getElementById('small-gem-options');
208
+ const puzzleFeedback = document.getElementById('puzzle-feedback');
209
+ const resetPuzzleButton = document.getElementById('reset-puzzle-button');
210
+ const checkPuzzleButton = document.getElementById('check-puzzle-button');
211
+
212
+ // --- 秘密揭曉元素 ---
213
+ const secretQuestion = document.getElementById('secret-question');
214
+ const secretReveal = document.getElementById('secret-reveal');
215
+ const secretFeedback = document.getElementById('secret-feedback');
216
+ const secretChoiceButtons = document.querySelectorAll('.secret-choice');
217
+
218
+ // --- 遊戲設定 ---
219
+ const LANE_COUNT = 3;
220
+ let laneWidth;
221
+ let gameInterval, timer, obstacleInterval;
222
+ let timeLeft;
223
+ let perfectDodges = 0;
224
+ const perfectDodgeTexts = [];
225
+ let keySequence = [];
226
+ const cheatCode = "kkkkk";
227
+ let currentStage = 0;
228
+ let currentStageSettings;
229
+
230
+ const gameStages = [
231
+ { speed: 2.5, time: 20, spawnRate: 1800 },
232
+ { speed: 3.5, time: 15, spawnRate: 1200 },
233
+ { speed: 4.0, time: 12, spawnRate: 1000 }
234
+ ];
235
+
236
+ // --- 寶石解謎設定 ---
237
+ const allSmallGems = [
238
+ { id: 1, src: 'https://i.meee.com.tw/FeaxgIn.png' },
239
+ { id: 2, src: 'https://i.meee.com.tw/VgnnNxo.png' },
240
+ { id: 3, src: 'https://i.meee.com.tw/5vu7Qk1.png' },
241
+ { id: 4, src: 'https://i.meee.com.tw/eoel84q.png' },
242
+ { id: 5, src: 'https://i.meee.com.tw/b2uL22E.png' },
243
+ { id: 6, src: 'https://i.meee.com.tw/HeduyDE.png' },
244
+ { id: 7, src: 'https://i.meee.com.tw/2gorePd.png' },
245
+ { id: 8, src: 'https://i.meee.com.tw/HNNEM09.png' },
246
+ { id: 9, src: 'https://i.meee.com.tw/cpw8ccy.png' },
247
+ { id: 10, src: 'https://i.meee.com.tw/Rk8hY7H.png' }
248
+ ];
249
+
250
+ const puzzleLevels = [
251
+ {
252
+ largeGemSrc: 'https://i.meee.com.tw/PU1busV.png',
253
+ options: [1, 2, 3],
254
+ answer: { 1: 1, 2: 3, 3: 1 },
255
+ maxGems: 5
256
+ },
257
+ {
258
+ largeGemSrc: 'https://i.meee.com.tw/JCK7B2S.png',
259
+ options: [2, 3, 4, 5, 6, 7],
260
+ answer: { 3: 2, 7: 2, 2: 1, 4: 1, 5: 1, 6: 1 },
261
+ maxGems: 8
262
+ },
263
+ {
264
+ largeGemSrc: 'https://i.meee.com.tw/wFJquPy.png',
265
+ options: [2, 3, 4, 5, 6, 8, 9, 10],
266
+ answer: { 8: 3, 10: 3, 5: 6, 9: 1 },
267
+ maxGems: 13
268
+ }
269
+ ];
270
+ let playerSelection = {};
271
+
272
+ // --- 玩家設定 ---
273
+ const player = {
274
+ x: 0, y: 0, width: 100, height: 100, lane: 1, image: new Image()
275
+ };
276
+ const normalCartSrc = 'https://i.meee.com.tw/w6krwUz.png';
277
+ const crashedCartSrc = 'https://i.meee.com.tw/4wSy5uX.png';
278
+ player.image.src = normalCartSrc;
279
+ player.image.onerror = () => { console.error("礦車圖片載入失敗!"); };
280
+
281
+ // --- 障礙物設定 ---
282
+ const obstacles = [];
283
+ const trackTies = [];
284
+ const obstacleImage = new Image();
285
+ obstacleImage.src = 'https://i.meee.com.tw/L0tV6le.png';
286
+ obstacleImage.onerror = () => { console.error("障礙物圖片載入失敗!"); };
287
+ let lastTieY = 0;
288
+ const TIE_SPACING = 50;
289
+
290
+ // --- 畫面管理 ---
291
+ function showScreen(screenId) {
292
+ ['start-screen', 'game-screen', 'puzzle-screen', 'intermission-screen', 'final-win-screen'].forEach(id => {
293
+ const screen = document.getElementById(id);
294
+ if (id === screenId) {
295
+ screen.style.display = 'flex';
296
+ screen.classList.remove('hidden');
297
+ if(id === 'start-screen' || id === 'intermission-screen' || id === 'final-win-screen') {
298
+ screen.classList.add('my-auto');
299
+ } else {
300
+ screen.classList.remove('my-auto');
301
+ }
302
+ } else {
303
+ screen.style.display = 'none';
304
+ screen.classList.add('hidden');
305
+ }
306
+ });
307
+ }
308
+
309
+ // --- 礦車遊戲函式 ---
310
+ function resizeCanvas() {
311
+ const container = document.getElementById('container');
312
+ const controls = document.getElementById('controls');
313
+ canvas.width = container.clientWidth;
314
+ canvas.height = window.innerHeight * 0.9 - controls.offsetHeight - 20;
315
+ canvasWrapper.style.width = `${canvas.width}px`;
316
+ canvasWrapper.style.height = `${canvas.height}px`;
317
+ laneWidth = canvas.width / LANE_COUNT;
318
+ player.y = canvas.height - player.height - 10;
319
+ }
320
+
321
+ function getLaneCenterX(lane) {
322
+ return (lane * laneWidth) + (laneWidth / 2);
323
+ }
324
+
325
+ function spawnTrackTies() {
326
+ while (lastTieY < canvas.height + TIE_SPACING) {
327
+ trackTies.push({ y: lastTieY });
328
+ lastTieY += TIE_SPACING;
329
+ }
330
+ }
331
+
332
+ function drawTrack() {
333
+ ctx.fillStyle = '#57402c';
334
+ trackTies.forEach(tie => {
335
+ for (let i = 0; i < LANE_COUNT; i++) {
336
+ const laneX = getLaneCenterX(i);
337
+ const rail1X = laneX - laneWidth * 0.25;
338
+ const rail2X = laneX + laneWidth * 0.25;
339
+ ctx.fillRect(rail1X, tie.y, rail2X - rail1X, 10);
340
+ }
341
+ });
342
+ const railColor = '#858585', highlightColor = '#b0b0b0', railWidth = 8;
343
+ for (let i = 0; i < LANE_COUNT; i++) {
344
+ const laneX = getLaneCenterX(i);
345
+ const rail1X = laneX - laneWidth * 0.25;
346
+ const rail2X = laneX + laneWidth * 0.25;
347
+ ctx.fillStyle = railColor;
348
+ ctx.fillRect(rail1X - railWidth / 2, 0, railWidth, canvas.height);
349
+ ctx.fillRect(rail2X - railWidth / 2, 0, railWidth, canvas.height);
350
+ ctx.fillStyle = highlightColor;
351
+ ctx.fillRect(rail1X - railWidth / 2, 0, railWidth / 2, canvas.height);
352
+ ctx.fillRect(rail2X - railWidth / 2, 0, railWidth / 2, canvas.height);
353
+ }
354
+ }
355
+
356
+ function drawPlayer() {
357
+ player.x = getLaneCenterX(player.lane) - player.width / 2;
358
+ if (player.image.complete && player.image.naturalHeight !== 0) {
359
+ ctx.drawImage(player.image, player.x, player.y, player.width, player.height);
360
+ } else {
361
+ ctx.fillStyle = 'blue';
362
+ ctx.fillRect(player.x, player.y, player.width, player.height);
363
+ }
364
+ }
365
+
366
+ function drawObstacles() {
367
+ obstacles.forEach(obstacle => {
368
+ if (obstacleImage.complete && obstacleImage.naturalHeight !== 0) {
369
+ ctx.drawImage(obstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height);
370
+ } else {
371
+ ctx.fillStyle = '#a16207';
372
+ ctx.beginPath();
373
+ ctx.roundRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height, [10]);
374
+ ctx.fill();
375
+ }
376
+ });
377
+ }
378
+
379
+ function drawUI() {
380
+ ctx.fillStyle = 'white';
381
+ ctx.font = 'bold 20px "Noto Sans TC"';
382
+ ctx.textAlign = 'center';
383
+ ctx.fillText(`倒數計時: ${timeLeft.toFixed(1)} 秒`, canvas.width / 2, 30);
384
+ ctx.textAlign = 'right';
385
+ ctx.fillText(`完美閃避: ${perfectDodges}`, canvas.width - 20, 30);
386
+ perfectDodgeTexts.forEach(text => {
387
+ ctx.save();
388
+ ctx.globalAlpha = text.alpha;
389
+ ctx.fillStyle = '#fde047';
390
+ ctx.font = 'bold 24px "Noto Sans TC"';
391
+ ctx.textAlign = 'center';
392
+ ctx.fillText(text.text, text.x, text.y);
393
+ ctx.restore();
394
+ });
395
+ }
396
+
397
+ function update() {
398
+ for (let i = trackTies.length - 1; i >= 0; i--) {
399
+ trackTies[i].y += currentStageSettings.speed;
400
+ if (trackTies[i].y > canvas.height) {
401
+ trackTies.splice(i, 1);
402
+ trackTies.unshift({ y: (trackTies[0]?.y || 0) - TIE_SPACING });
403
+ }
404
+ }
405
+ for (let i = obstacles.length - 1; i >= 0; i--) {
406
+ obstacles[i].y += currentStageSettings.speed;
407
+ if (obstacles[i].y > canvas.height) obstacles.splice(i, 1);
408
+ }
409
+ for (let i = perfectDodgeTexts.length - 1; i >= 0; i--) {
410
+ perfectDodgeTexts[i].y -= 1;
411
+ perfectDodgeTexts[i].alpha -= 0.02;
412
+ if (perfectDodgeTexts[i].alpha <= 0) perfectDodgeTexts.splice(i, 1);
413
+ }
414
+ obstacles.forEach(obstacle => {
415
+ if (player.lane === obstacle.lane && player.y < obstacle.y + obstacle.height && player.y + player.height > obstacle.y) {
416
+ gameOver();
417
+ }
418
+ });
419
+ }
420
+
421
+ function draw() {
422
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
423
+ drawTrack();
424
+ drawObstacles();
425
+ drawPlayer();
426
+ drawUI();
427
+ }
428
+
429
+ function spawnObstacle() {
430
+ const lane = Math.floor(Math.random() * LANE_COUNT);
431
+ const size = laneWidth * 0.5;
432
+ const x = getLaneCenterX(lane) - size / 2;
433
+ obstacles.push({ x, y: -size, width: size, height: size, lane, dodged: false });
434
+ }
435
+
436
+ function gameLoop() {
437
+ update();
438
+ draw();
439
+ gameInterval = requestAnimationFrame(gameLoop);
440
+ }
441
+
442
+ function stopGame() {
443
+ if (gameInterval) cancelAnimationFrame(gameInterval);
444
+ if (timer) clearInterval(timer);
445
+ if (obstacleInterval) clearInterval(obstacleInterval);
446
+ gameInterval = timer = obstacleInterval = null;
447
+ }
448
+
449
+ function startGame(stageIndex) {
450
+ currentStage = stageIndex;
451
+ currentStageSettings = gameStages[stageIndex];
452
+ player.image.src = normalCartSrc; // Reset to normal cart image
453
+ stopGame();
454
+ instructionsScreen.classList.add('hidden');
455
+ gameOverScreen.classList.add('hidden');
456
+ winScreen.classList.add('hidden');
457
+ resizeCanvas();
458
+ obstacles.length = 0;
459
+ trackTies.length = 0;
460
+ perfectDodgeTexts.length = 0;
461
+ perfectDodges = 0;
462
+ lastTieY = 0;
463
+ spawnTrackTies();
464
+ player.lane = 1;
465
+ timeLeft = currentStageSettings.time;
466
+ timer = setInterval(() => {
467
+ timeLeft -= 0.1;
468
+ if (timeLeft <= 0) {
469
+ timeLeft = 0;
470
+ winGame();
471
+ }
472
+ }, 100);
473
+ obstacleInterval = setInterval(spawnObstacle, currentStageSettings.spawnRate);
474
+ gameLoop();
475
+ }
476
+
477
+ function gameOver() {
478
+ stopGame();
479
+ player.image.src = crashedCartSrc; // Change to crashed cart image
480
+ draw(); // Redraw canvas one last time with the new image
481
+ gameOverScreen.classList.remove('hidden');
482
+ }
483
+
484
+ function winGame() {
485
+ stopGame();
486
+ winPerfectDodges.textContent = `完美閃避: ${perfectDodges} 次`;
487
+ winScreen.classList.remove('hidden');
488
+ }
489
+
490
+ function handleMove(direction) {
491
+ if (!gameInterval) return;
492
+ const oldLane = player.lane;
493
+ let newLane = player.lane;
494
+ if (direction === 'left') newLane = Math.max(0, player.lane - 1);
495
+ else if (direction === 'right') newLane = Math.min(LANE_COUNT - 1, player.lane + 1);
496
+ if (oldLane !== newLane) {
497
+ player.lane = newLane;
498
+ checkPerfectDodge(oldLane);
499
+ }
500
+ }
501
+
502
+ function checkPerfectDodge(fromLane) {
503
+ const perfectDodgeZoneTop = player.y - player.height / 2;
504
+ const perfectDodgeZoneBottom = player.y + player.height;
505
+ obstacles.forEach(obstacle => {
506
+ if (obstacle.lane === fromLane && !obstacle.dodged) {
507
+ if (obstacle.y + obstacle.height > perfectDodgeZoneTop && obstacle.y < perfectDodgeZoneBottom) {
508
+ perfectDodges++;
509
+ obstacle.dodged = true;
510
+ perfectDodgeTexts.push({ text: '完美!', x: getLaneCenterX(fromLane), y: player.y, alpha: 1 });
511
+ }
512
+ }
513
+ });
514
+ }
515
+
516
+ // --- 寶石解謎函式 ---
517
+ function showPuzzleScreen() {
518
+ showScreen('puzzle-screen');
519
+ loadPuzzleLevel(currentStage);
520
+ }
521
+
522
+ function loadPuzzleLevel(levelIndex) {
523
+ const level = puzzleLevels[levelIndex];
524
+ largeGemImage.src = level.largeGemSrc;
525
+ smallGemOptions.innerHTML = '';
526
+ level.options.forEach(gemId => {
527
+ const gemData = allSmallGems.find(g => g.id === gemId);
528
+ if (gemData) {
529
+ const gemBtn = document.createElement('button');
530
+ gemBtn.className = 'gem-button';
531
+ gemBtn.innerHTML = `<img src="${gemData.src}" alt="小寶石 ${gemData.id}" class="h-16 w-16 object-contain">`;
532
+ gemBtn.onclick = () => addGemToSelection(gemData);
533
+ smallGemOptions.appendChild(gemBtn);
534
+ }
535
+ });
536
+ resetPuzzle();
537
+ }
538
+
539
+ function addGemToSelection(gem) {
540
+ const currentTotal = Object.values(playerSelection).reduce((sum, count) => sum + count, 0);
541
+ const level = puzzleLevels[currentStage];
542
+
543
+ if (currentTotal >= level.maxGems) {
544
+ puzzleFeedback.textContent = '數量太多了喔!';
545
+ puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-yellow-400';
546
+ setTimeout(() => {
547
+ if(puzzleFeedback.textContent === '數量太多了喔!') {
548
+ puzzleFeedback.textContent = '';
549
+ }
550
+ }, 2000);
551
+ return;
552
+ }
553
+
554
+ playerSelection[gem.id] = (playerSelection[gem.id] || 0) + 1;
555
+ renderSelection();
556
+ }
557
+
558
+ function removeGemFromSelection(gemId) {
559
+ if (playerSelection[gemId]) {
560
+ playerSelection[gemId]--;
561
+ if (playerSelection[gemId] === 0) {
562
+ delete playerSelection[gemId];
563
+ }
564
+ }
565
+ renderSelection();
566
+ }
567
+
568
+ function renderSelection() {
569
+ answerSlots.innerHTML = '';
570
+ puzzleLevels[currentStage].options.forEach(gemId => {
571
+ const gemData = allSmallGems.find(g => g.id === gemId);
572
+ if (playerSelection[gemId]) {
573
+ const count = playerSelection[gemId];
574
+ for (let i = 0; i < count; i++) {
575
+ const img = document.createElement('img');
576
+ img.src = gemData.src;
577
+ img.alt = `[已選擇的小寶石 ${gemData.id}]`;
578
+ img.className = 'h-12 w-12 object-contain cursor-pointer';
579
+ img.onclick = () => removeGemFromSelection(gemId);
580
+ answerSlots.appendChild(img);
581
+ }
582
+ }
583
+ });
584
+ }
585
+
586
+ function resetPuzzle() {
587
+ playerSelection = {};
588
+ answerSlots.innerHTML = '';
589
+ puzzleFeedback.textContent = '';
590
+ }
591
+
592
+ function checkPuzzleAnswer() {
593
+ const level = puzzleLevels[currentStage];
594
+ if (!level) return;
595
+ const answer = level.answer;
596
+ let correct = true;
597
+
598
+ if (Object.keys(playerSelection).length !== Object.keys(answer).length) {
599
+ correct = false;
600
+ } else {
601
+ for (const gemId in answer) {
602
+ if (playerSelection[gemId] !== answer[gemId]) {
603
+ correct = false;
604
+ break;
605
+ }
606
+ }
607
+ }
608
+
609
+ if (correct) {
610
+ puzzleFeedback.textContent = '組合正確!你獲得了大寶石!';
611
+ puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-green-400';
612
+ setTimeout(() => {
613
+ const nextStage = currentStage + 1;
614
+ if (nextStage < puzzleLevels.length) {
615
+ showScreen('intermission-screen');
616
+ } else {
617
+ showScreen('final-win-screen');
618
+ }
619
+ }, 1500);
620
+ } else {
621
+ puzzleFeedback.textContent = '組合不對喔,再試一次!';
622
+ puzzleFeedback.className = 'text-center text-xl font-bold min-h-[32px] mb-4 text-red-500';
623
+ }
624
+ }
625
+
626
+ // --- 事件監聽 ---
627
+ startButton.addEventListener('click', () => {
628
+ showScreen('game-screen');
629
+ resizeCanvas();
630
+ instructionsScreen.classList.remove('hidden');
631
+ });
632
+
633
+ playGameButton.addEventListener('click', () => startGame(0));
634
+ restartButton.addEventListener('click', () => startGame(currentStage));
635
+ continueButton.addEventListener('click', showPuzzleScreen);
636
+ startNextStageButton.addEventListener('click', () => {
637
+ showScreen('game-screen');
638
+ startGame(currentStage + 1);
639
+ });
640
+ resetPuzzleButton.addEventListener('click', resetPuzzle);
641
+ checkPuzzleButton.addEventListener('click', checkPuzzleAnswer);
642
+ unlockSecretButton.addEventListener('click', () => {
643
+ container.style.display = 'none';
644
+ secretScreen.style.display = 'flex';
645
+ });
646
+
647
+ secretChoiceButtons.forEach(button => {
648
+ button.addEventListener('click', () => {
649
+ const choice = button.dataset.choice;
650
+ secretFeedback.classList.remove('text-yellow-400', 'text-green-400');
651
+ if (choice === 'C') {
652
+ secretFeedback.textContent = '';
653
+ secretQuestion.style.display = 'none';
654
+ secretReveal.style.display = 'block';
655
+ } else if (choice === 'A') {
656
+ secretFeedback.textContent = '駕駛技術固然重要,但那只是過程喔!';
657
+ secretFeedback.classList.add('text-yellow-400');
658
+ } else {
659
+ secretFeedback.textContent = '閃避只是手段,不是目的呀!';
660
+ secretFeedback.classList.add('text-yellow-400');
661
+ }
662
+ });
663
+ });
664
+
665
+ window.addEventListener('resize', resizeCanvas);
666
+
667
+ window.addEventListener('keydown', (e) => {
668
+ if (e.key === 'ArrowLeft') handleMove('left');
669
+ else if (e.key === 'ArrowRight') handleMove('right');
670
+
671
+ if (gameInterval) {
672
+ keySequence.push(e.key);
673
+ keySequence = keySequence.slice(-cheatCode.length);
674
+ if (keySequence.join('') === cheatCode) {
675
+ winGame();
676
+ }
677
+ }
678
+ });
679
+
680
+ canvas.addEventListener('click', (e) => {
681
+ const rect = canvas.getBoundingClientRect();
682
+ const clickX = e.clientX - rect.left;
683
+ if (clickX < canvas.width / 2) handleMove('left');
684
+ else handleMove('right');
685
+ });
686
+ moveLeftButton.addEventListener('click', () => handleMove('left'));
687
+ moveRightButton.addEventListener('click', () => handleMove('right'));
688
+
689
+ // --- 初始啟動 ---
690
+ showScreen('start-screen');
691
+ });
692
+ </script>
693
+ </body>
694
+ </html>
harbor.html ADDED
@@ -0,0 +1,654 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>數學探險島 - 海風港灣</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <!-- MathJax for rendering formulas -->
12
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
13
+ <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
14
+ <style>
15
+ body {
16
+ font-family: 'Noto Sans TC', sans-serif;
17
+ overflow: hidden;
18
+ background-image: url('https://i.meee.com.tw/TDC4EZE.png');
19
+ background-size: cover;
20
+ background-position: center;
21
+ }
22
+ #game-container-bg {
23
+ background-image: url('https://i.meee.com.tw/vT2QeZF.png');
24
+ background-size: cover;
25
+ background-position: center;
26
+ }
27
+ .seaweed {
28
+ position: absolute;
29
+ bottom: -10px;
30
+ pointer-events: none;
31
+ filter: brightness(0.9);
32
+ }
33
+ .fish {
34
+ position: absolute;
35
+ cursor: pointer;
36
+ transition: transform 0.2s ease, opacity 0.15s ease;
37
+ }
38
+ .fish:hover {
39
+ transform: scale(1.1);
40
+ }
41
+ .catch-feedback {
42
+ position: absolute;
43
+ font-size: 2rem;
44
+ font-weight: bold;
45
+ pointer-events: none;
46
+ animation: fadeUp 1s forwards;
47
+ text-shadow: 1px 1px 2px black;
48
+ }
49
+ @keyframes fadeUp {
50
+ from { opacity: 1; transform: translateY(0); }
51
+ to { opacity: 0; transform: translateY(-50px); }
52
+ }
53
+ .loader {
54
+ border: 8px solid #f3f3f3;
55
+ border-radius: 50%;
56
+ border-top: 8px solid #3498db;
57
+ width: 60px;
58
+ height: 60px;
59
+ animation: spin 1s linear infinite;
60
+ }
61
+ @keyframes spin {
62
+ 0% { transform: rotate(0deg); }
63
+ 100% { transform: rotate(360deg); }
64
+ }
65
+ .market-choice.selected {
66
+ border-color: #facc15; /* yellow-400 */
67
+ box-shadow: 0 0 15px #facc15;
68
+ }
69
+ .secret-bg {
70
+ background-image: url('https://i.meee.com.tw/EKZpYKI.png');
71
+ background-size: cover;
72
+ background-position: center;
73
+ }
74
+ </style>
75
+ </head>
76
+ <body class="flex items-center justify-center h-screen">
77
+
78
+ <div id="container" class="w-full max-w-4xl mx-auto text-white p-4 flex flex-col h-[90vh] max-h-[800px] relative">
79
+
80
+ <!-- 引導畫面 -->
81
+ <div id="start-screen" class="flex flex-col items-center justify-center text-center p-8 bg-black bg-opacity-70 rounded-lg my-auto">
82
+ <h1 class="text-5xl font-bold text-cyan-300 mb-4" style="text-shadow: 2px 2px 4px #000;">海風港灣</h1>
83
+ <p class="text-xl text-gray-200 mb-8 max-w-2xl">歡迎來到海風港灣!這次的任務是捕捉指定的魚種,並到市場賣出。有了捕捉的技術,真正要賺大錢還得靠數學呢!請你在捕捉完漁獲後,根據市場需求,將漁獲賣到最適合的市場!</p>
84
+ <button id="start-button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-4 px-8 rounded-lg text-2xl">
85
+ 開始抓魚
86
+ </button>
87
+ </div>
88
+
89
+ <!-- 載入畫面 -->
90
+ <div id="loading-screen" class="hidden flex-col items-center justify-center text-center p-8 bg-black bg-opacity-70 rounded-lg my-auto">
91
+ <div class="loader mb-6"></div>
92
+ <p id="loading-text" class="text-2xl text-gray-200">正在準備海底世界...</p>
93
+ </div>
94
+
95
+ <!-- 遊戲畫面 -->
96
+ <div id="game-screen" class="hidden flex-col h-full w-full">
97
+ <div class="flex justify-between items-center p-2 bg-black/50 rounded-t-lg">
98
+ <div>
99
+ <p class="text-lg">目標: <span id="goal-text" class="font-bold text-yellow-400"></span></p>
100
+ <p class="text-lg">已捕捉: <span id="score" class="font-bold text-xl">0</span> / <span id="goal-count"></span></p>
101
+ </div>
102
+ <p class="text-sm text-cyan-200">請適度捕撈,維持海洋永續</p>
103
+ <div>
104
+ <p class="text-lg">時間</p>
105
+ <p id="timer" class="font-bold text-3xl">20</p>
106
+ </div>
107
+ </div>
108
+ <div id="game-container-bg" class="relative flex-grow rounded-b-lg overflow-hidden border-4 border-black/50">
109
+ <div id="game-container" class="absolute inset-0">
110
+ <img src="https://i.meee.com.tw/XE9mkQQ.gif" class="seaweed" style="left: 0%; height: 45%; transform: scaleX(-1);" alt="[海草的Image]">
111
+ <img src="https://i.meee.com.tw/cTBZUgx.gif" class="seaweed" style="left: 20%; height: 35%;" alt="[海草的Image]">
112
+ <img src="https://i.meee.com.tw/KyN2GaF.gif" class="seaweed" style="left: 45%; height: 30%;" alt="[海草的Image]">
113
+ <img src="https://i.meee.com.tw/XE9mkQQ.gif" class="seaweed" style="right: 25%; height: 48%;" alt="[海草的Image]">
114
+ <img src="https://i.meee.com.tw/cTBZUgx.gif" class="seaweed" style="right: 10%; height: 55%; transform: scaleX(-1);" alt="[海草的Image]">
115
+ <img src="https://i.meee.com.tw/KyN2GaF.gif" class="seaweed" style="right: -2%; height: 38%;" alt="[海草的Image]">
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- 疊加畫面 (操作說明, 成功/失敗) -->
121
+ <div id="overlay-container" class="hidden absolute inset-0 flex-col items-center justify-center z-10">
122
+ <div id="instructions-screen" class="hidden bg-black bg-opacity-80 w-full h-full flex-col items-center justify-center text-center p-4">
123
+ <div id="instructions-content" class="bg-slate-800 p-8 rounded-lg">
124
+ <!-- JS will fill this -->
125
+ </div>
126
+ </div>
127
+ <div id="game-over-screen" class="hidden bg-black bg-opacity-70 w-full h-full flex-col items-center justify-center text-center p-4">
128
+ <h2 class="text-4xl font-bold text-red-500 mb-4">挑戰失敗!</h2>
129
+ <button id="restart-button" class="bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-3 px-6 rounded-lg text-xl">
130
+ 重新挑戰
131
+ </button>
132
+ </div>
133
+ <div id="win-screen" class="hidden bg-black bg-opacity-70 w-full h-full flex-col items-center justify-center text-center p-4">
134
+ <h2 class="text-4xl font-bold text-green-400 mb-4">成功!</h2>
135
+ <p class="text-xl mb-6">你抓到足夠的漁獲了!</p>
136
+ <button id="continue-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg text-xl">
137
+ 繼續前進
138
+ </button>
139
+ </div>
140
+ </div>
141
+
142
+ <!-- 市場選擇畫面 -->
143
+ <div id="market-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-70 rounded-lg">
144
+ <div id="market-instructions" class="text-center my-auto">
145
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">任務說明:選擇市場</h2>
146
+ <p class="text-lg">每個地點對<span class="market-fish-name text-yellow-300 font-bold"></span>的需求都不同,需求<span class="text-yellow-300 font-bold">比例</span>越高的市場,就能賣到越好的價格!</p>
147
+ <p class="text-lg mt-2">仔細觀察下方的統計圖,找出最賺錢的市場吧!</p>
148
+ <button id="show-market-choices-button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
149
+ 了解,前往選擇
150
+ </button>
151
+ </div>
152
+ <div id="market-choices" class="hidden flex-col">
153
+ <h2 class="text-3xl font-bold text-amber-300 text-center mb-4">你要把<span class="market-fish-name"></span>賣到哪裡?</h2>
154
+ <div id="market-images-container" class="grid grid-cols-1 md:grid-cols-3 gap-4">
155
+ <!-- Market images will be inserted here by JS -->
156
+ </div>
157
+ <p id="market-feedback" class="text-center text-xl font-bold min-h-[32px] mt-4"></p>
158
+ <button id="next-level-button" class="hidden bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg text-xl w-full max-w-xs mx-auto mt-4">
159
+ <!-- Button text will be set by JS -->
160
+ </button>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- 攻略畫面 -->
165
+ <div id="strategy-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-80 rounded-lg text-left overflow-y-auto">
166
+ <h1 class="text-3xl font-bold text-amber-300 text-center mb-4">經營之神的攻略</h1>
167
+ <p class="text-lg mb-4">在真實世界中,不同地點的調查所收到的資料不一定會一樣多,總人數不一樣的時候,我們很難用肉眼就判斷出比例的高低,因此需要「相對次數」這樣的統計數據。</p>
168
+ <div class="text-center bg-gray-900 p-3 rounded-lg text-xl mb-4">
169
+ $$\text{相對次數} = \frac{\text{需求(人)}}{\text{總人數}} \times 100\%$$
170
+ </div>
171
+ <img src="https://i.meee.com.tw/i67LLqV.png" class="rounded-lg mx-auto my-4 w-full max-w-md" alt="[包含相對次數的統計圖表]">
172
+ <p class="text-lg mb-6 text-center">有了相對次數,會不會更好判斷呢?用下一關的挑戰來試試吧!</p>
173
+ <button id="start-final-stage-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl w-full max-w-xs mx-auto mt-4">
174
+ 挑戰最終關卡
175
+ </button>
176
+ </div>
177
+
178
+ <!-- 最終秘密畫面 -->
179
+ <div id="final-secret-screen" class="hidden flex-col h-full w-full p-6 bg-black bg-opacity-80 rounded-lg text-left overflow-y-auto">
180
+ <h1 class="text-3xl font-bold text-amber-300 text-center mb-6">海風港灣的秘密</h1>
181
+
182
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
183
+ <img src="https://i.meee.com.tw/rmcY2Vc.png" class="rounded-lg border-4 border-amber-400" alt="[代數之丘的統計圖表]">
184
+ <img src="https://i.meee.com.tw/qV8QrUI.png" class="rounded-lg" alt="[哲學之塔的統計圖表]">
185
+ <img src="https://i.meee.com.tw/i67LLqV.png" class="rounded-lg" alt="[寶石洞窟的統計圖表]">
186
+ </div>
187
+
188
+ <p class="text-lg mb-4">恭喜你,完成了所有挑戰!你學會了捕魚,也學會了如何分析數據來做出最好的商業決策。</p>
189
+ <p class="text-2xl font-bold text-cyan-300 mb-4">這個港灣的秘密就是:<br>「數字」本身有時候會騙人,「比例」才能揭露真相。</p>
190
+ <p class="text-lg mb-4">單看數字,哲學之塔似乎是更好的市場。但當你把「總人數」也考慮進來,計算出「相對次數」(也就是需求比例),你才會發現代數之丘的需求比例,是超過哲學之塔的。</p>
191
+ <p class="text-lg">這個智慧,不只適用於賣魚。未來在你看新聞、分析報告,甚至做人生重大決定時,記得問自己:「這個數字背後的『分母』是什麼?」看透比例,你就能做出更聰明的選擇。</p>
192
+ <a href="index.html" id="back-to-map-button" class="inline-block text-center bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg text-xl w-full max-w-xs mx-auto mt-8">
193
+ 回到探險島地圖
194
+ </a>
195
+ </div>
196
+
197
+ </div>
198
+
199
+ <script>
200
+ document.addEventListener('DOMContentLoaded', () => {
201
+ // --- 畫面元素 ---
202
+ const startScreen = document.getElementById('start-screen');
203
+ const loadingScreen = document.getElementById('loading-screen');
204
+ const loadingText = document.getElementById('loading-text');
205
+ const gameScreen = document.getElementById('game-screen');
206
+ const startButton = document.getElementById('start-button');
207
+ const gameContainer = document.getElementById('game-container');
208
+ const scoreDisplay = document.getElementById('score');
209
+ const timerDisplay = document.getElementById('timer');
210
+ const instructionsScreen = document.getElementById('instructions-screen');
211
+ const instructionsContent = document.getElementById('instructions-content');
212
+ const goalText = document.getElementById('goal-text');
213
+ const goalCount = document.getElementById('goal-count');
214
+ const overlayContainer = document.getElementById('overlay-container');
215
+ const gameOverScreen = document.getElementById('game-over-screen');
216
+ const winScreen = document.getElementById('win-screen');
217
+ const restartButton = document.getElementById('restart-button');
218
+ const continueButton = document.getElementById('continue-button');
219
+ const marketScreen = document.getElementById('market-screen');
220
+ const marketInstructions = document.getElementById('market-instructions');
221
+ const marketChoices = document.getElementById('market-choices');
222
+ const showMarketChoicesButton = document.getElementById('show-market-choices-button');
223
+ const marketImagesContainer = document.getElementById('market-images-container');
224
+ const marketFeedback = document.getElementById('market-feedback');
225
+ const nextLevelButton = document.getElementById('next-level-button');
226
+ const startFinalStageButton = document.getElementById('start-final-stage-button');
227
+ const finalSecretScreen = document.getElementById('final-secret-screen');
228
+
229
+ // --- 遊戲設定 ---
230
+ const MAX_FISH_ON_SCREEN = 8;
231
+ let score = 0, timeLeft = 0;
232
+ let gameInterval, timerInterval, fishSpawnInterval;
233
+ let currentLevel = 0;
234
+ let currentLevelSettings;
235
+
236
+ const levels = [
237
+ { goal: 10, time: 20, targetFish: '小丑魚', speedMultiplier: 1.0, spawnRate: 1200, fishSet: ['小丑魚', '螃蟹'] },
238
+ { goal: 10, time: 20, targetFish: '水母', speedMultiplier: 1.5, spawnRate: 1000, fishSet: ['水母', '章魚'] },
239
+ { goal: 10, time: 20, targetFish: '水母', speedMultiplier: 1.8, spawnRate: 800, fishSet: ['水母', '鯊魚'] }
240
+ ];
241
+
242
+ const fishTypes = [
243
+ { name: '小丑魚', src: 'https://i.meee.com.tw/7tfldTT.gif', width: 160, height: 120 },
244
+ { name: '鯊魚', src: 'https://i.meee.com.tw/KCQk7C6.gif', width: 270, height: 270 },
245
+ { name: '章魚', src: 'https://i.meee.com.tw/lBWJmMy.gif', width: 160, height: 130 },
246
+ { name: '海馬', src: 'https://i.meee.com.tw/sMehyty.gif', width: 150, height: 150 },
247
+ { name: '螃蟹', src: 'https://i.meee.com.tw/zx9Vg0B.gif', width: 150, height: 110 },
248
+ { name: '水母', src: 'https://i.meee.com.tw/81gncdG.gif', width: 160, height: 160 },
249
+ ];
250
+
251
+ const marketLevels = [
252
+ {
253
+ images: { A: 'https://i.meee.com.tw/jQ3A90u.png', B: 'https://i.meee.com.tw/HEx1GWE.png', C: 'https://i.meee.com.tw/199bHzA.png' },
254
+ correctAnswer: 'B',
255
+ feedback: { correct: "答對了!哲學之塔的需求比例最高,要賺大錢還是得靠數學,不能蠻幹阿!", wrong: "每個地點的調查人數都一樣多,需求數量越多,即表示需求比例越高!" }
256
+ },
257
+ {
258
+ // A: 代數之丘(左), B: 哲學之塔(中), C: 寶石洞窟(右)
259
+ images: { A: 'https://i.meee.com.tw/lTXgvxv.png', B: 'https://i.meee.com.tw/FKPNhKh.png', C: 'https://i.meee.com.tw/zdv5MtZ.png' },
260
+ correctAnswer: 'A', // 正確答案是代數之丘
261
+ feedback: {
262
+ firstClick: {
263
+ C: "你確定嗎?其他兩個地方的需求人數比較多喔!若是確定請再點選一次",
264
+ B: "你確定嗎?雖然哲學之塔的需求人數最多,但他的總人數也最多喔!你確定他是比例最高的嗎?",
265
+ A: "你確定嗎?哲學之塔的需求人數比較多喔!"
266
+ },
267
+ correct: "你做出了正確的決定!但到底是運氣好,還是數學好呢,如果是運氣好的話,可沒有辦法長久經營阿~",
268
+ wrong: "商品買賣,不是越多人買越賺錢,不是喔!因為你長期的決策錯誤,導致買賣虧損,最後造成店家倒閉..."
269
+ }
270
+ },
271
+ {
272
+ // A: 代數之丘, B: 哲學之塔, C: 寶石洞窟
273
+ images: { A: 'https://i.meee.com.tw/rmcY2Vc.png', B: 'https://i.meee.com.tw/qV8QrUI.png', C: 'https://i.meee.com.tw/i67LLqV.png' },
274
+ correctAnswer: 'A', // 正確答案是代數之丘
275
+ feedback: { correct: "有了相對次數這項統計數據,是不是就更容易做選擇了呢~", wrong: "再仔細看看,哪個市場的需求比例最高呢?" }
276
+ }
277
+ ];
278
+ let firstChoice = null;
279
+ const fishesOnScreen = [];
280
+
281
+ // --- 畫面管理 ---
282
+ function showScreen(screenId) {
283
+ ['start-screen', 'game-screen', 'market-screen', 'loading-screen', 'strategy-screen', 'final-secret-screen'].forEach(id => {
284
+ document.getElementById(id).style.display = (id === screenId) ? 'flex' : 'none';
285
+ });
286
+ }
287
+
288
+ function showOverlay(overlayId) {
289
+ overlayContainer.style.display = 'flex';
290
+ ['instructions-screen', 'game-over-screen', 'win-screen'].forEach(id => {
291
+ document.getElementById(id).style.display = (id === overlayId) ? 'flex' : 'none';
292
+ });
293
+ }
294
+
295
+ function hideOverlay() {
296
+ overlayContainer.style.display = 'none';
297
+ }
298
+
299
+ // --- 預載入函式 ---
300
+ function preloadImages(urls, onProgress) {
301
+ return new Promise((resolve) => {
302
+ let loadedCount = 0;
303
+ const totalImages = urls.length;
304
+ if (totalImages === 0) resolve();
305
+ urls.forEach(url => {
306
+ const img = new Image();
307
+ img.src = url;
308
+ const onFinish = () => {
309
+ loadedCount++;
310
+ onProgress(loadedCount, totalImages);
311
+ if (loadedCount === totalImages) resolve();
312
+ };
313
+ img.onload = onFinish;
314
+ img.onerror = () => { console.error(`圖片載入失敗: ${url}`); onFinish(); };
315
+ });
316
+ });
317
+ }
318
+
319
+ function startGame(levelIndex) {
320
+ currentLevel = levelIndex;
321
+ currentLevelSettings = levels[currentLevel];
322
+ score = 0;
323
+ timeLeft = currentLevelSettings.time;
324
+ updateUI();
325
+
326
+ fishesOnScreen.forEach(fish => fish.element.remove());
327
+ fishesOnScreen.length = 0;
328
+
329
+ fishSpawnInterval = setInterval(spawnFish, currentLevelSettings.spawnRate);
330
+
331
+ timerInterval = setInterval(() => {
332
+ timeLeft--;
333
+ updateUI();
334
+ if (timeLeft <= 0) endGame(false);
335
+ }, 1000);
336
+
337
+ gameInterval = setInterval(updateGame, 1000 / 60);
338
+ }
339
+
340
+ function spawnFish() {
341
+ if (fishesOnScreen.length >= MAX_FISH_ON_SCREEN) return;
342
+
343
+ let fishData;
344
+ const fishSet = currentLevelSettings.fishSet.map(name => fishTypes.find(f => f.name === name));
345
+ const targetFish = fishSet.find(f => f.name === currentLevelSettings.targetFish);
346
+ const otherFish = fishSet.filter(f => f.name !== currentLevelSettings.targetFish);
347
+
348
+ const activeDistractor = otherFish.length > 0 ? otherFish[0] : targetFish;
349
+
350
+ if (Math.random() < 0.75) {
351
+ fishData = targetFish;
352
+ } else {
353
+ fishData = activeDistractor;
354
+ }
355
+
356
+ const fishElement = document.createElement('img');
357
+ fishElement.src = fishData.src;
358
+ fishElement.className = 'fish';
359
+
360
+ const direction = Math.random() < 0.5 ? 'left' : 'right';
361
+ const speed = (Math.random() * 2 + 1.5) * currentLevelSettings.speedMultiplier;
362
+ const startY = Math.random() * (gameContainer.clientHeight - fishData.height);
363
+
364
+ fishElement.style.width = `${fishData.width}px`;
365
+ fishElement.style.height = `${fishData.height}px`;
366
+ fishElement.style.top = `${startY}px`;
367
+
368
+ if (direction === 'left') {
369
+ fishElement.style.left = `${gameContainer.clientWidth}px`;
370
+ fishElement.style.transform = 'scaleX(-1)';
371
+ } else {
372
+ fishElement.style.left = `-${fishData.width}px`;
373
+ }
374
+
375
+ const fishObject = { element: fishElement, data: fishData, speed, direction, vy: (Math.random() - 0.5) * 2, lastVyChange: Date.now() };
376
+
377
+ if (currentLevel === 2 && fishData.name === '水母') {
378
+ fishObject.health = 2;
379
+ } else {
380
+ fishObject.health = 1;
381
+ }
382
+
383
+ fishElement.onclick = () => catchFish(fishObject);
384
+ fishesOnScreen.push(fishObject);
385
+ gameContainer.appendChild(fishObject.element);
386
+ }
387
+
388
+ function updateGame() {
389
+ for (let i = fishesOnScreen.length - 1; i >= 0; i--) {
390
+ const fish = fishesOnScreen[i];
391
+ let currentX = parseFloat(fish.element.style.left);
392
+ let currentY = parseFloat(fish.element.style.top);
393
+
394
+ if (fish.direction === 'left') {
395
+ currentX -= fish.speed;
396
+ if (currentX < -fish.data.width) {
397
+ fish.element.remove();
398
+ fishesOnScreen.splice(i, 1);
399
+ continue;
400
+ }
401
+ } else {
402
+ currentX += fish.speed;
403
+ if (currentX > gameContainer.clientWidth) {
404
+ fish.element.remove();
405
+ fishesOnScreen.splice(i, 1);
406
+ continue;
407
+ }
408
+ }
409
+ fish.element.style.left = `${currentX}px`;
410
+
411
+ if (currentLevel > 0) {
412
+ if (Date.now() - fish.lastVyChange > 1000) {
413
+ fish.vy = (Math.random() - 0.5) * 6;
414
+ fish.lastVyChange = Date.now();
415
+ }
416
+ currentY += fish.vy;
417
+ if (currentY < 0 || currentY > gameContainer.clientHeight - fish.data.height) {
418
+ fish.vy *= -1;
419
+ }
420
+ fish.element.style.top = `${currentY}px`;
421
+ }
422
+ }
423
+ }
424
+
425
+ function catchFish(fishObject) {
426
+ fishObject.health--;
427
+
428
+ if (fishObject.health > 0) {
429
+ showCatchFeedback('!', fishObject.element, false);
430
+ fishObject.element.style.opacity = '0.5';
431
+ setTimeout(() => {
432
+ if (fishObject.element) {
433
+ fishObject.element.style.opacity = '1';
434
+ }
435
+ }, 150);
436
+ return;
437
+ }
438
+
439
+ if (fishObject.data.name === currentLevelSettings.targetFish) {
440
+ score++;
441
+ showCatchFeedback('+1', fishObject.element, true);
442
+ } else {
443
+ score = Math.max(0, score - 1);
444
+ showCatchFeedback('-1', fishObject.element, false);
445
+ }
446
+
447
+ fishObject.element.remove();
448
+ const index = fishesOnScreen.indexOf(fishObject);
449
+ if (index > -1) fishesOnScreen.splice(index, 1);
450
+ updateUI();
451
+ if (score >= currentLevelSettings.goal) endGame(true);
452
+ }
453
+
454
+ function showCatchFeedback(text, fishElement, isCorrect) {
455
+ const feedback = document.createElement('div');
456
+ feedback.className = 'catch-feedback';
457
+ feedback.textContent = text;
458
+ feedback.style.color = isCorrect ? '#4ade80' : '#f87171';
459
+ const rect = fishElement.getBoundingClientRect();
460
+ const containerRect = gameContainer.getBoundingClientRect();
461
+ feedback.style.left = `${rect.left - containerRect.left + rect.width / 2}px`;
462
+ feedback.style.top = `${rect.top - containerRect.top + rect.height / 2}px`;
463
+ gameContainer.appendChild(feedback);
464
+ setTimeout(() => feedback.remove(), 1000);
465
+ }
466
+
467
+ function updateUI() {
468
+ scoreDisplay.textContent = score;
469
+ timerDisplay.textContent = timeLeft;
470
+ }
471
+
472
+ function endGame(isWin) {
473
+ clearInterval(gameInterval);
474
+ clearInterval(timerInterval);
475
+ clearInterval(fishSpawnInterval);
476
+ gameInterval = timerInterval = fishSpawnInterval = null;
477
+
478
+ if (isWin) {
479
+ showOverlay('win-screen');
480
+ } else {
481
+ showOverlay('game-over-screen');
482
+ }
483
+ }
484
+
485
+ function setupMarketScreen() {
486
+ const marketLevel = marketLevels[currentLevel];
487
+ document.querySelectorAll('.market-fish-name').forEach(el => el.textContent = levels[currentLevel].targetFish);
488
+ marketImagesContainer.innerHTML = '';
489
+ // A, B, C keys correspond to left, middle, right images
490
+ const marketOrder = ['A', 'B', 'C'];
491
+ marketOrder.forEach(key => {
492
+ const src = marketLevel.images[key];
493
+ const img = document.createElement('img');
494
+ img.src = src;
495
+ img.dataset.market = key;
496
+ img.alt = `[${key}市場的統計圖表]`;
497
+ img.className = 'market-choice cursor-pointer rounded-lg border-4 border-transparent hover:border-yellow-400';
498
+ img.addEventListener('click', handleMarketChoice);
499
+ marketImagesContainer.appendChild(img);
500
+ });
501
+
502
+ marketFeedback.textContent = '';
503
+ nextLevelButton.classList.add('hidden');
504
+ firstChoice = null;
505
+ }
506
+
507
+ function handleMarketChoice(e) {
508
+ const choice = e.target.dataset.market;
509
+ const marketLevel = marketLevels[currentLevel];
510
+
511
+ document.querySelectorAll('.market-choice').forEach(img => img.classList.remove('selected'));
512
+
513
+ if (currentLevel === 0) {
514
+ if (choice === marketLevel.correctAnswer) {
515
+ marketFeedback.textContent = marketLevel.feedback.correct;
516
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
517
+ nextLevelButton.textContent = "前往第二關";
518
+ nextLevelButton.classList.remove('hidden');
519
+ } else {
520
+ marketFeedback.textContent = marketLevel.feedback.wrong;
521
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-yellow-400';
522
+ }
523
+ } else if (currentLevel === 1) {
524
+ if (firstChoice === choice) {
525
+ if (choice === marketLevel.correctAnswer) {
526
+ marketFeedback.textContent = marketLevel.feedback.correct;
527
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
528
+ nextLevelButton.textContent = "經營之神的攻略";
529
+ nextLevelButton.classList.remove('hidden');
530
+ } else {
531
+ marketFeedback.textContent = marketLevel.feedback.wrong;
532
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-red-500';
533
+ }
534
+ firstChoice = null;
535
+ } else {
536
+ firstChoice = choice;
537
+ e.target.classList.add('selected');
538
+ marketFeedback.textContent = marketLevel.feedback.firstClick[choice];
539
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-cyan-300';
540
+ }
541
+ } else if (currentLevel === 2) {
542
+ if (choice === marketLevel.correctAnswer) {
543
+ marketFeedback.textContent = marketLevel.feedback.correct;
544
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-green-400';
545
+ nextLevelButton.textContent = "查看海風港灣的秘密";
546
+ nextLevelButton.classList.remove('hidden');
547
+ } else {
548
+ marketFeedback.textContent = marketLevel.feedback.wrong;
549
+ marketFeedback.className = 'text-center text-xl font-bold min-h-[32px] mt-4 text-red-500';
550
+ }
551
+ }
552
+ }
553
+
554
+
555
+ // --- 事件監聽 ---
556
+ startButton.addEventListener('click', async () => {
557
+ showScreen('loading-screen');
558
+ const imageUrls = [
559
+ ...fishTypes.map(fish => fish.src),
560
+ ...Object.values(marketLevels[0].images),
561
+ ...Object.values(marketLevels[1].images),
562
+ ...Object.values(marketLevels[2].images),
563
+ 'https://i.meee.com.tw/i67LLqV.png'
564
+ ];
565
+ await preloadImages(imageUrls, (loaded, total) => {
566
+ loadingText.textContent = `正在準備海底世界... (${loaded}/${total})`;
567
+ });
568
+
569
+ showScreen('game-screen');
570
+ instructionsContent.innerHTML = `
571
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">第一關任務</h2>
572
+ <div class="space-y-4 text-lg">
573
+ <p>在 <span class="text-yellow-400 font-bold">${levels[0].time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levels[0].goal}</span> 隻${levels[0].targetFish}!</p>
574
+ <p>點擊<span class="text-green-400 font-bold">正確的魚</span>來加分。</p>
575
+ <p>注意!抓到<span class="text-red-400 font-bold">錯誤的魚</span>會扣分喔!</p>
576
+ </div>
577
+ <button id="play-game-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
578
+ 了解!
579
+ </button>`;
580
+ document.getElementById('play-game-button').onclick = () => { hideOverlay(); startGame(0); };
581
+ showOverlay('instructions-screen');
582
+ goalText.textContent = `抓 ${levels[0].goal} 隻${levels[0].targetFish}`;
583
+ goalCount.textContent = levels[0].goal;
584
+ });
585
+
586
+ restartButton.addEventListener('click', () => {
587
+ hideOverlay();
588
+ startGame(currentLevel);
589
+ });
590
+
591
+ continueButton.addEventListener('click', () => {
592
+ hideOverlay();
593
+ showScreen('market-screen');
594
+ marketInstructions.style.display = 'block';
595
+ marketChoices.style.display = 'none';
596
+ });
597
+
598
+ showMarketChoicesButton.addEventListener('click', () => {
599
+ marketInstructions.style.display = 'none';
600
+ marketChoices.style.display = 'flex';
601
+ setupMarketScreen();
602
+ });
603
+
604
+ nextLevelButton.addEventListener('click', () => {
605
+ if (currentLevel === 0) {
606
+ const nextLevel = currentLevel + 1;
607
+ const levelSettings = levels[nextLevel];
608
+ showScreen('game-screen');
609
+ instructionsContent.innerHTML = `
610
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">第 ${nextLevel + 1} 關任務</h2>
611
+ <div class="space-y-4 text-lg">
612
+ <p>在 <span class="text-yellow-400 font-bold">${levelSettings.time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levelSettings.goal}</span> 隻${levelSettings.targetFish}!</p>
613
+ <p>海洋生物的移動方式不一樣了,小心!</p>
614
+ </div>
615
+ <button id="play-next-level-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
616
+ 挑戰!
617
+ </button>`;
618
+ document.getElementById('play-next-level-button').onclick = () => { hideOverlay(); startGame(nextLevel); };
619
+ showOverlay('instructions-screen');
620
+ goalText.textContent = `抓 ${levelSettings.goal} 隻${levelSettings.targetFish}`;
621
+ goalCount.textContent = levelSettings.goal;
622
+ } else if (currentLevel === 1) {
623
+ showScreen('strategy-screen');
624
+ } else if (currentLevel === 2) {
625
+ showScreen('final-secret-screen');
626
+ }
627
+ });
628
+
629
+ startFinalStageButton.addEventListener('click', () => {
630
+ const nextLevel = currentLevel + 1;
631
+ const levelSettings = levels[nextLevel];
632
+ showScreen('game-screen');
633
+ instructionsContent.innerHTML = `
634
+ <h2 class="text-3xl font-bold text-amber-300 mb-6">最終挑戰!</h2>
635
+ <div class="space-y-4 text-lg">
636
+ <p>在 <span class="text-yellow-400 font-bold">${levelSettings.time}</span> 秒內,捕捉 <span class="text-yellow-400 font-bold">${levelSettings.goal}</span> 隻${levelSettings.targetFish}!</p>
637
+ <p class="text-cyan-300">注意!這次的${levelSettings.targetFish}比��頑強,需要點擊兩下才能捕捉!</p>
638
+ </div>
639
+ <button id="play-final-level-button" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-8 rounded-lg text-xl mt-8">
640
+ 挑戰!
641
+ </button>`;
642
+ document.getElementById('play-final-level-button').onclick = () => { hideOverlay(); startGame(nextLevel); };
643
+ showOverlay('instructions-screen');
644
+ goalText.textContent = `抓 ${levelSettings.goal} 隻${levelSettings.targetFish}`;
645
+ goalCount.textContent = levelSettings.goal;
646
+ });
647
+
648
+ // --- 初始啟動 ---
649
+ showScreen('start-screen');
650
+ });
651
+ </script>
652
+ </body>
653
+ </html>
654
+
index.html CHANGED
@@ -1,19 +1,180 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>數學探險島</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Noto Sans TC', sans-serif;
14
+ background-color: #333;
15
+ overflow: hidden; /* 隱藏滾動條 */
16
+ }
17
+ #map-container {
18
+ position: absolute;
19
+ top: 0;
20
+ left: 0;
21
+ width: 100vw;
22
+ height: 100vh;
23
+ background-image: url('https://i.meee.com.tw/emt8qds.png');
24
+ background-size: cover; /* 填滿整個容器 */
25
+ background-repeat: no-repeat;
26
+ background-position: center;
27
+ }
28
+ .main-title {
29
+ position: relative; /* 確保標題在最上層 */
30
+ z-index: 20;
31
+ animation: fadeOutTitle 5s forwards;
32
+ animation-delay: 2s; /* 2秒後開始淡出 */
33
+ }
34
+ @keyframes fadeOutTitle {
35
+ 0% { opacity: 1; }
36
+ 100% { opacity: 0; pointer-events: none; }
37
+ }
38
+ .location-link {
39
+ position: absolute;
40
+ border-radius: 50%;
41
+ transform: translate(-50%, -50%);
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ text-decoration: none;
46
+ cursor: pointer;
47
+ z-index: 10;
48
+ }
49
+ .location-link::before {
50
+ content: '';
51
+ position: absolute;
52
+ width: 80px; /* 縮小光圈的尺寸 */
53
+ height: 80px;
54
+ border-radius: 50%;
55
+ transition: all 0.3s ease;
56
+ transform: scale(0); /* 預設隱藏 */
57
+ opacity: 0;
58
+ }
59
+ .location-link:hover::before {
60
+ transform: scale(1); /* 滑鼠移入時顯示 */
61
+ opacity: 1;
62
+ background-color: rgba(255, 255, 255, 0.2);
63
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.5), 0 0 20px 7px rgba(255, 235, 59, 0.6);
64
+ }
65
+ .location-link .label {
66
+ opacity: 0;
67
+ color: white;
68
+ font-size: 1.5rem;
69
+ font-weight: bold;
70
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
71
+ transition: opacity 0.3s ease;
72
+ background-color: rgba(0,0,0,0.5);
73
+ padding: 0.5rem 1rem;
74
+ border-radius: 999px;
75
+ position: relative; /* 確保文字在光圈之上 */
76
+ z-index: 5;
77
+ }
78
+ .location-link:hover .label {
79
+ opacity: 1;
80
+ }
81
+
82
+ /* 最終調整後的地點位置 */
83
+ #algebra-hill {
84
+ top: 28%;
85
+ left: 88%;
86
+ width: 25%;
87
+ height: 35%;
88
+ }
89
+ #philosophy-tower {
90
+ top: 35%;
91
+ left: 37%; /* 再次偏左 */
92
+ width: 22%;
93
+ height: 40%;
94
+ }
95
+ #gemstone-cave {
96
+ top: 58%;
97
+ left: 88%;
98
+ width: 20%;
99
+ height: 25%;
100
+ }
101
+ #harbor-bay {
102
+ top: 75%;
103
+ left: 25%;
104
+ width: 35%;
105
+ height: 35%;
106
+ }
107
+
108
+ /* 風的痕跡 SVG 樣式 */
109
+ #footprints-svg {
110
+ position: absolute;
111
+ top: 0;
112
+ left: 0;
113
+ width: 100%;
114
+ height: 100%;
115
+ pointer-events: none; /* 讓 SVG 不會擋到滑鼠點擊 */
116
+ z-index: 5;
117
+ }
118
+ .footprint-path {
119
+ stroke: rgba(255, 255, 255, 0.4);
120
+ stroke-width: 2.5px;
121
+ fill: none;
122
+ stroke-linecap: round;
123
+ stroke-dasharray: 150 400; /* 150px長的痕跡, 400px的間隔 */
124
+ stroke-dashoffset: 550;
125
+ animation: flow 15s linear infinite;
126
+ }
127
+ /* 讓每個路徑的動畫錯開,看起來更自然 */
128
+ .footprint-path:nth-child(2) { animation-delay: -3s; }
129
+ .footprint-path:nth-child(3) { animation-delay: -7s; }
130
+ .footprint-path:nth-child(4) { animation-delay: -10s; }
131
+
132
+ @keyframes flow {
133
+ from {
134
+ stroke-dashoffset: 550; /* 從路徑外開始 */
135
+ }
136
+ to {
137
+ stroke-dashoffset: -550; /* 移動到路徑的另一端外 */
138
+ }
139
+ }
140
+
141
+ </style>
142
+ </head>
143
+ <body class="bg-gray-900 flex items-center justify-center min-h-screen p-4">
144
+
145
+ <div id="map-container">
146
+ <!-- 動態痕跡 SVG -->
147
+ <svg id="footprints-svg" viewBox="0 0 1200 900" preserveAspectRatio="xMidYMid meet">
148
+ <!-- 重新設計的、更不規則的曲線路徑 -->
149
+ <path class="footprint-path" d="M600 900 C 500 880, 350 800, 300 675" />
150
+ <path class="footprint-path" d="M600 900 C 600 700, 380 550, 444 315" />
151
+ <path class="footprint-path" d="M600 900 C 750 850, 950 700, 1056 522" />
152
+ <path class="footprint-path" d="M600 900 C 700 750, 980 500, 1056 252" />
153
+ </svg>
154
+
155
+ <!-- 地點連結 -->
156
+ <a id="algebra-hill" href="algebra.html" class="location-link">
157
+ <span class="label">代數之丘</span>
158
+ </a>
159
+ <a id="philosophy-tower" href="philosophy.html" class="location-link">
160
+ <span class="label">哲學之塔</span>
161
+ </a>
162
+ <a id="gemstone-cave" href="gemstone.html" class="location-link">
163
+ <span class="label">寶石洞窟</span>
164
+ </a>
165
+ <a id="harbor-bay" href="harbor.html" class="location-link">
166
+ <span class="label">海風港灣</span>
167
+ </a>
168
+ </div>
169
+
170
+ <h1 class="main-title text-4xl md:text-5xl font-bold text-white text-center" style="text-shadow: 3px 3px 5px rgba(0,0,0,0.5);">歡迎來到數學探險島</h1>
171
+
172
+ <!-- 設計者資訊 -->
173
+ <div class="absolute bottom-4 right-4 text-right text-white text-sm opacity-80 z-10" style="text-shadow: 1px 1px 3px rgba(0,0,0,0.7);">
174
+ <p>遊戲設計者:新竹縣精華國中藍星宇</p>
175
+ <p>FB教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" class="underline hover:text-yellow-300 transition-colors">萬物皆數</a></p>
176
+ </div>
177
+
178
+ </body>
179
+ </html>
180
+
philosophy.html ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>數學探險島 - 哲學之塔</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Noto Sans TC', sans-serif;
14
+ background-image: url('https://i.meee.com.tw/wCWCOGx.png');
15
+ background-size: cover;
16
+ background-position: center;
17
+ background-attachment: fixed;
18
+ }
19
+ .tower-bg {
20
+ background-image: url('https://www.transparenttextures.com/patterns/stone-wall.png');
21
+ background-color: #e2e8f0;
22
+ }
23
+ /* 自訂拉桿樣式 */
24
+ input[type=range] {
25
+ -webkit-appearance: none;
26
+ width: 100%;
27
+ background: transparent;
28
+ }
29
+ input[type=range]:focus {
30
+ outline: none;
31
+ }
32
+ input[type=range]::-webkit-slider-runnable-track {
33
+ width: 100%;
34
+ height: 12px;
35
+ cursor: pointer;
36
+ background: #93c5fd;
37
+ border-radius: 5px;
38
+ border: 1px solid #60a5fa;
39
+ }
40
+ input[type=range]::-webkit-slider-thumb {
41
+ border: 2px solid #3b82f6;
42
+ height: 30px;
43
+ width: 30px;
44
+ border-radius: 50%;
45
+ background: #eff6ff;
46
+ cursor: pointer;
47
+ -webkit-appearance: none;
48
+ margin-top: -10px;
49
+ }
50
+ /* 成功時的閃爍動畫 */
51
+ @keyframes flash {
52
+ 0%, 100% { box-shadow: 0 0 20px 5px rgba(34, 197, 94, 0.7); }
53
+ 50% { box-shadow: 0 0 5px 0px rgba(34, 197, 94, 0.2); }
54
+ }
55
+ .success-flash {
56
+ animation: flash 1.5s ease-in-out;
57
+ }
58
+ /* 測驗選項樣式 */
59
+ .quiz-option {
60
+ display: block;
61
+ padding: 0.75rem 1rem;
62
+ border: 2px solid #e5e7eb;
63
+ border-radius: 0.5rem;
64
+ margin-bottom: 0.5rem;
65
+ cursor: pointer;
66
+ transition: all 0.2s;
67
+ }
68
+ .quiz-option:hover {
69
+ background-color: #f3f4f6;
70
+ }
71
+ input[type="radio"]:checked + .quiz-option {
72
+ background-color: #dbeafe;
73
+ border-color: #60a5fa;
74
+ }
75
+ .question-container.incorrect {
76
+ border: 2px solid #ef4444;
77
+ border-radius: 0.75rem;
78
+ padding: 1rem;
79
+ background-color: #fee2e2;
80
+ }
81
+ </style>
82
+ </head>
83
+ <body class="flex items-center justify-center min-h-screen p-4">
84
+
85
+ <div id="main-container" class="container mx-auto max-w-5xl">
86
+
87
+ <!-- 故事引導畫面 -->
88
+ <div id="story-view" class="bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8 text-center">
89
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-800 text-center mb-6">前情提要:西帕索斯的悲劇</h1>
90
+ <div class="border rounded-lg shadow-inner bg-gray-100 p-12 flex flex-col items-center justify-center" style="height: 50vh;">
91
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-indigo-300 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
92
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v11.494m-9-5.747h18" />
93
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v11.494m-9-5.747h18" />
94
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 18h16" />
95
+ </svg>
96
+ <p class="text-xl text-gray-600">故事即將展開...</p>
97
+ <p class="mt-4 text-lg text-indigo-600 font-semibold">點擊下方按鈕,在新分頁閱讀故事前情提要!</p>
98
+ </div>
99
+ <a href="https://g.co/gemini/share/d181055c5aa2" target="_blank" id="story-link-button" class="mt-8 inline-block w-full md:w-auto bg-blue-500 text-white font-bold py-3 px-8 rounded-lg hover:bg-blue-600 transition-colors shadow-lg text-lg">
100
+ 閱讀故事
101
+ </a>
102
+ <button id="start-quiz-button" class="mt-4 w-full md:w-auto bg-indigo-600 text-white font-bold py-3 px-8 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg">
103
+ 開始挑戰!
104
+ </button>
105
+ </div>
106
+
107
+ <!-- 閱讀測驗畫面 (新增) -->
108
+ <div id="quiz-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8">
109
+ <h1 class="text-3xl font-bold text-gray-800 text-center mb-6">閱讀測��:西帕索斯的考驗</h1>
110
+ <div class="space-y-6 text-left">
111
+ <!-- 問題 1 -->
112
+ <div id="question-container-0" class="question-container">
113
+ <p class="font-semibold mb-2">1. 根據故事內容,西帕索斯為什麼被畢達哥拉斯囚禁在哲學之塔?</p>
114
+ <div class="space-y-2">
115
+ <label><input type="radio" name="q0" value="A" class="hidden"> <span class="quiz-option">(A) 因為他試圖偷取畢達哥拉斯的數學理論。</span></label>
116
+ <label><input type="radio" name="q0" value="B" class="hidden"> <span class="quiz-option">(B) 因為他在公開場合侮辱了畢達哥拉斯本人。</span></label>
117
+ <label><input type="radio" name="q0" value="C" class="hidden"> <span class="quiz-option">(C) 因為他發現了一個無法用分數或小數表示的數字,這與畢達哥拉斯學派的信念相衝突。</span></label>
118
+ <label><input type="radio" name="q0" value="D" class="hidden"> <span class="quiz-option">(D) 因為他沒能成功計算出一個正方形的面積。</span></label>
119
+ </div>
120
+ </div>
121
+ <!-- 問題 2 -->
122
+ <div id="question-container-1" class="question-container">
123
+ <p class="font-semibold mb-2">2. 故事中,那個動搖了畢達哥拉斯學派信念的「神秘數字」,最初是源自於什麼?</p>
124
+ <div class="space-y-2">
125
+ <label><input type="radio" name="q1" value="A" class="hidden"> <span class="quiz-option">(A) 一個面積為3的圓形半徑。</span></label>
126
+ <label><input type="radio" name="q1" value="B" class="hidden"> <span class="quiz-option">(B) 一個面積為2的正方形邊長。</span></label>
127
+ <label><input type="radio" name="q1" value="C" class="hidden"> <span class="quiz-option">(C) 一個從未有人見過的全新立體圖形。</span></label>
128
+ <label><input type="radio" name="q1" value="D" class="hidden"> <span class="quiz-option">(D) 一個畢達哥拉斯自己提出的數學謎題。</span></label>
129
+ </div>
130
+ </div>
131
+ <!-- 問題 3 -->
132
+ <div id="question-container-2" class="question-container">
133
+ <p class="font-semibold mb-2">3. 在故事的結尾,你(讀者)被賦予的主要任務是什麼?</p>
134
+ <div class="space-y-2">
135
+ <label><input type="radio" name="q2" value="A" class="hidden"> <span class="quiz-option">(A) 找到一把萬能鑰匙,趁半夜把西帕索斯從監獄裡救出來。</span></label>
136
+ <label><input type="radio" name="q2" value="B" class="hidden"> <span class="quiz-option">(B) 回到未來,尋找歷史文獻來證明西帕索斯是對的。</span></label>
137
+ <label><input type="radio" name="q2" value="C" class="hidden"> <span class="quiz-option">(C) 成為畢達哥拉斯的學徒,從內部瓦解他的學派。</span></label>
138
+ <label><input type="radio" name="q2" value="D" class="hidden"> <span class="quiz-option">(D) 找出幾個「神秘數字」的近似值,用具體的證據去說服畢達哥拉斯。</span></label>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ <div id="quiz-feedback" class="text-center font-semibold mt-6 min-h-[24px]"></div>
143
+ <div class="flex flex-col md:flex-row gap-4 mt-6">
144
+ <button id="reread-story-button" class="w-full md:w-1/2 bg-gray-500 text-white font-bold py-3 px-6 rounded-lg hover:bg-gray-600 transition-colors">再看一次故事</button>
145
+ <button id="submit-quiz-button" class="w-full md:w-1/2 bg-green-500 text-white font-bold py-3 px-6 rounded-lg hover:bg-green-600 transition-colors">提交答案</button>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- 遊戲畫面 (預設隱藏) -->
150
+ <div id="game-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8 tower-bg">
151
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-800 text-center mb-2">哲學之塔</h1>
152
+ <p class="text-center text-gray-600 mb-6">幫助被囚禁的西帕索斯,找出正方形的神秘邊長!</p>
153
+
154
+ <div class="grid md:grid-cols-2 gap-8 items-center">
155
+ <!-- 左側:正方形展示區 -->
156
+ <div class="flex flex-col items-center justify-center bg-white/70 p-6 rounded-lg shadow-inner">
157
+ <div id="square-display" class="w-48 h-48 bg-blue-300 border-4 border-blue-500 flex items-center justify-center relative transition-transform duration-500">
158
+ <p class="text-2xl font-bold text-white">面積 = <span id="target-area-text">2</span></p>
159
+ </div>
160
+ <p class="mt-4 text-2xl font-mono text-gray-700">邊長 = <span id="target-sqrt-text">√2</span></p>
161
+ </div>
162
+
163
+ <!-- 右側:互動操作區 -->
164
+ <div class="flex flex-col space-y-6">
165
+ <div class="bg-white/80 p-4 rounded-lg text-center">
166
+ <p class="text-lg">你的猜測邊長 <span id="guess-sqrt-text" class="font-mono">√2</span> ≒ <span id="current-value" class="font-bold text-2xl text-indigo-600">1.50</span></p>
167
+ <p class="text-lg">邊長的平方(正方形面積):<span id="current-squared-value" class="font-bold text-2xl text-red-500">2.25</span></p>
168
+ </div>
169
+
170
+ <input type="range" id="value-slider" min="1" max="2" value="1.5" step="0.01" class="w-full">
171
+
172
+ <button id="check-button" class="w-full bg-green-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-green-600 transition-colors shadow-md text-xl">
173
+ 確定答案
174
+ </button>
175
+
176
+ <div id="feedback-box" class="text-center text-xl font-semibold min-h-[32px]"></div>
177
+ <button id="next-level-button" class="w-full bg-indigo-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-indigo-600 transition-colors shadow-md text-xl hidden">
178
+ 挑戰下一關
179
+ </button>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- 生活應用畫面 (預設隱藏) -->
185
+ <div id="application-view" class="hidden bg-white/80 backdrop-blur-md rounded-xl shadow-2xl p-8">
186
+ <h1 class="text-3xl md:text-4xl font-bold text-green-600 mb-4 text-center">恭喜你,成功解救了西帕索斯!</h1>
187
+ <p class="text-lg text-gray-700 mb-8 text-center">你們玩遊戲的時候,有沒有想過,為什麼角色撞到怪物就會扣血?為什麼子彈會正好打到你?這不是靠神奇的第六感,是靠數學──而且關鍵數學招式就是平方根!</p>
188
+
189
+ <div class="bg-blue-50 p-6 rounded-lg text-left">
190
+ <h2 class="text-2xl font-bold text-indigo-600 mb-4 text-center">🎮 遊戲物理引擎裡的平方根魔法</h2>
191
+ <div class="space-y-6">
192
+ <div>
193
+ <h3 class="font-semibold text-xl mb-2">判斷「有沒有撞到」</h3>
194
+ <p>遊戲世界是由座標組成的(就像地圖上的 X、Y 點)。當角色和怪物在不同位置,遊戲必須算兩者的距離,來判斷是不是近到可以碰撞。</p>
195
+ <p class="mt-2 p-3 bg-gray-200 rounded-md text-center font-mono text-lg">距離 = √((x₁ - x₂)² + (y₁ - y₂)²)</p>
196
+ <p class="mt-2">最後那個開根號,就是讓距離變回「真實長度」。沒有平方根,遊戲根本不知道你到底是不是碰到牆、怪物、寶箱。</p>
197
+ </div>
198
+ <div>
199
+ <h3 class="font-semibold text-xl mb-2">算出移動的真速度</h3>
200
+ <p>在 3D 遊戲中,速度不只有一個方向。例如你同時向前(X 軸)+ 向右(Y 軸)移動,總速度不是單純相加,而是:</p>
201
+ <p class="mt-2 p-3 bg-gray-200 rounded-md text-center font-mono text-lg">總速度 = √(水平速度² + 垂直速度²)</p>
202
+ <p class="mt-2">這樣遊戲才能正確決定動畫快慢、物理反應強度。</p>
203
+ </div>
204
+ <div>
205
+ <h3 class="font-semibold text-xl mb-2">模擬「真實的碰撞反應」</h3>
206
+ <p>角色被怪物推一下,會往斜方向飛。遊戲會算推力向量的大小(也就是「合力長度」)來決定你飛多遠,這裡也要平方根。</p>
207
+ </div>
208
+ </div>
209
+ <div class="mt-6 pt-6 border-t">
210
+ <p class="text-xl text-center"><span class="font-bold">💡 簡單比喻:</span>「平方根在遊戲引擎裡,就像一把『距離測量尺』,沒有它,遊戲世界就不知道東西有多近、有多快,甚至不會知道你到底撞到沒。」</p>
211
+ </div>
212
+ </div>
213
+
214
+ <div class="mt-12 text-center">
215
+ <a href="index.html" class="inline-block w-full md:w-1/2 bg-indigo-600 text-white font-bold py-3 md:py-4 px-6 rounded-lg hover:bg-indigo-700 transition-colors shadow-lg text-lg md:text-xl">
216
+ 回到探險島地圖
217
+ </a>
218
+ </div>
219
+ </div>
220
+
221
+ </div>
222
+
223
+ <script>
224
+ document.addEventListener('DOMContentLoaded', () => {
225
+ // --- 關卡設定 ---
226
+ const levels = [
227
+ { target: 2, min: 1, max: 2, tolerance: 0.05, type: 'manual' },
228
+ { target: 3, min: 1, max: 2, tolerance: 0.05, type: 'auto', speed: 0.007 },
229
+ { target: 5, min: 2, max: 3, tolerance: 0.05, type: 'auto', speed: 0.011 }
230
+ ];
231
+ let currentLevel = 0;
232
+
233
+ const quizQuestions = [
234
+ { answer: 'C' },
235
+ { answer: 'B' },
236
+ { answer: 'D' }
237
+ ];
238
+
239
+ // --- DOM 元素 ---
240
+ const storyView = document.getElementById('story-view');
241
+ const startQuizButton = document.getElementById('start-quiz-button');
242
+ const quizView = document.getElementById('quiz-view');
243
+ const submitQuizButton = document.getElementById('submit-quiz-button');
244
+ const rereadStoryButton = document.getElementById('reread-story-button');
245
+ const quizFeedback = document.getElementById('quiz-feedback');
246
+ const gameView = document.getElementById('game-view');
247
+ const applicationView = document.getElementById('application-view');
248
+ const squareDisplay = document.getElementById('square-display');
249
+ const targetAreaText = document.getElementById('target-area-text');
250
+ const targetSqrtText = document.getElementById('target-sqrt-text');
251
+ const guessSqrtText = document.getElementById('guess-sqrt-text');
252
+ const currentValue = document.getElementById('current-value');
253
+ const currentSquaredValue = document.getElementById('current-squared-value');
254
+ const valueSlider = document.getElementById('value-slider');
255
+ const checkButton = document.getElementById('check-button');
256
+ const feedbackBox = document.getElementById('feedback-box');
257
+ const nextLevelButton = document.getElementById('next-level-button');
258
+
259
+ // --- 遊戲狀態 ---
260
+ let sliderAnimationId = null;
261
+ let sliderDirection = 1;
262
+
263
+ // --- 核心函式 ---
264
+ function initLevel(levelIndex) {
265
+ const level = levels[levelIndex];
266
+
267
+ if (sliderAnimationId) {
268
+ cancelAnimationFrame(sliderAnimationId);
269
+ sliderAnimationId = null;
270
+ }
271
+
272
+ targetAreaText.textContent = level.target;
273
+ targetSqrtText.innerHTML = `√${level.target}`;
274
+ guessSqrtText.innerHTML = `√${level.target}`;
275
+
276
+ valueSlider.min = level.min;
277
+ valueSlider.max = level.max;
278
+ valueSlider.value = (level.min + level.max) / 2;
279
+ valueSlider.step = 0.01;
280
+
281
+ updateSliderDisplay();
282
+ feedbackBox.textContent = '';
283
+ feedbackBox.className = 'text-center text-xl font-semibold min-h-[32px]';
284
+ checkButton.disabled = false;
285
+ nextLevelButton.classList.add('hidden');
286
+ squareDisplay.classList.remove('success-flash');
287
+
288
+ if (level.type === 'manual') {
289
+ valueSlider.style.pointerEvents = 'auto';
290
+ checkButton.textContent = '確定答案';
291
+ } else {
292
+ valueSlider.style.pointerEvents = 'none';
293
+ checkButton.textContent = '按下鎖定!';
294
+ sliderDirection = 1;
295
+ startSliderAnimation();
296
+ }
297
+
298
+ if (levelIndex === levels.length - 1) {
299
+ nextLevelButton.textContent = '查看生活應用';
300
+ } else {
301
+ nextLevelButton.textContent = '挑戰下一關';
302
+ }
303
+ }
304
+
305
+ function startSliderAnimation() {
306
+ const level = levels[currentLevel];
307
+ let val = parseFloat(valueSlider.value);
308
+ val += sliderDirection * level.speed;
309
+ if (val >= level.max || val <= level.min) {
310
+ sliderDirection *= -1;
311
+ val = Math.max(level.min, Math.min(level.max, val));
312
+ }
313
+ valueSlider.value = val;
314
+ updateSliderDisplay();
315
+ sliderAnimationId = requestAnimationFrame(startSliderAnimation);
316
+ }
317
+
318
+ function updateSliderDisplay() {
319
+ const val = parseFloat(valueSlider.value);
320
+ const squaredVal = val * val;
321
+ currentValue.textContent = val.toFixed(2);
322
+ currentSquaredValue.textContent = squaredVal.toFixed(2);
323
+ }
324
+
325
+ function checkAnswer() {
326
+ const level = levels[currentLevel];
327
+
328
+ if (level.type === 'auto' && sliderAnimationId) {
329
+ cancelAnimationFrame(sliderAnimationId);
330
+ sliderAnimationId = null;
331
+ }
332
+
333
+ checkButton.disabled = true;
334
+
335
+ const val = parseFloat(valueSlider.value);
336
+ const squaredVal = val * val;
337
+ const difference = Math.abs(squaredVal - level.target);
338
+ feedbackBox.textContent = '';
339
+
340
+ if (difference <= level.tolerance) {
341
+ feedbackBox.textContent = '太棒了!你找到了!';
342
+ feedbackBox.className = 'text-center text-xl font-semibold text-green-600';
343
+ nextLevelButton.classList.remove('hidden');
344
+ squareDisplay.classList.add('success-flash');
345
+ } else {
346
+ const hint = squaredVal < level.target ? '太小了' : '太大了';
347
+ feedbackBox.textContent = `喔喔!${hint},再試一次!`;
348
+ feedbackBox.className = 'text-center text-xl font-semibold text-red-600';
349
+
350
+ if (level.type === 'auto') {
351
+ setTimeout(() => {
352
+ initLevel(currentLevel);
353
+ }, 2000);
354
+ } else {
355
+ checkButton.disabled = false;
356
+ }
357
+ }
358
+ }
359
+
360
+ function loadNext() {
361
+ currentLevel++;
362
+ if (currentLevel < levels.length) {
363
+ initLevel(currentLevel);
364
+ } else {
365
+ gameView.classList.add('hidden');
366
+ applicationView.classList.remove('hidden');
367
+ }
368
+ }
369
+
370
+ function checkQuiz() {
371
+ let allCorrect = true;
372
+ quizFeedback.textContent = '';
373
+ quizFeedback.classList.remove('text-red-500', 'text-green-600');
374
+
375
+ quizQuestions.forEach((question, index) => {
376
+ const container = document.getElementById(`question-container-${index}`);
377
+ const selected = document.querySelector(`input[name="q${index}"]:checked`);
378
+
379
+ container.classList.remove('incorrect');
380
+
381
+ if (!selected || selected.value !== question.answer) {
382
+ allCorrect = false;
383
+ container.classList.add('incorrect');
384
+ }
385
+ });
386
+
387
+ if (allCorrect) {
388
+ quizFeedback.textContent = '太棒了!你很用心在看故事喔!';
389
+ quizFeedback.classList.add('text-green-600');
390
+ submitQuizButton.disabled = true;
391
+ rereadStoryButton.disabled = true;
392
+ setTimeout(() => {
393
+ quizView.classList.add('hidden');
394
+ gameView.classList.remove('hidden');
395
+ }, 2000);
396
+ } else {
397
+ quizFeedback.textContent = '有題目答錯了,再檢查看看或重讀一次故事吧!';
398
+ quizFeedback.classList.add('text-red-500');
399
+ }
400
+ }
401
+
402
+ // --- 事件監聽 ---
403
+ startQuizButton.addEventListener('click', () => {
404
+ storyView.classList.add('hidden');
405
+ quizView.classList.remove('hidden');
406
+ submitQuizButton.disabled = false;
407
+ rereadStoryButton.disabled = false;
408
+ quizFeedback.textContent = '';
409
+ quizQuestions.forEach((_, index) => {
410
+ document.getElementById(`question-container-${index}`).classList.remove('incorrect');
411
+ });
412
+ });
413
+
414
+ rereadStoryButton.addEventListener('click', () => {
415
+ quizView.classList.add('hidden');
416
+ storyView.classList.remove('hidden');
417
+ });
418
+
419
+ submitQuizButton.addEventListener('click', checkQuiz);
420
+ valueSlider.addEventListener('input', updateSliderDisplay);
421
+ checkButton.addEventListener('click', checkAnswer);
422
+ nextLevelButton.addEventListener('click', loadNext);
423
+
424
+ // --- 初始啟動 ---
425
+ initLevel(currentLevel);
426
+ });
427
+ </script>
428
+ </body>
429
+ </html>