🤝 How to Contribute

Add your own simulation, fix a bug, or write an article. The full project runs on static files — no build tools, no npm required.

Performance

  • ≥ 30 FPS on integrated GPU (Intel UHD 620)
  • Use InstancedMesh for > 50 identical objects
  • requestAnimationFrame loop (no setInterval)
  • devicePixelRatio scaling for HiDPI displays

Responsive

  • Touch events: pinch, drag on mobile
  • Canvas resizes on window resize
  • Controls readable at 360px viewport width
  • Minimum touch target 44×44 px

Accessible

  • aria-label or role="img" on canvas
  • All sliders have aria-valuemin/max/now
  • Keyboard-navigable controls
  • Simulation info button (algorithm description)

SEO

  • Unique <title> and meta description
  • og:title, og:description, og:image
  • canonical link and hreflang
  • JSON-LD SoftwareApplication + BreadcrumbList
  1. Create the folder

    Add a new folder in the project root with the slug of your simulation (lowercase, hyphens). Copy the template file as your starting point:

    • Folder name = simulation ID, e.g. double-pendulum/
    • Single file: double-pendulum/index.html
    • No subdirectories, no external JS files, all code inline
  2. Use the HTML template

    Start from the minimal template below. It includes all required meta tags, the shared navbar/footer, and a canvas with correct ARIA attributes.

    HTML
    <!DOCTYPE html>
    <html lang="en" data-theme="dark">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="canonical" href="https://www.mysimulator.uk/your-sim/">
      <title>Your Simulation — 3D Browser Simulations</title>
      <meta name="description" content="Short description (≤ 160 chars).">
      <meta property="og:title" content="Your Simulation — 3D Browser Simulations">
      <meta property="og:description" content="Short description.">
      <meta property="og:image" content="https://www.mysimulator.uk/preview/your-sim.jpg">
      <meta property="og:url" content="https://www.mysimulator.uk/your-sim/">
      <meta property="og:type" content="website">
      <meta name="twitter:card" content="summary_large_image">
      <link rel="preconnect" href="https://cdn.jsdelivr.net">
      <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
      <link rel="stylesheet" href="../shared/theme.css">
      <link rel="stylesheet" href="../shared/components.css">
    </head>
    <body>
      <a href="#main-content" class="skip-link">Skip to content</a>
      <header id="navbar-root" data-base="../" aria-label="Main navigation"></header>
    
      <main id="main-content">
        <canvas id="canvas"
          role="img"
          aria-label="Interactive Your Simulation — use mouse to interact">
        </canvas>
        <!-- HUD injected by initSimHUD() -->
      </main>
    
      <footer id="footer-root" aria-label="Site footer"></footer>
    
      <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
      <script src="../shared/components.js"></script>
      <script>
        /* ── Simulation config for HUD ── */
        window.__SIM_INFO__ = {
          title: 'Your Simulation',
          category: 'physics',
          categoryPath: '../categories/physics/',
          description: 'What this simulation shows and the algorithm behind it.',
          controls: 'Mouse: drag to rotate | Scroll: zoom | R: reset',
          tags: ['Three.js', 'Your-tag'],
          relatedIds: ['fluid', 'nbody']
        };
    
        /* ── Three.js setup ── */
        const canvas = document.getElementById('canvas');
        const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.setSize(window.innerWidth, window.innerHeight);
    
        const scene  = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 5);
    
        /* ── Your simulation code ── */
        // TODO: physics, geometry, animate()
    
        function animate() {
          requestAnimationFrame(animate);
          // TODO: update physics
          renderer.render(scene, camera);
        }
        animate();
    
        /* ── Resize ── */
        window.addEventListener('resize', () => {
          camera.aspect = innerWidth / innerHeight;
          camera.updateProjectionMatrix();
          renderer.setSize(innerWidth, innerHeight);
        });
    
        /* ── Init HUD (FPS, back btn, fullscreen, info modal) ── */
        if (typeof initSimHUD === 'function') initSimHUD(window.__SIM_INFO__);
      </script>
    </body>
    </html>
  3. Register in simulations.json

    Add an entry to shared/data/simulations.json. All fields are required except thumbnail and articleId:

    JSON
    {
      "id": "your-sim",
      "title": "Your Simulation Name",
      "titleUk": "Ваша Симуляція",
      "emoji": "🔬",
      "category": "physics",
      "description": "One sentence that explains what this simulates.",
      "tags": ["Three.js", "GLSL", "Your-Algorithm"],
      "difficulty": 3,
      "status": "ready",
      "featured": false,
      "dateAdded": "2026-03-14",
      "thumbnail": "/preview/your-sim.jpg",
      "controls": "Mouse: drag | Scroll: zoom | R: reset",
      "algorithms": ["Algorithm Name"],
      "articleId": null,
      "fps": 60
    }
    • difficulty: 1 (trivial) → 5 (research-level)
    • status: "ready" | "wip" | "planned"
    • category: one of space physics nature life chaos math algorithms geometry chemistry sound
  4. Rebuild the search index

    After adding to simulations.json, regenerate the search index so your simulation appears in the global search:

    Shell
    node shared/scripts/build-search-index.js

    This reads simulations.json, articles.json, and categories.json and generates shared/data/search-index.json.

  5. Test locally

    Open the project via a local HTTP server (not file:// — Service Worker requires HTTP):

    Shell
    # Python (built-in)
    python -m http.server 8080
    
    # Node.js (npx, no install needed)
    npx serve .
    
    # VS Code: use "Live Server" extension
    • Check FPS counter — aim for ≥ 30 FPS on integrated GPU
    • Test on mobile viewport (Chrome DevTools → responsive mode)
    • Tab through all controls — every element must be reachable
    • Check the info modal (ⓘ button in HUD) shows correct text

Ideas for new simulations

Name Category Difficulty Algorithm
Double Pendulum Physics Easy Lagrangian mechanics, RK4
Electric Field Lines Physics Easy Coulomb's law, streamlines
Voronoi Diagram Math Easy Fortune's algorithm
Mandelbrot Set Math Medium GLSL fragment shader escape time
Heat Diffusion Physics Medium Finite differences, Crank-Nicolson
Quantum Harmonic Oscillator Physics Medium Schrödinger wavefunction
Reaction-Diffusion (Gray-Scott) Chemistry Medium PDE on GPU texture ping-pong
Cloth Tear Simulation Physics Hard Position-based dynamics, mesh splitting
SPH + Rigid Body Coupling Physics Hard SPH + Cannon-es coupling
Ray Marching Fractals Math Hard GLSL signed distance functions

📜 Code of Conduct

This project is open and welcoming to everyone. By participating you agree to follow these guidelines:

✅ Do

  • Be respectful and constructive in all discussions
  • Welcome newcomers regardless of skill level
  • Provide clear, reproducible bug reports
  • Keep pull requests focused on a single feature or fix
  • Write self-documenting code with meaningful variable names
  • Credit external algorithms and papers in code comments

🚫 Don't

  • Submit simulations with external tracking or analytics
  • Copy closed-source shader code without a compatible licence
  • Add build tools, bundlers, or npm dependencies
  • Introduce breaking changes to shared/components.js without discussion
  • Use offensive, hateful, or discriminatory language

Violations may result in your contribution being declined or your access removed. For major concerns, open a GitHub Issue.

🌟 Contributors

Thank you to everyone who has helped build this project — simulations, bug reports, articles and design feedback are all valued equally.

Simulation Authors

Built and contributed WebGL simulations to the library

Article Writers

Wrote the maths and physics explainers in /content/

Bug Reporters

Filed detailed issues that led to real improvements

Translators

Helped bring the Ukrainian version to life

Want to see your name here? Follow the steps above and open a pull request. All contributors are listed in CONTRIBUTING.md.

🔌 Embed API

Every simulation on this site can be embedded in any web page as a self-contained <iframe>. No API key, no account — just copy the snippet and paste it into your HTML.

Basic iframe

Replace fluid with the slug of any simulation (e.g. rain, galaxy, double-slit).

HTML
<iframe
  src="https://www.mysimulator.uk/fluid/"
  title="Fluid Simulation — 3D Browser Simulations"
  width="800"
  height="600"
  loading="lazy"
  allowfullscreen
  sandbox="allow-scripts allow-same-origin">
</iframe>

URL parameters

Append query parameters to customise the embedded simulation:

Parameter Values Description
?dark=1 0 / 1 Force light (0) or dark (1) theme regardless of the visitor's system preference
?ui=0 0 / 1 Hide (0) or show (1) the HUD controls overlay
?autoplay=1 0 / 1 Start the simulation immediately (1) without waiting for a user click

Responsive wrapper

Use a CSS aspect-ratio container so the iframe scales correctly on any screen width:

HTML
<!-- Responsive 4:3 embed container -->
<div style="position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;border-radius:8px;">
  <iframe
    src="https://www.mysimulator.uk/fluid/"
    title="Fluid Simulation — 3D Browser Simulations"
    style="position:absolute;inset:0;width:100%;height:100%;border:0;"
    loading="lazy"
    allowfullscreen
    sandbox="allow-scripts allow-same-origin">
  </iframe>
</div>

The sandbox="allow-scripts allow-same-origin" attribute is strongly recommended — it prevents the embedded page from navigating the parent frame, opening popups, or accessing cookies outside its own origin.

Ready to build something?

The project lives in a flat folder structure — no build step, no dependencies. Clone it, add a folder, and open it in a browser.