From ac89b5c4e761041e1f84999b0ba96546b9c3f296 Mon Sep 17 00:00:00 2001
From: Robert Lanzafame <R.C.Lanzafame@tudelft.nl>
Date: Wed, 19 Jul 2023 02:00:33 +0200
Subject: [PATCH 1/2] set up cookbook

---
 book/_toc.yml                            |    7 +-
 book/cookbook/blank.md                   |    3 -
 book/cookbook/interactive_element.ipynb  | 1112 ----------------------
 book/cookbook/interactive_ex_python.md   |   44 +
 book/cookbook/interactive_exercise.ipynb |  358 +++++++
 book/cookbook/overview.md                |   20 +
 book/cookbook/programming.md             |   12 +
 book/cookbook/referencing.md             |    4 +
 8 files changed, 443 insertions(+), 1117 deletions(-)
 delete mode 100644 book/cookbook/blank.md
 delete mode 100644 book/cookbook/interactive_element.ipynb
 create mode 100644 book/cookbook/interactive_ex_python.md
 create mode 100644 book/cookbook/interactive_exercise.ipynb
 create mode 100644 book/cookbook/overview.md
 create mode 100644 book/cookbook/programming.md
 create mode 100644 book/cookbook/referencing.md

diff --git a/book/_toc.yml b/book/_toc.yml
index ba85f9d2..0537a152 100644
--- a/book/_toc.yml
+++ b/book/_toc.yml
@@ -50,8 +50,11 @@ parts:
       - file: sandbox/SanPart/premath/00_03_PreMath.md
   - caption: MUDE Cookbook
     chapters:
-    - file: cookbook/blank
-    - file: cookbook/interactive_element
+    - file: cookbook/overview
+    - file: cookbook/referencing
+    - file: cookbook/interactive_exercise
+    - file: cookbook/interactive_ex_python
+    - file: cookbook/programming
   - caption: Old Material
     chapters:
     - file: old/blank
diff --git a/book/cookbook/blank.md b/book/cookbook/blank.md
deleted file mode 100644
index 53d3da81..00000000
--- a/book/cookbook/blank.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Cookbook
-
-Examples. Don't forget to check our [other book](https://interactivetextbooks.citg.tudelft.nl/manual/intro.html), the [repo for the other book](https://gitlab.tudelft.nl/interactivetextbooks-citg/jupyter-book-manual)and the [Jupyter Book website](https://jupyterbook.org/en/stable/).
\ No newline at end of file
diff --git a/book/cookbook/interactive_element.ipynb b/book/cookbook/interactive_element.ipynb
deleted file mode 100644
index f30da475..00000000
--- a/book/cookbook/interactive_element.ipynb
+++ /dev/null
@@ -1,1112 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": [
-    "# Adding non-coding interactive elements\n",
-    "\n",
-    "To add interactivity to the book, you can make use of interactive elements which are embedded in the book. You've four options:\n",
-    "\n",
-    " - Admonition: question provided in normal markdown language with answers in a drowdown menu. There's no feedback delivered to the student, only the correct answer no matter what the student's thinking/work.\n",
-    " - JupyterQuiz: code-based implementation of multiple-choiche questions and numerical answer questions. This is a basic open course tool which requires you to write your quiz questions in Python language as a `dict` or to a `.json` file.\n",
-    " - H5p: TU-Delft-licensed tool to create questions (and many other interactive elements) using an [online GUI](https://tudelft.h5p.com/). These elements are embedded into the book using an iframe which requires very little coding. This elements are easier to implement than the JupyterQuiz and have more features (although proper numerical answer questions are missing). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/cookbook/h5p.html)\n",
-    " - Grasple-exercises: TU-Delft-licensed tool to creating math-based quiz questions. Again, these elements are embedded into the book using an iframe which requiries very little coding. These elements are very well suited to math-based problems as it allows analytical evaluation of numerical values and formulas. Furthermore, Grasple allows parameterization (not required for formative exercises). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/cookbook/grasple.html)\n",
-    "\n",
-    "Examples of each type is shown below based on the following problem:\n",
-    "\n",
-    "A pre-fabricated bridge design is being considered for river crossings in a remote region of the world, as shown in Figure 1. Cities 1, 2 and 3 are labelled C1, C2 and C3, with bridges labelled B1-B4.\n",
-    "\n",
-    "```{figure} ../figures/pd/simple-city.png\n",
-    "---\n",
-    "width: 600px\n",
-    "name: exercise-simple-city\n",
-    "---"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": [
-    "## Admonition\n",
-    "If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2\n",
-    "\n",
-    "\n",
-    "```{admonition} Answer\n",
-    ":class: tip, dropdown\n",
-    "\n",
-    "$$\n",
-    "    P_{f_{C1-C2}} = P_{f_{B1}} * P_{f_{B4-B3-B2}} = P_{f_{B1}} * (1 - (1 - P_{f_{B4}})(1 - P_{f_{B3}}*P_{f_{B2}}))\n",
-    "$$\n",
-    "$$\n",
-    "    P_{f_{C1 - C2}} = 0.1 * (1 - (1 - 0.1)(1 - 0.1 * 0.1)) = 0.0109\n",
-    "$$\n",
-    "```"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": [
-    "## JupyterQuiz"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true,
-    "tags": [
-     "remove-input"
-    ]
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/html": [
-       "<div id=\"utfoWIEwcBPi\" data-shufflequestions=\"False\"\n",
-       "               data-shuffleanswers=\"True\"\n",
-       "               data-preserveresponses=\"false\"\n",
-       "               data-numquestions=\"1000000\"\n",
-       "               data-maxwidth=\"600\"\n",
-       "               style=\"border-radius: 0px; text-align: left\"> <style>\n",
-       "#utfoWIEwcBPi {\n",
-       "   --jq-multiple-choice-bg: #6f78ffff;\n",
-       "   --jq-mc-button-bg: #fafafa;\n",
-       "   --jq-mc-button-border: #e0e0e0e0;\n",
-       "   --jq-mc-button-inset-shadow: #555555;\n",
-       "   --jq-many-choice-bg: #f75c03ff;\n",
-       "   --jq-numeric-bg: #392061ff;\n",
-       "   --jq-numeric-input-bg: #c0c0c0;\n",
-       "   --jq-numeric-input-label: #101010;\n",
-       "   --jq-numeric-input-shadow: #999999;\n",
-       "   --jq-incorrect-color: #c80202;\n",
-       "   --jq-correct-color: #009113;\n",
-       "   --jq-text-color: #fafafa;\n",
-       "}\n",
-       "\n",
-       ".Quiz {\n",
-       "    max-width: 600px;\n",
-       "    margin-top: 15px;\n",
-       "    margin-left: auto;\n",
-       "    margin-right: auto;\n",
-       "    margin-bottom: 15px;\n",
-       "    padding-bottom: 4px;\n",
-       "    padding-top: 4px;\n",
-       "    line-height: 1.1;\n",
-       "    font-size: 16pt;\n",
-       "    border-radius: inherit;\n",
-       "}\n",
-       "\n",
-       ".QuizCode {\n",
-       "    font-size: 14pt;\n",
-       "    margin-top: 10px;\n",
-       "    margin-left: 20px;\n",
-       "    margin-right: 20px;\n",
-       "}\n",
-       "\n",
-       ".QuizCode>pre {\n",
-       "    padding: 4px;\n",
-       "}\n",
-       "\n",
-       ".Answer {\n",
-       "    margin: 10px 0;\n",
-       "    display: grid;\n",
-       "    grid-template-columns: 1fr 1fr;\n",
-       "    grid-gap: 10px;\n",
-       "    border-radius: inherit;\n",
-       "}\n",
-       "\n",
-       ".Feedback {\n",
-       "    font-size: 16pt;\n",
-       "    text-align: center;\n",
-       "    min-height: 2em;\n",
-       "}\n",
-       "\n",
-       ".Input {\n",
-       "    align: left;\n",
-       "    font-size: 20pt;\n",
-       "}\n",
-       "\n",
-       ".Input-text {\n",
-       "    display: block;\n",
-       "    margin: 10px;\n",
-       "    color: inherit;\n",
-       "    width: 140px;\n",
-       "    background-color: var(--jq-numeric-input-bg);\n",
-       "    color: var(--jq-text-color);\n",
-       "    padding: 5px;\n",
-       "    padding-left: 10px;\n",
-       "    font-family: inherit;\n",
-       "    font-size: 20px;\n",
-       "    font-weight: inherit;\n",
-       "    line-height: 20pt;\n",
-       "    border: none;\n",
-       "    border-radius: 0.2rem;\n",
-       "    transition: box-shadow 0.1s);\n",
-       "}\n",
-       "\n",
-       ".Input-text:focus {\n",
-       "    outline: none;\n",
-       "    background-color: var(--jq-numeric-input-bg);\n",
-       "    box-shadow: 0.6rem 0.8rem 1.4rem -0.5rem var(--jq-numeric-input-shadow);\n",
-       "}\n",
-       "\n",
-       ".MCButton {\n",
-       "    background: var(--jq-mc-button-bg);\n",
-       "    border: 1px solid var(--jq-mc-button-border);\n",
-       "    border-radius: inherit;\n",
-       "    padding: 10px;\n",
-       "    font-size: 16px;\n",
-       "    cursor: pointer;\n",
-       "    text-align: center;\n",
-       "    display: flex;\n",
-       "    align-items: center;\n",
-       "    justify-content: center;\n",
-       "}\n",
-       "\n",
-       ".MCButton p {\n",
-       "    color: inherit;\n",
-       "}\n",
-       "\n",
-       ".MultipleChoiceQn {\n",
-       "    padding: 10px;\n",
-       "    background: var(--jq-multiple-choice-bg);\n",
-       "    color: var(--jq-text-color);\n",
-       "    border-radius: inherit;\n",
-       "}\n",
-       "\n",
-       ".ManyChoiceQn {\n",
-       "    padding: 10px;\n",
-       "    background: var(--jq-many-choice-bg);\n",
-       "    color: var(--jq-text-color);\n",
-       "    border-radius: inherit;\n",
-       "}\n",
-       "\n",
-       ".NumericQn {\n",
-       "    padding: 10px;\n",
-       "    background: var(--jq-numeric-bg);\n",
-       "    color: var(--jq-text-color);\n",
-       "    border-radius: inherit;\n",
-       "}\n",
-       "\n",
-       ".NumericQn p {\n",
-       "    color: inherit;\n",
-       "}\n",
-       "\n",
-       ".InpLabel {\n",
-       "    line-height: 34px;\n",
-       "    float: left;\n",
-       "    margin-right: 10px;\n",
-       "    color: var(--jq-numeric-input-label);\n",
-       "    font-size: 15pt;\n",
-       "}\n",
-       "\n",
-       ".incorrect {\n",
-       "    color: var(--jq-incorrect-color);\n",
-       "}\n",
-       "\n",
-       ".correct {\n",
-       "    color: var(--jq-correct-color);\n",
-       "}\n",
-       "\n",
-       ".correctButton {\n",
-       "    /*\n",
-       "    background: var(--jq-correct-color);\n",
-       "   */\n",
-       "    animation: correct-anim 0.6s ease;\n",
-       "    animation-fill-mode: forwards;\n",
-       "    color: var(--jq-text-color);\n",
-       "    box-shadow: inset 0px 0px 5px var(--jq-mc-button-inset-shadow);\n",
-       "    outline: none;\n",
-       "}\n",
-       "\n",
-       ".incorrectButton {\n",
-       "    animation: incorrect-anim 0.8s ease;\n",
-       "    animation-fill-mode: forwards;\n",
-       "    color: var(--jq-text-color);\n",
-       "    box-shadow: inset 0px 0px 5px var(--jq-mc-button-inset-shadow);\n",
-       "    outline: none;\n",
-       "}\n",
-       "\n",
-       "@keyframes incorrect-anim {\n",
-       "    100% {\n",
-       "        background-color: var(--jq-incorrect-color);\n",
-       "    }\n",
-       "}\n",
-       "\n",
-       "@keyframes correct-anim {\n",
-       "    100% {\n",
-       "        background-color: var(--jq-correct-color);\n",
-       "    }\n",
-       "}\n",
-       "</style>"
-      ],
-      "text/plain": [
-       "<IPython.core.display.HTML object>"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/javascript": [
-       "var questionsutfoWIEwcBPi=[{\"question\": \"If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2:\", \"type\": \"numeric\", \"answers\": [{\"type\": \"range\", \"value\": 0.0109, \"correct\": true, \"feedback\": \"Correct.\"}, {\"type\": \"default\", \"feedback\": \"Try again\"}]}];\n",
-       "    // Make a random ID\n",
-       "function makeid(length) {\n",
-       "    var result = [];\n",
-       "    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n",
-       "    var charactersLength = characters.length;\n",
-       "    for (var i = 0; i < length; i++) {\n",
-       "        result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));\n",
-       "    }\n",
-       "    return result.join('');\n",
-       "}\n",
-       "\n",
-       "// Choose a random subset of an array. Can also be used to shuffle the array\n",
-       "function getRandomSubarray(arr, size) {\n",
-       "    var shuffled = arr.slice(0), i = arr.length, temp, index;\n",
-       "    while (i--) {\n",
-       "        index = Math.floor((i + 1) * Math.random());\n",
-       "        temp = shuffled[index];\n",
-       "        shuffled[index] = shuffled[i];\n",
-       "        shuffled[i] = temp;\n",
-       "    }\n",
-       "    return shuffled.slice(0, size);\n",
-       "}\n",
-       "\n",
-       "function printResponses(responsesContainer) {\n",
-       "    var responses=JSON.parse(responsesContainer.dataset.responses);\n",
-       "    var stringResponses='<B>IMPORTANT!</B>To preserve this answer sequence for submission, when you have finalized your answers: <ol> <li> Copy the text in this cell below \"Answer String\"</li> <li> Double click on the cell directly below the Answer String, labeled \"Replace Me\"</li> <li> Select the whole \"Replace Me\" text</li> <li> Paste in your answer string and press shift-Enter.</li><li>Save the notebook using the save icon or File->Save Notebook menu item</li></ul><br><br><br><b>Answer String:</b><br> ';\n",
-       "    console.log(responses);\n",
-       "    responses.forEach((response, index) => {\n",
-       "        if (response) {\n",
-       "            console.log(index + ': ' + response);\n",
-       "            stringResponses+= index + ': ' + response +\"<BR>\";\n",
-       "        }\n",
-       "    });\n",
-       "    responsesContainer.innerHTML=stringResponses;\n",
-       "}\n",
-       "function check_mc() {\n",
-       "    var id = this.id.split('-')[0];\n",
-       "    //var response = this.id.split('-')[1];\n",
-       "    //console.log(response);\n",
-       "    //console.log(\"In check_mc(), id=\"+id);\n",
-       "    //console.log(event.srcElement.id)           \n",
-       "    //console.log(event.srcElement.dataset.correct)   \n",
-       "    //console.log(event.srcElement.dataset.feedback)\n",
-       "\n",
-       "    var label = event.srcElement;\n",
-       "    //console.log(label, label.nodeName);\n",
-       "    var depth = 0;\n",
-       "    while ((label.nodeName != \"LABEL\") && (depth < 20)) {\n",
-       "        label = label.parentElement;\n",
-       "        console.log(depth, label);\n",
-       "        depth++;\n",
-       "    }\n",
-       "\n",
-       "\n",
-       "\n",
-       "    var answers = label.parentElement.children;\n",
-       "\n",
-       "    //console.log(answers);\n",
-       "\n",
-       "\n",
-       "    // Split behavior based on multiple choice vs many choice:\n",
-       "    var fb = document.getElementById(\"fb\" + id);\n",
-       "\n",
-       "\n",
-       "\n",
-       "\n",
-       "    if (fb.dataset.numcorrect == 1) {\n",
-       "        // What follows is for the saved responses stuff\n",
-       "        var outerContainer = fb.parentElement.parentElement;\n",
-       "        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n",
-       "        if (responsesContainer) {\n",
-       "            //console.log(responsesContainer);\n",
-       "            var response = label.firstChild.innerText;\n",
-       "            if (label.querySelector(\".QuizCode\")){\n",
-       "                response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n",
-       "            }\n",
-       "            console.log(response);\n",
-       "            //console.log(document.getElementById(\"quizWrap\"+id));\n",
-       "            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n",
-       "            console.log(\"Question \" + qnum);\n",
-       "            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n",
-       "            var responses=JSON.parse(responsesContainer.dataset.responses);\n",
-       "            console.log(responses);\n",
-       "            responses[qnum]= response;\n",
-       "            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n",
-       "            printResponses(responsesContainer);\n",
-       "        }\n",
-       "        // End code to preserve responses\n",
-       "        \n",
-       "        for (var i = 0; i < answers.length; i++) {\n",
-       "            var child = answers[i];\n",
-       "            //console.log(child);\n",
-       "            child.className = \"MCButton\";\n",
-       "        }\n",
-       "\n",
-       "\n",
-       "\n",
-       "        if (label.dataset.correct == \"true\") {\n",
-       "            // console.log(\"Correct action\");\n",
-       "            if (\"feedback\" in label.dataset) {\n",
-       "                fb.textContent = jaxify(label.dataset.feedback);\n",
-       "            } else {\n",
-       "                fb.textContent = \"Correct!\";\n",
-       "            }\n",
-       "            label.classList.add(\"correctButton\");\n",
-       "\n",
-       "            fb.className = \"Feedback\";\n",
-       "            fb.classList.add(\"correct\");\n",
-       "\n",
-       "        } else {\n",
-       "            if (\"feedback\" in label.dataset) {\n",
-       "                fb.textContent = jaxify(label.dataset.feedback);\n",
-       "            } else {\n",
-       "                fb.textContent = \"Incorrect -- try again.\";\n",
-       "            }\n",
-       "            //console.log(\"Error action\");\n",
-       "            label.classList.add(\"incorrectButton\");\n",
-       "            fb.className = \"Feedback\";\n",
-       "            fb.classList.add(\"incorrect\");\n",
-       "        }\n",
-       "    }\n",
-       "    else {\n",
-       "        var reset = false;\n",
-       "        var feedback;\n",
-       "         if (label.dataset.correct == \"true\") {\n",
-       "            if (\"feedback\" in label.dataset) {\n",
-       "                feedback = jaxify(label.dataset.feedback);\n",
-       "            } else {\n",
-       "                feedback = \"Correct!\";\n",
-       "            }\n",
-       "            if (label.dataset.answered <= 0) {\n",
-       "                if (fb.dataset.answeredcorrect < 0) {\n",
-       "                    fb.dataset.answeredcorrect = 1;\n",
-       "                    reset = true;\n",
-       "                } else {\n",
-       "                    fb.dataset.answeredcorrect++;\n",
-       "                }\n",
-       "                if (reset) {\n",
-       "                    for (var i = 0; i < answers.length; i++) {\n",
-       "                        var child = answers[i];\n",
-       "                        child.className = \"MCButton\";\n",
-       "                        child.dataset.answered = 0;\n",
-       "                    }\n",
-       "                }\n",
-       "                label.classList.add(\"correctButton\");\n",
-       "                label.dataset.answered = 1;\n",
-       "                fb.className = \"Feedback\";\n",
-       "                fb.classList.add(\"correct\");\n",
-       "\n",
-       "            }\n",
-       "        } else {\n",
-       "            if (\"feedback\" in label.dataset) {\n",
-       "                feedback = jaxify(label.dataset.feedback);\n",
-       "            } else {\n",
-       "                feedback = \"Incorrect -- try again.\";\n",
-       "            }\n",
-       "            if (fb.dataset.answeredcorrect > 0) {\n",
-       "                fb.dataset.answeredcorrect = -1;\n",
-       "                reset = true;\n",
-       "            } else {\n",
-       "                fb.dataset.answeredcorrect--;\n",
-       "            }\n",
-       "\n",
-       "            if (reset) {\n",
-       "                for (var i = 0; i < answers.length; i++) {\n",
-       "                    var child = answers[i];\n",
-       "                    child.className = \"MCButton\";\n",
-       "                    child.dataset.answered = 0;\n",
-       "                }\n",
-       "            }\n",
-       "            label.classList.add(\"incorrectButton\");\n",
-       "            fb.className = \"Feedback\";\n",
-       "            fb.classList.add(\"incorrect\");\n",
-       "        }\n",
-       "        // What follows is for the saved responses stuff\n",
-       "        var outerContainer = fb.parentElement.parentElement;\n",
-       "        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n",
-       "        if (responsesContainer) {\n",
-       "            //console.log(responsesContainer);\n",
-       "            var response = label.firstChild.innerText;\n",
-       "            if (label.querySelector(\".QuizCode\")){\n",
-       "                response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n",
-       "            }\n",
-       "            console.log(response);\n",
-       "            //console.log(document.getElementById(\"quizWrap\"+id));\n",
-       "            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n",
-       "            console.log(\"Question \" + qnum);\n",
-       "            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n",
-       "            var responses=JSON.parse(responsesContainer.dataset.responses);\n",
-       "            if (label.dataset.correct == \"true\") {\n",
-       "                if (typeof(responses[qnum]) == \"object\"){\n",
-       "                    if (!responses[qnum].includes(response))\n",
-       "                        responses[qnum].push(response);\n",
-       "                } else{\n",
-       "                    responses[qnum]= [ response ];\n",
-       "                }\n",
-       "            } else {\n",
-       "                responses[qnum]= response;\n",
-       "            }\n",
-       "            console.log(responses);\n",
-       "            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n",
-       "            printResponses(responsesContainer);\n",
-       "        }\n",
-       "        // End save responses stuff\n",
-       "\n",
-       "\n",
-       "\n",
-       "        var numcorrect = fb.dataset.numcorrect;\n",
-       "        var answeredcorrect = fb.dataset.answeredcorrect;\n",
-       "        if (answeredcorrect >= 0) {\n",
-       "            fb.textContent = feedback + \" [\" + answeredcorrect + \"/\" + numcorrect + \"]\";\n",
-       "        } else {\n",
-       "            fb.textContent = feedback + \" [\" + 0 + \"/\" + numcorrect + \"]\";\n",
-       "        }\n",
-       "\n",
-       "\n",
-       "    }\n",
-       "\n",
-       "    if (typeof MathJax != 'undefined') {\n",
-       "        var version = MathJax.version;\n",
-       "        console.log('MathJax version', version);\n",
-       "        if (version[0] == \"2\") {\n",
-       "            MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n",
-       "        } else if (version[0] == \"3\") {\n",
-       "            MathJax.typeset([fb]);\n",
-       "        }\n",
-       "    } else {\n",
-       "        console.log('MathJax not detected');\n",
-       "    }\n",
-       "\n",
-       "}\n",
-       "\n",
-       "function make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id) {\n",
-       "    var shuffled;\n",
-       "    if (shuffle_answers == \"True\") {\n",
-       "        //console.log(shuffle_answers+\" read as true\");\n",
-       "        shuffled = getRandomSubarray(qa.answers, qa.answers.length);\n",
-       "    } else {\n",
-       "        //console.log(shuffle_answers+\" read as false\");\n",
-       "        shuffled = qa.answers;\n",
-       "    }\n",
-       "\n",
-       "\n",
-       "    var num_correct = 0;\n",
-       "\n",
-       "\n",
-       "\n",
-       "    shuffled.forEach((item, index, ans_array) => {\n",
-       "        //console.log(answer);\n",
-       "\n",
-       "        // Make input element\n",
-       "        var inp = document.createElement(\"input\");\n",
-       "        inp.type = \"radio\";\n",
-       "        inp.id = \"quizo\" + id + index;\n",
-       "        inp.style = \"display:none;\";\n",
-       "        aDiv.append(inp);\n",
-       "\n",
-       "        //Make label for input element\n",
-       "        var lab = document.createElement(\"label\");\n",
-       "        lab.className = \"MCButton\";\n",
-       "        lab.id = id + '-' + index;\n",
-       "        lab.onclick = check_mc;\n",
-       "        var aSpan = document.createElement('span');\n",
-       "        aSpan.classsName = \"\";\n",
-       "        //qDiv.id=\"quizQn\"+id+index;\n",
-       "        if (\"answer\" in item) {\n",
-       "            aSpan.innerHTML = jaxify(item.answer);\n",
-       "            //aSpan.innerHTML=item.answer;\n",
-       "        }\n",
-       "        lab.append(aSpan);\n",
-       "\n",
-       "        // Create div for code inside question\n",
-       "        var codeSpan;\n",
-       "        if (\"code\" in item) {\n",
-       "            codeSpan = document.createElement('span');\n",
-       "            codeSpan.id = \"code\" + id + index;\n",
-       "            codeSpan.className = \"QuizCode\";\n",
-       "            var codePre = document.createElement('pre');\n",
-       "            codeSpan.append(codePre);\n",
-       "            var codeCode = document.createElement('code');\n",
-       "            codePre.append(codeCode);\n",
-       "            codeCode.innerHTML = item.code;\n",
-       "            lab.append(codeSpan);\n",
-       "            //console.log(codeSpan);\n",
-       "        }\n",
-       "\n",
-       "        //lab.textContent=item.answer;\n",
-       "\n",
-       "        // Set the data attributes for the answer\n",
-       "        lab.setAttribute('data-correct', item.correct);\n",
-       "        if (item.correct) {\n",
-       "            num_correct++;\n",
-       "        }\n",
-       "        if (\"feedback\" in item) {\n",
-       "            lab.setAttribute('data-feedback', item.feedback);\n",
-       "        }\n",
-       "        lab.setAttribute('data-answered', 0);\n",
-       "\n",
-       "        aDiv.append(lab);\n",
-       "\n",
-       "    });\n",
-       "\n",
-       "    if (num_correct > 1) {\n",
-       "        outerqDiv.className = \"ManyChoiceQn\";\n",
-       "    } else {\n",
-       "        outerqDiv.className = \"MultipleChoiceQn\";\n",
-       "    }\n",
-       "\n",
-       "    return num_correct;\n",
-       "\n",
-       "}\n",
-       "function check_numeric(ths, event) {\n",
-       "\n",
-       "    if (event.keyCode === 13) {\n",
-       "        ths.blur();\n",
-       "\n",
-       "        var id = ths.id.split('-')[0];\n",
-       "\n",
-       "        var submission = ths.value;\n",
-       "        if (submission.indexOf('/') != -1) {\n",
-       "            var sub_parts = submission.split('/');\n",
-       "            //console.log(sub_parts);\n",
-       "            submission = sub_parts[0] / sub_parts[1];\n",
-       "        }\n",
-       "        //console.log(\"Reader entered\", submission);\n",
-       "\n",
-       "        if (\"precision\" in ths.dataset) {\n",
-       "            var precision = ths.dataset.precision;\n",
-       "            // console.log(\"1:\", submission)\n",
-       "            submission = Math.round((1 * submission + Number.EPSILON) * 10 ** precision) / 10 ** precision;\n",
-       "            // console.log(\"Rounded to \", submission, \" precision=\", precision  );\n",
-       "        }\n",
-       "\n",
-       "\n",
-       "        //console.log(\"In check_numeric(), id=\"+id);\n",
-       "        //console.log(event.srcElement.id)           \n",
-       "        //console.log(event.srcElement.dataset.feedback)\n",
-       "\n",
-       "        var fb = document.getElementById(\"fb\" + id);\n",
-       "        fb.style.display = \"none\";\n",
-       "        fb.textContent = \"Incorrect -- try again.\";\n",
-       "\n",
-       "        var answers = JSON.parse(ths.dataset.answers);\n",
-       "        //console.log(answers);\n",
-       "\n",
-       "        var defaultFB = \"\";\n",
-       "        var correct;\n",
-       "        var done = false;\n",
-       "        answers.every(answer => {\n",
-       "            //console.log(answer.type);\n",
-       "\n",
-       "            correct = false;\n",
-       "            // if (answer.type==\"value\"){\n",
-       "            if ('value' in answer) {\n",
-       "                if (submission == answer.value) {\n",
-       "                    if (\"feedback\" in answer) {\n",
-       "                        fb.textContent = jaxify(answer.feedback);\n",
-       "                    } else {\n",
-       "                        fb.textContent = jaxify(\"Correct\");\n",
-       "                    }\n",
-       "                    correct = answer.correct;\n",
-       "                    //console.log(answer.correct);\n",
-       "                    done = true;\n",
-       "                }\n",
-       "                // } else if (answer.type==\"range\") {\n",
-       "            } else if ('range' in answer) {\n",
-       "                //console.log(answer.range);\n",
-       "                if ((submission >= answer.range[0]) && (submission < answer.range[1])) {\n",
-       "                    fb.textContent = jaxify(answer.feedback);\n",
-       "                    correct = answer.correct;\n",
-       "                    //console.log(answer.correct);\n",
-       "                    done = true;\n",
-       "                }\n",
-       "            } else if (answer.type == \"default\") {\n",
-       "                defaultFB = answer.feedback;\n",
-       "            }\n",
-       "            if (done) {\n",
-       "                return false; // Break out of loop if this has been marked correct\n",
-       "            } else {\n",
-       "                return true; // Keep looking for case that includes this as a correct answer\n",
-       "            }\n",
-       "        });\n",
-       "\n",
-       "        if ((!done) && (defaultFB != \"\")) {\n",
-       "            fb.innerHTML = jaxify(defaultFB);\n",
-       "            //console.log(\"Default feedback\", defaultFB);\n",
-       "        }\n",
-       "\n",
-       "        fb.style.display = \"block\";\n",
-       "        if (correct) {\n",
-       "            ths.className = \"Input-text\";\n",
-       "            ths.classList.add(\"correctButton\");\n",
-       "            fb.className = \"Feedback\";\n",
-       "            fb.classList.add(\"correct\");\n",
-       "        } else {\n",
-       "            ths.className = \"Input-text\";\n",
-       "            ths.classList.add(\"incorrectButton\");\n",
-       "            fb.className = \"Feedback\";\n",
-       "            fb.classList.add(\"incorrect\");\n",
-       "        }\n",
-       "\n",
-       "        // What follows is for the saved responses stuff\n",
-       "        var outerContainer = fb.parentElement.parentElement;\n",
-       "        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n",
-       "        if (responsesContainer) {\n",
-       "            console.log(submission);\n",
-       "            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n",
-       "            //console.log(\"Question \" + qnum);\n",
-       "            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n",
-       "            var responses=JSON.parse(responsesContainer.dataset.responses);\n",
-       "            console.log(responses);\n",
-       "            if (submission == ths.value){\n",
-       "                responses[qnum]= submission;\n",
-       "            } else {\n",
-       "                responses[qnum]= ths.value + \"(\" + submission +\")\";\n",
-       "            }\n",
-       "            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n",
-       "            printResponses(responsesContainer);\n",
-       "        }\n",
-       "        // End code to preserve responses\n",
-       "\n",
-       "        if (typeof MathJax != 'undefined') {\n",
-       "            var version = MathJax.version;\n",
-       "            console.log('MathJax version', version);\n",
-       "            if (version[0] == \"2\") {\n",
-       "                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n",
-       "            } else if (version[0] == \"3\") {\n",
-       "                MathJax.typeset([fb]);\n",
-       "            }\n",
-       "        } else {\n",
-       "            console.log('MathJax not detected');\n",
-       "        }\n",
-       "        return false;\n",
-       "    }\n",
-       "\n",
-       "}\n",
-       "\n",
-       "function isValid(el, charC) {\n",
-       "    //console.log(\"Input char: \", charC);\n",
-       "    if (charC == 46) {\n",
-       "        if (el.value.indexOf('.') === -1) {\n",
-       "            return true;\n",
-       "        } else if (el.value.indexOf('/') != -1) {\n",
-       "            var parts = el.value.split('/');\n",
-       "            if (parts[1].indexOf('.') === -1) {\n",
-       "                return true;\n",
-       "            }\n",
-       "        }\n",
-       "        else {\n",
-       "            return false;\n",
-       "        }\n",
-       "    } else if (charC == 47) {\n",
-       "        if (el.value.indexOf('/') === -1) {\n",
-       "            if ((el.value != \"\") && (el.value != \".\")) {\n",
-       "                return true;\n",
-       "            } else {\n",
-       "                return false;\n",
-       "            }\n",
-       "        } else {\n",
-       "            return false;\n",
-       "        }\n",
-       "    } else if (charC == 45) {\n",
-       "        var edex = el.value.indexOf('e');\n",
-       "        if (edex == -1) {\n",
-       "            edex = el.value.indexOf('E');\n",
-       "        }\n",
-       "\n",
-       "        if (el.value == \"\") {\n",
-       "            return true;\n",
-       "        } else if (edex == (el.value.length - 1)) { // If just after e or E\n",
-       "            return true;\n",
-       "        } else {\n",
-       "            return false;\n",
-       "        }\n",
-       "    } else if (charC == 101) { // \"e\"\n",
-       "        if ((el.value.indexOf('e') === -1) && (el.value.indexOf('E') === -1) && (el.value.indexOf('/') == -1)) {\n",
-       "            // Prev symbol must be digit or decimal point:\n",
-       "            if (el.value.slice(-1).search(/\\d/) >= 0) {\n",
-       "                return true;\n",
-       "            } else if (el.value.slice(-1).search(/\\./) >= 0) {\n",
-       "                return true;\n",
-       "            } else {\n",
-       "                return false;\n",
-       "            }\n",
-       "        } else {\n",
-       "            return false;\n",
-       "        }\n",
-       "    } else {\n",
-       "        if (charC > 31 && (charC < 48 || charC > 57))\n",
-       "            return false;\n",
-       "    }\n",
-       "    return true;\n",
-       "}\n",
-       "\n",
-       "function numeric_keypress(evnt) {\n",
-       "    var charC = (evnt.which) ? evnt.which : evnt.keyCode;\n",
-       "\n",
-       "    if (charC == 13) {\n",
-       "        check_numeric(this, evnt);\n",
-       "    } else {\n",
-       "        return isValid(this, charC);\n",
-       "    }\n",
-       "}\n",
-       "\n",
-       "\n",
-       "\n",
-       "\n",
-       "\n",
-       "function make_numeric(qa, outerqDiv, qDiv, aDiv, id) {\n",
-       "\n",
-       "\n",
-       "\n",
-       "    //console.log(answer);\n",
-       "\n",
-       "\n",
-       "    outerqDiv.className = \"NumericQn\";\n",
-       "    aDiv.style.display = 'block';\n",
-       "\n",
-       "    var lab = document.createElement(\"label\");\n",
-       "    lab.className = \"InpLabel\";\n",
-       "    lab.textContent = \"Type numeric answer here:\";\n",
-       "    aDiv.append(lab);\n",
-       "\n",
-       "    var inp = document.createElement(\"input\");\n",
-       "    inp.type = \"text\";\n",
-       "    //inp.id=\"input-\"+id;\n",
-       "    inp.id = id + \"-0\";\n",
-       "    inp.className = \"Input-text\";\n",
-       "    inp.setAttribute('data-answers', JSON.stringify(qa.answers));\n",
-       "    if (\"precision\" in qa) {\n",
-       "        inp.setAttribute('data-precision', qa.precision);\n",
-       "    }\n",
-       "    aDiv.append(inp);\n",
-       "    //console.log(inp);\n",
-       "\n",
-       "    //inp.addEventListener(\"keypress\", check_numeric);\n",
-       "    //inp.addEventListener(\"keypress\", numeric_keypress);\n",
-       "    /*\n",
-       "    inp.addEventListener(\"keypress\", function(event) {\n",
-       "        return numeric_keypress(this, event);\n",
-       "    }\n",
-       "                        );\n",
-       "                        */\n",
-       "    //inp.onkeypress=\"return numeric_keypress(this, event)\";\n",
-       "    inp.onkeypress = numeric_keypress;\n",
-       "    inp.onpaste = event => false;\n",
-       "\n",
-       "    inp.addEventListener(\"focus\", function (event) {\n",
-       "        this.value = \"\";\n",
-       "        return false;\n",
-       "    }\n",
-       "    );\n",
-       "\n",
-       "\n",
-       "}\n",
-       "function jaxify(string) {\n",
-       "    var mystring = string;\n",
-       "\n",
-       "    var count = 0;\n",
-       "    var loc = mystring.search(/([^\\\\]|^)(\\$)/);\n",
-       "\n",
-       "    var count2 = 0;\n",
-       "    var loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n",
-       "\n",
-       "    //console.log(loc);\n",
-       "\n",
-       "    while ((loc >= 0) || (loc2 >= 0)) {\n",
-       "\n",
-       "        /* Have to replace all the double $$ first with current implementation */\n",
-       "        if (loc2 >= 0) {\n",
-       "            if (count2 % 2 == 0) {\n",
-       "                mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\[\");\n",
-       "            } else {\n",
-       "                mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\]\");\n",
-       "            }\n",
-       "            count2++;\n",
-       "        } else {\n",
-       "            if (count % 2 == 0) {\n",
-       "                mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\(\");\n",
-       "            } else {\n",
-       "                mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\)\");\n",
-       "            }\n",
-       "            count++;\n",
-       "        }\n",
-       "        loc = mystring.search(/([^\\\\]|^)(\\$)/);\n",
-       "        loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n",
-       "        //console.log(mystring,\", loc:\",loc,\", loc2:\",loc2);\n",
-       "    }\n",
-       "\n",
-       "    //console.log(mystring);\n",
-       "    return mystring;\n",
-       "}\n",
-       "\n",
-       "\n",
-       "function show_questions(json, mydiv) {\n",
-       "    console.log('show_questions');\n",
-       "    //var mydiv=document.getElementById(myid);\n",
-       "    var shuffle_questions = mydiv.dataset.shufflequestions;\n",
-       "    var num_questions = mydiv.dataset.numquestions;\n",
-       "    var shuffle_answers = mydiv.dataset.shuffleanswers;\n",
-       "    var max_width = mydiv.dataset.maxwidth;\n",
-       "\n",
-       "    if (num_questions > json.length) {\n",
-       "        num_questions = json.length;\n",
-       "    }\n",
-       "\n",
-       "    var questions;\n",
-       "    if ((num_questions < json.length) || (shuffle_questions == \"True\")) {\n",
-       "        //console.log(num_questions+\",\"+json.length);\n",
-       "        questions = getRandomSubarray(json, num_questions);\n",
-       "    } else {\n",
-       "        questions = json;\n",
-       "    }\n",
-       "\n",
-       "    //console.log(\"SQ: \"+shuffle_questions+\", NQ: \" + num_questions + \", SA: \", shuffle_answers);\n",
-       "\n",
-       "    // Iterate over questions\n",
-       "    questions.forEach((qa, index, array) => {\n",
-       "        //console.log(qa.question); \n",
-       "\n",
-       "        var id = makeid(8);\n",
-       "        //console.log(id);\n",
-       "\n",
-       "\n",
-       "        // Create Div to contain question and answers\n",
-       "        var iDiv = document.createElement('div');\n",
-       "        //iDiv.id = 'quizWrap' + id + index;\n",
-       "        iDiv.id = 'quizWrap' + id;\n",
-       "        iDiv.className = 'Quiz';\n",
-       "        iDiv.setAttribute('data-qnum', index);\n",
-       "        iDiv.style.maxWidth  =max_width+\"px\";\n",
-       "        mydiv.appendChild(iDiv);\n",
-       "        // iDiv.innerHTML=qa.question;\n",
-       "        \n",
-       "        var outerqDiv = document.createElement('div');\n",
-       "        outerqDiv.id = \"OuterquizQn\" + id + index;\n",
-       "        // Create div to contain question part\n",
-       "        var qDiv = document.createElement('div');\n",
-       "        qDiv.id = \"quizQn\" + id + index;\n",
-       "        \n",
-       "        if (qa.question) {\n",
-       "            iDiv.append(outerqDiv);\n",
-       "\n",
-       "            //qDiv.textContent=qa.question;\n",
-       "            qDiv.innerHTML = jaxify(qa.question);\n",
-       "            outerqDiv.append(qDiv);\n",
-       "        }\n",
-       "\n",
-       "        // Create div for code inside question\n",
-       "        var codeDiv;\n",
-       "        if (\"code\" in qa) {\n",
-       "            codeDiv = document.createElement('div');\n",
-       "            codeDiv.id = \"code\" + id + index;\n",
-       "            codeDiv.className = \"QuizCode\";\n",
-       "            var codePre = document.createElement('pre');\n",
-       "            codeDiv.append(codePre);\n",
-       "            var codeCode = document.createElement('code');\n",
-       "            codePre.append(codeCode);\n",
-       "            codeCode.innerHTML = qa.code;\n",
-       "            outerqDiv.append(codeDiv);\n",
-       "            //console.log(codeDiv);\n",
-       "        }\n",
-       "\n",
-       "\n",
-       "        // Create div to contain answer part\n",
-       "        var aDiv = document.createElement('div');\n",
-       "        aDiv.id = \"quizAns\" + id + index;\n",
-       "        aDiv.className = 'Answer';\n",
-       "        iDiv.append(aDiv);\n",
-       "\n",
-       "        //console.log(qa.type);\n",
-       "\n",
-       "        var num_correct;\n",
-       "        if ((qa.type == \"multiple_choice\") || (qa.type == \"many_choice\") ) {\n",
-       "            num_correct = make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id);\n",
-       "            if (\"answer_cols\" in qa) {\n",
-       "                //aDiv.style.gridTemplateColumns = 'auto '.repeat(qa.answer_cols);\n",
-       "                aDiv.style.gridTemplateColumns = 'repeat(' + qa.answer_cols + ', 1fr)';\n",
-       "            }\n",
-       "        } else if (qa.type == \"numeric\") {\n",
-       "            //console.log(\"numeric\");\n",
-       "            make_numeric(qa, outerqDiv, qDiv, aDiv, id);\n",
-       "        }\n",
-       "\n",
-       "\n",
-       "        //Make div for feedback\n",
-       "        var fb = document.createElement(\"div\");\n",
-       "        fb.id = \"fb\" + id;\n",
-       "        //fb.style=\"font-size: 20px;text-align:center;\";\n",
-       "        fb.className = \"Feedback\";\n",
-       "        fb.setAttribute(\"data-answeredcorrect\", 0);\n",
-       "        fb.setAttribute(\"data-numcorrect\", num_correct);\n",
-       "        iDiv.append(fb);\n",
-       "\n",
-       "\n",
-       "    });\n",
-       "    var preserveResponses = mydiv.dataset.preserveresponses;\n",
-       "    console.log(preserveResponses);\n",
-       "    console.log(preserveResponses == \"true\");\n",
-       "    if (preserveResponses == \"true\") {\n",
-       "        console.log(preserveResponses);\n",
-       "        // Create Div to contain record of answers\n",
-       "        var iDiv = document.createElement('div');\n",
-       "        iDiv.id = 'responses' + mydiv.id;\n",
-       "        iDiv.className = 'JCResponses';\n",
-       "        // Create a place to store responses as an empty array\n",
-       "        iDiv.setAttribute('data-responses', '[]');\n",
-       "\n",
-       "        // Dummy Text\n",
-       "        iDiv.innerHTML=\"<b>Select your answers and then follow the directions that will appear here.</b>\"\n",
-       "        //iDiv.className = 'Quiz';\n",
-       "        mydiv.appendChild(iDiv);\n",
-       "    }\n",
-       "//console.log(\"At end of show_questions\");\n",
-       "    if (typeof MathJax != 'undefined') {\n",
-       "        console.log(\"MathJax version\", MathJax.version);\n",
-       "        var version = MathJax.version;\n",
-       "        setTimeout(function(){\n",
-       "            var version = MathJax.version;\n",
-       "            console.log('After sleep, MathJax version', version);\n",
-       "            if (version[0] == \"2\") {\n",
-       "                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n",
-       "            } else if (version[0] == \"3\") {\n",
-       "                MathJax.typeset([mydiv]);\n",
-       "            }\n",
-       "        }, 500);\n",
-       "if (typeof version == 'undefined') {\n",
-       "        } else\n",
-       "        {\n",
-       "            if (version[0] == \"2\") {\n",
-       "                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n",
-       "            } else if (version[0] == \"3\") {\n",
-       "                MathJax.typeset([mydiv]);\n",
-       "            } else {\n",
-       "                console.log(\"MathJax not found\");\n",
-       "            }\n",
-       "        }\n",
-       "    }\n",
-       "    return false;\n",
-       "}\n",
-       "/* This is to handle asynchrony issues in loading Jupyter notebooks\n",
-       "           where the quiz has been previously run. The Javascript was generally\n",
-       "           being run before the div was added to the DOM. I tried to do this\n",
-       "           more elegantly using Mutation Observer, but I didn't get it to work.\n",
-       "\n",
-       "           Someone more knowledgeable could make this better ;-) */\n",
-       "\n",
-       "        function try_show() {\n",
-       "          if(document.getElementById(\"utfoWIEwcBPi\")) {\n",
-       "            show_questions(questionsutfoWIEwcBPi,  utfoWIEwcBPi); \n",
-       "          } else {\n",
-       "             setTimeout(try_show, 200);\n",
-       "          }\n",
-       "        };\n",
-       "    \n",
-       "        {\n",
-       "        // console.log(element);\n",
-       "\n",
-       "        //console.log(\"utfoWIEwcBPi\");\n",
-       "        // console.log(document.getElementById(\"utfoWIEwcBPi\"));\n",
-       "\n",
-       "        try_show();\n",
-       "        }\n",
-       "        "
-      ],
-      "text/plain": [
-       "<IPython.core.display.Javascript object>"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
-   "source": [
-    "import json\n",
-    "from jupyterquiz import display_quiz\n",
-    "with open(\"jupyterquizexample.json\", \"r\") as file:\n",
-    "    questions=json.load(file)\n",
-    "\n",
-    "display_quiz(questions,  border_radius=0)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": [
-    "## H5p\n",
-    "<iframe src=\"https://tudelft.h5p.com/content/1292010272694757307/embed\" aria-label=\"Exmple MUDE\" width=\"1088\" height=\"637\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" allow=\"autoplay *; geolocation *; microphone *; camera *; midi *; encrypted-media *\"></iframe><script src=\"https://tudelft.h5p.com/js/h5p-resizer.js\" charset=\"UTF-8\"></script>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": [
-    "## Grasple\n",
-    "<iframe height=\"560\" src=\"https://embed.grasple.com/exercises/156d55e4-8198-4bc7-a2c1-c0cb0ff3fef8?id=80710\" title=\"Grasple Exercise 80710\" width=\"100%\" allow=\"clipboard-read; clipboard-write\"></iframe>"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "hideCode": true,
-    "hidePrompt": true
-   },
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "celltoolbar": "Tags",
-  "hide_code_all_hidden": true,
-  "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.9.17"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/book/cookbook/interactive_ex_python.md b/book/cookbook/interactive_ex_python.md
new file mode 100644
index 00000000..9be4abc0
--- /dev/null
+++ b/book/cookbook/interactive_ex_python.md
@@ -0,0 +1,44 @@
+# Interactive Excercises (Python)
+
+In contrast to the exercise types illustrated on the previous page, it is also possible to include exercises where students solve a problem using Python. There are several ways to do this, depending on where and how the students enter and run Python code, as well as how the result is determined to be correct (if at all) and whether feedback is provided.
+
+## Running Python in the browser
+
+### Directly in the book
+
+Thebe Lite
+
+### Embedded in the book
+
+JupyterLite REPL (or notebook).
+
+This is the "Python calculator" that is used in the [Python course](https://tudelft-citg.github.io/learn-python/Python_Toolbox.html).
+
+## Giving feedback on the exercises
+
+Multiple ways:
+- write the answer in the book text (or consider a dropdown or JupyterQuiz)
+- write asserts in the notebook directly (Thebe)
+- include asserts in a local file (Thebe)
+- write asserts in a local file in the JupyterLite repo (REPL); need to check this one
+- use a package on PyPI (also done in Python course); can do with `mude` package
+
+## Running Python elsewhere
+
+As time goes on, students will be encouraged to run Python on their own computers as needed. This means that only PyPI packages can be used to check their answers. Exercises in the Jupyter Book should be simple enough where students use the Thebe or REPL cells as needed to solve the problem. Something more complex is simply homework or programming assignments that are assessed in another way (i.e., in class on Wed/Fri or with autograding).
+
+### Cloud-based Python kernels
+
+Several options. Loading local files becomes complicated; easiest is to use PyPI packages.
+
+#### Deepnote
+
+Looking into it
+
+#### Colab
+
+See Python course.
+
+#### Binder
+
+See Python course. Not used in MUDE; too slow, plus Thebe is better!
\ No newline at end of file
diff --git a/book/cookbook/interactive_exercise.ipynb b/book/cookbook/interactive_exercise.ipynb
new file mode 100644
index 00000000..9ce044a3
--- /dev/null
+++ b/book/cookbook/interactive_exercise.ipynb
@@ -0,0 +1,358 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": [
+    "# Interactive Excercises (no Python)\n",
+    "\n",
+    "To add interactivity to the book, you can make use of interactive elements which are embedded in the book. You've four options:\n",
+    "\n",
+    " - Admonition: question provided in normal markdown language with answers in a drowdown menu. There's no feedback delivered to the student, only the correct answer no matter what the student's thinking/work.\n",
+    " - JupyterQuiz: code-based implementation of multiple-choiche questions and numerical answer questions. This is a basic open course tool which requires you to write your quiz questions in Python language as a `dict` or to a `.json` file. You can include feedback in \"correct\" or \"incorrect\" response after submitting an answer.\n",
+    " - H5p: TU-Delft-licensed tool to create questions (and many other interactive elements) using an [online GUI](https://tudelft.h5p.com/). These elements are embedded into the book using an iframe which requires very little coding. These elements are easier to implement than the JupyterQuiz and have more features (although proper numerical answer questions are missing). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/cookbook/h5p.html)\n",
+    " - Grasple-exercises: TU-Delft-licensed tool to creating math-based quiz questions. Again, these elements are embedded into the book using an iframe which requiries very little coding. These elements are very well suited to math-based problems as it allows analytical evaluation of numerical values and formulas. Furthermore, Grasple allows parameterization (not required for formative exercises). More information on this is provided in the [CiTG Jupyter Book introduction book](https://interactivetextbooks.citg.tudelft.nl/cookbook/grasple.html)\n",
+    "\n",
+    "Examples of each type are shown below based on the following problem:\n",
+    "\n",
+    "A pre-fabricated bridge design is being considered for river crossings in a remote region of the world, as shown in Figure 1. Cities 1, 2 and 3 are labelled C1, C2 and C3, with bridges labelled B1-B4.\n",
+    "\n",
+    "```{figure} ../figures/pd/simple-city.png\n",
+    "---\n",
+    "width: 600px\n",
+    "name: exercise-simple-city\n",
+    "---"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": [
+    "## Admonition\n",
+    "If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2\n",
+    "\n",
+    "\n",
+    "```{admonition} Answer\n",
+    ":class: tip, dropdown\n",
+    "\n",
+    "$$\n",
+    "    P_{f_{C1-C2}} = P_{f_{B1}} * P_{f_{B4-B3-B2}} = P_{f_{B1}} * (1 - (1 - P_{f_{B4}})(1 - P_{f_{B3}}*P_{f_{B2}}))\n",
+    "$$\n",
+    "$$\n",
+    "    P_{f_{C1 - C2}} = 0.1 * (1 - (1 - 0.1)(1 - 0.1 * 0.1)) = 0.0109\n",
+    "$$\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": [
+    "## JupyterQuiz"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true,
+    "tags": [
+     "remove-input"
+    ]
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div id=\"utfoWIEwcBPi\" data-shufflequestions=\"False\"\n",
+       "               data-shuffleanswers=\"True\"\n",
+       "               data-preserveresponses=\"false\"\n",
+       "               data-numquestions=\"1000000\"\n",
+       "               data-maxwidth=\"600\"\n",
+       "               style=\"border-radius: 0px; text-align: left\"> <style>\n",
+       "#utfoWIEwcBPi {\n",
+       "   --jq-multiple-choice-bg: #6f78ffff;\n",
+       "   --jq-mc-button-bg: #fafafa;\n",
+       "   --jq-mc-button-border: #e0e0e0e0;\n",
+       "   --jq-mc-button-inset-shadow: #555555;\n",
+       "   --jq-many-choice-bg: #f75c03ff;\n",
+       "   --jq-numeric-bg: #392061ff;\n",
+       "   --jq-numeric-input-bg: #c0c0c0;\n",
+       "   --jq-numeric-input-label: #101010;\n",
+       "   --jq-numeric-input-shadow: #999999;\n",
+       "   --jq-incorrect-color: #c80202;\n",
+       "   --jq-correct-color: #009113;\n",
+       "   --jq-text-color: #fafafa;\n",
+       "}\n",
+       "\n",
+       ".Quiz {\n",
+       "    max-width: 600px;\n",
+       "    margin-top: 15px;\n",
+       "    margin-left: auto;\n",
+       "    margin-right: auto;\n",
+       "    margin-bottom: 15px;\n",
+       "    padding-bottom: 4px;\n",
+       "    padding-top: 4px;\n",
+       "    line-height: 1.1;\n",
+       "    font-size: 16pt;\n",
+       "    border-radius: inherit;\n",
+       "}\n",
+       "\n",
+       ".QuizCode {\n",
+       "    font-size: 14pt;\n",
+       "    margin-top: 10px;\n",
+       "    margin-left: 20px;\n",
+       "    margin-right: 20px;\n",
+       "}\n",
+       "\n",
+       ".QuizCode>pre {\n",
+       "    padding: 4px;\n",
+       "}\n",
+       "\n",
+       ".Answer {\n",
+       "    margin: 10px 0;\n",
+       "    display: grid;\n",
+       "    grid-template-columns: 1fr 1fr;\n",
+       "    grid-gap: 10px;\n",
+       "    border-radius: inherit;\n",
+       "}\n",
+       "\n",
+       ".Feedback {\n",
+       "    font-size: 16pt;\n",
+       "    text-align: center;\n",
+       "    min-height: 2em;\n",
+       "}\n",
+       "\n",
+       ".Input {\n",
+       "    align: left;\n",
+       "    font-size: 20pt;\n",
+       "}\n",
+       "\n",
+       ".Input-text {\n",
+       "    display: block;\n",
+       "    margin: 10px;\n",
+       "    color: inherit;\n",
+       "    width: 140px;\n",
+       "    background-color: var(--jq-numeric-input-bg);\n",
+       "    color: var(--jq-text-color);\n",
+       "    padding: 5px;\n",
+       "    padding-left: 10px;\n",
+       "    font-family: inherit;\n",
+       "    font-size: 20px;\n",
+       "    font-weight: inherit;\n",
+       "    line-height: 20pt;\n",
+       "    border: none;\n",
+       "    border-radius: 0.2rem;\n",
+       "    transition: box-shadow 0.1s);\n",
+       "}\n",
+       "\n",
+       ".Input-text:focus {\n",
+       "    outline: none;\n",
+       "    background-color: var(--jq-numeric-input-bg);\n",
+       "    box-shadow: 0.6rem 0.8rem 1.4rem -0.5rem var(--jq-numeric-input-shadow);\n",
+       "}\n",
+       "\n",
+       ".MCButton {\n",
+       "    background: var(--jq-mc-button-bg);\n",
+       "    border: 1px solid var(--jq-mc-button-border);\n",
+       "    border-radius: inherit;\n",
+       "    padding: 10px;\n",
+       "    font-size: 16px;\n",
+       "    cursor: pointer;\n",
+       "    text-align: center;\n",
+       "    display: flex;\n",
+       "    align-items: center;\n",
+       "    justify-content: center;\n",
+       "}\n",
+       "\n",
+       ".MCButton p {\n",
+       "    color: inherit;\n",
+       "}\n",
+       "\n",
+       ".MultipleChoiceQn {\n",
+       "    padding: 10px;\n",
+       "    background: var(--jq-multiple-choice-bg);\n",
+       "    color: var(--jq-text-color);\n",
+       "    border-radius: inherit;\n",
+       "}\n",
+       "\n",
+       ".ManyChoiceQn {\n",
+       "    padding: 10px;\n",
+       "    background: var(--jq-many-choice-bg);\n",
+       "    color: var(--jq-text-color);\n",
+       "    border-radius: inherit;\n",
+       "}\n",
+       "\n",
+       ".NumericQn {\n",
+       "    padding: 10px;\n",
+       "    background: var(--jq-numeric-bg);\n",
+       "    color: var(--jq-text-color);\n",
+       "    border-radius: inherit;\n",
+       "}\n",
+       "\n",
+       ".NumericQn p {\n",
+       "    color: inherit;\n",
+       "}\n",
+       "\n",
+       ".InpLabel {\n",
+       "    line-height: 34px;\n",
+       "    float: left;\n",
+       "    margin-right: 10px;\n",
+       "    color: var(--jq-numeric-input-label);\n",
+       "    font-size: 15pt;\n",
+       "}\n",
+       "\n",
+       ".incorrect {\n",
+       "    color: var(--jq-incorrect-color);\n",
+       "}\n",
+       "\n",
+       ".correct {\n",
+       "    color: var(--jq-correct-color);\n",
+       "}\n",
+       "\n",
+       ".correctButton {\n",
+       "    /*\n",
+       "    background: var(--jq-correct-color);\n",
+       "   */\n",
+       "    animation: correct-anim 0.6s ease;\n",
+       "    animation-fill-mode: forwards;\n",
+       "    color: var(--jq-text-color);\n",
+       "    box-shadow: inset 0px 0px 5px var(--jq-mc-button-inset-shadow);\n",
+       "    outline: none;\n",
+       "}\n",
+       "\n",
+       ".incorrectButton {\n",
+       "    animation: incorrect-anim 0.8s ease;\n",
+       "    animation-fill-mode: forwards;\n",
+       "    color: var(--jq-text-color);\n",
+       "    box-shadow: inset 0px 0px 5px var(--jq-mc-button-inset-shadow);\n",
+       "    outline: none;\n",
+       "}\n",
+       "\n",
+       "@keyframes incorrect-anim {\n",
+       "    100% {\n",
+       "        background-color: var(--jq-incorrect-color);\n",
+       "    }\n",
+       "}\n",
+       "\n",
+       "@keyframes correct-anim {\n",
+       "    100% {\n",
+       "        background-color: var(--jq-correct-color);\n",
+       "    }\n",
+       "}\n",
+       "</style>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.HTML object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/javascript": "var questionsutfoWIEwcBPi=[{\"question\": \"If the probability of failure for an individual bridge is 0.1 per year, compute the probability that you cannot drive from City 1 to City 2:\", \"type\": \"numeric\", \"answers\": [{\"type\": \"range\", \"value\": 0.0109, \"correct\": true, \"feedback\": \"Correct.\"}, {\"type\": \"default\", \"feedback\": \"Try again\"}]}];\n    // Make a random ID\nfunction makeid(length) {\n    var result = [];\n    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n    var charactersLength = characters.length;\n    for (var i = 0; i < length; i++) {\n        result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));\n    }\n    return result.join('');\n}\n\n// Choose a random subset of an array. Can also be used to shuffle the array\nfunction getRandomSubarray(arr, size) {\n    var shuffled = arr.slice(0), i = arr.length, temp, index;\n    while (i--) {\n        index = Math.floor((i + 1) * Math.random());\n        temp = shuffled[index];\n        shuffled[index] = shuffled[i];\n        shuffled[i] = temp;\n    }\n    return shuffled.slice(0, size);\n}\n\nfunction printResponses(responsesContainer) {\n    var responses=JSON.parse(responsesContainer.dataset.responses);\n    var stringResponses='<B>IMPORTANT!</B>To preserve this answer sequence for submission, when you have finalized your answers: <ol> <li> Copy the text in this cell below \"Answer String\"</li> <li> Double click on the cell directly below the Answer String, labeled \"Replace Me\"</li> <li> Select the whole \"Replace Me\" text</li> <li> Paste in your answer string and press shift-Enter.</li><li>Save the notebook using the save icon or File->Save Notebook menu item</li></ul><br><br><br><b>Answer String:</b><br> ';\n    console.log(responses);\n    responses.forEach((response, index) => {\n        if (response) {\n            console.log(index + ': ' + response);\n            stringResponses+= index + ': ' + response +\"<BR>\";\n        }\n    });\n    responsesContainer.innerHTML=stringResponses;\n}\nfunction check_mc() {\n    var id = this.id.split('-')[0];\n    //var response = this.id.split('-')[1];\n    //console.log(response);\n    //console.log(\"In check_mc(), id=\"+id);\n    //console.log(event.srcElement.id)           \n    //console.log(event.srcElement.dataset.correct)   \n    //console.log(event.srcElement.dataset.feedback)\n\n    var label = event.srcElement;\n    //console.log(label, label.nodeName);\n    var depth = 0;\n    while ((label.nodeName != \"LABEL\") && (depth < 20)) {\n        label = label.parentElement;\n        console.log(depth, label);\n        depth++;\n    }\n\n\n\n    var answers = label.parentElement.children;\n\n    //console.log(answers);\n\n\n    // Split behavior based on multiple choice vs many choice:\n    var fb = document.getElementById(\"fb\" + id);\n\n\n\n\n    if (fb.dataset.numcorrect == 1) {\n        // What follows is for the saved responses stuff\n        var outerContainer = fb.parentElement.parentElement;\n        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n        if (responsesContainer) {\n            //console.log(responsesContainer);\n            var response = label.firstChild.innerText;\n            if (label.querySelector(\".QuizCode\")){\n                response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n            }\n            console.log(response);\n            //console.log(document.getElementById(\"quizWrap\"+id));\n            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n            console.log(\"Question \" + qnum);\n            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n            var responses=JSON.parse(responsesContainer.dataset.responses);\n            console.log(responses);\n            responses[qnum]= response;\n            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n            printResponses(responsesContainer);\n        }\n        // End code to preserve responses\n        \n        for (var i = 0; i < answers.length; i++) {\n            var child = answers[i];\n            //console.log(child);\n            child.className = \"MCButton\";\n        }\n\n\n\n        if (label.dataset.correct == \"true\") {\n            // console.log(\"Correct action\");\n            if (\"feedback\" in label.dataset) {\n                fb.textContent = jaxify(label.dataset.feedback);\n            } else {\n                fb.textContent = \"Correct!\";\n            }\n            label.classList.add(\"correctButton\");\n\n            fb.className = \"Feedback\";\n            fb.classList.add(\"correct\");\n\n        } else {\n            if (\"feedback\" in label.dataset) {\n                fb.textContent = jaxify(label.dataset.feedback);\n            } else {\n                fb.textContent = \"Incorrect -- try again.\";\n            }\n            //console.log(\"Error action\");\n            label.classList.add(\"incorrectButton\");\n            fb.className = \"Feedback\";\n            fb.classList.add(\"incorrect\");\n        }\n    }\n    else {\n        var reset = false;\n        var feedback;\n         if (label.dataset.correct == \"true\") {\n            if (\"feedback\" in label.dataset) {\n                feedback = jaxify(label.dataset.feedback);\n            } else {\n                feedback = \"Correct!\";\n            }\n            if (label.dataset.answered <= 0) {\n                if (fb.dataset.answeredcorrect < 0) {\n                    fb.dataset.answeredcorrect = 1;\n                    reset = true;\n                } else {\n                    fb.dataset.answeredcorrect++;\n                }\n                if (reset) {\n                    for (var i = 0; i < answers.length; i++) {\n                        var child = answers[i];\n                        child.className = \"MCButton\";\n                        child.dataset.answered = 0;\n                    }\n                }\n                label.classList.add(\"correctButton\");\n                label.dataset.answered = 1;\n                fb.className = \"Feedback\";\n                fb.classList.add(\"correct\");\n\n            }\n        } else {\n            if (\"feedback\" in label.dataset) {\n                feedback = jaxify(label.dataset.feedback);\n            } else {\n                feedback = \"Incorrect -- try again.\";\n            }\n            if (fb.dataset.answeredcorrect > 0) {\n                fb.dataset.answeredcorrect = -1;\n                reset = true;\n            } else {\n                fb.dataset.answeredcorrect--;\n            }\n\n            if (reset) {\n                for (var i = 0; i < answers.length; i++) {\n                    var child = answers[i];\n                    child.className = \"MCButton\";\n                    child.dataset.answered = 0;\n                }\n            }\n            label.classList.add(\"incorrectButton\");\n            fb.className = \"Feedback\";\n            fb.classList.add(\"incorrect\");\n        }\n        // What follows is for the saved responses stuff\n        var outerContainer = fb.parentElement.parentElement;\n        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n        if (responsesContainer) {\n            //console.log(responsesContainer);\n            var response = label.firstChild.innerText;\n            if (label.querySelector(\".QuizCode\")){\n                response+= label.querySelector(\".QuizCode\").firstChild.innerText;\n            }\n            console.log(response);\n            //console.log(document.getElementById(\"quizWrap\"+id));\n            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n            console.log(\"Question \" + qnum);\n            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n            var responses=JSON.parse(responsesContainer.dataset.responses);\n            if (label.dataset.correct == \"true\") {\n                if (typeof(responses[qnum]) == \"object\"){\n                    if (!responses[qnum].includes(response))\n                        responses[qnum].push(response);\n                } else{\n                    responses[qnum]= [ response ];\n                }\n            } else {\n                responses[qnum]= response;\n            }\n            console.log(responses);\n            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n            printResponses(responsesContainer);\n        }\n        // End save responses stuff\n\n\n\n        var numcorrect = fb.dataset.numcorrect;\n        var answeredcorrect = fb.dataset.answeredcorrect;\n        if (answeredcorrect >= 0) {\n            fb.textContent = feedback + \" [\" + answeredcorrect + \"/\" + numcorrect + \"]\";\n        } else {\n            fb.textContent = feedback + \" [\" + 0 + \"/\" + numcorrect + \"]\";\n        }\n\n\n    }\n\n    if (typeof MathJax != 'undefined') {\n        var version = MathJax.version;\n        console.log('MathJax version', version);\n        if (version[0] == \"2\") {\n            MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n        } else if (version[0] == \"3\") {\n            MathJax.typeset([fb]);\n        }\n    } else {\n        console.log('MathJax not detected');\n    }\n\n}\n\nfunction make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id) {\n    var shuffled;\n    if (shuffle_answers == \"True\") {\n        //console.log(shuffle_answers+\" read as true\");\n        shuffled = getRandomSubarray(qa.answers, qa.answers.length);\n    } else {\n        //console.log(shuffle_answers+\" read as false\");\n        shuffled = qa.answers;\n    }\n\n\n    var num_correct = 0;\n\n\n\n    shuffled.forEach((item, index, ans_array) => {\n        //console.log(answer);\n\n        // Make input element\n        var inp = document.createElement(\"input\");\n        inp.type = \"radio\";\n        inp.id = \"quizo\" + id + index;\n        inp.style = \"display:none;\";\n        aDiv.append(inp);\n\n        //Make label for input element\n        var lab = document.createElement(\"label\");\n        lab.className = \"MCButton\";\n        lab.id = id + '-' + index;\n        lab.onclick = check_mc;\n        var aSpan = document.createElement('span');\n        aSpan.classsName = \"\";\n        //qDiv.id=\"quizQn\"+id+index;\n        if (\"answer\" in item) {\n            aSpan.innerHTML = jaxify(item.answer);\n            //aSpan.innerHTML=item.answer;\n        }\n        lab.append(aSpan);\n\n        // Create div for code inside question\n        var codeSpan;\n        if (\"code\" in item) {\n            codeSpan = document.createElement('span');\n            codeSpan.id = \"code\" + id + index;\n            codeSpan.className = \"QuizCode\";\n            var codePre = document.createElement('pre');\n            codeSpan.append(codePre);\n            var codeCode = document.createElement('code');\n            codePre.append(codeCode);\n            codeCode.innerHTML = item.code;\n            lab.append(codeSpan);\n            //console.log(codeSpan);\n        }\n\n        //lab.textContent=item.answer;\n\n        // Set the data attributes for the answer\n        lab.setAttribute('data-correct', item.correct);\n        if (item.correct) {\n            num_correct++;\n        }\n        if (\"feedback\" in item) {\n            lab.setAttribute('data-feedback', item.feedback);\n        }\n        lab.setAttribute('data-answered', 0);\n\n        aDiv.append(lab);\n\n    });\n\n    if (num_correct > 1) {\n        outerqDiv.className = \"ManyChoiceQn\";\n    } else {\n        outerqDiv.className = \"MultipleChoiceQn\";\n    }\n\n    return num_correct;\n\n}\nfunction check_numeric(ths, event) {\n\n    if (event.keyCode === 13) {\n        ths.blur();\n\n        var id = ths.id.split('-')[0];\n\n        var submission = ths.value;\n        if (submission.indexOf('/') != -1) {\n            var sub_parts = submission.split('/');\n            //console.log(sub_parts);\n            submission = sub_parts[0] / sub_parts[1];\n        }\n        //console.log(\"Reader entered\", submission);\n\n        if (\"precision\" in ths.dataset) {\n            var precision = ths.dataset.precision;\n            // console.log(\"1:\", submission)\n            submission = Math.round((1 * submission + Number.EPSILON) * 10 ** precision) / 10 ** precision;\n            // console.log(\"Rounded to \", submission, \" precision=\", precision  );\n        }\n\n\n        //console.log(\"In check_numeric(), id=\"+id);\n        //console.log(event.srcElement.id)           \n        //console.log(event.srcElement.dataset.feedback)\n\n        var fb = document.getElementById(\"fb\" + id);\n        fb.style.display = \"none\";\n        fb.textContent = \"Incorrect -- try again.\";\n\n        var answers = JSON.parse(ths.dataset.answers);\n        //console.log(answers);\n\n        var defaultFB = \"\";\n        var correct;\n        var done = false;\n        answers.every(answer => {\n            //console.log(answer.type);\n\n            correct = false;\n            // if (answer.type==\"value\"){\n            if ('value' in answer) {\n                if (submission == answer.value) {\n                    if (\"feedback\" in answer) {\n                        fb.textContent = jaxify(answer.feedback);\n                    } else {\n                        fb.textContent = jaxify(\"Correct\");\n                    }\n                    correct = answer.correct;\n                    //console.log(answer.correct);\n                    done = true;\n                }\n                // } else if (answer.type==\"range\") {\n            } else if ('range' in answer) {\n                //console.log(answer.range);\n                if ((submission >= answer.range[0]) && (submission < answer.range[1])) {\n                    fb.textContent = jaxify(answer.feedback);\n                    correct = answer.correct;\n                    //console.log(answer.correct);\n                    done = true;\n                }\n            } else if (answer.type == \"default\") {\n                defaultFB = answer.feedback;\n            }\n            if (done) {\n                return false; // Break out of loop if this has been marked correct\n            } else {\n                return true; // Keep looking for case that includes this as a correct answer\n            }\n        });\n\n        if ((!done) && (defaultFB != \"\")) {\n            fb.innerHTML = jaxify(defaultFB);\n            //console.log(\"Default feedback\", defaultFB);\n        }\n\n        fb.style.display = \"block\";\n        if (correct) {\n            ths.className = \"Input-text\";\n            ths.classList.add(\"correctButton\");\n            fb.className = \"Feedback\";\n            fb.classList.add(\"correct\");\n        } else {\n            ths.className = \"Input-text\";\n            ths.classList.add(\"incorrectButton\");\n            fb.className = \"Feedback\";\n            fb.classList.add(\"incorrect\");\n        }\n\n        // What follows is for the saved responses stuff\n        var outerContainer = fb.parentElement.parentElement;\n        var responsesContainer = document.getElementById(\"responses\" + outerContainer.id);\n        if (responsesContainer) {\n            console.log(submission);\n            var qnum = document.getElementById(\"quizWrap\"+id).dataset.qnum;\n            //console.log(\"Question \" + qnum);\n            //console.log(id, \", got numcorrect=\",fb.dataset.numcorrect);\n            var responses=JSON.parse(responsesContainer.dataset.responses);\n            console.log(responses);\n            if (submission == ths.value){\n                responses[qnum]= submission;\n            } else {\n                responses[qnum]= ths.value + \"(\" + submission +\")\";\n            }\n            responsesContainer.setAttribute('data-responses', JSON.stringify(responses));\n            printResponses(responsesContainer);\n        }\n        // End code to preserve responses\n\n        if (typeof MathJax != 'undefined') {\n            var version = MathJax.version;\n            console.log('MathJax version', version);\n            if (version[0] == \"2\") {\n                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n            } else if (version[0] == \"3\") {\n                MathJax.typeset([fb]);\n            }\n        } else {\n            console.log('MathJax not detected');\n        }\n        return false;\n    }\n\n}\n\nfunction isValid(el, charC) {\n    //console.log(\"Input char: \", charC);\n    if (charC == 46) {\n        if (el.value.indexOf('.') === -1) {\n            return true;\n        } else if (el.value.indexOf('/') != -1) {\n            var parts = el.value.split('/');\n            if (parts[1].indexOf('.') === -1) {\n                return true;\n            }\n        }\n        else {\n            return false;\n        }\n    } else if (charC == 47) {\n        if (el.value.indexOf('/') === -1) {\n            if ((el.value != \"\") && (el.value != \".\")) {\n                return true;\n            } else {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    } else if (charC == 45) {\n        var edex = el.value.indexOf('e');\n        if (edex == -1) {\n            edex = el.value.indexOf('E');\n        }\n\n        if (el.value == \"\") {\n            return true;\n        } else if (edex == (el.value.length - 1)) { // If just after e or E\n            return true;\n        } else {\n            return false;\n        }\n    } else if (charC == 101) { // \"e\"\n        if ((el.value.indexOf('e') === -1) && (el.value.indexOf('E') === -1) && (el.value.indexOf('/') == -1)) {\n            // Prev symbol must be digit or decimal point:\n            if (el.value.slice(-1).search(/\\d/) >= 0) {\n                return true;\n            } else if (el.value.slice(-1).search(/\\./) >= 0) {\n                return true;\n            } else {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    } else {\n        if (charC > 31 && (charC < 48 || charC > 57))\n            return false;\n    }\n    return true;\n}\n\nfunction numeric_keypress(evnt) {\n    var charC = (evnt.which) ? evnt.which : evnt.keyCode;\n\n    if (charC == 13) {\n        check_numeric(this, evnt);\n    } else {\n        return isValid(this, charC);\n    }\n}\n\n\n\n\n\nfunction make_numeric(qa, outerqDiv, qDiv, aDiv, id) {\n\n\n\n    //console.log(answer);\n\n\n    outerqDiv.className = \"NumericQn\";\n    aDiv.style.display = 'block';\n\n    var lab = document.createElement(\"label\");\n    lab.className = \"InpLabel\";\n    lab.textContent = \"Type numeric answer here:\";\n    aDiv.append(lab);\n\n    var inp = document.createElement(\"input\");\n    inp.type = \"text\";\n    //inp.id=\"input-\"+id;\n    inp.id = id + \"-0\";\n    inp.className = \"Input-text\";\n    inp.setAttribute('data-answers', JSON.stringify(qa.answers));\n    if (\"precision\" in qa) {\n        inp.setAttribute('data-precision', qa.precision);\n    }\n    aDiv.append(inp);\n    //console.log(inp);\n\n    //inp.addEventListener(\"keypress\", check_numeric);\n    //inp.addEventListener(\"keypress\", numeric_keypress);\n    /*\n    inp.addEventListener(\"keypress\", function(event) {\n        return numeric_keypress(this, event);\n    }\n                        );\n                        */\n    //inp.onkeypress=\"return numeric_keypress(this, event)\";\n    inp.onkeypress = numeric_keypress;\n    inp.onpaste = event => false;\n\n    inp.addEventListener(\"focus\", function (event) {\n        this.value = \"\";\n        return false;\n    }\n    );\n\n\n}\nfunction jaxify(string) {\n    var mystring = string;\n\n    var count = 0;\n    var loc = mystring.search(/([^\\\\]|^)(\\$)/);\n\n    var count2 = 0;\n    var loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n\n    //console.log(loc);\n\n    while ((loc >= 0) || (loc2 >= 0)) {\n\n        /* Have to replace all the double $$ first with current implementation */\n        if (loc2 >= 0) {\n            if (count2 % 2 == 0) {\n                mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\[\");\n            } else {\n                mystring = mystring.replace(/([^\\\\]|^)(\\$\\$)/, \"$1\\\\]\");\n            }\n            count2++;\n        } else {\n            if (count % 2 == 0) {\n                mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\(\");\n            } else {\n                mystring = mystring.replace(/([^\\\\]|^)(\\$)/, \"$1\\\\)\");\n            }\n            count++;\n        }\n        loc = mystring.search(/([^\\\\]|^)(\\$)/);\n        loc2 = mystring.search(/([^\\\\]|^)(\\$\\$)/);\n        //console.log(mystring,\", loc:\",loc,\", loc2:\",loc2);\n    }\n\n    //console.log(mystring);\n    return mystring;\n}\n\n\nfunction show_questions(json, mydiv) {\n    console.log('show_questions');\n    //var mydiv=document.getElementById(myid);\n    var shuffle_questions = mydiv.dataset.shufflequestions;\n    var num_questions = mydiv.dataset.numquestions;\n    var shuffle_answers = mydiv.dataset.shuffleanswers;\n    var max_width = mydiv.dataset.maxwidth;\n\n    if (num_questions > json.length) {\n        num_questions = json.length;\n    }\n\n    var questions;\n    if ((num_questions < json.length) || (shuffle_questions == \"True\")) {\n        //console.log(num_questions+\",\"+json.length);\n        questions = getRandomSubarray(json, num_questions);\n    } else {\n        questions = json;\n    }\n\n    //console.log(\"SQ: \"+shuffle_questions+\", NQ: \" + num_questions + \", SA: \", shuffle_answers);\n\n    // Iterate over questions\n    questions.forEach((qa, index, array) => {\n        //console.log(qa.question); \n\n        var id = makeid(8);\n        //console.log(id);\n\n\n        // Create Div to contain question and answers\n        var iDiv = document.createElement('div');\n        //iDiv.id = 'quizWrap' + id + index;\n        iDiv.id = 'quizWrap' + id;\n        iDiv.className = 'Quiz';\n        iDiv.setAttribute('data-qnum', index);\n        iDiv.style.maxWidth  =max_width+\"px\";\n        mydiv.appendChild(iDiv);\n        // iDiv.innerHTML=qa.question;\n        \n        var outerqDiv = document.createElement('div');\n        outerqDiv.id = \"OuterquizQn\" + id + index;\n        // Create div to contain question part\n        var qDiv = document.createElement('div');\n        qDiv.id = \"quizQn\" + id + index;\n        \n        if (qa.question) {\n            iDiv.append(outerqDiv);\n\n            //qDiv.textContent=qa.question;\n            qDiv.innerHTML = jaxify(qa.question);\n            outerqDiv.append(qDiv);\n        }\n\n        // Create div for code inside question\n        var codeDiv;\n        if (\"code\" in qa) {\n            codeDiv = document.createElement('div');\n            codeDiv.id = \"code\" + id + index;\n            codeDiv.className = \"QuizCode\";\n            var codePre = document.createElement('pre');\n            codeDiv.append(codePre);\n            var codeCode = document.createElement('code');\n            codePre.append(codeCode);\n            codeCode.innerHTML = qa.code;\n            outerqDiv.append(codeDiv);\n            //console.log(codeDiv);\n        }\n\n\n        // Create div to contain answer part\n        var aDiv = document.createElement('div');\n        aDiv.id = \"quizAns\" + id + index;\n        aDiv.className = 'Answer';\n        iDiv.append(aDiv);\n\n        //console.log(qa.type);\n\n        var num_correct;\n        if ((qa.type == \"multiple_choice\") || (qa.type == \"many_choice\") ) {\n            num_correct = make_mc(qa, shuffle_answers, outerqDiv, qDiv, aDiv, id);\n            if (\"answer_cols\" in qa) {\n                //aDiv.style.gridTemplateColumns = 'auto '.repeat(qa.answer_cols);\n                aDiv.style.gridTemplateColumns = 'repeat(' + qa.answer_cols + ', 1fr)';\n            }\n        } else if (qa.type == \"numeric\") {\n            //console.log(\"numeric\");\n            make_numeric(qa, outerqDiv, qDiv, aDiv, id);\n        }\n\n\n        //Make div for feedback\n        var fb = document.createElement(\"div\");\n        fb.id = \"fb\" + id;\n        //fb.style=\"font-size: 20px;text-align:center;\";\n        fb.className = \"Feedback\";\n        fb.setAttribute(\"data-answeredcorrect\", 0);\n        fb.setAttribute(\"data-numcorrect\", num_correct);\n        iDiv.append(fb);\n\n\n    });\n    var preserveResponses = mydiv.dataset.preserveresponses;\n    console.log(preserveResponses);\n    console.log(preserveResponses == \"true\");\n    if (preserveResponses == \"true\") {\n        console.log(preserveResponses);\n        // Create Div to contain record of answers\n        var iDiv = document.createElement('div');\n        iDiv.id = 'responses' + mydiv.id;\n        iDiv.className = 'JCResponses';\n        // Create a place to store responses as an empty array\n        iDiv.setAttribute('data-responses', '[]');\n\n        // Dummy Text\n        iDiv.innerHTML=\"<b>Select your answers and then follow the directions that will appear here.</b>\"\n        //iDiv.className = 'Quiz';\n        mydiv.appendChild(iDiv);\n    }\n//console.log(\"At end of show_questions\");\n    if (typeof MathJax != 'undefined') {\n        console.log(\"MathJax version\", MathJax.version);\n        var version = MathJax.version;\n        setTimeout(function(){\n            var version = MathJax.version;\n            console.log('After sleep, MathJax version', version);\n            if (version[0] == \"2\") {\n                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n            } else if (version[0] == \"3\") {\n                MathJax.typeset([mydiv]);\n            }\n        }, 500);\nif (typeof version == 'undefined') {\n        } else\n        {\n            if (version[0] == \"2\") {\n                MathJax.Hub.Queue([\"Typeset\", MathJax.Hub]);\n            } else if (version[0] == \"3\") {\n                MathJax.typeset([mydiv]);\n            } else {\n                console.log(\"MathJax not found\");\n            }\n        }\n    }\n    return false;\n}\n/* This is to handle asynchrony issues in loading Jupyter notebooks\n           where the quiz has been previously run. The Javascript was generally\n           being run before the div was added to the DOM. I tried to do this\n           more elegantly using Mutation Observer, but I didn't get it to work.\n\n           Someone more knowledgeable could make this better ;-) */\n\n        function try_show() {\n          if(document.getElementById(\"utfoWIEwcBPi\")) {\n            show_questions(questionsutfoWIEwcBPi,  utfoWIEwcBPi); \n          } else {\n             setTimeout(try_show, 200);\n          }\n        };\n    \n        {\n        // console.log(element);\n\n        //console.log(\"utfoWIEwcBPi\");\n        // console.log(document.getElementById(\"utfoWIEwcBPi\"));\n\n        try_show();\n        }\n        ",
+      "text/plain": [
+       "<IPython.core.display.Javascript object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import json\n",
+    "from jupyterquiz import display_quiz\n",
+    "with open(\"jupyterquizexample.json\", \"r\") as file:\n",
+    "    questions=json.load(file)\n",
+    "\n",
+    "display_quiz(questions,  border_radius=0)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The question is implemented in a `JSON` file `jupyterquizexample.json`, the contents of which are printed here:\n",
+    "\n",
+    "```{eval-rst}\n",
+    ".. literalinclude:: ./jupyterquizexample.json\n",
+    "   :language: json\n",
+    "```"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": [
+    "## H5p\n",
+    "<iframe src=\"https://tudelft.h5p.com/content/1292010272694757307/embed\" aria-label=\"Exmple MUDE\" width=\"1088\" height=\"637\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" allow=\"autoplay *; geolocation *; microphone *; camera *; midi *; encrypted-media *\"></iframe><script src=\"https://tudelft.h5p.com/js/h5p-resizer.js\" charset=\"UTF-8\"></script>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": [
+    "## Grasple\n",
+    "<iframe height=\"560\" src=\"https://embed.grasple.com/exercises/156d55e4-8198-4bc7-a2c1-c0cb0ff3fef8?id=80710\" title=\"Grasple Exercise 80710\" width=\"100%\" allow=\"clipboard-read; clipboard-write\"></iframe>"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "hideCode": true,
+    "hidePrompt": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "celltoolbar": "Tags",
+  "hide_code_all_hidden": true,
+  "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.9.17"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/book/cookbook/overview.md b/book/cookbook/overview.md
new file mode 100644
index 00000000..316ff5d6
--- /dev/null
+++ b/book/cookbook/overview.md
@@ -0,0 +1,20 @@
+# Cookbook
+
+*This Part of the book is temporary: it will be removed prior to the start of Q1 (either hidden, or transferred to another book).*
+
+The Cookbook has two primary purposes: 1) defining the official style and formatting guidelines for our book, as well as b) illustrating commonly used elements of the Jupyter Book. Everything in this Part is meant to serve as a reference and facilitate easy copy/paste, so we'll try to include code blocks illustrating the rendered example when viewing the actualy book. 
+
+Don't forget to check our [other book](https://interactivetextbooks.citg.tudelft.nl/manual/intro.html), the [repo for the other book](https://gitlab.tudelft.nl/interactivetextbooks-citg/jupyter-book-manual) as well as the [Jupyter Book website](https://jupyterbook.org/en/stable/).
+
+
+## Naming Convention
+
+Details.
+
+## File Strucure
+
+Details.
+
+## Common Pitfalls
+
+Details.
\ No newline at end of file
diff --git a/book/cookbook/programming.md b/book/cookbook/programming.md
new file mode 100644
index 00000000..73d8ec13
--- /dev/null
+++ b/book/cookbook/programming.md
@@ -0,0 +1,12 @@
+# Programming
+
+Several elements will be added:
+- let's break it down
+- rules on commenting, formatting, etc...Golden Rules
+- pseudo-code
+- including code in the book
+  - code blocks for generating figures
+  - code dropdowns
+- links to relevant coding chapters/sections
+- practice material (exercises)
+- practice place (Thebe, REPL, etc)
\ No newline at end of file
diff --git a/book/cookbook/referencing.md b/book/cookbook/referencing.md
new file mode 100644
index 00000000..51174019
--- /dev/null
+++ b/book/cookbook/referencing.md
@@ -0,0 +1,4 @@
+# Referencing
+
+Referencing things in the book
+- all
\ No newline at end of file
-- 
GitLab


From 0ea21cd8aed9a440ff2cde959f7a45afecf90a38 Mon Sep 17 00:00:00 2001
From: Tom van Woudenberg <t.r.vanwoudenberg@tudelft.nl>
Date: Wed, 19 Jul 2023 09:19:42 +0200
Subject: [PATCH 2/2] Fixed broken url

---
 book/cookbook/overview.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/book/cookbook/overview.md b/book/cookbook/overview.md
index 316ff5d6..544a6447 100644
--- a/book/cookbook/overview.md
+++ b/book/cookbook/overview.md
@@ -4,7 +4,7 @@
 
 The Cookbook has two primary purposes: 1) defining the official style and formatting guidelines for our book, as well as b) illustrating commonly used elements of the Jupyter Book. Everything in this Part is meant to serve as a reference and facilitate easy copy/paste, so we'll try to include code blocks illustrating the rendered example when viewing the actualy book. 
 
-Don't forget to check our [other book](https://interactivetextbooks.citg.tudelft.nl/manual/intro.html), the [repo for the other book](https://gitlab.tudelft.nl/interactivetextbooks-citg/jupyter-book-manual) as well as the [Jupyter Book website](https://jupyterbook.org/en/stable/).
+Don't forget to check our [other book](https://interactivetextbooks.citg.tudelft.nl/intro.html), the [repo for the other book](https://gitlab.tudelft.nl/interactivetextbooks-citg/jupyter-book-manual) as well as the [Jupyter Book website](https://jupyterbook.org/en/stable/).
 
 
 ## Naming Convention
@@ -17,4 +17,4 @@ Details.
 
 ## Common Pitfalls
 
-Details.
\ No newline at end of file
+Details.
-- 
GitLab