← Về thư mục
📄 / / projects / tech / OpenMAIC / app / packages / @openmaic / generation / templates / simulation-content / system.md

Simulation Widget Content Generator

Generate a self-contained HTML simulation with embedded widget configuration.

Output Structure

Your output must be a complete HTML document with:

  1. Standard HTML5 structure
  2. Embedded widget configuration in a <script type="application/json" id="widget-config"> tag
  3. Interactive controls for variables
  4. Canvas or SVG visualization
  5. Mobile-responsive design
  6. postMessage listener for widget actions (REQUIRED)

Widget Config Schema

{
  "type": "simulation",
  "concept": "projectile_motion",
  "description": "...",
  "variables": [
    { "name": "angle", "label": "Launch Angle", "min": 0, "max": 90, "default": 45, "unit": "°" }
  ],
  "presets": [
    { "name": "Hit the target", "variables": { "angle": 30, "velocity": 25 } }
  ]
}

CRITICAL: postMessage Listener for Widget Actions

Your HTML MUST include this message listener to respond to widget actions:

// Add this script at the end of your HTML
window.addEventListener('message', function(event) {
  const { type, target, state, content } = event.data;

  switch (type) {
    case 'SET_WIDGET_STATE':
      // Update all variables in the state object
      if (state) {
        Object.entries(state).forEach(([key, value]) => {
          // Find the slider/input for this variable and update it
          const slider = document.getElementById(key + '-slider') || document.querySelector('[data-var="' + key + '"]');
          if (slider) {
            slider.value = value;
            // Trigger change event to update simulation
            slider.dispatchEvent(new Event('input', { bubbles: true }));
          }
        });
      }
      break;

    case 'HIGHLIGHT_ELEMENT':
      // Highlight the target element with a pulsing border
      const highlightEl = document.querySelector(target);
      if (highlightEl) {
        highlightEl.style.outline = '3px solid rgba(139, 92, 246, 0.8)';
        highlightEl.style.outlineOffset = '4px';
        highlightEl.style.animation = 'pulse-highlight 2s infinite';
        // Remove highlight after 3 seconds
        setTimeout(() => {
          highlightEl.style.outline = '';
          highlightEl.style.animation = '';
        }, 3000);
      }
      break;

    case 'ANNOTATE_ELEMENT':
      // Show an annotation tooltip near the target element
      const annotateEl = document.querySelector(target);
      if (annotateEl && content) {
        const rect = annotateEl.getBoundingClientRect();
        const tooltip = document.createElement('div');
        tooltip.className = 'teacher-annotation';
        tooltip.style.cssText = 'position:fixed; top:' + (rect.top - 40) + 'px; left:' + rect.left + 'px; background:rgba(139,92,246,0.95); color:white; padding:8px 12px; border-radius:8px; font-size:14px; z-index:1000; animation:fadeIn 0.3s;';
        tooltip.textContent = content;
        document.body.appendChild(tooltip);
        setTimeout(() => tooltip.remove(), 4000);
      }
      break;

    case 'REVEAL_ELEMENT':
      // Reveal a hidden element
      const revealEl = document.querySelector(target);
      if (revealEl) {
        revealEl.style.display = '';
        revealEl.style.opacity = '1';
      }
      break;
  }
});

// Add this CSS for animations
const style = document.createElement('style');
style.textContent = '@keyframes pulse-highlight { 0%, 100% { outline-color: rgba(139, 92, 246, 0.8); } 50% { outline-color: rgba(139, 92, 246, 0.4); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }';
document.head.appendChild(style);

Element Naming Convention

To make highlight/annotation work, use consistent IDs for controls: - Sliders: id="{variable_name}-slider" (e.g., id="angle-slider", id="velocity-slider") - Buttons: id="{action}-btn" (e.g., id="start-btn", id="reset-btn") - Displays: id="{variable_name}-display" (e.g., id="acceleration-display")

CRITICAL Design Requirements

1. Mobile Layout - NO OVERLAP

Example mobile-safe layout:

<body class="flex flex-col min-h-screen md:flex-row">
  <!-- Mobile: Full-width, collapsible control panel -->
  <div id="controls" class="w-full md:w-80 shrink-0 overflow-auto max-h-[40vh] md:max-h-screen">
    <!-- Controls here -->
    <button onclick="toggleControls()" class="md:hidden">Hide Controls</button>
  </div>
  <!-- Canvas area gets remaining space -->
  <div class="flex-1 min-h-[300px] relative">
    <canvas id="canvas"></canvas>
  </div>
</body>

2. Reset Button - MUST WORK CORRECTLY

Correct implementation:

let state = { running: false, ended: false, posX: 50, velocity: 0 };

function handleMainButton() {
  if (state.ended) {
    // If simulation ended, reset first
    resetSimulation();
  } else if (state.running) {
    pauseSimulation();
  } else {
    startSimulation();
  }
}

function resetSimulation() {
  state.running = false;
  state.ended = false;
  state.posX = 50;  // Reset to initial position!
  state.velocity = 0;  // Reset velocity!
  updateButton('启动');
  draw();
}

// When simulation hits boundary/ends:
function onSimulationEnd() {
  state.running = false;
  state.ended = true;
  updateButton('重新开始');
}

function updateButton(text) {
  document.getElementById('mainBtn').innerText = text;
}

3. Button State Management

4. Touch-Friendly Controls

5. Canvas Sizing

6. Visual Feedback

7. Visible Animation (CRITICAL)

When the user clicks "启动" (Start), there MUST be OBVIOUS visual animation.

Animation Requirements:

  1. Moving objects: Objects should visibly move, rotate, or change when simulation runs
  2. Clear motion: Animation should be immediately noticeable - not subtle
  3. Rotation animations: For spinning/rotating objects (earth, wheels, etc.), show actual rotation: ```javascript // GOOD: Earth visibly rotates function draw() { ctx.clearRect(0, 0, w, h); ctx.save(); ctx.translate(centerX, centerY); ctx.rotate(rotationAngle); // Earth rotates! // Draw earth content... ctx.restore();

    if (state.running) { rotationAngle += 0.02 * state.speed; // Update rotation } } ``` 4. Multiple visual cues: Combine motion with other feedback: - Object position/rotation changes - Clock/timer updates - Color changes or highlights - Particle effects for dynamic simulations

BAD Example (User can't tell if it's running):

// Earth is static 2D circle, only time number changes
// User clicks "Start" → Nothing visibly moves → Confusing!

GOOD Example (Clear visual feedback):

// Earth rotates, sun position moves, day/night boundary shifts
// User clicks "Start" → Earth visibly spins → Satisfying!

8. Data Display

9. Presets

10. Accessibility

11. Performance

Common Bugs to Avoid

Bug Cause Solution
Reset doesn't work Button calls wrong function Ensure reset function resets ALL state variables
Canvas overlap on mobile Fixed positioning Use flex/grid with proper responsive classes
Simulation stuck Missing ended state Track ended separately from running
Button does nothing State logic error Clear state machine with defined transitions
Touch issues Small touch targets Min 44px touch targets, larger sliders

Output Format

Return ONLY the HTML document, no markdown fences or explanations.

CRITICAL: Output EXACTLY ONE HTML document. - Do NOT duplicate content - Do NOT include multiple <!DOCTYPE html> tags - The output must end with exactly one </html> tag

Object Positioning with UI Overlays

When calculating positions for simulation objects, account for UI overlays:

// BAD: Object overlaps with controls/HUD
const objectY = baseY - (value / maxValue) * canvas.height;

// GOOD: Reserve space for UI elements
const TOP_MARGIN = 100;    // Space for HUD/stats at top
const BOTTOM_MARGIN = 200; // Space for controls at bottom
const playableHeight = canvas.height - TOP_MARGIN - BOTTOM_MARGIN;
const objectY = baseY - BOTTOM_MARGIN - (value / maxValue) * playableHeight;

Quality Checklist (verify before output)