From 660c30905f827c4ac3bbad3d6285cddb89ac8e09 Mon Sep 17 00:00:00 2001
From: Steve Rumsby <steve@rumsby.org.uk>
Date: Tue, 27 Feb 2024 10:13:51 +0000
Subject: [PATCH] Initial commit

---
 .github/workflows/deploy.yml                  |  48 ++
 .gitignore                                    | 116 +++
 .nojekyll                                     |   1 +
 README.md                                     |  29 +
 content/data/Museums_in_DC.geojson            |   1 +
 content/data/bar.vl.json                      |  54 ++
 content/data/fasta-example.fasta              |   8 +
 content/data/iris.csv                         | 151 ++++
 content/data/matplotlib.png                   | Bin 0 -> 26389 bytes
 content/javascript.ipynb                      |  86 +++
 content/p5.ipynb                              | 150 ++++
 content/pyodide/altair.ipynb                  | 231 ++++++
 content/pyodide/folium.ipynb                  | 154 ++++
 content/pyodide/interactive-widgets.ipynb     | 268 +++++++
 content/pyodide/ipycanvas.ipynb               | 178 +++++
 content/pyodide/ipyleaflet.ipynb              | 259 +++++++
 content/pyodide/matplotlib.ipynb              | 113 +++
 content/pyodide/plotly.ipynb                  | 158 ++++
 content/pyodide/pyb2d/0_tutorial.ipynb        | 649 ++++++++++++++++
 content/pyodide/pyb2d/color_mixing.ipynb      | 123 +++
 .../pyodide/pyb2d/games/angry_shapes.ipynb    | 419 ++++++++++
 content/pyodide/pyb2d/games/billiard.ipynb    | 299 ++++++++
 content/pyodide/pyb2d/games/goo.ipynb         | 575 ++++++++++++++
 content/pyodide/pyb2d/games/rocket.ipynb      | 282 +++++++
 content/pyodide/pyb2d/gauss_machine.ipynb     | 128 ++++
 content/pyodide/pyb2d/newtons_cradle.ipynb    | 130 ++++
 content/pyodide/renderers.ipynb               |   1 +
 content/python.ipynb                          | 721 ++++++++++++++++++
 content/xeus-lua/canvas.ipynb                 |  29 +
 content/xeus-lua/game-of-life.ipynb           |  92 +++
 content/xeus-lua/widgets.ipynb                | 632 +++++++++++++++
 content/xeus-sqlite/simple-operations.ipynb   | 392 ++++++++++
 repl/jupyter-lite.json                        |  11 +
 requirements.txt                              |  49 ++
 34 files changed, 6537 insertions(+)
 create mode 100644 .github/workflows/deploy.yml
 create mode 100644 .gitignore
 create mode 100644 .nojekyll
 create mode 100644 README.md
 create mode 100644 content/data/Museums_in_DC.geojson
 create mode 100644 content/data/bar.vl.json
 create mode 100644 content/data/fasta-example.fasta
 create mode 100644 content/data/iris.csv
 create mode 100644 content/data/matplotlib.png
 create mode 100644 content/javascript.ipynb
 create mode 100644 content/p5.ipynb
 create mode 100644 content/pyodide/altair.ipynb
 create mode 100644 content/pyodide/folium.ipynb
 create mode 100644 content/pyodide/interactive-widgets.ipynb
 create mode 100644 content/pyodide/ipycanvas.ipynb
 create mode 100644 content/pyodide/ipyleaflet.ipynb
 create mode 100644 content/pyodide/matplotlib.ipynb
 create mode 100644 content/pyodide/plotly.ipynb
 create mode 100644 content/pyodide/pyb2d/0_tutorial.ipynb
 create mode 100644 content/pyodide/pyb2d/color_mixing.ipynb
 create mode 100644 content/pyodide/pyb2d/games/angry_shapes.ipynb
 create mode 100644 content/pyodide/pyb2d/games/billiard.ipynb
 create mode 100644 content/pyodide/pyb2d/games/goo.ipynb
 create mode 100644 content/pyodide/pyb2d/games/rocket.ipynb
 create mode 100644 content/pyodide/pyb2d/gauss_machine.ipynb
 create mode 100644 content/pyodide/pyb2d/newtons_cradle.ipynb
 create mode 100644 content/pyodide/renderers.ipynb
 create mode 100644 content/python.ipynb
 create mode 100644 content/xeus-lua/canvas.ipynb
 create mode 100644 content/xeus-lua/game-of-life.ipynb
 create mode 100644 content/xeus-lua/widgets.ipynb
 create mode 100644 content/xeus-sqlite/simple-operations.ipynb
 create mode 100644 repl/jupyter-lite.json
 create mode 100644 requirements.txt

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..497ee55
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,48 @@
+name: Build and Deploy
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - '*'
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.11'
+      - name: Install the dependencies
+        run: |
+          python -m pip install -r requirements.txt
+      - name: Build the JupyterLite site
+        run: |
+          cp README.md content
+          jupyter lite build --contents content --output-dir dist
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v3
+        with:
+          path: ./dist
+
+  deploy:
+    needs: build
+    if: github.ref == 'refs/heads/main'
+    permissions:
+      pages: write
+      id-token: write
+
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+
+    runs-on: ubuntu-latest
+    steps:
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b661992
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,116 @@
+*.bundle.*
+lib/
+node_modules/
+.yarn-packages/
+*.egg-info/
+.ipynb_checkpoints
+*.tsbuildinfo
+
+# Created by https://www.gitignore.io/api/python
+# Edit at https://www.gitignore.io/?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# OS X stuff
+*.DS_Store
+
+# End of https://www.gitignore.io/api/python
+
+# jupyterlite
+*.doit.db
+_output
diff --git a/.nojekyll b/.nojekyll
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/.nojekyll
@@ -0,0 +1 @@
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f8d0424
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# JupyterLite Demo
+
+[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jupyterlite.github.io/demo)
+
+JupyterLite deployed as a static site to GitHub Pages, for demo purposes.
+
+## ✨ Try it in your browser ✨
+
+➡️ **https://jupyterlite.github.io/demo**
+
+![github-pages](https://user-images.githubusercontent.com/591645/120649478-18258400-c47d-11eb-80e5-185e52ff2702.gif)
+
+## Requirements
+
+JupyterLite is being tested against modern web browsers:
+
+- Firefox 90+
+- Chromium 89+
+
+## Deploy your JupyterLite website on GitHub Pages
+
+Check out the guide on the JupyterLite documentation: https://jupyterlite.readthedocs.io/en/latest/quickstart/deploy.html
+
+## Further Information and Updates
+
+For more info, keep an eye on the JupyterLite documentation:
+
+- How-to Guides: https://jupyterlite.readthedocs.io/en/latest/howto/index.html
+- Reference: https://jupyterlite.readthedocs.io/en/latest/reference/index.html
diff --git a/content/data/Museums_in_DC.geojson b/content/data/Museums_in_DC.geojson
new file mode 100644
index 0000000..a20a9e6
--- /dev/null
+++ b/content/data/Museums_in_DC.geojson
@@ -0,0 +1 @@
+{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"OBJECTID":1,"ADDRESS":"716 MONROE STREET NE","NAME":"AMERICAN POETRY MUSEUM","ADDRESS_ID":309744,"LEGALNAME":"HERITAGE US","ALTNAME":"AMERICAN POETRY MUSEUM","WEBURL":" http://americanpoetrymuseum.org/"},"geometry":{"type":"Point","coordinates":[-76.995003703568,38.9328428790235]}},{"type":"Feature","properties":{"OBJECTID":2,"ADDRESS":"719 6TH STREET NW","NAME":"GERMAN-AMERICAN HERITAGE MUSEUM","ADDRESS_ID":238949,"LEGALNAME":"CORCORAN GALLERY OF ART","ALTNAME":" ","WEBURL":"http://gahmusa.org/"},"geometry":{"type":"Point","coordinates":[-77.01958878310639,38.89911061096782]}},{"type":"Feature","properties":{"OBJECTID":3,"ADDRESS":"1307 NEW HAMPSHIRE AVENUE NW","NAME":"HEURICH HOUSE FOUNDATION","ADDRESS_ID":241060,"LEGALNAME":"U.S. DEPARTMENT OF THE INTERIOR MUSEUM","ALTNAME":"HEURICH HOUSE FOUNDATION","WEBURL":"HTTP://HEURICHHOUSE.ORG"},"geometry":{"type":"Point","coordinates":[-77.04460619923155,38.908030206509885]}},{"type":"Feature","properties":{"OBJECTID":4,"ADDRESS":"950 INDEPENDENCE AVENUE SW","NAME":"NATIONAL MUSEUM OF AFRICAN ART","ADDRESS_ID":293262,"LEGALNAME":"BUILDING PRESERVATION FOUNDATION","ALTNAME":"NATIONAL MUSEUM OF AFRICAN ART","WEBURL":"HTTP://AFRICA.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.02550917725944,38.88796214949963]}},{"type":"Feature","properties":{"OBJECTID":5,"ADDRESS":"740 JACKSON PLACE NW","NAME":"THE WHITE HOUSE ENDOWMENT TRUST","ADDRESS_ID":218748,"LEGALNAME":"NATIONAL BUILDING MUSEUM","ALTNAME":"THE WHITE HOUSE ENDOWMENT TRUST","WEBURL":"HTTP://WWW.WHITEHOUSEHISTORY.ORG"},"geometry":{"type":"Point","coordinates":[-77.03820629325264,38.899842529027275]}},{"type":"Feature","properties":{"OBJECTID":6,"ADDRESS":"921 PENNSYLVANIA AVENUE SE","NAME":"OLD NAVAL HOSPITAL FOUNDATION","ADDRESS_ID":82564,"LEGALNAME":"JEWISH WAR VETERANS NATIONAL MEMORIAL MUSEUM ARCHIVES AND LI","ALTNAME":"OLD NAVAL HOSPITAL FOUNDATION","WEBURL":"http://hillcenterdc.org/home/"},"geometry":{"type":"Point","coordinates":[-76.99314290714912,38.8829885933721]}},{"type":"Feature","properties":{"OBJECTID":7,"ADDRESS":"2201 C STREET NW","NAME":"DIPLOMATIC ROOMS FOUNDATION","ADDRESS_ID":243360,"LEGALNAME":"NATIONAL PLASTICS MUSEUM INC","ALTNAME":"DIPLOMATIC ROOMS FOUNDATION","WEBURL":"https://diplomaticrooms.state.gov/home.aspx"},"geometry":{"type":"Point","coordinates":[-77.04831079505838,38.894135140073566]}},{"type":"Feature","properties":{"OBJECTID":8,"ADDRESS":"4400 MASSACHUSETTS AVENUE NW","NAME":"AMERICAN UNIVERSITY MUSEUM AT THE KATZEN ARTS CENTER","ADDRESS_ID":223994,"LEGALNAME":"VERNISSAGE FOUNDATION","ALTNAME":"AMERICAN UNIVERSITY MUSEUM AT THE KATZEN ARTS CENTER","WEBURL":"HTTP://WWW.AMERICAN.EDU/CAS/MUSEUM/"},"geometry":{"type":"Point","coordinates":[-77.08841712551974,38.9390892139132]}},{"type":"Feature","properties":{"OBJECTID":9,"ADDRESS":"2320 S STREET NW","NAME":"TEXTILE MUSEUM","ADDRESS_ID":243164,"LEGALNAME":"SMITHSONIAN INSTITUTION, S. DILLON RIPLEY CENTER","ALTNAME":"TEXTILE MUSEUM","WEBURL":"HTTP://WWW.TEXTILEMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-77.0464284034822,38.89880233850966]}},{"type":"Feature","properties":{"OBJECTID":10,"ADDRESS":"1145 17TH STREET NW","NAME":"NATIONAL GEOGRAPHIC MUSEUM","ADDRESS_ID":290192,"LEGALNAME":"CAPITOL HILL RESTORATION SOCIETY INC","ALTNAME":" ","WEBURL":"HTTP://WWW.NATIONALGEOGRAPHIC.COM"},"geometry":{"type":"Point","coordinates":[-77.03815544194862,38.90519711304962]}},{"type":"Feature","properties":{"OBJECTID":11,"ADDRESS":"3501 NEW YORK AVENUE NE","NAME":"THE NATIONAL BONSAI & PENJING MUSEUM","ADDRESS_ID":293238,"LEGALNAME":"NATIONAL BONSAI FOUNDATION","ALTNAME":" ","WEBURL":"https://www.bonsai-nbf.org/contact-us/"},"geometry":{"type":"Point","coordinates":[-76.96989266812075,38.91241055669072]}},{"type":"Feature","properties":{"OBJECTID":12,"ADDRESS":"2020 O STREET NW","NAME":"O STREET MUSEUM","ADDRESS_ID":243057,"LEGALNAME":"LEPIDOPTERISTS SOCIETY","ALTNAME":" ","WEBURL":"http://www.omuseum.org/museum/"},"geometry":{"type":"Point","coordinates":[-77.04592748104784,38.90839101941751]}},{"type":"Feature","properties":{"OBJECTID":13,"ADDRESS":"2101 CONSTITUTION AVENUE NW","NAME":"NATIONAL ACADEMY OF SCIENCES","ADDRESS_ID":242716,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATURAL HISTORY MUSEUM","ALTNAME":"NATIONAL ACADEMY OF SCIENCES","WEBURL":"WWW.NATIONALACADEMIES.ORG/NAS/ARTS"},"geometry":{"type":"Point","coordinates":[-77.0476448925699,38.89296693766957]}},{"type":"Feature","properties":{"OBJECTID":14,"ADDRESS":"2401 FOXHALL ROAD NW","NAME":"KREEGER MUSEUM","ADDRESS_ID":271251,"LEGALNAME":"CONGRESSIONAL CEMETERY","ALTNAME":"KREEGER MUSEUM","WEBURL":"HTTP://WWW.KREEGERMUSEUM.ORG/"},"geometry":{"type":"Point","coordinates":[-77.08878098790044,38.92191197499568]}},{"type":"Feature","properties":{"OBJECTID":15,"ADDRESS":"1250 NEW YORK AVENUE NW","NAME":"THE NATIONAL MUSEUM OF WOMEN IN THE ART","ADDRESS_ID":279010,"LEGALNAME":"NATIONAL MUSEUM OF HEALTH AND MEDICINE","ALTNAME":"THE NATIONAL MUSEUM OF WOMEN IN THE ART","WEBURL":"HTTP://WWW.NMWA.ORG"},"geometry":{"type":"Point","coordinates":[-77.029163689541,38.90005647268176]}},{"type":"Feature","properties":{"OBJECTID":16,"ADDRESS":"900 JEFFERSON DRIVE SW","NAME":"ARTS AND INDUSTRIES BUILDING","ADDRESS_ID":293260,"LEGALNAME":"ANACOSTIA COMMUNITY MUSEUM","ALTNAME":" ","WEBURL":"http://www.si.edu/Museums/arts-and-industries-building"},"geometry":{"type":"Point","coordinates":[-77.02446647929001,38.888201004559114]}},{"type":"Feature","properties":{"OBJECTID":17,"ADDRESS":"736 SICARD STREET SE","NAME":"NATIONAL MUSEUM OF UNITED STATES NAVY","ADDRESS_ID":311896,"LEGALNAME":"BLACK SPORTS LEGENDS FOUNDATION","ALTNAME":"NATIONAL MUSEUM OF UNITED STATES NAVY","WEBURL":"http://www.history.navy.mil/museums/NationalMuseum/org8-1.htm"},"geometry":{"type":"Point","coordinates":[-76.99526950368147,38.87303084860059]}},{"type":"Feature","properties":{"OBJECTID":18,"ADDRESS":"500 17TH STREET NW","NAME":"CORCORAN GALLERY OF ART","ADDRESS_ID":279802,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL ZOOLOGICAL PARK","ALTNAME":"CORCORAN GALLERY OF ART","WEBURL":"http://www.corcoran.org/"},"geometry":{"type":"Point","coordinates":[-77.0397427304576,38.895854463821884]}},{"type":"Feature","properties":{"OBJECTID":19,"ADDRESS":"2017 I STREET NW","NAME":"THE ARTS CLUB OF WASHINGTON","ADDRESS_ID":285527,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","ALTNAME":"THE ARTS CLUB OF WASHINGTON","WEBURL":"HTTP://WWW.ARTSCLUBOFWASHINGTON.ORG"},"geometry":{"type":"Point","coordinates":[-77.04573426864144,38.90157618582308]}},{"type":"Feature","properties":{"OBJECTID":20,"ADDRESS":"701 3RD STREET NW","NAME":"LILLIAN AND ALBERT SMALL JEWISH MUSEUM","ADDRESS_ID":293253,"LEGALNAME":"LILLIAN AND ALBERT SMALL JEWISH MUSEUM","ALTNAME":" ","WEBURL":"http://www.jhsgw.org/"},"geometry":{"type":"Point","coordinates":[-77.01493675564363,38.89857205791096]}},{"type":"Feature","properties":{"OBJECTID":21,"ADDRESS":"320 A STREET NE","NAME":"FREDERICK DOUGLASS MUSEUM","ADDRESS_ID":38979,"LEGALNAME":"COSMOS CLUB HISTORIC PRESERVATION FOUNDATION","ALTNAME":" ","WEBURL":"http://www3.nahc.org/fd/"},"geometry":{"type":"Point","coordinates":[-77.00110470253333,38.891131915241964]}},{"type":"Feature","properties":{"OBJECTID":22,"ADDRESS":"1334 G STREET NW","NAME":"ARMENIAN GENOCIDE MUSEUM AND MEMORIAL","ADDRESS_ID":240658,"LEGALNAME":"GERMAN-AMERICAN HERITAGE MUSEUM","ALTNAME":"ARMENIAN GENOCIDE MUSEUM AND MEMORIAL","WEBURL":"http://www.armeniangenocidemuseum.org/"},"geometry":{"type":"Point","coordinates":[-77.03108432435003,38.89804891426683]}},{"type":"Feature","properties":{"OBJECTID":23,"ADDRESS":"1799 NEW YORK AVENUE NW","NAME":"OCTAGON MUSEUM","ADDRESS_ID":218490,"LEGALNAME":"AMERICAN RED CROSS MUSEUM","ALTNAME":" ","WEBURL":"HTTP://WWW.THEOCTAGON.ORG"},"geometry":{"type":"Point","coordinates":[-77.04141820048949,38.89635375607101]}},{"type":"Feature","properties":{"OBJECTID":24,"ADDRESS":"1901 FORT PLACE SE","NAME":"ANACOSTIA COMMUNITY MUSEUM","ADDRESS_ID":286524,"LEGALNAME":"FAUNA & FLORA INTERNATIONAL INC","ALTNAME":"ANACOSTIA COMMUNITY MUSEUM","WEBURL":"HTTP://ANACOSTIA.SI.EDU"},"geometry":{"type":"Point","coordinates":[-76.97678467186984,38.8565826636904]}},{"type":"Feature","properties":{"OBJECTID":25,"ADDRESS":"2312 CALIFORNIA STREET NW","NAME":"NATIONAL MUSEUM OF THE JEWISH PEOPLE","ADDRESS_ID":234961,"LEGALNAME":"GREENSEED COMMUNITY GARDEN LAND TRUST","ALTNAME":" ","WEBURL":"http://www.nsideas.com/archive/nmjh/"},"geometry":{"type":"Point","coordinates":[-77.05118108814123,38.91537084189858]}},{"type":"Feature","properties":{"OBJECTID":26,"ADDRESS":"430 17TH STREET NW","NAME":"AMERICAN RED CROSS MUSEUM","ADDRESS_ID":300987,"LEGALNAME":"DOUBLE M MANAGEMENT","ALTNAME":"AMERICAN RED CROSS MUSEUM","WEBURL":"http://www.redcross.org/"},"geometry":{"type":"Point","coordinates":[-77.04020705622152,38.89482654014118]}},{"type":"Feature","properties":{"OBJECTID":27,"ADDRESS":"1600 21ST STREET NW","NAME":"THE PHILLIPS COLLECTION","ADDRESS_ID":243333,"LEGALNAME":"SMITHSONIAN INSTITUTION, RENWICK GALLERY","ALTNAME":"THE PHILLIPS COLLECTION","WEBURL":"HTTP://WWW.PHILLIPSCOLLECTION.ORG"},"geometry":{"type":"Point","coordinates":[-77.04685454590388,38.91150979086159]}},{"type":"Feature","properties":{"OBJECTID":28,"ADDRESS":"800 F STREET NW","NAME":"INTERNATIONAL SPY MUSEUM","ADDRESS_ID":238378,"LEGALNAME":"CONFEDERATE MEMORIAL HALL ASSOCIATION","ALTNAME":"INTERNATIONAL SPY MUSEUM","WEBURL":"HTTP://WWW.SPYMUSEUM.ORG/"},"geometry":{"type":"Point","coordinates":[-77.02328618491306,38.896986480912865]}},{"type":"Feature","properties":{"OBJECTID":29,"ADDRESS":"100 RAOUL WALLENBERG PLACE SW","NAME":"UNITED STATES HOLOCAUST MEMORIAL MUSEUM","ADDRESS_ID":293186,"LEGALNAME":"NATIONAL MUSIC CENTER AND MUSEUM FOUNDATION","ALTNAME":"UNITED STATES HOLOCAUST MEMORIAL MUSEUM","WEBURL":"HTTP://WWW.USHMM.ORG"},"geometry":{"type":"Point","coordinates":[-77.03268853739414,38.88668873773371]}},{"type":"Feature","properties":{"OBJECTID":30,"ADDRESS":"801 K STREET NW","NAME":"HISTORICAL SOCIETY OF WASHINGTON DC","ADDRESS_ID":238956,"LEGALNAME":"Historical Society of Washington, D.C","ALTNAME":" ","WEBURL":"http://www.dchistory.org/"},"geometry":{"type":"Point","coordinates":[-77.02294505078932,38.90262956584554]}},{"type":"Feature","properties":{"OBJECTID":31,"ADDRESS":"1849 C STREET NW","NAME":"INTERIOR MUSEUM","ADDRESS_ID":293214,"LEGALNAME":"VICE PRESIDENTS RESIDENCE FOUNDATION","ALTNAME":"INTERIOR MUSEUM","WEBURL":"HTTP://WWW.DOI.GOV/INTERIORMUSEUM"},"geometry":{"type":"Point","coordinates":[-77.04260256434321,38.89445283458921]}},{"type":"Feature","properties":{"OBJECTID":32,"ADDRESS":"4155 LINNEAN AVENUE NW","NAME":"HILLWOOD MUSEUM & GARDENS","ADDRESS_ID":284839,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL GALLERY OF ART","ALTNAME":"HILLWOOD MUSEUM & GARDENS","WEBURL":"WWW.HILLWOODMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-77.0526196505072,38.94364171194315]}},{"type":"Feature","properties":{"OBJECTID":33,"ADDRESS":"1318 VERMONT AVENUE NW","NAME":"BETHUNE MEMORIAL MUSEUM","ADDRESS_ID":225385,"LEGALNAME":"NATIONAL MUSEUM OF WOMEN IN THE ARTS INC","ALTNAME":" ","WEBURL":"http://www.nps.gov/mamc/index.htm"},"geometry":{"type":"Point","coordinates":[-77.03086564182146,38.90817580546652]}},{"type":"Feature","properties":{"OBJECTID":34,"ADDRESS":"1500 MASSACHUSETTS AVENUE NW","NAME":"NATIONAL MUSEUM OF CATHOLIC ART AND LIBRARY","ADDRESS_ID":242324,"LEGALNAME":"KREEGER MUSEUM","ALTNAME":" ","WEBURL":"http://nmcal.org/nmcah_exhibition_in_washington.html"},"geometry":{"type":"Point","coordinates":[-77.03551120800971,38.90651019329394]}},{"type":"Feature","properties":{"OBJECTID":35,"ADDRESS":"1 MASSACHUSETTS AVENUE NW","NAME":"NATIONAL GUARD MEMORIAL MUSEUM","ADDRESS_ID":238009,"LEGALNAME":"CARL SCHMITT FOUNDATION INC","ALTNAME":" ","WEBURL":"HTTP://WWW.NGEF.ORG"},"geometry":{"type":"Point","coordinates":[-77.00956143652462,38.89812580681995]}},{"type":"Feature","properties":{"OBJECTID":36,"ADDRESS":"1811 R STREET NW","NAME":"NATIONAL MUSEUM OF AMERICAN JEWISH MILITARY HISTORY","ADDRESS_ID":243292,"LEGALNAME":"CITY TAVERN PRESERVATION FOUNDATION","ALTNAME":"JEWISH WAR VETERANS NATIONAL MEMORIAL MUSEUM ARCHIVES AND LIBRARY","WEBURL":"http://www.nmajmh.org/"},"geometry":{"type":"Point","coordinates":[-77.04211577477285,38.91282059721026]}},{"type":"Feature","properties":{"OBJECTID":37,"ADDRESS":"3900 HAREWOOD ROAD NE","NAME":"POPE JOHN PAUL II CULTURAL CENTER","ADDRESS_ID":288031,"LEGALNAME":"AMERICAN POETRY MUSEUM","ALTNAME":" ","WEBURL":"HTTP://WWW.JP2CC.ORG"},"geometry":{"type":"Point","coordinates":[-77.00466710351098,38.93776654366721]}},{"type":"Feature","properties":{"OBJECTID":38,"ADDRESS":"700 PENNSYLVANIA AVENUE NW","NAME":"NATIONAL ARCHIVES MUSEUM","ADDRESS_ID":293251,"LEGALNAME":"PHILLIPS COLLECTION","ALTNAME":"NATIONAL ARCHIVES MUSEUM","WEBURL":"https://www.archives.gov/dc-metro/washington/"},"geometry":{"type":"Point","coordinates":[-77.0228592459719,38.89285370583677]}},{"type":"Feature","properties":{"OBJECTID":39,"ADDRESS":"201 18TH STREET NW","NAME":"ART MUSEUM OF THE AMERICAS","ADDRESS_ID":294191,"LEGALNAME":"Art Museum of the Americas","ALTNAME":" ","WEBURL":"http://www.museum.oas.org/"},"geometry":{"type":"Point","coordinates":[-77.04147388756545,38.892799844291474]}},{"type":"Feature","properties":{"OBJECTID":40,"ADDRESS":"9 HILLYER COURT NW","NAME":"INTERNATIONAL ARTS & ARTISTS","ADDRESS_ID":279975,"LEGALNAME":"THE INTERNATIONAL SPY MUSEUM","ALTNAME":"INTERNATIONAL ARTS & ARTISTS","WEBURL":"WWW.ARTSANDARTISTS.ORG"},"geometry":{"type":"Point","coordinates":[-77.04730884101534,38.91222144699389]}},{"type":"Feature","properties":{"OBJECTID":41,"ADDRESS":"2 MASSACHUSETTS AVENUE NE","NAME":"NATIONAL POSTAL MUSEUM","ADDRESS_ID":293217,"LEGALNAME":"BEAD SOCIETY OF GREATER WASHINGTON","ALTNAME":"NATIONAL POSTAL MUSEUM","WEBURL":"HTTP://POSTALMUSEUM.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.00819124512859,38.8981463599396]}},{"type":"Feature","properties":{"OBJECTID":42,"ADDRESS":"1519 MONROE STREET NW","NAME":"POWHATAN MUSEUM","ADDRESS_ID":234557,"LEGALNAME":"AMERICAN UNIVERSITY MUSEUM","ALTNAME":" ","WEBURL":"http://www.powhatanmuseum.com/"},"geometry":{"type":"Point","coordinates":[-77.03550660261739,38.93243814726252]}},{"type":"Feature","properties":{"OBJECTID":43,"ADDRESS":"144 CONSTITUTION AVENUE NE","NAME":"SEWALL-BELMONT HOUSE AND MUSEUM","ADDRESS_ID":286201,"LEGALNAME":"AMERICAN MUSEUM OF PEACE INC","ALTNAME":" ","WEBURL":"HTTP://WWW.SEWALLBELMONT.ORG"},"geometry":{"type":"Point","coordinates":[-77.00375845550963,38.89219466787653]}},{"type":"Feature","properties":{"OBJECTID":44,"ADDRESS":"802 MASSACHUSETTS AVENUE NE","NAME":"SHOOK MUSEUM FOUNDATION","ADDRESS_ID":79669,"LEGALNAME":"GREENPEACE FUND","ALTNAME":" ","WEBURL":"SHOOKMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-76.9944246526475,38.891834530779185]}},{"type":"Feature","properties":{"OBJECTID":45,"ADDRESS":"1400 CONSTITUTION AVENUE NW","NAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF NATURAL HISTORY","ADDRESS_ID":310702,"LEGALNAME":"B'NAI B'RITH KLUTZNICK MUSEUM","ALTNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF NATURAL HISTORY","WEBURL":"http://www.mnh.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02591603234607,38.89121850995097]}},{"type":"Feature","properties":{"OBJECTID":46,"ADDRESS":"500 HOWARD PLACE NW","NAME":"HOWARD UNIVERSITY MUSEUM","ADDRESS_ID":243398,"LEGALNAME":"COLLECTONS STRIES AMRCN MSLIMS","ALTNAME":" ","WEBURL":"http://www.coas.howard.edu/msrc/museum.html"},"geometry":{"type":"Point","coordinates":[-77.0196991986925,38.922360224748935]}},{"type":"Feature","properties":{"OBJECTID":47,"ADDRESS":"8TH STREET NW AND  F ST NW","NAME":"NATIONAL PORTRAIT GALLERY","ADDRESS_ID":294248,"LEGALNAME":"BOHEMIA ARTS","ALTNAME":"NATIONAL PORTRAIT GALLERY","WEBURL":"HTTP://WWW.NPG.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.02295571583119,38.89815890118559]}},{"type":"Feature","properties":{"OBJECTID":48,"ADDRESS":"14TH STREET NW AND CONSTITUTION AVENUE NW","NAME":"NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","ADDRESS_ID":903110,"LEGALNAME":"AMERICANS FOR BATTLEFIELD PRESERVATION","ALTNAME":"NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","WEBURL":"HTTP://WWW.NMAAHC.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.03271597832732,38.89119983415094]}},{"type":"Feature","properties":{"OBJECTID":49,"ADDRESS":"4TH STREET SW AND INDEPENDENCE AVENUE SW","NAME":"NATIONAL MUSEUM OF AMERICAN INDIAN","ADDRESS_ID":294429,"LEGALNAME":"BLAIR HOUSE RESTORATION FUND","ALTNAME":" ","WEBURL":"WWW.NMAI.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.01672595283219,38.88826561652]}},{"type":"Feature","properties":{"OBJECTID":50,"ADDRESS":"6TH STREET SW AND INDEPENDENCE AVENUE SW","NAME":"NATIONAL AIR AND SPACE MUSEUM","ADDRESS_ID":301565,"LEGALNAME":"BETHUNE MEMORIAL MUSEUM","ALTNAME":"NATIONAL AIR AND SPACE MUSEUM","WEBURL":"HTTP://WWW.NASM.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.01979999825605,38.888161175521944]}},{"type":"Feature","properties":{"OBJECTID":51,"ADDRESS":"7THB STREET AND INDEPENDENCE AVENUE SW","NAME":"HIRSHHORN MUSEUM AND SCULPTURE GARDEN","ADDRESS_ID":294428,"LEGALNAME":"D.C. OFFICE OF PUBLIC RECORDS AND ARCHIVES","ALTNAME":"HIRSHHORN MUSEUM AND SCULPTURE GARDEN","WEBURL":"HTTP://HIRSHHORN.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.02294902891254,38.88843565656003]}},{"type":"Feature","properties":{"OBJECTID":52,"ADDRESS":"MADISON DRIVE NW AND 12TH STREET NW","NAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AMERICAN HISTORY","ADDRESS_ID":293200,"LEGALNAME":null,"ALTNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AMERICAN HISTORY","WEBURL":"HTTP://AMERICANHISTORY.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.03005156534492,38.89123181993075]}},{"type":"Feature","properties":{"OBJECTID":53,"ADDRESS":"4TH STREET NW AND MADISON DRIVE NW","NAME":"NATIONAL GALLERY OF ART - EAST BUILDING","ADDRESS_ID":293209,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.nga.gov/content/ngaweb/visit/maps-and-information/east-building.html"},"geometry":{"type":"Point","coordinates":[-77.01668919569053,38.89125721273486]}},{"type":"Feature","properties":{"OBJECTID":54,"ADDRESS":"4TH STREET NW AND MADISON DRIVE NW","NAME":"NATIONAL GALLERY OF ART - WEST BUILDING","ADDRESS_ID":293249,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.nga.gov/content/ngaweb/visit/maps-and-information/west-building.html"},"geometry":{"type":"Point","coordinates":[-77.01989150273015,38.891313914429645]}},{"type":"Feature","properties":{"OBJECTID":55,"ADDRESS":"1000 JEFFERSON DRIVE SW","NAME":"SMITHSONIAN INSTITUTION - CASTLE","ADDRESS_ID":293187,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.si.edu/Museums/smithsonian-institution-building"},"geometry":{"type":"Point","coordinates":[-77.02597189316775,38.88879577572046]}},{"type":"Feature","properties":{"OBJECTID":56,"ADDRESS":"1050 INDEPENDENCE AVENUE SW","NAME":"SACKLER GALLERY","ADDRESS_ID":293191,"LEGALNAME":"ARTHUR M. SACKLER GALLERY","ALTNAME":null,"WEBURL":"http://www.asia.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02645343758842,38.88796502751886]}},{"type":"Feature","properties":{"OBJECTID":57,"ADDRESS":"JEFFERSON DRIVE SW AND 12TH STREET SW","NAME":"FREER GALLERY","ADDRESS_ID":294417,"LEGALNAME":"FREER GALLERY OF ART","ALTNAME":null,"WEBURL":"http://www.asia.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02736845485786,38.8882746680144]}}]}
\ No newline at end of file
diff --git a/content/data/bar.vl.json b/content/data/bar.vl.json
new file mode 100644
index 0000000..f5b7b37
--- /dev/null
+++ b/content/data/bar.vl.json
@@ -0,0 +1,54 @@
+{
+  "data": {
+    "values": [
+      {
+        "a": "A",
+        "b": 28
+      },
+      {
+        "a": "B",
+        "b": 55
+      },
+      {
+        "a": "C",
+        "b": 43
+      },
+      {
+        "a": "D",
+        "b": 91
+      },
+      {
+        "a": "E",
+        "b": 81
+      },
+      {
+        "a": "F",
+        "b": 53
+      },
+      {
+        "a": "G",
+        "b": 19
+      },
+      {
+        "a": "H",
+        "b": 87
+      },
+      {
+        "a": "I",
+        "b": 52
+      }
+    ]
+  },
+  "description": "A simple bar chart with embedded data.",
+  "encoding": {
+    "x": {
+      "field": "a",
+      "type": "ordinal"
+    },
+    "y": {
+      "field": "b",
+      "type": "quantitative"
+    }
+  },
+  "mark": "bar"
+}
diff --git a/content/data/fasta-example.fasta b/content/data/fasta-example.fasta
new file mode 100644
index 0000000..cfcbad5
--- /dev/null
+++ b/content/data/fasta-example.fasta
@@ -0,0 +1,8 @@
+>SEQUENCE_1
+MTEITAAMVKELRESTGAGMMDCKNALSETNGDFDKAVQLLREKGLGKAAKKADRLAAEG
+LVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYKALVAELEKENEERRRLKDPNKPEHK
+IPQFASRKQLSDAILKEAEEKIKEELKAQGKPEKIWDNIIPGKMNSFIADNSQLDSKLTL
+MGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAAEVAAQL
+>SEQUENCE_2
+SATVSEINSETDFVAKNDQFIALTKDTTAHIQSNSLQSVEELHSSTINGVKFEEYLKSQI
+ATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH
\ No newline at end of file
diff --git a/content/data/iris.csv b/content/data/iris.csv
new file mode 100644
index 0000000..43ff582
--- /dev/null
+++ b/content/data/iris.csv
@@ -0,0 +1,151 @@
+sepal_length,sepal_width,petal_length,petal_width,species
+5.1,3.5,1.4,0.2,se
+4.9,3,1.4,0.2,setosa
+4.7,3.2,1.3,0.2,setosa
+4.6,3.1,1.5,0.2,setosa
+5,3.6,1.4,0.2,setosa
+5.4,3.9,1.7,0.4,setosa
+4.6,3.4,1.4,0.3,setosa
+5,3.4,1.5,0.2,setosa
+4.4,2.9,1.4,0.2,setosa
+4.9,3.1,1.5,0.1,setosa
+5.4,3.7,1.5,0.2,setosa
+4.8,3.4,1.6,0.2,setosa
+4.8,3,1.4,0.1,setosa
+4.3,3,1.1,0.1,setosa
+5.8,4,1.2,0.2,setosa
+5.7,4.4,1.5,0.4,setosa
+5.4,3.9,1.3,0.4,setosa
+5.1,3.5,1.4,0.3,setosa
+5.7,3.8,1.7,0.3,setosa
+5.1,3.8,1.5,0.3,setosa
+5.4,3.4,1.7,0.2,setosa
+5.1,3.7,1.5,0.4,setosa
+4.6,3.6,1,0.2,setosa
+5.1,3.3,1.7,0.5,setosa
+4.8,3.4,1.9,0.2,setosa
+5,3,1.6,0.2,setosa
+5,3.4,1.6,0.4,setosa
+5.2,3.5,1.5,0.2,setosa
+5.2,3.4,1.4,0.2,setosa
+4.7,3.2,1.6,0.2,setosa
+4.8,3.1,1.6,0.2,setosa
+5.4,3.4,1.5,0.4,setosa
+5.2,4.1,1.5,0.1,setosa
+5.5,4.2,1.4,0.2,setosa
+4.9,3.1,1.5,0.1,setosa
+5,3.2,1.2,0.2,setosa
+5.5,3.5,1.3,0.2,setosa
+4.9,3.1,1.5,0.1,setosa
+4.4,3,1.3,0.2,setosa
+5.1,3.4,1.5,0.2,setosa
+5,3.5,1.3,0.3,setosa
+4.5,2.3,1.3,0.3,setosa
+4.4,3.2,1.3,0.2,setosa
+5,3.5,1.6,0.6,setosa
+5.1,3.8,1.9,0.4,setosa
+4.8,3,1.4,0.3,setosa
+5.1,3.8,1.6,0.2,setosa
+4.6,3.2,1.4,0.2,setosa
+5.3,3.7,1.5,0.2,setosa
+5,3.3,1.4,0.2,setosa
+7,3.2,4.7,1.4,versicolor
+6.4,3.2,4.5,1.5,versicolor
+6.9,3.1,4.9,1.5,versicolor
+5.5,2.3,4,1.3,versicolor
+6.5,2.8,4.6,1.5,versicolor
+5.7,2.8,4.5,1.3,versicolor
+6.3,3.3,4.7,1.6,versicolor
+4.9,2.4,3.3,1,versicolor
+6.6,2.9,4.6,1.3,versicolor
+5.2,2.7,3.9,1.4,versicolor
+5,2,3.5,1,versicolor
+5.9,3,4.2,1.5,versicolor
+6,2.2,4,1,versicolor
+6.1,2.9,4.7,1.4,versicolor
+5.6,2.9,3.6,1.3,versicolor
+6.7,3.1,4.4,1.4,versicolor
+5.6,3,4.5,1.5,versicolor
+5.8,2.7,4.1,1,versicolor
+6.2,2.2,4.5,1.5,versicolor
+5.6,2.5,3.9,1.1,versicolor
+5.9,3.2,4.8,1.8,versicolor
+6.1,2.8,4,1.3,versicolor
+6.3,2.5,4.9,1.5,versicolor
+6.1,2.8,4.7,1.2,versicolor
+6.4,2.9,4.3,1.3,versicolor
+6.6,3,4.4,1.4,versicolor
+6.8,2.8,4.8,1.4,versicolor
+6.7,3,5,1.7,versicolor
+6,2.9,4.5,1.5,versicolor
+5.7,2.6,3.5,1,versicolor
+5.5,2.4,3.8,1.1,versicolor
+5.5,2.4,3.7,1,versicolor
+5.8,2.7,3.9,1.2,versicolor
+6,2.7,5.1,1.6,versicolor
+5.4,3,4.5,1.5,versicolor
+6,3.4,4.5,1.6,versicolor
+6.7,3.1,4.7,1.5,versicolor
+6.3,2.3,4.4,1.3,versicolor
+5.6,3,4.1,1.3,versicolor
+5.5,2.5,4,1.3,versicolor
+5.5,2.6,4.4,1.2,versicolor
+6.1,3,4.6,1.4,versicolor
+5.8,2.6,4,1.2,versicolor
+5,2.3,3.3,1,versicolor
+5.6,2.7,4.2,1.3,versicolor
+5.7,3,4.2,1.2,versicolor
+5.7,2.9,4.2,1.3,versicolor
+6.2,2.9,4.3,1.3,versicolor
+5.1,2.5,3,1.1,versicolor
+5.7,2.8,4.1,1.3,versicolor
+6.3,3.3,6,2.5,virginica
+5.8,2.7,5.1,1.9,virginica
+7.1,3,5.9,2.1,virginica
+6.3,2.9,5.6,1.8,virginica
+6.5,3,5.8,2.2,virginica
+7.6,3,6.6,2.1,virginica
+4.9,2.5,4.5,1.7,virginica
+7.3,2.9,6.3,1.8,virginica
+6.7,2.5,5.8,1.8,virginica
+7.2,3.6,6.1,2.5,virginica
+6.5,3.2,5.1,2,virginica
+6.4,2.7,5.3,1.9,virginica
+6.8,3,5.5,2.1,virginica
+5.7,2.5,5,2,virginica
+5.8,2.8,5.1,2.4,virginica
+6.4,3.2,5.3,2.3,virginica
+6.5,3,5.5,1.8,virginica
+7.7,3.8,6.7,2.2,virginica
+7.7,2.6,6.9,2.3,virginica
+6,2.2,5,1.5,virginica
+6.9,3.2,5.7,2.3,virginica
+5.6,2.8,4.9,2,virginica
+7.7,2.8,6.7,2,virginica
+6.3,2.7,4.9,1.8,virginica
+6.7,3.3,5.7,2.1,virginica
+7.2,3.2,6,1.8,virginica
+6.2,2.8,4.8,1.8,virginica
+6.1,3,4.9,1.8,virginica
+6.4,2.8,5.6,2.1,virginica
+7.2,3,5.8,1.6,virginica
+7.4,2.8,6.1,1.9,virginica
+7.9,3.8,6.4,2,virginica
+6.4,2.8,5.6,2.2,virginica
+6.3,2.8,5.1,1.5,virginica
+6.1,2.6,5.6,1.4,virginica
+7.7,3,6.1,2.3,virginica
+6.3,3.4,5.6,2.4,virginica
+6.4,3.1,5.5,1.8,virginica
+6,3,4.8,1.8,virginica
+6.9,3.1,5.4,2.1,virginica
+6.7,3.1,5.6,2.4,virginica
+6.9,3.1,5.1,2.3,virginica
+5.8,2.7,5.1,1.9,virginica
+6.8,3.2,5.9,2.3,virginica
+6.7,3.3,5.7,2.5,virginica
+6.7,3,5.2,2.3,virginica
+6.3,2.5,5,1.9,virginica
+6.5,3,5.2,2,virginica
+6.2,3.4,5.4,2.3,virginica
+5.9,3,5.1,1.8,virginica
diff --git a/content/data/matplotlib.png b/content/data/matplotlib.png
new file mode 100644
index 0000000000000000000000000000000000000000..a7fcb1d03f3ee020033c74fee4d808b660ed3582
GIT binary patch
literal 26389
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

literal 0
HcmV?d00001

diff --git a/content/javascript.ipynb b/content/javascript.ipynb
new file mode 100644
index 0000000..a5e7487
--- /dev/null
+++ b/content/javascript.ipynb
@@ -0,0 +1,86 @@
+{
+  "metadata": {
+    "language_info": {
+      "codemirror_mode": {
+        "name": "javascript"
+      },
+      "file_extension": ".js",
+      "mimetype": "text/javascript",
+      "name": "javascript",
+      "nbconvert_exporter": "javascript",
+      "pygments_lexer": "javascript",
+      "version": "es2017"
+    },
+    "kernelspec": {
+      "name": "javascript",
+      "display_name": "JavaScript",
+      "language": "javascript"
+    },
+    "toc-showcode": true
+  },
+  "nbformat_minor": 4,
+  "nbformat": 4,
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "source": "# JavaScript in `JupyterLite`\n\n![](https://jupyterlite.readthedocs.io/en/latest/_static/kernelspecs/javascript.svg)",
+      "metadata": {}
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Standard streams",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "console.log('hello world')",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "console.error('error')",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## JavaScript specific constructs",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "const delay = 2000;\n\nsetTimeout(() => {\n    console.log('done');\n}, delay);",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "var str = \"hello world\"\nstr.split('').forEach(c => {\n    console.log(c)\n})",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Markdown cells",
+      "metadata": {}
+    },
+    {
+      "cell_type": "markdown",
+      "source": "Lorenz system of differential equations\n\n$$\n\\begin{aligned}\n\\dot{x} & = \\sigma(y-x) \\\\\n\\dot{y} & = \\rho x - y - xz \\\\\n\\dot{z} & = -\\beta z + xy\n\\end{aligned}\n$$\n",
+      "metadata": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/content/p5.ipynb b/content/p5.ipynb
new file mode 100644
index 0000000..78be9cb
--- /dev/null
+++ b/content/p5.ipynb
@@ -0,0 +1,150 @@
+{
+   "metadata":{
+      "kernelspec":{
+         "name":"p5js",
+         "display_name":"p5.js",
+         "language":"javascript"
+      },
+      "language_info":{
+         "codemirror_mode":{
+            "name":"javascript"
+         },
+         "file_extension":".js",
+         "mimetype":"text/javascript",
+         "name":"p5js",
+         "nbconvert_exporter":"javascript",
+         "pygments_lexer":"javascript",
+         "version":"es2017"
+      }
+   },
+   "nbformat_minor":4,
+   "nbformat":4,
+   "cells":[
+      {
+         "cell_type":"markdown",
+         "source":"# p5 notebook\n\nA minimal Jupyter notebook UI for [p5.js](https://p5js.org) kernels.",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"markdown",
+         "source":"First let's define a couple of variables:",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"var n = 4;\nvar speed = 1;",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"markdown",
+         "source":"## The `setup` function\n\nThe usual p5 setup function, which creates the canvas.",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"function setup () {\n  createCanvas(innerWidth, innerHeight);\n  rectMode(CENTER);\n}",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"markdown",
+         "source":"## The `draw` function\n\nFrom the [p5.js documentation](https://p5js.org/reference/#/p5/draw):\n\n> The `draw()` function continuously executes the lines of code contained inside its block until the program is stopped or `noLoop()` is called.",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"function draw() {\n  background('#ddd');\n  translate(innerWidth / 2, innerHeight / 2);\n  for (let i = 0; i < n; i++) {\n    push();\n    rotate(frameCount * speed / 1000 * (i + 1));\n    fill(i * 5, i * 100, i * 150);\n    const s = 200 - i * 10;\n    rect(0, 0, s, s);\n    pop();\n  }\n}",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"markdown",
+         "source":"## Show the sketch\n\nNow let's show the sketch by using the `%show` magic:",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"%show",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"markdown",
+         "source":"## Tweak the values\n\nWe can also tweak some values in real time:",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"speed = 3",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"code",
+         "source":"n = 20",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      },
+      {
+         "cell_type":"markdown",
+         "source":"We can also show the sketch a second time taking into account the new values:",
+         "metadata":{
+
+         }
+      },
+      {
+         "cell_type":"code",
+         "source":"%show",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+
+         ]
+      }
+   ]
+}
\ No newline at end of file
diff --git a/content/pyodide/altair.ipynb b/content/pyodide/altair.ipynb
new file mode 100644
index 0000000..d7b8780
--- /dev/null
+++ b/content/pyodide/altair.ipynb
@@ -0,0 +1,231 @@
+{
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Altair in `JupyterLite`\n",
+        "\n",
+        "**Altair** is a declarative statistical visualization library for Python.\n",
+        "\n",
+        "Most of the examples below are from: https://altair-viz.github.io/gallery"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Import the dependencies:"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%pip install -q altair"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Simple Bar Chart"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import altair as alt\n",
+        "import pandas as pd\n",
+        "\n",
+        "source = pd.DataFrame({\n",
+        "    'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],\n",
+        "    'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]\n",
+        "})\n",
+        "\n",
+        "alt.Chart(source).mark_bar().encode(\n",
+        "    x='a',\n",
+        "    y='b'\n",
+        ")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Simple Heatmap"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import altair as alt\n",
+        "import numpy as np\n",
+        "import pandas as pd\n",
+        "\n",
+        "# Compute x^2 + y^2 across a 2D grid\n",
+        "x, y = np.meshgrid(range(-5, 5), range(-5, 5))\n",
+        "z = x ** 2 + y ** 2\n",
+        "\n",
+        "# Convert this grid to columnar data expected by Altair\n",
+        "source = pd.DataFrame({'x': x.ravel(),\n",
+        "                     'y': y.ravel(),\n",
+        "                     'z': z.ravel()})\n",
+        "\n",
+        "alt.Chart(source).mark_rect().encode(\n",
+        "    x='x:O',\n",
+        "    y='y:O',\n",
+        "    color='z:Q'\n",
+        ")\n"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Install the Vega Dataset"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%pip install -q vega_datasets"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Interactive Average"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import altair as alt\n",
+        "from vega_datasets import data\n",
+        "\n",
+        "source = data.seattle_weather()\n",
+        "brush = alt.selection(type='interval', encodings=['x'])\n",
+        "\n",
+        "bars = alt.Chart().mark_bar().encode(\n",
+        "    x='month(date):O',\n",
+        "    y='mean(precipitation):Q',\n",
+        "    opacity=alt.condition(brush, alt.OpacityValue(1), alt.OpacityValue(0.7)),\n",
+        ").add_selection(\n",
+        "    brush\n",
+        ")\n",
+        "\n",
+        "line = alt.Chart().mark_rule(color='firebrick').encode(\n",
+        "    y='mean(precipitation):Q',\n",
+        "    size=alt.SizeValue(3)\n",
+        ").transform_filter(\n",
+        "    brush\n",
+        ")\n",
+        "\n",
+        "alt.layer(bars, line, data=source)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Locations of US Airports"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import altair as alt\n",
+        "from vega_datasets import data\n",
+        "\n",
+        "airports = data.airports.url\n",
+        "states = alt.topo_feature(data.us_10m.url, feature='states')\n",
+        "\n",
+        "# US states background\n",
+        "background = alt.Chart(states).mark_geoshape(\n",
+        "    fill='lightgray',\n",
+        "    stroke='white'\n",
+        ").properties(\n",
+        "    width=500,\n",
+        "    height=300\n",
+        ").project('albersUsa')\n",
+        "\n",
+        "# airport positions on background\n",
+        "points = alt.Chart(airports).transform_aggregate(\n",
+        "    latitude='mean(latitude)',\n",
+        "    longitude='mean(longitude)',\n",
+        "    count='count()',\n",
+        "    groupby=['state']\n",
+        ").mark_circle().encode(\n",
+        "    longitude='longitude:Q',\n",
+        "    latitude='latitude:Q',\n",
+        "    size=alt.Size('count:Q', title='Number of Airports'),\n",
+        "    color=alt.value('steelblue'),\n",
+        "    tooltip=['state:N','count:Q']\n",
+        ").properties(\n",
+        "    title='Number of airports in US'\n",
+        ")\n",
+        "\n",
+        "background + points\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {},
+      "outputs": [],
+      "source": []
+    }
+  ],
+  "metadata": {
+    "kernelspec": {
+      "display_name": "Python (Pyodide)",
+      "language": "python",
+      "name": "python"
+    },
+    "language_info": {
+      "codemirror_mode": {
+        "name": "python",
+        "version": 3
+      },
+      "file_extension": ".py",
+      "mimetype": "text/x-python",
+      "name": "python",
+      "nbconvert_exporter": "python",
+      "pygments_lexer": "ipython3",
+      "version": "3.8"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 4
+}
diff --git a/content/pyodide/folium.ipynb b/content/pyodide/folium.ipynb
new file mode 100644
index 0000000..2828de0
--- /dev/null
+++ b/content/pyodide/folium.ipynb
@@ -0,0 +1,154 @@
+{
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "tags": []
+      },
+      "source": [
+        "# `folium` Interactive Map Demo\n",
+        "\n",
+        "Simple demonstration of rendering a map in a `jupyterlite` notebook.\n",
+        "\n",
+        "Note that the `folium` package has several dependencies which themselves may have dependencies.\n",
+        "\n",
+        "The following code fragement, run in a fresh Python enviroment into which `folium` has already been installed, identifies the packages that are loaded in when `folium` is loaded:\n",
+        "\n",
+        "```python\n",
+        "#https://stackoverflow.com/a/40381601/454773\n",
+        "import sys\n",
+        "before = [str(m) for m in sys.modules]\n",
+        "import folium\n",
+        "after = [str(m) for m in sys.modules]\n",
+        "set([m.split('.')[0] for m in after if not m in before and not m.startswith('_')])\n",
+        "```\n",
+        "\n",
+        "The loaded packages are:\n",
+        "\n",
+        "```\n",
+        "{'branca',\n",
+        " 'certifi',\n",
+        " 'chardet',\n",
+        " 'cmath',\n",
+        " 'csv',\n",
+        " 'dateutil',\n",
+        " 'encodings',\n",
+        " 'folium',\n",
+        " 'gzip',\n",
+        " 'http',\n",
+        " 'idna',\n",
+        " 'importlib',\n",
+        " 'jinja2',\n",
+        " 'markupsafe',\n",
+        " 'mmap',\n",
+        " 'numpy',\n",
+        " 'pandas',\n",
+        " 'pkg_resources',\n",
+        " 'pytz',\n",
+        " 'requests',\n",
+        " 'secrets',\n",
+        " 'stringprep',\n",
+        " 'urllib3',\n",
+        " 'zipfile'}\n",
+        " ```\n",
+        " "
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "The following packages seem to need installing in order load `folium`, along with folium itself:\n",
+        "\n",
+        "```\n",
+        "chardet, certifi, idna, branca, urllib3, Jinja2, requests, Markupsafe\n",
+        "```\n",
+        "\n",
+        "Universal wheels, with filenames of the form `PACKAGE-VERSION-py2.py3-none-any.whl` appearing in the *Download files* area of a PyPi package page ([example](https://pypi.org/project/requests/#files)] are required in order to install the package.\n",
+        "\n",
+        "One required package, [`Markupsafe`](https://pypi.org/project/Markupsafe/#files)) *did not* have a universal wheel available, so a wheel was manually built elsewhere (by hacking the [`setup.py` file](https://github.com/pallets/markupsafe/blob/main/setup.py) to force it to build the wheel in a platform and speedup free way) and pushed to a downloadable location in an [*ad hoc* wheelhouse](https://opencomputinglab.github.io/vce-wheelhouse/)."
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "# Install folium requirements\n",
+        "%pip install -q folium"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Demo of `folium` Map\n",
+        "\n",
+        "Load in the `folium` package:"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import folium"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "And render a demo map:"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "m = folium.Map(location=[50.693848, -1.304734], zoom_start=11)\n",
+        "m"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {},
+      "outputs": [],
+      "source": []
+    }
+  ],
+  "metadata": {
+    "kernelspec": {
+      "display_name": "Python (Pyodide)",
+      "language": "python",
+      "name": "python"
+    },
+    "language_info": {
+      "codemirror_mode": {
+        "name": "python",
+        "version": 3
+      },
+      "file_extension": ".py",
+      "mimetype": "text/x-python",
+      "name": "python",
+      "nbconvert_exporter": "python",
+      "pygments_lexer": "ipython3",
+      "version": "3.8"
+    },
+    "orig_nbformat": 4,
+    "toc-showcode": false
+  },
+  "nbformat": 4,
+  "nbformat_minor": 4
+}
diff --git a/content/pyodide/interactive-widgets.ipynb b/content/pyodide/interactive-widgets.ipynb
new file mode 100644
index 0000000..267273d
--- /dev/null
+++ b/content/pyodide/interactive-widgets.ipynb
@@ -0,0 +1,268 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "9ca234f7-84b7-4107-9bcd-74f5a4ffd07d",
+   "metadata": {},
+   "source": [
+    "# `ipywidgets` Interactive Demo\n",
+    "\n",
+    "Simple demonstration of rendering Interactive widgets in a `jupyterlite` notebook.\n",
+    "\n",
+    "`ipywidgets` can be installed in this deployment (it provides the @jupyter-widgets/jupyterlab-manager federated extension), but you will need to make your own deployment to have access to other interactive widgets libraries."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d62fba6e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install -q ipywidgets"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3bab23f8-de91-43c9-9cec-84f4924425fc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipywidgets import IntSlider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a15c5acb-ee72-4005-8761-5693db853f22",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider = IntSlider()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8ba89682-e0d7-4bd2-961a-f9956850fd5a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "50510ade-668f-4477-8cb2-41574609ac73",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7bac1ed8-8c77-426b-a781-1c1a6cfad829",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider.value"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "976a70a0-e99d-4c20-b005-f59bbba10f85",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider.value = 5"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3134c76e-cffb-4701-8230-e6c4bfbbfdb9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipywidgets import IntText, link"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f7b3fe0a-5695-4ef2-a573-40785e68fbae",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text = IntText()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5e2fd50e-19e0-4e20-a1f7-ad65400ec636",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bb3bedce-7311-48c0-aeab-8fe3aa554b92",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "link((slider, 'value'), (text, 'value'));"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "71b68c3e-184e-4320-9513-d0bc72800a85",
+   "metadata": {},
+   "source": [
+    "# `bqplot` Interactive Demo\n",
+    "\n",
+    "Plotting in JupyterLite\n",
+    "\n",
+    "`bqplot` can be installed in this deployment (it provides the bqplot federated extension), but you will need to make your own deployment to have access to other interactive widgets libraries."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "119eb9a3-ac98-42c3-98d4-1ac460eb75d3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install -q bqplot"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "23b32857-2958-4083-b16a-ac26cd2408d4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from bqplot import *\n",
+    "\n",
+    "import numpy as np\n",
+    "import pandas as pd\n",
+    "\n",
+    "np.random.seed(0)\n",
+    "\n",
+    "n = 100\n",
+    "\n",
+    "x = list(range(n))\n",
+    "y = np.cumsum(np.random.randn(n)) + 100.\n",
+    "\n",
+    "sc_x = LinearScale()\n",
+    "sc_y = LinearScale()\n",
+    "\n",
+    "lines = Lines(\n",
+    "    x=x, y=y,\n",
+    "    scales={'x': sc_x, 'y': sc_y}\n",
+    ")\n",
+    "ax_x = Axis(scale=sc_x, label='Index')\n",
+    "ax_y = Axis(scale=sc_y, orientation='vertical', label='lines')\n",
+    "\n",
+    "Figure(marks=[lines], axes=[ax_x, ax_y], title='Lines')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ddb6b44e-06a0-4049-a79d-33ffc90d5a03",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "lines.colors = ['green']"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e367e7fb-b403-41aa-9629-224827ec3005",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "lines.fill = 'bottom'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d4a167f3-07c4-4880-92f5-7fcdea0c61c6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "lines.marker = 'circle'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1d1342f7-ec08-4f53-84dc-d712226d9e46",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n = 100\n",
+    "\n",
+    "x = list(range(n))\n",
+    "y = np.cumsum(np.random.randn(n))\n",
+    "\n",
+    "sc_x = LinearScale()\n",
+    "sc_y = LinearScale()\n",
+    "\n",
+    "bars = Bars(\n",
+    "    x=x, y=y,\n",
+    "    scales={'x': sc_x, 'y': sc_y}\n",
+    ")\n",
+    "ax_x = Axis(scale=sc_x, label='Index')\n",
+    "ax_y = Axis(scale=sc_y, orientation='vertical', label='bars')\n",
+    "\n",
+    "Figure(marks=[bars], axes=[ax_x, ax_y], title='Bars', animation_duration=1000)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f86bbcfb-5b02-4700-b8d6-f90068893b55",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "bars.y = np.cumsum(np.random.randn(n))"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python (Pyodide)",
+   "language": "python",
+   "name": "python"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "python",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8"
+  },
+  "orig_nbformat": 4,
+  "toc-showcode": false
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/ipycanvas.ipynb b/content/pyodide/ipycanvas.ipynb
new file mode 100644
index 0000000..33e1b9a
--- /dev/null
+++ b/content/pyodide/ipycanvas.ipynb
@@ -0,0 +1,178 @@
+{
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# ipycanvas: John Conway's Game Of Life"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "Some of the following code is adapted from https://jakevdp.github.io/blog/2013/08/07/conways-game-of-life/"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%pip install -q ipycanvas"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import asyncio\n",
+        "\n",
+        "import numpy as np\n",
+        "\n",
+        "from ipycanvas import RoughCanvas, hold_canvas"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "def life_step(x):\n",
+        "    \"\"\"Game of life step\"\"\"\n",
+        "    nbrs_count = sum(np.roll(np.roll(x, i, 0), j, 1)\n",
+        "                     for i in (-1, 0, 1) for j in (-1, 0, 1)\n",
+        "                     if (i != 0 or j != 0))\n",
+        "    return (nbrs_count == 3) | (x & (nbrs_count == 2))"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "def draw(x, canvas, color='black'):\n",
+        "    with hold_canvas(canvas):\n",
+        "        canvas.clear()\n",
+        "        canvas.fill_style = '#FFF0C9'\n",
+        "        canvas.rough_fill_style = 'solid'\n",
+        "        canvas.fill_rect(-10, -10, canvas.width + 10, canvas.height + 10)\n",
+        "        canvas.rough_fill_style = 'cross-hatch'\n",
+        "\n",
+        "        canvas.fill_style = color\n",
+        "        canvas.stroke_style = color\n",
+        "\n",
+        "        living_cells = np.where(x)\n",
+        "        \n",
+        "        rects_x = living_cells[1] * n_pixels\n",
+        "        rects_y = living_cells[0] * n_pixels\n",
+        "\n",
+        "        canvas.fill_rects(rects_x, rects_y, n_pixels)\n",
+        "        canvas.stroke_rects(rects_x, rects_y, n_pixels)"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "glider_gun =\\\n",
+        "[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],\n",
+        " [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],\n",
+        " [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n",
+        " [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]\n",
+        "\n",
+        "x = np.zeros((50, 70), dtype=bool)\n",
+        "x[1:10,1:37] = glider_gun"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "n_pixels = 15\n",
+        "\n",
+        "canvas = RoughCanvas(width=x.shape[1]*n_pixels, height=x.shape[0]*n_pixels)\n",
+        "canvas.fill_style = '#FFF0C9'\n",
+        "canvas.rough_fill_style = 'solid'\n",
+        "canvas.fill_rect(0, 0, canvas.width, canvas.height)\n",
+        "\n",
+        "canvas"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "draw(x, canvas, '#5770B3')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "for _ in range(300):\n",
+        "    x = life_step(x)\n",
+        "    draw(x, canvas, '#5770B3')\n",
+        "\n",
+        "    await asyncio.sleep(0.1)"
+      ]
+    }
+  ],
+  "metadata": {
+    "kernelspec": {
+      "display_name": "Python (Pyodide)",
+      "language": "python",
+      "name": "python"
+    },
+    "language_info": {
+      "codemirror_mode": {
+        "name": "python",
+        "version": 3
+      },
+      "file_extension": ".py",
+      "mimetype": "text/x-python",
+      "name": "python",
+      "nbconvert_exporter": "python",
+      "pygments_lexer": "ipython3",
+      "version": "3.8"
+    },
+    "orig_nbformat": 4,
+    "toc-showcode": false
+  },
+  "nbformat": 4,
+  "nbformat_minor": 4
+}
diff --git a/content/pyodide/ipyleaflet.ipynb b/content/pyodide/ipyleaflet.ipynb
new file mode 100644
index 0000000..64dcff2
--- /dev/null
+++ b/content/pyodide/ipyleaflet.ipynb
@@ -0,0 +1,259 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install -q bqplot ipyleaflet"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "from urllib.request import urlopen\n",
+    "import json\n",
+    "from datetime import datetime\n",
+    "\n",
+    "import numpy as np\n",
+    "import pandas as pd\n",
+    "\n",
+    "from js import fetch\n",
+    "\n",
+    "from ipywidgets import Dropdown\n",
+    "\n",
+    "from bqplot import Lines, Figure, LinearScale, DateScale, Axis\n",
+    "\n",
+    "from ipyleaflet import Map, GeoJSON, WidgetControl"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "URL = \"https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/nations.json\"\n",
+    "\n",
+    "res = await fetch(URL)\n",
+    "text = await res.text()\n",
+    "\n",
+    "data = pd.read_json(text)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def clean_data(data):\n",
+    "    for column in ['income', 'lifeExpectancy', 'population']:\n",
+    "        data = data.drop(data[data[column].apply(len) <= 4].index)\n",
+    "    return data\n",
+    "\n",
+    "def extrap_interp(data):\n",
+    "    data = np.array(data)\n",
+    "    x_range = np.arange(1800, 2009, 1.)\n",
+    "    y_range = np.interp(x_range, data[:, 0], data[:, 1])\n",
+    "    return y_range\n",
+    "\n",
+    "def extrap_data(data):\n",
+    "    for column in ['income', 'lifeExpectancy', 'population']:\n",
+    "        data[column] = data[column].apply(extrap_interp)\n",
+    "    return data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "data = clean_data(data)\n",
+    "data = extrap_data(data)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "date_start = datetime(1800, 12, 31)\n",
+    "date_end = datetime(2009, 12, 31)\n",
+    "\n",
+    "date_scale = DateScale(min=date_start, max=date_end)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "date_data = pd.date_range(start=date_start, end=date_end, freq='A', normalize=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "country_name = 'Angola'\n",
+    "data_name = 'income'\n",
+    "\n",
+    "x_data = data[data.name == country_name][data_name].values[0]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x_scale = LinearScale()\n",
+    "\n",
+    "lines = Lines(x=date_data, y=x_data, scales={'x': date_scale, 'y': x_scale})\n",
+    "\n",
+    "ax_x = Axis(label='Year', scale=date_scale, num_ticks=10, tick_format='%Y')\n",
+    "ax_y = Axis(label=data_name.capitalize(), scale=x_scale, orientation='vertical', side='left')\n",
+    "\n",
+    "figure = Figure(axes=[ax_x, ax_y], title=country_name, marks=[lines], animation_duration=500,\n",
+    "                layout={'max_height': '250px', 'max_width': '400px'})"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def update_figure(country_name, data_name):\n",
+    "    try:\n",
+    "        lines.y = data[data.name == country_name][data_name].values[0]\n",
+    "        ax_y.label = data_name.capitalize()\n",
+    "        figure.title = country_name\n",
+    "    except IndexError:\n",
+    "        pass"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "URL = \"https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/countries.geo.json\"\n",
+    "\n",
+    "res = await fetch(URL)\n",
+    "text = await res.text()\n",
+    "\n",
+    "countries = json.loads(text)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = Map(zoom=3)\n",
+    "\n",
+    "geo = GeoJSON(data=countries, style={'fillColor': 'white', 'weight': 0.5}, hover_style={'fillColor': '#1f77b4'}, name='Countries')\n",
+    "m.add_layer(geo)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "widget_control1 = WidgetControl(widget=figure, position='bottomright')\n",
+    "\n",
+    "m.add_control(widget_control1)\n",
+    "\n",
+    "def on_hover(event, feature, **kwargs):\n",
+    "    global country_name\n",
+    "\n",
+    "    country_name = feature['properties']['name']\n",
+    "    update_figure(country_name, data_name)\n",
+    "\n",
+    "geo.on_hover(on_hover)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dropdown = Dropdown(\n",
+    "    options=['income', 'population', 'lifeExpectancy'],\n",
+    "    value=data_name,\n",
+    "    description='Plotting:'\n",
+    ")\n",
+    "\n",
+    "def on_click(change):\n",
+    "    global data_name\n",
+    "\n",
+    "    data_name = change['new']\n",
+    "    update_figure(country_name, data_name)\n",
+    "\n",
+    "dropdown.observe(on_click, 'value')\n",
+    "\n",
+    "widget_control2 = WidgetControl(widget=dropdown, position='bottomleft')\n",
+    "\n",
+    "m.add_control(widget_control2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python (Pyodide)",
+   "language": "python",
+   "name": "python"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "python",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8"
+  },
+  "orig_nbformat": 4,
+  "toc-showcode": false
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/matplotlib.ipynb b/content/pyodide/matplotlib.ipynb
new file mode 100644
index 0000000..c2ec2be
--- /dev/null
+++ b/content/pyodide/matplotlib.ipynb
@@ -0,0 +1,113 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Matplotlib"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x = np.linspace(0, 10, 1000)\n",
+    "plt.plot(x, np.sin(x));"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAA9hAAAPYQGoP6dpAABmgklEQVR4nO3deXxTZdo+8OskaZKuKd1baKGFQtkLRUrZkQoILoy4oDgogzg6OqPCjCPvb9R3RmcYfd3GZWQYZXDfxhVUFMsOZWspe0sXoPveJt2X5Pz+yAIVKKVNcpKc6/v55A/DSXqnwsl1zvM89yOIoiiCiIiIiGRDIXUBRERERORcDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMsMASERERCQzDIBEREREMqOSugB3ZjKZUFpaCn9/fwiCIHU5RERE1AOiKKKhoQFRUVFQKOR5L4wBsA9KS0sRHR0tdRlERETUC0VFRRgwYIDUZUiCAbAP/P39AZj/AgUEBEhcDREREfWEwWBAdHS07XtcjhgA+8A67BsQEMAASERE5GbkPH1LngPfRERERDLGAEhEREQkMwyARERERDLDAEhEREQkMwyARERERDLDAEhEREQkMwyARERERDLDAEhEREQkMwyARERERDLjFgFw586duPHGGxEVFQVBEPDVV19d8TXbt2/H+PHjodFoMGTIEGzYsOGi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",
+      "text/plain": [
+       "<pyolite.display.Image object at 0x254e300>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Matplotlib: support for widgets backend"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install -q ipympl"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib widget"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x = np.linspace(0, 10, 1000)\n",
+    "plt.plot(x, np.sin(x))"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python (Pyodide)",
+   "language": "python",
+   "name": "python"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "python",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8"
+  },
+  "orig_nbformat": 4,
+  "toc-showcode": false
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/plotly.ipynb b/content/pyodide/plotly.ipynb
new file mode 100644
index 0000000..9716bee
--- /dev/null
+++ b/content/pyodide/plotly.ipynb
@@ -0,0 +1,158 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Plotly in JupyterLite\n",
+    "\n",
+    "`plotly.py` is an interactive, open-source, and browser-based graphing library for Python: https://plotly.com/python/"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install -q nbformat plotly"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Basic Figure"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import plotly.graph_objects as go\n",
+    "fig = go.Figure()\n",
+    "fig.add_trace(go.Scatter(y=[2, 1, 4, 3]))\n",
+    "fig.add_trace(go.Bar(y=[1, 4, 3, 2]))\n",
+    "fig.update_layout(title = 'Hello Figure')\n",
+    "fig.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Basic Table with a Pandas DataFrame"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import plotly.graph_objects as go\n",
+    "import pandas as pd\n",
+    "\n",
+    "from js import fetch\n",
+    "\n",
+    "URL = \"https://raw.githubusercontent.com/plotly/datasets/master/2014_usa_states.csv\"\n",
+    "\n",
+    "res = await fetch(URL)\n",
+    "text = await res.text()\n",
+    "\n",
+    "filename = 'data.csv'\n",
+    "\n",
+    "with open(filename, 'w') as f:\n",
+    "    f.write(text)\n",
+    "\n",
+    "df = pd.read_csv(filename)\n",
+    "\n",
+    "fig = go.Figure(data=[go.Table(\n",
+    "    header=dict(values=list(df.columns),\n",
+    "                fill_color='paleturquoise',\n",
+    "                align='left'),\n",
+    "    cells=dict(values=[df.Rank, df.State, df.Postal, df.Population],\n",
+    "               fill_color='lavender',\n",
+    "               align='left'))\n",
+    "])\n",
+    "\n",
+    "fig.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Quiver Plot with Points"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import plotly.figure_factory as ff\n",
+    "import plotly.graph_objects as go\n",
+    "\n",
+    "import numpy as np\n",
+    "\n",
+    "x,y = np.meshgrid(np.arange(-2, 2, .2),\n",
+    "                  np.arange(-2, 2, .25))\n",
+    "z = x*np.exp(-x**2 - y**2)\n",
+    "v, u = np.gradient(z, .2, .2)\n",
+    "\n",
+    "# Create quiver figure\n",
+    "fig = ff.create_quiver(x, y, u, v,\n",
+    "                       scale=.25,\n",
+    "                       arrow_scale=.4,\n",
+    "                       name='quiver',\n",
+    "                       line_width=1)\n",
+    "\n",
+    "# Add points to figure\n",
+    "fig.add_trace(go.Scatter(x=[-.7, .75], y=[0,0],\n",
+    "                    mode='markers',\n",
+    "                    marker_size=12,\n",
+    "                    name='points'))\n",
+    "\n",
+    "fig.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python (Pyodide)",
+   "language": "python",
+   "name": "python"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "python",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8"
+  },
+  "orig_nbformat": 4,
+  "toc-showcode": false
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/pyb2d/0_tutorial.ipynb b/content/pyodide/pyb2d/0_tutorial.ipynb
new file mode 100644
index 0000000..3772314
--- /dev/null
+++ b/content/pyodide/pyb2d/0_tutorial.ipynb
@@ -0,0 +1,649 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b07a3b47-2262-4135-a1d2-52e8392b44eb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "49c3f9ea-23ce-4c5c-b3fe-44f1cecadf20",
+   "metadata": {},
+   "source": [
+    "pyb2d is imported as b2d"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "dff93359-2c68-467a-9239-478a0e550a4b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import b2d\n",
+    "# import pyb2d_jupyterlite_backend\n",
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "import numpy as np\n",
+    "import matplotlib.pylab as plt"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bc977c4e-75ee-4349-9408-650c3dcd01e0",
+   "metadata": {},
+   "source": [
+    "# Tutorial 0: A free falling body\n",
+    "The first step with Box2D is the creation of the world. The world is parametrized by a gravity vector."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4ff914a6-eb18-45a1-b1ed-e8ad7ab0d298",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# the world\n",
+    "gravity = (0, -10)\n",
+    "world = b2d.World(gravity)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3afdbb2a-e694-4779-b95e-73a5b38d34b6",
+   "metadata": {},
+   "source": [
+    "Create a circle-shaped body"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "99837a63-4628-483c-8f2d-cc4aec9cb1d5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# the body def\n",
+    "body_def = b2d.BodyDef()\n",
+    "body_def.type = b2d.BodyType.dynamic\n",
+    "body_def.position = (0, 0)\n",
+    "\n",
+    "# the body\n",
+    "body = world.create_body(body_def)\n",
+    "\n",
+    "# shape\n",
+    "circle_shape = b2d.CircleShape()\n",
+    "circle_shape.radius = 1.0\n",
+    "\n",
+    "# the fixture\n",
+    "fixture_def = b2d.FixtureDef()\n",
+    "fixture_def.shape = circle_shape\n",
+    "fixture_def.density = 1.0\n",
+    "\n",
+    "# create and add the fixture to the body\n",
+    "fixture = body.create_fixture(fixture_def)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bf9758a6-fb6e-4f9c-b15f-783f9488cf7e",
+   "metadata": {},
+   "source": [
+    "We can now have a look at the world: We render the world st. each meter in the Box2D world will be 100 pixels in the image:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8b433892-3c82-43be-a085-eda3e4279b2c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# from b2d.plot import render_world\n",
+    "b2d.plot.plot_world(world, ppm=100)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5e1db1f1-6e47-454c-9ea9-86262d7da309",
+   "metadata": {},
+   "source": [
+    "Lets run the world for a total of 5 seconds. \n",
+    "Usually one wants to run the world at a certain frame rate.\n",
+    "With the frame rate and the total time we can compute the delta for each iteration and how many steps we need"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "41a232a9-a3c5-425d-9aed-d3adb90d6314",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = 5\n",
+    "fps = 40\n",
+    "dt = 1.0 / fps\n",
+    "n_steps = int(t / dt + 0.5)\n",
+    "print(f\"t={t} fps={fps} dt={dt} n_steps={n_steps}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4d458acb-6d5c-47ba-bcbf-d15ea2cf2537",
+   "metadata": {},
+   "source": [
+    "in each step we query the bodies position and velocity and store then for later plotting"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4e042c7b-07a7-445f-ba04-e38173b46c0f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "positions = np.zeros([n_steps, 2])\n",
+    "velocites = np.zeros([n_steps, 2])\n",
+    "timepoints = np.zeros([n_steps])\n",
+    "\n",
+    "t_elapsed = 0.0\n",
+    "for i in range(n_steps):\n",
+    "\n",
+    "    # get the bodies center of mass\n",
+    "    positions[i, :] = body.world_center\n",
+    "\n",
+    "    # get the bodies velocity\n",
+    "    velocites[i, :] = body.linear_velocity\n",
+    "\n",
+    "    timepoints[i] = t_elapsed\n",
+    "\n",
+    "    world.step(time_step=dt, velocity_iterations=1, position_iterations=1)\n",
+    "    t_elapsed += dt"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0ec7d66c-c979-40fa-8af3-9e99873ec105",
+   "metadata": {},
+   "source": [
+    "plot the y-position against the time. We can see that the body is falling down in an accelerating way:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "434cb907-1b76-414e-bb5e-6ea32dd1f829",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot(timepoints, positions[:, 1])\n",
+    "plt.ylabel('y-poistion [meter]')\n",
+    "plt.xlabel('t [sec]')\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b7f58954-4ea1-49f9-b0b0-7df38336860d",
+   "metadata": {},
+   "source": [
+    "as expected the x position is not changing since the gravity vector is non-zero only in the x direction"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39573eed-e6c8-45bf-8e35-4251b660ce3f",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "plt.plot(timepoints, positions[:, 0])\n",
+    "plt.ylabel('x-poistion [meter]')\n",
+    "plt.xlabel('t [sec]')\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "cdb98dc5-3bc8-4933-91a0-a1db3afb9c34",
+   "metadata": {},
+   "source": [
+    "# Tutorial 1: A  falling body in a box, more pythonic\n",
+    "Create a world, but in a more pythonic way, and animate the world"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d58c2639-21da-490b-8dcd-205962f63dfc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# the world\n",
+    "world = b2d.world(gravity=(0, -10))\n",
+    "\n",
+    "# create the dynamic body\n",
+    "body = world.create_dynamic_body(\n",
+    "    position=(5, 5),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.75),\n",
+    ")\n",
+    "\n",
+    "# create a box\n",
+    "box_shape = b2d.ChainShape()\n",
+    "box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n",
+    "box = world.create_static_body(\n",
+    "    position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n",
+    ")\n",
+    "b2d.plot.animate_world(world, ppm=20, t=10)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "10dbb85a-84b0-4820-8fb3-6108d9c0fe00",
+   "metadata": {},
+   "source": [
+    "note that when we animate that world again, the body has already been fallen"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c919702c-7c62-4d87-bf1f-df5027d72a83",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "b2d.plot.animate_world(world, ppm=20, t=2)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7322e9c5-8608-4375-81ed-766cbb2af927",
+   "metadata": {},
+   "source": [
+    "# Tutorial 2: Interactive worlds\n",
+    "While animating the world already is already nice, interacting with the world is even better.\n",
+    "pyb2d has a framwork to interact with the world for multiple backends.\n",
+    "This framework is called `TestbedBase` since you can \"test\" your world in an interactive way"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "97cddd47-5a88-4cae-8543-cfcdf658255a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "\n",
+    "class InteractiveExample(TestbedBase):\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(InteractiveExample, self).__init__(settings=settings)\n",
+    "        # create two balls\n",
+    "        body = self.world.create_dynamic_body(position=(5, 5),\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.5),\n",
+    "        )\n",
+    "        body = self.world.create_dynamic_body(position=(8, 5),\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.8),\n",
+    "        )\n",
+    "        # create a box\n",
+    "        box_shape = b2d.ChainShape()\n",
+    "        box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n",
+    "        box = self.world.create_static_body(\n",
+    "            position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n",
+    "        )\n",
+    "        \n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [300,300]\n",
+    "b2d.testbed.run(InteractiveExample, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "64bf55d1-4117-4af0-8f1f-65de33751743",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "# Tutorial 3: Joints"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "07147cab-23be-4406-85f7-4b3d174e3954",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Tutorial 3.1: Prismatic Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9a2d178d-33d7-4c51-b0ff-f66c98cac673",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "anchor_body = world.create_static_body(position=(0, 0))\n",
+    "b = world.create_dynamic_body(\n",
+    "    position=(10, 10),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
+    "    linear_damping=0.0,\n",
+    "    angular_damping=0.0,\n",
+    ")\n",
+    "world.create_prismatic_joint(anchor_body, b, local_axis_a=(1, 1))\n",
+    "b2d.plot.animate_world(world, ppm=20, t=3, bounding_box=((0,0),(10,10)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "07971d69-b2ef-4d74-8c1c-48f38dcc708c",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Tutorial 3.2: Pully Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39b7fef2-4b4b-4904-9899-1a34f1039693",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "\n",
+    "\n",
+    "a = world.create_dynamic_body(\n",
+    "    position=(-5, 0),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.8]), density=1),\n",
+    "    linear_damping=0.0,\n",
+    "    angular_damping=0.0,\n",
+    ")\n",
+    "b = world.create_dynamic_body(\n",
+    "    position=(5, 0),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
+    "    linear_damping=0.0,\n",
+    "    angular_damping=0.0,\n",
+    ")\n",
+    "world.create_pully_joint(\n",
+    "    a,\n",
+    "    b,\n",
+    "    length_a=10,\n",
+    "    length_b=10,\n",
+    "    ground_anchor_a=(-5, 10),\n",
+    "    ground_anchor_b=(5, 10),\n",
+    "    local_anchor_a=(0, 0),\n",
+    "    local_anchor_b=(0, 0),\n",
+    ")\n",
+    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-10,-12),(10,12)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7e8fb17d-1dda-45cb-98df-6b98be5b4e6c",
+   "metadata": {},
+   "source": [
+    "## Tutorial 3.3: Revolute Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a2686e5f-3fa2-412d-8a40-2e9a67123d43",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "bodies = []\n",
+    "b = world.create_static_body(position=(0, 15))\n",
+    "bodies.append(b)\n",
+    "for i in range(5):\n",
+    "    b = world.create_dynamic_body(\n",
+    "        position=(i * 4 + 2, 15),\n",
+    "        fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n",
+    "        linear_damping=0.0,\n",
+    "        angular_damping=0.0,\n",
+    "    )\n",
+    "    bodies.append(b)\n",
+    "world.create_revolute_joint(\n",
+    "    bodies[0], bodies[1], local_anchor_a=(0, 0), local_anchor_b=(-2, 0.0)\n",
+    ")\n",
+    "for i in range(1, len(bodies) - 1):\n",
+    "    a = bodies[i]\n",
+    "    b = bodies[i + 1]\n",
+    "    world.create_revolute_joint(a, b, local_anchor_a=(2, 0.0), local_anchor_b=(-2, 0.0))\n",
+    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-20,-10),(20,20)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9caa18f0-4eb7-4e72-8445-8f6d096d9465",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Tutorial 3.4: Weld Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3d74fb2f-7da5-4ad7-8f21-fba1f97acee2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# the world\n",
+    "world = b2d.world(gravity=(0, -10))\n",
+    "\n",
+    "\n",
+    "bodies = []\n",
+    "\n",
+    "# create  a static body as anchor\n",
+    "b = world.create_static_body(\n",
+    "    position=(0, 4), fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]))\n",
+    ")\n",
+    "bodies.append(b)\n",
+    "\n",
+    "for i in range(4):\n",
+    "    b = world.create_dynamic_body(\n",
+    "        position=(i + 1.0, 4),\n",
+    "        fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]), density=0.1),\n",
+    "        linear_damping=2.5,\n",
+    "        angular_damping=2.5,\n",
+    "    )\n",
+    "    bodies.append(b)\n",
+    "\n",
+    "for i in range(len(bodies) - 1):\n",
+    "    a = bodies[i]\n",
+    "    b = bodies[i + 1]\n",
+    "    world.create_weld_joint(\n",
+    "        a,\n",
+    "        b,\n",
+    "        local_anchor_a=(0.5, 0.5),\n",
+    "        local_anchor_b=(-0.5, 0.5),\n",
+    "        damping=0.1,\n",
+    "        reference_angle=0,\n",
+    "        stiffness=20,\n",
+    "    )\n",
+    "    world.create_weld_joint(\n",
+    "        a,\n",
+    "        b,\n",
+    "        local_anchor_a=(0.5, -0.5),\n",
+    "        local_anchor_b=(-0.5, -0.5),\n",
+    "        damping=0.1,\n",
+    "        reference_angle=0,\n",
+    "        stiffness=20,\n",
+    "    )\n",
+    "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((0,-5),(5,5)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7373461e-d1fa-4ad9-aeaa-048287839fd9",
+   "metadata": {},
+   "source": [
+    "## Tutorial 3.5: Wheel Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1eb711e0-ae53-43ed-b0c1-c0a2fe42b407",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "edge = world.create_static_body(\n",
+    "    position=(0, 0), fixtures=b2d.fixture_def(shape=b2d.edge_shape([(-20, 0), (5, 0)]))\n",
+    ")\n",
+    "\n",
+    "# random slope\n",
+    "x = np.linspace(5, 50, 10)\n",
+    "y = np.random.rand(10) * 4 - 2\n",
+    "y[0] = 0\n",
+    "xy = np.stack([x, y]).T\n",
+    "xy = np.flip(xy, axis=0)\n",
+    "edge = world.create_static_body(\n",
+    "    position=(0, 0),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.chain_shape(xy, prev_vertex=(10, 0))),\n",
+    ")\n",
+    "# create car\n",
+    "left_wheel = world.create_dynamic_body(\n",
+    "    position=(-3, 2),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n",
+    ")\n",
+    "right_wheel = world.create_dynamic_body(\n",
+    "    position=(3, 2),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n",
+    ")\n",
+    "\n",
+    "chasis = world.create_dynamic_body(\n",
+    "    position=(0, 2),\n",
+    "    fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[3, 0.5]), density=1),\n",
+    ")\n",
+    "\n",
+    "wheel_joint_def = dict(\n",
+    "    stiffness=10,\n",
+    "    enable_motor=True,\n",
+    "    motor_speed=-100,\n",
+    "    max_motor_torque=100,\n",
+    "    collide_connected=False,\n",
+    "    enable_limit=True,\n",
+    "    lower_translation=-0.4,\n",
+    "    upper_translation=0.4,\n",
+    "    local_axis_a=(0, 1),\n",
+    ")\n",
+    "world.create_wheel_joint(chasis, left_wheel, local_anchor_a=(-3, 0), **wheel_joint_def)\n",
+    "world.create_wheel_joint(chasis, right_wheel, local_anchor_a=(3, 0), **wheel_joint_def)\n",
+    "\n",
+    "\n",
+    "b2d.plot.animate_world(world, ppm=20, t=15, bounding_box=((-10,-5),(20,5)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d9a0fffc-ae40-47ec-b185-2a6fe0dde496",
+   "metadata": {},
+   "source": [
+    "## Tutorial 3.6: Distance Joint"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3c121398-f08f-4ea0-a875-de141ba53508",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "\n",
+    "for i in range(10):\n",
+    "\n",
+    "    # create static anchor (does not need shape/fixture)\n",
+    "    anchor = world.create_static_body(position=(i, 0))\n",
+    "\n",
+    "    # 5 below the anchor\n",
+    "    body = world.create_dynamic_body(\n",
+    "        position=(i, -10),\n",
+    "        fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=0.4), density=0.5),\n",
+    "    )\n",
+    "\n",
+    "    # distance joints of various stiffness-es\n",
+    "    world.create_distance_joint(anchor, body, length=10, stiffness=0.5 * (i + 1))\n",
+    "\n",
+    "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-2,-20),(10,0)))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fb6afaff-8236-4206-85c7-3ba2de466ba9",
+   "metadata": {},
+   "source": [
+    "# Tutorial 4: Particles"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8f5c3b83-51d9-47cb-9b73-d5f8b0e03a76",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "world = b2d.world(gravity=(0, -10))\n",
+    "pdef = b2d.particle_system_def(radius=0.1)\n",
+    "psystem = world.create_particle_system(pdef)\n",
+    "\n",
+    "emitter_pos = (0, 0)\n",
+    "emitter_def = b2d.RandomizedLinearEmitterDef()\n",
+    "emitter_def.emite_rate = 400\n",
+    "emitter_def.lifetime = 5.1\n",
+    "emitter_def.size = (2, 1)\n",
+    "emitter_def.velocity = (6, 20)\n",
+    "emitter = b2d.RandomizedLinearEmitter(psystem, emitter_def)\n",
+    "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-10,-20),(20,5)), pre_step=emitter.step)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ea9d7882-d3a4-45bb-b59b-cb1c9cd33990",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/pyb2d/color_mixing.ipynb b/content/pyodide/pyb2d/color_mixing.ipynb
new file mode 100644
index 0000000..9dc274c
--- /dev/null
+++ b/content/pyodide/pyb2d/color_mixing.ipynb
@@ -0,0 +1,123 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "import random\n",
+    "import numpy\n",
+    "import b2d\n",
+    "\n",
+    "class ColorMixing(TestbedBase):\n",
+    "\n",
+    "    name = \"ColorMixing\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(ColorMixing, self).__init__(settings=settings)\n",
+    "        dimensions = [30, 30]\n",
+    "\n",
+    "        # the outer box\n",
+    "        box_shape = b2d.ChainShape()\n",
+    "        box_shape.create_loop(\n",
+    "            [\n",
+    "                (0, 0),\n",
+    "                (0, dimensions[1]),\n",
+    "                (dimensions[0], dimensions[1]),\n",
+    "                (dimensions[0], 0),\n",
+    "            ]\n",
+    "        )\n",
+    "        box = self.world.create_static_body(position=(0, 0), shape=box_shape)\n",
+    "\n",
+    "        fixtureA = b2d.fixture_def(\n",
+    "            shape=b2d.circle_shape(1), density=2.2, friction=0.2, restitution=0.5\n",
+    "        )\n",
+    "        body = self.world.create_dynamic_body(position=(13, 10), fixtures=fixtureA)\n",
+    "\n",
+    "        pdef = b2d.particle_system_def(\n",
+    "            viscous_strength=0.9,\n",
+    "            spring_strength=0.0,\n",
+    "            damping_strength=0.5,\n",
+    "            pressure_strength=0.5,\n",
+    "            color_mixing_strength=0.008,\n",
+    "            density=2,\n",
+    "        )\n",
+    "        psystem = self.world.create_particle_system(pdef)\n",
+    "        psystem.radius = 0.3\n",
+    "        psystem.damping = 1.0\n",
+    "\n",
+    "        colors = [\n",
+    "            (255, 0, 0, 255),\n",
+    "            (0, 255, 0, 255),\n",
+    "            (0, 0, 255, 255),\n",
+    "            (255, 255, 0, 255),\n",
+    "        ]\n",
+    "        posiitons = [(6, 10), (20, 10), (20, 20), (6, 20)]\n",
+    "        for color, pos in zip(colors, posiitons):\n",
+    "\n",
+    "            shape = b2d.polygon_shape(box=(5, 5), center=pos, angle=0)\n",
+    "            pgDef = b2d.particle_group_def(\n",
+    "                flags=b2d.ParticleFlag.waterParticle\n",
+    "                | b2d.ParticleFlag.colorMixingParticle,\n",
+    "                # group_flags=b2d.ParticleGroupFlag.solidParticleGroup,\n",
+    "                shape=shape,\n",
+    "                strength=1.0,\n",
+    "                color=color,\n",
+    "            )\n",
+    "            group = psystem.create_particle_group(pgDef)\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [1000,500]\n",
+    "s.scale = 8\n",
+    "s.fps = 40\n",
+    "\n",
+    "tb = b2d.testbed.run(ColorMixing, backend=JupyterAsyncGui, gui_settings=s)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/pyb2d/games/angry_shapes.ipynb b/content/pyodide/pyb2d/games/angry_shapes.ipynb
new file mode 100644
index 0000000..cb199ef
--- /dev/null
+++ b/content/pyodide/pyb2d/games/angry_shapes.ipynb
@@ -0,0 +1,419 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2859de40-f927-4790-b192-c5b0531058f7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8ac3e93b-3e9e-4cd7-a183-0214b0dcb513",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "import math\n",
+    "import numpy\n",
+    "import b2d\n",
+    "\n",
+    "class AngryShapes(TestbedBase):\n",
+    "\n",
+    "    name = \"AngryShapes\"\n",
+    "\n",
+    "    class Settings(TestbedBase.Settings):\n",
+    "        substeps: int = 2\n",
+    "\n",
+    "    def draw_segment(self, p1, p2, color, line_width=1):\n",
+    "        screen_p1 = self._point(self.world_to_screen(p1))\n",
+    "        screen_p2 = self._point(self.world_to_screen(p2))\n",
+    "        screen_color = self._uint8_color(color)\n",
+    "        screen_line_width = self._line_width(line_width)\n",
+    "\n",
+    "        cv.line(self._image, screen_p1, screen_p2, screen_color, screen_line_width)\n",
+    "\n",
+    "    def draw_polygon(self, vertices, color, line_width=1):\n",
+    "        # todo add C++ function for this\n",
+    "        screen_vertices = numpy.array(\n",
+    "            [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n",
+    "        )\n",
+    "        screen_color = self._uint8_color(color)\n",
+    "        screen_line_width = self._line_width(line_width)\n",
+    "\n",
+    "        cv.polylines(\n",
+    "            self._image, [screen_vertices], True, screen_color, screen_line_width, 8\n",
+    "        )\n",
+    "\n",
+    "    def draw_solid_polygon(self, vertices, color):\n",
+    "        # todo add C++ function for this\n",
+    "        screen_vertices = numpy.array(\n",
+    "            [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n",
+    "        )\n",
+    "        screen_color = self._uint8_color(color)\n",
+    "\n",
+    "        cv.fillPoly(self._image, [screen_vertices], screen_color, 8)\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(AngryShapes, self).__init__(settings=settings)\n",
+    "\n",
+    "        self.targets = []\n",
+    "        self.projectiles = []\n",
+    "        self.marked_for_destruction = []\n",
+    "        self.emitter = None\n",
+    "\n",
+    "        # particle system\n",
+    "        pdef = b2d.particle_system_def(\n",
+    "            viscous_strength=0.9,\n",
+    "            spring_strength=0.0,\n",
+    "            damping_strength=100.5,\n",
+    "            pressure_strength=1.0,\n",
+    "            color_mixing_strength=0.05,\n",
+    "            density=0.1,\n",
+    "        )\n",
+    "\n",
+    "        self.psystem = self.world.create_particle_system(pdef)\n",
+    "        self.psystem.radius = 1\n",
+    "        self.psystem.damping = 0.5\n",
+    "\n",
+    "        self.build_outer_box()\n",
+    "        self.build_castle()\n",
+    "        self.build_launcher()\n",
+    "        self.arm_launcher()\n",
+    "        self.build_explosives()\n",
+    "\n",
+    "    def build_outer_box(self):\n",
+    "        # the outer box\n",
+    "\n",
+    "        shape = b2d.edge_shape([(100, 0), (600, 0)])\n",
+    "        box = self.world.create_static_body(\n",
+    "            position=(0, 0), fixtures=b2d.fixture_def(shape=shape, friction=1)\n",
+    "        )\n",
+    "\n",
+    "    def build_target(self, pos):\n",
+    "        t = self.world.create_dynamic_body(\n",
+    "            position=pos,\n",
+    "            fixtures=[\n",
+    "                b2d.fixture_def(shape=b2d.circle_shape(radius=4), density=1.0),\n",
+    "                b2d.fixture_def(\n",
+    "                    shape=b2d.circle_shape(radius=2, pos=(3, 3)), density=1.0\n",
+    "                ),\n",
+    "                b2d.fixture_def(\n",
+    "                    shape=b2d.circle_shape(radius=2, pos=(-3, 3)), density=1.0\n",
+    "                ),\n",
+    "            ],\n",
+    "            linear_damping=0,\n",
+    "            angular_damping=0,\n",
+    "            user_data=\"target\",\n",
+    "        )\n",
+    "        self.targets.append(t)\n",
+    "\n",
+    "    def build_castle(self):\n",
+    "        def build_pyramid(offset, bar_shape, n):\n",
+    "            def build_brick(pos, size):\n",
+    "                hsize = [s / 2 for s in size]\n",
+    "                self.world.create_dynamic_body(\n",
+    "                    position=(\n",
+    "                        pos[0] + hsize[0] + offset[0],\n",
+    "                        pos[1] + hsize[1] + offset[1],\n",
+    "                    ),\n",
+    "                    fixtures=b2d.fixture_def(\n",
+    "                        shape=b2d.polygon_shape(box=hsize), density=8\n",
+    "                    ),\n",
+    "                    user_data=\"brick\",\n",
+    "                )\n",
+    "\n",
+    "            bar_length = bar_shape[0]\n",
+    "            bar_width = bar_shape[1]\n",
+    "\n",
+    "            nxm = n\n",
+    "            for y in range(nxm):\n",
+    "                py = y * (bar_length + bar_width)\n",
+    "                nx = nxm - y\n",
+    "                for x in range(nx):\n",
+    "                    px = x * bar_length + y * (bar_length) / 2.0\n",
+    "                    if y + 1 < nxm - 1:\n",
+    "                        if x == 0:\n",
+    "                            px += bar_width / 2\n",
+    "                        if x + 1 == nx:\n",
+    "                            px -= bar_width / 2\n",
+    "\n",
+    "                    build_brick((px, py), (bar_width, bar_length))\n",
+    "                    if x < nx - 1:\n",
+    "                        self.build_target(\n",
+    "                            pos=(\n",
+    "                                px + offset[0] + bar_length / 2,\n",
+    "                                py + offset[1] + bar_width,\n",
+    "                            )\n",
+    "                        )\n",
+    "                        build_brick(\n",
+    "                            (px + bar_width / 2, py + bar_length),\n",
+    "                            (bar_length, bar_width),\n",
+    "                        )\n",
+    "\n",
+    "        build_pyramid(offset=(100, 0), bar_shape=[40, 4], n=4)\n",
+    "        build_pyramid(offset=(400, 0), bar_shape=[30, 3], n=4)\n",
+    "\n",
+    "    def build_launcher(self):\n",
+    "\n",
+    "        self.launcher_anchor_pos = (30, 0)\n",
+    "        self.launcher_anchor = self.world.create_static_body(\n",
+    "            position=self.launcher_anchor_pos\n",
+    "        )\n",
+    "\n",
+    "    def arm_launcher(self):\n",
+    "        self.reload_time = None\n",
+    "        self.is_armed = True\n",
+    "        self.projectile_radius = 3\n",
+    "        projectile_pos = (self.launcher_anchor_pos[0], self.launcher_anchor_pos[1] / 2)\n",
+    "\n",
+    "        self.projectile = self.world.create_dynamic_body(\n",
+    "            position=projectile_pos,\n",
+    "            fixtures=b2d.fixture_def(\n",
+    "                shape=b2d.circle_shape(radius=self.projectile_radius), density=100.0\n",
+    "            ),\n",
+    "            linear_damping=0,\n",
+    "            angular_damping=0,\n",
+    "            user_data=\"projectile\",\n",
+    "        )\n",
+    "        self.projectiles.append(self.projectile)\n",
+    "        self.projectile_joint = self.world.create_distance_joint(\n",
+    "            self.launcher_anchor, self.projectile, length=1, stiffness=10000\n",
+    "        )\n",
+    "        self.mouse_joint = None\n",
+    "\n",
+    "    def build_explosives(self):\n",
+    "        self.explosives = []\n",
+    "\n",
+    "    def on_mouse_down(self, p):\n",
+    "        if self.is_armed:\n",
+    "            body = self.world.find_body(pos=p)\n",
+    "            if body is not None and body.user_data is not None:\n",
+    "                print(\"got body\")\n",
+    "                if body.user_data == \"projectile\":\n",
+    "                    print(\"got projectile\")\n",
+    "                    kwargs = dict(\n",
+    "                        body_a=self.groundbody,\n",
+    "                        body_b=body,\n",
+    "                        target=p,\n",
+    "                        max_force=50000.0 * body.mass,\n",
+    "                        stiffness=10000.0,\n",
+    "                    )\n",
+    "\n",
+    "                    self.mouse_joint = self.world.create_mouse_joint(**kwargs)\n",
+    "                    body.awake = True\n",
+    "                    return True\n",
+    "\n",
+    "        return False\n",
+    "\n",
+    "    def on_mouse_move(self, p):\n",
+    "        if self.is_armed:\n",
+    "            if self.mouse_joint is not None:\n",
+    "                self.mouse_joint.target = p\n",
+    "                return True\n",
+    "        return False\n",
+    "\n",
+    "    def on_mouse_up(self, p):\n",
+    "        if self.is_armed:\n",
+    "            if self.mouse_joint is not None:\n",
+    "                self.world.destroy_joint(self.mouse_joint)\n",
+    "                if self.projectile_joint is not None:\n",
+    "                    self.world.destroy_joint(self.projectile_joint)\n",
+    "                self.projectile_joint = None\n",
+    "                self.mouse_joint = None\n",
+    "                delta = self.launcher_anchor.position - b2d.vec2(p)\n",
+    "                scaled_delta = delta * 50000.0\n",
+    "                print(scaled_delta)\n",
+    "\n",
+    "                self.projectile.apply_linear_impulse_to_center(scaled_delta, True)\n",
+    "                self.reload_time = self.elapsed_time + 1.0\n",
+    "                self.is_armed = False\n",
+    "        return False\n",
+    "\n",
+    "    def begin_contact(self, contact):\n",
+    "        body_a = contact.body_a\n",
+    "        body_b = contact.body_b\n",
+    "        ud_a = body_a.user_data\n",
+    "        ud_b = body_b.user_data\n",
+    "        if ud_b == \"projectile\":\n",
+    "            body_a, body_b = body_b, body_a\n",
+    "            ud_a, ud_b = ud_b, ud_a\n",
+    "        if ud_a == \"projectile\":\n",
+    "\n",
+    "            if ud_b == \"target\" or ud_b == \"brick\":\n",
+    "                self.marked_for_destruction.append(body_a)\n",
+    "                emitter_def = b2d.RandomizedRadialEmitterDef()\n",
+    "                emitter_def.emite_rate = 20000\n",
+    "                emitter_def.lifetime = 0.7\n",
+    "                emitter_def.enabled = True\n",
+    "                emitter_def.inner_radius = 0.0\n",
+    "                emitter_def.outer_radius = 1.0\n",
+    "                emitter_def.velocity_magnitude = 1000.0\n",
+    "                emitter_def.start_angle = 0\n",
+    "                emitter_def.stop_angle = math.pi\n",
+    "                emitter_def.transform = b2d.Transform(body_a.position, b2d.Rot(0))\n",
+    "                self.emitter = b2d.RandomizedRadialEmitter(self.psystem, emitter_def)\n",
+    "                self.emitter_die_time = self.elapsed_time + 0.02\n",
+    "\n",
+    "    def pre_step(self, dt):\n",
+    "\n",
+    "        if self.reload_time is not None:\n",
+    "            if self.elapsed_time >= self.reload_time:\n",
+    "                self.arm_launcher()\n",
+    "\n",
+    "        # delete contact bodies\n",
+    "        for body in self.marked_for_destruction:\n",
+    "            if body in self.projectiles:\n",
+    "                self.projectiles.remove(body)\n",
+    "                self.world.destroy_body(body)\n",
+    "            if body == self.projectile:\n",
+    "                self.reload_time = self.elapsed_time + 1.0\n",
+    "            self.marked_for_destruction = []\n",
+    "\n",
+    "        # delete bodies which have fallen down\n",
+    "        for body in self.world.bodies:\n",
+    "            if body.position.y < -100:\n",
+    "                if body.user_data == \"projectile\":\n",
+    "                    self.projectiles.remove(body)\n",
+    "                if body.user_data == \"target\":\n",
+    "                    self.targets.remove(body)\n",
+    "                self.world.destroy_body(body)\n",
+    "\n",
+    "        # emmiter\n",
+    "        if self.emitter is not None:\n",
+    "            self.emitter.step(dt)\n",
+    "            if self.elapsed_time >= self.emitter_die_time:\n",
+    "                self.emitter = None\n",
+    "\n",
+    "    def draw_target(self, target):\n",
+    "        center = target.position\n",
+    "        center_l = target.get_world_point((-3, 3))\n",
+    "        center_r = target.get_world_point((3, 3))\n",
+    "        eye_left = target.get_world_point((-1, 1))\n",
+    "        eye_right = target.get_world_point((1, 1))\n",
+    "        pink = [c / 255 for c in (248, 24, 148)]\n",
+    "\n",
+    "        self.debug_draw.draw_solid_circle(\n",
+    "            center=center, radius=4, axis=None, color=pink\n",
+    "        )\n",
+    "        self.debug_draw.draw_solid_circle(\n",
+    "            center=center_l, radius=2, axis=None, color=pink\n",
+    "        )\n",
+    "        self.debug_draw.draw_solid_circle(\n",
+    "            center=center_r, radius=2, axis=None, color=pink\n",
+    "        )\n",
+    "\n",
+    "        # schnautze\n",
+    "        nose_center = target.get_world_point((0, -1))\n",
+    "        nose_center_l = target.get_world_point((-0.3, -1))\n",
+    "        nose_center_r = target.get_world_point((0.3, -1))\n",
+    "\n",
+    "        self.debug_draw.draw_circle(\n",
+    "            center=nose_center,\n",
+    "            radius=2,\n",
+    "            # axis=None,\n",
+    "            color=(1, 1, 1),\n",
+    "            line_width=0.2,\n",
+    "        )\n",
+    "        # eyes\n",
+    "        for nose_center in [nose_center_l, nose_center_r]:\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                center=nose_center, radius=0.6, axis=None, color=(1, 1, 1)\n",
+    "            )\n",
+    "        # eyes\n",
+    "        for eye_center in [eye_left, eye_right]:\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n",
+    "            )\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n",
+    "            )\n",
+    "\n",
+    "    def draw_projectile(self, projectile):\n",
+    "\n",
+    "        center = projectile.position\n",
+    "        # center_l = target.get_world_point((-3,3))\n",
+    "        # center_r = target.get_world_point(( 3,3))\n",
+    "        eye_left = projectile.get_world_point((-1, 1))\n",
+    "        eye_right = projectile.get_world_point((1, 1))\n",
+    "\n",
+    "        self.debug_draw.draw_solid_circle(\n",
+    "            center=center,\n",
+    "            radius=self.projectile_radius * 1.1,\n",
+    "            axis=None,\n",
+    "            color=(1, 0, 0),\n",
+    "        )\n",
+    "\n",
+    "        # eyes\n",
+    "        for eye_center in [eye_left, eye_right]:\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n",
+    "            )\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n",
+    "            )\n",
+    "\n",
+    "    def post_debug_draw(self):\n",
+    "        for target in self.targets:\n",
+    "            self.draw_target(target)\n",
+    "\n",
+    "        for projectile in self.projectiles:\n",
+    "            self.draw_projectile(projectile)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6df7c8b9-216b-4fd2-8ee8-aeec294e149d",
+   "metadata": {},
+   "source": [
+    "# Controlls\n",
+    "* To play this game, click and drag the red ball and release it to shot it.\n",
+    "* Use the mouse-wheel to zoom in/out, a\n",
+    "* Click and drag in the empty space to translate the view."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "df412e76-7a9a-4e1d-8bc7-c02e222e10dc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [1000,500]\n",
+    "s.scale = 2\n",
+    "s.translate = [100,100]\n",
+    "tb = b2d.testbed.run(AngryShapes, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/pyb2d/games/billiard.ipynb b/content/pyodide/pyb2d/games/billiard.ipynb
new file mode 100644
index 0000000..13fff3a
--- /dev/null
+++ b/content/pyodide/pyb2d/games/billiard.ipynb
@@ -0,0 +1,299 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy\n",
+    "import b2d\n",
+    "import math\n",
+    "import random\n",
+    "\n",
+    "from b2d.testbed import TestbedBase\n",
+    "\n",
+    "class Billiard(TestbedBase):\n",
+    "\n",
+    "    name = \"Billiard\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(Billiard, self).__init__(gravity=(0, 0), settings=settings)\n",
+    "        dimensions = [30, 50]\n",
+    "        self.dimensions = dimensions\n",
+    "\n",
+    "        # the outer box\n",
+    "        box_shape = b2d.ChainShape()\n",
+    "        box_shape.create_loop(\n",
+    "            [\n",
+    "                (0, 0),\n",
+    "                (0, dimensions[1]),\n",
+    "                (dimensions[0], dimensions[1]),\n",
+    "                (dimensions[0], 0),\n",
+    "            ]\n",
+    "        )\n",
+    "        self.ball_radius = 1\n",
+    "        box = self.world.create_static_body(\n",
+    "            position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n",
+    "        )\n",
+    "\n",
+    "        self.place_balls()\n",
+    "        self.place_pockets()\n",
+    "\n",
+    "        # mouse interaction\n",
+    "        self._selected_ball = None\n",
+    "        self._selected_ball_pos = None\n",
+    "        self._last_pos = None\n",
+    "\n",
+    "        # balls to be destroyed in the next step\n",
+    "        # since they are in the pocket\n",
+    "        self._to_be_destroyed = []\n",
+    "\n",
+    "    def place_pockets(self):\n",
+    "        pocket_radius = 1\n",
+    "        self.pockets = []\n",
+    "\n",
+    "        def place_pocket(position):\n",
+    "            pocket_shape = b2d.circle_shape(radius=pocket_radius / 3)\n",
+    "            pocket = self.world.create_static_body(\n",
+    "                position=position,\n",
+    "                fixtures=b2d.fixture_def(shape=pocket_shape, is_sensor=True),\n",
+    "                user_data=(\"pocket\", None),\n",
+    "            )\n",
+    "            self.pockets.append(pocket)\n",
+    "\n",
+    "        d = pocket_radius / 2\n",
+    "\n",
+    "        place_pocket(position=(0 + d, 0 + d))\n",
+    "        place_pocket(position=(self.dimensions[0] - d, 0 + d))\n",
+    "\n",
+    "        place_pocket(position=(0 + d, self.dimensions[1] / 2))\n",
+    "        place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] / 2))\n",
+    "\n",
+    "        place_pocket(position=(0 + d, self.dimensions[1] - d))\n",
+    "        place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] - d))\n",
+    "\n",
+    "    def place_balls(self):\n",
+    "        self.balls = []\n",
+    "\n",
+    "        base_colors = [\n",
+    "            (1, 1, 0),\n",
+    "            (0, 0, 1),\n",
+    "            (1, 0, 0),\n",
+    "            (1, 0, 1),\n",
+    "            (1, 0.6, 0),\n",
+    "            (0, 1, 0),\n",
+    "            (0.7, 0.4, 0.4),\n",
+    "        ]\n",
+    "        colors = []\n",
+    "        for color in base_colors:\n",
+    "            # ``full`` ball\n",
+    "            colors.append((color, color))\n",
+    "            # ``half`` ball (half white)\n",
+    "            colors.append((color, (1, 1, 1)))\n",
+    "\n",
+    "        random.shuffle(colors)\n",
+    "        colors.insert(4, ((0, 0, 0), (0, 0, 0)))  # black\n",
+    "\n",
+    "        n_y = 5\n",
+    "        c_x = self.dimensions[0] / 2\n",
+    "        diameter = (self.ball_radius * 2) * 1.01\n",
+    "\n",
+    "        bi = 0\n",
+    "        for y in range(n_y):\n",
+    "\n",
+    "            py = y * diameter * 0.5 * math.sqrt(3)\n",
+    "            n_x = y + 1\n",
+    "            ox = diameter * (n_y - y) / 2\n",
+    "            for x in range(y + 1):\n",
+    "                position = (x * diameter + 10 + ox, py + 30)\n",
+    "                self.create_billard_ball(position=position, color=colors[bi])\n",
+    "                bi += 1\n",
+    "\n",
+    "        self.create_billard_ball(position=(c_x, 10), color=((1, 1, 1), (1, 1, 1)))\n",
+    "\n",
+    "    def create_billard_ball(self, position, color):\n",
+    "\n",
+    "        ball = self.world.create_dynamic_body(\n",
+    "            position=position,\n",
+    "            fixtures=b2d.fixture_def(\n",
+    "                shape=b2d.circle_shape(radius=self.ball_radius),\n",
+    "                density=1.0,\n",
+    "                restitution=0.8,\n",
+    "            ),\n",
+    "            linear_damping=0.8,\n",
+    "            user_data=(\"ball\", color),\n",
+    "            fixed_rotation=True,\n",
+    "        )\n",
+    "        self.balls.append(ball)\n",
+    "\n",
+    "    def begin_contact(self, contact):\n",
+    "        body_a = contact.body_a\n",
+    "        body_b = contact.body_b\n",
+    "\n",
+    "        ud_a = body_a.user_data\n",
+    "        ud_b = body_b.user_data\n",
+    "        if ud_a is None or ud_b is None:\n",
+    "            return\n",
+    "\n",
+    "        if ud_b[0] == \"ball\":\n",
+    "            body_a, body_b = body_b, body_a\n",
+    "            ud_a, ud_b = ud_b, ud_a\n",
+    "\n",
+    "        if ud_a[0] == \"ball\" and ud_b[0] == \"pocket\":\n",
+    "            self._to_be_destroyed.append(body_a)\n",
+    "\n",
+    "    def pre_step(self, dt):\n",
+    "        for b in self._to_be_destroyed:\n",
+    "            self.balls.remove(b)\n",
+    "            self.world.destroy_body(b)\n",
+    "        self._to_be_destroyed = []\n",
+    "\n",
+    "    def ball_at_position(self, pos):\n",
+    "        body = self.world.find_body(pos)\n",
+    "        if body is not None:\n",
+    "            user_data = body.user_data\n",
+    "            if user_data is not None and user_data[0] == \"ball\":\n",
+    "                return body\n",
+    "        return None\n",
+    "\n",
+    "    def on_mouse_down(self, pos):\n",
+    "        body = self.ball_at_position(pos)\n",
+    "        if body is not None:\n",
+    "            self._selected_ball = body\n",
+    "            self._selected_ball_pos = pos\n",
+    "            return True\n",
+    "\n",
+    "        return False\n",
+    "\n",
+    "    def on_mouse_move(self, pos):\n",
+    "        if self._selected_ball is not None:\n",
+    "            self._last_pos = pos\n",
+    "            return True\n",
+    "        return False\n",
+    "\n",
+    "    def on_mouse_up(self, pos):\n",
+    "        if self._selected_ball is not None:\n",
+    "            self._last_pos = pos\n",
+    "            # if the mouse is in the starting ball itself we do nothing\n",
+    "            if self.ball_at_position(pos) != self._selected_ball:\n",
+    "                delta = b2d.vec2(self._selected_ball_pos) - b2d.vec2(self._last_pos)\n",
+    "                delta *= 100.0\n",
+    "                self._selected_ball.apply_linear_impulse(\n",
+    "                    delta, self._selected_ball_pos, True\n",
+    "                )\n",
+    "        self._selected_ball = None\n",
+    "        self._selected_ball_pos = None\n",
+    "        self._last_pos = None\n",
+    "        return False\n",
+    "\n",
+    "    def post_debug_draw(self):\n",
+    "\n",
+    "        for pocket in self.pockets:\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                pocket.position, self.ball_radius, (1, 0), (1, 1, 1)\n",
+    "            )\n",
+    "\n",
+    "        for ball in self.balls:\n",
+    "            _, (color0, color1) = ball.user_data\n",
+    "\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                ball.position, self.ball_radius, (1, 0), color0\n",
+    "            )\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                ball.position, self.ball_radius / 2, (1, 0), color1\n",
+    "            )\n",
+    "            self.debug_draw.draw_circle(\n",
+    "                ball.position, self.ball_radius, (1, 1, 1), line_width=0.1\n",
+    "            )\n",
+    "\n",
+    "        if self._selected_ball is not None:\n",
+    "\n",
+    "            # draw circle around selected ball\n",
+    "            self.debug_draw.draw_circle(\n",
+    "                self._selected_ball.position,\n",
+    "                self.ball_radius * 2,\n",
+    "                (1, 1, 1),\n",
+    "                line_width=0.2,\n",
+    "            )\n",
+    "\n",
+    "            # mark position on selected ball with red dot\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                self._selected_ball_pos, self.ball_radius * 0.2, (1, 0), (1, 0, 0)\n",
+    "            )\n",
+    "\n",
+    "            # draw the line between marked pos on ball and last pos\n",
+    "            if self._last_pos is not None:\n",
+    "                self.debug_draw.draw_segment(\n",
+    "                    self._selected_ball_pos, self._last_pos, (1, 1, 1), line_width=0.2\n",
+    "                )"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Controlls\n",
+    "* To play this game, click and hold inside a billiard ball, move and release the mouse to shoot the ball.\n",
+    "* Use the mouse-wheel to zoom in/out, a\n",
+    "* Click and drag in the empty space to translate the view."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "backend = JupyterAsyncGui\n",
+    "s = backend.Settings()\n",
+    "s.resolution = [500,600]\n",
+    "s.scale = 8\n",
+    "s.fps = 40\n",
+    "s.translate = [125,100]\n",
+    "b2d.testbed.run(Billiard, backend=backend, gui_settings=s);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/pyb2d/games/goo.ipynb b/content/pyodide/pyb2d/games/goo.ipynb
new file mode 100644
index 0000000..cbe018c
--- /dev/null
+++ b/content/pyodide/pyb2d/games/goo.ipynb
@@ -0,0 +1,575 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('networkx')\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import b2d\n",
+    "from b2d.testbed import TestbedBase\n",
+    "import math\n",
+    "import random\n",
+    "import numpy\n",
+    "from functools import partial\n",
+    "import networkx\n",
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "\n",
+    "def best_pairwise_distance(data, f, distance):\n",
+    "    n = len(data)\n",
+    "    best = (None, None, float(\"inf\"))\n",
+    "    for i in range(n - 1):\n",
+    "        da = f(data[i])\n",
+    "        for j in range(i + 1, n):\n",
+    "            db = f(data[j])\n",
+    "\n",
+    "            d = distance(da, db)\n",
+    "            if d < best[2]:\n",
+    "                best = (i, j, d)\n",
+    "    return best\n",
+    "\n",
+    "class Level(object):\n",
+    "    def __init__(self, testbed):\n",
+    "        self.testbed = testbed\n",
+    "        self.world = testbed.world\n",
+    "\n",
+    "        self.gap_size = 15\n",
+    "        self.kill_sensors_height = 0.5\n",
+    "        self.usable_size = 20\n",
+    "        self.h = 10\n",
+    "        self.end_zone_height = 3\n",
+    "\n",
+    "        self.outline_verts = [\n",
+    "            (0, self.h),\n",
+    "            (0, 2 * self.h),\n",
+    "            (0, self.h),\n",
+    "            (self.usable_size, self.h),\n",
+    "            (self.usable_size, 0),\n",
+    "            (self.usable_size + self.gap_size, 0),\n",
+    "            (self.usable_size + self.gap_size, self.h),\n",
+    "            (2 * self.usable_size + self.gap_size, self.h),\n",
+    "            (2 * self.usable_size + self.gap_size, 2 * self.h),\n",
+    "        ]\n",
+    "\n",
+    "        # outline of the level\n",
+    "        shape = b2d.chain_shape(vertices=numpy.flip(self.outline_verts, axis=0))\n",
+    "        self.outline = self.world.create_static_body(position=(0, 0), shape=shape)\n",
+    "\n",
+    "        # kill sensors\n",
+    "        self.kill_sensor_pos = (\n",
+    "            self.usable_size + self.gap_size / 2,\n",
+    "            self.kill_sensors_height / 2,\n",
+    "        )\n",
+    "\n",
+    "        shape = b2d.polygon_shape(box=(self.gap_size / 2, self.kill_sensors_height / 2))\n",
+    "        self._kill_sensor = self.world.create_static_body(\n",
+    "            position=self.kill_sensor_pos,\n",
+    "            fixtures=b2d.fixture_def(shape=shape, is_sensor=True),\n",
+    "        )\n",
+    "        self._kill_sensor.user_data = \"destroyer\"\n",
+    "\n",
+    "        # end sensor\n",
+    "        shape = b2d.polygon_shape(box=(self.usable_size / 2, self.end_zone_height / 2))\n",
+    "        self._end_sensor = self.world.create_static_body(\n",
+    "            position=(\n",
+    "                1.5 * self.usable_size + self.gap_size,\n",
+    "                self.h + self.end_zone_height / 2,\n",
+    "            ),\n",
+    "            fixtures=b2d.fixture_def(shape=shape, is_sensor=True),\n",
+    "        )\n",
+    "        self._end_sensor.user_data = \"goal\"\n",
+    "\n",
+    "        goo_radius = 1\n",
+    "        a = self.testbed.insert_goo(\n",
+    "            pos=(self.usable_size / 3, self.h + goo_radius), static=True\n",
+    "        )\n",
+    "        b = self.testbed.insert_goo(\n",
+    "            pos=(self.usable_size * 2 / 3, self.h + goo_radius), static=True\n",
+    "        )\n",
+    "        c = self.testbed.insert_goo(\n",
+    "            pos=(self.usable_size * 1 / 2, self.h + goo_radius + 4), static=False\n",
+    "        )\n",
+    "\n",
+    "        self.testbed.connect_goos(a, b)\n",
+    "        self.testbed.connect_goos(a, c)\n",
+    "        self.testbed.connect_goos(b, c)\n",
+    "\n",
+    "    def draw(self, debug_draw):\n",
+    "\n",
+    "        # draw outline\n",
+    "        for i in range(len(self.outline_verts) - 1):\n",
+    "            debug_draw.draw_segment(\n",
+    "                self.outline_verts[i],\n",
+    "                self.outline_verts[i + 1],\n",
+    "                color=(1, 1, 0),\n",
+    "                line_width=0.3,\n",
+    "            )\n",
+    "\n",
+    "        left = list(self.kill_sensor_pos)\n",
+    "        left[0] -= self.gap_size / 2\n",
+    "        left[1] += self.kill_sensors_height / 2\n",
+    "\n",
+    "        right = list(self.kill_sensor_pos)\n",
+    "        right[0] += self.gap_size / 2\n",
+    "        right[1] += self.kill_sensors_height / 2\n",
+    "        debug_draw.draw_segment(left, right, (1, 0, 0), line_width=0.4)\n",
+    "\n",
+    "\n",
+    "class FindGoos(b2d.QueryCallback):\n",
+    "    def __init__(self):\n",
+    "        super(FindGoos, self).__init__()\n",
+    "        self.goos = []\n",
+    "\n",
+    "    def report_fixture(self, fixture):\n",
+    "        body = fixture.body\n",
+    "        if body.user_data == \"goo\":\n",
+    "            self.goos.append(body)\n",
+    "        return True\n",
+    "\n",
+    "\n",
+    "class Goo(TestbedBase):\n",
+    "\n",
+    "    name = \"Goo\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(Goo, self).__init__(settings=settings)\n",
+    "\n",
+    "        self.goo_graph = networkx.Graph()\n",
+    "        self.level = Level(testbed=self)\n",
+    "\n",
+    "        # mouse related\n",
+    "        self.last_mouse_pos = None\n",
+    "        self.is_mouse_down = False\n",
+    "        self.could_place_goo_when_mouse_was_down = False\n",
+    "\n",
+    "        # callback to draw tentative placement\n",
+    "        self.draw_callback = None\n",
+    "\n",
+    "        # goos marked for destruction\n",
+    "        self.goo_to_destroy = []\n",
+    "\n",
+    "        # joints marked for destruction\n",
+    "        self.joints_to_destroy = []\n",
+    "        self.gamma = 0.003\n",
+    "        self.break_threshold = 0.5\n",
+    "\n",
+    "        # time point when goo can be inserted\n",
+    "        self.insert_time_point = 0\n",
+    "        self.insert_delay = 1.0\n",
+    "\n",
+    "        # handle finishing of level\n",
+    "        self.with_goal_contact = dict()\n",
+    "\n",
+    "        # amount of seconds one has to be in the finishing zone\n",
+    "        self.win_delay = 3.0\n",
+    "\n",
+    "        # particle system will be defined an used on win!\n",
+    "        # this is then used for some kind of fireworks\n",
+    "        self.psystem = None\n",
+    "        self.emitter = None\n",
+    "        self.emitter_stop_time = None\n",
+    "        self.emitter_start_time = None\n",
+    "\n",
+    "    # trigger some fireworks on win\n",
+    "    def on_win(self, win_body):\n",
+    "\n",
+    "        if self.psystem is None:\n",
+    "            # particle system\n",
+    "            pdef = b2d.particle_system_def(\n",
+    "                viscous_strength=0.9,\n",
+    "                spring_strength=0.0,\n",
+    "                damping_strength=100.5,\n",
+    "                pressure_strength=1.0,\n",
+    "                color_mixing_strength=0.05,\n",
+    "                density=0.1,\n",
+    "            )\n",
+    "\n",
+    "            self.psystem = self.world.create_particle_system(pdef)\n",
+    "            self.psystem.radius = 0.1\n",
+    "            self.psystem.damping = 0.5\n",
+    "\n",
+    "            emitter_def = b2d.RandomizedRadialEmitterDef()\n",
+    "            emitter_def.emite_rate = 2000\n",
+    "            emitter_def.lifetime = 0.9\n",
+    "            emitter_def.enabled = True\n",
+    "            emitter_def.inner_radius = 0.0\n",
+    "            emitter_def.outer_radius = 0.1\n",
+    "            emitter_def.velocity_magnitude = 1000.0\n",
+    "            emitter_def.start_angle = 0\n",
+    "            emitter_def.stop_angle = 2 * math.pi\n",
+    "            emitter_def.transform = b2d.Transform(\n",
+    "                win_body.position + b2d.vec2(0, 20), b2d.Rot(0)\n",
+    "            )\n",
+    "            self.emitter = b2d.RandomizedRadialEmitter(self.psystem, emitter_def)\n",
+    "            self.emitter_stop_time = self.elapsed_time + 0.2\n",
+    "\n",
+    "    def draw_goo(self, pos, angle, body=None):\n",
+    "        self.debug_draw.draw_solid_circle(pos, 1, axis=None, color=(1, 0, 1))\n",
+    "        self.debug_draw.draw_circle(pos, 1.1, (1, 1, 1), line_width=0.1)\n",
+    "\n",
+    "        if body is not None:\n",
+    "            centers = [\n",
+    "                body.get_world_point((-0.3, 0.2)),\n",
+    "                body.get_world_point((0.3, 0.2)),\n",
+    "            ]\n",
+    "            for center in centers:\n",
+    "                self.debug_draw.draw_solid_circle(\n",
+    "                    center, 0.4, axis=None, color=(1, 1, 1)\n",
+    "                )\n",
+    "                self.debug_draw.draw_solid_circle(\n",
+    "                    center, 0.2, axis=None, color=(0, 0, 0)\n",
+    "                )\n",
+    "\n",
+    "    def draw_edge(self, pos_a, pos_b, stress):\n",
+    "        no_stress = numpy.array([1, 1, 1])\n",
+    "        has_stress = numpy.array([1, 0, 0])\n",
+    "        color = (1.0 - stress) * no_stress + stress * has_stress\n",
+    "        color = tuple([float(c) for c in color])\n",
+    "        self.debug_draw.draw_segment(pos_a, pos_b, color=color, line_width=0.4)\n",
+    "\n",
+    "    def insert_goo(self, pos, static=False):\n",
+    "        if static:\n",
+    "            f = self.world.create_static_body\n",
+    "        else:\n",
+    "            f = self.world.create_dynamic_body\n",
+    "\n",
+    "        goo = f(\n",
+    "            position=pos,\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1),\n",
+    "            user_data=\"goo\",\n",
+    "        )\n",
+    "        self.goo_graph.add_node(goo)\n",
+    "        return goo\n",
+    "\n",
+    "    def connect_goos(self, goo_a, goo_b):\n",
+    "        length = (goo_a.position - goo_b.position).length\n",
+    "        joint = self.world.create_distance_joint(\n",
+    "            goo_a,\n",
+    "            goo_b,\n",
+    "            stiffness=500,\n",
+    "            damping=0.1,\n",
+    "            length=length,\n",
+    "            user_data=dict(length=length, stress=0),\n",
+    "        )\n",
+    "        self.goo_graph.add_edge(goo_a, goo_b, joint=joint)\n",
+    "\n",
+    "    def query_placement(self, pos):\n",
+    "\n",
+    "        radius = 8\n",
+    "\n",
+    "        # find all goos in around pos\n",
+    "        pos = b2d.vec2(pos)\n",
+    "        box = b2d.aabb(\n",
+    "            lower_bound=pos - b2d.vec2(radius, radius),\n",
+    "            upper_bound=pos + b2d.vec2(radius, radius),\n",
+    "        )\n",
+    "        query = FindGoos()\n",
+    "        self.world.query_aabb(query, box)\n",
+    "        goos = query.goos\n",
+    "        n_goos = len(goos)\n",
+    "\n",
+    "        if n_goos >= 2:\n",
+    "\n",
+    "            # try to insert to goo as edge between\n",
+    "            # 2 existing goos\n",
+    "            def distance(a, b, p):\n",
+    "                if self.goo_graph.has_edge(a[0], b[0]):\n",
+    "                    return float(\"inf\")\n",
+    "                return numpy.linalg.norm((a[1] + b[1]) / 2 - p)\n",
+    "\n",
+    "            i, j, best_dist = best_pairwise_distance(\n",
+    "                goos,\n",
+    "                f=lambda goo: (goo, numpy.array(goo.position)),\n",
+    "                distance=partial(distance, p=pos),\n",
+    "            )\n",
+    "\n",
+    "            if best_dist < 0.8:\n",
+    "\n",
+    "                def draw_callback():\n",
+    "                    self.draw_edge(goos[i].position, goos[j].position, stress=0)\n",
+    "\n",
+    "                def insert_callack():\n",
+    "                    self.connect_goos(goos[i], goos[j])\n",
+    "\n",
+    "                return True, draw_callback, insert_callack\n",
+    "\n",
+    "            # try to insert the goo as brand new\n",
+    "            # goo and connect it with 2 existing goos\n",
+    "            f = lambda goo: (goo, (goo.position - b2d.vec2(pos)).length)\n",
+    "\n",
+    "            def distance(a, b):\n",
+    "                if not self.goo_graph.has_edge(a[0], b[0]):\n",
+    "                    return float(\"inf\")\n",
+    "                return a[1] + b[1]\n",
+    "\n",
+    "            i, j, best_dist = best_pairwise_distance(goos, f=f, distance=distance)\n",
+    "            if best_dist < float(\"inf\"):\n",
+    "\n",
+    "                def draw_callback():\n",
+    "\n",
+    "                    self.draw_edge(pos, goos[i].position, stress=0)\n",
+    "                    self.draw_edge(pos, goos[j].position, stress=0)\n",
+    "                    self.draw_goo(pos, angle=None)\n",
+    "\n",
+    "                def insert_callack():\n",
+    "                    goo = self.insert_goo(pos=pos)\n",
+    "                    self.connect_goos(goo, goos[i])\n",
+    "                    self.connect_goos(goo, goos[j])\n",
+    "\n",
+    "                return True, draw_callback, insert_callack\n",
+    "\n",
+    "        return False, None, None\n",
+    "\n",
+    "    def on_mouse_down(self, pos):\n",
+    "        self.last_mouse_pos = pos\n",
+    "        self.is_mouse_down = True\n",
+    "        can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n",
+    "        self.could_place_goo_when_mouse_was_down = can_be_placed\n",
+    "        if can_be_placed:\n",
+    "            if self.elapsed_time < self.insert_time_point:\n",
+    "                return True\n",
+    "            self.draw_callback = draw_callback\n",
+    "            return True\n",
+    "        return False\n",
+    "\n",
+    "    def on_mouse_move(self, pos):\n",
+    "        self.last_mouse_pos = pos\n",
+    "        if self.is_mouse_down:\n",
+    "            can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n",
+    "            if can_be_placed:\n",
+    "                if self.elapsed_time < self.insert_time_point:\n",
+    "                    return True\n",
+    "                self.draw_callback = draw_callback\n",
+    "                return True\n",
+    "            else:\n",
+    "                self.draw_callback = None\n",
+    "        return self.could_place_goo_when_mouse_was_down\n",
+    "\n",
+    "    def on_mouse_up(self, pos):\n",
+    "        self.last_mouse_pos = pos\n",
+    "        self.is_mouse_down = False\n",
+    "        self.draw_callback = None\n",
+    "        can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n",
+    "        if can_be_placed:\n",
+    "            if self.elapsed_time < self.insert_time_point:\n",
+    "                return True\n",
+    "            # self.draw_callback = draw_callback\n",
+    "            insert_callback()\n",
+    "            self.insert_time_point = self.elapsed_time + self.insert_delay\n",
+    "            return True\n",
+    "        return False\n",
+    "\n",
+    "    def begin_contact(self, contact):\n",
+    "        body_a = contact.body_a\n",
+    "        body_b = contact.body_b\n",
+    "        if body_b.user_data == \"goo\":\n",
+    "            body_a, body_b = body_b, body_a\n",
+    "\n",
+    "        user_data_a = body_a.user_data\n",
+    "        user_data_b = body_b.user_data\n",
+    "        if body_a.user_data == \"goo\":\n",
+    "            if user_data_b == \"destroyer\":\n",
+    "                self.goo_to_destroy.append(body_a)\n",
+    "            elif user_data_b == \"goal\":\n",
+    "                self.with_goal_contact[body_a] = self.elapsed_time + self.win_delay\n",
+    "\n",
+    "    def end_contact(self, contact):\n",
+    "        body_a = contact.body_a\n",
+    "        body_b = contact.body_b\n",
+    "        if body_b.user_data == \"goo\":\n",
+    "            body_a, body_b = body_b, body_a\n",
+    "\n",
+    "        user_data_a = body_a.user_data\n",
+    "        user_data_b = body_b.user_data\n",
+    "        if body_a.user_data == \"goo\":\n",
+    "            if user_data_b == \"goal\":\n",
+    "                if body_a in self.with_goal_contact:\n",
+    "                    del self.with_goal_contact[body_a]\n",
+    "\n",
+    "    def pre_step(self, dt):\n",
+    "\n",
+    "        # query if goo can be inserted\n",
+    "        if (\n",
+    "            self.is_mouse_down\n",
+    "            and self.last_mouse_pos is not None\n",
+    "            and self.draw_callback is None\n",
+    "        ):\n",
+    "            can_be_placed, draw_callback, insert_callback = self.query_placement(\n",
+    "                self.last_mouse_pos\n",
+    "            )\n",
+    "            if can_be_placed and self.elapsed_time >= self.insert_time_point:\n",
+    "                self.draw_callback = draw_callback\n",
+    "\n",
+    "        # compute joint stress\n",
+    "        for goo_a, goo_b, joint in self.goo_graph.edges(data=\"joint\"):\n",
+    "            jd = joint.user_data\n",
+    "\n",
+    "            # distance based stress\n",
+    "            insert_length = jd[\"length\"]\n",
+    "            length = (goo_a.position - goo_b.position).length\n",
+    "\n",
+    "            d = length - insert_length\n",
+    "            if d > 0:\n",
+    "\n",
+    "                # reaction force based stress\n",
+    "                rf = joint.get_reaction_force(30).length\n",
+    "\n",
+    "                normalized_rf = 1.0 - math.exp(-rf * self.gamma)\n",
+    "\n",
+    "                jd[\"stress\"] = normalized_rf / self.break_threshold\n",
+    "                if normalized_rf > self.break_threshold:\n",
+    "                    self.joints_to_destroy.append((goo_a, goo_b, joint))\n",
+    "\n",
+    "            else:\n",
+    "                jd[\"stress\"] = 0\n",
+    "\n",
+    "        for goo_a, goo_b, joint in self.joints_to_destroy:\n",
+    "            self.goo_graph.remove_edge(u=goo_a, v=goo_b)\n",
+    "            self.world.destroy_joint(joint)\n",
+    "        self.joints_to_destroy = []\n",
+    "\n",
+    "        # destroy goos\n",
+    "        for goo in self.goo_to_destroy:\n",
+    "            self.goo_graph.remove_node(goo)\n",
+    "            self.world.destroy_body(goo)\n",
+    "\n",
+    "        # destroy all with wrong degree\n",
+    "        while True:\n",
+    "            destroyed_any = False\n",
+    "            to_remove = []\n",
+    "            for goo in self.goo_graph.nodes:\n",
+    "                if self.goo_graph.degree(goo) < 2:\n",
+    "                    destroyed_any = True\n",
+    "                    to_remove.append(goo)\n",
+    "            if not destroyed_any:\n",
+    "                break\n",
+    "            for goo in to_remove:\n",
+    "                self.goo_graph.remove_node(goo)\n",
+    "                self.world.destroy_body(goo)\n",
+    "        self.goo_to_destroy = []\n",
+    "\n",
+    "        # check if we are done\n",
+    "        for goo, finish_time in self.with_goal_contact.items():\n",
+    "            if finish_time <= self.elapsed_time:\n",
+    "                self.on_win(goo)\n",
+    "\n",
+    "        if self.emitter is not None:\n",
+    "            if self.emitter_stop_time is not None:\n",
+    "                if self.elapsed_time > self.emitter_stop_time:\n",
+    "                    self.emitter.enabled = False\n",
+    "                    self.emitter_start_time = self.elapsed_time + 0.4\n",
+    "                    self.emitter_stop_time = None\n",
+    "                    p = list(self.emitter.position)\n",
+    "                    p[0] += (random.random() - 0.5) * 10.0\n",
+    "                    p[1] += (random.random() - 0.5) * 2.0\n",
+    "                    self.emitter.position = p\n",
+    "            if self.emitter_start_time is not None:\n",
+    "                if self.elapsed_time > self.emitter_start_time:\n",
+    "                    self.emitter.enabled = True\n",
+    "                    self.emitter_start_time = None\n",
+    "                    self.emitter_stop_time = self.elapsed_time + 0.2\n",
+    "            self.emitter.step(dt)\n",
+    "\n",
+    "    def post_debug_draw(self):\n",
+    "\n",
+    "        self.level.draw(self.debug_draw)\n",
+    "\n",
+    "        # draw mouse when mouse is down\n",
+    "        if (\n",
+    "            self.is_mouse_down\n",
+    "            and self.last_mouse_pos is not None\n",
+    "            and self.draw_callback is None\n",
+    "        ):\n",
+    "            d = (self.insert_time_point - self.elapsed_time) / self.insert_delay\n",
+    "            if d > 0:\n",
+    "                d = d * math.pi * 2\n",
+    "                x = math.sin(d)\n",
+    "                y = math.cos(d)\n",
+    "                p = self.last_mouse_pos[0] + x, self.last_mouse_pos[1] + y\n",
+    "                self.debug_draw.draw_segment(\n",
+    "                    p, self.last_mouse_pos, color=(1, 0, 0), line_width=0.2\n",
+    "                )\n",
+    "            self.debug_draw.draw_circle(\n",
+    "                self.last_mouse_pos, 1, (1, 0, 0), line_width=0.2\n",
+    "            )\n",
+    "\n",
+    "        # draw the tentative placement\n",
+    "        if self.draw_callback is not None:\n",
+    "            self.draw_callback()\n",
+    "\n",
+    "        for goo_a, goo_b, joint in self.goo_graph.edges(data=\"joint\"):\n",
+    "            self.draw_edge(\n",
+    "                goo_a.position, goo_b.position, stress=joint.user_data[\"stress\"]\n",
+    "            )\n",
+    "\n",
+    "        for goo in self.goo_graph:\n",
+    "            self.draw_goo(goo.position, goo.angle, body=goo)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Controlls\n",
+    "* To play this game, click and drag next to the existing \"goos\"\n",
+    "* try to bridge the tiny gap\n",
+    "* Use the mouse-wheel to zoom in/out, a\n",
+    "* Click and drag in the empty space to translate the view."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [1000,500]\n",
+    "s.scale = 8\n",
+    "tb = b2d.testbed.run(Goo, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/content/pyodide/pyb2d/games/rocket.ipynb b/content/pyodide/pyb2d/games/rocket.ipynb
new file mode 100644
index 0000000..316e9ce
--- /dev/null
+++ b/content/pyodide/pyb2d/games/rocket.ipynb
@@ -0,0 +1,282 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f7b8452d-61fe-4356-8084-cac603096fef",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0bfa61e4-9817-4bea-aa66-6a660a423ae6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "import random\n",
+    "import numpy\n",
+    "import b2d\n",
+    "import math\n",
+    "\n",
+    "class Rocket(TestbedBase):\n",
+    "\n",
+    "    name = \"Rocket\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(Rocket, self).__init__(gravity=(0, 0), settings=settings)\n",
+    "\n",
+    "        # gravitational constant\n",
+    "        self.gravitational_constant = 6.0\n",
+    "\n",
+    "        self.planets = {}\n",
+    "\n",
+    "        # home planet\n",
+    "        home_planet = self.world.create_kinematic_body(\n",
+    "            position=(10, 0),\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=20)),\n",
+    "            user_data=\"home_planet\",\n",
+    "        )\n",
+    "\n",
+    "        # target planet\n",
+    "        target_planet = self.world.create_kinematic_body(\n",
+    "            position=(100, 100),\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=10)),\n",
+    "            user_data=\"target_planet\",\n",
+    "        )\n",
+    "\n",
+    "        # black hole\n",
+    "        black_hole = self.world.create_kinematic_body(\n",
+    "            position=(0, 400),\n",
+    "            fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1)),\n",
+    "            user_data=\"black_hole\",\n",
+    "        )\n",
+    "\n",
+    "        self.planets = {\n",
+    "            home_planet: dict(radius=20, density=1, color=(0, 0.2, 1)),\n",
+    "            target_planet: dict(radius=10, density=1, color=(0.7, 0.7, 0.7)),\n",
+    "            black_hole: dict(radius=1, density=10000, color=(0.1, 0.1, 0.1)),\n",
+    "        }\n",
+    "\n",
+    "        # a tiny rocket\n",
+    "        self.rocket = self.world.create_dynamic_body(\n",
+    "            position=(10, 10),\n",
+    "            fixtures=[\n",
+    "                b2d.fixture_def(shape=b2d.polygon_shape(box=[1, 1]), density=1),\n",
+    "                b2d.fixture_def(\n",
+    "                    shape=b2d.polygon_shape(vertices=[(-1, 1), (0, 4), (1, 1)]),\n",
+    "                    density=1,\n",
+    "                ),\n",
+    "            ],\n",
+    "            angular_damping=0.5,\n",
+    "            linear_damping=0.2,\n",
+    "            user_data=\"rocket\",\n",
+    "        )\n",
+    "        # check if the rocket is gone\n",
+    "        self.touched_black_hole = False\n",
+    "\n",
+    "        # particle system\n",
+    "        pdef = b2d.particle_system_def(\n",
+    "            viscous_strength=0.9,\n",
+    "            spring_strength=0.0,\n",
+    "            damping_strength=100.5,\n",
+    "            pressure_strength=1.0,\n",
+    "            color_mixing_strength=0.05,\n",
+    "            density=0.1,\n",
+    "        )\n",
+    "\n",
+    "        psystem = self.world.create_particle_system(pdef)\n",
+    "        psystem.radius = 0.1\n",
+    "        psystem.damping = 0.5\n",
+    "\n",
+    "        self.emitters = []\n",
+    "        self.key_map = {\"w\": 0, \"a\": 1, \"d\": 2}\n",
+    "\n",
+    "        angle_width = (math.pi * 2) / 16\n",
+    "        emitter_def = b2d.RandomizedRadialEmitterDef()\n",
+    "        emitter_def.emite_rate = 2000\n",
+    "        emitter_def.lifetime = 1.0\n",
+    "        emitter_def.enabled = False\n",
+    "        emitter_def.inner_radius = 1\n",
+    "        emitter_def.outer_radius = 1\n",
+    "        emitter_def.velocity_magnitude = 10.0\n",
+    "        emitter_def.start_angle = math.pi / 2 - angle_width / 2.0\n",
+    "        emitter_def.stop_angle = math.pi / 2 + angle_width / 2.0\n",
+    "        emitter_def.body = self.rocket\n",
+    "\n",
+    "        delta = 0.2\n",
+    "        self.emitter_local_anchors = [\n",
+    "            (0, -delta),  # main\n",
+    "            (-delta, -0.5),  # left,\n",
+    "            (delta, -0.5),  # right\n",
+    "        ]\n",
+    "        self.emitter_local_rot = [math.pi, math.pi / 2, -math.pi / 2]  # main\n",
+    "\n",
+    "        # main trust\n",
+    "        emitter_def.emite_rate = 2000\n",
+    "        world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[0])\n",
+    "        emitter_def.transform = b2d.Transform(\n",
+    "            world_anchor, b2d.Rot(self.emitter_local_rot[0])\n",
+    "        )\n",
+    "        emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n",
+    "        self.emitters.append(emitter)\n",
+    "\n",
+    "        # left\n",
+    "        emitter_def.emite_rate = 200\n",
+    "        world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[1])\n",
+    "        emitter_def.transform = b2d.Transform(\n",
+    "            world_anchor, b2d.Rot(self.emitter_local_rot[1])\n",
+    "        )\n",
+    "        emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n",
+    "        self.emitters.append(emitter)\n",
+    "\n",
+    "        # right\n",
+    "        emitter_def.emite_rate = 200\n",
+    "        world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[1])\n",
+    "        emitter_def.transform = b2d.Transform(\n",
+    "            world_anchor, b2d.Rot(self.emitter_local_rot[1])\n",
+    "        )\n",
+    "        emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n",
+    "        self.emitters.append(emitter)\n",
+    "\n",
+    "    def pre_step(self, dt):\n",
+    "\n",
+    "        # check if the rocket has died\n",
+    "        if self.touched_black_hole:\n",
+    "            if self.rocket is not None:\n",
+    "                self.world.destroy_body(self.rocket)\n",
+    "                self.rocket = None\n",
+    "        else:\n",
+    "            rocket_center = self.rocket.world_center\n",
+    "            rocket_mass = self.rocket.mass\n",
+    "            # compute gravitational forces\n",
+    "            net_force = numpy.zeros([2])\n",
+    "            for planet, planet_def in self.planets.items():\n",
+    "                radius = planet_def[\"radius\"]\n",
+    "                planet_center = planet.position\n",
+    "                planet_mass = planet_def[\"density\"] * radius ** 2 * math.pi\n",
+    "                delta = rocket_center - planet_center\n",
+    "                distance = delta.normalize()\n",
+    "                f = (\n",
+    "                    -self.gravitational_constant\n",
+    "                    * rocket_mass\n",
+    "                    * planet_mass\n",
+    "                    / (distance * distance)\n",
+    "                )\n",
+    "                net_force += delta * f\n",
+    "            f = float(net_force[0]), float(net_force[1])\n",
+    "            self.rocket.apply_force_to_center(f)\n",
+    "\n",
+    "            # run the rockets engines\n",
+    "            for emitter, local_anchor, local_rotation in zip(\n",
+    "                self.emitters, self.emitter_local_anchors, self.emitter_local_rot\n",
+    "            ):\n",
+    "                world_anchor = self.rocket.get_world_point(local_anchor)\n",
+    "                emitter.position = world_anchor\n",
+    "                emitter.angle = self.rocket.angle + local_rotation\n",
+    "                emitter.step(dt)\n",
+    "\n",
+    "    def begin_contact(self, contact):\n",
+    "        body_a = contact.body_a\n",
+    "        body_b = contact.body_b\n",
+    "        if body_b.user_data == \"rocket\":\n",
+    "            body_a, body_b = body_b, body_a\n",
+    "\n",
+    "        user_data_a = body_a.user_data\n",
+    "        user_data_b = body_b.user_data\n",
+    "        if body_a.user_data == \"rocket\":\n",
+    "            if user_data_b == \"black_hole\":\n",
+    "                self.touched_black_hole = True\n",
+    "\n",
+    "    def on_keyboard_down(self, key):\n",
+    "        if key in self.key_map:\n",
+    "            self.emitters[self.key_map[key]].enabled = True\n",
+    "            return True\n",
+    "        return False\n",
+    "\n",
+    "    def on_keyboard_up(self, key):\n",
+    "        if key in self.key_map:\n",
+    "            self.emitters[self.key_map[key]].enabled = False\n",
+    "            return False\n",
+    "        return False\n",
+    "\n",
+    "    def pre_debug_draw(self):\n",
+    "        pass\n",
+    "\n",
+    "    def post_debug_draw(self):\n",
+    "        for planet, planet_def in self.planets.items():\n",
+    "            pos = planet.position\n",
+    "            self.debug_draw.draw_solid_circle(\n",
+    "                pos, planet_def[\"radius\"] + 0.1, axis=None, color=planet_def[\"color\"]\n",
+    "            )\n",
+    "            if planet.user_data == \"black_hole\":\n",
+    "                self.debug_draw.draw_circle(\n",
+    "                    pos, planet_def[\"radius\"] * 5, color=(1, 1, 1), line_width=0.1\n",
+    "                )"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "357866b3-876e-421f-8d2a-77d6697551d3",
+   "metadata": {},
+   "source": [
+    "# Controlls\n",
+    "* To play this game, use 'w','a','s','d' on your keyboard to steer the rocket\n",
+    "* try to land on the other planet\n",
+    "* avoid the black hole\n",
+    "* Use the mouse-wheel to zoom in/out, a\n",
+    "* Click and drag in the empty space to translate the view."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7bab75b7-cec1-4348-b95d-9ffd282ded5c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [1000,1000]\n",
+    "s.scale = 3\n",
+    "tb = b2d.testbed.run(Rocket, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "674c57c8-b5b1-45a9-b75e-5ddc487f7d9b",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/pyb2d/gauss_machine.ipynb b/content/pyodide/pyb2d/gauss_machine.ipynb
new file mode 100644
index 0000000..b7bb72c
--- /dev/null
+++ b/content/pyodide/pyb2d/gauss_machine.ipynb
@@ -0,0 +1,128 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "39a80aae-a990-4ed8-b880-3db2e7f70f16",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9d9f8d68-0f3b-49c1-9512-e3e8344e7342",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "import random\n",
+    "import numpy\n",
+    "import b2d\n",
+    "\n",
+    "\n",
+    "class GaussMachine(TestbedBase):\n",
+    "\n",
+    "    name = \"Gauss Machine\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(GaussMachine, self).__init__(settings=settings)\n",
+    "\n",
+    "        self.box_shape = 30, 20\n",
+    "        box_shape = self.box_shape\n",
+    "\n",
+    "        # outer box\n",
+    "        verts = numpy.array(\n",
+    "            [(0, box_shape[1]), (0, 0), (box_shape[0], 0), (box_shape[0], box_shape[1])]\n",
+    "        )\n",
+    "        shape = b2d.chain_shape(vertices=numpy.flip(verts, axis=0))\n",
+    "        box = self.world.create_static_body(position=(0, 0), shape=shape)\n",
+    "\n",
+    "        # \"bins\"\n",
+    "        bin_height = box_shape[1] / 3\n",
+    "        bin_width = 1\n",
+    "        for x in range(0, box_shape[0], bin_width):\n",
+    "            box = self.world.create_static_body(\n",
+    "                position=(0, 0), shape=b2d.two_sided_edge_shape((x, 0), (x, bin_height))\n",
+    "            )\n",
+    "\n",
+    "        # reflectors\n",
+    "        ref_start_y = int(bin_height + box_shape[1] / 10.0)\n",
+    "        ref_stop_y = int(box_shape[1] * 0.9)\n",
+    "        for x in range(0, box_shape[0] + 1):\n",
+    "\n",
+    "            for y in range(ref_start_y, ref_stop_y):\n",
+    "                s = [0.5, 0][y % 2 == 0]\n",
+    "                shape = b2d.circle_shape(radius=0.3)\n",
+    "                box = self.world.create_static_body(position=(x + s, y), shape=shape)\n",
+    "\n",
+    "        # particle system\n",
+    "        pdef = b2d.particle_system_def(\n",
+    "            viscous_strength=0.9,\n",
+    "            spring_strength=0.0,\n",
+    "            damping_strength=100.5,\n",
+    "            pressure_strength=1.0,\n",
+    "            color_mixing_strength=0.05,\n",
+    "            density=2,\n",
+    "        )\n",
+    "\n",
+    "        psystem = self.world.create_particle_system(pdef)\n",
+    "        psystem.radius = 0.1\n",
+    "        psystem.damping = 0.5\n",
+    "\n",
+    "        # linear emitter\n",
+    "        emitter_pos = (self.box_shape[0] / 2, self.box_shape[1] + 10)\n",
+    "        emitter_def = b2d.RandomizedLinearEmitterDef()\n",
+    "        emitter_def.emite_rate = 400\n",
+    "        emitter_def.lifetime = 25\n",
+    "        emitter_def.size = (10, 1)\n",
+    "        emitter_def.transform = b2d.Transform(emitter_pos, b2d.Rot(0))\n",
+    "\n",
+    "        self.emitter = b2d.RandomizedLinearEmitter(psystem, emitter_def)\n",
+    "\n",
+    "    def pre_step(self, dt):\n",
+    "        self.emitter.step(dt)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "51e5df1b-f5e6-486a-a6c0-173597198e5d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [350,400]\n",
+    "s.scale = 11\n",
+    "tb = b2d.testbed.run(GaussMachine, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/pyb2d/newtons_cradle.ipynb b/content/pyodide/pyb2d/newtons_cradle.ipynb
new file mode 100644
index 0000000..9ef3ff3
--- /dev/null
+++ b/content/pyodide/pyb2d/newtons_cradle.ipynb
@@ -0,0 +1,130 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "82ae535a-a041-40f0-8c40-e689b894b0bc",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "if \"pyodide\" in sys.modules:\n",
+    "    import piplite\n",
+    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.0')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "7381ff66-7924-4216-b141-8f7b15ed038a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from b2d.testbed import TestbedBase\n",
+    "import b2d\n",
+    "\n",
+    "\n",
+    "class NewtonsCradle(TestbedBase):\n",
+    "\n",
+    "    name = \"newton's cradle\"\n",
+    "\n",
+    "    def __init__(self, settings=None):\n",
+    "        super(NewtonsCradle, self).__init__(settings=settings)\n",
+    "\n",
+    "        # radius of the circles\n",
+    "        r = 1.0\n",
+    "        # length of the rope\n",
+    "        l = 10.0\n",
+    "        # how many balls\n",
+    "        n = 10\n",
+    "\n",
+    "        offset = (l + r, 2 * r)\n",
+    "        dynamic_circles = []\n",
+    "        static_bodies = []\n",
+    "        for i in range(n):\n",
+    "            if i + 1 == n:\n",
+    "                position = (offset[0] + i * 2 * r + l, offset[1] + l)\n",
+    "            else:\n",
+    "                position = (offset[0] + i * 2 * r, offset[1])\n",
+    "\n",
+    "            circle = self.world.create_dynamic_body(\n",
+    "                position=position,\n",
+    "                fixtures=b2d.fixture_def(\n",
+    "                    shape=b2d.circle_shape(radius=r * 0.90),\n",
+    "                    density=1.0,\n",
+    "                    restitution=1.0,\n",
+    "                    friction=0.0,\n",
+    "                ),\n",
+    "                linear_damping=0.01,\n",
+    "                angular_damping=1.0,\n",
+    "                fixed_rotation=True,\n",
+    "            )\n",
+    "            dynamic_circles.append(circle)\n",
+    "\n",
+    "            static_body = self.world.create_static_body(\n",
+    "                position=(offset[0] + i * 2 * r, offset[1] + l)\n",
+    "            )\n",
+    "\n",
+    "            self.world.create_distance_joint(\n",
+    "                static_body,\n",
+    "                circle,\n",
+    "                local_anchor_a=(0, 0),\n",
+    "                local_anchor_b=(0, 0),\n",
+    "                max_length=l,\n",
+    "                stiffness=0,\n",
+    "            )\n",
+    "\n",
+    "            static_bodies.append(static_body)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "6f694d32-8b23-40cf-91c3-0edd6abb658d",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "b24ad88f6cfd4db19fc3145000e79635",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
+    "s = JupyterAsyncGui.Settings()\n",
+    "s.resolution = [1000,300]\n",
+    "b2d.testbed.run(NewtonsCradle, backend=JupyterAsyncGui, gui_settings=s);"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/pyodide/renderers.ipynb b/content/pyodide/renderers.ipynb
new file mode 100644
index 0000000..286490f
--- /dev/null
+++ b/content/pyodide/renderers.ipynb
@@ -0,0 +1 @@
+{"metadata":{"language_info":{"codemirror_mode":{"name":"python","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.8"},"kernelspec":{"name":"python","display_name":"Pyolite","language":"python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# JupyterLab Renderers","metadata":{}},{"cell_type":"markdown","source":"## FASTA","metadata":{}},{"cell_type":"code","source":"def Fasta(data=''):\n    bundle = {}\n    bundle['application/vnd.fasta.fasta'] = data\n    bundle['text/plain'] = data\n    display(bundle, raw=True)\n\nFasta(\"\"\">SEQUENCE_1\nMTEITAAMVKELRESTGAGMMDCKNALSETNGDFDKAVQLLREKGLGKAAKKADRLAAEG\nLVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYKALVAELEKENEERRRLKDPNKPEHK\nIPQFASRKQLSDAILKEAEEKIKEELKAQGKPEKIWDNIIPGKMNSFIADNSQLDSKLTL\nMGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAAEVAAQL\n>SEQUENCE_2\nSATVSEINSETDFVAKNDQFIALTKDTTAHIQSNSLQSVEELHSSTINGVKFEEYLKSQI\nATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH\"\"\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## GeoJSON","metadata":{}},{"cell_type":"code","source":"def GeoJSON(data):\n    bundle = {}\n    bundle['application/geo+json'] = data\n    bundle['text/plain'] = data\n    display(bundle, raw=True)\n    \nGeoJSON({\n  \"type\": \"Feature\",\n  \"geometry\": {\n    \"type\": \"Point\",\n    \"coordinates\": [125.6, 10.1]\n  },\n  \"properties\": {\n    \"name\": \"Dinagat Islands\"\n  }\n})","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}
\ No newline at end of file
diff --git a/content/python.ipynb b/content/python.ipynb
new file mode 100644
index 0000000..689e883
--- /dev/null
+++ b/content/python.ipynb
@@ -0,0 +1,721 @@
+{
+  "cells": [
+    {
+      "attachments": {},
+      "cell_type": "markdown",
+      "metadata": {
+        "tags": []
+      },
+      "source": [
+        "# A Python kernel backed by Pyodide\n",
+        "\n",
+        "![](https://raw.githubusercontent.com/pyodide/pyodide/master/docs/_static/img/pyodide-logo.png)"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import pyodide_kernel\n",
+        "pyodide_kernel.__version__"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Simple code execution"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "a = 3"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "a"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "b = 89\n",
+        "\n",
+        "def sq(x):\n",
+        "    return x * x\n",
+        "\n",
+        "sq(b)"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "print"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "tags": []
+      },
+      "source": [
+        "# Redirected streams"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import sys\n",
+        "\n",
+        "print(\"Error !!\", file=sys.stderr)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Error handling"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "scrolled": true,
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "\"Hello\"\n",
+        "\n",
+        "def dummy_function():\n",
+        "    import missing_module"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "dummy_function()"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Code completion"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### press `tab` to see what is available in `sys` module"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from sys import "
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Code inspection"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### using the question mark"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "?print"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### by pressing `shift+tab`"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "print("
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Input support"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "name = await input('Enter your name: ')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "'Hello, ' + name"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "# Rich representation"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import display, Markdown, HTML, JSON, Latex"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "tags": []
+      },
+      "source": [
+        "## HTML"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "print('Before display')\n",
+        "\n",
+        "s = '<h1>HTML Title</h1>'\n",
+        "display(HTML(s))\n",
+        "\n",
+        "print('After display')"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Markdown"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "Markdown('''\n",
+        "# Title\n",
+        "\n",
+        "**in bold**\n",
+        "\n",
+        "~~Strikthrough~~\n",
+        "''')"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Pandas DataFrame"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import pandas as pd\n",
+        "import numpy as np\n",
+        "from string import ascii_uppercase as letters\n",
+        "from IPython.display import display\n",
+        "\n",
+        "df = pd.DataFrame(np.random.randint(0, 100, size=(100, len(letters))), columns=list(letters))\n",
+        "df"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### Show the same DataFrame "
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "df"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## IPython.display module"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import clear_output, display, update_display\n",
+        "from asyncio import sleep"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### Update display"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "class Square:\n",
+        "    color = 'PeachPuff'\n",
+        "    def _repr_html_(self):\n",
+        "        return '''\n",
+        "        <div style=\"background: %s; width: 200px; height: 100px; border-radius: 10px;\">\n",
+        "        </div>''' % self.color\n",
+        "square = Square()\n",
+        "\n",
+        "display(square, display_id='some-square')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "square.color = 'OliveDrab'\n",
+        "update_display(square, display_id='some-square')"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### Clear output"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "print(\"hello\")\n",
+        "await sleep(3)\n",
+        "clear_output()             # will flicker when replacing \"hello\" with \"goodbye\"\n",
+        "print(\"goodbye\")"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "print(\"hello\")\n",
+        "await sleep(3)\n",
+        "clear_output(wait=True)   # prevents flickering\n",
+        "print(\"goodbye\")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "### Display classes"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import HTML\n",
+        "HTML('''\n",
+        "        <div style=\"background: aliceblue; width: 200px; height: 100px; border-radius: 10px;\">\n",
+        "        </div>''')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import Math\n",
+        "Math(r'F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import Latex\n",
+        "Latex(r\"\"\"\\begin{eqnarray}\n",
+        "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n",
+        "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n",
+        "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n",
+        "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n",
+        "\\end{eqnarray}\"\"\")"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import ProgressBar\n",
+        "\n",
+        "for i in ProgressBar(10):\n",
+        "    await sleep(0.1)"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import JSON\n",
+        "JSON(['foo', {'bar': ('baz', None, 1.0, 2)}], metadata={}, expanded=True, root='test')"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from IPython.display import GeoJSON\n",
+        "GeoJSON(\n",
+        "  data={\n",
+        "      \"type\": \"Feature\",\n",
+        "      \"geometry\": {\n",
+        "          \"type\": \"Point\",\n",
+        "          \"coordinates\": [11.8, -45.04]\n",
+        "      }\n",
+        "  }, url_template=\"http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png\",\n",
+        "  layer_options={\n",
+        "      \"basemap_id\": \"celestia_mars-shaded-16k_global\",\n",
+        "      \"attribution\" : \"Celestia/praesepe\",\n",
+        "      \"tms\": True,\n",
+        "      \"minZoom\" : 0,\n",
+        "      \"maxZoom\" : 5\n",
+        "  }\n",
+        ")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Network requests and JSON"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import json\n",
+        "from js import fetch"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "res = await fetch('https://httpbin.org/get')\n",
+        "text = await res.text()\n",
+        "obj = json.loads(text) \n",
+        "JSON(obj)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Sympy"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "from sympy import Integral, sqrt, symbols, init_printing\n",
+        "\n",
+        "init_printing()\n",
+        "\n",
+        "x = symbols('x')\n",
+        "\n",
+        "Integral(sqrt(1 / x), x)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "## Magics"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import os\n",
+        "os.listdir()"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%cd /home"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%pwd"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "current_path = %pwd\n",
+        "print(current_path)"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%%writefile test.txt\n",
+        "\n",
+        "This will create a new file. \n",
+        "With the text that you see here."
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%history"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "import time"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "trusted": true
+      },
+      "outputs": [],
+      "source": [
+        "%%timeit \n",
+        "\n",
+        "time.sleep(0.1)"
+      ]
+    }
+  ],
+  "metadata": {
+    "kernelspec": {
+      "display_name": "Python (Pyodide)",
+      "language": "python",
+      "name": "python"
+    },
+    "language_info": {
+      "codemirror_mode": {
+        "name": "python",
+        "version": 3
+      },
+      "file_extension": ".py",
+      "mimetype": "text/x-python",
+      "name": "python",
+      "nbconvert_exporter": "python",
+      "pygments_lexer": "ipython3",
+      "version": "3.8"
+    },
+    "orig_nbformat": 4
+  },
+  "nbformat": 4,
+  "nbformat_minor": 4
+}
diff --git a/content/xeus-lua/canvas.ipynb b/content/xeus-lua/canvas.ipynb
new file mode 100644
index 0000000..89f9e52
--- /dev/null
+++ b/content/xeus-lua/canvas.ipynb
@@ -0,0 +1,29 @@
+{
+  "metadata": {
+    "orig_nbformat": 4,
+    "language_info": {
+      "file_extension": ".lua",
+      "mimetype": "text/x-luasrc",
+      "name": "lua",
+      "version": "14.0.0"
+    },
+    "kernelspec": {
+      "name": "Lua",
+      "display_name": "Lua",
+      "language": "lua"
+    }
+  },
+  "nbformat_minor": 4,
+  "nbformat": 4,
+  "cells": [
+    {
+      "cell_type": "code",
+      "source": "canvas = ilua.canvas.canvas()\nilua.display.display(canvas)\ncanvas:cache()\nfor var=1,100 do\n    canvas:begin_path()\n    canvas:move_to(canvas:rand_coord())\n    for s=1,10 do\n        canvas:line_to(canvas:rand_coord())\n    end\n    canvas.fill_style = canvas:rand_color()\n    canvas:fill()\nend\ncanvas:flush()",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/content/xeus-lua/game-of-life.ipynb b/content/xeus-lua/game-of-life.ipynb
new file mode 100644
index 0000000..09dabe4
--- /dev/null
+++ b/content/xeus-lua/game-of-life.ipynb
@@ -0,0 +1,92 @@
+{
+   "metadata":{
+      "orig_nbformat":4,
+      "kernelspec":{
+         "name":"Lua",
+         "display_name":"Lua",
+         "language":"lua"
+      },
+      "language_info":{
+         "file_extension":".lua",
+         "mimetype":"text/x-luasrc",
+         "name":"lua",
+         "version":"14.0.0"
+      }
+   },
+   "nbformat_minor":5,
+   "nbformat":4,
+   "cells":[
+      {
+         "cell_type":"code",
+         "source":"math = require(\"math\")\n\n-- the game of life class itself\nGameOfLife = {}\nGameOfLife.__index = GameOfLife\nfunction GameOfLife:Create(grid_size)\n    local this =\n    {\n        grid_size = grid_size or {10,10},\n        grid = {}\n    }\n    setmetatable(this, GameOfLife)\n    this.size = this.grid_size[1] * this.grid_size[2]\n    for i=1,this.size do\n        this.grid[i] = 0\n    end\n    return this\nend",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"61dc1d13-5546-477d-a162-e18ec564e3ee"
+      },
+      {
+         "cell_type":"code",
+         "source":"-- member function to initalize with some random values\nfunction GameOfLife:init_random(p)\n    for i=1,self.size do\n        self.grid[i] = (math.random() < p) and 1 or 0\n    end\nend",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"72da5a15-dd9d-4a62-bfa3-5a3ec457c5f4"
+      },
+      {
+         "cell_type":"code",
+         "source":"-- helper function to convert a coordinate {x,y} into a \n-- scalar offset\nfunction GameOfLife:to_offset(coord)\n    return (coord[1]-1) * self.grid_size[2] + (coord[2] -1) + 1\nend\n-- helper function to access the value of the grid at a coordinate {x,y}\nfunction GameOfLife:at(coord)\n    return self.grid[self:to_offset(coord)]\nend",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"0b341677-64f2-4ef3-8ea4-a8e2eb275df2"
+      },
+      {
+         "cell_type":"code",
+         "source":"-- the function to do a step\nfunction GameOfLife:step()\n    new_grid = {}\n  \n    for x=1,self.grid_size[1] do\n        for y=1, self.grid_size[2] do\n            \n            local c = 0\n            for xx=-1,1 do\n                for yy=-1,1 do\n                    nx = x + xx\n                    ny = y + yy\n                    if nx >=1 and ny>=1 and nx <=self.grid_size[1] and ny <=self.grid_size[2] and not (xx==0 and yy ==0) then\n                        c = c + self:at({nx,ny})\n                    end\n                end\n            end\n            \n            local offset =  self:to_offset({x,y})\n            local current_state = self:at({x,y})\n            \n            new_grid[offset] = 0\n            if current_state == 0 then\n                if c == 3 then\n                    new_grid[offset] = 1\n                end\n            else\n                if c==2 or c==3 then\n                    new_grid[offset] = 1\n                else\n                    new_grid[offset] = 0\n                end\n            end\n--             \n        end\n    end\n    self.grid = new_grid\nend",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"7958dd53-006c-480d-a4ad-9f04b147fd29"
+      },
+      {
+         "cell_type":"code",
+         "source":"-- helper function to draw the grid as string\nfunction GameOfLife:__tostring()\n    s = \"*\"\n    for y=1, self.grid_size[2] do\n        s = s .. \"--\"\n    end\n    s =  s .. \"*\\n\"\n    for x=1,self.grid_size[1] do\n        s = s .. \"|\"\n        for y=1, self.grid_size[2] do\n            local state = self:at({x,y})\n            if state == 0 then\n                ss = \" \"\n            else\n                ss =  \"O\"\n            end\n            s = s .. ss .. \" \"\n        end\n        s =  s .. \"|\\n\"\n    end\n     s =  s .. \"*\"\n    for y=1, self.grid_size[2] do\n        s = s .. \"--\"\n    end\n    s =  s .. \"*\\n\"\n    return s\nend",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"884414f6-8bc9-4719-9671-ef3be310356b"
+      },
+      {
+         "cell_type":"code",
+         "source":"-- initalize the game of life\ngrid_size = {20,20}\ngame_of_life = GameOfLife:Create(grid_size)\ngame_of_life:init_random(0.5)\n\n-- a tiny gui\nfunction speed_to_interval(speed)\n    return 1.0 / speed\nend\n\nspeed = 0.001\n\nhbox = ilua.widgets.hbox()\n\nplay = ilua.widgets.play({interval=speed_to_interval(speed), max=1000000})\noutput = ilua.widgets.output()\nstep_label = ilua.widgets.label({value=\"Step: \"..tostring(play.value)})\nspeed_label = ilua.widgets.label({value=\"Speed: \"..tostring(speed)})\nspeed_slider = ilua.widgets.slider({min=0.001, max=0.5, step=0.01})\n\nhbox:add(play,step_label,speed_label)\n\nspeed_slider:register_observer(function(value)\n    output:captured(function()\n    speed = value\n    play.interval = speed_to_interval(speed)\n    speed_label.value = \"Speed: \" .. tostring(speed)\n    end)\nend)\n\nplay:register_observer(function(value)\n    if value <= 0.1 then\n        game_of_life:init_random(0.24)\n    end\n    --  use output widget to caputre prints   \n    output:captured(function()\n        ilua.display.clear_output(false)\n        step_label.value = \"STEP \"..tostring(play.value)\n        game_of_life:step()\n        print(tostring(game_of_life))\n    end)\nend)\nilua.display.display(hbox,speed_slider, output)",
+         "metadata":{
+            "trusted":true
+         },
+         "execution_count":null,
+         "outputs":[
+            
+         ],
+         "id":"40cabf23-7eef-4501-badd-99c1a68c5ced"
+      }
+   ]
+}
\ No newline at end of file
diff --git a/content/xeus-lua/widgets.ipynb b/content/xeus-lua/widgets.ipynb
new file mode 100644
index 0000000..ef337f0
--- /dev/null
+++ b/content/xeus-lua/widgets.ipynb
@@ -0,0 +1,632 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "6d755081-7f9f-4c6a-93e5-a28c8627a0ec",
+   "metadata": {},
+   "source": [
+    "Button\n",
+    "======="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "be69e205-0c56-44b7-926b-5482d5300a38",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "button = ilua.widgets.button({description=\"hello\"})\n",
+    "output = ilua.widgets.output()\n",
+    "ilua.display.display(button,output)\n",
+    "button:on_click(function()\n",
+    "    output:captured(function()\n",
+    "        print(\"clicked\")\n",
+    "    end)\n",
+    "end)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d11b0def-5115-4369-9972-4766802c846f",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "Box Layout\n",
+    "=========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5d044224-ac2e-4722-b3fb-6d75b13ec366",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "slider_a = ilua.widgets.slider({min=1, max=2, step=0.1})\n",
+    "slider_b = ilua.widgets.slider({min=10, max=20, step=1})\n",
+    "hbox = ilua.widgets.hbox()\n",
+    "output = ilua.widgets.output()\n",
+    "hbox:add(slider_a, slider_b)\n",
+    "ilua.display.display(hbox,output)\n",
+    "\n",
+    "slider_a:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       io.write(\"f(x) = x**2; f(\", value ,\")=\", value*value,\"\\n\")\n",
+    "    end)\n",
+    "end)\n",
+    "slider_b:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       io.write(\"f(x) = x**3; f(\", value ,\")=\", value*value*value,\"\\n\")\n",
+    "    end)\n",
+    "end)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "98e79948-9aeb-4c31-a7b9-3c6bce6c026f",
+   "metadata": {},
+   "source": [
+    "Accordion\n",
+    "================"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b755c37d-602a-4b46-97ee-8e044f2f2314",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "output = ilua.widgets.output()\n",
+    "button = ilua.widgets.button({description=\"a button\"})\n",
+    "button:on_click(function()  \n",
+    "    output:captured(function()\n",
+    "       print(\"clicked button\")\n",
+    "    end)\n",
+    "end)\n",
+    "slider = ilua.widgets.slider()\n",
+    "color_picker = ilua.widgets.color_picker()\n",
+    "audio = ilua.widgets.audio()\n",
+    "accordion = ilua.widgets.accordion()\n",
+    "accordion:add(slider,button,color_picker,audio)\n",
+    "accordion:set_title(1,\"the slider\")\n",
+    "accordion:set_title(2,\"the button\")\n",
+    "accordion:set_title(3,\"the color_picker\")\n",
+    "accordion:set_title(4,\"the audio\")\n",
+    "ilua.display.display(accordion, output)\n",
+    "\n",
+    "slider:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"slider value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "color_picker:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"color_picker value\", value)\n",
+    "    end)\n",
+    "end)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "097d19f7-a0c9-48a9-8eff-6e3e06780c88",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "Dropdown\n",
+    "========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "95e12c50-4e77-457c-b2c2-6d20421a3bea",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "labels = {\"aaa\", \"bb\",\"cc\"}\n",
+    "dropdown = ilua.widgets.dropdown({_options_labels= labels})\n",
+    "output = ilua.widgets.output()\n",
+    "dropdown:register_observer(function(index)\n",
+    "    output:captured(function()\n",
+    "       print(labels[index])\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(dropdown, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ca362331-bd0c-4203-9a6f-dc937d309297",
+   "metadata": {},
+   "source": [
+    "Html\n",
+    "======="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "abc66753-53ba-4361-96a6-48cd9460c8e3",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "src = [[\n",
+    "<!DOCTYPE html>\n",
+    "<html>\n",
+    "    <head>\n",
+    "    </head>\n",
+    "    <body>\n",
+    "        <h1>My First Page</h1>\n",
+    "        <p>This is my first page.</p>\n",
+    "        <h2>A secondary header.</h2>\n",
+    "        <p>Some more text.</p>\n",
+    "    </body>\n",
+    "</html>\n",
+    "]]\n",
+    "html = ilua.widgets.html({value=src})\n",
+    "ilua.display.display(html)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e8eeacfe-350a-4ecb-81a9-c8091af3c22e",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "numeral\n",
+    "========"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9c177cfe-2613-43a3-a217-d93bc257420e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "numeral = ilua.widgets.numeral()\n",
+    "output = ilua.widgets.output()\n",
+    "numeral:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "ilua.display.display(numeral, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9fede355-2e4f-48c9-b39b-1886e060ce8b",
+   "metadata": {},
+   "source": [
+    "password\n",
+    "========"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "455996cf-9b63-4d50-869a-0f479ab3daf4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "password = ilua.widgets.password()\n",
+    "output = ilua.widgets.output()\n",
+    "password:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(password, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6306ce8e-37d3-4dbc-a8cf-955b2dfe684c",
+   "metadata": {},
+   "source": [
+    "play\n",
+    "====="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "986147ab-c9de-4ad9-b8be-f65260dd815d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "play = ilua.widgets.play({interval=1000})\n",
+    "output = ilua.widgets.output()\n",
+    "play:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(play, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4e03ccf2-3a10-40d6-8273-168a91f3d9a7",
+   "metadata": {},
+   "source": [
+    "progress\n",
+    "========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a4f194ee-0995-44f3-8636-b629bd5b65ba",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "progress = ilua.widgets.progress({min=10, max=20})\n",
+    "play = ilua.widgets.play({min=10, max=20, interval=100})\n",
+    "play:register_observer(function(value)\n",
+    "    progress.value = value\n",
+    "end)\n",
+    "ilua.display.display(play, progress)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "39714034-99f4-4f36-b50f-33aa3f71d2a6",
+   "metadata": {},
+   "source": [
+    "Selection Slider\n",
+    "================="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7f6aab9e-b3ce-4df9-83a6-94eb725aceb0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "labels = {\"aaa\", \"bb\",\"cc\"}\n",
+    "selectionslider = ilua.widgets.selectionslider({_options_labels = labels})\n",
+    "output = ilua.widgets.output()\n",
+    "selectionslider:register_observer(function(index)\n",
+    "    output:captured(function()\n",
+    "       print(labels[index])\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(selectionslider, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "45d72396-3fcc-421e-865e-487d93b333b8",
+   "metadata": {},
+   "source": [
+    "Tab\n",
+    "======"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6d0684e8-62a2-4483-995a-70d7cc2783ac",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "\n",
+    "slider_a = ilua.widgets.slider()\n",
+    "slider_b = ilua.widgets.slider()\n",
+    "\n",
+    "titles =  {\"aaa\", \"bb\",\"cc\"}\n",
+    "tab = ilua.widgets.tab({_titles = titles})\n",
+    "tab:add(slider_a,slider_b)\n",
+    "output = ilua.widgets.output()\n",
+    "\n",
+    "tab:register_observer(function(index)\n",
+    "    --  use output widget to caputre prints   \n",
+    "    output:captured(function()\n",
+    "       print(titles[index])\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(tab,output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7f9f85de-a5b8-4e60-9eff-33b25d819ec9",
+   "metadata": {},
+   "source": [
+    "Text\n",
+    "====="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "893e92ad-eff2-4adb-ba82-71a9ed2cfe9e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "text = ilua.widgets.text()\n",
+    "output = ilua.widgets.output()\n",
+    "text:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "text:on_submit(function()\n",
+    "    output:captured(function()\n",
+    "       print(\"on_submit\")\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(text, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "94572d62-12ea-4026-a531-d0e7bc5b20b9",
+   "metadata": {},
+   "source": [
+    "TextArea\n",
+    "========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1e76b56f-f087-49f8-a50e-eb794060c63c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "textarea = ilua.widgets.textarea({continuous_update=false})\n",
+    "output = ilua.widgets.output()\n",
+    "textarea:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "ilua.display.display(textarea, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "68011d67-ac8c-43ff-bae9-4caf95273f98",
+   "metadata": {},
+   "source": [
+    "Toggle Button\n",
+    "========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cf780275-b2de-48d4-bb2e-3f38e8c45bcd",
+   "metadata": {
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "togglebutton = ilua.widgets.togglebutton({\n",
+    "    icon=\"plus\",\n",
+    "    button_style=\"\"\n",
+    "})\n",
+    "output = ilua.widgets.output()\n",
+    "togglebutton:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(togglebutton, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1bc5c504-cf9c-432f-a91c-2497910d787b",
+   "metadata": {},
+   "source": [
+    "ToggleButtons\n",
+    "========="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f764f285-d71f-4c93-92b4-923484846b0c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "togglebuttons = ilua.widgets.togglebuttons()\n",
+    "togglebuttons._options_labels = {\"1\", \"2\",\"3\"}\n",
+    "output = ilua.widgets.output()\n",
+    "togglebuttons:register_observer(function(value)\n",
+    "    output:captured(function()\n",
+    "       print(\"value\", value)\n",
+    "    end)\n",
+    "end)\n",
+    "\n",
+    "ilua.display.display(togglebuttons, output)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "55447db2-bab0-4282-9a89-f6b9f8fd9e30",
+   "metadata": {},
+   "source": [
+    "Image\n",
+    "========"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d21c5e4b-1d27-44e9-9966-0da4ef55f651",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "io = require(\"io\")\n",
+    "file = io.open(\"marie.png\", \"r\")\n",
+    "content = file:read(\"*all\")\n",
+    "io.close(file)\n",
+    "\n",
+    "image = ilua.widgets.image()\n",
+    "image.value = content\n",
+    "ilua.display.display(image)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee4e079c-0101-49a9-94f6-2d7e73ad3f59",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "Video\n",
+    "========"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "654c050a-3cff-48d2-b7a1-d8f6acfe042a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "io = require(\"io\")\n",
+    "file = io.open(\"Big.Buck.Bunny.mp4\", \"r\")\n",
+    "content = file:read(\"*all\")\n",
+    "io.close(file)\n",
+    "\n",
+    "video = ilua.widgets.video({loop=false})\n",
+    "video.value = content\n",
+    "ilua.display.display(video)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "829cf76c-238d-456f-a5b9-51f3a526f45c",
+   "metadata": {},
+   "source": [
+    "Audio\n",
+    "======"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "43922e68-ee53-40bd-bf2c-8dcc810bdb6a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "io = require(\"io\")\n",
+    "file = io.open(\"hehe.flac\", \"r\")\n",
+    "content = file:read(\"*all\")\n",
+    "io.close(file)\n",
+    "\n",
+    "audio = ilua.widgets.audio({loop=false})\n",
+    "audio.value = content\n",
+    "ilua.display.display(audio)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2aee848b-a3e6-4ce3-9d2d-8650871bb225",
+   "metadata": {},
+   "source": [
+    "Valid\n",
+    "======"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ef2ae714-2077-4767-9c24-af821fbc2c9d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "valid = ilua.widgets.valid()\n",
+    "valid.value = false\n",
+    "ilua.display.display(valid)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "96b64ffe-ac69-4b0e-9299-0a54842a77c1",
+   "metadata": {},
+   "source": [
+    "Link Widgets\n",
+    "============"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "191675ca-c53e-4c83-a0cf-8dcd78b02fae",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider = ilua.widgets.slider()\n",
+    "numeral = ilua.widgets.numeral()\n",
+    "\n",
+    "link = ilua.widgets.link(slider, \"value\", numeral, \"value\")\n",
+    "ilua.display.display(link, slider, numeral)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3d54b0cc-7f2f-4ad7-92a8-9f61abe989f9",
+   "metadata": {},
+   "source": [
+    "Directional Link\n",
+    "==================="
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "21499154-6da9-4a4a-a6ce-7637916d26c6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "slider = ilua.widgets.slider()\n",
+    "numeral = ilua.widgets.numeral()\n",
+    "\n",
+    "link = ilua.widgets.directional_link(slider, \"value\", numeral, \"value\")\n",
+    "ilua.display.display(link, slider, numeral)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Lua 0.2 (XLua)",
+   "language": "lua",
+   "name": "xlua"
+  },
+  "language_info": {
+   "file_extension": ".lua",
+   "mimetype": "text/x-luasrc",
+   "name": "lua",
+   "version": "14.0.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/xeus-sqlite/simple-operations.ipynb b/content/xeus-sqlite/simple-operations.ipynb
new file mode 100644
index 0000000..5d4c4a6
--- /dev/null
+++ b/content/xeus-sqlite/simple-operations.ipynb
@@ -0,0 +1,392 @@
+{
+  "metadata": {
+    "kernelspec": {
+      "name": "SQLite",
+      "display_name": "SQLite",
+      "language": "sql"
+    },
+    "language_info": {
+      "file_extension": ".sqlite3-console",
+      "mimetype": "text/x-sqlite3-console",
+      "name": "sqlite3",
+      "version": "0.4.0"
+    }
+  },
+  "nbformat_minor": 4,
+  "nbformat": 4,
+  "cells": [
+    {
+      "cell_type": "markdown",
+      "source": "# JupyterLite `xeus-sqlite` Kernel Demo\n\nThe [`jupyterlite/xeus-sqlite-kernel`](https://github.com/jupyterlite/xeus-sqlite-kernel) wraps the original [`jupyter-xeus/xeus-sqlite`](https://github.com/jupyter-xeus/xeus-sqlite/) kernel for use in JupyterLite.\n\nOriginal kernel docs can be found [here](https://xeus-sqlite.readthedocs.io/en/latest/).\n\nThe kernel provides cell magic for command line database operations, and native execution of SQL code against a connected database.",
+      "metadata": {}
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Creating a Database\n\nLine magic is used to create an in-memory database:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "%CREATE example_db.db",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 1,
+      "outputs": []
+    },
+    {
+      "cell_type": "markdown",
+      "source": "Currently, there is no ability to:\n\n- save the database to browser storage;\n- export the database;\n- load a database from browser storage;\n- load a database from a URL;\n- load a database from the desktop;\n- connect to a remote sqlite database file.",
+      "metadata": {}
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Create and Populate Tables\n\nTables are created and populated using SQL:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "CREATE TABLE players (Name STRING, Class STRING, Level INTEGER, Hitpoints INTEGER)",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 2,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "INSERT INTO players (Name, Class, Level, Hitpoints) VALUES (\"Martin Splitskull\", \"Warrior\", 3, 40)",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 3,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT COUNT(*) as rowcount FROM players",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 4,
+      "outputs": [
+        {
+          "execution_count": 4,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>rowcount</th>\n</tr>\n<tr>\n<td>1</td>\n</tr>\n</table>",
+            "text/plain": "+----------+\n| rowcount |\n+----------+\n| 1        |\n+----------+"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "Only one command can be executed from within a single code cell:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "INSERT INTO players (Name, Class, Level, Hitpoints) VALUES (\"Sir Wolf\", \"Cleric\", 2, 20);\n\n-- The following will not be inserted\nINSERT INTO players (Name, Class, Level, Hitpoints) VALUES (\"Sylvain, The Grey\", \"Wizard\", 1, 10);",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 5,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT Name, Level, Hitpoints FROM players;",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 6,
+      "outputs": [
+        {
+          "execution_count": 6,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>Name</th>\n<th>Level</th>\n<th>Hitpoints</th>\n</tr>\n<tr>\n<td>Martin Splitskull</td>\n<td>3</td>\n<td>40</td>\n</tr>\n<tr>\n<td>Sir Wolf</td>\n<td>2</td>\n<td>20</td>\n</tr>\n</table>",
+            "text/plain": "+-------------------+-------+-----------+\n| Name              | Level | Hitpoints |\n+-------------------+-------+-----------+\n| Martin Splitskull | 3     | 40        |\n+-------------------+-------+-----------+\n| Sir Wolf          | 2     | 20        |\n+-------------------+-------+-----------+"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "code",
+      "source": "INSERT INTO players (Name, Class, Level, Hitpoints) VALUES (\"Sylvain, The Grey\", \"Wizard\", 1, 10);",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 7,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT Name, Level, Hitpoints FROM players;",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 8,
+      "outputs": [
+        {
+          "execution_count": 8,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>Name</th>\n<th>Level</th>\n<th>Hitpoints</th>\n</tr>\n<tr>\n<td>Martin Splitskull</td>\n<td>3</td>\n<td>40</td>\n</tr>\n<tr>\n<td>Sir Wolf</td>\n<td>2</td>\n<td>20</td>\n</tr>\n<tr>\n<td>Sylvain, The Grey</td>\n<td>1</td>\n<td>10</td>\n</tr>\n</table>",
+            "text/plain": "+-------------------+-------+-----------+\n| Name              | Level | Hitpoints |\n+-------------------+-------+-----------+\n| Martin Splitskull | 3     | 40        |\n+-------------------+-------+-----------+\n| Sir Wolf          | 2     | 20        |\n+-------------------+-------+-----------+\n| Sylvain, The Grey | 1     | 10        |\n+-------------------+-------+-----------+"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Quuerying Tables\n\nA full range of SQL query commands are supported, including aggregation operations:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT SUM (Level) FROM players",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": null,
+      "outputs": []
+    },
+    {
+      "cell_type": "markdown",
+      "source": "Grouping also works:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT Level, SUM(Hitpoints) AS `Total Hitpoints`\nFROM players\nGROUP BY Level\nORDER BY `Total Hitpoints` DESC;",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 11,
+      "outputs": [
+        {
+          "execution_count": 11,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>Level</th>\n<th>Total Hitpoints</th>\n</tr>\n<tr>\n<td>3</td>\n<td>40</td>\n</tr>\n<tr>\n<td>2</td>\n<td>20</td>\n</tr>\n<tr>\n<td>1</td>\n<td>10</td>\n</tr>\n</table>",
+            "text/plain": "+-------+-----------------+\n| Level | Total Hitpoints |\n+-------+-----------------+\n| 3     | 40              |\n+-------+-----------------+\n| 2     | 20              |\n+-------+-----------------+\n| 1     | 10              |\n+-------+-----------------+"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Charting Using Vega\n\nThe `jupyter-xeus/xeus-sqlite` kernel also bundles Vega charting components.\n\nVega charts can be generated by piping the result of a SQL query into a Vega line magic command.",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "%XVEGA_PLOT\n    X_FIELD Level\n    Y_FIELD Hitpoints\n    MARK circle\n    WIDTH 100\n    HEIGHT 200\n    <>\n    SELECT Level, Hitpoints FROM players",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 12,
+      "outputs": [
+        {
+          "execution_count": 12,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>Level</th>\n<th>Hitpoints</th>\n</tr>\n<tr>\n<td>3</td>\n<td>40</td>\n</tr>\n<tr>\n<td>2</td>\n<td>20</td>\n</tr>\n<tr>\n<td>1</td>\n<td>10</td>\n</tr>\n</table>",
+            "text/plain": "+-------+-----------+\n| Level | Hitpoints |\n+-------+-----------+\n| 3     | 40        |\n+-------+-----------+\n| 2     | 20        |\n+-------+-----------+\n| 1     | 10        |\n+-------+-----------+"
+          },
+          "metadata": {}
+        },
+        {
+          "execution_count": 12,
+          "output_type": "execute_result",
+          "data": {
+            "application/vnd.vegalite.v3+json": {
+              "$schema": "https://vega.github.io/schema/vega-lite/v4.json",
+              "config": {
+                "axis": {
+                  "grid": true
+                }
+              },
+              "data": {
+                "values": [
+                  {
+                    "Hitpoints": "name",
+                    "Level": "name"
+                  },
+                  {
+                    "Hitpoints": "40",
+                    "Level": "3"
+                  },
+                  {
+                    "Hitpoints": "20",
+                    "Level": "2"
+                  },
+                  {
+                    "Hitpoints": "10",
+                    "Level": "1"
+                  }
+                ]
+              },
+              "encoding": {
+                "x": {
+                  "field": "Level",
+                  "type": "quantitative"
+                },
+                "y": {
+                  "field": "Hitpoints",
+                  "type": "quantitative"
+                }
+              },
+              "height": 200,
+              "mark": {
+                "type": "circle"
+              },
+              "width": 100
+            },
+            "image/png": "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"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Database Admininstration\n\nSeveral line magics are defined to support database administration",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "%TABLE_EXISTS players",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 15,
+      "outputs": [
+        {
+          "execution_count": 15,
+          "output_type": "execute_result",
+          "data": {
+            "text/plain": "The table players exists."
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "code",
+      "source": "%TABLE_EXISTS npcs",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 16,
+      "outputs": [
+        {
+          "execution_count": 16,
+          "output_type": "execute_result",
+          "data": {
+            "text/plain": "The table npcs doesn't exist."
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "code",
+      "source": "%GET_INFO",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 17,
+      "outputs": [
+        {
+          "execution_count": 17,
+          "output_type": "execute_result",
+          "data": {
+            "text/plain": "Magic header string: SQLite format 3\nPage size bytes: 4096\nFile format write version: 1\nFile format read version: 1\nReserved space bytes: 0\nMax embedded payload fraction 64\nMin embedded payload fraction: 32\nLeaf payload fraction: 32\nFile change counter: 4\nDatabase size pages: 2\nFirst freelist trunk page: 0\nTotal freelist trunk pages: 0\nSchema cookie: 1\nSchema format number: 4\nDefault page cache size bytes: 0\nLargest B tree page number: 0\nDatabase text encoding: 1\nUser version: 0\nIncremental vaccum mode: 0\nApplication ID: 0\nVersion valid for: 4\nSQLite version: 3032003\n"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "## Connecting to a Different Databases\n\nCreating a new database will connect the kernel to the new database instance.",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "%CREATE potato.db ",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 18,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "CREATE TABLE potaters(production INTEGER)",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 19,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "INSERT INTO potaters (production) VALUES (7)",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 20,
+      "outputs": []
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT * FROM potaters",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 21,
+      "outputs": [
+        {
+          "execution_count": 21,
+          "output_type": "execute_result",
+          "data": {
+            "text/html": "<table>\n<tr>\n<th>production</th>\n</tr>\n<tr>\n<td>7</td>\n</tr>\n</table>",
+            "text/plain": "+------------+\n| production |\n+------------+\n| 7          |\n+------------+"
+          },
+          "metadata": {}
+        }
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "source": "The original database is lost:",
+      "metadata": {}
+    },
+    {
+      "cell_type": "code",
+      "source": "SELECT Name, Level, Hitpoints FROM players;",
+      "metadata": {
+        "trusted": true
+      },
+      "execution_count": 23,
+      "outputs": [
+        {
+          "ename": "Error",
+          "evalue": "no such table: players",
+          "traceback": [
+            "Error: no such table: players"
+          ],
+          "output_type": "error"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/repl/jupyter-lite.json b/repl/jupyter-lite.json
new file mode 100644
index 0000000..c202a1b
--- /dev/null
+++ b/repl/jupyter-lite.json
@@ -0,0 +1,11 @@
+{
+    "jupyter-lite-schema-version": 0,
+    "jupyter-config-data": {
+      "disabledExtensions": [
+        "@jupyterlab/drawio-extension",
+        "jupyterlab-kernel-spy",
+        "jupyterlab-tour"
+      ]
+    }
+  }
+  
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e1cf1fe
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,49 @@
+# Core modules (mandatory)
+jupyterlite-core==0.2.3
+jupyterlab~=4.0.11
+notebook~=7.0.7
+
+
+# Python kernel (optional)
+jupyterlite-pyodide-kernel==0.2.3
+
+# JavaScript kernel (optional)
+jupyterlite-javascript-kernel==0.2.3
+
+# Language support (optional)
+jupyterlab-language-pack-fr-FR
+jupyterlab-language-pack-zh-CN
+
+# SQLite kernel (optional)
+jupyterlite-xeus-sqlite==0.2.1
+# P5 kernel (optional)
+jupyterlite-p5-kernel==0.1.0
+# Lua kernel (optional)
+jupyterlite-xeus-lua==0.3.1
+
+# JupyterLab: Fasta file renderer (optional)
+jupyterlab-fasta>=3.3.0,<4
+# JupyterLab: Geojson file renderer (optional)
+jupyterlab-geojson>=3.4.0,<4
+# JupyterLab: guided tour (optional)
+# TODO: re-enable after https://github.com/jupyterlab-contrib/jupyterlab-tour/issues/82
+# jupyterlab-tour
+# JupyterLab: dark theme
+jupyterlab-night
+# JupyterLab: Miami nights theme (optional)
+jupyterlab_miami_nights
+
+# Python: ipywidget library for Jupyter notebooks (optional)
+ipywidgets>=8.1.1,<9
+# Python: ipyevents library for Jupyter notebooks (optional)
+ipyevents>=2.0.1
+# Python: interative Matplotlib library for Jupyter notebooks (optional)
+ipympl>=0.8.2
+# Python: ipycanvas library for Jupyter notebooks (optional)
+ipycanvas>=0.9.1
+# Python: ipyleaflet library for Jupyter notebooks (optional)
+ipyleaflet
+
+# Python: plotting libraries (optional)
+plotly>=5,<6
+bqplot