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 => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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
Name your agent
Based on . You'll get your own private copy to try and customize.