| | {% extends "layout.html" %}
|
| | {% block content %}<!DOCTYPE html>
|
| | <html lang="en">
|
| | <head>
|
| | <meta charset="UTF-8">
|
| | <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| | <title>Clever Critter Classifier</title>
|
| | <script src="https://cdn.tailwindcss.com"></script>
|
| | <style>
|
| |
|
| | body {
|
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| | background-color: #e2e8f0;
|
| | display: block;
|
| | flex-direction: column;
|
| | align-items: center;
|
| | justify-content: center;
|
| | max-height: 100vh;
|
| | padding: 20px;
|
| | overflow-x: hidden;
|
| | }
|
| |
|
| | .main-wrapper {
|
| | max-width: 1400px;
|
| | width: 100%;
|
| | display: flex;
|
| | flex-direction: column;
|
| | gap: 2rem;
|
| | }
|
| |
|
| | .game-container {
|
| | background-color: #ffffff;
|
| | padding: 2.5rem;
|
| | border-radius: 1rem;
|
| | box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
|
| | text-align: center;
|
| | position: relative;
|
| | }
|
| |
|
| | h1 {
|
| | font-size: 3rem;
|
| | font-weight: 800;
|
| | margin-bottom: 1rem;
|
| | color: #1a202c;
|
| | letter-spacing: -0.05em;
|
| | }
|
| |
|
| | .tagline {
|
| | font-size: 1.25rem;
|
| | color: #4a5568;
|
| | margin-bottom: 2rem;
|
| | font-style: italic;
|
| | }
|
| |
|
| | .tree-container {
|
| | position: relative;
|
| | margin-top: 3rem;
|
| | min-height: 850px;
|
| | display: flex;
|
| | flex-direction: column;
|
| | align-items: center;
|
| | padding-top: 20px;
|
| | }
|
| |
|
| | #tree-svg {
|
| | position: absolute;
|
| | top: 0;
|
| | left: 0;
|
| | width: 100%;
|
| | height: 100%;
|
| | pointer-events: none;
|
| | overflow: visible;
|
| | }
|
| |
|
| | .tree-row {
|
| | display: flex;
|
| | justify-content: center;
|
| | width: 100%;
|
| | margin-bottom: 7rem;
|
| | position: relative;
|
| | z-index: 1;
|
| | }
|
| |
|
| | .tree-node {
|
| | background-color: #ebf8ff;
|
| | border: 2px solid #63b3ed;
|
| | border-radius: 0.75rem;
|
| | padding: 0.8rem 0.8rem;
|
| | margin: 0 1rem;
|
| | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
| | transition: all 0.4s ease-in-out, transform 0.2s ease-out, opacity 0.4s ease-in-out;
|
| | width: 220px;
|
| | min-height: 110px;
|
| | display: flex;
|
| | flex-direction: column;
|
| | justify-content: center;
|
| | align-items: center;
|
| | position: relative;
|
| | opacity: 0.3;
|
| | pointer-events: none;
|
| | text-align: center;
|
| | }
|
| |
|
| | .tree-node.active {
|
| | border-color: #3182ce;
|
| | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25);
|
| | background-color: #bee3f8;
|
| | opacity: 1;
|
| | pointer-events: all;
|
| | transform: translateY(-5px);
|
| | z-index: 2;
|
| | }
|
| |
|
| | .tree-node.path-taken {
|
| | border-color: #90cdf4;
|
| | background-color: #e0f2fe;
|
| | opacity: 0.7;
|
| | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
| | pointer-events: none;
|
| | transform: translateY(0);
|
| | z-index: 1;
|
| | }
|
| |
|
| | .root-node {
|
| | background-color: #38a169;
|
| | border-color: #2f855a;
|
| | color: white;
|
| | padding: 1.2rem;
|
| | font-size: 1.1rem;
|
| | opacity: 1;
|
| | pointer-events: all;
|
| | width: 260px;
|
| | min-height: 120px;
|
| | }
|
| | .root-node.active {
|
| | background-color: #2f855a;
|
| | }
|
| |
|
| | .leaf-node {
|
| | background-color: #d6eaff;
|
| | border-color: #63b3ed;
|
| | opacity: 0.3;
|
| | pointer-events: none;
|
| | }
|
| | .leaf-node.active {
|
| | background-color: #a7d3ff;
|
| | box-shadow: 0 0 20px rgba(99, 179, 237, 0.7);
|
| | transform: scale(1.05);
|
| | opacity: 1;
|
| | pointer-events: none;
|
| | }
|
| |
|
| | .node-question {
|
| | font-size: 1.1rem;
|
| | font-weight: 700;
|
| | margin-bottom: 0.8rem;
|
| | color: inherit;
|
| | }
|
| |
|
| | .classification-text {
|
| | font-size: 1.5rem;
|
| | font-weight: bold;
|
| | color: #2f855a;
|
| | padding: 0.4rem;
|
| | }
|
| |
|
| | .node-buttons {
|
| | display: flex;
|
| | justify-content: center;
|
| | gap: 0.6rem;
|
| | flex-wrap: wrap;
|
| | margin-top: auto;
|
| | }
|
| |
|
| | .node-button {
|
| | background-color: #4299e1;
|
| | color: white;
|
| | padding: 0.6rem 1.2rem;
|
| | border-radius: 0.5rem;
|
| | font-weight: 700;
|
| | cursor: pointer;
|
| | border: none;
|
| | transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out, box-shadow 0.2s ease-in-out;
|
| | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| | font-size: 0.9rem;
|
| | }
|
| |
|
| | .node-button:hover:not(:disabled) {
|
| | background-color: #3182ce;
|
| | transform: translateY(-2px);
|
| | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
| | }
|
| | .node-button:active:not(:disabled) {
|
| | transform: translateY(0);
|
| | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| | }
|
| |
|
| | .node-button:disabled {
|
| | background-color: #a0aec0;
|
| | cursor: not-allowed;
|
| | opacity: 0.7;
|
| | transform: none;
|
| | box-shadow: none;
|
| | }
|
| |
|
| |
|
| | .connector-line {
|
| | stroke: #a0aec0;
|
| | stroke-width: 3;
|
| | transition: stroke 0.4s ease-in-out, stroke-width 0.4s ease-in-out, opacity 0.4s ease-in-out;
|
| | }
|
| |
|
| | .connector-line.active-path {
|
| | stroke: #3182ce !important;
|
| | stroke-width: 5 !important;
|
| | opacity: 1 !important;
|
| | }
|
| | .connector-line.inactive-path {
|
| | opacity: 0.3;
|
| | stroke-width: 2;
|
| | }
|
| |
|
| |
|
| | .reset-button {
|
| | background-color: #e53e3e;
|
| | color: white;
|
| | padding: 0.9rem 2rem;
|
| | border-radius: 0.5rem;
|
| | font-weight: 700;
|
| | cursor: pointer;
|
| | border: none;
|
| | transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out, box-shadow 0.2s ease-in-out;
|
| | margin-top: 2rem;
|
| | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| | }
|
| | .reset-button:hover {
|
| | background-color: #c53030;
|
| | transform: translateY(-2px);
|
| | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
| | }
|
| | .reset-button:active {
|
| | transform: translateY(0);
|
| | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| | }
|
| |
|
| | .llm-explanation-container {
|
| | margin-top: 2rem;
|
| | background-color: #f0f8ff;
|
| | border: 1px solid #a7d3ff;
|
| | padding: 2rem;
|
| | border-radius: 0.75rem;
|
| | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
|
| | text-align: left;
|
| | }
|
| | .llm-explanation-container h4 {
|
| | font-size: 1.25rem;
|
| | font-weight: bold;
|
| | margin-bottom: 1rem;
|
| | color: #2c5282;
|
| | }
|
| | .llm-explanation-container p {
|
| | font-size: 1.05rem;
|
| | line-height: 1.7;
|
| | color: #4a5568;
|
| | }
|
| | .loading-indicator {
|
| | text-align: center;
|
| | font-style: italic;
|
| | color: #718096;
|
| | padding: 1rem;
|
| | }
|
| |
|
| |
|
| | .confetti-container {
|
| | position: absolute;
|
| | top: 0;
|
| | left: 0;
|
| | width: 100%;
|
| | height: 100%;
|
| | overflow: hidden;
|
| | pointer-events: none;
|
| | z-index: 10;
|
| | }
|
| | .confetti {
|
| | position: absolute;
|
| | width: 10px;
|
| | height: 10px;
|
| | background-color: #f06;
|
| | opacity: 0;
|
| | animation: fall 3s ease-out forwards;
|
| | }
|
| | .confetti:nth-child(2n) { background-color: #6cf; }
|
| | .confetti:nth-child(3n) { background-color: #ffc; }
|
| | .confetti:nth-child(4n) { background-color: #9c9; }
|
| | .confetti:nth-child(5n) { background-color: #f60; }
|
| |
|
| | @keyframes fall {
|
| | 0% { transform: translateY(0) rotateZ(0deg); opacity: 1; }
|
| | 100% { transform: translateY(100vh) rotateZ(720deg); opacity: 0; }
|
| | }
|
| |
|
| |
|
| | @media (max-width: 768px) {
|
| | h1 {
|
| | font-size: 2rem;
|
| | }
|
| | .tagline {
|
| | font-size: 1rem;
|
| | }
|
| | .tree-node {
|
| | width: 90%;
|
| | min-height: unset;
|
| | margin: 1rem auto;
|
| | padding: 1rem;
|
| | }
|
| | .tree-row {
|
| | flex-direction: column;
|
| | align-items: center;
|
| | margin-bottom: 3rem;
|
| | }
|
| | .node-buttons {
|
| | flex-direction: column;
|
| | gap: 0.75rem;
|
| | }
|
| | .node-button {
|
| | width: 100%;
|
| | padding: 0.75rem 1rem;
|
| | }
|
| | .root-node {
|
| | width: 90%;
|
| | min-height: unset;
|
| | padding: 1.5rem;
|
| | }
|
| | }
|
| | </style>
|
| | </head>
|
| | <body>
|
| | <div class="main-wrapper">
|
| | <div class="game-container">
|
| | <h1 class="text-gray-900">🐾 Clever Critter Classifier! 🌿</h1>
|
| | <p class="tagline text-gray-700">
|
| | Think of your favorite animal, answer wisely, and watch the tree reveal its identity!
|
| | </p>
|
| |
|
| | <div class="tree-container" id="game-tree-container">
|
| | <svg id="tree-svg"></svg>
|
| | <div class="tree-row level-1">
|
| | <div id="node-root" class="tree-node root-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-root" class="node-buttons"></div>
|
| | </div>
|
| | </div>
|
| | <div class="tree-row level-2">
|
| | <div id="node-land-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-land-branch" class="node-buttons"></div>
|
| | </div>
|
| | <div id="node-water-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-water-branch" class="node-buttons"></div>
|
| | </div>
|
| | </div>
|
| | <div class="tree-row level-3">
|
| | <div id="node-fur-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-fur-branch" class="node-buttons"></div>
|
| | </div>
|
| | <div id="node-no-fur-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-no-fur-branch" class="node-buttons"></div>
|
| | </div>
|
| | <div id="node-water-mammal-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-water-mammal-branch" class="node-buttons"></div>
|
| | </div>
|
| | <div id="node-water-non-mammal-branch" class="tree-node internal-node">
|
| | <p class="node-question"></p>
|
| | <div id="buttons-water-non-mammal-branch" class="node-buttons"></div>
|
| | </div>
|
| | </div>
|
| | <div class="tree-row level-4">
|
| | <div id="node-leaf-lion" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-cat" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-eagle" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-snake" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-dolphin" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-whale" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-fish" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | <div id="node-leaf-octopus" class="tree-node leaf-node">
|
| | <p class="classification-text"></p>
|
| | </div>
|
| | </div>
|
| | </div>
|
| |
|
| | <button id="explain-classification-button" class="node-button mt-6 hidden">✨ Deep Dive into Classification ✨</button>
|
| | <div id="llm-explanation-container" class="llm-explanation-container hidden">
|
| | <div id="llm-explanation-box" class="llm-explanation-box"></div>
|
| | </div>
|
| | <button id="reset-button" class="reset-button hidden">Start Over</button>
|
| | </div>
|
| |
|
| | <div class="mt-8 p-6 bg-blue-50 rounded-lg border border-blue-200 shadow-md">
|
| | <h3 class="text-xl font-extrabold mb-4 text-center text-blue-800">Understanding Decision Trees</h3>
|
| | <p class="text-gray-700 leading-relaxed mb-4">
|
| | This game is a simple, fun way to explore how a **Decision Tree** algorithm works in Machine Learning. Imagine a flowchart that helps make decisions!
|
| | </p>
|
| | <ul class="list-disc list-inside text-gray-800 space-y-2">
|
| | <li>
|
| | <strong>🌳 Root Node (Top):</strong> This is your starting point, like the first big question everyone asks. In real Decision Trees, this node considers all possible data and picks the best question to split it.
|
| | </li>
|
| | <li>
|
| | <strong>🌿 Internal Nodes (Middle Questions):</strong> These are where you make choices based on features (like "Does it have fur?"). Each answer leads you down a specific branch, getting you closer to a final decision.
|
| | </li>
|
| | <li>
|
| | <strong>🌸 Leaf Nodes (Bottom - Classifications):</strong> You've reached the end of a path! These are the final answers or predictions. In machine learning, this could be classifying an email as "Spam" or predicting if a customer will "Churn."
|
| | </li>
|
| | <li>
|
| | <strong>🔗 Paths (Decision Rules):</strong> The sequence of choices you make from the root to a leaf node creates a unique "rule." This rule can then be applied to new, unseen data to make a prediction!
|
| | </li>
|
| | </ul>
|
| | <p class="mt-4 text-gray-700 leading-relaxed">
|
| | Just like your choices guide you through this game, Decision Trees learn these question-and-answer paths from large datasets to classify new information.
|
| | </p>
|
| | </div>
|
| | </div>
|
| |
|
| | <script>
|
| | const treeData = {
|
| | 'root': {
|
| | question: 'Does your favorite animal live on land or in water?',
|
| | options: [
|
| | { text: 'Land 🏞️', next: 'land-branch', value: 'land' },
|
| | { text: 'Water 🌊', next: 'water-branch', value: 'water' }
|
| | ],
|
| | level: 1
|
| | },
|
| | 'land-branch': {
|
| | question: 'Does your favorite animal have fur?',
|
| | options: [
|
| | { text: 'Yes 👍', next: 'fur-branch', value: 'fur' },
|
| | { text: 'No 👎', next: 'no-fur-branch', value: 'no_fur' }
|
| | ],
|
| | level: 2
|
| | },
|
| | 'water-branch': {
|
| | question: 'Is your favorite animal a mammal?',
|
| | options: [
|
| | { text: 'Yes 👍', next: 'water-mammal-branch', value: 'mammal' },
|
| | { text: 'No 👎', next: 'water-non-mammal-branch', value: 'not_mammal' }
|
| | ],
|
| | level: 2
|
| | },
|
| | 'fur-branch': {
|
| | question: 'Is your favorite animal large?',
|
| | options: [
|
| | { text: 'Yes 👍', next: 'leaf-lion', value: 'large' },
|
| | { text: 'No 👎', next: 'leaf-cat', value: 'small' }
|
| | ],
|
| | level: 3
|
| | },
|
| | 'no-fur-branch': {
|
| | question: 'Can it fly?',
|
| | options: [
|
| | { text: 'Yes 👍', next: 'leaf-eagle', value: 'flies' },
|
| | { text: 'No 👎', next: 'leaf-snake', value: 'no_fly' }
|
| | ],
|
| | level: 3
|
| | },
|
| | 'water-mammal-branch': {
|
| | question: 'Is it found in saltwater or freshwater?',
|
| | options: [
|
| | { text: 'Saltwater 🧂', next: 'leaf-dolphin', value: 'saltwater' },
|
| | { text: 'Freshwater 💧', next: 'leaf-whale', value: 'freshwater_mammal' }
|
| | ],
|
| | level: 3
|
| | },
|
| | 'water-non-mammal-branch': {
|
| | question: 'Does it have scales?',
|
| | options: [
|
| | { text: 'Yes ✅', next: 'leaf-fish', value: 'scales' },
|
| | { text: 'No ❌', next: 'leaf-octopus', value: 'no_scales' }
|
| | ],
|
| | level: 3
|
| | },
|
| |
|
| | 'leaf-lion': { classification: 'Lion 🦁', explanation: 'Lions are large, furry land animals. They fit the criteria for this classification because they reside on land, possess fur, and are generally considered large mammals.', level: 4 },
|
| | 'leaf-cat': { classification: 'Cat 🐈', explanation: 'Cats are small, furry land animals. This classification is reached as cats live on land, have fur, and are small in size.', level: 4 },
|
| | 'leaf-eagle': { classification: 'Eagle 🦅', explanation: 'Eagles are non-furry land animals that can fly. They are classified here because they inhabit land, lack fur, and are characterized by their ability to fly.', level: 4 },
|
| | 'leaf-snake': { classification: 'Snake 🐍', explanation: 'Snakes are non-furry land animals that cannot fly. This classification applies to snakes as they live on land, do not have fur, and are unable to fly.', level: 4 },
|
| | 'leaf-dolphin': { classification: 'Dolphin 🐬', explanation: 'Dolphins are water mammals found in saltwater. They are classified here because they live in water, are mammals, and inhabit saltwater environments.', level: 4 },
|
| | 'leaf-whale': { classification: 'Whale 🐳', explanation: 'Whales are typically large water mammals. This classification applies to water animals that are mammals and are very large.', level: 4 },
|
| | 'leaf-fish': { classification: 'Fish 🐠', explanation: 'Fish are aquatic animals, often with scales, found in various water bodies. This classification is given to non-mammalian water creatures with scales.', level: 4 },
|
| | 'leaf-octopus': { classification: 'Octopus 🐙', explanation: 'Octopuses are aquatic animals without scales. This classification is for water non-mammals that do not have scales.', level: 4 }
|
| | };
|
| |
|
| | let currentNodeLogicalId = 'root';
|
| | let currentPath = [];
|
| | let lastClassificationNode = null;
|
| |
|
| |
|
| | function getElementById(id) {
|
| | return document.getElementById(id);
|
| | }
|
| |
|
| | function clearNodeButtons(nodeId) {
|
| | const buttonsContainer = getElementById(`buttons-${nodeId}`);
|
| | if (buttonsContainer) {
|
| | buttonsContainer.innerHTML = '';
|
| | }
|
| | }
|
| |
|
| | function drawLine(startX, startY, endX, endY, id, is_active_path = false) {
|
| | const svg = getElementById('tree-svg');
|
| | let line = getElementById(id);
|
| | if (!line) {
|
| | line = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
| | line.setAttribute('id', id);
|
| | line.setAttribute('class', 'connector-line');
|
| | svg.appendChild(line);
|
| | }
|
| | line.setAttribute('x1', startX);
|
| | line.setAttribute('y1', startY);
|
| | line.setAttribute('x2', endX);
|
| | line.setAttribute('y2', endY);
|
| |
|
| |
|
| | if (is_active_path) {
|
| | line.classList.add('active-path');
|
| | line.classList.remove('inactive-path');
|
| | } else {
|
| | line.classList.remove('active-path');
|
| | line.classList.add('inactive-path');
|
| | }
|
| | }
|
| |
|
| |
|
| | function getAbsolutePosition(element, type = 'bottom') {
|
| | if (!element) return { x: 0, y: 0, width: 0, height: 0 };
|
| | const rect = element.getBoundingClientRect();
|
| | const svg = getElementById('tree-svg');
|
| | const svgRect = svg.getBoundingClientRect();
|
| |
|
| | let x = rect.left - svgRect.left + rect.width / 2;
|
| | let y;
|
| | if (type === 'bottom') {
|
| | y = rect.top - svgRect.top + rect.height;
|
| | } else {
|
| | y = rect.top - svgRect.top;
|
| | }
|
| | return { x, y, width: rect.width, height: rect.height };
|
| | }
|
| |
|
| |
|
| | function drawAllLines() {
|
| | const svg = getElementById('tree-svg');
|
| |
|
| | while (svg.firstChild) {
|
| | svg.removeChild(svg.firstChild);
|
| | }
|
| |
|
| |
|
| | for (const parentLogicalId in treeData) {
|
| | const parentData = treeData[parentLogicalId];
|
| | if (parentData.options) {
|
| | parentData.options.forEach(option => {
|
| | const childLogicalId = option.next;
|
| | const parentNode = getElementById(`node-${parentLogicalId}`);
|
| | const childNode = getElementById(`node-${childLogicalId}`);
|
| |
|
| | if (parentNode && childNode) {
|
| | const parentPos = getAbsolutePosition(parentNode, 'bottom');
|
| | const childPos = getAbsolutePosition(childNode, 'top');
|
| |
|
| | const lineId = `line-${parentLogicalId}-to-${childLogicalId}`;
|
| |
|
| | const isPathSegment = currentPath.includes(parentLogicalId) && currentPath.includes(childLogicalId);
|
| | drawLine(parentPos.x, parentPos.y, childPos.x, childPos.y, lineId, isPathSegment);
|
| | }
|
| | });
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | function launchConfetti() {
|
| | const confettiContainer = document.createElement('div');
|
| | confettiContainer.classList.add('confetti-container');
|
| | getElementById('game-tree-container').appendChild(confettiContainer);
|
| |
|
| | for (let i = 0; i < 50; i++) {
|
| | const confetti = document.createElement('div');
|
| | confetti.classList.add('confetti');
|
| | confetti.style.left = `${Math.random() * 100}%`;
|
| | confetti.style.backgroundColor = `hsl(${Math.random() * 360}, 70%, 60%)`;
|
| | confetti.style.animationDelay = `${Math.random() * 0.5}s`;
|
| | confetti.style.animationDuration = `${3 + Math.random() * 2}s`;
|
| | confettiContainer.appendChild(confetti);
|
| | }
|
| |
|
| |
|
| | setTimeout(() => {
|
| | confettiContainer.remove();
|
| | }, 5000);
|
| | }
|
| |
|
| |
|
| | function initializeNode(nodeLogicalId) {
|
| | const nodeData = treeData[nodeLogicalId];
|
| | const nodeElement = getElementById(`node-${nodeLogicalId}`);
|
| | const buttonsContainer = getElementById(`buttons-${nodeLogicalId}`);
|
| |
|
| | if (!nodeElement || !nodeData) {
|
| | console.error(`Node element or data not found for ID: ${nodeLogicalId}`);
|
| | return;
|
| | }
|
| |
|
| |
|
| | document.querySelectorAll('.tree-node').forEach(node => {
|
| | node.classList.remove('active', 'path-taken');
|
| |
|
| | node.style.opacity = '0.3';
|
| | node.style.pointerEvents = 'none';
|
| | clearNodeButtons(node.id.replace('node-', ''));
|
| | });
|
| |
|
| |
|
| | currentPath.forEach(pathId => {
|
| | const pathNodeElement = getElementById(`node-${pathId}`);
|
| | if (pathNodeElement && pathId !== nodeLogicalId) {
|
| | pathNodeElement.classList.add('path-taken');
|
| | pathNodeElement.style.opacity = '0.7';
|
| | pathNodeElement.style.pointerEvents = 'none';
|
| | }
|
| | });
|
| |
|
| |
|
| | nodeElement.classList.add('active');
|
| | nodeElement.style.opacity = '1';
|
| | nodeElement.style.pointerEvents = 'all';
|
| |
|
| | if (nodeData.classification) {
|
| | nodeElement.querySelector('.classification-text').textContent = nodeData.classification;
|
| | clearNodeButtons(nodeLogicalId);
|
| | lastClassificationNode = nodeLogicalId;
|
| | getElementById('explain-classification-button').classList.remove('hidden');
|
| | getElementById('reset-button').classList.remove('hidden');
|
| | launchConfetti();
|
| | } else {
|
| | const questionElement = nodeElement.querySelector('.node-question');
|
| | questionElement.textContent = nodeData.question;
|
| |
|
| | buttonsContainer.innerHTML = '';
|
| | nodeData.options.forEach(option => {
|
| | const button = document.createElement('button');
|
| | button.classList.add('node-button');
|
| | button.textContent = option.text;
|
| | button.onclick = () => chooseOption(nodeLogicalId, option.next);
|
| | buttonsContainer.appendChild(button);
|
| | });
|
| | getElementById('explain-classification-button').classList.add('hidden');
|
| | getElementById('llm-explanation-container').classList.add('hidden');
|
| | getElementById('reset-button').classList.add('hidden');
|
| | }
|
| |
|
| |
|
| | drawAllLines();
|
| | }
|
| |
|
| | function chooseOption(parentNodeLogicalId, nextNodeLogicalId) {
|
| |
|
| | if (!currentPath.includes(parentNodeLogicalId)) {
|
| | currentPath.push(parentNodeLogicalId);
|
| | }
|
| | currentPath.push(nextNodeLogicalId);
|
| |
|
| | currentNodeLogicalId = nextNodeLogicalId;
|
| |
|
| |
|
| | initializeNode(nextNodeLogicalId);
|
| | }
|
| |
|
| | function startGame() {
|
| |
|
| | currentNodeLogicalId = 'root';
|
| | currentPath = [];
|
| | lastClassificationNode = null;
|
| |
|
| |
|
| | for (const id in treeData) {
|
| | const nodeData = treeData[id];
|
| | const nodeElement = getElementById(`node-${id}`);
|
| | if (nodeElement) {
|
| |
|
| | if (nodeData.classification) {
|
| | const classificationText = nodeElement.querySelector('.classification-text');
|
| | if (classificationText) classificationText.textContent = nodeData.classification;
|
| | } else if (nodeData.question) {
|
| | const questionText = nodeElement.querySelector('.node-question');
|
| | if (questionText) questionText.textContent = nodeData.question;
|
| | }
|
| |
|
| |
|
| | nodeElement.classList.remove('active', 'path-taken');
|
| | nodeElement.style.opacity = '0.3';
|
| | nodeElement.style.pointerEvents = 'none';
|
| | clearNodeButtons(nodeElement.id.replace('node-', ''));
|
| | }
|
| | }
|
| |
|
| |
|
| | initializeNode(currentNodeLogicalId);
|
| | }
|
| |
|
| |
|
| | async function explainClassification() {
|
| | const explanationContainer = getElementById('llm-explanation-container');
|
| | const explanationBox = getElementById('llm-explanation-box');
|
| | explanationContainer.classList.remove('hidden');
|
| | explanationBox.innerHTML = '<p class="loading-indicator">Loading explanation...</p>';
|
| |
|
| | if (lastClassificationNode && treeData[lastClassificationNode]) {
|
| | const animalName = treeData[lastClassificationNode].classification.split(' ')[0];
|
| | const explanationText = treeData[lastClassificationNode].explanation;
|
| |
|
| |
|
| | setTimeout(() => {
|
| | explanationBox.innerHTML = `
|
| | <h4>Why ${animalName}?</h4>
|
| | <p>${explanationText}</p>
|
| | `;
|
| | }, 1000);
|
| | } else {
|
| | explanationBox.innerHTML = '<p class="text-red-500">No classification to explain yet. Please play the game!</p>';
|
| | }
|
| | }
|
| |
|
| |
|
| | window.onload = () => {
|
| | startGame();
|
| |
|
| | window.addEventListener('resize', drawAllLines);
|
| | };
|
| | getElementById('reset-button').addEventListener('click', startGame);
|
| | getElementById('explain-classification-button').addEventListener('click', explainClassification);
|
| | </script>
|
| |
|
| | <div class="back-button">
|
| | <a href="/decision_tree" class=" bg-gray-200 center hover:bg-gray-300 text-black-800 px-4 py-2 rounded shadow">
|
| | ← Back to decision tree
|
| | </a>
|
| | </div>
|
| | </body>
|
| | </html>
|
| | {% endblock %} |