Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

281 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Symbulator — browser build

The whole simulator, running as static files. There is no server: Python, SymPy and the symbulator package all run inside the visitor's browser via Pyodide (CPython compiled to WebAssembly). Solving happens in their tab, not on your host.

Two consequences worth having:

  • It uploads anywhere. No "Setup Python App", no subprocess permissions, no memory limits, no PHP-versus-Python question — any host that serves files will do, cPanel included.
  • It works offline and installs. A service worker caches everything on the first visit, so afterwards it runs with no internet, and the page offers an Install bar to give it a desktop or home-screen icon (see the install bar notes in CLAUDE.md).

Putting it online

Upload the whole folder to your web root (or a subfolder) and visit index.html. That's the entire deployment.

Two server details matter:

  1. Serve .wasm as application/wasm. Most hosts already do. If the page reports that the maths engine could not start, this is the usual reason — add to .htaccess:

    AddTypeapplication/wasm .wasm
    AddTypeapplication/json .webmanifest
  2. HTTPS is required for offline mode. Service workers only run on https:// (or localhost). Without it the site still works, it just won't cache or install. Both of your domains have certificates, so this is automatic.

Enabling gzip/deflate is worth it: the first visit transfers about 17 MB compressed rather than 25 MB raw.

Running it locally

You cannot just double-click index.html — browsers block WebAssembly loading from file:// URLs ("Cross origin requests are only supported for http, https"). Serve the folder over HTTP instead:

python -m http.server 8000

then open http://localhost:8000. For everyday offline use, though, prefer installing the hosted version as an app: visit the site once and use the browser's Install option. That needs no Python at all.

What the visitor experiences

momentwhat happens
page appearsinstantly — the form is usable straight away
first ~8 sPython, SymPy and symbulator load in the background, with a small notice; typing a circuit takes about this long anyway
every solve after that20–50 ms for DC, a few hundred ms for AC or transient — faster than the server version, since nothing crosses the network
second visitno download (cached); the ~8 s warm-up runs again, still in the background
offlineidentical, once it has been visited once

Files

filepurpose
index.htmlthe entire interface, and the code that boots Python — generated, do not hand-edit
build_local.pyregenerates index.html from the server version's template
build_zip.pyassembles the downloadable ZIP, and verifies the icons, cache list and head links before writing it
CLAUDE.mdhow all three site variants fit together — read this first
README.txtthe readme that ships inside the ZIP, for end users (this file is for developers)
start.bat, start.sh, start.commandlaunchers shipped in the ZIP; they start python -m http.server and open a browser
bridge.pythin adapter: JSON in, JSON out
symbulator_ui.pyshared with the Flask build — all solving, formatting, units, ordering
circuitbook.pythe [Name] circuit-file format, also shared
examples/the built-in example files, copied from ../server by build_local.py; each carries its own title:
sw.jsservice worker: offline caching. Bump CACHE_VERSION when you change app files, or returning visitors keep the old build
manifest.webmanifestwhat makes it installable. Its icons must include a 192px and a 512px entry, or Chrome on Android will not offer to install
icon.pngmaster artwork; the favicon*, apple-touch-icon.png and icon-*.png files are generated from it. Regenerate them if it changes
LICENSEMIT
vendor/Pyodide runtime plus the sympy, mpmath and symbulator wheels
static/mathjax/typeset maths, served locally

symbulator_ui.py and circuitbook.py are byte-identical to the ones in the server project — deliberately, so the two front ends can never drift apart. If you change one, copy it to the other.

Changing the interface

There is only one interface, and it lives in the server project, at ../server/templates/index.html. This build's index.html is produced from it:

python3 build_local.py # regenerate index.html
python3 build_local.py --check # exit 1 if it is stale

Every substitution the script makes asserts that it matched, so a change to the template that breaks the transformation fails loudly instead of quietly dropping a feature — which is exactly how an earlier build shipped with no service worker registered. Edit the template, re-run the script, bump CACHE_VERSION in sw.js.

Updating the symbulator package

The wheel in vendor/ is pinned. To move to a newer release:

pip download symbulator==X.Y.Z --no-deps -d vendor/

then update the filename in index.html (the loadPackage line) and in sw.js (the ASSETS list), and bump CACHE_VERSION.

About

Offline browser build of Symbulator Py, running client-side via Pyodide

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages