diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a39409e759c75fac8d42c61e4a6f941fcc9e1dea..302a872077f5eaaa8d2616cd4644ed30e953f65d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,4 +29,4 @@ build and upload the contents:
     - mkdocs build
     - "rsync -rv site/* solidstate@tnw-tn1.tudelft.net:"
   only:
-    - branches@solidstate/lectures
+    - master@solidstate/lectures
diff --git a/code/bands_2d.py b/code/bands_2d.py
deleted file mode 100644
index a8bf92872e415aac8ca1e00ba178ce8779baaf3c..0000000000000000000000000000000000000000
--- a/code/bands_2d.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import numpy as np
-import plotly.offline as py
-import plotly.graph_objs as go
-
-
-pi = np.pi
-
-def E(k_x, k_y):
-    delta = np.array([-2*pi, 0, 2*pi])
-    H = np.diag(
-        ((k_x + delta)[:, np.newaxis]**2
-        + (k_y + delta)[np.newaxis]**2).flatten()
-    )
-    return tuple(np.linalg.eigvalsh(H + 5)[:3])
-
-E = np.vectorize(E, otypes=(float, float, float))
-
-
-figure_html = """
-<head>
-<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
-</head>
-<body>{figure}
-</body>
-""".format
-
-
-def plot_nfem():
-    momenta = np.linspace(-2*pi, 2*pi, 100)
-    kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :]
-    bands = E(kx, ky)
-
-    # Extended Brillouin zone scheme
-    pad = .3
-    first_BZ = ((abs(kx) < pi + pad) & (abs(ky) < pi + pad))
-    second_BZ = (
-        ((abs(kx) > pi - pad) | (abs(ky) > pi - pad))
-        & ((abs(kx + ky) < 2*pi + pad) & (abs(kx - ky) < 2*pi + pad))
-    )
-    third_BZ = (
-        (abs(kx + ky) > 2*pi - pad) | (abs(kx - ky) > 2*pi - pad)
-    )
-
-    bands[0][~first_BZ] = np.nan
-    bands[1][~second_BZ] = np.nan
-    #bands[2][~third_BZ] = np.nan
-
-    # Actually plotting
-
-    fig = go.Figure(
-        data = [
-            go.Surface(
-                z=band / 5,
-                colorscale=color,
-                opacity=opacity,
-                showscale=False,
-                hoverinfo=False,
-                x=momenta,
-                y=momenta,
-            )
-            for band, color, opacity
-            in zip(bands[:2],
-                   ['#cf483d', '#3d88cf'],
-                   (1, 0.9))
-        ],
-        layout = go.Layout(
-            title='Nearly free electrons in 2D',
-            autosize=True,
-            width=500,
-            height=500,
-            hovermode=False,
-            scene=dict(
-                yaxis={"title": "k_y"},
-                xaxis={"title": "k_x"},
-                zaxis={"title": "E"},
-            )
-        )
-    )
-    with open('src/figures/nfem_2d.html', 'w') as f:
-        f.write(figure_html(figure=
-            py.plot(fig, show_link=False,
-                    output_type='div', include_plotlyjs=False)
-        ))
-
-
-def plot_tb():
-    momenta = np.linspace(-pi, pi, 100)
-    kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :]
-    energies = -np.cos(kx) - np.cos(ky)
-    fig = go.Figure(
-        data = [
-            go.Surface(
-                z=energies,
-                colorscale='#3d88cf',
-                opacity=1,
-                showscale=False,
-                hoverinfo=False,
-                x=momenta,
-                y=momenta,
-            )
-        ],
-        layout = go.Layout(
-            title='Tight-binding in 2D',
-            autosize=True,
-            width=500,
-            height=500,
-            hovermode=False,
-            scene=dict(
-                yaxis={"title": "k_y"},
-                xaxis={"title": "k_x"},
-                zaxis={"title": "E"},
-            )
-        )
-    )
-    with open('src/figures/tb_2d.html', 'w') as f:
-        f.write(figure_html(figure=
-            py.plot(fig, show_link=False,
-                    output_type='div', include_plotlyjs=False)
-        ))
-
-
-if __name__ == '__main__':
-    plot_nfem()
-    plot_tb()
diff --git a/execute.py b/execute.py
index 71c7b23ea2d2bffa62a0ec3efd43f7a936c2ccdc..826a279e74606c99db6f1760d136aa43d1ade0a0 100644
--- a/execute.py
+++ b/execute.py
@@ -1,27 +1,52 @@
 from pathlib import Path
 import shutil
+import mimetypes
 
 import nbconvert
 import notedown
 from traitlets.config import Config
 
+from nbconvert_fix import ExtractOutputPreprocessor
+
+
 reader = notedown.MarkdownReader()
 
+mimetypes.add_type('application/vnd.plotly.v1+json', '.json')
+
 src = Path('src')
 target = Path('docs')
 shutil.rmtree(target, ignore_errors=True)
 target.mkdir(exist_ok=True)
 shutil.copytree(src / 'figures', target / 'figures')
 
+output_extractor = ExtractOutputPreprocessor()
+output_extractor.extract_output_types = (
+    output_extractor.extract_output_types
+    | {'application/vnd.plotly.v1+json'}
+)
+
 exporter = nbconvert.MarkdownExporter(
     config=Config(dict(
         MarkdownExporter=dict(
             preprocessors=[
                 nbconvert.preprocessors.ExecutePreprocessor,
-                nbconvert.preprocessors.ExtractOutputPreprocessor,
+                output_extractor,
             ],
-            exclude_input=True
-        )
+            exclude_input=True,
+            template_file='extra_markdown.tpl',
+        ),
+        NbConvertBase=dict(
+            display_data_priority=[
+                'text/html',
+                'text/markdown',
+                'image/svg+xml',
+                'text/latex',
+                'image/png',
+                'application/vnd.plotly.v1+json',
+                'image/jpeg',
+                'text/plain'
+            ]
+        ),
     ))
 )
 
diff --git a/extra_markdown.tpl b/extra_markdown.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..24c58ba066f31bf340d29cfa3292a9547c0fc776
--- /dev/null
+++ b/extra_markdown.tpl
@@ -0,0 +1,20 @@
+{% extends 'markdown.tpl' %}
+
+{% set plotly_counter = 1 %}
+
+{%- block data_other -%}
+{%- for type in output.data | filter_data_type -%}
+{%- if type == 'application/vnd.plotly.v1+json' -%}
+{% set plotly_url = output.metadata.filenames['application/vnd.plotly.v1+json'] | path2url %}
+<div id="plotly-{{plotly_counter}}"></div>
+
+<script>
+window.addEventListener('load', function() {
+  Plotly.d3.json('/{{plotly_url}}', function(error, fig) {
+    Plotly.plot('plotly-{{plotly_counter}}', fig.data, fig.layout);
+  });
+});
+</script>
+{%- endif -%}
+{%- endfor -%}
+{%- endblock -%}
diff --git a/mkdocs.yml b/mkdocs.yml
index 950a5bd42ae7cc3455a60f3bff0e2637711ad97b..9a93ea2cf403b79f0cc724bf552ab704f77d97e0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,5 +33,5 @@ markdown_extensions:
 
 extra_javascript:
   - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/MathJax.js?config=TeX-AMS_HTML'
-
+  - 'https://cdn.plot.ly/plotly-latest.min.js'
 copyright: "Copyright © 2017-2018 Delft University of Technology, CC-BY-SA-NC 4.0."
diff --git a/nbconvert_fix.py b/nbconvert_fix.py
new file mode 100644
index 0000000000000000000000000000000000000000..65f94101d05e9c70fb6ec63e974e8a9a51e269d7
--- /dev/null
+++ b/nbconvert_fix.py
@@ -0,0 +1,102 @@
+import sys
+import os
+import json
+from mimetypes import guess_extension
+from binascii import a2b_base64
+
+from nbformat.notebooknode import NotebookNode
+import nbconvert
+from nbconvert.preprocessors.extractoutput import guess_extension_without_jpe
+
+
+class ExtractOutputPreprocessor(nbconvert.preprocessors.ExtractOutputPreprocessor):
+    # Patches against gh-846 following
+    # https://github.com/jupyter/nbconvert/pull/847
+
+    def preprocess_cell(self, cell, resources, cell_index):
+        """
+        Apply a transformation on each cell,
+
+        Parameters
+        ----------
+        cell : NotebookNode cell
+            Notebook cell being processed
+        resources : dictionary
+            Additional resources used in the conversion process.  Allows
+            preprocessors to pass variables into the Jinja engine.
+        cell_index : int
+            Index of the cell being processed (see base.py)
+        """
+
+        #Get the unique key from the resource dict if it exists.  If it does not
+        #exist, use 'output' as the default.  Also, get files directory if it
+        #has been specified
+        unique_key = resources.get('unique_key', 'output')
+        output_files_dir = resources.get('output_files_dir', None)
+
+        #Make sure outputs key exists
+        if not isinstance(resources['outputs'], dict):
+            resources['outputs'] = {}
+
+        #Loop through all of the outputs in the cell
+        for index, out in enumerate(cell.get('outputs', [])):
+            if out.output_type not in {'display_data', 'execute_result'}:
+                continue
+            #Get the output in data formats that the template needs extracted
+            for mime_type in self.extract_output_types:
+                if mime_type in out.data:
+                    data = out.data[mime_type]
+
+                    # If data was JSON, it will become a NotebookNode at this point.
+                    if isinstance(data, NotebookNode):
+                        data = json.dumps(data)
+
+                    #Binary files are base64-encoded, SVG is already XML
+                    if mime_type in {'image/png', 'image/jpeg', 'application/pdf'}:
+                        # data is b64-encoded as text (str, unicode),
+                        # we want the original bytes
+                        data = a2b_base64(data)
+                    elif sys.platform == 'win32':
+                        data = data.replace('\n', '\r\n').encode("UTF-8")
+                    else:
+                        data = data.encode("UTF-8")
+
+                    ext = guess_extension_without_jpe(mime_type)
+                    if ext is None:
+                        ext = '.' + mime_type.rsplit('/')[-1]
+                    if out.metadata.get('filename', ''):
+                        filename = out.metadata['filename']
+                        if not filename.endswith(ext):
+                            filename+=ext
+                    else:
+                        filename = self.output_filename_template.format(
+                                    unique_key=unique_key,
+                                    cell_index=cell_index,
+                                    index=index,
+                                    extension=ext)
+
+                    # On the cell, make the figure available via
+                    #   cell.outputs[i].metadata.filenames['mime/type']
+                    # where
+                    #   cell.outputs[i].data['mime/type'] contains the data
+                    if output_files_dir is not None:
+                        filename = os.path.join(output_files_dir, filename)
+                    out.metadata.setdefault('filenames', {})
+                    out.metadata['filenames'][mime_type] = filename
+
+                    if filename in resources['outputs']:
+                        raise ValueError(
+                            "Your outputs have filename metadata associated "
+                            "with them. Nbconvert saves these outputs to "
+                            "external files using this filename metadata. "
+                            "Filenames need to be unique across the notebook, "
+                            "or images will be overwritten. The filename {} is "
+                            "associated with more than one output. The second "
+                            "output associated with this filename is in cell "
+                            "{}.".format(filename, cell_index)
+                            )
+                    #In the resources, make the figure available via
+                    #   resources['outputs']['filename'] = data
+                    resources['outputs'][filename] = data
+
+        return cell, resources
diff --git a/src/lecture_6.md b/src/lecture_6.md
index 7823068afc6f0db186b4c3f1d395c7e49301e677..d0f686ff6963af320b9aae30f5ab8d6f65a7079d 100644
--- a/src/lecture_6.md
+++ b/src/lecture_6.md
@@ -1,3 +1,11 @@
+```python
+import numpy as np
+import plotly.offline as py
+import plotly.graph_objs as go
+
+pi = np.pi
+```
+
 # Tight binding and nearly free electrons
 
 Let's summarize what we learned about electrons so far:
@@ -58,7 +66,7 @@ The band gaps open where two copies of the free electron dispersion cross.
 Let's focus on the first crossing. The momentum near it is $k = \pi/a + \delta k$ and we have two copies of the original band structure coming together. One with $\psi_+ \propto e^{i\pi x/a}$, another with $\psi_- \propto e^{-i\pi x/a}$. Near the crossing the wave function is the linear superposition of $\psi_+$ and $\psi_-$: $\psi = \alpha \psi_+ + \beta \psi_-$. We actually used almost the same form of the wave function in LCAO, except instead of $\psi_\pm$ we used the orbitals $\phi_1$ and $\phi_2$ there.
 
 Without the lattice potential we can approximate the Hamiltonian of these two states as follows:
-$$H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = 
+$$H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} =
 \begin{pmatrix} E_0 + v \hbar \delta k & 0 \\ 0 & E_0 - v \hbar \delta k\end{pmatrix}
 \begin{pmatrix}\alpha \\ \beta \end{pmatrix}.
 $$
@@ -73,7 +81,7 @@ Without $V(x)$ the two wave functions $\psi_+$ and $\psi_-$ are independent sinc
 So in presence of $V(x)$ the Hamiltonian becomes
 
 $$
-H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = 
+H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} =
 \begin{pmatrix} E_0 + v \hbar \delta k & W \\ W^* & E_0 - v \hbar \delta k\end{pmatrix}
 \begin{pmatrix}\alpha \\ \beta \end{pmatrix},
 $$
@@ -174,7 +182,74 @@ Sequence of steps (same procedure as in 1D, but harder because of the need to im
 
 The resulting band structure looks like this (in the extended Brillouin zone scheme):
 
-<iframe width="100%", height="600" src="../figures/nfem_2d.html" frameBorder="0" align="center" style="border:0;">Your browser still doesn't support iframes??</iframe>
+
+```python
+def E(k_x, k_y):
+    delta = np.array([-2*pi, 0, 2*pi])
+    H = np.diag(
+        ((k_x + delta)[:, np.newaxis]**2
+        + (k_y + delta)[np.newaxis]**2).flatten()
+    )
+    return tuple(np.linalg.eigvalsh(H + 5)[:3])
+
+E = np.vectorize(E, otypes=(float, float, float))
+
+momenta = np.linspace(-2*pi, 2*pi, 100)
+kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :]
+bands = E(kx, ky)
+
+# Extended Brillouin zone scheme
+pad = .3
+first_BZ = ((abs(kx) < pi + pad) & (abs(ky) < pi + pad))
+second_BZ = (
+    ((abs(kx) > pi - pad) | (abs(ky) > pi - pad))
+    & ((abs(kx + ky) < 2*pi + pad) & (abs(kx - ky) < 2*pi + pad))
+)
+third_BZ = (
+    (abs(kx + ky) > 2*pi - pad) | (abs(kx - ky) > 2*pi - pad)
+)
+
+bands[0][~first_BZ] = np.nan
+bands[1][~second_BZ] = np.nan
+#bands[2][~third_BZ] = np.nan
+
+# Actually plotting
+
+fig = go.Figure(
+    data = [
+        go.Surface(
+            z=band / 5,
+            # colorscale=color,
+            opacity=opacity,
+            showscale=False,
+            hoverinfo='none',
+            x=momenta,
+            y=momenta,
+        )
+        for band, opacity
+        in zip(bands[:2],
+               # ['#cf483d', '#3d88cf'],
+               (1, 0.9))
+    ],
+    layout = go.Layout(
+        title='Nearly free electrons in 2D',
+        autosize=True,
+        hovermode=False,
+        margin=dict(
+            t=50,
+            l=20,
+            r=20,
+            b=50,
+        ),
+        scene=dict(
+            yaxis={"title": "k_y"},
+            xaxis={"title": "k_x"},
+            zaxis={"title": "E"},
+        )
+    )
+)
+py.iplot(fig, show_link=False)
+```
 
 Observe that the top of the first band is above the bottom of the lowest band. Therefore if $V$ is sufficiently weak, the material can be conducting even with 2 electrons per unit cell!
 
@@ -183,7 +258,41 @@ Let's compare the almost parabolic dispersion of the nearly free electron model
 
 We now have a dispersion relation $E = E_0 + 2t(\cos k_x a + \cos k_y a)$, which looks like this:
 
-<iframe width="100%", height="600" src="../figures/tb_2d.html" frameBorder="0" align="center" style="border:0;">Your browser still doesn't support iframes??</iframe>
+```python
+momenta = np.linspace(-pi, pi, 100)
+kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :]
+energies = -np.cos(kx) - np.cos(ky)
+fig = go.Figure(
+    data = [
+        go.Surface(
+            z=energies,
+            # colorscale='#3d88cf',
+            opacity=1,
+            showscale=False,
+            hoverinfo='none',
+            x=momenta,
+            y=momenta,
+        )
+    ],
+    layout = go.Layout(
+        title='Tight-binding in 2D',
+        autosize=True,
+        hovermode=False,
+        margin=dict(
+            t=50,
+            l=20,
+            r=20,
+            b=50,
+        ),
+        scene=dict(
+            yaxis={"title": "k_y"},
+            xaxis={"title": "k_x"},
+            zaxis={"title": "E"},
+        )
+    )
+)
+py.iplot(fig, show_link=False)
+```
 
 ### Light adsorption