Free Jungle Connect-the-Dots HTML Game for Reading, Spelling, and Sentence Building

 


Play the GAME here > 

Teaching reading, spelling, and sentence formation becomes more exciting through interactive learning games. This Jungle Connect-the-Dots Educational Game is designed for teachers, parents, and ESL/EFL learners who want a fun classroom activity for reading and language practice.

✨ Game Features

  • ✅ Single-player mode
  • ✅ Team competition mode
  • ✅ Sentence formation activities
  • ✅ Word spelling practice
  • ✅ Audio effects and animations
  • ✅ Teacher settings panel
  • ✅ Import and export question database

PART 1 — How to Play the Game

🌟 Objective of the Game

Players connect the correct letters or words in order to form a complete word or sentence.

🕹️ Step-by-Step Guide

Step 1 — Open the Game

Open the HTML file in your browser and click:

🎮 Play Game

Step 2 — Read the Challenge

Students will see letters or words inside circles scattered around the screen.

Step 3 — Connect the Dots

Drag the mouse or finger from one circle to another in the correct order.

Examples:

  • T → I → G → E → R
  • She → is → happy.

Step 4 — Check the Answer

Click:

✅ Check Answer

The system checks the answer and gives feedback instantly.

🏆 Scoring System

✅ Correct Answer

  • +1 point
  • Celebration animation
  • Victory sound effect

❌ Wrong Answer

  • -1 point
  • Students can try again

👥 Team Mode

Click:

👥 Switch Team Mode

This creates Team A and Team B for classroom competitions and collaborative learning activities.


⚙️ How to Use the Buttons

Button Purpose
🎵 Music ON/OFF Turns the background music on or off.
🔊 Volume Slider Adjusts the game audio volume.
👥 Switch Team Mode Changes the game into team competition mode.
⚙️ Settings Panel Opens the teacher control panel.
✅ Check Answer Checks the player's answer.
🔄 Clear Paths Removes all current connections.

How Teachers Can Add Questions

  1. Open the ⚙️ Settings Panel
  2. Select:
    • Sentence Formation
    • Spelling Mode
  3. Type the content.
  4. Optional: Paste a clue image using Ctrl + V
  5. Click ➕ Append to Pool

Example Questions:

  • She is happy.
  • Tiger
  • The monkey climbs trees.

Export and Import Features

Teachers can save and reuse classroom activities.

  • 📤 Export Pool — Downloads question data as a JSON file.
  • 📥 Import Pool — Loads saved activities from another device.

Educational Benefits

This game supports:

  • Phonics learning
  • Vocabulary recognition
  • Sentence construction
  • Reading fluency
  • Critical thinking
  • Collaborative learning

It is highly suitable for:

  • Elementary students
  • ESL learners
  • EFL classrooms
  • Interactive English learning activities

PART 2 — Prompts and Exact Codes

✨ Suggested AI Prompt

Create an HTML educational game with a jungle theme connect-the-dots activity.

The game should:
- allow sentence formation and spelling activities,
- include draggable circles with letters or words,
- have single-player and team modes,
- include score tracking,
- support background music and sound effects,
- include a settings panel for teachers,
- allow importing and exporting JSON question data,
- support image clues,
- use responsive design for desktop and mobile,
- and have engaging animations for students.

The design should be colorful, kid-friendly, and suitable for ESL/EFL learners.

📌 Paste Your Exact Game Code Below

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Jungle Connect-the-Dots Educational Game</title>
    <style>
        /* Import an EFL-friendly, highly legible comic font for young learners */
        @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Comic+Neue:wght@700&display=swap');

        :root {
            --jungle-green: #2d6a4f;
            --light-green: #52b788;
            --wood-brown: #8c6239;
            --light-wood: #b38659;
            --cream: #f4f1de;
            --gold: #ffb703;
        }

        * {
            box-sizing: border-box;
            user-select: none;
            -webkit-user-select: none;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Comic Neue', 'Fredoka One', cursive, sans-serif;
            background-color: #1a3a2a;
            color: var(--cream);
            overflow: hidden;
            width: 100vw;
            height: 100vh;
        }

        /* App Wrapper */
        #app-container {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            background-image: url('background.gif');
            background-size: cover;
            background-position: center;
        }

        /* Top Bar Interface */
        header {
            background: rgba(45, 106, 79, 0.9);
            border-bottom: 5px solid var(--wood-brown);
            padding: 12px 24px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            z-index: 10;
        }

        .title-area h1 {
            font-size: 28px;
            color: var(--gold);
            text-shadow: 2px 2px #000;
            font-family: 'Fredoka One', cursive, sans-serif;
        }

        .global-controls {
            display: flex;
            gap: 12px;
            align-items: center;
        }

        /* Buttons Style */
        .jungle-btn {
            font-family: 'Comic Neue', sans-serif;
            font-weight: 700;
            background: linear-gradient(to bottom, var(--wood-brown), #5c3a21);
            color: var(--cream);
            border: 3px solid #3d2514;
            border-radius: 8px;
            padding: 10px 20px;
            cursor: pointer;
            font-size: 16px;
            text-shadow: 1px 1px #000;
            box-shadow: 0 5px 0px #3d2514;
            transition: all 0.1s ease;
        }
        .jungle-btn:active {
            transform: translateY(3px);
            box-shadow: 0 2px 0px #3d2514;
        }
        .jungle-btn.green-btn {
            background: linear-gradient(to bottom, var(--light-green), var(--jungle-green));
            border-color: #1b4332;
            box-shadow: 0 5px 0px #1b4332;
        }

        /* Audio Control Sub-panel */
        .audio-popover {
            background: rgba(0,0,0,0.8);
            padding: 10px 14px;
            border-radius: 10px;
            display: flex;
            align-items: center;
            gap: 12px;
            border: 2px solid var(--light-green);
        }

        /* Game Modes Containers */
        #game-stage {
            flex: 1;
            display: flex;
            position: relative;
            width: 100%;
            height: calc(100% - 78px);
        }

        .game-zone {
            flex: 1;
            position: relative;
            height: 100%;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .game-zone:nth-child(2) {
            border-left: 5px dashed var(--wood-brown);
            background: rgba(0, 0, 0, 0.15);
        }

        /* Shared Wooden Floating Signboards */
        .wooden-sign {
            background: linear-gradient(135deg, var(--light-wood), var(--wood-brown));
            border: 5px solid #4a3119;
            border-radius: 12px;
            padding: 14px 20px;
            box-shadow: 0 8px 16px rgba(0,0,0,0.6), inset 0 0 12px rgba(0,0,0,0.3);
            text-align: center;
            margin: 12px auto;
            width: 90%;
            max-width: 90%;
            z-index: 5;
            position: relative;
        }

        .hud-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            width: 92%;
            margin: 8px auto 0 auto;
            z-index: 5;
        }

        .answer-box {
            font-size: 32px; 
            font-weight: bold;
            color: #fff;
            min-height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            letter-spacing: 1.5px;
            text-shadow: 2px 2px 3px #000;
        }

        .score-badge {
            font-size: 18px; 
            background: rgba(0,0,0,0.7);
            padding: 6px 14px;
            border-radius: 20px;
            border: 2px solid var(--gold);
            color: var(--gold);
            font-weight: bold;
        }

        /* Picture Clue Box Style */
        .clue-pic-holder {
            width: 110px;
            height: 110px;
            margin: 5px auto 0 auto;
            border: 3px solid var(--gold);
            border-radius: 8px;
            background: rgba(0,0,0,0.4);
            display: none;
            overflow: hidden;
            justify-content: center;
            align-items: center;
            box-shadow: 0 4px 8px rgba(0,0,0,0.5);
        }
        .clue-pic-holder img {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
        }

        /* Canvas Overlay Area */
        .canvas-container {
            flex: 1;
            position: relative;
            width: 100%;
        }

        .dot-canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 2;
        }

        /* Celebration Confetti Foreground Canvas Layer */
        .confetti-canvas {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none;
            z-index: 4;
        }

        /* Enriched Circle Node Elements */
        .dot-node {
            position: absolute;
            width: 110px; 
            height: 110px; 
            border-radius: 50%;
            background: radial-gradient(circle, #ffeaa7, #fdcb6e);
            border: 5px solid var(--wood-brown);
            box-shadow: 0 6px 12px rgba(0,0,0,0.4), inset 0 0 10px rgba(255,255,255,0.7);
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: 700;
            font-size: 24px; 
            color: #2d3436;
            cursor: pointer;
            z-index: 3;
            transform: translate(-50%, -50%);
            transition: transform 0.2s, background 0.3s;
            text-align: center;
            word-break: break-word;
            padding: 8px;
            line-height: 1.1;
        }

        .dot-node:hover {
            transform: translate(-50%, -50%) scale(1.08);
        }

        .dot-node.selected {
            background: radial-gradient(circle, #55efc4, #00b894);
            border-color: #006266;
            color: #fff;
            box-shadow: 0 0 20px #55efc4;
        }

        /* Generic Overlay Layout style used by Welcome message and Feedback boards */
        .feedback-overlay {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.75);
            z-index: 90;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s ease;
        }

        .feedback-overlay.active {
            opacity: 1;
            pointer-events: auto;
        }

        .feedback-card {
            transform: scale(0.7);
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            max-width: 550px !important;
            width: 90%;
        }
        .feedback-overlay.active .feedback-card {
            transform: scale(1);
        }

        .feedback-text {
            font-size: 42px;
            font-weight: bold;
            margin-bottom: 15px;
            text-shadow: 2px 2px #000;
            font-family: 'Fredoka One', cursive, sans-serif;
        }
        .feedback-success { color: #2ecc71; }
        .feedback-fail { color: #e74c3c; }

        /* Settings System Panel Overlay */
        #settings-panel {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(26, 58, 42, 0.98);
            z-index: 100;
            display: none;
            padding: 24px;
            overflow-y: auto;
        }

        .settings-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 24px;
            max-width: 1200px;
            margin: 0 auto;
        }

        @media (max-width: 768px) {
            .settings-grid { grid-template-columns: 1fr; }
            #game-stage { flex-direction: column; }
            .game-zone:nth-child(2) { border-left: none; border-top: 5px dashed var(--wood-brown); }
            .dot-node { width: 90px; height: 90px; font-size: 18px; }
            .answer-box { font-size: 24px; }
        }

        .settings-block {
            background: rgba(255,255,255,0.05);
            border: 2px solid var(--wood-brown);
            border-radius: 8px;
            padding: 15px;
        }

        .settings-block h3 {
            color: var(--gold);
            margin-bottom: 12px;
            border-bottom: 1px solid var(--wood-brown);
            padding-bottom: 4px;
            font-family: 'Fredoka One', sans-serif;
        }

        .form-group {
            margin-bottom: 12px;
        }

        label {
            display: block;
            margin-bottom: 4px;
            font-size: 15px;
            color: var(--light-green);
        }

        input[type="text"], select, textarea {
            font-family: inherit;
            width: 100%;
            background: #11261c;
            border: 2px solid var(--wood-brown);
            border-radius: 6px;
            padding: 10px;
            color: #fff;
            font-size: 15px;
        }

        .paste-box-preview {
            width: 100%;
            height: 90px;
            border: 2px dashed var(--light-green);
            background: rgba(0,0,0,0.2);
            border-radius: 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #aaa;
            cursor: pointer;
            font-size: 14px;
            margin-top: 5px;
            overflow: hidden;
        }
        .paste-box-preview img {
            max-height: 100%;
            object-fit: contain;
        }

        .question-list-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: rgba(0,0,0,0.3);
            padding: 8px 12px;
            margin-bottom: 8px;
            border-radius: 6px;
            font-size: 14px;
        }

        .btn-sm {
            padding: 6px 12px;
            font-size: 14px;
        }

        .toggle-switch {
            display: flex;
            align-items: center;
            gap: 10px;
            cursor: pointer;
        }

        .toggle-switch input {
            width: 40px;
            height: 20px;
        }
    </style>
</head>
<body>

    <div id="app-container">
        
        <header>
            <div class="title-area">
                <h1 id="game-title-text">Jungle Connect Game</h1>
            </div>
            <div class="global-controls">
                <div class="audio-popover">
                    <button class="jungle-btn btn-sm" id="btn-toggle-audio">🎵 Music: OFF</button>
                    <input type="range" id="vol-slider" min="0" max="1" step="0.1" value="0.4" style="width:70px;">
                </div>
                <button class="jungle-btn" id="btn-toggle-mode">👥 Switch Team Mode</button>
                <button class="jungle-btn green-btn" id="btn-open-settings">⚙️ Settings Panel</button>
            </div>
        </header>

        <main id="game-stage">
            <div class="game-zone" id="zone-left">
                <div class="hud-row">
                    <div class="score-badge" id="score-left">Score: 0 / 0</div>
                    <div class="score-badge" id="mode-badge-left">Single Player Mode</div>
                </div>
                
                <div class="wooden-sign">
                    <div class="answer-box" id="ans-box-left">Connect dots to answer!</div>
                    <div class="clue-pic-holder" id="clue-holder-left"></div>
                </div>

                <div class="canvas-container" id="canvas-container-left">
                    <canvas class="dot-canvas" id="canvas-left"></canvas>
                    <canvas class="confetti-canvas" id="confetti-left"></canvas>
                </div>

                <div style="padding:15px; text-align:center; z-index:5;">
                    <button class="jungle-btn green-btn" id="btn-check-left" style="width:45%; max-width:220px; font-size:18px;">Check Answer</button>
                    <button class="jungle-btn" id="btn-reset-left" style="width:45%; max-width:220px; margin-left:10px; font-size:18px;">Clear Paths</button>
                </div>

                <div class="feedback-overlay" id="feedback-left">
                    <div class="wooden-sign feedback-card">
                        <div class="feedback-text" id="feedback-text-left">Well Done!</div>
                        <p id="feedback-sub-left" style="margin-bottom:20px; color:#fff; font-size: 20px;"></p>
                        <div style="display:flex; justify-content:center; gap:10px;">
                            <button class="jungle-btn green-btn" id="btn-feedback-next-left" style="font-size: 18px;">Next Challenge</button>
                            <button class="jungle-btn" id="btn-replay-left" style="font-size: 18px; display:none; background: linear-gradient(to bottom, #ffb703, #fb8500); border-color:#d4a373;">🔄 Replay Game</button>
                        </div>
                    </div>
                </div>
            </div>

            <div class="game-zone" id="zone-right" style="display: none;">
                <div class="hud-row">
                    <div class="score-badge" id="score-right">Score: 0 / 0</div>
                    <div class="score-badge" style="color:#55efc4; border-color:#55efc4;">Team B</div>
                </div>
                
                <div class="wooden-sign">
                    <div class="answer-box" id="ans-box-right">Connect dots to answer!</div>
                    <div class="clue-pic-holder" id="clue-holder-right"></div>
                </div>

                <div class="canvas-container" id="canvas-container-right">
                    <canvas class="dot-canvas" id="canvas-right"></canvas>
                    <canvas class="confetti-canvas" id="confetti-right"></canvas>
                </div>

                <div style="padding:15px; text-align:center; z-index:5;">
                    <button class="jungle-btn green-btn" id="btn-check-right" style="width:45%; max-width:220px; font-size:18px;">Check Answer</button>
                    <button class="jungle-btn" id="btn-reset-right" style="width:45%; max-width:220px; margin-left:10px; font-size:18px;">Clear Paths</button>
                </div>

                <div class="feedback-overlay" id="feedback-right">
                    <div class="wooden-sign feedback-card">
                        <div class="feedback-text" id="feedback-text-right">Well Done!</div>
                        <p id="feedback-sub-right" style="margin-bottom:20px; color:#fff; font-size: 20px;"></p>
                        <div style="display:flex; justify-content:center; gap:10px;">
                            <button class="jungle-btn green-btn" id="btn-feedback-next-right" style="font-size: 18px;">Next Challenge</button>
                            <button class="jungle-btn" id="btn-replay-right" style="font-size: 18px; display:none; background: linear-gradient(to bottom, #ffb703, #fb8500); border-color:#d4a373;">🔄 Replay Game</button>
                        </div>
                    </div>
                </div>
            </div>
        </main>

        <div class="feedback-overlay active" id="welcome-screen" style="z-index: 200;">
            <div class="wooden-sign feedback-card" style="padding: 30px 20px;">
                <div class="feedback-text" style="color: var(--gold);">🌴 Welcome to Jungle Connect! 🌴</div>
                <div style="color: #fff; font-size: 20px; line-height: 1.6; margin-bottom: 25px; text-align: left; background: rgba(0,0,0,0.3); padding: 15px; border-radius: 8px;">
                    <p style="margin-bottom: 10px; font-weight: bold; text-align: center; color: #55efc4; font-size: 22px;">How to Play:</p>
                    <p>1. 👈 **Drag your finger or mouse** from dot to dot to connect letters or words in order.</p>
                    <p style="margin-top: 8px;">2. ✅ Click **"Check Answer"** to test your word sequence.</p>
                    <p style="margin-top: 8px; color: #ff7675;">⚠️ **Be careful!** If your answer is wrong, you will **lose 1 point** (-1)! You can try again until you solve it!</p>
                </div>
                <button class="jungle-btn green-btn" id="btn-close-welcome" style="font-size: 24px; padding: 12px 35px; font-family: 'Fredoka One', sans-serif;">🎮 Play Game</button>
            </div>
        </div>

        <section id="settings-panel">
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                <h2 style="color: var(--gold); font-family: 'Fredoka One', sans-serif;">⚙️ Teacher Administration Control Console</h2>
                <button class="jungle-btn green-btn" id="btn-close-settings">💾 Save and Play</button>
            </div>

            <div class="settings-grid">
                <div class="settings-block" style="grid-column: span 1;">
                    <h3>Add New Educational Challenge</h3>
                    <div class="form-group">
                        <label for="input-type">Challenge Type Mode</label>
                        <select id="input-type">
                            <option value="sentence">Sentence Formation (Split by spaces)</option>
                            <option value="spelling">Spelling Mode (Split by character tokens)</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="input-content">Content Data Text</label>
                        <input type="text" id="input-content" placeholder="e.g. She is happy. OR Tiger">
                        <small style="color:#aaa; font-size:12px;">Sentences split into words; spelling splits into individual letters.</small>
                    </div>
                    <div class="form-group">
                        <label>Optional Clue Picture (Click & press Ctrl+V to paste)</label>
                        <div class="paste-box-preview" id="image-paste-zone" tabIndex="0">
                            Click here & paste your photo (Ctrl+V)
                        </div>
                    </div>
                    <button class="jungle-btn green-btn btn-sm" id="btn-add-question" style="width: 100%;">➕ Append to Pool</button>
                </div>

                <div class="settings-block">
                    <h3>Game Rules & Mechanics Sync</h3>
                    <div class="form-group">
                        <div class="toggle-switch">
                            <input type="checkbox" id="sync-toggle" checked>
                            <label for="sync-toggle"><strong>Synchronize Target Pool Questions</strong> (Both teams get the same challenge order sequences)</label>
                        </div>
                    </div>
                    <hr style="border-color: var(--wood-brown); margin: 15px 0;">
                    <h3>Export and Import Data (.JSON Configuration)</h3>
                    <p style="font-size:12px; margin-bottom:10px; color:#bbb;">Save configuration profiles locally or backup database assets for separate devices.</p>
                    <div style="display:flex; gap:10px;">
                        <button class="jungle-btn btn-sm" id="btn-export-data">📤 Export Pool</button>
                        <button class="jungle-btn btn-sm" id="btn-import-trigger">📥 Load Pool File</button>
                        <input type="file" id="file-import-input" accept=".json" style="display: none;">
                    </div>
                    <button class="jungle-btn btn-sm" id="btn-clear-system" style="background:linear-gradient(to bottom, #d63031, #b2bec3); margin-top:15px;">⚠️ Hard Factory Reset Data</button>
                </div>

                <div class="settings-block" style="grid-column: span 2;">
                    <h3>Current Challenge Item Pool Database (<span id="pool-count-label">0</span> Questions loaded)</h3>
                    <div id="settings-questions-container" style="max-height: 250px; overflow-y: auto; margin-top:10px;">
                    </div>
                </div>
            </div>
        </section>

    </div>

    <audio id="bg-audio" loop>
        <source src="ES_Play Cabin - Andreas Ericson.mp3" type="audio/mpeg">
    </audio>

    <audio id="victory-audio">
        <source src="winner sound effect.mp3" type="audio/mpeg">
    </audio>

    <script>
        /************************************************************************
         * STATE INITIALIZATION & CONSTANTS
         ************************************************************************/
        const DEFAULT_QUESTIONS = [
            { id: 1, type: "sentence", tokens: ["She", "is", "happy."], answer: "She is happy.", image: "" },
            { id: 2, type: "spelling", tokens: ["T", "i", "g", "e", "r"], answer: "Tiger", image: "" },
            { id: 3, type: "sentence", tokens: ["The", "monkey", "climbs", "trees."], answer: "The monkey climbs trees.", image: "" },
            { id: 4, type: "spelling", tokens: ["J", "u", "n", "g", "l", "e"], answer: "Jungle", image: "" }
        ];

        let questionPool = JSON.parse(localStorage.getItem("jungle_game_pool")) || DEFAULT_QUESTIONS;
        let isTeamMode = false;
        let syncQuestions = true;
        let currentUploadedImageBase64 = ""; 

        let state = {
            left: { pool: [], index: 0, score: 0, currentChain: [], activeDrawing: false, lastMouseX: 0, lastMouseY: 0, confettiParticles: [], confettiActive: false },
            right: { pool: [], index: 0, score: 0, currentChain: [], activeDrawing: false, lastMouseX: 0, lastMouseY: 0, confettiParticles: [], confettiActive: false }
        };

        const audioEl = document.getElementById('bg-audio');
        const victoryAudioEl = document.getElementById('victory-audio');
        const audioBtn = document.getElementById('btn-toggle-audio');
        const volSlider = document.getElementById('vol-slider');

        const AudioFX = {
            ctx: null,
            init() { if(!this.ctx) this.ctx = new (window.AudioContext || window.webkitAudioContext)(); },
            playTone(freq, type, duration, startTimeOffset = 0, volumeMultiplier = 1.0) {
                try {
                    this.init();
                    let osc = this.ctx.createOscillator();
                    let gain = this.ctx.createGain();
                    osc.type = type;
                    osc.frequency.value = freq;
                    // Boosted base sound effects to be distinctly heard over background music tracks
                    gain.gain.setValueAtTime(0.5 * volumeMultiplier, this.ctx.currentTime + startTimeOffset);
                    gain.gain.exponentialRampToValueAtTime(0.00001, this.ctx.currentTime + startTimeOffset + duration);
                    osc.connect(gain);
                    gain.connect(this.ctx.destination);
                    osc.start(this.ctx.currentTime + startTimeOffset);
                    osc.stop(this.ctx.currentTime + startTimeOffset + duration);
                } catch(e) {}
            },
            success() { 
                this.playTone(523.25, 'sine', 0.18, 0, 1.2); 
                setTimeout(() => this.playTone(659.25, 'sine', 0.35, 0, 1.2), 100); 
            },
            fail() { 
                this.playTone(220, 'sawtooth', 0.45, 0, 1.3); 
            },
            victory() {
                try {
                    // Trigger custom winner sound effect file at 100% full master volume
                    victoryAudioEl.currentTime = 0;
                    victoryAudioEl.volume = 1.0;
                    victoryAudioEl.play();
                } catch(e) {
                    console.log("Audio playback error:", e);
                }
            }
        };

        /************************************************************************
         * DOM LIFECYCLE ENGINE INITIALIZER
         ************************************************************************/
        window.addEventListener('DOMContentLoaded', () => {
            initDOMEvents();
            loadRulesFromStorage();
            rebuildGameSession();
            renderSettingsUI();
            setupPasteFeature();
            
            window.addEventListener('resize', () => {
                rebuildGameSession();
            });
        });

        function loadRulesFromStorage() {
            const savedSync = localStorage.getItem("jungle_game_sync");
            if(savedSync !== null) {
                syncQuestions = savedSync === 'true';
                document.getElementById('sync-toggle').checked = syncQuestions;
            }
        }

        function savePoolToStorage() {
            try {
                localStorage.setItem("jungle_game_pool", JSON.stringify(questionPool));
                localStorage.setItem("jungle_game_sync", syncQuestions);
            } catch(e) {
                alert("Storage limit warning! Try using smaller/compressed pictures if updates fail to hold.");
            }
            renderSettingsUI();
        }

        function rebuildGameSession() {
            if(questionPool.length === 0) {
                questionPool = [...DEFAULT_QUESTIONS];
            }

            if (syncQuestions) {
                state.left.pool = JSON.parse(JSON.stringify(questionPool));
                state.right.pool = JSON.parse(JSON.stringify(questionPool));
            } else {
                state.left.pool = [...questionPool].sort(() => Math.random() - 0.5);
                state.right.pool = [...questionPool].sort(() => Math.random() - 0.5);
            }

            state.left.index = 0;
            state.left.score = 0;
            state.right.index = 0;
            state.right.score = 0;

            stopConfettiAnimation('left');
            stopConfettiAnimation('right');

            document.getElementById('feedback-left').classList.remove('active');
            document.getElementById('feedback-right').classList.remove('active');

            applyLayoutUIModes();
        }

        function resetSingleTeamSession(side) {
            const sideState = state[side];
            if (syncQuestions) {
                sideState.pool = JSON.parse(JSON.stringify(questionPool));
            } else {
                sideState.pool = [...questionPool].sort(() => Math.random() - 0.5);
            }
            sideState.index = 0;
            sideState.score = 0;

            stopConfettiAnimation(side);
            document.getElementById(`feedback-${side}`).classList.remove('active');
            setupChallengeRound(side);
        }

        function applyLayoutUIModes() {
            const zoneRight = document.getElementById('zone-right');
            const modeBadgeLeft = document.getElementById('mode-badge-left');
            const titleText = document.getElementById('game-title-text');

            if(isTeamMode) {
                zoneRight.style.display = 'flex';
                modeBadgeLeft.innerText = "Team A";
                modeBadgeLeft.style.borderColor = "#fdcb6e";
                modeBadgeLeft.style.color = "#fdcb6e";
                titleText.innerText = "Jungle Connect: Team Faceoff";
            } else {
                zoneRight.style.display = 'none';
                modeBadgeLeft.innerText = "Single Player Mode";
                modeBadgeLeft.style.borderColor = "var(--gold)";
                modeBadgeLeft.style.color = "var(--gold)";
                titleText.innerText = "Jungle Connect Game";
            }

            setupChallengeRound('left');
            if(isTeamMode) setupChallengeRound('right');
        }

        /************************************************************************
         * ANTI-OVERLAP FORCE DISTRIBUTION & DETERMINISTIC PIXEL BUFFER GRID
         ************************************************************************/
        function generateSpreadLayout(count, side) {
            const container = document.getElementById(`canvas-container-${side}`);
            const containerWidth = container.clientWidth || 600;
            const containerHeight = container.clientHeight || 400;

            const minDistancePixels = Math.max(140, containerWidth * 0.22); 

            let points = [];
            let attempts = 0;
            let success = false;

            while (attempts < 150 && !success) {
                points = [];
                success = true;

                for (let i = 0; i < count; i++) {
                    let p = {
                        x: 15 + Math.random() * 70, 
                        y: 20 + Math.random() * 60  
                    };

                    for (let j = 0; j < points.length; j++) {
                        let dx = (p.x - points[j].x) * (containerWidth / 100);
                        let dy = (p.y - points[j].y) * (containerHeight / 100);
                        let distance = Math.sqrt(dx * dx + dy * dy);

                        if (distance < minDistancePixels) {
                            success = false;
                            break;
                        }
                    }

                    if (!success) break;
                    points.push(p);
                }
                attempts++;
            }

            if (!success || points.length < count) {
                points = [];
                const cols = Math.ceil(Math.sqrt(count));
                const rows = Math.ceil(count / cols);
                let index = 0;

                for (let r = 0; r < rows; r++) {
                    for (let c = 0; c < cols; c++) {
                        if (index >= count) break;
                        let gridX = 20 + (c * (60 / Math.max(1, cols - 1)));
                        let gridY = 25 + (r * (50 / Math.max(1, rows - 1)));
                        
                        if (cols === 1) gridX = 50;
                        if (rows === 1) gridY = 50;

                        points.push({ x: gridX, y: gridY });
                        index++;
                    }
                }
            }

            return points;
        }

        function setupChallengeRound(side) {
            const sideState = state[side];
            sideState.currentChain = [];
            sideState.activeDrawing = false;

            const container = document.getElementById(`canvas-container-${side}`);
            container.querySelectorAll('.dot-node').forEach(n => n.remove());

            document.getElementById(`ans-box-${side}`).innerText = "Connect dots in order!";
            document.getElementById(`score-${side}`).innerText = `Score: ${sideState.score}`;

            document.getElementById(`btn-feedback-next-${side}`).style.display = "inline-block";
            document.getElementById(`btn-replay-${side}`).style.display = "none";

            const clueHolder = document.getElementById(`clue-holder-${side}`);
            clueHolder.innerHTML = "";
            clueHolder.style.display = "none";

            if(sideState.index >= sideState.pool.length) {
                const teamLabelName = isTeamMode ? (side === 'left' ? "Team A" : "Team B") : "Single Player";
                
                // Trigger Custom Victory MP3 Sound Effect + Confetti Celebration System
                AudioFX.victory();
                triggerConfettiExplosion(side);

                document.getElementById(`feedback-text-${side}`).innerText = "🏆 Pool Completed!";
                document.getElementById(`feedback-text-${side}`).className = "feedback-text feedback-success";
                document.getElementById(`feedback-sub-${side}`).innerHTML = `<strong style="color:var(--gold); font-size:26px;">${teamLabelName} Score Card</strong><br><br>Final Points Earned: ${sideState.score} points!`;
                
                document.getElementById(`btn-feedback-next-${side}`).style.display = "none";
                document.getElementById(`btn-replay-${side}`).style.display = "inline-block";
                
                document.getElementById(`feedback-${side}`).classList.add('active');
                return;
            }

            const currentQuestion = sideState.pool[sideState.index];
            
            if (currentQuestion.image) {
                const imgNode = document.createElement('img');
                imgNode.src = currentQuestion.image;
                clueHolder.appendChild(imgNode);
                clueHolder.style.display = "flex";
            }

            const rawTokens = currentQuestion.tokens;
            const spreadCoordinates = generateSpreadLayout(rawTokens.length, side);

            let tokenObjects = rawTokens.map((token, originalIndex) => {
                return {
                    token: token,
                    id: originalIndex,
                    x: spreadCoordinates[originalIndex].x,
                    y: spreadCoordinates[originalIndex].y
                };
            });

            let shuffledLayouts = [...tokenObjects].sort(() => Math.random() - 0.5);

            shuffledLayouts.forEach((item) => {
                const node = document.createElement('div');
                node.className = 'dot-node';
                node.id = `node-${side}-${item.id}`;
                node.innerText = item.token;
                node.style.left = `${item.x}%`;
                node.style.top = `${item.y}%`;

                node.dataset.itemId = item.id;
                node.dataset.token = item.token;

                node.addEventListener('mousedown', (e) => handleNodeSelectionStart(e, side, item, node));
                node.addEventListener('touchstart', (e) => {
                    handleNodeSelectionStart(e, side, item, node);
                }, {passive: true});

                container.appendChild(node);
            });

            setTimeout(() => {
                resizeCanvas(side);
                drawCanvasVisuals(side);
            }, 50);
        }

        /************************************************************************
         * CONFETTI CELEBRATION PARTICLE ENGINE
         ************************************************************************/
        function triggerConfettiExplosion(side) {
            const canvas = document.getElementById(`confetti-${side}`);
            const container = document.getElementById(`canvas-container-${side}`);
            if(!canvas) return;

            canvas.width = container.clientWidth;
            canvas.height = container.clientHeight;

            const sideState = state[side];
            sideState.confettiParticles = [];
            sideState.confettiActive = true;

            const colors = ['#ffb703', '#fb8500', '#52b788', '#2d6a4f', '#2ecc71', '#e74c3c', '#9b59b6', '#3498db'];

            // Generate particles bursting upward from bottom center
            for (let i = 0; i < 120; i++) {
                sideState.confettiParticles.push({
                    x: canvas.width / 2,
                    y: canvas.height - 20,
                    size: Math.random() * 8 + 6,
                    color: colors[Math.floor(Math.random() * colors.length)],
                    speedX: (Math.random() - 0.5) * 15,
                    speedY: -(Math.random() * 12 + 10),
                    gravity: 0.3,
                    rotation: Math.random() * 360,
                    rotationSpeed: (Math.random() - 0.5) * 10
                });
            }

            function updateConfettiFrame() {
                if (!sideState.confettiActive) return;
                const ctx = canvas.getContext('2d');
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                let activeParticles = false;

                sideState.confettiParticles.forEach(p => {
                    p.x += p.speedX;
                    p.y += p.speedY;
                    p.speedY += p.gravity;
                    p.rotation += p.rotationSpeed;

                    if (p.y < canvas.height && p.x > 0 && p.x < canvas.width) {
                        activeParticles = true;
                        ctx.save();
                        ctx.translate(p.x, p.y);
                        ctx.rotate(p.rotation * Math.PI / 180);
                        ctx.fillStyle = p.color;
                        ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
                        ctx.restore();
                    }
                });

                if (activeParticles) {
                    requestAnimationFrame(updateConfettiFrame);
                } else {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                }
            }

            requestAnimationFrame(updateConfettiFrame);
        }

        function stopConfettiAnimation(side) {
            state[side].confettiActive = false;
            const canvas = document.getElementById(`confetti-${side}`);
            if (canvas) {
                const ctx = canvas.getContext('2d');
                ctx.clearRect(0, 0, canvas.width, canvas.height);
            }
        }

        /************************************************************************
         * COPY-PASTE PICTURE OPTIMIZATION COMPRESSION PIPELINE
         ************************************************************************/
        function setupPasteFeature() {
            const pasteZone = document.getElementById('image-paste-zone');
            
            pasteZone.addEventListener('paste', (e) => {
                const clipboardItems = e.clipboardData.items;
                for (let i = 0; i < clipboardItems.length; i++) {
                    if (clipboardItems[i].type.indexOf('image') !== -1) {
                        const blob = clipboardItems[i].getAsFile();
                        const reader = new FileReader();
                        
                        reader.onload = function(event) {
                            compressImagePipeline(event.target.result, 200, (compressedBase64) => {
                                currentUploadedImageBase64 = compressedBase64;
                                pasteZone.innerHTML = `<img src="${compressedBase64}" alt="Clue thumbnail">`;
                                pasteZone.style.borderColor = "var(--gold)";
                            });
                        };
                        reader.readAsDataURL(blob);
                        e.preventDefault();
                        break;
                    }
                }
            });
        }

        function compressImagePipeline(base64Str, maxSize, callback) {
            const img = new Image();
            img.src = base64Str;
            img.onload = function() {
                const canvas = document.createElement('canvas');
                let width = img.width;
                let height = img.height;

                if (width > height) {
                    if (width > maxSize) {
                        height *= maxSize / width;
                        width = maxSize;
                    }
                } else {
                    if (height > maxSize) {
                        width *= maxSize / height;
                        height = maxSize;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0, width, height);
                callback(canvas.toDataURL('image/jpeg', 0.7)); 
            };
        }

        /************************************************************************
         * CANVAS DIMENSION & DRAWING OPERATIONS (VINE GENERATOR GRAPHICS)
         ************************************************************************/
        function resizeCanvas(side) {
            const canvas = document.getElementById(`canvas-${side}`);
            if(!canvas) return;
            const container = document.getElementById(`canvas-container-${side}`);
            canvas.width = container.clientWidth;
            canvas.height = container.clientHeight;

            const confCanvas = document.getElementById(`confetti-${side}`);
            if(confCanvas) {
                confCanvas.width = container.clientWidth;
                confCanvas.height = container.clientHeight;
            }
        }

        function drawCanvasVisuals(side) {
            const canvas = document.getElementById(`canvas-${side}`);
            if(!canvas) return;
            const ctx = canvas.getContext('2d');
            const container = document.getElementById(`canvas-container-${side}`);
            
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            const sideState = state[side];
            if(!sideState || sideState.currentChain.length === 0) return;

            ctx.lineWidth = 8; 
            ctx.strokeStyle = '#2d6a4f';
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';

            ctx.shadowColor = 'rgba(0,0,0,0.4)';
            ctx.shadowBlur = 4;
            ctx.shadowOffsetY = 3;

            ctx.beginPath();
            
            sideState.currentChain.forEach((nodeId, index) => {
                const element = document.getElementById(`node-${side}-${nodeId}`);
                if(element) {
                    const rect = element.getBoundingClientRect();
                    const containerRect = container.getBoundingClientRect();
                    
                    const x = (rect.left + rect.width / 2) - containerRect.left;
                    const y = (rect.top + rect.height / 2) - containerRect.top;

                    if(index === 0) {
                        ctx.moveTo(x, y);
                    } else {
                        ctx.lineTo(x, y);
                    }
                }
            });

            if(sideState.activeDrawing && sideState.currentChain.length > 0) {
                ctx.lineTo(sideState.lastMouseX, sideState.lastMouseY);
            }

            ctx.stroke();

            ctx.beginPath();
            ctx.lineWidth = 3;
            ctx.strokeStyle = '#52b788';
            ctx.shadowBlur = 0;
            ctx.shadowOffsetY = 0;

            sideState.currentChain.forEach((nodeId, index) => {
                const element = document.getElementById(`node-${side}-${nodeId}`);
                if(element) {
                    const rect = element.getBoundingClientRect();
                    const containerRect = container.getBoundingClientRect();
                    const x = (rect.left + rect.width / 2) - containerRect.left;
                    const y = (rect.top + rect.height / 2) - containerRect.top;

                    if(index === 0) {
                        ctx.moveTo(x, y);
                    } else {
                        ctx.lineTo(x, y);
                    }
                }
            });
            if(sideState.activeDrawing && sideState.currentChain.length > 0) {
                ctx.lineTo(sideState.lastMouseX, sideState.lastMouseY);
            }
            ctx.stroke();
        }

        /************************************************************************
         * INTERACTIVE CONNECTIONS INTERFACES CONTROL AND TOUCH EVENTS DRIVERS
         ************************************************************************/
        function handleNodeSelectionStart(e, side, item, node) {
            e.stopPropagation();
            const sideState = state[side];
            
            if(sideState.currentChain.includes(item.id)) return;

            sideState.currentChain.push(item.id);
            node.classList.add('selected');
            sideState.activeDrawing = true;

            updateTrackingCoordinates(e, side);
            updateAnswerPreviewDisplay(side);
            drawCanvasVisuals(side);
        }

        function updateTrackingCoordinates(e, side) {
            const container = document.getElementById(`canvas-container-${side}`);
            const rect = container.getBoundingClientRect();
            
            let clientX, clientY;
            if(e.touches && e.touches.length > 0) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }

            state[side].lastMouseX = clientX - rect.left;
            state[side].lastMouseY = clientY - rect.top;
        }

        function handlePointerTrackingMove(e, side) {
            const sideState = state[side];
            if(!sideState.activeDrawing) return;

            updateTrackingCoordinates(e, side);
            drawCanvasVisuals(side);

            let clientX, clientY;
            if(e.touches && e.touches.length > 0) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }

            const targetElement = document.elementFromPoint(clientX, clientY);
            if(targetElement && targetElement.classList.contains('dot-node')) {
                const targetSide = targetElement.id.split('-')[1];
                const targetId = parseInt(targetElement.dataset.itemId);

                if(targetSide === side && !sideState.currentChain.includes(targetId)) {
                    sideState.currentChain.push(targetId);
                    targetElement.classList.add('selected');
                    updateAnswerPreviewDisplay(side);
                    AudioFX.playTone(380, 'sine', 0.05, 0, 0.8);
                }
            }
        }

        function terminatePointerTracking(side) {
            const sideState = state[side];
            if(!sideState.activeDrawing) return;
            sideState.activeDrawing = false;
            drawCanvasVisuals(side);
        }

        function updateAnswerPreviewDisplay(side) {
            const sideState = state[side];
            const currentQuestion = sideState.pool[sideState.index];
            if(!currentQuestion) return;

            let outputTokens = sideState.currentChain.map(id => {
                return currentQuestion.tokens[id];
            });

            const separator = (currentQuestion.type === 'sentence') ? ' ' : '';
            const builtString = outputTokens.join(separator);
            
            document.getElementById(`ans-box-${side}`).innerText = builtString || "Connect dots to answer!";
        }

        /************************************************************************
         * GLOBAL INPUT EVENT PIPELINE ATTACHMENTS MANAGER
         ************************************************************************/
        function initDOMEvents() {
            document.getElementById('btn-close-welcome').addEventListener('click', () => {
                document.getElementById('welcome-screen').classList.remove('active');
                
                if(audioEl.paused) {
                    audioEl.volume = volSlider.value;
                    audioEl.play().then(() => {
                        audioBtn.innerText = "🎵 Music: ON";
                        audioBtn.style.background = "linear-gradient(to bottom, #2ecc71, #27ae60)";
                    }).catch(() => {});
                }
            });

            ['left', 'right'].forEach(side => {
                const container = document.getElementById(`canvas-container-${side}`);
                
                container.addEventListener('mousemove', (e) => handlePointerTrackingMove(e, side));
                container.addEventListener('touchmove', (e) => handlePointerTrackingMove(e, side), {passive: true});

                window.addEventListener('mouseup', () => terminatePointerTracking(side));
                window.addEventListener('touchend', () => terminatePointerTracking(side));

                document.getElementById(`btn-check-${side}`).addEventListener('click', () => evaluateCurrentSubmission(side));
                document.getElementById(`btn-reset-${side}`).addEventListener('click', () => clearCurrentPaths(side));
                
                document.getElementById(`btn-feedback-next-${side}`).addEventListener('click', () => {
                    document.getElementById(`feedback-${side}`).classList.remove('active');
                    setupChallengeRound(side);
                });

                document.getElementById(`btn-replay-${side}`).addEventListener('click', () => {
                    resetSingleTeamTeamSession(side);
                });
            });

            document.getElementById('btn-toggle-mode').addEventListener('click', () => {
                isTeamMode = !isTeamMode;
                rebuildGameSession();
            });

            document.getElementById('btn-open-settings').addEventListener('click', () => {
                document.getElementById('settings-panel').style.display = 'block';
            });

            document.getElementById('btn-close-settings').addEventListener('click', () => {
                syncQuestions = document.getElementById('sync-toggle').checked;
                savePoolToStorage();
                document.getElementById('settings-panel').style.display = 'none';
                rebuildGameSession();
            });

            document.getElementById('btn-add-question').addEventListener('click', executeAppendQuestionAction);
            document.getElementById('btn-clear-system').addEventListener('click', executeSystemResetAction);
            document.getElementById('btn-export-data').addEventListener('click', exportPoolDataJSON);
            
            const fileInput = document.getElementById('file-import-input');
            document.getElementById('btn-import-trigger').addEventListener('click', () => fileInput.click());
            fileInput.addEventListener('change', handleFileImportEvent);

            audioBtn.addEventListener('click', () => {
                if(audioEl.paused) {
                    audioEl.play().then(() => {
                        audioBtn.innerText = "🎵 Music: ON";
                        audioBtn.style.background = "linear-gradient(to bottom, #2ecc71, #27ae60)";
                    }).catch(() => {
                        alert("Play interaction blocked by browser security. Tap anywhere on the stage window first.");
                    });
                } else {
                    audioEl.pause();
                    audioBtn.innerText = "🎵 Music: OFF";
                    audioBtn.style.background = "linear-gradient(to bottom, var(--wood-brown), #5c3a21)";
                }
            });

            volSlider.addEventListener('input', (e) => {
                audioEl.volume = e.target.value;
            });
        }

        window.resetSingleTeamTeamSession = function(side) {
            resetSingleTeamSession(side);
        };

        /************************************************************************
         * EVALUATION ENGINE & INTERACTIVE ANSWER CHECKER MECHANICS LOGIC
         ************************************************************************/
        function clearCurrentPaths(side) {
            const sideState = state[side];
            sideState.currentChain = [];
            sideState.activeDrawing = false;
            
            const container = document.getElementById(`canvas-container-${side}`);
            container.querySelectorAll('.dot-node').forEach(node => node.classList.remove('selected'));
            
            updateAnswerPreviewDisplay(side);
            drawCanvasVisuals(side);
        }

        function evaluateCurrentSubmission(side) {
            const sideState = state[side];
            const currentQuestion = sideState.pool[sideState.index];
            if(!currentQuestion) return;

            const separator = (currentQuestion.type === 'sentence') ? ' ' : '';
            let userSubmission = sideState.currentChain.map(id => currentQuestion.tokens[id]).join(separator);

            const sanitizedUser = userSubmission.trim().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");
            const sanitizedTarget = currentQuestion.answer.trim().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");

            const isCorrect = (sanitizedUser === sanitizedTarget) && (sideState.currentChain.length === currentQuestion.tokens.length);

            const overlay = document.getElementById(`feedback-${side}`);
            const textEl = document.getElementById(`feedback-text-${side}`);
            const subEl = document.getElementById(`feedback-sub-${side}`);
            const nextBtn = document.getElementById(`btn-feedback-next-${side}`);

            if(isCorrect) {
                AudioFX.success();
                sideState.score++; 
                sideState.index++;
                
                textEl.innerText = "🎉 WELL DONE!";
                textEl.className = "feedback-text feedback-success";
                subEl.innerHTML = `"${currentQuestion.answer}" is completely correct!<br><span style="color:var(--gold); font-weight:bold;">+1 Point Earned!</span>`;
                nextBtn.innerText = "Continue Voyage";
            } else {
                AudioFX.fail();
                
                sideState.score--;
                if(sideState.score < 0) sideState.score = 0; 
                
                textEl.innerText = "❌ TRY AGAIN!";
                textEl.className = "feedback-text feedback-fail";
                subEl.innerHTML = `Expected Order: "${currentQuestion.answer}"<br><span style="color:#ff7675; font-weight:bold;">Incorrect submission check: -1 Point!</span>`;
                nextBtn.innerText = "Retry Challenge";
                
                clearCurrentPaths(side);
            }

            document.getElementById(`score-${side}`).innerText = `Score: ${sideState.score}`;
            overlay.classList.add('active');
        }

        /************************************************************************
         * TEACHER SETTINGS MODAL CONTROL SYSTEM OPERATIONS MANAGEMENT
         ************************************************************************/
        function renderSettingsUI() {
            const container = document.getElementById('settings-questions-container');
            container.innerHTML = '';
            
            document.getElementById('pool-count-label').innerText = questionPool.length;

            questionPool.forEach((q, idx) => {
                const row = document.createElement('div');
                row.className = 'question-list-item';
                
                let imgThumbHtml = q.image ? `<img src="${q.image}" style="max-height:35px; border-radius:4px; margin-right:8px; vertical-align:middle;">` : '';
                
                row.innerHTML = `
                    <div>
                        ${imgThumbHtml}
                        <strong>[${q.type.toUpperCase()}]</strong> ${q.answer}
                        <br><small style="color:#2ecc71;">Tokens: ${JSON.stringify(q.tokens)}</small>
                    </div>
                    <button class="jungle-btn btn-sm" style="background:#e74c3c; border-color:#c0392b; box-shadow:0 2px 0 #c0392b;" onclick="deleteQuestionItemByIndex(${idx})">Delete</button>
                `;
                container.appendChild(row);
            });
        }

        function executeAppendQuestionAction() {
            const type = document.getElementById('input-type').value;
            let rawContent = document.getElementById('input-content').value;

            rawContent = rawContent.replace(/\s+/g, ' ').trim();

            if(!rawContent) {
                alert("Please input context parameters to append!");
                return;
            }

            let tokens = [];
            let answer = rawContent;

            if(type === 'sentence') {
                tokens = rawContent.split(' ').filter(t => t.length > 0);
            } else {
                const cleanWord = rawContent.replace(/\s+/g, '');
                tokens = cleanWord.split('');
                answer = cleanWord;
            }

            if(tokens.length === 0) {
                alert("Failed parsing text content into single array processing components tokens!");
                return;
            }

            const newQuestion = {
                id: Date.now() + Math.random(),
                type: type,
                tokens: tokens,
                answer: answer,
                image: currentUploadedImageBase64 
            };

            questionPool.push(newQuestion);
            
            document.getElementById('input-content').value = '';
            const pasteZone = document.getElementById('image-paste-zone');
            pasteZone.innerHTML = "Click here & paste your photo (Ctrl+V)";
            pasteZone.style.borderColor = "var(--light-green)";
            currentUploadedImageBase64 = "";

            savePoolToStorage();
        }

        window.deleteQuestionItemByIndex = function(index) {
            questionPool.splice(index, 1);
            savePoolToStorage();
        };

        function executeSystemResetAction() {
            if(confirm("Confirm structural purge? This resets configuration settings parameters to factory profiles definitions!")) {
                localStorage.removeItem("jungle_game_pool");
                questionPool = [...DEFAULT_QUESTIONS];
                savePoolToStorage();
                rebuildGameSession();
            }
        }

        function exportPoolDataJSON() {
            const outputString = JSON.stringify(questionPool, null, 2);
            const dataBlob = new Blob([outputString], { type: 'application/json' });
            const temporaryAnchor = document.createElement('a');
            temporaryAnchor.href = URL.createObjectURL(dataBlob);
            temporaryAnchor.download = `jungle_game_pool_${Date.now()}.json`;
            temporaryAnchor.click();
            URL.revokeObjectURL(temporaryAnchor.href);
        }

        function handleFileImportEvent(e) {
            const TargetFile = e.target.files[0];
            if(!TargetFile) return;

            const readerInstance = new FileReader();
            readerInstance.onload = function(event) {
                try {
                    const compiledData = JSON.parse(event.target.result);
                    if(Array.isArray(compiledData)) {
                        questionPool = compiledData;
                        savePoolToStorage();
                        alert("Configuration pool data structure parsed and synchronized successfully!");
                    } else {
                        alert("Malformed data model pattern structure! JSON profile requires top level vector arrays mapping signatures.");
                    }
                } catch(error) {
                    alert("Failure parsing validation target matrix records: " + error.message);
                }
            };
            readerInstance.readAsText(TargetFile);
        }
    </script>
</body>
</html>


Interactive educational games make learning more engaging and meaningful for students. This Jungle Connect-the-Dots Game transforms reading and spelling activities into a fun classroom adventure.

Teachers can customize lessons, create team competitions, and reuse activity databases for future classes.

🌴 Happy Teaching and Learning! 🎮📚

Post a Comment

0 Comments