Taf2023's picture
ต้องมีเสียง browser แต่ละการกดปุ่มด้วยตอนนี้ปุ่มดูเหมือนจะไม่ทำงาน
406efb9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blind Chef's Culinary Journey</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&display=swap');
body {
font-family: 'Atkinson Hyperlegible', sans-serif;
}
.screen-reader-text {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.audio-cue {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.haptic-feedback {
transition: transform 0.1s;
}
.haptic-feedback:active {
transform: scale(0.95);
}
</style>
</head>
<body class="bg-amber-50 text-gray-900 min-h-screen flex flex-col">
<!-- Audio elements for accessibility -->
<audio id="welcome-sound" class="audio-cue" src="https://assets.mixkit.co/sfx/preview/mixkit-positive-interface-beep-221.mp3" preload="auto"></audio>
<audio id="button-sound" class="audio-cue" src="https://assets.mixkit.co/sfx/preview/mixkit-modern-click-box-check-1120.mp3" preload="auto"></audio>
<audio id="error-sound" class="audio-cue" src="https://assets.mixkit.co/sfx/preview/mixkit-warning-alarm-688.mp3" preload="auto"></audio>
<audio id="success-sound" class="audio-cue" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio>
<!-- Main container with ARIA landmarks -->
<main class="flex-1 container mx-auto px-4 py-8" role="main" aria-labelledby="main-heading">
<div id="vanta-bg" class="fixed inset-0 -z-10 opacity-20"></div>
<header class="mb-12 text-center">
<h1 id="main-heading" class="text-4xl md:text-5xl font-bold mb-4 text-amber-800" tabindex="0">Blind Chef's Culinary Journey</h1>
<p class="text-lg md:text-xl text-amber-700 max-w-3xl mx-auto" tabindex="0">
Experience the world of culinary arts through sound, touch, and imagination.
</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
<!-- Game Section -->
<section aria-labelledby="game-section" class="bg-white rounded-xl shadow-lg p-6 md:col-span-2">
<h2 id="game-section" class="text-2xl font-bold mb-6 text-amber-700" tabindex="0">Your Restaurant</h2>
<div id="game-container" class="space-y-6">
<div class="bg-amber-100 rounded-lg p-4 border-l-4 border-amber-400">
<h3 class="font-bold mb-2" tabindex="0">Today's Special</h3>
<p id="daily-special" class="mb-4" tabindex="0">Loading today's menu...</p>
<button id="generate-special" class="haptic-feedback bg-amber-600 hover:bg-amber-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Generate today's special dish">
<i data-feather="refresh-cw" aria-hidden="true"></i>
<span>Generate Special</span>
</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="bg-white border border-amber-200 rounded-lg p-4">
<h3 class="font-bold mb-2" tabindex="0">Customers Waiting</h3>
<p id="customer-count" class="text-3xl font-bold text-amber-600" tabindex="0">0</p>
<button id="serve-customer" class="haptic-feedback mt-2 bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Serve next customer">
<i data-feather="user-plus" aria-hidden="true"></i>
<span>Serve Customer</span>
</button>
</div>
<div class="bg-white border border-amber-200 rounded-lg p-4">
<h3 class="font-bold mb-2" tabindex="0">Dishes Prepared</h3>
<p id="dish-count" class="text-3xl font-bold text-amber-600" tabindex="0">0</p>
<button id="prepare-dish" class="haptic-feedback mt-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Prepare a dish">
<i data-feather="utensils" aria-hidden="true"></i>
<span>Prepare Dish</span>
</button>
</div>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-200">
<h3 class="font-bold mb-2" tabindex="0">Kitchen Status</h3>
<div id="kitchen-status" class="space-y-2" tabindex="0">
<p>Your kitchen is quiet. Start preparing dishes!</p>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section aria-labelledby="stats-section" class="bg-white rounded-xl shadow-lg p-6">
<h2 id="stats-section" class="text-2xl font-bold mb-6 text-amber-700" tabindex="0">Restaurant Stats</h2>
<div class="space-y-6">
<div>
<h3 class="font-bold mb-2" tabindex="0">Total Customers Served</h3>
<p id="total-customers" class="text-3xl font-bold text-amber-600" tabindex="0">0</p>
</div>
<div>
<h3 class="font-bold mb-2" tabindex="0">Total Dishes Prepared</h3>
<p id="total-dishes" class="text-3xl font-bold text-amber-600" tabindex="0">0</p>
</div>
<div>
<h3 class="font-bold mb-2" tabindex="0">Popular Dishes</h3>
<ol id="popular-dishes" class="list-decimal list-inside space-y-1" tabindex="0">
<li>No dishes prepared yet</li>
</ol>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-200">
<h3 class="font-bold mb-2" tabindex="0">Save Your Progress</h3>
<div class="flex flex-col gap-2">
<button id="save-game" class="haptic-feedback bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Save game progress">
<i data-feather="save" aria-hidden="true"></i>
<span>Save Game</span>
</button>
<button id="load-game" class="haptic-feedback bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Load saved game">
<i data-feather="upload" aria-hidden="true"></i>
<span>Load Game</span>
</button>
<button id="reset-game" class="haptic-feedback bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors" aria-label="Reset game data">
<i data-feather="trash-2" aria-hidden="true"></i>
<span>Reset Game</span>
</button>
</div>
</div>
</div>
</section>
</div>
<!-- Audio feedback for actions -->
<div class="screen-reader-text" aria-live="polite" id="audio-feedback"></div>
</main>
<footer class="bg-amber-800 text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p tabindex="0">An accessible restaurant simulation game for everyone</p>
<p class="mt-2" tabindex="0">All progress is saved in your browser's local storage</p>
</div>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
<script>
// Initialize Vanta.js background
VANTA.GLOBE({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0xe3a008,
backgroundColor: 0xfef3c7,
size: 0.80
});
// Game state
const gameState = {
customersWaiting: 0,
dishesPrepared: 0,
totalCustomers: 0,
totalDishes: 0,
dailySpecial: "",
popularDishes: {},
kitchenLog: [],
lastSaved: null
};
// Dish database
const dishes = [
"Braille Burgers", "Tactile Tacos", "Audio Alfredo",
"Sensory Salad", "Haptic Hotdogs", "Echo Eclairs",
"Guide Dog Gyros", "White Cane Waffles", "Sonar Soufflé",
"Accessible Apple Pie", "Descriptive Dumplings",
"Non-Visual Nachos", "Olfactory Omelette", "Audio-Descriptive Asparagus",
"Haptic Hash Browns", "Talking Tiramisu", "Sighted Guide Sandwich",
"Braille Biscuits", "Echo Espresso", "Tactile Toast"
];
// Initialize game
document.addEventListener('DOMContentLoaded', () => {
feather.replace();
playSound('welcome-sound');
loadGame();
updateUI();
// Set up event listeners with haptic feedback and sound
const buttons = [
{id: 'generate-special', func: generateDailySpecial},
{id: 'serve-customer', func: serveCustomer},
{id: 'prepare-dish', func: prepareDish},
{id: 'save-game', func: saveGame},
{id: 'load-game', func: loadGame},
{id: 'reset-game', func: resetGame}
];
buttons.forEach(btn => {
const button = document.getElementById(btn.id);
button.addEventListener('click', () => {
playSound('button-sound');
btn.func();
});
});
// Add keyboard navigation
document.querySelectorAll('button').forEach(button => {
button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
button.click();
}
});
});
});
// Game functions
function generateDailySpecial() {
const randomIndex = Math.floor(Math.random() * dishes.length);
gameState.dailySpecial = dishes[randomIndex];
updateUI();
speakFeedback(`Today's special is ${gameState.dailySpecial}`);
}
function serveCustomer() {
gameState.customersWaiting++;
gameState.totalCustomers++;
updateUI();
speakFeedback(`Customer arrived. Total waiting: ${gameState.customersWaiting}`);
// Add to kitchen log
addKitchenLog(`Customer #${gameState.totalCustomers} arrived`);
}
function prepareDish() {
if (gameState.customersWaiting === 0) {
playSound('error-sound');
speakFeedback("No customers to serve!");
addKitchenLog("Tried to prepare dish with no customers");
return;
}
if (!gameState.dailySpecial) {
playSound('error-sound');
speakFeedback("Please set today's special first");
return;
}
gameState.customersWaiting--;
gameState.dishesPrepared++;
gameState.totalDishes++;
// Track popular dishes
if (!gameState.popularDishes[gameState.dailySpecial]) {
gameState.popularDishes[gameState.dailySpecial] = 0;
}
gameState.popularDishes[gameState.dailySpecial]++;
// Add to kitchen log
addKitchenLog(`Prepared ${gameState.dailySpecial} for customer`);
playSound('success-sound');
updateUI();
speakFeedback(`Prepared ${gameState.dailySpecial}. ${gameState.customersWaiting} customers remaining`);
}
function addKitchenLog(message) {
const timestamp = new Date().toLocaleTimeString();
gameState.kitchenLog.unshift(`${timestamp}: ${message}`);
// Keep only the last 5 logs
if (gameState.kitchenLog.length > 5) {
gameState.kitchenLog.pop();
}
updateUI();
}
function saveGame() {
try {
localStorage.setItem('blindChefGame', JSON.stringify(gameState));
gameState.lastSaved = new Date();
speakFeedback("Game progress saved successfully");
addKitchenLog("Game saved to browser storage");
updateUI();
playSound('success-sound');
} catch (e) {
playSound('error-sound');
speakFeedback("Error saving game: " + e.message);
}
}
function loadGame() {
try {
const savedGame = localStorage.getItem('blindChefGame');
if (savedGame) {
const parsedGame = JSON.parse(savedGame);
Object.assign(gameState, parsedGame);
speakFeedback("Game loaded successfully");
addKitchenLog("Loaded game from browser storage");
updateUI();
playSound('success-sound');
} else {
speakFeedback("No saved game found");
}
} catch (e) {
playSound('error-sound');
speakFeedback("Error loading game: " + e.message);
}
}
function resetGame() {
if (confirm("Are you sure you want to reset all game data?")) {
localStorage.removeItem('blindChefGame');
Object.assign(gameState, {
customersWaiting: 0,
dishesPrepared: 0,
totalCustomers: 0,
totalDishes: 0,
dailySpecial: "",
popularDishes: {},
kitchenLog: [],
lastSaved: null
});
speakFeedback("Game has been reset");
updateUI();
playSound('success-sound');
} else {
speakFeedback("Reset cancelled");
}
}
function updateUI() {
// Update counters
document.getElementById('customer-count').textContent = gameState.customersWaiting;
document.getElementById('dish-count').textContent = gameState.dishesPrepared;
document.getElementById('total-customers').textContent = gameState.totalCustomers;
document.getElementById('total-dishes').textContent = gameState.totalDishes;
// Update daily special
const dailySpecialEl = document.getElementById('daily-special');
if (gameState.dailySpecial) {
dailySpecialEl.textContent = gameState.dailySpecial;
} else {
dailySpecialEl.textContent = "No special set yet";
}
// Update kitchen log
const kitchenStatusEl = document.getElementById('kitchen-status');
kitchenStatusEl.innerHTML = '';
if (gameState.kitchenLog.length > 0) {
gameState.kitchenLog.forEach(log => {
const p = document.createElement('p');
p.textContent = log;
kitchenStatusEl.appendChild(p);
});
} else {
kitchenStatusEl.innerHTML = '<p>Your kitchen is quiet. Start preparing dishes!</p>';
}
// Update popular dishes
const popularDishesEl = document.getElementById('popular-dishes');
popularDishesEl.innerHTML = '';
if (Object.keys(gameState.popularDishes).length > 0) {
// Sort dishes by popularity
const sortedDishes = Object.entries(gameState.popularDishes)
.sort((a, b) => b[1] - a[1])
.slice(0, 3);
sortedDishes.forEach(([dish, count]) => {
const li = document.createElement('li');
li.textContent = `${dish} (${count})`;
popularDishesEl.appendChild(li);
});
} else {
const li = document.createElement('li');
li.textContent = "No dishes prepared yet";
popularDishesEl
</body>
</html>