Interactive ESL Game for Teaching Past Time Expressions (Free HTML Classroom Activity)


Download this .json file [load this file for quiz] in the settinghttps://drive.google.com/file/d/1Hud8GdxGBbTiKV57DolGqW5I77exsMhs/view?usp=sharing

Part 1: How to Play

🎮 Step-by-Step Instructions

  1. Start the Game
    Click the START GAME button to begin. Background music will automatically play (you can turn it off anytime).

  2. Generate a Time Expression ⏱️
    Click the ⏱️ Randomizer button → Click PICK EXPRESSION.
    A time expression will appear (e.g., yesterday, last night).

  3. Select a Date 📅
    Click a date on the calendar. Selected dates will be highlighted.
    Click OK to continue.

  4. Complete the Activity 🧩
    Drag and drop words into the blanks to complete sentences:
    • Where were you yesterday?
    • I was at the park.
    Then click SUBMIT.

  5. Check Answers ✅❌
    A correct or incorrect animation will appear.
    If correct, click RETURN TO CALENDAR.

🧰 How to Use the Buttons

  • 🎵 Music Controls: Turn music ON/OFF and adjust volume
  • ⏱️ Randomizer: Generate time expressions (no repeats until all are used)
  • ✏️ Pen Tool: Draw on the calendar to highlight or explain
  • ⚙️ Settings: Add expressions, places, and images
  • 📆 Calendar Controls: Change month and year

💡 Teaching Tips

  • Use this as a warm-up activity
  • Let students take turns interacting
  • Encourage full sentence speaking
  • Use the pen tool to guide attention

💻 Part 2: Complete Prompts + Full HTML Code

🧠 Game Creation Prompts

Prompt 1: Game Concept
Create an interactive ESL classroom game using HTML, CSS, and JavaScript focused on past time expressions such as "yesterday," "last week," and "two hours ago." Include a calendar interface.

Prompt 2: Core Features
Include a start screen, background music, calendar navigation, selectable dates, and a randomizer that avoids repetition.

Prompt 3: Gameplay
Add drag-and-drop sentence construction, WH-questions, Yes/No questions, and instant feedback.

Prompt 4: Customization
Allow teachers to edit expressions, add 15 places, paste images, and save/load using JSON.

Prompt 5: Teacher Tools
Include a drawing tool, draggable widgets, and sound effects.

Prompt 6: User Experience
Design should be colorful, engaging, responsive, and touch-friendly.

Prompt 7: Technical Requirements
Use pure HTML, CSS, and JavaScript. Must work offline and be full-screen responsive.


📄 Full HTML Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Let's Travel Back in Time</title>
    <style>
        :root {
            --primary: #4a90e2;
            --success: #2ecc71;
            --error: #e74c3c;
            --gold: #f1c40f;
            --dark: #2c3e50;
        }
        body, html {
            margin: 0; padding: 0; height: 100%; width: 100%;
            overflow: hidden; font-family: 'Arial Black', sans-serif;
            background: url('background.gif') no-repeat center center fixed;
            background-size: cover;
        }
        /* --- Full Screen GIF Introduction --- */
        #start-screen {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('Travel Back in Time.gif') no-repeat center center;
            background-size: cover;
            z-index: 9000;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            text-align: center; padding: 20px;
            transition: opacity 0.5s ease;
        }
        #start-screen h1 { 
            color: white; 
            font-size: 4.5rem; 
            margin: 0; 
            text-shadow: 4px 4px 10px rgba(0,0,0,0.8); 
        }
        #start-screen p { 
            font-size: 1.5rem; 
            color: #f0f0f0; 
            max-width: 700px; 
            margin: 25px 0; 
            line-height: 1.4;
            text-shadow: 2px 2px 5px rgba(0,0,0,0.9);
        }   
        .footer-link { margin-top: 50px; font-size: 1.3rem; }
        .footer-link a { 
            color: var(--gold); 
            text-decoration: none; 
            font-weight: bold;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
            transition: 0.3s;
        }
        .footer-link a:hover { color: white; text-decoration: underline; }

        #app-overlay {
            width: 100%; height: 100%;
            background: rgba(255, 255, 255, 0.65);
            display: flex; flex-direction: column;
        }
        .header {
            display: flex; justify-content: center; align-items: center; gap: 15px; 
            padding: 10px; background: rgba(255,255,255,0.9);
            box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 10;
        }
        .nav-btn {
            background: var(--primary); color: white; border: none;
            padding: 10px 20px; border-radius: 8px; cursor: pointer;
            font-size: 0.9rem; font-weight: bold; transition: 0.3s;
        }
        #calendar-container {
            flex-grow: 1; display: flex; flex-direction: column;
            padding: 10px; transition: filter 0.4s ease;
            position: relative;
        }      
        #drawing-canvas {
            position: absolute; top: 0; left: 0;
            width: 100%; height: 100%;
            z-index: 5; pointer-events: none;
        }
        #drawing-canvas.active { pointer-events: auto; cursor: crosshair; }
        .calendar-header {
            display: flex; justify-content: space-between; align-items: center;
            background: white; padding: 5px 30px; border-radius: 15px; margin-bottom: 5px;
            border: 2px solid var(--dark); position: relative; z-index: 15;
        }
        .grid {
            flex-grow: 1; display: grid; grid-template-columns: repeat(7, 1fr);
            grid-template-rows: auto repeat(6, 1fr); gap: 5px;
            position: relative; z-index: 2;
        }
        .weekday { background: var(--dark); color: white; text-align: center; padding: 10px; border-radius: 5px; font-size: 0.85rem; border: 1px solid black; }
        .day { background: white; border: 2px solid black; border-radius: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 2rem; position: relative; font-weight: bold; }
        .day.selected { background: var(--gold); box-shadow: 0 0 15px var(--gold); transform: scale(0.95); }
        #ok-btn { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 15px 80px; font-size: 2rem; background: var(--success); display: none; z-index: 50; border: 4px solid white; color: white; border-radius: 50px; cursor: pointer; }
        /* Draggable Widget Styling */
        .draggable-widget {
            position: absolute; background: white; padding: 15px; border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.4); z-index: 60;
            border: 3px solid var(--dark); cursor: grab; touch-action: none;
            display: none; /* Initially hidden, toggled by widget buttons */
        }
        .draggable-widget:active { cursor: grabbing; }
        .widget-handle { background:#333; color:white; font-size:0.7rem; padding:3px; text-align:center; margin-bottom:10px; border-radius: 5px; pointer-events: none;}

        #pen-widget { top: 150px; right: 20px; width: 120px; }
        #draggable-randomizer { top: 150px; left: 20px; width: 280px; }

        .color-dot { width: 30px; height: 30px; border-radius: 50%; cursor: pointer; border: 2px solid #ddd; display: inline-block; }
        .color-dot.active { border-color: black; transform: scale(1.1); }
        #settings-overlay {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: white; z-index: 2000; overflow-y: auto; padding: 20px;
        }
        .setting-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
        .setting-item { border: 1px solid #ccc; padding: 10px; border-radius: 10px; background: #f9f9f9; position: relative; }
        .paste-box { width: 100%; height: 100px; border: 3px dashed var(--primary); display: flex; align-items: center; justify-content: center; font-size: 0.8rem; background: #fff; cursor: crosshair; }
        .paste-box img { max-height: 100%; pointer-events: none; }

        #focused-overlay {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.85); z-index: 100; justify-content: center; align-items: center;
        }
        .focused-card { background: white; width: 95%; height: 95%; border-radius: 30px; border: 10px solid var(--gold); display: flex; flex-direction: column; align-items: center; padding: 15px; position: relative; }
        
        .drop-box { 
            display: inline-block; min-width: 140px; border-bottom: 4px solid var(--dark); 
            margin: 0 5px; height: 45px; vertical-align: bottom; text-align: center; 
            background: transparent; transition: background 0.3s; pointer-events: auto;
        }
        .word-tile { background: var(--primary); color: white; padding: 8px 15px; border-radius: 8px; cursor: grab; font-size: 1.1rem; user-select: none; touch-action: none; }
        .drag-zone { display: flex; flex-wrap: wrap; gap: 8px; margin: 15px; padding: 15px; border: 2px solid #ddd; border-radius: 10px; width: 90%; background: #f0f0f0; min-height: 60px; }

        #expression-popup { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--primary); color: white; padding: 40px; border-radius: 30px; font-size: 4rem; text-align: center; z-index: 3000; border: 8px solid white; }
        .focus-active #calendar-container, .focus-active .header, .focus-active #draggable-randomizer, .focus-active #pen-widget { filter: blur(15px); pointer-events: none; } 
        #exit-game-btn { position: absolute; top: 20px; right: 20px; background: var(--error); padding: 10px 20px; }
    </style>
</head>
<body>
<div id="start-screen">
    <h1>Let's Travel Back in Time</h1>
    <p>Master past time expressions through fun vocabulary and grammar challenges!</p>
    <button class="nav-btn" style="padding: 25px 90px; font-size: 2rem; background: var(--success); border-radius: 60px; box-shadow: 0 5px 20px rgba(0,0,0,0.3);" onclick="startGame()">START GAME</button>
    <div class="footer-link">
        <a href="https://teacherkhenlearninghub.blogspot.com/" target="_blank">Teacher Khen Learning Hub</a>
    </div>
</div>

<audio id="bgMusic" loop><source src="background music.mp3" type="audio/mpeg"></audio>
<audio id="sfxClick"><source src="click.mp3" type="audio/mpeg"></audio>
<audio id="sfxRandomizer"><source src="randomizer sound effect.mp3" type="audio/mpeg"></audio>
<audio id="sfxCorrect"><source src="correct.mp3" type="audio/mpeg"></audio>

<div id="app-overlay">
    <div class="header">
        <div class="music-ctrl" style="display:flex; align-items:center; gap:10px; background:#eee; padding:5px 15px; border-radius:20px;">
            <button class="nav-btn" onclick="toggleMusic()">MUSIC: OFF</button>
            <input type="range" min="0" max="1" step="0.1" value="0.5" onchange="setVolume(this.value)">
        </div>
        <button class="nav-btn" style="background:#8e44ad" onclick="toggleWidget('draggable-randomizer')">⏱️</button>
        <button class="nav-btn" style="background:#f39c12" onclick="toggleWidget('pen-widget')">✏️</button>
        
        <button class="nav-btn" style="background:var(--dark)" onclick="openSettings()">⚙️ SETTINGS</button>
    </div>

    <div id="calendar-container">
        <canvas id="drawing-canvas"></canvas>
        <div class="calendar-header">
            <button class="nav-btn" onclick="changeMonth(-1)">❮ MONTH</button>
            <span id="month-display" style="font-size:1.5rem">Month</span>
            <button class="nav-btn" onclick="changeMonth(1)">MONTH ❯</button>
            <button class="nav-btn" onclick="changeYear(-1)">❮ YEAR</button>
            <span id="year-display" style="font-size:1.5rem">2026</span>
            <button class="nav-btn" onclick="changeYear(1)">YEAR ❯</button>
        </div>
        <div class="grid" id="calendar-grid"></div>
    </div>
</div>

<button id="ok-btn" onclick="openBigScreen()">OK</button>

<div id="pen-widget" class="draggable-widget">
    <div class="widget-handle">MOVE PEN</div>
    <button id="pen-toggle" class="nav-btn" style="background:#555; width:100%; margin-bottom:10px;" onclick="toggleDrawingMode()">✏️ OFF</button>
    <div style="display:grid; grid-template-columns: 1fr 1fr; gap:5px; margin-bottom:10px;">
        <div class="color-dot active" style="background:red" onclick="setPenColor('red', this)"></div>
        <div class="color-dot" style="background:blue" onclick="setPenColor('blue', this)"></div>
        <div class="color-dot" style="background:yellow" onclick="setPenColor('rgba(255,255,0,0.5)', this)"></div>
        <div class="color-dot" style="background:black" onclick="setPenColor('black', this)"></div>
    </div>
    <button class="nav-btn" style="background:var(--error); font-size:0.7rem; width:100%;" onclick="clearCanvas()">ERASE ALL</button>
</div>

<div id="draggable-randomizer" class="draggable-widget">
    <div class="widget-handle">MOVE RANDOMIZER</div>
    <textarea id="exp-input-box" style="width:100%; height:60px;">two days ago, yesterday, last night, two hours ago, an hour ago, thirty minutes ago, last week, last month, last year, this morning, last Monday</textarea>
    <button class="nav-btn" style="width:100%; margin-top:5px;" onclick="pickExpression()">PICK EXPRESSION</button>
</div>

<div id="settings-overlay">
    <div style="display:flex; justify-content: space-between; align-items:center; padding:15px; background:#f4f4f4; border-radius:15px;">
        <h2>Game Setup</h2>
        <div style="display:flex; gap:10px;">
            <button class="nav-btn" style="background:var(--error)" onclick="clearAllData()">CLEAR ALL DATA</button>
            <button class="nav-btn" style="background:var(--dark)" onclick="saveToFile()">SAVE FILE</button>
            <button class="nav-btn" style="background:var(--dark)" onclick="document.getElementById('fileIn').click()">LOAD FILE</button>
            <button class="nav-btn" style="background:var(--success)" onclick="closeSettings()">CLOSE & PLAY</button>
        </div>
    </div>
    <input type="file" id="fileIn" style="display:none" onchange="loadFromFile(event)">
    <div style="padding:20px;">
        <h3>Part 1: Time Expressions</h3>
        <textarea id="setting-exps" style="width:100%; height:60px; font-size:1rem;"></textarea>
        <h3>Part 2: 15 Place Items</h3>
        <div class="setting-grid" id="places-grid"></div>
    </div>
</div>

<div id="focused-overlay">
    <div class="focused-card">
        <button id="exit-game-btn" class="nav-btn" onclick="closeBigScreen()">GO BACK TO CALENDAR</button>
        <div id="full-date-display" style="font-size:1.8rem; color:var(--dark); margin-bottom:5px;"></div>
        <div style="display:flex; gap:15px; margin-bottom:10px;">
            <img src="clock.gif" style="width:50px">
            <select id="h-sel" class="time-select" style="font-size:1.5rem"></select> : 
            <select id="m-sel" class="time-select" style="font-size:1.5rem"></select>
            <select id="ampm-sel" class="time-select" style="font-size:1.5rem"><option>AM</option><option>PM</option></select>
        </div>
        <img id="game-image" style="height:180px; border:5px solid var(--primary); border-radius:15px;">
        <h2 id="game-place-name" style="color:var(--primary); margin:5px; font-weight: normal;"></h2>
        <div id="sentence-area" style="font-size:1.3rem; width:95%; background:#f9f9f9; padding:15px; border-radius:15px; line-height:2.5;">
            <div id="q1-line"></div>
            <hr>
            <div id="q2-line"></div>
        </div>
        <div class="drag-zone" id="word-bank"></div>
        <div id="game-controls">
            <button id="submit-btn" class="nav-btn" style="background:var(--success); padding:15px 60px; font-size:1.5rem;" onclick="checkAnswers()">SUBMIT</button>
            <button id="back-btn" class="nav-btn" style="background:var(--dark); display:none;" onclick="closeBigScreen()">RETURN TO CALENDAR</button>
        </div>
    </div>
</div>

<div id="expression-popup" onclick="this.style.display='none'"></div>
<div id="result-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; z-index:5000; background:rgba(0,0,0,0.8); justify-content:center; align-items:center;"><img id="result-gif" style="width:400px;"></div>

<script>
    let currentViewDate = new Date(2026, 3, 19);
    let selectedDates = [];
    let currentExp = "";
    let gameData = Array(15).fill().map(() => ({ word: "", img: "" }));
    let correctAnswers = {};
    let unusedExps = [];

    // Drawing State
    let isDrawingEnabled = false, isDrawing = false, penColor = 'red';
    const canvas = document.getElementById('drawing-canvas');
    const ctx = canvas.getContext('2d');

    function startGame() {
        const screen = document.getElementById('start-screen');
        screen.style.opacity = '0';
        setTimeout(() => screen.style.display = 'none', 500);
        document.getElementById('bgMusic').play().catch(() => {});
        document.querySelector('.music-ctrl .nav-btn').innerText = "MUSIC: ON";
        resizeCanvas();
    }

    function toggleWidget(id) {
        const el = document.getElementById(id);
        el.style.display = (el.style.display === 'block') ? 'none' : 'block';
        playSfx('sfxClick');
    }

    function resizeCanvas() {
        const container = document.getElementById('calendar-container');
        canvas.width = container.offsetWidth; canvas.height = container.offsetHeight;
    }
    window.addEventListener('resize', resizeCanvas);

    function toggleDrawingMode() {
        isDrawingEnabled = !isDrawingEnabled;
        canvas.classList.toggle('active', isDrawingEnabled);
        document.getElementById('pen-toggle').innerText = isDrawingEnabled ? "✏️ ON" : "✏️ OFF";
        document.getElementById('pen-toggle').style.background = isDrawingEnabled ? "var(--success)" : "#555";
    }

    function setPenColor(c, el) {
        penColor = c;
        document.querySelectorAll('.color-dot').forEach(d => d.classList.remove('active'));
        el.classList.add('active');
        if(!isDrawingEnabled) toggleDrawingMode();
    }

    function clearCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); }

    function getPos(e) {
        const rect = canvas.getBoundingClientRect();
        const clientX = e.touches ? e.touches[0].clientX : e.clientX;
        const clientY = e.touches ? e.touches[0].clientY : e.clientY;
        return { x: clientX - rect.left, y: clientY - rect.top };
    }

    function startDraw(e) { if(!isDrawingEnabled) return; isDrawing = true; const pos = getPos(e); ctx.beginPath(); ctx.moveTo(pos.x, pos.y); }
    function doDraw(e) {
        if(!isDrawing || !isDrawingEnabled) return;
        e.preventDefault(); const pos = getPos(e);
        ctx.lineTo(pos.x, pos.y); ctx.strokeStyle = penColor;
        ctx.lineWidth = penColor.includes('rgba') ? 20 : 4; ctx.lineCap = 'round'; ctx.stroke();
    }
    canvas.addEventListener('mousedown', startDraw); canvas.addEventListener('mousemove', doDraw);
    window.addEventListener('mouseup', () => isDrawing = false);
    canvas.addEventListener('touchstart', startDraw, {passive:false}); canvas.addEventListener('touchmove', doDraw, {passive:false});
    canvas.addEventListener('touchend', () => isDrawing = false);

    // Dynamic UI Init
    for(let i=1; i<=12; i++) document.getElementById('h-sel').add(new Option(i, i));
    for(let i=0; i<60; i++) document.getElementById('m-sel').add(new Option(i.toString().padStart(2,'0'), i));
    
    // Initial Load
    if(localStorage.getItem('calGamePlaces')) gameData = JSON.parse(localStorage.getItem('calGamePlaces'));
    if(localStorage.getItem('calGameExps')) {
        document.getElementById('exp-input-box').value = localStorage.getItem('calGameExps');
        document.getElementById('setting-exps').value = localStorage.getItem('calGameExps');
    }

    function playSfx(id) { const s = document.getElementById(id); if(s) { s.currentTime = 0; s.play(); } }

    function openSettings() {
        document.getElementById('setting-exps').value = document.getElementById('exp-input-box').value;
        const grid = document.getElementById('places-grid'); grid.innerHTML = '';
        gameData.forEach((item, i) => {
            const div = document.createElement('div'); div.className = 'setting-item';
            div.innerHTML = `<button style="position:absolute; top:2px; right:2px; background:red; color:white; border:none; border-radius:50%; width:20px; cursor:pointer;" onclick="gameData[${i}]={word:'',img:''}; openSettings();">X</button>
                <input type="text" placeholder="name ${i+1}" value="${item.word}" style="width:80%;" onchange="gameData[${i}].word=this.value; saveLocal();">
                <div class="paste-box" onpaste="handlePaste(event, ${i})">${item.img ? `<img src="${item.img}">` : 'Paste Photo'}</div>`;
            grid.appendChild(div);
        });
        document.getElementById('settings-overlay').style.display = 'block';
    }

    function handlePaste(e, i) {
        e.preventDefault(); const item = Array.from(e.clipboardData.items).find(x => x.type.indexOf("image") !== -1);
        if (item) {
            const reader = new FileReader();
            reader.onload = (event) => { 
                const img = new Image(); img.onload = function() {
                    const canvasProc = document.createElement('canvas'); const max_w = 400; const scale = max_w / img.width;
                    canvasProc.width = max_w; canvasProc.height = img.height * scale;
                    const ctxProc = canvasProc.getContext('2d'); ctxProc.drawImage(img, 0, 0, canvasProc.width, canvasProc.height);
                    gameData[i].img = canvasProc.toDataURL('image/jpeg', 0.7); openSettings(); saveLocal();
                }; img.src = event.target.result;
            }; reader.readAsDataURL(item.getAsFile());
        }
    }

    function saveLocal() { 
        try { 
            localStorage.setItem('calGamePlaces', JSON.stringify(gameData)); 
            localStorage.setItem('calGameExps', document.getElementById('exp-input-box').value); 
        } catch(e) { console.error("Storage Error", e); } 
        unusedExps = []; 
    }

    function saveToFile() { 
        const blob = new Blob([JSON.stringify({
            places: gameData, 
            exps: document.getElementById('setting-exps').value || document.getElementById('exp-input-box').value
        })], {type:"application/json"}); 
        const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download="game_settings.json"; a.click(); 
    }

    function loadFromFile(e) { 
        const reader = new FileReader(); reader.onload=(ev)=>{ 
            try {
                const d=JSON.parse(ev.target.result); 
                gameData = d.places; 
                const exps = d.exps || "";
                document.getElementById('exp-input-box').value = exps;
                document.getElementById('setting-exps').value = exps;
                saveLocal(); 
                openSettings(); 
            } catch(err) { alert("Error loading file."); }
        }; reader.readAsText(e.target.files[0]); 
    }

    function clearAllData() {
        if(confirm("Are you sure you want to delete everything?")) {
            localStorage.clear();
            gameData = Array(15).fill().map(() => ({ word: "", img: "" }));
            const defaultExps = "two days ago, yesterday, last night, two hours ago, an hour ago, thirty minutes ago, last week, last month, last year, this morning, last Monday";
            document.getElementById('exp-input-box').value = defaultExps;
            document.getElementById('setting-exps').value = defaultExps;
            openSettings();
        }
    }

    function closeSettings() { 
        const newExps = document.getElementById('setting-exps').value;
        document.getElementById('exp-input-box').value = newExps; 
        saveLocal(); 
        document.getElementById('settings-overlay').style.display = 'none'; 
    }

    function pickExpression() {
        if (unusedExps.length === 0) unusedExps = document.getElementById('exp-input-box').value.split(',').map(s => s.trim()).filter(s => s !== "");
        if(unusedExps.length === 0) return alert("Add expressions!");
        const idx = Math.floor(Math.random() * unusedExps.length); currentExp = unusedExps.splice(idx, 1)[0];
        const p = document.getElementById('expression-popup'); p.innerText = currentExp.toUpperCase(); p.style.display = 'block'; playSfx('sfxRandomizer');
    }

    function renderCalendar() {
        const grid = document.getElementById('calendar-grid'); grid.innerHTML = '';
        const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
        document.getElementById('month-display').innerText = months[currentViewDate.getMonth()];
        document.getElementById('year-display').innerText = currentViewDate.getFullYear();
        
        const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
        daysOfWeek.forEach(d => {
            const div = document.createElement('div'); div.className = 'weekday'; div.innerText = d; grid.appendChild(div);
        });

        const first = new Date(currentViewDate.getFullYear(), currentViewDate.getMonth(), 1).getDay();
        const daysInMonth = new Date(currentViewDate.getFullYear(), currentViewDate.getMonth() + 1, 0).getDate();
        for(let i=0; i<first; i++) grid.appendChild(document.createElement('div'));
        for(let d=1; d<=daysInMonth; d++) {
            const dayDiv = document.createElement('div'); dayDiv.className = 'day';
            const dateID = `${currentViewDate.getFullYear()}-${currentViewDate.getMonth()}-${d}`;
            if(selectedDates.includes(dateID)) dayDiv.classList.add('selected');
            dayDiv.innerHTML = `<span>${d}</span>`;
            dayDiv.onclick = () => {
                if(isDrawingEnabled) return; playSfx('sfxClick');
                if(selectedDates.includes(dateID)) selectedDates = selectedDates.filter(x => x !== dateID);
                else selectedDates.push(dateID);
                renderCalendar(); document.getElementById('ok-btn').style.display = selectedDates.length > 0 ? 'block' : 'none';
            }; grid.appendChild(dayDiv);
        }
    }

    function openBigScreen() {
        const valid = gameData.filter(p => p.word !== ""); if(valid.length === 0) return alert("Add places!");
        const place = valid[Math.floor(Math.random()*valid.length)];
        document.body.classList.add('focus-active'); document.getElementById('focused-overlay').style.display = 'flex';
        document.getElementById('submit-btn').style.display = 'block'; document.getElementById('back-btn').style.display = 'none';
        const dParts = selectedDates[0].split('-');
        const dObj = new Date(dParts[0], dParts[1], dParts[2]);
        document.getElementById('full-date-display').innerText = dObj.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
        setupGame(place);
    }
    function setupGame(item) {
        document.getElementById('game-image').src = item.img || "";
        document.getElementById('game-place-name').innerText = item.word;
        const isNow = currentExp.toLowerCase() === "now";
        const isSpecific = item.word.charAt(0) === item.word.charAt(0).toUpperCase() && isNaN(item.word.charAt(0));
        const article = isSpecific ? "" : "the ";
        document.getElementById('q1-line').innerHTML = `Where <div class="drop-box" id="drop-v1" ondrop="drop(event)" ondragover="allowDrop(event)"></div> you <div class="drop-box" id="drop-exp1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>? <br> I <div class="drop-box" id="drop-v2" ondrop="drop(event)" ondragover="allowDrop(event)"></div> at ${article}<div class="drop-box" id="drop-place1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>.`;
        const isCorrect = Math.random() > 0.5; const otherPlaces = gameData.filter(p => p.word !== "" && p.word !== item.word);
        const decoyPlace = otherPlaces.length > 0 ? otherPlaces[Math.floor(Math.random()*otherPlaces.length)].word : "bakery";
        const askWhere = isCorrect ? item.word : decoyPlace;
        const decoySpecific = askWhere.charAt(0) === askWhere.charAt(0).toUpperCase() && isNaN(askWhere.charAt(0));
        const decoyArticle = decoySpecific ? "" : "the ";
        let optionYes, optionNo, yesNoAns;
        if(isNow) {
            optionYes = "Yes, I am."; optionNo = `No, I'm not. I'm at ${article}${item.word}.`; yesNoAns = isCorrect ? optionYes : optionNo;
            document.getElementById('q2-line').innerHTML = `Are you at ${decoyArticle}${askWhere} ${currentExp}? <div class="drop-box" id="drop-yn" style="width:450px" ondrop="drop(event)" ondragover="allowDrop(event)"></div>`;
            correctAnswers = { v1: "are", exp1: currentExp, v2: "am", place1: item.word, yn: yesNoAns };
        } else {
            optionYes = "Yes, I was."; optionNo = isCorrect ? `No, I wasn't. I was at ${decoyArticle}${decoyPlace}.` : `No, I wasn't. I was at ${article}${item.word}.`; yesNoAns = isCorrect ? optionYes : optionNo;
            document.getElementById('q2-line').innerHTML = `Were you at ${decoyArticle}${askWhere} ${currentExp}? <div class="drop-box" id="drop-yn" style="width:450px" ondrop="drop(event)" ondragover="allowDrop(event)"></div>`;
            correctAnswers = { v1: "were", exp1: currentExp, v2: "was", place1: item.word, yn: yesNoAns };
        }
        const words = isNow ? ["are", "am", "I'm not", "I'm"] : ["were", "was", "wasn't"];
        words.push(currentExp, item.word, optionYes, optionNo, "at"); if(!isSpecific) words.push("the");
        const bank = document.getElementById('word-bank'); bank.innerHTML = '';
        [...new Set(words)].sort(() => Math.random() - 0.5).forEach((w, i) => {
            const span = document.createElement('span'); span.className = 'word-tile'; span.innerText = w; span.draggable = true; span.id = "w-" + i;
            span.ondragstart = (e) => e.dataTransfer.setData("text", e.target.id);
            span.addEventListener('touchstart', handleTouchStart, {passive: false});
            span.addEventListener('touchmove', handleTouchMove, {passive: false});
            span.addEventListener('touchend', handleTouchEnd, {passive: false});
            bank.appendChild(span);
        });
    }

    let touchDraggingElement = null, touchClone = null;
    function handleTouchStart(e) { touchDraggingElement = e.target; touchClone = touchDraggingElement.cloneNode(true); touchClone.style.position = "fixed"; touchClone.style.zIndex = "1000"; touchClone.style.opacity = "0.8"; touchClone.style.pointerEvents = "none"; document.body.appendChild(touchClone); moveAt(e.touches[0].pageX, e.touches[0].pageY); }
    function handleTouchMove(e) { if (!touchClone) return; e.preventDefault(); moveAt(e.touches[0].pageX, e.touches[0].pageY); }
    function handleTouchEnd(e) { if (!touchClone) return; const touch = e.changedTouches[0]; document.body.removeChild(touchClone); const dropTarget = document.elementFromPoint(touch.clientX, touch.clientY); const realTarget = dropTarget ? dropTarget.closest('.drop-box') : null;
        if (realTarget) { realTarget.innerHTML = ""; const finalClone = touchDraggingElement.cloneNode(true); finalClone.style.cursor = "default"; finalClone.draggable = false; realTarget.appendChild(finalClone); realTarget.style.background = "rgba(74, 144, 226, 0.1)"; }
        touchClone = null; touchDraggingElement = null;
    }
    function moveAt(pageX, pageY) { if (touchClone) { touchClone.style.left = pageX - touchClone.offsetWidth / 2 + 'px'; touchClone.style.top = pageY - touchClone.offsetHeight / 2 + 'px'; } }

    function checkAnswers() {
        const check = (id, key) => document.getElementById(id).innerText.trim() === correctAnswers[key];
        const isOk = check('drop-v1', 'v1') && check('drop-exp1', 'exp1') && check('drop-v2', 'v2') && check('drop-place1', 'place1') && check('drop-yn', 'yn');
        const overlay = document.getElementById('result-overlay'); document.getElementById('result-gif').src = isOk ? 'correct.gif' : 'wrong.gif';
        if(isOk) playSfx('sfxCorrect'); overlay.style.display = 'flex';
        setTimeout(() => { overlay.style.display = 'none'; if(isOk) { document.getElementById('submit-btn').style.display = 'none'; document.getElementById('back-btn').style.display = 'block'; } }, 2000);
    }
    function allowDrop(ev) { ev.preventDefault(); }
    function drop(ev) { ev.preventDefault(); const data = ev.dataTransfer.getData("text"); const draggedElement = document.getElementById(data); let target = ev.target; if (target.classList.contains('word-tile')) target = target.parentElement; 
        if (target.classList.contains('drop-box')) { target.innerHTML = ""; const clone = draggedElement.cloneNode(true); clone.id = "clone-" + Math.random(); clone.style.cursor = "default"; clone.draggable = false; target.appendChild(clone); target.style.background = "rgba(74, 144, 226, 0.1)"; }
    }
    function closeBigScreen() { document.body.classList.remove('focus-active'); document.getElementById('focused-overlay').style.display = 'none'; selectedDates = []; document.getElementById('ok-btn').style.display = 'none'; document.querySelectorAll('.drop-box').forEach(b => { b.innerHTML = ""; b.style.background = "transparent"; }); renderCalendar(); }

    function makeWidgetDraggable(el) {
        let active = false, currentX = 0, currentY = 0, initialX, initialY, xOffset = 0, yOffset = 0;
        function dragStart(e) {
            if (e.target.tagName === "TEXTAREA" || e.target.tagName === "BUTTON" || e.target.classList.contains('color-dot')) return;
            initialX = (e.type === "touchstart" ? e.touches[0].clientX : e.clientX) - xOffset;
            initialY = (e.type === "touchstart" ? e.touches[0].clientY : e.clientY) - yOffset;
            active = true;
        }
        function dragging(e) {
            if (active) {
                e.preventDefault();
                currentX = (e.type === "touchmove" ? e.touches[0].clientX : e.clientX) - initialX;
                currentY = (e.type === "touchmove" ? e.touches[0].clientY : e.clientY) - initialY;
                xOffset = currentX; yOffset = currentY;
                el.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
            }
        }
        el.addEventListener("mousedown", dragStart); el.addEventListener("touchstart", dragStart, {passive: false});
        document.addEventListener("mousemove", dragging); document.addEventListener("touchmove", dragging, {passive: false});
        document.addEventListener("mouseup", () => active = false); document.addEventListener("touchend", () => active = false);
    }
    function toggleMusic() { const m = document.getElementById('bgMusic'); const btn = document.querySelector('.music-ctrl .nav-btn'); if(m.paused) { m.play(); btn.innerText = "MUSIC: ON"; } else { m.pause(); btn.innerText = "MUSIC: OFF"; } }
    function setVolume(v) { document.getElementById('bgMusic').volume = v; }
    function changeMonth(d) { currentViewDate.setMonth(currentViewDate.getMonth() + d); renderCalendar(); }
    function changeYear(d) { currentViewDate.setFullYear(currentViewDate.getFullYear() + d); renderCalendar(); }
    window.onload = () => { 
        renderCalendar(); resizeCanvas();
        makeWidgetDraggable(document.getElementById("draggable-randomizer"));
        makeWidgetDraggable(document.getElementById("pen-widget"));
    };
</script>
</body>
</html>

Created by Teacher Khen Learning Hub

Post a Comment

0 Comments