Cauldron Simulator

This prompt requests code for an interactive web-based cauldron simulator. Users can drag blocks to change liquid temperature, triggering visual changes, animations, and comments, with a reset option.

EducationVisualCode

Prompt

I am a teacher and want to create an interactive web app for my lessons. I want a cauldron simulator in which:

- The cauldron shows the current temperature.
- A 'liquid' in the cauldron changes visually (in height and color) based on the temperature.
- Two blocks are present: one for increasing (plus) and one for decreasing (minus) the temperature. You can drag these blocks to the cauldron to adjust the temperature.
- When dragging, animations (emojis) appear and a funny comment is shown depending on the temperature.
- There is also a reset button to reset the simulator to the starting values.

Can you provide a web app code to build this?

Example Output

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>πŸ§™ Cauldron Simulator 🌑️</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        text-align: center;
        background: #00ae26;
        margin: 20px;
      }
      h1 {
        margin-bottom: 20px;
        color: #ffffff; /* Purple */
      }
      /* Central simulator container */
      .simulation-container {
        max-width: 400px;
        margin: 0 auto;
      }
      /* Square cauldron (dropzone) */
      .cauldron {
        position: relative;
        width: 250px;
        height: 250px;
        background: #555;
        border: 5px solid #222;
        margin: 0 auto;
      }
      /* The "liquid" in the cauldron with color transition */
      .liquid {
        position: absolute;
        bottom: 0;
        width: 100%;
        height: 0%;
        background-color: blue;
        transition: height 0.5s, background-color 0.5s;
      }
      /* Temperature text in the cauldron */
      .temp-text {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        color: #fff;
        font-size: 28px;
        font-weight: bold;
        text-shadow: 1px 1px 2px #000;
        pointer-events: none;
      }
      /* Container for the two temperature blocks */
      .blocks-container {
        display: flex;
        justify-content: space-around;
        margin: 20px 0;
      }
      /* Block styling */
      .block {
        padding: 15px;
        border-radius: 5px;
        display: flex;
        align-items: center;
        gap: 5px;
        cursor: grab;
      }
      /* Plus block (left, red) */
      #blockPlus {
        background: red;
        color: #fff;
      }
      /* Minus block (right, blue) */
      #blockMinus {
        background: blue;
        color: #fff;
      }
      /* Icons: make them larger */
      .icon {
        font-size: 28px;
        font-weight: bold;
      }
      /* Ensure the input field does not drag */
      .block input {
        font-size: 24px;
        width: 80px;
        text-align: center;
        border: none;
        background: transparent;
        color: inherit;
        /* Prevent the input field from triggering a dragstart */
        pointer-events: auto;
      }
      /* Funny comments below the cauldron: background becomes purple, text green */
      .joke-container {
        background: #800080; /* Purple */
        color: #00ff00; /* Green letters */
        border: 2px dashed #e0c97f;
        border-radius: 10px;
        padding: 15px;
        margin-top: 20px;
        text-align: center;
        min-height: 50px;
      }
      /* Reset button */
      .reset-btn {
        margin-top: 20px;
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
      }
      /* Emoji animation: multiple at once, visible longer */
      .emoji-fly {
        position: absolute;
        font-size: 32px;
        pointer-events: none;
        animation: flyOut 2s forwards;
      }
      @keyframes flyOut {
        0% {
          opacity: 1;
          transform: translateY(0);
        }
        100% {
          opacity: 0;
          transform: translateY(-150px);
        }
      }
    </style>
  </head>
  <body>
    <h1>πŸ§™ Cauldron Simulator 🌑️</h1>

    <div class="simulation-container">
      <div
        id="cauldron"
        class="cauldron"
        ondrop="drop(event)"
        ondragover="allowDrop(event)"
      >
        <div id="liquid" class="liquid"></div>
        <div id="tempText" class="temp-text">-5Β°C</div>
      </div>

      <div class="blocks-container">
        <div
          class="block"
          id="blockPlus"
          draggable="true"
          ondragstart="drag(event)"
        >
          <span class="icon">+</span>
          <input
            type="number"
            value="0"
            step="0.5"
            ondragstart="event.stopPropagation()"
          />
        </div>
        <div
          class="block"
          id="blockMinus"
          draggable="true"
          ondragstart="drag(event)"
        >
          <span class="icon">-</span>
          <input
            type="number"
            value="0"
            step="0.5"
            ondragstart="event.stopPropagation()"
          />
        </div>
      </div>

      <div class="joke-container">
        <h2>Magic Temperature πŸ§™β€β™€οΈ</h2>
        <div id="joke"></div>
      </div>

      <button class="reset-btn" onclick="resetSimulator()">
        Reset Simulator πŸ”„
      </button>
    </div>

    <script>
      // Initial values
      let currentTemp = -5;
      const minTemp = -10;
      const maxTemp = 20;

      // Elements
      const tempText = document.getElementById('tempText');
      const liquid = document.getElementById('liquid');
      const cauldron = document.getElementById('cauldron');
      const jokeDiv = document.getElementById('joke');

      // Update the display in the cauldron
      function updateDisplay() {
        tempText.textContent = currentTemp + 'Β°C';
        // Calculate fill percentage based on the range
        const clampedTemp = Math.min(Math.max(currentTemp, minTemp), maxTemp);
        const percent = ((clampedTemp - minTemp) / (maxTemp - minTemp)) * 100;
        liquid.style.height = percent + '%';
        // Color transition: below 0 blue, from 0 red
        liquid.style.backgroundColor = currentTemp < 0 ? 'blue' : 'red';
        updateJoke();
      }

      // Update the funny comment below the cauldron
      function updateJoke() {
        let joke = '';
        if (currentTemp < 0) {
          joke = 'Brrr... Ice cold! πŸ₯Άβ„️';
        } else if (currentTemp < 10) {
          joke = 'It\'s starting to warm up... πŸ™‚πŸŒ€οΈ';
        } else if (currentTemp < 20) {
          joke = 'Sunny vibes in the cauldron! 😎🌞';
        } else {
          joke = 'Wow, hot cauldron! πŸ”₯πŸ₯΅';
        }
        jokeDiv.textContent = joke;
      }

      // Ensure the dropzone (cauldron) accepts drops
      function allowDrop(ev) {
        ev.preventDefault();
      }

      // On dragstart: get the value and set it in DataTransfer
      function drag(ev) {
        const block = ev.target.closest('.block');
        const input = block.querySelector('input');
        let value = parseFloat(input.value) || 0;
        // Determine if it's the plus or minus block (based on id)
        const blockType = block.id === 'blockPlus' ? 'plus' : 'minus';
        // Ensure plus block always gives a positive value and minus block a negative
        value = blockType === 'plus' ? Math.abs(value) : -Math.abs(value);
        ev.dataTransfer.setData('value', value);
        ev.dataTransfer.setData('blockId', block.id);
      }

      // On drop in the cauldron
      function drop(ev) {
        ev.preventDefault();
        let value = parseFloat(ev.dataTransfer.getData('value')) || 0;
        const blockId = ev.dataTransfer.getData('blockId');
        currentTemp += value;
        updateDisplay();
        animateEmojis();
        // Reset the input field of the dragged block to 0
        document.querySelector(`#${blockId} input`).value = 0;
      }

      // Make multiple emojis "fly" out of the cauldron simultaneously
      function animateEmojis() {
        let emoji = '';
        if (currentTemp < 0) {
          emoji = '❄️';
        } else if (currentTemp < 10) {
          emoji = '🌀️';
        } else if (currentTemp < 20) {
          emoji = '🌞';
        } else {
          emoji = 'πŸ”₯';
        }
        // Create 3 emojis with random horizontal positions
        for (let i = 0; i < 3; i++) {
          let span = document.createElement('span');
          span.className = 'emoji-fly';
          span.textContent = emoji;
          // Random left position within the cauldron (between 10% and 80%)
          span.style.left = Math.random() * 70 + 10 + '%';
          span.style.top = '20%';
          cauldron.appendChild(span);
          span.addEventListener('animationend', () => {
            cauldron.removeChild(span);
          });
        }
      }

      // Reset the simulator
      function resetSimulator() {
        currentTemp = -5;
        updateDisplay();
        document.querySelector('#blockPlus input').value = 0;
        document.querySelector('#blockMinus input').value = 0;
      }

      // Initialization
      updateDisplay();
    </script>
  </body>
</html>