<!DOCTYPE html>
<html lang="zh-HK">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>100層樓的家 - 語文探索之旅</title>
<style>
* { box-sizing: border-box; user-select: none; }
body { margin: 0; padding: 0; background: #e0f2fe; font-family: "PingFang HK", "Microsoft JhengHei", sans-serif; overflow: hidden; }
#header { position: fixed; top: 0; left: 0; width: 100%; height: 50px; background: rgba(255,255,255,0.9); display: flex; justify-content: space-between; align-items: center; padding: 0 16px; font-size: 1.1rem; font-weight: bold; border-bottom: 2px solid #bae6fd; z-index: 100; }
#game-container { position: relative; width: 100vw; height: calc(100vh - 140px); margin-top: 50px; overflow: hidden; background: #fdf6e2; }
.floor { position: absolute; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; background-size: cover; background-position: center; transition: transform 0.6s ease; }
.ladder { position: absolute; right: 25px; bottom: 0; width: 60px; height: 100%; background: repeating-linear-gradient(0deg, #d97706, #d97706 10px, transparent 10px, transparent 40px); border-left: 6px solid #b45309; border-right: 6px solid #b45309; cursor: pointer; }
#player { position: absolute; bottom: 10px; left: 60px; width: 70px; height: 85px; transition: left 0.15s linear, bottom 0.2s ease; z-index: 20; }
#player img { width: 100%; height: 100%; object-fit: contain; }
#touch-controls { position: fixed; bottom: 0; left: 0; width: 100%; height: 90px; background: #ffffff; display: flex; justify-content: space-around; align-items: center; border-top: 2px solid #cbd5e1; z-index: 100; }
.btn-ctrl { background: #38bdf8; border: none; border-radius: 12px; color: white; font-size: 1.4rem; font-weight: bold; padding: 12px 24px; box-shadow: 0 4px #0284c7; }
.btn-ctrl:active { transform: translateY(3px); box-shadow: 0 1px #0284c7; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 200; justify-content: center; align-items: center; }
.modal-content { background: #fff; width: 90%; max-width: 520px; border-radius: 20px; padding: 20px; text-align: center; border: 4px solid #38bdf8; position: relative; }
.btn-difficulty { width: 100%; margin: 8px 0; padding: 14px; font-size: 1.15rem; font-weight: bold; border-radius: 12px; border: none; cursor: pointer; color: white; }
.btn-easy { background: #22c55e; }
.btn-medium { background: #f59e0b; }
.btn-hard { background: #ef4444; }
.question-box { font-size: 1.25rem; font-weight: bold; margin: 15px 0; color: #1e293b; }
.options-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; }
.option-card { background: #f1f5f9; border: 3px solid #94a3b8; border-radius: 12px; padding: 15px 10px; font-size: 1.15rem; font-weight: bold; cursor: pointer; }
.drop-slot { display: inline-block; min-width: 100px; height: 38px; border-bottom: 3px dashed #0284c7; background: #e0f2fe; margin: 0 4px; vertical-align: middle; border-radius: 4px; line-height: 38px; }
.word-chip { display: inline-block; background: #fed7aa; border: 2px solid #f97316; padding: 6px 14px; border-radius: 20px; margin: 6px; font-size: 1.1rem; cursor: pointer; font-weight: bold; }
.btn-voice { background: #ef4444; color: white; border: none; border-radius: 50%; width: 64px; height: 64px; font-size: 1.8rem; margin-top: 10px; cursor: pointer; }
.btn-hint { background: #a855f7; color: white; border: none; border-radius: 20px; padding: 6px 16px; font-size: 0.95rem; margin-top: 10px; cursor: pointer; }
#hint-text { margin-top: 8px; color: #7e22ce; font-weight: bold; min-height: 24px; }
</style>
</head>
<body>
<div id="header">
<div id="floor-title">第 1 層:雲朵房間</div>
<div id="stars-display">⭐ 星星:0</div>
</div>
<div id="game-container">
<div id="floor-view" class="floor">
<div class="ladder" onclick="triggerDoor()"></div>
<div id="player">
<img id="avatar-img" src="avatar.png" alt="主角" onerror="this.src='data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><circle cx=\'50\' cy=\'40\' r=\'25\' fill=\'%23f59e0b\'/><rect x=\'30\' y=\'65\' width=\'40\' height=\'35\' rx=\'10\' fill=\'%233b82f6\'/></svg>'">
</div>
</div>
</div>
<div id="touch-controls">
<button class="btn-ctrl" onclick="movePlayer(-30)">◀ 左移</button>
<button class="btn-ctrl" style="background:#22c55e;" onclick="triggerDoor()">登梯 / 挑戰 ▲</button>
<button class="btn-ctrl" onclick="movePlayer(30)">右移 ▶</button>
</div>
<div id="difficulty-modal" class="modal">
<div class="modal-content">
<h2>請選擇挑戰難度</h2>
<button class="btn-difficulty btn-easy" onclick="startChallenge('easy')">⭐ 普通難度(看圖選詞)</button>
<button class="btn-difficulty btn-medium" onclick="startChallenge('medium')">⭐⭐ 中等難度(填空造句)</button>
<button class="btn-difficulty btn-hard" onclick="startChallenge('hard')">⭐⭐⭐ 高難度(語音說話)</button>
</div>
</div>
<div id="task-modal" class="modal">
<div class="modal-content">
<div id="task-body"></div>
<button class="btn-hint" onclick="provideHint()">💡 提示我</button>
<div id="hint-text"></div>
</div>
</div>
<script>
const gameConfig = [
{
floor: 1,
title: "第 1 層:雲朵房間",
bgImage: "floor_1.jpg",
sentenceTemplate: "這裏有一張{adj}的床。",
hintText: "摸上去好似軟綿綿的雲朵咁樣!",
easy: {
question: "這裏有一張怎樣的床?",
options: [
{ label: "柔軟", icon: "☁️", isCorrect: true },
{ label: "堅硬", icon: "🪨", isCorrect: false }
]
},
medium: {
templatePrefix: "這裏有一張",
templateSuffix: "的床。",
wordBank: ["柔軟", "冰冷", "可怕"],
correctWord: "柔軟"
},
hard: {
question: "主角來到了一個怎樣的房間?請用粵語講出完整的句子:",
keywords: ["柔軟", "舒服", "大", "雲"],
example: "這裏有一張柔軟的床。"
}
},
{
floor: 2,
title: "第 2 層:彩虹花園",
bgImage: "floor_2.jpg",
sentenceTemplate: "房間裏開滿了{adj}的花朵。",
hintText: "花朵有好多鮮艷的顏色!",
easy: {
question: "房間裏開滿了怎樣的花朵?",
options: [
{ label: "美麗", icon: "🌸", isCorrect: true },
{ label: "漆黑", icon: "⬛", isCorrect: false }
]
},
medium: {
templatePrefix: "房間裏開滿了",
templateSuffix: "的花朵。",
wordBank: ["美麗", "寒冷", "吵鬧"],
correctWord: "美麗"
},
hard: {
question: "請形容這個房間的花朵:",
keywords: ["美麗", "鮮艷", "五顏六色", "彩虹"],
example: "房間裏開滿了美麗的花朵。"
}
}
];
let currentFloorIndex = 0;
let playerX = 60;
let stars = 0;
let selectedLevel = 'easy';
function playTone(freq, duration = 0.15) {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.frequency.value = freq;
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + duration);
osc.stop(ctx.currentTime + duration);
} catch (e) {}
}
function speakCantonese(text) {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'zh-HK';
utterance.rate = 0.9;
window.speechSynthesis.speak(utterance);
}
}
function movePlayer(offset) {
const containerWidth = window.innerWidth;
playerX = Math.max(20, Math.min(containerWidth - 90, playerX + offset));
document.getElementById('player').style.left = playerX + 'px';
}
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') movePlayer(-30);
if (e.key === 'ArrowRight') movePlayer(30);
if (e.key === 'ArrowUp' || e.key === 'Enter') triggerDoor();
});
function triggerDoor() {
document.getElementById('difficulty-modal').style.display = 'flex';
}
function startChallenge(level) {
selectedLevel = level;
document.getElementById('difficulty-modal').style.display = 'none';
document.getElementById('hint-text').innerText = '';
const cfg = gameConfig[currentFloorIndex];
const modal = document.getElementById('task-modal');
const body = document.getElementById('task-body');
if (level === 'easy') {
speakCantonese(cfg.easy.question);
body.innerHTML = `
<div class="question-box">🔊 ${cfg.easy.question}</div>
<div class="options-grid">
${cfg.easy.options.map(opt => `
<div class="option-card" onclick="checkAnswer(${opt.isCorrect})">
<div style="font-size:2.2rem;">${opt.icon}</div>
<div>${opt.label}</div>
</div>
`).join('')}
</div>
`;
} else if (level === 'medium') {
speakCantonese(cfg.medium.templatePrefix + "甚麼" + cfg.medium.templateSuffix);
body.innerHTML = `
<div class="question-box">${cfg.medium.templatePrefix} <span id="target-slot" class="drop-slot"></span> ${cfg.medium.templateSuffix}</div>
<div style="margin-top:15px;">
${cfg.medium.wordBank.map(w => `<span class="word-chip" onclick="fillWord('${w}', '${cfg.medium.correctWord}')">${w}</span>`).join('')}
</div>
`;
} else if (level === 'hard') {
speakCantonese(cfg.hard.question);
body.innerHTML = `
<div class="question-box">🎤 ${cfg.hard.question}</div>
<p style="color:#64748b; font-size:0.95rem;">點擊麥克風說出答案,或直接鍵入:</p>
<button class="btn-voice" onclick="startSpeechRec()">🎙️</button>
<div style="margin-top:10px;">
<input type="text" id="manual-input" style="width:75%; padding:8px; font-size:1rem; border-radius:6px; border:1px solid #cbd5e1;" placeholder="語音辨識結果或手動輸入...">
<button style="padding:8px 12px; border-radius:6px; border:none; background:#38bdf8; color:white; font-weight:bold;" onclick="checkHardAnswer(document.getElementById('manual-input').value)">確定</button>
</div>
`;
}
modal.style.display = 'flex';
}
function fillWord(word, correctWord) {
document.getElementById('target-slot').innerText = word;
setTimeout(() => checkAnswer(word === correctWord), 350);
}
function startSpeechRec() {
const SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRec) {
alert("瀏覽器未支援語音識別,請直接於文字框輸入回答。");
return;
}
const rec = new SpeechRec();
rec.lang = 'zh-HK';
rec.onresult = (e) => {
const text = e.results[0][0].transcript;
document.getElementById('manual-input').value = text;
checkHardAnswer(text);
};
rec.start();
}
function checkHardAnswer(text) {
const keywords = gameConfig[currentFloorIndex].hard.keywords;
const isHit = keywords.some(k => text.includes(k));
checkAnswer(isHit);
}
function provideHint() {
const hint = gameConfig[currentFloorIndex].hintText;
document.getElementById('hint-text').innerText = "💡 提示:" + hint;
speakCantonese("提示:" + hint);
}
function checkAnswer(isCorrect) {
if (isCorrect) {
playTone(523.25); setTimeout(() => playTone(659.25), 100); setTimeout(() => playTone(783.99), 200);
stars += (selectedLevel === 'hard' ? 3 : (selectedLevel === 'medium' ? 2 : 1));
document.getElementById('stars-display').innerText = "⭐ 星星:" + stars;
speakCantonese("答啱咗喇!好叻呀!");
alert("🎉 答對了!獲得星星!");
document.getElementById('task-modal').style.display = 'none';
nextFloor();
} else {
playTone(220, 0.25);
speakCantonese("唔緊要,再試一次啦!或者按提示按鈕。");
alert("差少少,再試一次或按提示按鈕睇睇!");
}
}
function nextFloor() {
if (currentFloorIndex < gameConfig.length - 1) {
currentFloorIndex++;
const cfg = gameConfig[currentFloorIndex];
document.getElementById('floor-title').innerText = cfg.title;
document.getElementById('floor-view').style.backgroundImage = `url('${cfg.bgImage}')`;
playerX = 60;
document.getElementById('player').style.left = playerX + 'px';
} else {
alert("🏆 太厲害了!你已經完成所有樓層的挑戰,成為描寫小達人!");
}
}
window.onload = () => {
const firstCfg = gameConfig[0];
document.getElementById('floor-view').style.backgroundImage = `url('${firstCfg.bgImage}')`;
};
</script>
</body>
</html>