Interactive Classroom Seating Game
This prompt describes a single, all-in-one HTML page for teachers. It functions as an interactive tool to create, save, and load classroom layouts, add student names and photos, and play a game to memorize them.
Prompt
Create a single, complete HTML page that serves as an interactive tool for teachers to create a classroom floor plan and learn student names. The page should contain all HTML, CSS, and JavaScript in a single file.
Tool Goal:
A teacher should be able to build a floor plan, assign names and photos to tables, save this arrangement as a file, and play a game to practice the names.
### General Structure and Style
Layout: The interface consists of a top control panel and a large "classroom" area below.
Styling: Use the "Poppins" font from Google Fonts and Font Awesome for all icons. The style should be modern and clean, with a color palette of blue (primary), orange/yellow (accent), and green (success). Use soft shadows for depth.
Help Sidebar: There is a help button with an info icon. Clicking this button causes a sidebar with instructions to slide in from the left side of the screen. Clicking again hides the sidebar.
### Visual Components (UI)
Control panel (top):
Button to show/hide the help sidebar.
Buttons to add elements: 'Student desk' (<i class="fa-solid fa-user-graduate"></i>), 'Teacher desk' (<i class="fa-solid fa-chalkboard-user"></i>), and 'Blackboard' (<i class="fa-solid fa-person-chalkboard"></i>).
A 'Remove mode' button (<i class="fa-solid fa-eraser"></i>).
A dropdown menu with standard layouts: 'Bus layout', 'Groups', 'U-shape'.
File manager:
A 'Download' button (<i class="fa-solid fa-download"></i>) to save the floor plan as a file. An 'Upload' button (<i class="fa-solid fa-upload"></i>) to load a saved file.
A 'Clear' button (<i class="fa-solid fa-trash"></i>).
A 'Start Game' button.
A score counter in the top right corner.
Classroom Elements:
Student Desk: A simple rectangular desk without a chair.
Teacher Desk: A larger, slightly darker desk without a chair.
Blackboard: A thin, white rectangle with a dark border.
### Functionality in Setup Mode (default)
Add & Drag: Users can click the buttons to add elements. All elements in the classroom should be draggable.
Select & Remove:
A single click on an element selects it and gives it a clear blue border.
Once an element is selected, it can be removed with the 'Delete' or 'Backspace' key. The 'Delete Mode' button activates a mode in which clicking any element immediately deletes it.
Adding Names: Double-clicking a student table makes its name editable. If no name has been created yet, a pencil icon should appear in the center of the table.
Adding Photos: When the user hovers the mouse over a student table, a camera icon appears. Clicking this opens a dialog box to select an image file. The photo is resized and saved as a data URI, and appears as the table's background.
Default Layouts:
Choosing a layout from the dropdown menu should replace the current floor plan (after confirmation).
The 'Bus Layout' must consist exactly of a board at the top, a teacher's desk on the left, and 30 student tables in three columns of pairs (table...table), five rows deep.
The tool starts with this bus layout by default.
Download & Upload:
The 'Download' button should save the complete state of the classroom (positions, names, and photos as data URIs) to a .json file that the user can download.
The 'Upload' button should open a dialog box where the user can select this .json file. After selection, the floor plan will be fully restored.
### Functionality in Game Mode
Start: The 'Start Game' button starts the mode. All named tables flip over with a 3D flip effect. The back shows the student's photo, or a question mark if there is no photo. All editing buttons are disabled.
Play:
Clicking on a flipped table opens a custom pop-up (modal) in the center of the screen, not a browser prompt.
The modal asks "Who sits here?" and has an input field and two buttons: 'Cancel' and 'Guess!'.
Feedback:
Correct answer: A green "toast" notification appears in the bottom right corner with a congratulatory message. The score increases by 1. The selected table permanently flips back to the front (with name and photo).
Incorrect answer: A red "toast" notification appears, revealing the correct name. The table does NOT flip. Instead, the element gets a short "shake" animation as visual feedback that the answer was incorrect. The table remains on its question mark side.
Reset: The "Start Game" button changes to "Reset Game." Clicking this ends the game mode, flips all tables back, resets the score to 0, and reactivates all editing buttons.Example Output
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Klaslokaal Pro - Namen Leren</title>
<!-- Google Fonts & Font Awesome Icons -->
<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=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css">
<link rel="icon" type="image/png" href="/images/favicon1.png">
<style>
:root {
--primary-color: #4A90E2; --secondary-color: #50E3C2; --accent-color: #F5A623;
--dark-color: #4A4A4A; --light-color: #FDFDFD; --floor-color: #F4F1E8;
--desk-color: #D2B48C; --desk-border: #8B5A2B;
--success-color: #28a745; --error-color: #dc3545; --selection-color: #007bff;
}
/* --- Algemene Styling & App Container --- */
body {
font-family: 'Poppins', sans-serif; display: flex; flex-direction: column;
align-items: center; background-color: #e9e9e9; margin: 0; padding: 20px;
box-sizing: border-box;
}
.app-container {
width: 100%; max-width: 1600px; background: var(--light-color); border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.15); overflow: hidden; display: flex; flex-direction: column;
}
/* --- Bedieningspaneel --- */
.controls {
padding: 15px 25px; background-color: var(--primary-color); color: white; display: flex;
flex-wrap: wrap; align-items: center; gap: 15px; z-index: 10;
}
.control-group { display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid rgba(255,255,255,0.2); }
.control-group:last-of-type { border-right: none; }
.controls button, .controls select {
padding: 8px 15px; border: none; border-radius: 6px; font-family: 'Poppins', sans-serif;
font-weight: 600; cursor: pointer; transition: all 0.2s ease-in-out;
background-color: rgba(255,255,255,0.2); color: white;
}
.controls button:hover { background-color: rgba(255,255,255,0.4); transform: translateY(-2px); }
.controls button.active { background-color: var(--accent-color); color: var(--dark-color); }
#game-button { background-color: var(--secondary-color); color: var(--dark-color); }
.score-display { margin-left: auto; font-size: 1.3em; font-weight: 700; background-color: rgba(0,0,0,0.1); padding: 5px 15px; border-radius: 6px; }
/* --- Hoofdgedeelte & Zijbalk --- */
.main-content { display: flex; position: relative; }
.sidebar {
width: 280px; padding: 20px; background-color: #f8f9fa; border-right: 1px solid #dee2e6;
position: absolute; top: 0; left: 0; bottom: 0; z-index: 5;
transform: translateX(-100%); transition: transform 0.3s ease-in-out;
}
.sidebar.visible { transform: translateX(0); }
.sidebar h3 { margin-top: 0; color: var(--primary-color); } .sidebar ul { padding-left: 20px; font-size: 0.9em; color: #6c757d; } .sidebar li { margin-bottom: 10px; }
.classroom-container { flex-grow: 1; transition: margin-left 0.3s ease-in-out; }
.sidebar.visible + .classroom-container { margin-left: 300px; }
.classroom-area {
position: relative; min-height: 80vh; background-color: var(--floor-color);
background-image: linear-gradient(rgba(0,0,0,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(0,0,0,0.02) 1px, transparent 1px);
background-size: 20px 20px; overflow: hidden;
}
.app-container.delete-mode .class-element { cursor: crosshair !important; }
/* --- Klas-elementen --- */
.class-element { position: absolute; box-sizing: border-box; user-select: none; cursor: grab; }
.class-element.selected { outline: 3px solid var(--selection-color); border-radius: 4px; }
.class-element.dragging { cursor: grabbing; opacity: 0.7; z-index: 1000; }
.class-element.shaking { animation: shake 0.5s ease-in-out; }
.desk { width: 100px; height: 60px; }
.teacher-desk { width: 150px; height: 70px; background-color: #a0522d; border: 2px solid #8B5A2B; border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.whiteboard { width: 300px; height: 15px; background-color: #fff; border: 4px solid #333; box-shadow: 0 5px 10px rgba(0,0,0,0.2); }
/* Leerlingtafel - Interactief deel */
.desk .table-top { perspective: 1000px; background-color: transparent; border: none; box-shadow: none; width: 100%; height: 100%;}
.desk-inner { position: relative; width: 100%; height: 100%; transition: transform 0.6s; transform-style: preserve-3d; }
.desk.flipped .desk-inner { transform: rotateY(180deg); }
.desk-front, .desk-back {
position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border: 2px solid var(--desk-border);
border-radius: 4px; display: flex; justify-content: center; align-items: center;
box-sizing: border-box; box-shadow: 0 4px 6px rgba(0,0,0,0.1); overflow: hidden;
background-size: cover; background-position: center;
}
.desk-front { background-color: var(--desk-color); }
.desk-back { background-color: var(--desk-border); color: white; transform: rotateY(180deg); font-size: 2.5em; font-weight: bold; }
.desk-name {
padding: 2px 8px; width: 100%; text-align: center; overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; font-weight: 600; color: var(--dark-color); position: absolute; bottom: 0; left: 0;
background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(2px);
}
.desk-front.has-photo .desk-name { color: white; background: rgba(0, 0, 0, 0.5); }
.desk-name input { width: 90%; border: 1px solid #ccc; text-align: center; font-family: 'Poppins', sans-serif; }
.photo-upload-icon {
position: absolute; top: 5px; right: 5px; font-size: 16px; color: white; background-color: rgba(0,0,0,0.5);
width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
cursor: pointer; opacity: 0; transition: opacity 0.2s; z-index: 10;
}
.edit-icon { top: 50%; left: 50%; transform: translate(-50%, -50%); opacity: 1; color: var(--desk-border); background: none; font-size: 24px; }
.app-container:not(.game-mode) .desk:hover .photo-upload-icon { opacity: 1; }
.desk-front.has-name .edit-icon { display: none; }
/* --- Custom Modal & Toast Notificaties --- */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 2000;
display: flex; justify-content: center; align-items: center; opacity: 0; visibility: hidden; transition: all 0.3s;
}
.modal-overlay.visible { opacity: 1; visibility: visible; }
.modal-content {
background: white; padding: 30px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2);
text-align: center; width: 90%; max-width: 400px; transform: scale(0.9); transition: all 0.3s;
}
.modal-overlay.visible .modal-content { transform: scale(1); }
.modal-content h3 { margin-top: 0; color: var(--dark-color); }
.modal-content input { width: 100%; padding: 12px; border-radius: 6px; border: 1px solid #ccc; font-size: 1em; margin-bottom: 20px; box-sizing: border-box; }
.modal-buttons { display: flex; gap: 10px; justify-content: center; }
.modal-buttons button { padding: 10px 20px; width: 120px; font-weight: 600; border-radius: 6px; cursor: pointer; border: none; }
#modal-confirm { background-color: var(--secondary-color); color: var(--dark-color); } #modal-cancel { background-color: #ddd; }
.toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 3000; }
.toast {
padding: 15px 20px; margin-bottom: 10px; border-radius: 8px; color: white;
font-weight: 600; box-shadow: 0 4px 15px rgba(0,0,0,0.2);
opacity: 0; transform: translateX(100%); animation: slideIn 0.5s forwards;
}
.toast.success { background-color: var(--success-color); } .toast.error { background-color: var(--error-color); }
@keyframes slideIn { to { opacity: 1; transform: translateX(0); } }
@keyframes shake {
10%, 90% { transform: translateX(-2px); }
20%, 80% { transform: translateX(2px); }
30%, 50%, 70% { transform: translateX(-4px); }
40%, 60% { transform: translateX(4px); }
}
</style>
</head>
<body>
<div id="app" class="app-container">
<div class="controls">
<button id="toggle-help" title="Toon/verberg help"><i class="fa-solid fa-circle-info"></i></button>
<div class="control-group">
<button id="add-desk" title="Voeg leerlingtafel toe"><i class="fa-solid fa-user-graduate"></i></button>
<button id="add-teacher-desk" title="Voeg lerarenbureau toe"><i class="fa-solid fa-chalkboard-user"></i></button>
<button id="add-whiteboard" title="Voeg schoolbord toe"><i class="fa-solid fa-person-chalkboard"></i></button>
<button id="delete-mode-btn" title="Verwijder-modus (of selecteer & druk 'Del')"><i class="fa-solid fa-eraser"></i></button>
</div>
<div class="control-group">
<select id="layout-select">
<option value="">Kies opstelling (30 plekken)...</option>
<option value="bus">Busopstelling</option>
<option value="groups">Groepjes</option>
<option value="u-shape">U-vorm</option>
</select>
<button id="download-layout-btn" title="Download opstelling als bestand"><i class="fa-solid fa-download"></i> Download</button>
<button id="upload-layout-btn" title="Upload opstelling uit bestand"><i class="fa-solid fa-upload"></i> Upload</button>
<button id="clear-layout" title="Maak lokaal leeg"><i class="fa-solid fa-trash"></i></button>
</div>
<button id="game-button"><i class="fa-solid fa-play"></i> Start Spel</button>
<div class="score-display">Score: <span id="score">0</span></div>
</div>
<div class="main-content">
<div id="sidebar" class="sidebar">
<h3>Hoe het werkt</h3>
<ul>
<li><strong>Bouwen:</strong> Voeg elementen toe en sleep ze naar de juiste plek.</li>
<li><strong>Selecteren:</strong> Klik op een element om het te selecteren.</li>
<li><strong>Verwijderen:</strong> Selecteer een element en druk op 'Delete', of activeer de Verwijder-modus (<i class="fa-solid fa-eraser"></i>).</li>
<li><strong>Namen:</strong> Dubbelklik op een leerlingtafel om een naam in te voeren.</li>
<li><strong>Foto's:</strong> Beweeg over een leerlingtafel en klik op <i class="fa-solid fa-camera"></i>.</li>
<li><strong>Opslaan:</strong> Klik op 'Download' om je opstelling als een bestand op te slaan.</li>
<li><strong>Laden:</strong> Klik op 'Upload' om een eerder opgeslagen bestand te laden.</li>
<li><strong>Spelen:</strong> Klik op 'Start Spel' om te oefenen!</li>
</ul>
</div>
<div class="classroom-container">
<div id="classroom" class="classroom-area"></div>
</div>
</div>
</div>
<div id="guess-modal" class="modal-overlay">
<div class="modal-content">
<h3>Wie zit hier?</h3>
<input type="text" id="guess-input" placeholder="Naam...">
<div class="modal-buttons">
<button id="modal-cancel">Annuleren</button>
<button id="modal-confirm">Raad!</button>
</div>
</div>
</div>
<div id="toast-container" class="toast-container"></div>
<input type="file" id="photo-uploader" accept="image/*" style="display: none;">
<input type="file" id="layout-uploader" accept=".json" style="display: none;">
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- SELECTORS & STATE ---
const app = document.getElementById('app');
const classroom = document.getElementById('classroom');
const sidebar = document.getElementById('sidebar');
const photoUploader = document.getElementById('photo-uploader');
const layoutUploader = document.getElementById('layout-uploader');
const scoreDisplay = document.getElementById('score');
const gameButton = document.getElementById('game-button');
const addButtons = document.querySelectorAll('#add-desk, #add-teacher-desk, #add-whiteboard');
const deleteModeBtn = document.getElementById('delete-mode-btn');
const guessModal = document.getElementById('guess-modal');
const guessInput = document.getElementById('guess-input');
const modalConfirm = document.getElementById('modal-confirm');
const modalCancel = document.getElementById('modal-cancel');
let score = 0, gameMode = false, deleteMode = false, elementIdCounter = 0;
let draggedElement = null, selectedElement = null, activeDeskForPhoto = null, activeDeskForGuess = null;
let offsetX, offsetY;
// --- PRESET LAYOUTS ---
const PRESET_LAYOUTS = {
bus: [
{ type: 'whiteboard', top: '2%', left: 'calc(50% - 150px)' },
{ type: 'teacher-desk', top: '12%', left: '5%' },
...Array.from({ length: 30 }, (_, i) => {
const row = Math.floor(i / 6); const col_pair_index = Math.floor((i % 6) / 2);
const is_left_in_pair = (i % 2) === 0; const col_base_x = [15, 45, 75][col_pair_index];
const left = col_base_x + (is_left_in_pair ? 0 : 12); const top = 30 + row * 13;
return { type: 'desk', top: `${top}%`, left: `${left}%` };
})
],
groups: [
{ type: 'whiteboard', top: '1%', left: 'calc(50% - 150px)' },
...Array.from({ length: 6 }, (_, i) => {
const groupX = (i % 2) * 50 + 12; const groupY = Math.floor(i / 2) * 30 + 15;
return [
{ type: 'desk', top: `${groupY}%`, left: `${groupX}%` }, { type: 'desk', top: `${groupY}%`, left: `${groupX + 15}%` },
{ type: 'desk', top: `${groupY + 10}%`, left: `${groupX}%` }, { type: 'desk', top: `${groupY + 10}%`, left: `${groupX + 15}%` },
{ type: 'desk', top: `${groupY + 5}%`, left: `${groupX + 7.5}%` },
];
}).flat()
],
'u-shape': [
{ type: 'whiteboard', top: '1%', left: 'calc(50% - 150px)' }, { type: 'teacher-desk', top: '15%', left: 'calc(50% - 75px)' },
...Array.from({ length: 30 }, (_, i) => {
let top, left;
if (i < 11) { top = `${10 + i * 8}%`; left = '5%'; }
else if (i < 19) { top = `90%`; left = `${15 + (i - 11) * 10}%`; }
else { top = `${10 + (29 - i) * 8}%`; left = '85%'; }
return { type: 'desk', top, left };
})
]
};
// --- LAYOUT & FILE FUNCTIONS ---
function downloadLayout() {
const layoutData = Array.from(classroom.children).map(el => ({
type: el.dataset.type, left: el.style.left, top: el.style.top,
name: el.dataset.name || '', photo: el.dataset.photo || ''
}));
const jsonString = JSON.stringify(layoutData, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'klaslokaal-opstelling.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Opstelling gedownload!', 'success');
}
layoutUploader.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const layoutData = JSON.parse(event.target.result);
if (!Array.isArray(layoutData)) { throw new Error('Invalid format'); }
renderLayout(layoutData);
showToast('Opstelling succesvol geladen!', 'success');
} catch (error) {
showToast('Fout bij laden. Ongeldig bestand.', 'error');
}
};
reader.readAsText(file);
e.target.value = ''; // Reset for re-uploading same file
});
function renderLayout(layoutData) {
classroom.innerHTML = ''; elementIdCounter = 0;
if (layoutData) {
layoutData.forEach(item => createElement(item || {}));
}
}
// --- ELEMENT CREATION ---
function createElement({type, left = '50px', top = '50px', name = '', photo = ''}) {
const element = document.createElement('div');
element.id = `el-${elementIdCounter++}`;
element.className = `class-element ${type}`;
element.dataset.type = type;
element.style.left = left;
element.style.top = top;
if (type === 'desk') {
element.innerHTML = `
<div class="table-top">
<div class="desk-inner">
<div class="desk-front">
<i class="fa-solid fa-pencil edit-icon" title="Dubbelklik om naam toe te voegen"></i>
<i class="fa-solid fa-camera photo-upload-icon" title="Foto uploaden"></i>
<span class="desk-name"></span>
</div>
<div class="desk-back">?</div>
</div>
</div>`;
updateDeskAppearance(element, name, photo);
element.querySelector('.photo-upload-icon').addEventListener('click', (e) => { e.stopPropagation(); activeDeskForPhoto = element; photoUploader.click(); });
}
element.addEventListener('dblclick', handleDoubleClick);
classroom.appendChild(element);
return element;
}
function updateDeskAppearance(desk, name, photo) {
desk.dataset.name = name || '';
desk.dataset.photo = photo || '';
const nameSpan = desk.querySelector('.desk-name');
const front = desk.querySelector('.desk-front');
const back = desk.querySelector('.desk-back');
nameSpan.textContent = name;
front.classList.toggle('has-name', !!name);
if (photo) {
front.style.backgroundImage = `url(${photo})`; front.classList.add('has-photo');
back.style.backgroundImage = `url(${photo})`; back.textContent = '';
} else {
front.style.backgroundImage = 'none'; front.classList.remove('has-photo');
back.style.backgroundImage = 'none'; back.textContent = '?';
}
}
// --- PHOTO UPLOAD LOGIC ---
photoUploader.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file || !activeDeskForPhoto) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const MAX_WIDTH = 200;
const scaleSize = MAX_WIDTH / img.width;
canvas.width = MAX_WIDTH; canvas.height = img.height * scaleSize;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
updateDeskAppearance(activeDeskForPhoto, activeDeskForPhoto.dataset.name, dataUrl);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
// --- EVENT HANDLERS (Select, Delete, Drag, DoubleClick) ---
function selectElement(element) {
if (selectedElement) selectedElement.classList.remove('selected');
if (element) element.classList.add('selected');
selectedElement = element;
}
function deleteSelectedElement() {
if (selectedElement) {
selectedElement.remove();
showToast('Element verwijderd.', 'success');
selectedElement = null;
}
}
deleteModeBtn.addEventListener('click', () => {
deleteMode = !deleteMode;
app.classList.toggle('delete-mode', deleteMode);
deleteModeBtn.classList.toggle('active', deleteMode);
if (deleteMode) selectElement(null);
});
document.addEventListener('keydown', (e) => {
if ((e.key === 'Delete' || e.key === 'Backspace') && selectedElement && !gameMode) {
e.preventDefault();
deleteSelectedElement();
}
});
classroom.addEventListener('mousedown', (e) => {
if (gameMode) return;
const targetElement = e.target.closest('.class-element');
if (deleteMode) {
if (targetElement) { targetElement.remove(); showToast('Element verwijderd.', 'success'); }
return;
}
selectElement(targetElement);
if (!targetElement) return;
draggedElement = targetElement;
draggedElement.classList.add('dragging');
const rect = draggedElement.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
});
document.addEventListener('mousemove', (e) => {
if (!draggedElement) return;
e.preventDefault();
const classroomRect = classroom.getBoundingClientRect();
let x = e.clientX - classroomRect.left - offsetX;
let y = e.clientY - classroomRect.top - offsetY;
draggedElement.style.left = `${Math.max(0, Math.min(x, classroomRect.width - draggedElement.offsetWidth))}px`;
draggedElement.style.top = `${Math.max(0, Math.min(y, classroomRect.height - draggedElement.offsetHeight))}px`;
});
document.addEventListener('mouseup', () => {
if (draggedElement) { draggedElement.classList.remove('dragging'); draggedElement = null; }
});
function handleDoubleClick(e) {
if (gameMode) return;
const element = e.currentTarget;
if (element.dataset.type === 'desk') {
handleNameEdit(element);
}
}
function handleNameEdit(desk) {
const nameSpan = desk.querySelector('.desk-name');
const currentName = desk.dataset.name || '';
nameSpan.innerHTML = `<input type="text" value="${currentName}" />`;
const input = nameSpan.querySelector('input');
input.focus(); input.select();
const saveName = () => {
const newName = input.value.trim();
updateDeskAppearance(desk, newName, desk.dataset.photo);
};
input.addEventListener('blur', saveName);
input.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') input.blur(); });
}
// --- GAME LOGIC ---
gameButton.addEventListener('click', () => {
gameMode = !gameMode;
app.classList.toggle('game-mode', gameMode);
if (gameMode) {
gameButton.innerHTML = '<i class="fa-solid fa-stop"></i> Reset Spel';
document.querySelectorAll('.desk').forEach(desk => { if (desk.dataset.name) desk.classList.add('flipped'); });
[...addButtons, deleteModeBtn, document.getElementById('download-layout-btn'), document.getElementById('upload-layout-btn'), document.getElementById('clear-layout'), document.getElementById('layout-select')].forEach(btn => btn.disabled = true);
} else {
gameButton.innerHTML = '<i class="fa-solid fa-play"></i> Start Spel';
score = 0; updateScore();
document.querySelectorAll('.desk').forEach(desk => desk.classList.remove('flipped'));
[...addButtons, deleteModeBtn, document.getElementById('download-layout-btn'), document.getElementById('upload-layout-btn'), document.getElementById('clear-layout'), document.getElementById('layout-select')].forEach(btn => btn.disabled = false);
}
});
function handleGuessClick(e) {
const desk = e.currentTarget;
if (!gameMode || !desk.classList.contains('flipped') || !desk.dataset.name) return;
activeDeskForGuess = desk; showModal();
}
classroom.addEventListener('click', (e) => {
const desk = e.target.closest('.desk');
if (desk) handleGuessClick({currentTarget: desk});
});
function processGuess() {
if (!activeDeskForGuess) return;
const guess = guessInput.value.trim();
const correctName = activeDeskForGuess.dataset.name;
if (guess.toLowerCase() === correctName.toLowerCase()) {
showToast(`Correct! Dat is ${correctName}.`, 'success');
score++; updateScore();
activeDeskForGuess.classList.remove('flipped');
} else {
showToast(`Helaas, het juiste antwoord was: ${correctName}`, 'error');
activeDeskForGuess.classList.add('shaking');
setTimeout(() => { if(activeDeskForGuess) activeDeskForGuess.classList.remove('shaking'); }, 500);
}
hideModal();
}
function showModal() { guessModal.classList.add('visible'); guessInput.value = ''; setTimeout(() => guessInput.focus(), 100); }
function hideModal() { guessModal.classList.remove('visible'); activeDeskForGuess = null; }
function updateScore() { scoreDisplay.textContent = score; }
// --- INIT & MISC LISTENERS ---
document.getElementById('toggle-help').addEventListener('click', () => sidebar.classList.toggle('visible'));
document.getElementById('download-layout-btn').addEventListener('click', downloadLayout);
document.getElementById('upload-layout-btn').addEventListener('click', () => layoutUploader.click());
document.getElementById('clear-layout').addEventListener('click', () => { if (confirm('Weet je zeker dat je het hele lokaal wilt leegmaken?')) renderLayout([]); });
document.getElementById('layout-select').addEventListener('change', (e) => {
const selected = e.target.value;
if (selected && PRESET_LAYOUTS[selected]) {
if (classroom.children.length > 0 && !confirm('Huidige opstelling wordt overschreven. Doorgaan?')) { e.target.value = ""; return; }
renderLayout(PRESET_LAYOUTS[selected]);
}
});
document.getElementById('add-desk').addEventListener('click', () => selectElement(createElement({type: 'desk'})));
document.getElementById('add-teacher-desk').addEventListener('click', () => selectElement(createElement({type: 'teacher-desk'})));
document.getElementById('add-whiteboard').addEventListener('click', () => selectElement(createElement({type: 'whiteboard'})));
modalConfirm.addEventListener('click', processGuess);
modalCancel.addEventListener('click', hideModal);
guessInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') processGuess(); });
guessModal.addEventListener('click', (e) => { if (e.target === guessModal) hideModal(); });
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
document.getElementById('toast-container').appendChild(toast);
setTimeout(() => { toast.remove(); }, 3000);
}
renderLayout(PRESET_LAYOUTS.bus);
});
</script>
</body>
</html>