Agent Template

Physics Simulator Code Studio

Describe a physics experiment and generate a standalone, editable HTML canvas simulator that runs in a sandboxed preview.

physics simulation HTML canvas code generation
Jacoborodskak Oct 20, 2026 1 use

Preview Mode

This is a preview with sample data. The template uses placeholders like which will be replaced with actual agent data.

About This Template

Physics Simulator Code Studio is a browser-executable AI agent template built on AgentOp. It runs entirely in the browser using Python (via Pyodide) and can be deployed without a server — just download the generated HTML file and open it locally or host it anywhere.

Topics physics simulation HTML canvas code generation
Template Preview

Template Metadata

Slug
physics-simulator-code-studio
Created By
Jacoborodskak
Created
Oct 20, 2026
Usage Count
1

Tags

physics simulation HTML canvas code generation

Code Statistics

HTML Lines
52
CSS Lines
1
JS Lines
89
Python Lines
32

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ agent_name }}</title>
  <meta name="description" content="{{ description }}">
  <style>{{ css_code }}</style>
</head>
<body>
  <main class="app-shell">
    <header class="masthead">
      <div class="brand-mark" aria-hidden="true">⚛️</div>
      <div><p class="eyebrow">PHYSICS SIMULATOR CODE STUDIO</p><h1>{{ agent_name }}</h1><p class="subtitle">Turn an experiment idea into editable, runnable canvas code.</p></div>
    </header>

    <div class="studio-grid">
      <section class="panel brief-panel" aria-labelledby="brief-heading">
        <div class="panel-heading"><span class="step">01</span><div><h2 id="brief-heading">Design the experiment</h2><p>Choose a starting point and describe the motion.</p></div></div>
        <label for="scene-select">Physics scene</label>
        <select id="scene-select">
          <option value="pendulum">Pendulum</option>
          <option value="collisions">Ball collisions</option>
          <option value="projectile">Projectile motion</option>
          <option value="orbits">Orbital motion</option>
          <option value="custom">Custom experiment</option>
        </select>
        <label for="experiment-input">Experiment brief</label>
        <textarea id="experiment-input" rows="10" maxlength="1200" placeholder="Describe your pendulum's length, gravity, controls, and motion...">Build a pendulum with adjustable length and gravity. Show its path and a pause button.</textarea>
        <p class="field-hint">Include the forces, adjustable parameters, and anything you want displayed.</p>
        <button id="generate-btn" class="primary-btn" type="button" disabled>⚙️ Generate simulator code</button>
        <p id="status-message" class="status" role="status" aria-live="polite">Initializing the physics code studio…</p>
        <p id="error-message" class="error hidden" role="alert"></p>
      </section>

      <section class="panel preview-panel" aria-labelledby="preview-heading">
        <div class="panel-heading"><span class="step">02</span><div><h2 id="preview-heading">Test the simulator</h2><p>Run the generated experiment in an isolated preview.</p></div></div>
        <div id="empty-state" class="empty-state"><div class="empty-icon">🪐</div><h3>Your physics lab is ready</h3><p>Generate code to see your simulation here.</p><ul><li>Animate motion on an HTML canvas</li><li>Adjust parameters with on-page controls</li><li>Inspect and edit the complete source</li><li>Download one standalone HTML file</li></ul></div>
        <div id="results-container" class="results hidden"><iframe id="sim-preview" title="Sandboxed physics simulation preview" sandbox="allow-scripts" referrerpolicy="no-referrer"></iframe></div>
      </section>

      <section class="panel code-panel" aria-labelledby="code-heading">
        <div class="panel-heading"><span class="step">03</span><div><h2 id="code-heading">Edit the source</h2><p>Change the code, rerun it, or save the experiment.</p></div></div>
        <textarea id="code-output" aria-label="Editable physics simulator HTML source" spellcheck="false" placeholder="Your generated physics simulator HTML will appear here."></textarea>
        <div class="code-actions"><button id="run-btn" type="button" disabled>▶️ Run edited code</button><button id="copy-btn" type="button" disabled>📋 Copy code</button><button id="download-btn" type="button" disabled>💾 Download HTML</button></div>
      </section>
    </div>
  </main>
  <div id="loading-overlay" class="loading-overlay hidden" role="status" aria-live="polite"><div class="loading-card"><div class="spinner" aria-hidden="true"></div><strong>Building your physics simulator…</strong><span>Writing a standalone canvas experiment.</span></div></div>
  <script>{{ js_code }}</script>
</body>
</html>
:root{--bg:#0b1324;--surface:#141f34;--surface-raised:#1a2940;--line:#31425a;--text:#eef5ff;--muted:#a6b6cc;--accent:#60dfca;--accent-dark:#102d2d;--danger:#ffb7b6;--radius:18px;--shadow:0 16px 44px rgba(0,0,0,.2);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color-scheme:dark}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 80% 0%,#192e48 0%,var(--bg) 55%);color:var(--text);min-height:100vh}.hidden{display:none!important}.app-shell{max-width:1800px;margin:0 auto;padding:34px 26px 42px}.masthead{display:flex;align-items:center;gap:17px;margin-bottom:27px}.brand-mark{display:grid;place-items:center;width:58px;height:58px;border-radius:17px;background:#173e49;font-size:30px;flex:none}.eyebrow{margin:0 0 5px;color:var(--accent);font-size:.73rem;letter-spacing:.16em;font-weight:800}h1{font-size:clamp(1.65rem,3vw,2.3rem);margin:0;letter-spacing:-.035em}.subtitle,.panel-heading p{color:var(--muted);margin:5px 0 0;font-size:.92rem}.studio-grid{display:grid;grid-template-columns:minmax(270px,.9fr) minmax(400px,1.45fr) minmax(300px,1fr);gap:16px;align-items:stretch}.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:21px;min-width:0}.panel-heading{display:flex;gap:12px;align-items:start;margin-bottom:24px}.step{background:#214343;color:var(--accent);border-radius:9px;padding:7px 9px;font-size:.76rem;font-weight:800}.panel h2{margin:0;font-size:1.13rem}.panel label{display:block;font-weight:700;font-size:.85rem;margin:18px 0 9px}select,textarea{display:block;width:100%;border:1px solid var(--line);border-radius:12px;background:#0e1a2d;color:var(--text);padding:12px 13px;font:inherit}select{min-height:46px}textarea{resize:vertical;line-height:1.55}#experiment-input{min-height:200px}.field-hint{font-size:.78rem;line-height:1.5;color:var(--muted);margin:10px 0 19px}button{border:1px solid var(--line);border-radius:11px;background:var(--surface-raised);color:var(--text);padding:11px 14px;font:inherit;font-size:.84rem;font-weight:750;cursor:pointer;transition:background .2s,transform .2s}button:hover:not(:disabled){background:#27405b;transform:translateY(-1px)}button:disabled{opacity:.48;cursor:not-allowed;transform:none}.primary-btn{background:var(--accent);border-color:var(--accent);color:var(--accent-dark);width:100%;font-size:.94rem}.primary-btn:hover:not(:disabled){background:#8bf4e1}.status{color:var(--muted);font-size:.81rem;line-height:1.5;min-height:24px}.error{background:#492b38;border:1px solid #905267;color:#ffe0e0;border-radius:10px;padding:10px 12px;font-size:.85rem;line-height:1.5}select:focus-visible,textarea:focus-visible,button:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.preview-panel{display:flex;flex-direction:column}.empty-state{border:1px dashed #416079;border-radius:14px;background:#101f31;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:25px;color:var(--muted)}.empty-icon{font-size:42px}.empty-state h3{color:var(--text);margin:12px 0 0}.empty-state p{margin:9px 0}.empty-state ul{text-align:left;line-height:1.9;padding-left:20px;font-size:.86rem}.results{flex:1;min-height:490px;background:white;border-radius:12px;overflow:hidden}iframe{width:100%;height:100%;min-height:490px;border:0;background:white}.code-panel{display:flex;flex-direction:column}#code-output{flex:1;min-height:390px;font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;tab-size:2}.code-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.loading-overlay{position:fixed;inset:0;background:rgba(4,12,23,.72);display:grid;place-items:center;z-index:100;padding:20px}.loading-card{background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:32px;box-shadow:var(--shadow);display:flex;align-items:center;flex-direction:column;gap:11px;text-align:center}.loading-card span{color:var(--muted);font-size:.85rem}.spinner{width:35px;height:35px;border:3px solid #315267;border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:1200px){.studio-grid{grid-template-columns:minmax(270px,.85fr) minmax(380px,1.3fr)}.code-panel{grid-column:1/-1}#code-output{min-height:280px}}@media(max-width:768px){.app-shell{padding:22px 14px 32px}.studio-grid{grid-template-columns:1fr}.code-panel{grid-column:auto}.panel{padding:17px}.results,iframe{min-height:360px}.code-actions button{flex:1 1 140px}}
(() => {
  const $ = id => document.getElementById(id);
  const scene = $('scene-select');
  const brief = $('experiment-input');
  const source = $('code-output');
  const generate = $('generate-btn');
  const run = $('run-btn');
  const copy = $('copy-btn');
  const download = $('download-btn');
  const status = $('status-message');
  const error = $('error-message');
  const overlay = $('loading-overlay');
  const presets = {
    pendulum: 'Build a pendulum with adjustable length and gravity. Show its path and a pause button.',
    collisions: 'Simulate two balls colliding elastically inside a box. Let me adjust their masses and initial speeds.',
    projectile: 'Animate projectile motion with adjustable launch angle, speed, and gravity. Draw the trajectory.',
    orbits: 'Simulate a small body orbiting a central mass. Show a motion trail and adjustable gravity.',
    custom: ''
  };
  let busy = false;
  let ready = false;
  function escapeHtml(value) { return String(value).replace(/[&<>"']/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch])); }
  function showError(message) { error.innerHTML = escapeHtml(message); error.classList.remove('hidden'); }
  function clearError() { error.textContent = ''; error.classList.add('hidden'); }
  function isLocal() { return /local|wllama|browser/i.test(String(window.PROVIDER || '')); }
  function refresh() {
    ready = !!(window.pyodide && (window.pyodideReady || ready));
    const modelMissing = isLocal() && !(window.agentManager && window.agentManager.isLoaded);
    generate.disabled = busy || !ready || modelMissing;
    run.disabled = copy.disabled = download.disabled = busy || !source.value.trim();
    if (!busy) status.textContent = !ready ? 'Initializing the physics code studio…' : modelMissing ? 'Load a model from the bar above to generate simulator code.' : 'Ready to build your physics simulator.';
  }
  function setBusy(value) { busy = value; overlay.classList.toggle('hidden', !value); refresh(); }
  function previewCode(code) {
    if (!/<html[\s>]/i.test(code) || !/<script\b/i.test(code)) { showError('The source needs a complete HTML document with a script before it can run.'); return false; }
    $('sim-preview').srcdoc = code;
    $('empty-state').classList.add('hidden');
    $('results-container').classList.remove('hidden');
    return true;
  }
  document.addEventListener('pyodide-ready', () => { ready = true; refresh(); });
  if (window.pyodideReady && window.pyodide) ready = true;
  setInterval(refresh, 850);
  refresh();
  scene.addEventListener('change', () => { brief.value = presets[scene.value]; clearError(); });
  source.addEventListener('input', refresh);
  generate.addEventListener('click', async () => {
    const selected = scene.options[scene.selectedIndex].text;
    const request = brief.value.trim();
    clearError();
    if (!request) { showError('Describe the physics experiment before generating code.'); brief.focus(); return; }
    refresh();
    if (!ready) { showError('The Python runtime is still initializing. Please try again shortly.'); return; }
    if (isLocal() && !(window.agentManager && window.agentManager.isLoaded)) { showError('Load a model from the bar above before generating code.'); return; }
    setBusy(true);
    status.textContent = 'Generating your physics simulator…';
    try {
      window.pyodide.globals.set('user_query', 'Physics scene: ' + selected + '\nExperiment brief: ' + request);
      const result = String(await window.pyodide.runPythonAsync('await process_user_query(user_query)'));
      if (result.startsWith('ERROR:')) throw new Error(result.slice(6).trim());
      source.value = result;
      previewCode(result);
      status.textContent = 'Simulator generated. Edit the source and run it again whenever you like.';
    } catch (e) { showError('Could not generate the simulator: ' + (e && e.message ? e.message : String(e))); status.textContent = 'Generation failed; your experiment brief is still here.'; }
    finally { setBusy(false); }
  });
  run.addEventListener('click', () => {
    clearError();
    const code = source.value.trim();
    if (!code) { showError('Generate or paste physics simulator HTML before running it.'); return; }
    if (previewCode(code)) status.textContent = 'Running the current simulator source in the sandboxed preview.';
  });
  copy.addEventListener('click', async () => {
    clearError();
    const code = source.value;
    if (!code.trim()) { showError('There is no simulator code to copy yet.'); return; }
    try { await navigator.clipboard.writeText(code); status.textContent = 'Physics simulator source copied to the clipboard.'; }
    catch (_) { showError('Clipboard access is unavailable here. Select the source text and copy it manually.'); }
  });
  download.addEventListener('click', () => {
    clearError();
    const code = source.value;
    if (!code.trim()) { showError('There is no simulator code to download yet.'); return; }
    const url = URL.createObjectURL(new Blob([code], {type:'text/html;charset=utf-8'}));
    const link = document.createElement('a'); link.href = url; link.download = 'physics-' + scene.value + '-simulator.html'; document.body.appendChild(link); link.click(); link.remove();
    setTimeout(() => URL.revokeObjectURL(url), 60000);
    status.textContent = 'Physics simulator HTML downloaded.';
  });
})();
import re as _re

async def process_user_query(query: str) -> str:
    """Generate a standalone browser physics simulator from an experiment brief.

    Args:
        query: Physics scene and the user's experiment requirements.
    """
    brief = query.strip()[:1500]
    if not brief:
        return 'ERROR: Describe a physics experiment first.'
    prompt = (
        'Write ONE complete, standalone HTML document for this physics experiment. '
        'Output ONLY HTML code, starting with <!DOCTYPE html> and ending with </html>. '
        'Use inline CSS and vanilla JavaScript only; no packages, imports, CDNs, images, or network requests. '
        'Include a canvas, requestAnimationFrame animation, a clear physics update using delta time, '
        'and labeled working controls for relevant parameters plus pause and reset. '
        'Keep the simulation responsive and bound time steps for stability. '
        'Keep code compact. Implement the requested physics rather than describing it.\n\n'
        + brief
    )
    answer = str(await agentop_llm.generate(prompt, globals().get('TEMPLATE_SYSTEM_PROMPT', ''))).strip()
    fenced = _re.fullmatch(r'```(?:html)?\s*([\s\S]*?)\s*```', answer, _re.I)
    if fenced:
        answer = fenced.group(1).strip()
    start = _re.search(r'<!doctype\s+html\s*>|<html\b', answer, _re.I)
    end = _re.search(r'</html\s*>', answer, _re.I)
    if start and end and end.end() > start.start():
        answer = answer[start.start():end.end()]
    if not _re.search(r'<html\b', answer, _re.I) or not _re.search(r'<canvas\b', answer, _re.I) or not _re.search(r'<script\b', answer, _re.I) or not _re.search(r'</html\s*>', answer, _re.I):
        return 'ERROR: The model did not return a complete canvas simulator. Try a shorter, more specific experiment brief.'
    return answer