Spaces:
Running
Running
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>數學探險島 - 代數之丘</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Noto Sans TC', sans-serif; | |
| touch-action: none; /* 防止在觸控拖曳時滾動頁面 */ | |
| background-image: url('https://i.meee.com.tw/AF01kln.png'); | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| /* 自訂積木顏色 */ | |
| .block-a-squared { background-color: #fca5a5; } /* red-300 */ | |
| .block-b-squared { background-color: #818cf8; } /* indigo-400 */ | |
| .block-ab { background-color: #fcd34d; } /* amber-300 */ | |
| .block-distractor { background-color: #9ca3af; } /* gray-400 */ | |
| /* 拼圖區格線 */ | |
| #grid-container { | |
| display: grid; | |
| background-image: | |
| linear-gradient(to right, #d1d5db 1px, transparent 1px), | |
| linear-gradient(to bottom, #d1d5db 1px, transparent 1px); | |
| background-size: var(--unit-size) var(--unit-size); | |
| border: 2px solid #6b7280; | |
| } | |
| /* 拖曳中的複製物件 */ | |
| .dragging-clone { | |
| position: absolute; | |
| pointer-events: none; /* 讓滑鼠事件穿透複製物件 */ | |
| z-index: 1000; | |
| opacity: 0.8; | |
| border: 2px dashed #4f46e5; | |
| } | |
| /* 已放置在選項區的積木 */ | |
| .palette-block.placed { | |
| opacity: 0.3; | |
| cursor: not-allowed; | |
| pointer-events: none; | |
| } | |
| /* 已放置在拼圖區的積木 */ | |
| .placed-block { | |
| position: absolute; | |
| cursor: pointer; | |
| border: 2px solid #1f2937; | |
| transition: all 0.2s ease-in-out; | |
| } | |
| .placed-block:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 15px rgba(0,0,0,0.5); | |
| } | |
| /* 過關時的特別框線 */ | |
| .highlight-win { | |
| background-color: #fef9c3; /* yellow-100 */ | |
| border: 2px solid #f59e0b; /* amber-500 */ | |
| border-radius: 8px; | |
| padding: 2px 6px; | |
| display: inline-block; | |
| } | |
| /* 隱藏滾動條,但在觸控設備上仍可滾動 */ | |
| #block-palette::-webkit-scrollbar { | |
| display: none; | |
| } | |
| #block-palette { | |
| -ms-overflow-style: none; /* IE and Edge */ | |
| scrollbar-width: none; /* Firefox */ | |
| } | |
| </style> | |
| </head> | |
| <body class="w-screen h-screen overflow-hidden flex items-center justify-center relative"> | |
| <!-- 任務說明畫面 --> | |
| <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"> | |
| <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"> | |
| <h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-6">任務說明:代數之丘</h1> | |
| <p class="text-lg text-gray-700 mb-4">歡迎來到代數之丘!在這裡,我們不用死背公式,而是用雙手「拼」出數學!</p> | |
| <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> | |
| <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"> | |
| 開始挑戰 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 遊戲主畫面 --> | |
| <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"> | |
| <!-- 左欄:包含拼圖區和過關訊息 --> | |
| <div class="w-1/3 flex flex-col items-center justify-start gap-6"> | |
| <div id="grid-container" class="relative bg-gray-200/80 backdrop-blur-sm shadow-xl rounded-lg"> | |
| <!-- 拼圖區的積木會由 JS 動態加入這裡 --> | |
| </div> | |
| <!-- 過關訊息與按鈕 (已移至此處) --> | |
| <div id="win-message-container" class="w-full text-center p-4 bg-white/90 backdrop-blur-sm rounded-xl shadow-lg hidden"> | |
| <p class="text-2xl font-bold text-green-600">恭喜過關!</p> | |
| <p id="win-formula-explanation" class="text-lg text-gray-700 mt-2"></p> | |
| <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"> | |
| 挑戰下一關 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 右欄:控制項 --> | |
| <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"> | |
| <h1 class="text-3xl font-bold text-gray-800 text-center">代數之丘</h1> | |
| <hr class="my-4"> | |
| <!-- 算式區 --> | |
| <div class="bg-blue-50/80 p-4 rounded-lg mb-4 space-y-2"> | |
| <!-- 題目行 --> | |
| <div class="grid grid-cols-4 items-baseline"> | |
| <p class="text-lg text-gray-600 font-semibold col-span-1">題目:</p> | |
| <p id="equation-title" class="col-span-3 text-2xl font-bold text-indigo-700 text-center"></p> | |
| </div> | |
| <!-- 面積結果行 --> | |
| <div class="grid grid-cols-4 items-baseline"> | |
| <p class="text-lg text-gray-600 font-semibold col-span-1">面積結果:</p> | |
| <div id="equation-result" class="col-span-3 text-2xl font-mono text-gray-800 text-center"></div> | |
| </div> | |
| </div> | |
| <!-- 說明文字 --> | |
| <div id="instructions" class="text-center text-gray-500 mb-4 text-sm"> | |
| <p>請從下方拖曳積木,拼滿左邊的正方形。</p> | |
| <p class="mt-1">💡 <span class="font-semibold">提示:</span>點擊已放置的積木可以將它移除。</p> | |
| </div> | |
| <!-- 積木選項區 (包含捲動按鈕) --> | |
| <div class="relative mt-auto"> | |
| <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"> | |
| <!-- 積木選項會由 JS 動態加入這裡 --> | |
| </div> | |
| <!-- 左捲動按鈕 --> | |
| <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"> | |
| <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"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> | |
| </svg> | |
| </button> | |
| <!-- 右捲動按鈕 --> | |
| <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"> | |
| <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"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 秘密揭曉畫面 (從 secret.html 整合進來) --> | |
| <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"> | |
| <div class="container mx-auto p-6 md:p-8 bg-white rounded-xl shadow-2xl max-w-4xl text-center overflow-y-auto max-h-[90vh]"> | |
| <h1 class="text-3xl md:text-4xl font-bold text-indigo-600 mb-8">代數之丘最大的秘密</h1> | |
| <div class="grid md:grid-cols-3 gap-6 md:gap-8 mb-8"> | |
| <!-- Image 1 --> | |
| <div class="flex flex-col items-center p-2 border rounded-lg"> | |
| <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';"> | |
| <p class="text-lg md:text-xl font-mono font-bold">(6+4)² = 6² + <span class="highlight-win">2×6×4</span> + 4²</p> | |
| </div> | |
| <!-- Image 2 --> | |
| <div class="flex flex-col items-center p-2 border rounded-lg"> | |
| <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';"> | |
| <p class="text-lg md:text-xl font-mono font-bold">(4+9)² = 4² + <span class="highlight-win">2×4×9</span> + 9²</p> | |
| </div> | |
| <!-- Image 3 --> | |
| <div class="flex flex-col items-center p-2 border rounded-lg"> | |
| <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';"> | |
| <p class="text-lg md:text-xl font-mono font-bold">(7+5)² = 7² + <span class="highlight-win">2×7×5</span> + 5²</p> | |
| </div> | |
| </div> | |
| <div class="text-left text-base md:text-lg text-gray-700 space-y-4 border-t-2 pt-8"> | |
| <p>選舉最大的祕密就是:票多的贏,票少的輸...<span class="italic text-gray-500">咳咳不是這個啦</span></p> | |
| <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> | |
| <p class="font-semibold text-indigo-700">(請在學習單上做紀錄!)</p> | |
| </div> | |
| <div class="mt-12"> | |
| <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"> | |
| 回到探險島地圖 | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // --- 常數與設定 --- | |
| const UNIT_SIZE = 30; // 每個單位格的像素大小 (px) | |
| const levels = [ | |
| { a: 6, b: 4, distractors: [] }, | |
| { a: 4, b: 9, distractors: [{w: 5, h: 8}, {w: 7, h: 6}] }, | |
| { a: 7, b: 5, distractors: [{w: 8, h: 6}, {w: 6, h: 8}] } | |
| ]; | |
| let currentLevel = 0; | |
| // --- DOM 元素 --- | |
| const startScreen = document.getElementById('start-screen'); | |
| const startButton = document.getElementById('start-button'); | |
| const gameContainer = document.getElementById('game-container'); | |
| const secretView = document.getElementById('secret-view'); | |
| const gridContainer = document.getElementById('grid-container'); | |
| const blockPalette = document.getElementById('block-palette'); | |
| const equationTitle = document.getElementById('equation-title'); | |
| const equationResult = document.getElementById('equation-result'); | |
| const winMessageContainer = document.getElementById('win-message-container'); | |
| const winFormulaExplanation = document.getElementById('win-formula-explanation'); | |
| const nextLevelButton = document.getElementById('next-level-button'); | |
| const scrollLeftButton = document.getElementById('scroll-left-button'); | |
| const scrollRightButton = document.getElementById('scroll-right-button'); | |
| // --- 遊戲狀態 --- | |
| let gridState = []; // 2D 陣列,記錄拼圖區每個格子的佔用情況 | |
| let placedBlocks = new Map(); // 記錄已放置的積木 (key: placedId, value: { el, originalId, type }) | |
| let draggingElement = null; // 目前正在拖曳的原始積木 | |
| let clone = null; // 拖曳時的複製物件 | |
| let offset = { x: 0, y: 0 }; // 拖曳時的滑鼠偏移量 | |
| // --- 核心函式 --- | |
| function initLevel(levelIndex) { | |
| currentLevel = levelIndex; | |
| const level = levels[levelIndex]; | |
| const totalSize = level.a + level.b; | |
| gridContainer.innerHTML = ''; | |
| blockPalette.innerHTML = ''; | |
| winMessageContainer.classList.add('hidden'); | |
| placedBlocks.clear(); | |
| gridContainer.style.width = `${totalSize * UNIT_SIZE}px`; | |
| gridContainer.style.height = `${totalSize * UNIT_SIZE}px`; | |
| gridContainer.style.setProperty('--unit-size', `${UNIT_SIZE}px`); | |
| gridState = Array(totalSize).fill(null).map(() => Array(totalSize).fill(null)); | |
| createBlocks(level); | |
| updateEquation(); | |
| if (currentLevel === levels.length - 1) { | |
| nextLevelButton.textContent = '查看代數的秘密'; | |
| } else { | |
| nextLevelButton.textContent = '挑戰下一關'; | |
| } | |
| // 延遲執行以確保 DOM 尺寸已更新 | |
| setTimeout(updateScrollButtonsVisibility, 100); | |
| } | |
| function createBlocks(level) { | |
| const { a, b, distractors } = level; | |
| const blocksData = [ | |
| { id: 'a_squared', w: a, h: a, type: 'correct', class: 'block-a-squared', label: `${a}×${a}` }, | |
| { id: 'b_squared', w: b, h: b, type: 'correct', class: 'block-b-squared', label: `${b}×${b}` }, | |
| { id: 'ab1', w: a, h: b, type: 'correct', class: 'block-ab', label: `${a}×${b}` }, | |
| { id: 'ab2', w: b, h: a, type: 'correct', class: 'block-ab', label: `${b}×${a}` }, | |
| ...distractors.map((d, i) => ({ | |
| id: `distractor_${i}`, w: d.w, h: d.h, type: 'distractor', class: 'block-distractor', label: `${d.w}×${d.h}` | |
| })) | |
| ]; | |
| blocksData.sort(() => Math.random() - 0.5); | |
| blocksData.forEach(data => { | |
| const blockEl = document.createElement('div'); | |
| blockEl.id = `palette-${data.id}`; | |
| 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`; | |
| blockEl.style.width = `${data.w * UNIT_SIZE}px`; | |
| blockEl.style.height = `${data.h * UNIT_SIZE}px`; | |
| blockEl.textContent = data.label; | |
| blockEl.dataset.id = data.id; | |
| blockEl.dataset.w = data.w; | |
| blockEl.dataset.h = data.h; | |
| blockEl.dataset.type = data.type; | |
| blockEl.dataset.class = data.class; | |
| blockPalette.appendChild(blockEl); | |
| }); | |
| } | |
| function updateEquation() { | |
| const level = levels[currentLevel]; | |
| equationTitle.textContent = `(${level.a} + ${level.b})²`; | |
| const placedCorrectBlocks = Array.from(placedBlocks.values()).filter(b => b.type === 'correct'); | |
| let parts = []; | |
| if (placedCorrectBlocks.some(b => b.originalId === 'a_squared')) parts.push(`${level.a}²`); | |
| if (placedCorrectBlocks.some(b => b.originalId === 'b_squared')) parts.push(`${level.b}²`); | |
| const abCount = placedCorrectBlocks.filter(b => b.originalId.startsWith('ab')).length; | |
| if (abCount === 1) parts.push(`(${level.a}×${level.b})`); | |
| if (abCount === 2) parts.push(`2(${level.a}×${level.b})`); | |
| if (parts.length > 0) { | |
| equationResult.innerHTML = parts.join(' + '); | |
| } else { | |
| equationResult.innerHTML = '...'; | |
| } | |
| } | |
| // --- 拖曳事件處理 --- | |
| function onDragStart(e) { | |
| const target = e.target.closest('.palette-block'); | |
| if (!target || target.classList.contains('placed')) return; | |
| e.preventDefault(); | |
| draggingElement = target; | |
| const rect = target.getBoundingClientRect(); | |
| const pointer = getPointer(e); | |
| offset.x = pointer.x - rect.left; | |
| offset.y = pointer.y - rect.top; | |
| clone = target.cloneNode(true); | |
| clone.classList.remove('palette-block'); | |
| clone.classList.add('dragging-clone'); | |
| clone.style.width = `${target.dataset.w * UNIT_SIZE}px`; | |
| clone.style.height = `${target.dataset.h * UNIT_SIZE}px`; | |
| document.body.appendChild(clone); | |
| moveClone(pointer.x, pointer.y); | |
| document.addEventListener('mousemove', onDragMove); | |
| document.addEventListener('touchmove', onDragMove, { passive: false }); | |
| document.addEventListener('mouseup', onDragEnd); | |
| document.addEventListener('touchend', onDragEnd); | |
| } | |
| function onDragMove(e) { | |
| if (!clone) return; | |
| e.preventDefault(); | |
| const pointer = getPointer(e); | |
| moveClone(pointer.x, pointer.y); | |
| } | |
| function onDragEnd(e) { | |
| if (!draggingElement || !clone) return; | |
| const gridRect = gridContainer.getBoundingClientRect(); | |
| const pointer = getPointer(e); | |
| const blockTopLeftX = pointer.x - gridRect.left - offset.x; | |
| const blockTopLeftY = pointer.y - gridRect.top - offset.y; | |
| const gridX = Math.round(blockTopLeftX / UNIT_SIZE); | |
| const gridY = Math.round(blockTopLeftY / UNIT_SIZE); | |
| const w = parseInt(draggingElement.dataset.w); | |
| const h = parseInt(draggingElement.dataset.h); | |
| const type = draggingElement.dataset.type; | |
| if (canPlace(gridX, gridY, w, h, type)) { | |
| placeBlock(gridX, gridY, w, h); | |
| } | |
| document.body.removeChild(clone); | |
| clone = null; | |
| draggingElement = null; | |
| document.removeEventListener('mousemove', onDragMove); | |
| document.removeEventListener('touchmove', onDragMove); | |
| document.removeEventListener('mouseup', onDragEnd); | |
| document.removeEventListener('touchend', onDragEnd); | |
| } | |
| function canPlace(gridX, gridY, w, h, type) { | |
| const totalSize = levels[currentLevel].a + levels[currentLevel].b; | |
| if (currentLevel === 1 && type === 'distractor') { | |
| return false; | |
| } | |
| if (gridX < 0 || gridY < 0 || gridX + w > totalSize || gridY + h > totalSize) { | |
| return false; | |
| } | |
| for (let i = gridY; i < gridY + h; i++) { | |
| for (let j = gridX; j < gridX + w; j++) { | |
| if (i >= totalSize || j >= totalSize || gridState[i][j] !== null) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| function placeBlock(gridX, gridY, w, h) { | |
| const placedId = `placed-${Date.now()}`; | |
| for (let i = gridY; i < gridY + h; i++) { | |
| for (let j = gridX; j < gridX + w; j++) { | |
| gridState[i][j] = placedId; | |
| } | |
| } | |
| const placedEl = document.createElement('div'); | |
| placedEl.id = placedId; | |
| placedEl.className = `placed-block ${draggingElement.dataset.class} flex items-center justify-center text-white font-bold`; | |
| placedEl.style.left = `${gridX * UNIT_SIZE}px`; | |
| placedEl.style.top = `${gridY * UNIT_SIZE}px`; | |
| placedEl.style.width = `${w * UNIT_SIZE}px`; | |
| placedEl.style.height = `${h * UNIT_SIZE}px`; | |
| placedEl.textContent = `${w}×${h}`; | |
| gridContainer.appendChild(placedEl); | |
| placedBlocks.set(placedId, { | |
| el: placedEl, | |
| originalId: draggingElement.dataset.id, | |
| type: draggingElement.dataset.type, | |
| gridX, gridY, w, h | |
| }); | |
| draggingElement.classList.add('placed'); | |
| placedEl.addEventListener('click', () => removeBlock(placedId)); | |
| updateEquation(); | |
| checkWinCondition(); | |
| } | |
| function removeBlock(placedId) { | |
| const block = placedBlocks.get(placedId); | |
| if (!block) return; | |
| gridContainer.removeChild(block.el); | |
| for (let i = block.gridY; i < block.gridY + block.h; i++) { | |
| for (let j = block.gridX; j < block.gridX + block.w; j++) { | |
| if (gridState[i][j] === placedId) { | |
| gridState[i][j] = null; | |
| } | |
| } | |
| } | |
| placedBlocks.delete(placedId); | |
| const originalBlock = document.getElementById(`palette-${block.originalId}`); | |
| if (originalBlock) { | |
| originalBlock.classList.remove('placed'); | |
| } | |
| updateEquation(); | |
| } | |
| function checkWinCondition() { | |
| const level = levels[currentLevel]; | |
| const totalSize = level.a + level.b; | |
| const placedCorrectCount = Array.from(placedBlocks.values()).filter(b => b.type === 'correct').length; | |
| if (placedCorrectCount !== 4) return; | |
| let isFull = true; | |
| for (let i = 0; i < totalSize; i++) { | |
| for (let j = 0; j < totalSize; j++) { | |
| if (gridState[i][j] === null) { | |
| isFull = false; | |
| break; | |
| } | |
| } | |
| if (!isFull) break; | |
| } | |
| if (isFull) { | |
| showWinState(); | |
| } | |
| } | |
| function showWinState() { | |
| const level = levels[currentLevel]; | |
| const finalFormula = `(${level.a}+${level.b})² = ${level.a}² + <span class="highlight-win">2×${level.a}×${level.b}</span> + ${level.b}²`; | |
| winFormulaExplanation.innerHTML = finalFormula; | |
| winMessageContainer.classList.remove('hidden'); | |
| blockPalette.style.pointerEvents = 'none'; | |
| gridContainer.style.pointerEvents = 'none'; | |
| } | |
| // --- 捲動與輔助函式 --- | |
| function updateScrollButtonsVisibility() { | |
| const palette = blockPalette; | |
| const scrollLeft = palette.scrollLeft; | |
| const scrollWidth = palette.scrollWidth; | |
| const clientWidth = palette.clientWidth; | |
| if (scrollWidth <= clientWidth) { | |
| scrollLeftButton.classList.add('hidden'); | |
| scrollRightButton.classList.add('hidden'); | |
| return; | |
| } | |
| if (scrollLeft > 0) { | |
| scrollLeftButton.classList.remove('hidden'); | |
| } else { | |
| scrollLeftButton.classList.add('hidden'); | |
| } | |
| if (scrollLeft < scrollWidth - clientWidth - 1) { | |
| scrollRightButton.classList.remove('hidden'); | |
| } else { | |
| scrollRightButton.classList.add('hidden'); | |
| } | |
| } | |
| function getPointer(e) { | |
| // For touchend event, we need to use changedTouches because touches is empty. | |
| if (e.changedTouches && e.changedTouches.length > 0) { | |
| return { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY }; | |
| } | |
| // For touchstart and touchmove events. | |
| if (e.touches && e.touches.length > 0) { | |
| return { x: e.touches[0].clientX, y: e.touches[0].clientY }; | |
| } | |
| // Fallback for mouse events. | |
| return { x: e.clientX, y: e.clientY }; | |
| } | |
| function moveClone(x, y) { | |
| if (!clone) return; | |
| clone.style.left = `${x - offset.x}px`; | |
| clone.style.top = `${y - offset.y}px`; | |
| } | |
| // --- 事件監聽 --- | |
| startButton.addEventListener('click', () => { | |
| startScreen.style.opacity = '0'; | |
| gameContainer.classList.remove('hidden'); | |
| setTimeout(() => { | |
| startScreen.classList.add('hidden'); | |
| gameContainer.style.opacity = '1'; | |
| initLevel(currentLevel); | |
| }, 500); | |
| }); | |
| blockPalette.addEventListener('mousedown', onDragStart); | |
| blockPalette.addEventListener('touchstart', onDragStart, { passive: false }); | |
| blockPalette.addEventListener('scroll', updateScrollButtonsVisibility); | |
| window.addEventListener('resize', updateScrollButtonsVisibility); | |
| scrollLeftButton.addEventListener('click', () => { | |
| blockPalette.scrollBy({ left: -200, behavior: 'smooth' }); | |
| }); | |
| scrollRightButton.addEventListener('click', () => { | |
| blockPalette.scrollBy({ left: 200, behavior: 'smooth' }); | |
| }); | |
| nextLevelButton.addEventListener('click', () => { | |
| if (currentLevel < levels.length - 1) { | |
| initLevel(currentLevel + 1); | |
| blockPalette.style.pointerEvents = 'auto'; | |
| gridContainer.style.pointerEvents = 'auto'; | |
| } else { | |
| // 顯示秘密畫面 | |
| gameContainer.style.opacity = '0'; | |
| secretView.classList.remove('hidden'); | |
| setTimeout(() => { | |
| gameContainer.classList.add('hidden'); | |
| secretView.style.opacity = '1'; | |
| }, 500); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |